feat: releases

This commit is contained in:
Gab 2026-03-24 22:12:30 +11:00
parent d716f9a6d6
commit 2ae12f8d6b
10 changed files with 217 additions and 53 deletions

View File

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

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.0.0", "version": "1.0.2",
"name": "tfcode", "name": "tfcode",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",

View File

@ -6,22 +6,34 @@ import os from "os"
import { spawnSync } from "child_process" import { spawnSync } from "child_process"
const GITEA_HOST = process.env.TFCODE_GITEA_HOST || "gitea.toothfairyai.com" 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() { function detectPlatform() {
let platform let platform
switch (os.platform()) { switch (os.platform()) {
case "darwin": platform = "darwin"; break case "darwin":
case "linux": platform = "linux"; break platform = "darwin"
case "win32": platform = "windows"; break break
default: platform = os.platform() case "linux":
platform = "linux"
break
case "win32":
platform = "windows"
break
default:
platform = os.platform()
} }
let arch let arch
switch (os.arch()) { switch (os.arch()) {
case "x64": arch = "x64"; break case "x64":
case "arm64": arch = "arm64"; break arch = "x64"
default: arch = os.arch() break
case "arm64":
arch = "arm64"
break
default:
arch = os.arch()
} }
// Check for AVX2 on x64 // Check for AVX2 on x64
@ -170,9 +182,13 @@ async function main() {
// Install Python SDK // Install Python SDK
console.log("Installing ToothFairyAI Python SDK...") console.log("Installing ToothFairyAI Python SDK...")
const pipResult = spawnSync("python3", ["-m", "pip", "install", "--user", "--break-system-packages", "toothfairyai", "pydantic", "httpx", "rich"], { const pipResult = spawnSync(
stdio: "inherit" "python3",
}) ["-m", "pip", "install", "--user", "--break-system-packages", "toothfairyai", "pydantic", "httpx", "rich"],
{
stdio: "inherit",
},
)
if (pipResult.status === 0) { if (pipResult.status === 0) {
console.log("✓ Python SDK installed") console.log("✓ Python SDK installed")
@ -197,8 +213,8 @@ async function main() {
console.log("Quick Start:") console.log("Quick Start:")
console.log("") console.log("")
console.log(" 1. Set credentials:") console.log(" 1. Set credentials:")
console.log(" export TF_WORKSPACE_ID=\"your-workspace-id\"") console.log(' export TF_WORKSPACE_ID="your-workspace-id"')
console.log(" export TF_API_KEY=\"your-api-key\"") console.log(' export TF_API_KEY="your-api-key"')
console.log("") console.log("")
console.log(" 2. Validate:") console.log(" 2. Validate:")
console.log(" tfcode validate") console.log(" tfcode validate")

View File

@ -105,20 +105,45 @@ async function uploadToGitea() {
// Create main npm package // Create main npm package
async function createMainPackage() { async function createMainPackage() {
await $`mkdir -p ./dist/tfcode` await $`mkdir -p ./dist/tfcode/bin`
await $`cp -r ./bin ./dist/tfcode/bin`
await Bun.file(`./dist/tfcode/postinstall.mjs`).write(await Bun.file("./script/postinstall-tfcode.mjs").text()) 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()) 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( await Bun.file(`./dist/tfcode/package.json`).write(
JSON.stringify( JSON.stringify(
{ {
name: "@toothfairyai/tfcode", name: "@toothfairyai/tfcode",
version: TFCODE_VERSION, version: TFCODE_VERSION,
bin: { tfcode: "./bin/tfcode" }, bin: { tfcode: "./bin/tfcode.js" },
scripts: { postinstall: "node ./postinstall.mjs" }, scripts: { postinstall: "node ./postinstall.mjs" },
license: pkg.license, license: pkg.license,
optionalDependencies: binaries, optionalDependencies: binaries,
engines: { node: ">=18" },
homepage: "https://toothfairyai.com/developers/tfcode", homepage: "https://toothfairyai.com/developers/tfcode",
repository: { repository: {
type: "git", type: "git",

View File

@ -251,8 +251,15 @@ export namespace Agent {
return result return result
}) })
export async function get(agent: string) { export async function get(agent: string): Promise<Info> {
return state().then((x) => x[agent]) 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<Info[]> { async function loadTFCoderAgents(): Promise<Info[]> {
@ -264,17 +271,19 @@ export namespace Agent {
return data.tools return data.tools
.filter((t: any) => t.tool_type === "coder_agent") .filter((t: any) => t.tool_type === "coder_agent")
.map((t: any): Info => ({ .map(
name: t.name, (t: any): Info => ({
description: t.description, name: t.name,
mode: "primary" as const, description: t.description,
permission: Permission.fromConfig({ "*": "allow" }), mode: "primary" as const,
native: false, permission: Permission.fromConfig({ "*": "allow" }),
options: { native: false,
tf_agent_id: t.id, options: {
tf_auth_via: t.auth_via, tf_agent_id: t.id,
}, tf_auth_via: t.auth_via,
})) },
}),
)
} catch { } catch {
return [] return []
} }
@ -286,7 +295,7 @@ export namespace Agent {
const tfAgents = await loadTFCoderAgents() const tfAgents = await loadTFCoderAgents()
return pipe( return pipe(
{ ...localAgents, ...Object.fromEntries(tfAgents.map(a => [a.name, a])) }, { ...localAgents, ...Object.fromEntries(tfAgents.map((a) => [a.name, a])) },
values(), values(),
sortBy( sortBy(
[(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"], [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"],

View File

@ -58,10 +58,7 @@ function getPythonSyncPath(): string {
return "tf_sync" return "tf_sync"
} }
async function runPythonSync( async function runPythonSync(method: string, args: Record<string, unknown> = {}): Promise<unknown> {
method: string,
args: Record<string, unknown> = {},
): Promise<unknown> {
const credentials = await loadCredentials() const credentials = await loadCredentials()
const pythonCode = ` const pythonCode = `
@ -458,8 +455,7 @@ const ToolsCredentialsSetCommand = cmd({
const ToolsCommand = cmd({ const ToolsCommand = cmd({
command: "tools", command: "tools",
describe: "manage ToothFairyAI tools", describe: "manage ToothFairyAI tools",
builder: (yargs) => builder: (yargs) => yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).demandCommand(),
yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).demandCommand(),
async handler() {}, async handler() {},
}) })
@ -561,8 +557,106 @@ export const ToolsMainCommand = cmd({
command: "tools", command: "tools",
describe: "manage ToothFairyAI tools", describe: "manage ToothFairyAI tools",
builder: (yargs) => builder: (yargs) =>
yargs.command(ToolsListCommand).command(ToolsCredentialsSetCommand).command(ToolsDebugCommand).command(ToolsTestCommand).demandCommand(), yargs
.command(ToolsListCommand)
.command(ToolsCredentialsSetCommand)
.command(ToolsDebugCommand)
.command(ToolsTestCommand)
.demandCommand(),
async handler() {}, async handler() {},
}) })
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<string>((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<string>((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<string>((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 } export { ValidateCommand, SyncCommand, ToolsCommand }

View File

@ -11,6 +11,17 @@ interface ChangelogEntry {
} }
const CHANGELOG: 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", version: "1.0.0-beta",
date: "2026-03-24", date: "2026-03-24",

View File

@ -30,7 +30,7 @@ import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr" import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session" import { SessionCommand } from "./cli/cmd/session"
import { DbCommand } from "./cli/cmd/db" 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 { QuickstartCommand } from "./cli/cmd/quickstart"
import path from "path" import path from "path"
import { Global } from "./global" import { Global } from "./global"
@ -149,6 +149,7 @@ let cli = yargs(hideBin(process.argv))
.command(DbCommand) .command(DbCommand)
.command(ValidateCommand) .command(ValidateCommand)
.command(SyncCommand) .command(SyncCommand)
.command(SetupCommand)
.command(ToolsMainCommand) .command(ToolsMainCommand)
.command(QuickstartCommand) .command(QuickstartCommand)

View File

@ -115,10 +115,11 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
messages.push({ messages.push({
role: "assistant", role: "assistant",
content: text || null, content: text || undefined,
tool_calls: toolCalls.length > 0 ? toolCalls : undefined, tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
reasoning_text: reasoningOpaque ? reasoningText : undefined, reasoning_text: reasoningOpaque ? reasoningText : undefined,
reasoning_opaque: reasoningOpaque, reasoning_opaque: reasoningOpaque,
reasoning_content: reasoningText,
...metadata, ...metadata,
}) })

View File

@ -109,6 +109,13 @@ export namespace LLM {
mergeDeep(input.agent.options), mergeDeep(input.agent.options),
mergeDeep(variant), 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) { if (isOpenaiOauth) {
options.instructions = system.join("\n") options.instructions = system.join("\n")
} }