#!/usr/bin/env node 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" 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() } let arch switch (os.arch()) { case "x64": arch = "x64" break case "arm64": arch = "arm64" break default: arch = os.arch() } // Check for AVX2 on x64 let needsBaseline = false if (arch === "x64" && (platform === "linux" || platform === "darwin")) { try { if (platform === "linux") { const cpuinfo = fs.readFileSync("/proc/cpuinfo", "utf8") needsBaseline = !cpuinfo.toLowerCase().includes("avx2") } else if (platform === "darwin") { const result = spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { encoding: "utf8" }) needsBaseline = result.stdout.trim() !== "1" } } catch {} } // Check for musl on Linux let abi = "" if (platform === "linux") { try { if (fs.existsSync("/etc/alpine-release")) { abi = "musl" } else { const result = spawnSync("ldd", ["--version"], { encoding: "utf8" }) if ((result.stdout + result.stderr).toLowerCase().includes("musl")) { abi = "musl" } } } catch {} } return { platform, arch, needsBaseline, abi } } async function getVersion() { try { const res = await fetch(`https://${GITEA_HOST}/api/v1/repos/${GITEA_REPO}/releases/latest`) const data = await res.json() return data.tag_name?.replace(/^v/, "") || "1.0.0" } catch { return "1.0.0" } } async function downloadBinary() { const { platform, arch, needsBaseline, abi } = detectPlatform() const version = await getVersion() // 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}` const ext = platform === "linux" ? ".tar.gz" : ".zip" const filename = `${target}${ext}` const url = `https://${GITEA_HOST}/${GITEA_REPO}/releases/download/v${version}/${filename}` console.log(`Downloading tfcode v${version} for ${target}...`) // Download if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true }) const tmpDir = path.join(os.tmpdir(), `tfcode-install-${process.pid}`) fs.mkdirSync(tmpDir, { recursive: true }) const archivePath = path.join(tmpDir, filename) // Use curl to download const curlResult = spawnSync("curl", ["-fsSL", "-o", archivePath, url], { stdio: "inherit" }) 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}` // 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 (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) } // Extract console.log("Extracting...") if (platform === "linux") { spawnSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "inherit" }) } else { spawnSync("unzip", ["-q", archivePath, "-d", tmpDir], { stdio: "inherit" }) } // Move binary const extractedBinary = path.join(tmpDir, "tfcode") const targetBinary = path.join(binDir, binaryName) if (fs.existsSync(extractedBinary)) { if (fs.existsSync(targetBinary)) fs.unlinkSync(targetBinary) fs.copyFileSync(extractedBinary, targetBinary) fs.chmodSync(targetBinary, 0o755) console.log(`Installed tfcode to ${targetBinary}`) } // Cleanup fs.rmSync(tmpDir, { recursive: true, force: true }) } function setupBinary(sourcePath, platform) { const binDir = path.join(__dirname, "bin") const binaryName = platform === "windows" ? "tfcode.exe" : "tfcode" const targetBinary = path.join(binDir, binaryName) if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true }) if (fs.existsSync(targetBinary)) fs.unlinkSync(targetBinary) // Try hardlink, fall back to copy try { fs.linkSync(sourcePath, targetBinary) } catch { fs.copyFileSync(sourcePath, targetBinary) } fs.chmodSync(targetBinary, 0o755) console.log(`tfcode installed to ${targetBinary}`) } async function main() { console.log("") console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") console.log(" tfcode - ToothFairyAI's official coding agent") 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" }) 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", }, ) if (pipResult.status === 0) { console.log("✓ Python SDK installed") } else { console.log("! Python SDK install failed, run manually:") console.log(" pip install toothfairyai pydantic httpx rich") } } } catch { console.log("! Python 3.10+ not found. Install with:") console.log(" macOS: brew install python@3.12") console.log(" Ubuntu: sudo apt install python3.12") console.log(" Windows: Download from python.org/downloads") console.log("") } console.log("") console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") console.log("✓ tfcode installed successfully!") console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") console.log("") 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("") console.log(" 2. Validate:") console.log(" tfcode validate") console.log("") console.log(" 3. Sync tools:") console.log(" tfcode sync") console.log("") console.log(" 4. Start coding:") console.log(" tfcode") console.log("") console.log("Documentation: https://toothfairyai.com/developers/tfcode") console.log("") } main().catch(console.error)