From 2ae12f8d6b1d657a38fb072d8a143f5a5100bb6e Mon Sep 17 00:00:00 2001 From: Gab Date: Tue, 24 Mar 2026 22:12:30 +1100 Subject: [PATCH] feat: releases --- bun.lock | 2 +- packages/tfcode/package.json | 4 +- packages/tfcode/script/postinstall-tfcode.mjs | 54 ++++++--- packages/tfcode/script/publish-tfcode.ts | 31 ++++- packages/tfcode/src/agent/agent.ts | 41 ++++--- packages/tfcode/src/cli/cmd/tools.ts | 112 ++++++++++++++++-- .../src/cli/cmd/tui/ui/dialog-changelog.tsx | 13 +- packages/tfcode/src/index.ts | 3 +- ...vert-to-openai-compatible-chat-messages.ts | 3 +- packages/tfcode/src/session/llm.ts | 7 ++ 10 files changed, 217 insertions(+), 53 deletions(-) diff --git a/bun.lock b/bun.lock index 437df64d8..75999c631 100644 --- a/bun.lock +++ b/bun.lock @@ -381,7 +381,7 @@ }, "packages/tfcode": { "name": "tfcode", - "version": "1.0.0", + "version": "1.0.2", "bin": { "tfcode": "./bin/tfcode", }, diff --git a/packages/tfcode/package.json b/packages/tfcode/package.json index 2615b797b..b19038b2b 100644 --- a/packages/tfcode/package.json +++ b/packages/tfcode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.0", + "version": "1.0.2", "name": "tfcode", "type": "module", "license": "MIT", @@ -152,4 +152,4 @@ "overrides": { "drizzle-orm": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/tfcode/script/postinstall-tfcode.mjs b/packages/tfcode/script/postinstall-tfcode.mjs index c4386ab53..8699aaa1c 100644 --- a/packages/tfcode/script/postinstall-tfcode.mjs +++ b/packages/tfcode/script/postinstall-tfcode.mjs @@ -6,22 +6,34 @@ import os from "os" import { spawnSync } from "child_process" const GITEA_HOST = process.env.TFCODE_GITEA_HOST || "gitea.toothfairyai.com" -const GITEA_REPO = process.env.TFCODE_GITEA_REPO || "ToothFairyAI/tfcode" +const GITEA_REPO = process.env.TFCODE_GITEA_REPO || "ToothFairyAI/tf_code" function detectPlatform() { let platform switch (os.platform()) { - case "darwin": platform = "darwin"; break - case "linux": platform = "linux"; break - case "win32": platform = "windows"; break - default: platform = os.platform() + case "darwin": + platform = "darwin" + break + case "linux": + platform = "linux" + break + case "win32": + platform = "windows" + break + default: + platform = os.platform() } let arch switch (os.arch()) { - case "x64": arch = "x64"; break - case "arm64": arch = "arm64"; break - default: arch = os.arch() + case "x64": + arch = "x64" + break + case "arm64": + arch = "arm64" + break + default: + arch = os.arch() } // Check for AVX2 on x64 @@ -94,7 +106,7 @@ async function downloadBinary() { if (curlResult.status !== 0) { console.error(`Failed to download from ${url}`) console.error("Trying npm package fallback...") - + // Fallback to npm optionalDependencies try { const pkgName = `@toothfairyai/${target}` @@ -102,13 +114,13 @@ async function downloadBinary() { const pkgDir = path.dirname(pkgPath) const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode" const binaryPath = path.join(pkgDir, "bin", binaryName) - + if (fs.existsSync(binaryPath)) { setupBinary(binaryPath, platform) return } } catch {} - + process.exit(1) } @@ -167,13 +179,17 @@ async function main() { const result = spawnSync("python3", ["--version"], { encoding: "utf8" }) if (result.status === 0) { console.log(`✓ Found ${result.stdout.trim()}`) - + // Install Python SDK console.log("Installing ToothFairyAI Python SDK...") - const pipResult = spawnSync("python3", ["-m", "pip", "install", "--user", "--break-system-packages", "toothfairyai", "pydantic", "httpx", "rich"], { - stdio: "inherit" - }) - + const pipResult = spawnSync( + "python3", + ["-m", "pip", "install", "--user", "--break-system-packages", "toothfairyai", "pydantic", "httpx", "rich"], + { + stdio: "inherit", + }, + ) + if (pipResult.status === 0) { console.log("✓ Python SDK installed") } else { @@ -197,8 +213,8 @@ async function main() { console.log("Quick Start:") console.log("") console.log(" 1. Set credentials:") - console.log(" export TF_WORKSPACE_ID=\"your-workspace-id\"") - console.log(" export TF_API_KEY=\"your-api-key\"") + console.log(' export TF_WORKSPACE_ID="your-workspace-id"') + console.log(' export TF_API_KEY="your-api-key"') console.log("") console.log(" 2. Validate:") console.log(" tfcode validate") @@ -213,4 +229,4 @@ async function main() { console.log("") } -main().catch(console.error) \ No newline at end of file +main().catch(console.error) diff --git a/packages/tfcode/script/publish-tfcode.ts b/packages/tfcode/script/publish-tfcode.ts index 628375215..343e4dcba 100644 --- a/packages/tfcode/script/publish-tfcode.ts +++ b/packages/tfcode/script/publish-tfcode.ts @@ -105,20 +105,45 @@ async function uploadToGitea() { // Create main npm package async function createMainPackage() { - await $`mkdir -p ./dist/tfcode` - await $`cp -r ./bin ./dist/tfcode/bin` + await $`mkdir -p ./dist/tfcode/bin` await Bun.file(`./dist/tfcode/postinstall.mjs`).write(await Bun.file("./script/postinstall-tfcode.mjs").text()) await Bun.file(`./dist/tfcode/LICENSE`).write(await Bun.file("../../LICENSE").text()) + // Create a simple wrapper script that runs the installed binary + const wrapper = `#!/usr/bin/env node +const { spawn } = require('child_process') +const path = require('path') +const fs = require('fs') + +const binDir = path.join(__dirname, 'bin') +const binary = process.platform === 'win32' ? 'tfcode.exe' : 'tfcode' +const binaryPath = path.join(binDir, binary) + +if (!fs.existsSync(binaryPath)) { + console.error('tfcode binary not found. Run: npm install @toothfairyai/tfcode') + process.exit(1) +} + +const child = spawn(binaryPath, process.argv.slice(2), { + stdio: 'inherit', + env: process.env +}) + +child.on('exit', (code) => process.exit(code || 0)) +` + await Bun.file(`./dist/tfcode/bin/tfcode.js`).write(wrapper) + + // Create package.json await Bun.file(`./dist/tfcode/package.json`).write( JSON.stringify( { name: "@toothfairyai/tfcode", version: TFCODE_VERSION, - bin: { tfcode: "./bin/tfcode" }, + bin: { tfcode: "./bin/tfcode.js" }, scripts: { postinstall: "node ./postinstall.mjs" }, license: pkg.license, optionalDependencies: binaries, + engines: { node: ">=18" }, homepage: "https://toothfairyai.com/developers/tfcode", repository: { type: "git", diff --git a/packages/tfcode/src/agent/agent.ts b/packages/tfcode/src/agent/agent.ts index 8e7c3c303..3f846776d 100644 --- a/packages/tfcode/src/agent/agent.ts +++ b/packages/tfcode/src/agent/agent.ts @@ -251,8 +251,15 @@ export namespace Agent { return result }) - export async function get(agent: string) { - return state().then((x) => x[agent]) + export async function get(agent: string): Promise { + const localAgents = await state() + if (localAgents[agent]) return localAgents[agent] + + const tfAgents = await loadTFCoderAgents() + const tfAgent = tfAgents.find((a) => a.name === agent) + if (tfAgent) return tfAgent + + return localAgents.build } async function loadTFCoderAgents(): Promise { @@ -261,20 +268,22 @@ export namespace Agent { const content = await Bun.file(toolsPath).text() const data = JSON.parse(content) if (!data.success || !data.tools) return [] - + return data.tools .filter((t: any) => t.tool_type === "coder_agent") - .map((t: any): Info => ({ - name: t.name, - description: t.description, - mode: "primary" as const, - permission: Permission.fromConfig({ "*": "allow" }), - native: false, - options: { - tf_agent_id: t.id, - tf_auth_via: t.auth_via, - }, - })) + .map( + (t: any): Info => ({ + name: t.name, + description: t.description, + mode: "primary" as const, + permission: Permission.fromConfig({ "*": "allow" }), + native: false, + options: { + tf_agent_id: t.id, + tf_auth_via: t.auth_via, + }, + }), + ) } catch { return [] } @@ -284,9 +293,9 @@ export namespace Agent { const cfg = await Config.get() const localAgents = await state() const tfAgents = await loadTFCoderAgents() - + return pipe( - { ...localAgents, ...Object.fromEntries(tfAgents.map(a => [a.name, a])) }, + { ...localAgents, ...Object.fromEntries(tfAgents.map((a) => [a.name, a])) }, values(), sortBy( [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"], diff --git a/packages/tfcode/src/cli/cmd/tools.ts b/packages/tfcode/src/cli/cmd/tools.ts index a966739e3..7f3aeaf6a 100644 --- a/packages/tfcode/src/cli/cmd/tools.ts +++ b/packages/tfcode/src/cli/cmd/tools.ts @@ -58,12 +58,9 @@ function getPythonSyncPath(): string { return "tf_sync" } -async function runPythonSync( - method: string, - args: Record = {}, -): Promise { +async function runPythonSync(method: string, args: Record = {}): Promise { const credentials = await loadCredentials() - + const pythonCode = ` import json import sys @@ -458,8 +455,7 @@ const ToolsCredentialsSetCommand = cmd({ const ToolsCommand = cmd({ command: "tools", describe: "manage ToothFairyAI tools", - builder: (yargs) => - yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).demandCommand(), + builder: (yargs) => yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).demandCommand(), async handler() {}, }) @@ -561,8 +557,106 @@ export const ToolsMainCommand = cmd({ command: "tools", describe: "manage ToothFairyAI tools", builder: (yargs) => - yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).command(ToolsDebugCommand).command(ToolsTestCommand).demandCommand(), + yargs + .command(ToolsListCommand) + .command(ToolsCredentialsSetCommand) + .command(ToolsDebugCommand) + .command(ToolsTestCommand) + .demandCommand(), async handler() {}, }) -export { ValidateCommand, SyncCommand, ToolsCommand } \ No newline at end of file +export const SetupCommand = cmd({ + command: "setup", + describe: "configure ToothFairyAI credentials", + builder: (yargs) => + yargs + .option("api-key", { + type: "string", + describe: "API key", + }) + .option("workspace-id", { + type: "string", + describe: "Workspace ID", + }) + .option("region", { + type: "string", + describe: "Region (dev, au, eu, us)", + default: "au", + }), + handler: async (args) => { + const configPath = getConfigPath() + const credFile = getCredentialsFilePath() + + // Load existing credentials + let existing: { api_key?: string; workspace_id?: string; region?: string } = {} + if (await Filesystem.exists(credFile)) { + try { + existing = await Bun.file(credFile).json() + } catch {} + } + + // Get credentials from args or prompt + let apiKey = args["api-key"] + let workspaceId = args["workspace-id"] + let region = args.region || existing.region || "au" + + if (!apiKey || !workspaceId) { + info("") + info("ToothFairyAI Credentials Setup") + info("━".repeat(40)) + info("") + + if (!workspaceId) { + process.stdout.write(`Workspace ID [${existing.workspace_id || ""}]: `) + const input = await new Promise((resolve) => { + process.stdin.once("data", (data) => resolve(data.toString().trim())) + }) + workspaceId = input || existing.workspace_id + } + + if (!apiKey) { + process.stdout.write(`API Key [${existing.api_key ? "****" + existing.api_key.slice(-4) : ""}]: `) + const input = await new Promise((resolve) => { + process.stdin.once("data", (data) => resolve(data.toString().trim())) + }) + apiKey = input || existing.api_key + } + + process.stdout.write(`Region [${region}]: `) + const regionInput = await new Promise((resolve) => { + process.stdin.once("data", (data) => resolve(data.toString().trim())) + }) + if (regionInput) region = regionInput + } + + if (!apiKey || !workspaceId) { + printError("API key and workspace ID are required") + process.exitCode = 1 + return + } + + // Save credentials + await mkdir(configPath, { recursive: true }) + await Bun.write( + credFile, + JSON.stringify( + { + api_key: apiKey, + workspace_id: workspaceId, + region, + }, + null, + 2, + ), + ) + + success(`✓ Credentials saved to ${credFile}`) + info(` Workspace: ${workspaceId}`) + info(` Region: ${region}`) + info("") + info("Run 'tfcode validate' to test your credentials") + }, +}) + +export { ValidateCommand, SyncCommand, ToolsCommand } diff --git a/packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx b/packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx index f7a6dcb25..449a0a05e 100644 --- a/packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx +++ b/packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx @@ -11,6 +11,17 @@ interface ChangelogEntry { } const CHANGELOG: ChangelogEntry[] = [ + { + version: "1.0.2", + date: "2026-03-24", + changes: [ + "Fixed TF coder agents not loading correctly", + "Fixed content:null error in ToothFairyAI predictions API", + "Added reasoning_content support for thinking models", + "Added tfcode setup command to persist credentials", + "Removed TF-specific options for non-TF providers", + ], + }, { version: "1.0.0-beta", date: "2026-03-24", @@ -71,4 +82,4 @@ export function DialogChangelog() { ) -} \ No newline at end of file +} diff --git a/packages/tfcode/src/index.ts b/packages/tfcode/src/index.ts index 8ef3160d5..01fc16319 100644 --- a/packages/tfcode/src/index.ts +++ b/packages/tfcode/src/index.ts @@ -30,7 +30,7 @@ import { WebCommand } from "./cli/cmd/web" import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" import { DbCommand } from "./cli/cmd/db" -import { ValidateCommand, SyncCommand, ToolsMainCommand } from "./cli/cmd/tools" +import { ValidateCommand, SyncCommand, ToolsMainCommand, SetupCommand } from "./cli/cmd/tools" import { QuickstartCommand } from "./cli/cmd/quickstart" import path from "path" import { Global } from "./global" @@ -149,6 +149,7 @@ let cli = yargs(hideBin(process.argv)) .command(DbCommand) .command(ValidateCommand) .command(SyncCommand) + .command(SetupCommand) .command(ToolsMainCommand) .command(QuickstartCommand) diff --git a/packages/tfcode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts b/packages/tfcode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts index e1e3ed4c2..72e8e25df 100644 --- a/packages/tfcode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +++ b/packages/tfcode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts @@ -115,10 +115,11 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro messages.push({ role: "assistant", - content: text || null, + content: text || undefined, tool_calls: toolCalls.length > 0 ? toolCalls : undefined, reasoning_text: reasoningOpaque ? reasoningText : undefined, reasoning_opaque: reasoningOpaque, + reasoning_content: reasoningText, ...metadata, }) diff --git a/packages/tfcode/src/session/llm.ts b/packages/tfcode/src/session/llm.ts index a22c6d856..dad588be4 100644 --- a/packages/tfcode/src/session/llm.ts +++ b/packages/tfcode/src/session/llm.ts @@ -109,6 +109,13 @@ export namespace LLM { mergeDeep(input.agent.options), mergeDeep(variant), ) + + // Remove TF-specific options for non-ToothFairyAI providers + if (input.model.providerID !== "toothfairyai") { + delete options.tf_agent_id + delete options.tf_auth_via + } + if (isOpenaiOauth) { options.instructions = system.join("\n") }