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": {
"name": "tfcode",
"version": "1.0.14",
"version": "1.0.16",
"bin": {
"tfcode": "./bin/tfcode",
},

View File

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

View File

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

View File

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

View File

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

View File

@ -558,7 +558,7 @@ export const dict = {
"sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
"sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"app.name.desktop": "OpenCode Desktop",
"app.name.desktop": "TF Code Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
"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.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.provider.viewAll": "Show more providers",
@ -571,7 +571,7 @@ export const dict = {
"session.revertDock.expand": "Expand rolled back messages",
"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.mainWithBranch": "Main branch ({{branch}})",
"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.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.server": "Server",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,16 +9,16 @@ import { app, BrowserWindow, dialog } from "electron"
import pkg from "electron-updater"
const APP_NAMES: Record<string, string> = {
dev: "OpenCode Dev",
beta: "OpenCode Beta",
prod: "OpenCode",
dev: "TF Code Dev",
beta: "TF Code Beta",
prod: "TF Code",
}
const APP_IDS: Record<string, string> = {
dev: "ai.opencode.desktop.dev",
beta: "ai.opencode.desktop.beta",
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"))
const { autoUpdater } = pkg

View File

@ -5,6 +5,9 @@ import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag"
import open from "open"
import { networkInterfaces } from "os"
import { existsSync } from "fs"
import path from "path"
import { $ } from "bun"
function getNetworkIPs() {
const nets = networkInterfaces()
@ -15,12 +18,8 @@ function getNetworkIPs() {
if (!net) continue
for (const netInfo of net) {
// Skip internal and non-IPv4 addresses
if (netInfo.internal || netInfo.family !== "IPv4") continue
// Skip Docker bridge networks (typically 172.x.x.x)
if (netInfo.address.startsWith("172.")) continue
results.push(netInfo.address)
}
}
@ -28,6 +27,18 @@ function getNetworkIPs() {
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({
command: "web",
builder: (yargs) => withNetworkOptions(yargs),
@ -36,6 +47,9 @@ export const WebCommand = cmd({
if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
await ensureAppBuilt()
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
UI.empty()
@ -43,11 +57,9 @@ export const WebCommand = cmd({
UI.empty()
if (opts.hostname === "0.0.0.0") {
// Show localhost for local access
const localhostUrl = `http://localhost:${server.port}`
UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl)
// Show network IPs for remote access
const networkIPs = getNetworkIPs()
if (networkIPs.length > 0) {
for (const ip of networkIPs) {
@ -67,7 +79,6 @@ export const WebCommand = cmd({
)
}
// Open localhost in browser
open(localhostUrl.toString()).catch(() => {})
} else {
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 { Hono } from "hono"
import { cors } from "hono/cors"
import { proxy } from "hono/proxy"
import { basicAuth } from "hono/basic-auth"
import z from "zod"
import path from "path"
import { Provider } from "../provider/provider"
import { NamedError } from "@opencode-ai/util/error"
import { LSP } from "../lsp"
@ -222,9 +222,9 @@ export namespace Server {
openAPIRouteHandler(app, {
documentation: {
info: {
title: "opencode",
title: "tfcode",
version: "0.0.3",
description: "opencode api",
description: "tfcode api",
},
openapi: "3.1.1",
},
@ -529,20 +529,33 @@ export namespace Server {
},
)
.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}`, {
...c.req,
headers: {
...c.req.raw.headers,
host: "app.opencode.ai",
},
})
response.headers.set(
"Content-Security-Policy",
"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:",
)
return response
let filePath = path.join(appDist, reqPath)
let file = Bun.file(filePath)
if (!(await file.exists())) {
filePath = path.join(publicDir, reqPath)
file = Bun.file(filePath)
}
if (!(await file.exists())) {
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 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(), {
documentation: {
info: {
title: "opencode",
title: "tfcode",
version: "1.0.0",
description: "opencode api",
description: "tfcode api",
},
openapi: "3.1.1",
},

View File

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

View File

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