diff --git a/bun.lock b/bun.lock
index a93a90f1a..71376a901 100644
--- a/bun.lock
+++ b/bun.lock
@@ -381,7 +381,7 @@
},
"packages/tfcode": {
"name": "@toothfairyai/tfcode",
- "version": "1.0.33",
+ "version": "1.0.36",
"bin": {
"tfcode": "./bin/tfcode",
},
diff --git a/packages/app/index.html b/packages/app/index.html
index 8a79aed2d..70d9f1f18 100644
--- a/packages/app/index.html
+++ b/packages/app/index.html
@@ -4,10 +4,7 @@
TF Code
-
-
-
-
+
diff --git a/packages/app/public/favicon.png b/packages/app/public/favicon.png
new file mode 100755
index 000000000..c640e5404
Binary files /dev/null and b/packages/app/public/favicon.png differ
diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx
index b5cbed6e7..c4abdb33b 100644
--- a/packages/app/src/entry.tsx
+++ b/packages/app/src/entry.tsx
@@ -67,7 +67,7 @@ const notify: Platform["notify"] = async (title, description, href) => {
const notification = new Notification(title, {
body: description ?? "",
- icon: "https://opencode.ai/favicon-96x96-v3.png",
+ icon: "/favicon.png",
})
notification.onclick = () => {
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx
index a9627c5db..9226a36ab 100644
--- a/packages/app/src/pages/layout/sidebar-items.tsx
+++ b/packages/app/src/pages/layout/sidebar-items.tsx
@@ -43,9 +43,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
= 3 && minor >= 10) {
- return { cmd, version: result.trim() };
+ return { cmd, version: result.trim() }
}
}
} catch {}
}
-
- return null;
+
+ return null
}
function installPythonDeps(pythonCmd) {
return new Promise((resolve, reject) => {
- const packages = ['toothfairyai', 'pydantic', 'httpx', 'rich'];
-
- log(`${DIM}Installing Python packages: ${packages.join(', ')}...${RESET}`);
-
+ const packages = ["toothfairyai", "pydantic", "httpx", "rich"]
+
+ log(`${DIM}Installing Python packages: ${packages.join(", ")}...${RESET}`)
+
// Try with --user first, then --break-system-packages if needed
- const args = ['-m', 'pip', 'install', '--user', ...packages];
-
+ const args = ["-m", "pip", "install", "--user", ...packages]
+
const proc = spawn(pythonCmd, args, {
- stdio: 'inherit',
- shell: process.platform === 'win32'
- });
-
- proc.on('close', (code) => {
+ stdio: "inherit",
+ shell: process.platform === "win32",
+ })
+
+ proc.on("close", (code) => {
if (code === 0) {
- resolve();
+ resolve()
} else {
// Try with --break-system-packages flag
- log(`${DIM}Retrying with --break-system-packages...${RESET}`);
- const retryArgs = ['-m', 'pip', 'install', '--user', '--break-system-packages', ...packages];
+ log(`${DIM}Retrying with --break-system-packages...${RESET}`)
+ const retryArgs = ["-m", "pip", "install", "--user", "--break-system-packages", ...packages]
const retry = spawn(pythonCmd, retryArgs, {
- stdio: 'inherit',
- shell: process.platform === 'win32'
- });
-
- retry.on('close', (retryCode) => {
+ stdio: "inherit",
+ shell: process.platform === "win32",
+ })
+
+ retry.on("close", (retryCode) => {
if (retryCode === 0) {
- resolve();
+ resolve()
} else {
- reject(new Error(`pip install exited with code ${retryCode}`));
+ reject(new Error(`pip install exited with code ${retryCode}`))
}
- });
-
- retry.on('error', (err) => {
- reject(err);
- });
+ })
+
+ retry.on("error", (err) => {
+ reject(err)
+ })
}
- });
-
- proc.on('error', (err) => {
- reject(err);
- });
- });
+ })
+
+ proc.on("error", (err) => {
+ reject(err)
+ })
+ })
+}
+
+function copyAppDir() {
+ let platform
+ switch (process.platform) {
+ case "darwin":
+ platform = "darwin"
+ break
+ case "linux":
+ platform = "linux"
+ break
+ case "win32":
+ platform = "windows"
+ break
+ default:
+ platform = process.platform
+ }
+
+ let arch
+ switch (process.arch) {
+ case "x64":
+ arch = "x64"
+ break
+ case "arm64":
+ arch = "arm64"
+ break
+ default:
+ arch = process.arch
+ }
+
+ const pkgName = `@toothfairyai/tfcode-${platform}-${arch}`
+ const appDir = path.join(__dirname, "app")
+
+ if (fs.existsSync(path.join(appDir, "dist", "index.html"))) {
+ logSuccess("Web app already present")
+ return
+ }
+
+ try {
+ const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`))
+ const srcAppDir = path.join(pkgDir, "bin", "app")
+
+ if (!fs.existsSync(path.join(srcAppDir, "dist", "index.html"))) {
+ logWarning("Web app dist not found in platform package")
+ return
+ }
+
+ fs.cpSync(srcAppDir, appDir, { recursive: true })
+ logSuccess("Web app copied from platform package")
+ } catch (e) {
+ logWarning(`Could not copy web app: ${e.message}`)
+ }
}
async function main() {
- log('');
- log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
- log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`);
- log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
- log('');
-
+ log("")
+ log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
+ log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`)
+ log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
+ log("")
+
+ copyAppDir()
+
// Check for Python
- logInfo('Checking Python installation...');
- const python = checkPython();
-
+ logInfo("Checking Python installation...")
+ const python = checkPython()
+
if (!python) {
- log('');
- logError('Python 3.10+ is required but not found.');
- log('');
- log(`${BOLD}Please install Python 3.10 or later:${RESET}`);
- log('');
- log(` ${CYAN}macOS:${RESET} brew install python@3.12`);
- log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`);
- log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`);
- log('');
- log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`);
- log('');
- process.exit(1);
+ log("")
+ logError("Python 3.10+ is required but not found.")
+ log("")
+ log(`${BOLD}Please install Python 3.10 or later:${RESET}`)
+ log("")
+ log(` ${CYAN}macOS:${RESET} brew install python@3.12`)
+ log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`)
+ log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`)
+ log("")
+ log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`)
+ log("")
+ process.exit(1)
}
-
- logSuccess(`Found ${python.version} (${python.cmd})`);
- log('');
-
+
+ logSuccess(`Found ${python.version} (${python.cmd})`)
+ log("")
+
// Install Python dependencies
- logInfo('Installing ToothFairyAI Python SDK...');
+ logInfo("Installing ToothFairyAI Python SDK...")
try {
- await installPythonDeps(python.cmd);
- logSuccess('Python dependencies installed');
+ await installPythonDeps(python.cmd)
+ logSuccess("Python dependencies installed")
} catch (err) {
- logWarning(`Failed to install Python dependencies: ${err.message}`);
- log('');
- log(`${DIM}You can install them manually with:${RESET}`);
- log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`);
- log('');
+ logWarning(`Failed to install Python dependencies: ${err.message}`)
+ log("")
+ log(`${DIM}You can install them manually with:${RESET}`)
+ log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`)
+ log("")
// Don't exit - user might install manually later
}
-
- log('');
- log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
- log(`${GREEN}✓ tfcode installed successfully!${RESET}`);
- log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
- log('');
- log(`${BOLD}Quick Start:${RESET}`);
- log('');
- log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`);
- log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`);
- log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`);
- log('');
- log(` ${CYAN}2.${RESET} Validate your credentials:`);
- log(` ${DIM}tfcode validate${RESET}`);
- log('');
- log(` ${CYAN}3.${RESET} Sync tools from your workspace:`);
- log(` ${DIM}tfcode sync${RESET}`);
- log('');
- log(` ${CYAN}4.${RESET} Start coding:`);
- log(` ${DIM}tfcode${RESET}`);
- log('');
- log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`);
- log('');
+
+ log("")
+ log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
+ log(`${GREEN}✓ tfcode installed successfully!${RESET}`)
+ log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
+ log("")
+ log(`${BOLD}Quick Start:${RESET}`)
+ log("")
+ log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`)
+ log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`)
+ log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`)
+ log("")
+ log(` ${CYAN}2.${RESET} Validate your credentials:`)
+ log(` ${DIM}tfcode validate${RESET}`)
+ log("")
+ log(` ${CYAN}3.${RESET} Sync tools from your workspace:`)
+ log(` ${DIM}tfcode sync${RESET}`)
+ log("")
+ log(` ${CYAN}4.${RESET} Start coding:`)
+ log(` ${DIM}tfcode${RESET}`)
+ log("")
+ log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`)
+ log("")
}
main().catch((err) => {
- logError(`Installation failed: ${err.message}`);
- process.exit(1);
-});
\ No newline at end of file
+ logError(`Installation failed: ${err.message}`)
+ process.exit(1)
+})
diff --git a/packages/tfcode/src/cli/cmd/serve.ts b/packages/tfcode/src/cli/cmd/serve.ts
index 13a10d664..7568d46a5 100644
--- a/packages/tfcode/src/cli/cmd/serve.ts
+++ b/packages/tfcode/src/cli/cmd/serve.ts
@@ -11,9 +11,6 @@ export const ServeCommand = cmd({
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless tfcode server",
handler: async (args) => {
- if (!Flag.OPENCODE_SERVER_PASSWORD) {
- console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
- }
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
console.log(`tfcode server listening on http://${server.hostname}:${server.port}`)
diff --git a/packages/tfcode/src/cli/cmd/web.ts b/packages/tfcode/src/cli/cmd/web.ts
index 68cf569cb..a78c87365 100644
--- a/packages/tfcode/src/cli/cmd/web.ts
+++ b/packages/tfcode/src/cli/cmd/web.ts
@@ -27,8 +27,22 @@ function getNetworkIPs() {
return results
}
+function findAppDir(): string {
+ const binDir = path.dirname(process.execPath)
+ const candidates = [
+ path.join(binDir, "app"),
+ path.join(binDir, "..", "app"),
+ path.join(import.meta.dirname, "..", "app"),
+ path.join(import.meta.dirname, "..", "..", "..", "..", "app"),
+ ]
+ for (const dir of candidates) {
+ if (existsSync(path.join(dir, "dist", "index.html"))) return dir
+ }
+ return candidates[0]
+}
+
async function ensureAppBuilt() {
- const appDir = path.join(import.meta.dirname, "..", "..", "..", "..", "app")
+ const appDir = findAppDir()
const distDir = path.join(appDir, "dist")
if (!existsSync(distDir) || !existsSync(path.join(distDir, "index.html"))) {
@@ -44,10 +58,6 @@ export const WebCommand = cmd({
builder: (yargs) => withNetworkOptions(yargs),
describe: "start tfcode server and open web interface",
handler: async (args) => {
- 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)
diff --git a/packages/tfcode/src/server/server.ts b/packages/tfcode/src/server/server.ts
index 8cd43dbca..82ca4c3ce 100644
--- a/packages/tfcode/src/server/server.ts
+++ b/packages/tfcode/src/server/server.ts
@@ -5,6 +5,7 @@ import { cors } from "hono/cors"
import { basicAuth } from "hono/basic-auth"
import z from "zod"
import path from "path"
+import { existsSync } from "fs"
import { Provider } from "../provider/provider"
import { NamedError } from "@opencode-ai/util/error"
import { LSP } from "../lsp"
@@ -530,8 +531,15 @@ export namespace Server {
)
.all("/*", async (c) => {
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 binDir = path.dirname(process.execPath)
+ const appDistCandidates = [
+ path.join(binDir, "app", "dist"),
+ path.join(binDir, "..", "app", "dist"),
+ path.join(import.meta.dirname, "..", "app", "dist"),
+ path.join(import.meta.dirname, "..", "..", "..", "app", "dist"),
+ ]
+ const appDist = appDistCandidates.find((d) => existsSync(path.join(d, "index.html"))) ?? appDistCandidates[0]
+ const publicDir = path.join(appDist, "..", "public")
let filePath = path.join(appDist, reqPath)
let file = Bun.file(filePath)
diff --git a/packages/ui/src/assets/favicon.png b/packages/ui/src/assets/favicon.png
new file mode 100755
index 000000000..c640e5404
Binary files /dev/null and b/packages/ui/src/assets/favicon.png differ
diff --git a/packages/ui/src/components/logo.css b/packages/ui/src/components/logo.css
index a909782b7..fa5428c8e 100644
--- a/packages/ui/src/components/logo.css
+++ b/packages/ui/src/components/logo.css
@@ -1,4 +1,13 @@
[data-component="logo-mark"] {
width: 16px;
- aspect-ratio: 4/5;
+ aspect-ratio: 137/120;
+ object-fit: contain;
+}
+
+[data-component="logo-splash"] {
+ object-fit: contain;
+}
+
+[data-component="logo-full"] {
+ object-fit: contain;
}
diff --git a/packages/ui/src/components/logo.tsx b/packages/ui/src/components/logo.tsx
index 14b437a5e..a85976f2d 100644
--- a/packages/ui/src/components/logo.tsx
+++ b/packages/ui/src/components/logo.tsx
@@ -1,60 +1,39 @@
import { ComponentProps } from "solid-js"
+import tfIcon from "../assets/favicon.png"
export const Mark = (props: { class?: string }) => {
return (
-
+ src={tfIcon}
+ alt="ToothFairyAI"
+ draggable={false}
+ />
)
}
-export const Splash = (props: Pick, "ref" | "class">) => {
+export const Splash = (props: Pick, "ref" | "class">) => {
return (
-
+ src={tfIcon}
+ alt="ToothFairyAI"
+ draggable={false}
+ />
)
}
export const Logo = (props: { class?: string }) => {
return (
-
+ src={tfIcon}
+ alt="ToothFairyAI"
+ draggable={false}
+ />
)
}