diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 000000000..f3ba03646 --- /dev/null +++ b/PROGRESS.md @@ -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` diff --git a/bun.lock b/bun.lock index 75999c631..ed76ab86d 100644 --- a/bun.lock +++ b/bun.lock @@ -381,7 +381,7 @@ }, "packages/tfcode": { "name": "tfcode", - "version": "1.0.2", + "version": "1.0.5", "bin": { "tfcode": "./bin/tfcode", }, diff --git a/packages/tfcode/package.json b/packages/tfcode/package.json index b19038b2b..4bddf40d9 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.2", + "version": "1.0.5", "name": "tfcode", "type": "module", "license": "MIT", diff --git a/packages/tfcode/script/postinstall-tfcode.mjs b/packages/tfcode/script/postinstall-tfcode.mjs index 8699aaa1c..58b5a0ee5 100644 --- a/packages/tfcode/script/postinstall-tfcode.mjs +++ b/packages/tfcode/script/postinstall-tfcode.mjs @@ -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" }) diff --git a/packages/tfcode/script/publish-tfcode.ts b/packages/tfcode/script/publish-tfcode.ts index 343e4dcba..5b399862e 100644 --- a/packages/tfcode/script/publish-tfcode.ts +++ b/packages/tfcode/script/publish-tfcode.ts @@ -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') diff --git a/packages/tfcode/src/provider/transform.ts b/packages/tfcode/src/provider/transform.ts index 68918ae3b..914397957 100644 --- a/packages/tfcode/src/provider/transform.ts +++ b/packages/tfcode/src/provider/transform.ts @@ -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") {