mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +00:00
feat: tfcode
This commit is contained in:
parent
2ae12f8d6b
commit
3fe808d64c
90
PROGRESS.md
Normal file
90
PROGRESS.md
Normal 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`
|
||||||
2
bun.lock
2
bun.lock
@ -381,7 +381,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tfcode": {
|
"packages/tfcode": {
|
||||||
"name": "tfcode",
|
"name": "tfcode",
|
||||||
"version": "1.0.2",
|
"version": "1.0.5",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tfcode": "./bin/tfcode",
|
"tfcode": "./bin/tfcode",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.0.2",
|
"version": "1.0.5",
|
||||||
"name": "tfcode",
|
"name": "tfcode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import fs from "fs"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import { spawnSync } from "child_process"
|
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_HOST = process.env.TFCODE_GITEA_HOST || "gitea.toothfairyai.com"
|
||||||
const GITEA_REPO = process.env.TFCODE_GITEA_REPO || "ToothFairyAI/tf_code"
|
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 { platform, arch, needsBaseline, abi } = detectPlatform()
|
||||||
const version = await getVersion()
|
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}`
|
let target = `tfcode-${platform}-${arch}`
|
||||||
if (needsBaseline) target += "-baseline"
|
if (needsBaseline) target += "-baseline"
|
||||||
if (abi) target += `-${abi}`
|
if (abi) target += `-${abi}`
|
||||||
@ -94,7 +108,6 @@ async function downloadBinary() {
|
|||||||
console.log(`Downloading tfcode v${version} for ${target}...`)
|
console.log(`Downloading tfcode v${version} for ${target}...`)
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
const binDir = path.join(__dirname, "bin")
|
|
||||||
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true })
|
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true })
|
||||||
|
|
||||||
const tmpDir = path.join(os.tmpdir(), `tfcode-install-${process.pid}`)
|
const tmpDir = path.join(os.tmpdir(), `tfcode-install-${process.pid}`)
|
||||||
@ -110,17 +123,42 @@ async function downloadBinary() {
|
|||||||
// Fallback to npm optionalDependencies
|
// Fallback to npm optionalDependencies
|
||||||
try {
|
try {
|
||||||
const pkgName = `@toothfairyai/${target}`
|
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 binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
|
||||||
const binaryPath = path.join(pkgDir, "bin", binaryName)
|
const binaryPath = path.join(pkgDir, "bin", binaryName)
|
||||||
|
|
||||||
|
console.log(`Looking for binary at: ${binaryPath}`)
|
||||||
|
|
||||||
if (fs.existsSync(binaryPath)) {
|
if (fs.existsSync(binaryPath)) {
|
||||||
|
console.log(`Found binary at npm package location`)
|
||||||
setupBinary(binaryPath, platform)
|
setupBinary(binaryPath, platform)
|
||||||
return
|
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)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +171,6 @@ async function downloadBinary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move binary
|
// Move binary
|
||||||
const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode"
|
|
||||||
const extractedBinary = path.join(tmpDir, "tfcode")
|
const extractedBinary = path.join(tmpDir, "tfcode")
|
||||||
const targetBinary = path.join(binDir, binaryName)
|
const targetBinary = path.join(binDir, binaryName)
|
||||||
|
|
||||||
@ -174,6 +211,9 @@ async function main() {
|
|||||||
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
console.log("")
|
console.log("")
|
||||||
|
|
||||||
|
// Download and setup binary
|
||||||
|
await downloadBinary()
|
||||||
|
|
||||||
// Check for Python (needed for TF integration)
|
// Check for Python (needed for TF integration)
|
||||||
try {
|
try {
|
||||||
const result = spawnSync("python3", ["--version"], { encoding: "utf8" })
|
const result = spawnSync("python3", ["--version"], { encoding: "utf8" })
|
||||||
|
|||||||
@ -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/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())
|
||||||
|
|
||||||
|
// 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
|
// Create a simple wrapper script that runs the installed binary
|
||||||
const wrapper = `#!/usr/bin/env node
|
const wrapper = `#!/usr/bin/env node
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const binDir = path.join(__dirname, 'bin')
|
|
||||||
const binary = process.platform === 'win32' ? 'tfcode.exe' : 'tfcode'
|
const binary = process.platform === 'win32' ? 'tfcode.exe' : 'tfcode'
|
||||||
const binaryPath = path.join(binDir, binary)
|
const binaryPath = path.join(__dirname, binary)
|
||||||
|
|
||||||
if (!fs.existsSync(binaryPath)) {
|
if (!fs.existsSync(binaryPath)) {
|
||||||
console.error('tfcode binary not found. Run: npm install @toothfairyai/tfcode')
|
console.error('tfcode binary not found. Run: npm install @toothfairyai/tfcode')
|
||||||
|
|||||||
@ -348,6 +348,9 @@ export namespace ProviderTransform {
|
|||||||
)
|
)
|
||||||
return {}
|
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
|
// see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
|
||||||
if (id.includes("grok") && id.includes("grok-3-mini")) {
|
if (id.includes("grok") && id.includes("grok-3-mini")) {
|
||||||
if (model.api.npm === "@openrouter/ai-sdk-provider") {
|
if (model.api.npm === "@openrouter/ai-sdk-provider") {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user