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>
This commit is contained in:
Gab
2026-04-15 13:57:44 +10:00
parent c3e504f036
commit 3639218879
15 changed files with 251 additions and 175 deletions

View File

@@ -1,171 +1,225 @@
#!/usr/bin/env node
const { spawn, execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
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';
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);
console.log(msg)
}
function logSuccess(msg) {
console.log(`${GREEN}${RESET} ${msg}`);
console.log(`${GREEN}${RESET} ${msg}`)
}
function logError(msg) {
console.error(`${RED}${RESET} ${msg}`);
console.error(`${RED}${RESET} ${msg}`)
}
function logInfo(msg) {
console.log(`${CYAN}${RESET} ${msg}`);
console.log(`${CYAN}${RESET} ${msg}`)
}
function logWarning(msg) {
console.log(`${YELLOW}!${RESET} ${msg}`);
console.log(`${YELLOW}!${RESET} ${msg}`)
}
function checkPython() {
const commands = ['python3', 'python'];
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+)/);
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]);
const major = parseInt(match[1])
const minor = parseInt(match[2])
if (major >= 3 && minor >= 10) {
return { cmd, version: result.trim() };
return { cmd, version: result.trim() }
}
}
} catch {}
}
return null;
return null
}
function installPythonDeps(pythonCmd) {
return new Promise((resolve, reject) => {
const packages = ['toothfairyai', 'pydantic', 'httpx', 'rich'];
log(`${DIM}Installing Python packages: ${packages.join(', ')}...${RESET}`);
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 args = ["-m", "pip", "install", "--user", ...packages]
const proc = spawn(pythonCmd, args, {
stdio: 'inherit',
shell: process.platform === 'win32'
});
proc.on('close', (code) => {
stdio: "inherit",
shell: process.platform === "win32",
})
proc.on("close", (code) => {
if (code === 0) {
resolve();
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];
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) => {
stdio: "inherit",
shell: process.platform === "win32",
})
retry.on("close", (retryCode) => {
if (retryCode === 0) {
resolve();
resolve()
} else {
reject(new Error(`pip install exited with code ${retryCode}`));
reject(new Error(`pip install exited with code ${retryCode}`))
}
});
retry.on('error', (err) => {
reject(err);
});
})
retry.on("error", (err) => {
reject(err)
})
}
});
proc.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('');
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();
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);
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('');
logSuccess(`Found ${python.version} (${python.cmd})`)
log("")
// Install Python dependencies
logInfo('Installing ToothFairyAI Python SDK...');
logInfo("Installing ToothFairyAI Python SDK...")
try {
await installPythonDeps(python.cmd);
logSuccess('Python dependencies installed');
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('');
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('');
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);
});
logError(`Installation failed: ${err.message}`)
process.exit(1)
})