Files
tf_code/packages/tfcode/scripts/postinstall.cjs
Gab 3639218879 fix: tfcode web fails after npm install — copy app dist in postinstall
The web command crashed because postinstall scripts never copied the
app/dist directory from the platform package. Added copyAppDir() to
both postinstall scripts, multi-path resolution in findAppDir() and
server static serving, and updated branding to use local favicon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:57:44 +10:00

226 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const { spawn, execSync } = require("child_process")
const fs = require("fs")
const path = require("path")
const RESET = "\x1b[0m"
const BOLD = "\x1b[1m"
const GREEN = "\x1b[32m"
const YELLOW = "\x1b[33m"
const RED = "\x1b[31m"
const CYAN = "\x1b[36m"
const DIM = "\x1b[90m"
function log(msg) {
console.log(msg)
}
function logSuccess(msg) {
console.log(`${GREEN}${RESET} ${msg}`)
}
function logError(msg) {
console.error(`${RED}${RESET} ${msg}`)
}
function logInfo(msg) {
console.log(`${CYAN}${RESET} ${msg}`)
}
function logWarning(msg) {
console.log(`${YELLOW}!${RESET} ${msg}`)
}
function checkPython() {
const commands = ["python3", "python"]
for (const cmd of commands) {
try {
const result = execSync(`${cmd} --version`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
const match = result.match(/Python (\d+)\.(\d+)/)
if (match) {
const major = parseInt(match[1])
const minor = parseInt(match[2])
if (major >= 3 && minor >= 10) {
return { cmd, version: result.trim() }
}
}
} catch {}
}
return null
}
function installPythonDeps(pythonCmd) {
return new Promise((resolve, reject) => {
const packages = ["toothfairyai", "pydantic", "httpx", "rich"]
log(`${DIM}Installing Python packages: ${packages.join(", ")}...${RESET}`)
// Try with --user first, then --break-system-packages if needed
const args = ["-m", "pip", "install", "--user", ...packages]
const proc = spawn(pythonCmd, args, {
stdio: "inherit",
shell: process.platform === "win32",
})
proc.on("close", (code) => {
if (code === 0) {
resolve()
} else {
// Try with --break-system-packages flag
log(`${DIM}Retrying with --break-system-packages...${RESET}`)
const retryArgs = ["-m", "pip", "install", "--user", "--break-system-packages", ...packages]
const retry = spawn(pythonCmd, retryArgs, {
stdio: "inherit",
shell: process.platform === "win32",
})
retry.on("close", (retryCode) => {
if (retryCode === 0) {
resolve()
} else {
reject(new Error(`pip install exited with code ${retryCode}`))
}
})
retry.on("error", (err) => {
reject(err)
})
}
})
proc.on("error", (err) => {
reject(err)
})
})
}
function copyAppDir() {
let platform
switch (process.platform) {
case "darwin":
platform = "darwin"
break
case "linux":
platform = "linux"
break
case "win32":
platform = "windows"
break
default:
platform = process.platform
}
let arch
switch (process.arch) {
case "x64":
arch = "x64"
break
case "arm64":
arch = "arm64"
break
default:
arch = process.arch
}
const pkgName = `@toothfairyai/tfcode-${platform}-${arch}`
const appDir = path.join(__dirname, "app")
if (fs.existsSync(path.join(appDir, "dist", "index.html"))) {
logSuccess("Web app already present")
return
}
try {
const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`))
const srcAppDir = path.join(pkgDir, "bin", "app")
if (!fs.existsSync(path.join(srcAppDir, "dist", "index.html"))) {
logWarning("Web app dist not found in platform package")
return
}
fs.cpSync(srcAppDir, appDir, { recursive: true })
logSuccess("Web app copied from platform package")
} catch (e) {
logWarning(`Could not copy web app: ${e.message}`)
}
}
async function main() {
log("")
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`)
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
log("")
copyAppDir()
// Check for Python
logInfo("Checking Python installation...")
const python = checkPython()
if (!python) {
log("")
logError("Python 3.10+ is required but not found.")
log("")
log(`${BOLD}Please install Python 3.10 or later:${RESET}`)
log("")
log(` ${CYAN}macOS:${RESET} brew install python@3.12`)
log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`)
log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`)
log("")
log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`)
log("")
process.exit(1)
}
logSuccess(`Found ${python.version} (${python.cmd})`)
log("")
// Install Python dependencies
logInfo("Installing ToothFairyAI Python SDK...")
try {
await installPythonDeps(python.cmd)
logSuccess("Python dependencies installed")
} catch (err) {
logWarning(`Failed to install Python dependencies: ${err.message}`)
log("")
log(`${DIM}You can install them manually with:${RESET}`)
log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`)
log("")
// Don't exit - user might install manually later
}
log("")
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
log(`${GREEN}✓ tfcode installed successfully!${RESET}`)
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
log("")
log(`${BOLD}Quick Start:${RESET}`)
log("")
log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`)
log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`)
log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`)
log("")
log(` ${CYAN}2.${RESET} Validate your credentials:`)
log(` ${DIM}tfcode validate${RESET}`)
log("")
log(` ${CYAN}3.${RESET} Sync tools from your workspace:`)
log(` ${DIM}tfcode sync${RESET}`)
log("")
log(` ${CYAN}4.${RESET} Start coding:`)
log(` ${DIM}tfcode${RESET}`)
log("")
log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`)
log("")
}
main().catch((err) => {
logError(`Installation failed: ${err.message}`)
process.exit(1)
})