feat: tfcode

This commit is contained in:
Gab 2026-03-27 17:09:02 +11:00
parent 95340af79a
commit cbcaa4ba46
22 changed files with 84 additions and 62 deletions

View File

@ -381,7 +381,7 @@
}, },
"packages/tfcode": { "packages/tfcode": {
"name": "tfcode", "name": "tfcode",
"version": "1.0.14", "version": "1.0.16",
"bin": { "bin": {
"tfcode": "./bin/tfcode", "tfcode": "./bin/tfcode",
}, },

View File

@ -542,7 +542,7 @@ export const dict = {
"sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات", "sidebar.project.viewAllSessions": "عرض جميع الجلسات",
"sidebar.project.clearNotifications": "مسح الإشعارات", "sidebar.project.clearNotifications": "مسح الإشعارات",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "سطح المكتب", "settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم", "settings.section.server": "الخادم",
"settings.tab.general": "عام", "settings.tab.general": "عام",

View File

@ -549,7 +549,7 @@ export const dict = {
"sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões", "sidebar.project.viewAllSessions": "Ver todas as sessões",
"sidebar.project.clearNotifications": "Limpar notificações", "sidebar.project.clearNotifications": "Limpar notificações",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Servidor", "settings.section.server": "Servidor",
"settings.tab.general": "Geral", "settings.tab.general": "Geral",

View File

@ -611,7 +611,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "Prikaži sve sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije",
"sidebar.project.clearNotifications": "Očisti obavijesti", "sidebar.project.clearNotifications": "Očisti obavijesti",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",

View File

@ -607,7 +607,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "Vis alle sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner",
"sidebar.project.clearNotifications": "Ryd notifikationer", "sidebar.project.clearNotifications": "Ryd notifikationer",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",
"settings.tab.general": "Generelt", "settings.tab.general": "Generelt",

View File

@ -558,7 +558,7 @@ export const dict = {
"sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
"sidebar.project.clearNotifications": "Benachrichtigungen löschen", "sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",
"settings.tab.general": "Allgemein", "settings.tab.general": "Allgemein",

View File

@ -119,7 +119,7 @@ export const dict = {
"dialog.model.manage.description": "Customize which models appear in the model selector.", "dialog.model.manage.description": "Customize which models appear in the model selector.",
"dialog.model.manage.provider.toggle": "Toggle all {{provider}} models", "dialog.model.manage.provider.toggle": "Toggle all {{provider}} models",
"dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode", "dialog.model.unpaid.freeModels.title": "Free models provided by TF Code",
"dialog.model.unpaid.addMore.title": "Add more models from popular providers", "dialog.model.unpaid.addMore.title": "Add more models from popular providers",
"dialog.provider.viewAll": "Show more providers", "dialog.provider.viewAll": "Show more providers",
@ -571,7 +571,7 @@ export const dict = {
"session.revertDock.expand": "Expand rolled back messages", "session.revertDock.expand": "Expand rolled back messages",
"session.revertDock.restore": "Restore message", "session.revertDock.restore": "Restore message",
"session.new.title": "Build anything", "session.new.title": "Build with TF Code",
"session.new.worktree.main": "Main branch", "session.new.worktree.main": "Main branch",
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})", "session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
"session.new.worktree.create": "Create new worktree", "session.new.worktree.create": "Create new worktree",
@ -710,7 +710,7 @@ export const dict = {
"debugBar.mem.tipUnavailable": "Used JS heap vs heap limit. Chromium only.", "debugBar.mem.tipUnavailable": "Used JS heap vs heap limit. Chromium only.",
"debugBar.mem.tip": "Used JS heap vs heap limit. {{used}} of {{limit}}.", "debugBar.mem.tip": "Used JS heap vs heap limit. {{used}} of {{limit}}.",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",

View File

@ -614,7 +614,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "Ver todas las sesiones", "sidebar.project.viewAllSessions": "Ver todas las sesiones",
"sidebar.project.clearNotifications": "Borrar notificaciones", "sidebar.project.clearNotifications": "Borrar notificaciones",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Escritorio", "settings.section.desktop": "Escritorio",
"settings.section.server": "Servidor", "settings.section.server": "Servidor",

View File

@ -556,7 +556,7 @@ export const dict = {
"sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions", "sidebar.project.viewAllSessions": "Voir toutes les sessions",
"sidebar.project.clearNotifications": "Effacer les notifications", "sidebar.project.clearNotifications": "Effacer les notifications",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Bureau", "settings.section.desktop": "Bureau",
"settings.section.server": "Serveur", "settings.section.server": "Serveur",
"settings.tab.general": "Général", "settings.tab.general": "Général",

View File

@ -546,7 +546,7 @@ export const dict = {
"sidebar.project.recentSessions": "最近のセッション", "sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示", "sidebar.project.viewAllSessions": "すべてのセッションを表示",
"sidebar.project.clearNotifications": "通知をクリア", "sidebar.project.clearNotifications": "通知をクリア",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "デスクトップ", "settings.section.desktop": "デスクトップ",
"settings.section.server": "サーバー", "settings.section.server": "サーバー",
"settings.tab.general": "一般", "settings.tab.general": "一般",

View File

@ -547,7 +547,7 @@ export const dict = {
"sidebar.project.recentSessions": "최근 세션", "sidebar.project.recentSessions": "최근 세션",
"sidebar.project.viewAllSessions": "모든 세션 보기", "sidebar.project.viewAllSessions": "모든 세션 보기",
"sidebar.project.clearNotifications": "알림 지우기", "sidebar.project.clearNotifications": "알림 지우기",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "데스크톱", "settings.section.desktop": "데스크톱",
"settings.section.server": "서버", "settings.section.server": "서버",
"settings.tab.general": "일반", "settings.tab.general": "일반",

View File

@ -614,7 +614,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "Vis alle sesjoner", "sidebar.project.viewAllSessions": "Vis alle sesjoner",
"sidebar.project.clearNotifications": "Fjern varsler", "sidebar.project.clearNotifications": "Fjern varsler",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Skrivebord", "settings.section.desktop": "Skrivebord",
"settings.section.server": "Server", "settings.section.server": "Server",

View File

@ -547,7 +547,7 @@ export const dict = {
"sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
"sidebar.project.clearNotifications": "Wyczyść powiadomienia", "sidebar.project.clearNotifications": "Wyczyść powiadomienia",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Pulpit", "settings.section.desktop": "Pulpit",
"settings.section.server": "Serwer", "settings.section.server": "Serwer",
"settings.tab.general": "Ogólne", "settings.tab.general": "Ogólne",

View File

@ -612,7 +612,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "Посмотреть все сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии",
"sidebar.project.clearNotifications": "Очистить уведомления", "sidebar.project.clearNotifications": "Очистить уведомления",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Приложение", "settings.section.desktop": "Приложение",
"settings.section.server": "Сервер", "settings.section.server": "Сервер",
"settings.tab.general": "Основные", "settings.tab.general": "Основные",

View File

@ -605,7 +605,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "เดสก์ท็อป", "settings.section.desktop": "เดสก์ท็อป",
"settings.section.server": "เซิร์ฟเวอร์", "settings.section.server": "เซิร์ฟเวอร์",

View File

@ -604,7 +604,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "查看全部会话", "sidebar.project.viewAllSessions": "查看全部会话",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "桌面", "settings.section.desktop": "桌面",
"settings.section.server": "服务器", "settings.section.server": "服务器",

View File

@ -601,7 +601,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "查看全部工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "桌面", "settings.section.desktop": "桌面",
"settings.section.server": "伺服器", "settings.section.server": "伺服器",
"settings.tab.general": "一般", "settings.tab.general": "一般",

View File

@ -9,16 +9,16 @@ import { app, BrowserWindow, dialog } from "electron"
import pkg from "electron-updater" import pkg from "electron-updater"
const APP_NAMES: Record<string, string> = { const APP_NAMES: Record<string, string> = {
dev: "OpenCode Dev", dev: "TF Code Dev",
beta: "OpenCode Beta", beta: "TF Code Beta",
prod: "OpenCode", prod: "TF Code",
} }
const APP_IDS: Record<string, string> = { const APP_IDS: Record<string, string> = {
dev: "ai.opencode.desktop.dev", dev: "ai.opencode.desktop.dev",
beta: "ai.opencode.desktop.beta", beta: "ai.opencode.desktop.beta",
prod: "ai.opencode.desktop", prod: "ai.opencode.desktop",
} }
app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "OpenCode Dev") app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "TF Code Dev")
app.setPath("userData", join(app.getPath("appData"), app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev")) app.setPath("userData", join(app.getPath("appData"), app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev"))
const { autoUpdater } = pkg const { autoUpdater } = pkg

View File

@ -5,6 +5,9 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag" import { Flag } from "../../flag/flag"
import open from "open" import open from "open"
import { networkInterfaces } from "os" import { networkInterfaces } from "os"
import { existsSync } from "fs"
import path from "path"
import { $ } from "bun"
function getNetworkIPs() { function getNetworkIPs() {
const nets = networkInterfaces() const nets = networkInterfaces()
@ -15,12 +18,8 @@ function getNetworkIPs() {
if (!net) continue if (!net) continue
for (const netInfo of net) { for (const netInfo of net) {
// Skip internal and non-IPv4 addresses
if (netInfo.internal || netInfo.family !== "IPv4") continue if (netInfo.internal || netInfo.family !== "IPv4") continue
// Skip Docker bridge networks (typically 172.x.x.x)
if (netInfo.address.startsWith("172.")) continue if (netInfo.address.startsWith("172.")) continue
results.push(netInfo.address) results.push(netInfo.address)
} }
} }
@ -28,6 +27,18 @@ function getNetworkIPs() {
return results return results
} }
async function ensureAppBuilt() {
const appDir = path.join(import.meta.dirname, "..", "..", "..", "..", "app")
const distDir = path.join(appDir, "dist")
if (!existsSync(distDir) || !existsSync(path.join(distDir, "index.html"))) {
UI.println(UI.Style.TEXT_INFO_BOLD + " Building web app...", UI.Style.TEXT_NORMAL)
const result = $`cd ${appDir} && bun run build`.quiet()
await result
UI.println(UI.Style.TEXT_SUCCESS_BOLD + " Build complete!", UI.Style.TEXT_NORMAL)
}
}
export const WebCommand = cmd({ export const WebCommand = cmd({
command: "web", command: "web",
builder: (yargs) => withNetworkOptions(yargs), builder: (yargs) => withNetworkOptions(yargs),
@ -36,6 +47,9 @@ export const WebCommand = cmd({
if (!Flag.OPENCODE_SERVER_PASSWORD) { if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
} }
await ensureAppBuilt()
const opts = await resolveNetworkOptions(args) const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts) const server = Server.listen(opts)
UI.empty() UI.empty()
@ -43,11 +57,9 @@ export const WebCommand = cmd({
UI.empty() UI.empty()
if (opts.hostname === "0.0.0.0") { if (opts.hostname === "0.0.0.0") {
// Show localhost for local access
const localhostUrl = `http://localhost:${server.port}` const localhostUrl = `http://localhost:${server.port}`
UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl) UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl)
// Show network IPs for remote access
const networkIPs = getNetworkIPs() const networkIPs = getNetworkIPs()
if (networkIPs.length > 0) { if (networkIPs.length > 0) {
for (const ip of networkIPs) { for (const ip of networkIPs) {
@ -67,7 +79,6 @@ export const WebCommand = cmd({
) )
} }
// Open localhost in browser
open(localhostUrl.toString()).catch(() => {}) open(localhostUrl.toString()).catch(() => {})
} else { } else {
const displayUrl = server.url.toString() const displayUrl = server.url.toString()

View File

@ -2,9 +2,9 @@ import { Log } from "../util/log"
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi" import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
import { Hono } from "hono" import { Hono } from "hono"
import { cors } from "hono/cors" import { cors } from "hono/cors"
import { proxy } from "hono/proxy"
import { basicAuth } from "hono/basic-auth" import { basicAuth } from "hono/basic-auth"
import z from "zod" import z from "zod"
import path from "path"
import { Provider } from "../provider/provider" import { Provider } from "../provider/provider"
import { NamedError } from "@opencode-ai/util/error" import { NamedError } from "@opencode-ai/util/error"
import { LSP } from "../lsp" import { LSP } from "../lsp"
@ -222,9 +222,9 @@ export namespace Server {
openAPIRouteHandler(app, { openAPIRouteHandler(app, {
documentation: { documentation: {
info: { info: {
title: "opencode", title: "tfcode",
version: "0.0.3", version: "0.0.3",
description: "opencode api", description: "tfcode api",
}, },
openapi: "3.1.1", openapi: "3.1.1",
}, },
@ -529,20 +529,33 @@ export namespace Server {
}, },
) )
.all("/*", async (c) => { .all("/*", async (c) => {
const path = c.req.path const reqPath = c.req.path === "/" ? "/index.html" : c.req.path
const appDist = path.join(import.meta.dirname, "..", "..", "..", "app", "dist")
const publicDir = path.join(import.meta.dirname, "..", "..", "..", "app", "public")
const response = await proxy(`https://app.opencode.ai${path}`, { let filePath = path.join(appDist, reqPath)
...c.req, let file = Bun.file(filePath)
headers: {
...c.req.raw.headers, if (!(await file.exists())) {
host: "app.opencode.ai", filePath = path.join(publicDir, reqPath)
}, file = Bun.file(filePath)
}) }
response.headers.set(
"Content-Security-Policy", if (!(await file.exists())) {
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:", filePath = path.join(appDist, "index.html")
) file = Bun.file(filePath)
}
if (await file.exists()) {
const response = new Response(file)
const contentType = file.type
if (contentType) {
response.headers.set("Content-Type", contentType)
}
return response return response
}
return c.text("Not Found. Run 'bun run build' in packages/app first.", 404)
}) })
} }
@ -551,9 +564,9 @@ export namespace Server {
const result = await generateSpecs(Default(), { const result = await generateSpecs(Default(), {
documentation: { documentation: {
info: { info: {
title: "opencode", title: "tfcode",
version: "1.0.0", version: "1.0.0",
description: "opencode api", description: "tfcode api",
}, },
openapi: "3.1.1", openapi: "3.1.1",
}, },

View File

@ -35,7 +35,7 @@ export const Logo = (props: { class?: string }) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 234 42" viewBox="0 0 180 42"
fill="none" fill="none"
classList={{ [props.class ?? ""]: !!props.class }} classList={{ [props.class ?? ""]: !!props.class }}
> >
@ -44,18 +44,16 @@ export const Logo = (props: { class?: string }) => {
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="var(--icon-base)" /> <path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="var(--icon-base)" />
<path d="M48 30H36V18H48V30Z" fill="var(--icon-weak-base)" /> <path d="M48 30H36V18H48V30Z" fill="var(--icon-weak-base)" />
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="var(--icon-base)" /> <path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="var(--icon-base)" />
<path d="M84 24V30H66V24H84Z" fill="var(--icon-weak-base)" /> <text
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="var(--icon-base)" /> x="66"
<path d="M108 36H96V18H108V36Z" fill="var(--icon-weak-base)" /> y="32"
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="var(--icon-base)" /> font-size="28"
<path d="M144 30H126V18H144V30Z" fill="var(--icon-weak-base)" /> font-weight="600"
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="var(--icon-strong-base)" /> fill="var(--icon-strong-base)"
<path d="M168 30H156V18H168V30Z" fill="var(--icon-weak-base)" /> font-family="var(--font-family-sans)"
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="var(--icon-strong-base)" /> >
<path d="M198 30H186V18H198V30Z" fill="var(--icon-weak-base)" /> Code
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="var(--icon-strong-base)" /> </text>
<path d="M234 24V30H216V24H234Z" fill="var(--icon-weak-base)" />
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="var(--icon-strong-base)" />
</g> </g>
</svg> </svg>
) )

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://opencode.ai/desktop-theme.json", "$schema": "https://opencode.ai/desktop-theme.json",
"name": "OpenCode", "name": "TF Code",
"id": "opencode", "id": "opencode",
"light": { "light": {
"palette": { "palette": {