feat: tfcode

This commit is contained in:
Gab 2026-03-24 23:27:38 +11:00
parent 2ae12f8d6b
commit 3fe808d64c
6 changed files with 157 additions and 10 deletions

90
PROGRESS.md Normal file
View File

@ -0,0 +1,90 @@
# tfcode Progress Summary
## Goal
Build and deploy **tfcode** - ToothFairyAI's official AI coding agent, a fork of opencode.
## Completed Tasks
1. ✅ Implemented ToothFairyAI branding (logo, colors, strings)
2. ✅ Created ToothFairyAI provider with dynamic model fetching
3. ✅ Synced tools/agents/skills from ToothFairyAI workspace
4. ✅ Filtered models to only `deploymentType: "serverless"`
5. ✅ Implemented the `/predictions` endpoint for ToothFairyAI provider
6. ✅ Added `tfcode setup` command to persist credentials
7. ✅ Fixed npm installation flow (v1.0.4)
- Binary now included in main npm package
- Postinstall script checks if binary exists before downloading
- ESM-compatible module resolution for npm fallback
- Wrapper script fixed to look for binary in correct location
## Current Status
**Working**: npm installation flow is now fully functional. Users can install via:
```bash
npm install @toothfairyai/tfcode
```
The binary is included in the main package, making installation instant without needing to download from Gitea or copy from optionalDependencies.
## Next Steps
1. Build all platform binaries (12 targets: linux/darwin/windows × arm64/x64 × baseline/musl)
2. Test on different platforms (currently only darwin-arm64 is tested)
3. Create Gitea releases for backup download mechanism
4. Consider making Gitea repo public for transparency
## Technical Details
### npm Package Structure
The published package includes:
- `bin/tfcode` - The actual binary (92MB)
- `bin/tfcode.js` - Wrapper script that spawns the binary
- `postinstall.mjs` - Installation script (checks for binary, downloads if missing, installs Python SDK)
- `package.json` - Package metadata
- `LICENSE` - MIT license
### Installation Flow
1. npm installs main package + optionalDependencies
2. npm extracts files to `node_modules/@toothfairyai/tfcode/`
3. postinstall runs:
- Checks if binary already exists (✓ for darwin-arm64)
- If not, tries to download from Gitea
- If that fails, copies from optionalDependencies
- Installs Python SDK for TF integration
4. User can run `tfcode` command
### Key Fixes in v1.0.4
1. **Postinstall script**: Fixed duplicate variable declaration (`binaryName`)
2. **Postinstall script**: Added early return if binary already exists
3. **Postinstall script**: Fixed ESM module resolution (replaced `require.resolve` with `import.meta.resolve`)
4. **Publish script**: Copy current platform binary to main package
5. **Wrapper script**: Fixed binary path (removed extra `bin/` directory)
## Testing
```bash
# Fresh installation test
rm -rf /tmp/tfcode-test && mkdir /tmp/tfcode-test
cd /tmp/tfcode-test && npm init -y
npm install @toothfairyai/tfcode
node_modules/.bin/tfcode --version # Should output: 1.0.4
```
## Version History
- **1.0.4**: Fixed npm installation flow, binary included in main package
- **1.0.3**: Initial npm publication (broken installation)
- **1.0.2**: Earlier testing versions
- **1.0.0**: Initial release
## Credentials (Testing Only)
- workspace_id: `6586b7e6-683e-4ee6-a6cf-24c19729b5ff`
- api_key: `EWZooLROIS57EVW3BKGu7Pv6LNe4D6m4gkDjukx3`
- region: `dev`

View File

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

View File

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

View File

@ -4,6 +4,10 @@ import fs from "fs"
import path from "path"
import os from "os"
import { spawnSync } from "child_process"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const GITEA_HOST = process.env.TFCODE_GITEA_HOST || "gitea.toothfairyai.com"
const GITEA_REPO = process.env.TFCODE_GITEA_REPO || "ToothFairyAI/tf_code"
@ -82,7 +86,17 @@ async function downloadBinary() {
const { platform, arch, needsBaseline, abi } = detectPlatform()
const version = await getVersion()
// Build filename
// Check if binary already exists (included in npm package)
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
const binDir = path.join(__dirname, "bin")
const existingBinary = path.join(binDir, binaryName)
if (fs.existsSync(existingBinary)) {
console.log(`✓ Binary already exists at ${existingBinary}`)
return
}
// Build filename for download
let target = `tfcode-${platform}-${arch}`
if (needsBaseline) target += "-baseline"
if (abi) target += `-${abi}`
@ -94,7 +108,6 @@ async function downloadBinary() {
console.log(`Downloading tfcode v${version} for ${target}...`)
// Download
const binDir = path.join(__dirname, "bin")
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true })
const tmpDir = path.join(os.tmpdir(), `tfcode-install-${process.pid}`)
@ -110,17 +123,42 @@ async function downloadBinary() {
// Fallback to npm optionalDependencies
try {
const pkgName = `@toothfairyai/${target}`
const pkgPath = require.resolve(`${pkgName}/package.json`)
const pkgDir = path.dirname(pkgPath)
// ESM-compatible module resolution
let pkgDir
try {
const pkgUrl = import.meta.resolve(`${pkgName}/package.json`)
const pkgPath = fileURLToPath(pkgUrl)
pkgDir = path.dirname(pkgPath)
} catch (e) {
console.error(`Could not resolve ${pkgName}:`, e.message)
throw e
}
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
const binaryPath = path.join(pkgDir, "bin", binaryName)
console.log(`Looking for binary at: ${binaryPath}`)
if (fs.existsSync(binaryPath)) {
console.log(`Found binary at npm package location`)
setupBinary(binaryPath, platform)
return
} else {
console.error(`Binary not found at ${binaryPath}`)
}
} catch {}
} catch (e) {
console.error("npm package fallback failed:", e.message)
}
console.error("")
console.error("Installation failed. The binary could not be downloaded or found.")
console.error("")
console.error("Possible solutions:")
console.error(" 1. If this is a private installation, set TFCODE_GITEA_HOST to an accessible host")
console.error(" 2. Manually download the binary and place it in the bin/ directory")
console.error(" 3. Contact ToothFairyAI support for assistance")
console.error("")
process.exit(1)
}
@ -133,7 +171,6 @@ async function downloadBinary() {
}
// Move binary
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
const extractedBinary = path.join(tmpDir, "tfcode")
const targetBinary = path.join(binDir, binaryName)
@ -174,6 +211,9 @@ async function main() {
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
console.log("")
// Download and setup binary
await downloadBinary()
// Check for Python (needed for TF integration)
try {
const result = spawnSync("python3", ["--version"], { encoding: "utf8" })

View File

@ -109,15 +109,29 @@ async function createMainPackage() {
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())
// Copy the current platform's binary to the main package
// This makes installation faster (no need to download or copy from optionalDependencies)
const currentPlatform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : "windows"
const currentArch = process.arch
const platformBinDir = `./dist/tfcode-${currentPlatform}-${currentArch}/bin`
const binaryName = process.platform === "win32" ? "tfcode.exe" : "tfcode"
if (fs.existsSync(`${platformBinDir}/${binaryName}`)) {
console.log(`Including ${currentPlatform}-${currentArch} binary in main package`)
await $`cp ${platformBinDir}/${binaryName} ./dist/tfcode/bin/`
} else {
console.log(`Warning: No binary found for current platform (${currentPlatform}-${currentArch})`)
console.log(`The postinstall script will need to download or copy from optionalDependencies`)
}
// 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)
const binaryPath = path.join(__dirname, binary)
if (!fs.existsSync(binaryPath)) {
console.error('tfcode binary not found. Run: npm install @toothfairyai/tfcode')

View File

@ -348,6 +348,9 @@ export namespace ProviderTransform {
)
return {}
// ToothFairyAI doesn't support thinking/reasoning parameters yet
if (model.api.npm === "@toothfairyai/sdk") return {}
// see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
if (id.includes("grok") && id.includes("grok-3-mini")) {
if (model.api.npm === "@openrouter/ai-sdk-provider") {