mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 13:54:01 +00:00
517 lines
21 KiB
JavaScript
Executable File
517 lines
21 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
import { spawn } from "child_process"
|
||
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"
|
||
import { join, dirname } from "path"
|
||
import { homedir } from "os"
|
||
import * as readline from "readline"
|
||
import { fileURLToPath } from "url"
|
||
|
||
const __filename = fileURLToPath(import.meta.url)
|
||
const __dirname = dirname(__filename)
|
||
|
||
const TFCODE_DIR = join(homedir(), ".tfcode")
|
||
const TOOLS_FILE = join(TFCODE_DIR, "tools.json")
|
||
const CREDENTIALS_FILE = join(TFCODE_DIR, "credentials.json")
|
||
const CONFIG_FILE = join(TFCODE_DIR, "config.json")
|
||
|
||
const COLORS = {
|
||
reset: "\x1b[0m",
|
||
bold: "\x1b[1m",
|
||
green: "\x1b[32m",
|
||
red: "\x1b[31m",
|
||
cyan: "\x1b[36m",
|
||
dim: "\x1b[90m",
|
||
yellow: "\x1b[33m",
|
||
magenta: "\x1b[35m",
|
||
}
|
||
|
||
function log(msg) {
|
||
console.log(msg)
|
||
}
|
||
function success(msg) {
|
||
console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`)
|
||
}
|
||
function error(msg) {
|
||
console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`)
|
||
}
|
||
function info(msg) {
|
||
console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`)
|
||
}
|
||
|
||
function ensureConfigDir() {
|
||
if (!existsSync(TFCODE_DIR)) mkdirSync(TFCODE_DIR, { recursive: true })
|
||
}
|
||
|
||
function loadConfig() {
|
||
const envConfig = {
|
||
workspace_id: process.env.TF_WORKSPACE_ID,
|
||
api_key: process.env.TF_API_KEY,
|
||
region: process.env.TF_REGION,
|
||
}
|
||
if (envConfig.workspace_id && envConfig.api_key) return envConfig
|
||
if (existsSync(CONFIG_FILE)) {
|
||
try {
|
||
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"))
|
||
} catch {}
|
||
}
|
||
return null
|
||
}
|
||
|
||
function saveConfig(config) {
|
||
ensureConfigDir()
|
||
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
||
}
|
||
|
||
function runPythonSync(method, config = null) {
|
||
const wsId = config?.workspace_id || process.env.TF_WORKSPACE_ID || ""
|
||
const apiKey = config?.api_key || process.env.TF_API_KEY || ""
|
||
const region = config?.region || process.env.TF_REGION || "au"
|
||
|
||
const pythonCode = `
|
||
import json, sys, os
|
||
try:
|
||
os.environ["TF_WORKSPACE_ID"] = "${wsId}"
|
||
os.environ["TF_API_KEY"] = "${apiKey}"
|
||
os.environ["TF_REGION"] = "${region}"
|
||
from tf_sync.config import load_config, validate_credentials, Region
|
||
from tf_sync.tools import sync_tools
|
||
from tf_sync.config import get_region_urls
|
||
|
||
method = "${method}"
|
||
if method == "validate":
|
||
config = load_config()
|
||
result = validate_credentials(config)
|
||
urls = get_region_urls(config.region)
|
||
print(json.dumps({"success": result.success, "workspace_id": result.workspace_id, "workspace_name": result.workspace_name, "error": result.error, "base_url": urls["base_url"]}))
|
||
elif method == "sync":
|
||
config = load_config()
|
||
result = sync_tools(config)
|
||
tools_data = [{"id": t.id, "name": t.name, "description": t.description, "tool_type": t.tool_type.value, "request_type": t.request_type.value if t.request_type else None, "url": t.url, "auth_via": t.auth_via, "interpolation_string": t.interpolation_string, "goals": t.goals, "temperature": t.temperature, "max_tokens": t.max_tokens, "llm_base_model": t.llm_base_model, "llm_provider": t.llm_provider} for t in result.tools]
|
||
print(json.dumps({"success": result.success, "tools": tools_data, "by_type": result.by_type, "error": result.error}))
|
||
except Exception as e:
|
||
print(json.dumps({"success": False, "error": str(e)}))
|
||
`
|
||
|
||
return new Promise((resolve, reject) => {
|
||
const proc = spawn(process.env.TFCODE_PYTHON_PATH || "python3", ["-c", pythonCode], { env: { ...process.env } })
|
||
let stdout = "",
|
||
stderr = ""
|
||
proc.stdout.on("data", (d) => (stdout += d))
|
||
proc.stderr.on("data", (d) => (stderr += d))
|
||
proc.on("close", (code) => {
|
||
if (code !== 0 && !stdout) reject(new Error(`Python failed: ${stderr}`))
|
||
else
|
||
try {
|
||
resolve(JSON.parse(stdout.trim()))
|
||
} catch (e) {
|
||
reject(new Error(`Parse error: ${stdout}`))
|
||
}
|
||
})
|
||
proc.on("error", reject)
|
||
})
|
||
}
|
||
|
||
function loadCachedTools() {
|
||
if (!existsSync(TOOLS_FILE)) return null
|
||
try {
|
||
return JSON.parse(readFileSync(TOOLS_FILE, "utf-8"))
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
function saveToolsCache(tools) {
|
||
ensureConfigDir()
|
||
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2))
|
||
}
|
||
|
||
async function question(prompt) {
|
||
return new Promise((resolve) => {
|
||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
||
rl.question(prompt, (answer) => {
|
||
rl.close()
|
||
resolve(answer.trim())
|
||
})
|
||
})
|
||
}
|
||
|
||
async function select(prompt, options) {
|
||
log("")
|
||
log(prompt)
|
||
log("")
|
||
options.forEach((opt, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`))
|
||
log("")
|
||
const answer = await question("Select (1-" + options.length + "): ")
|
||
const idx = parseInt(answer) - 1
|
||
return idx >= 0 && idx < options.length ? idx : 0
|
||
}
|
||
|
||
async function interactiveSetup() {
|
||
log("")
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta} tfcode Setup${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
log("This will guide you through setting up your ToothFairyAI credentials.")
|
||
log("")
|
||
log(`${COLORS.dim}You can find your credentials at:${COLORS.reset}`)
|
||
log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`)
|
||
log("")
|
||
|
||
log(`${COLORS.bold}Step 1: Workspace ID${COLORS.reset}`)
|
||
log(`${COLORS.dim}This is your workspace UUID${COLORS.reset}`)
|
||
log("")
|
||
const workspaceId = await question("Enter your Workspace ID: ")
|
||
if (!workspaceId) {
|
||
error("Workspace ID is required")
|
||
process.exit(1)
|
||
}
|
||
log("")
|
||
|
||
log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`)
|
||
log(`${COLORS.dim}Paste or type your API key${COLORS.reset}`)
|
||
log("")
|
||
const apiKey = await question("Enter your API Key: ")
|
||
if (!apiKey) {
|
||
error("API Key is required")
|
||
process.exit(1)
|
||
}
|
||
log("")
|
||
|
||
log(`${COLORS.bold}Step 3: Region${COLORS.reset}`)
|
||
const regions = ["dev (Development)", "au (Australia)", "eu (Europe)", "us (United States)"]
|
||
const regionIdx = await select("Select your region:", regions)
|
||
const region = ["dev", "au", "eu", "us"][regionIdx]
|
||
|
||
log("")
|
||
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
log(`${COLORS.bold}Summary:${COLORS.reset}`)
|
||
log(` Workspace ID: ${workspaceId}`)
|
||
log(` API Key: ***${apiKey.slice(-4)}`)
|
||
log(` Region: ${region}`)
|
||
log("")
|
||
|
||
const confirm = await question("Save these credentials? (Y/n): ")
|
||
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
|
||
log("Setup cancelled.")
|
||
return
|
||
}
|
||
|
||
const config = { workspace_id: workspaceId, api_key: apiKey, region }
|
||
saveConfig(config)
|
||
success("Credentials saved to ~/.tfcode/config.json")
|
||
log("")
|
||
|
||
const testNow = await question("Validate credentials now? (Y/n): ")
|
||
if (testNow.toLowerCase() === "n" || testNow.toLowerCase() === "no") return
|
||
|
||
log("")
|
||
info("Validating credentials...")
|
||
log("")
|
||
|
||
try {
|
||
const result = await runPythonSync("validate", config)
|
||
if (result.success) {
|
||
success("Credentials valid!")
|
||
log(` API URL: ${result.base_url}`)
|
||
log(` Workspace ID: ${result.workspace_id}`)
|
||
log("")
|
||
|
||
const syncNow = await question("Sync tools now? (Y/n): ")
|
||
if (syncNow.toLowerCase() === "n" || syncNow.toLowerCase() === "no") return
|
||
|
||
log("")
|
||
info("Syncing tools...")
|
||
log("")
|
||
|
||
const syncResult = await runPythonSync("sync", config)
|
||
if (syncResult.success) {
|
||
saveToolsCache(syncResult)
|
||
success(`Synced ${syncResult.tools.length} tools`)
|
||
if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
|
||
log("")
|
||
log("By type:")
|
||
for (const [type, count] of Object.entries(syncResult.by_type)) log(` ${type}: ${count}`)
|
||
}
|
||
} else {
|
||
error(`Sync failed: ${syncResult.error}`)
|
||
}
|
||
} else {
|
||
error(`Validation failed: ${result.error}`)
|
||
}
|
||
} catch (e) {
|
||
error(`Failed: ${e.message}`)
|
||
}
|
||
|
||
log("")
|
||
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
log("Commands:")
|
||
log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Check credentials`)
|
||
log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools`)
|
||
log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List tools`)
|
||
log("")
|
||
}
|
||
|
||
function showHelp() {
|
||
log("")
|
||
log(`${COLORS.bold}tfcode${COLORS.reset} - ToothFairyAI's AI coding agent`)
|
||
log("")
|
||
log("Commands:")
|
||
log(` ${COLORS.cyan}tfcode setup${COLORS.reset} Interactive credential setup`)
|
||
log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Test credentials`)
|
||
log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools from workspace`)
|
||
log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List synced tools`)
|
||
log(` ${COLORS.cyan}tfcode test-agent <id>${COLORS.reset} Test agent prompt injection`)
|
||
log(` ${COLORS.cyan}tfcode debug${COLORS.reset} Show debug info`)
|
||
log(` ${COLORS.cyan}tfcode --help${COLORS.reset} Show this help`)
|
||
log(` ${COLORS.cyan}tfcode --version${COLORS.reset} Show version`)
|
||
log("")
|
||
log(`${COLORS.dim}For full TUI, run from source:${COLORS.reset}`)
|
||
log(`${COLORS.dim} bun run packages/tfcode/src/index.ts${COLORS.reset}`)
|
||
log("")
|
||
}
|
||
|
||
function showDebugInfo() {
|
||
log("")
|
||
log(`${COLORS.bold}Debug Information${COLORS.reset}`)
|
||
log("")
|
||
log(` ${COLORS.bold}TFCODE_DIR:${COLORS.reset} ${TFCODE_DIR}`)
|
||
log(` ${COLORS.bold}TOOLS_FILE:${COLORS.reset} ${TOOLS_FILE}`)
|
||
log(` ${COLORS.bold}CONFIG_FILE:${COLORS.reset} ${CONFIG_FILE}`)
|
||
log(` ${COLORS.bold}CREDENTIALS_FILE:${COLORS.reset} ${CREDENTIALS_FILE}`)
|
||
log("")
|
||
log(` ${COLORS.bold}tools.json exists:${COLORS.reset} ${existsSync(TOOLS_FILE)}`)
|
||
if (existsSync(TOOLS_FILE)) {
|
||
const tools = loadCachedTools()
|
||
log(` ${COLORS.bold}tools.json valid:${COLORS.reset} ${tools?.success ?? false}`)
|
||
log(` ${COLORS.bold}tools count:${COLORS.reset} ${tools?.tools?.length ?? 0}`)
|
||
const coderAgents = tools?.tools?.filter((t) => t.tool_type === "coder_agent") ?? []
|
||
log(` ${COLORS.bold}coder_agent count:${COLORS.reset} ${coderAgents.length}`)
|
||
if (coderAgents.length > 0) {
|
||
log("")
|
||
log(` ${COLORS.bold}Coder Agents:${COLORS.reset}`)
|
||
coderAgents.forEach((a) => {
|
||
log(` - ${a.name} (id: ${a.id})`)
|
||
log(` interpolation_string: ${a.interpolation_string ? "YES" : "NO"}`)
|
||
log(` goals: ${a.goals ? "YES" : "NO"}`)
|
||
log(` llm_provider: ${a.llm_provider ?? "(null)"}`)
|
||
log(` llm_base_model: ${a.llm_base_model ?? "(null)"}`)
|
||
})
|
||
}
|
||
}
|
||
log("")
|
||
}
|
||
|
||
function testAgentPrompt(agentId) {
|
||
const tools = loadCachedTools()
|
||
if (!tools?.success) {
|
||
error("No tools. Run: tfcode sync")
|
||
process.exit(1)
|
||
}
|
||
|
||
const agent = tools.tools.find((t) => t.id === agentId || t.name === agentId)
|
||
if (!agent) {
|
||
error(`Agent not found: ${agentId}`)
|
||
log("")
|
||
log("Available coder agents:")
|
||
tools.tools
|
||
.filter((t) => t.tool_type === "coder_agent")
|
||
.forEach((t) => {
|
||
log(` ${COLORS.cyan}${t.id}${COLORS.reset} - ${t.name}`)
|
||
})
|
||
process.exit(1)
|
||
}
|
||
|
||
log("")
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta} Agent Data from tools.json${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
log(` ${COLORS.bold}id:${COLORS.reset} ${agent.id}`)
|
||
log(` ${COLORS.bold}name:${COLORS.reset} ${agent.name}`)
|
||
log(` ${COLORS.bold}description:${COLORS.reset} ${agent.description || "(none)"}`)
|
||
log(` ${COLORS.bold}tool_type:${COLORS.reset} ${agent.tool_type}`)
|
||
log(` ${COLORS.bold}auth_via:${COLORS.reset} ${agent.auth_via}`)
|
||
log("")
|
||
log(` ${COLORS.bold}interpolation_string:${COLORS.reset}`)
|
||
if (agent.interpolation_string) {
|
||
log(` ${agent.interpolation_string.substring(0, 200)}${agent.interpolation_string.length > 200 ? "..." : ""}`)
|
||
} else {
|
||
log(` ${COLORS.dim}(none)${COLORS.reset}`)
|
||
}
|
||
log("")
|
||
log(` ${COLORS.bold}goals:${COLORS.reset}`)
|
||
if (agent.goals) {
|
||
log(` ${agent.goals.substring(0, 200)}${agent.goals.length > 200 ? "..." : ""}`)
|
||
} else {
|
||
log(` ${COLORS.dim}(none)${COLORS.reset}`)
|
||
}
|
||
log("")
|
||
log(` ${COLORS.bold}temperature:${COLORS.reset} ${agent.temperature ?? "(none)"}`)
|
||
log(` ${COLORS.bold}max_tokens:${COLORS.reset} ${agent.max_tokens ?? "(none)"}`)
|
||
log(` ${COLORS.bold}llm_base_model:${COLORS.reset} ${agent.llm_base_model ?? "(none)"}`)
|
||
log(` ${COLORS.bold}llm_provider:${COLORS.reset} ${agent.llm_provider ?? "(none)"}`)
|
||
log("")
|
||
|
||
// Build highlighted instructions
|
||
const isTFProvider = !agent.llm_provider || agent.llm_provider === "toothfairyai" || agent.llm_provider === "tf"
|
||
|
||
const hasPrompt = agent.interpolation_string && agent.interpolation_string.trim().length > 0
|
||
const hasGoals = agent.goals && agent.goals.trim().length > 0
|
||
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta} Model Mapping${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
log(` ${COLORS.bold}isTFProvider:${COLORS.reset} ${isTFProvider}`)
|
||
log(
|
||
` ${COLORS.bold}mapped model:${COLORS.reset} ${isTFProvider && agent.llm_base_model ? `toothfairyai/${agent.llm_base_model}` : "(no mapping)"}`,
|
||
)
|
||
log("")
|
||
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta} Highlighted Instructions Preview${COLORS.reset}`)
|
||
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`)
|
||
log("")
|
||
|
||
if (!hasPrompt && !hasGoals) {
|
||
log(
|
||
` ${COLORS.dim}(No interpolation_string or goals - no highlighted instructions will be generated)${COLORS.reset}`,
|
||
)
|
||
} else {
|
||
log("")
|
||
log("═══════════════════════════════════════════════════════════════════════════════")
|
||
log("⚠️ ULTRA IMPORTANT - AGENT CONFIGURATION ⚠️")
|
||
log("═══════════════════════════════════════════════════════════════════════════════")
|
||
log("")
|
||
log(`You are acting as the agent: "${agent.name}"`)
|
||
if (agent.description) {
|
||
log(`Description: ${agent.description}`)
|
||
}
|
||
log("")
|
||
log("The following instructions and goals are MANDATORY and MUST be followed")
|
||
log("with the HIGHEST PRIORITY. These override any conflicting default behaviors.")
|
||
log("═══════════════════════════════════════════════════════════════════════════════")
|
||
|
||
if (hasPrompt) {
|
||
log("")
|
||
log("┌─────────────────────────────────────────────────────────────────────────────┐")
|
||
log(`│ 🎯 AGENT "${agent.name}" INSTRUCTIONS (CRITICAL - MUST FOLLOW) │`)
|
||
log("└─────────────────────────────────────────────────────────────────────────────┘")
|
||
log("")
|
||
log(agent.interpolation_string)
|
||
}
|
||
|
||
if (hasGoals) {
|
||
log("")
|
||
log("┌─────────────────────────────────────────────────────────────────────────────┐")
|
||
log(`│ 🎯 AGENT "${agent.name}" GOALS (CRITICAL - MUST ACHIEVE) │`)
|
||
log("└─────────────────────────────────────────────────────────────────────────────┘")
|
||
log("")
|
||
log(agent.goals)
|
||
}
|
||
|
||
log("")
|
||
log("═══════════════════════════════════════════════════════════════════════════════")
|
||
log(`⚠️ END OF ULTRA IMPORTANT AGENT "${agent.name}" CONFIGURATION ⚠️`)
|
||
log("═══════════════════════════════════════════════════════════════════════════════")
|
||
}
|
||
log("")
|
||
}
|
||
|
||
const args = process.argv.slice(2)
|
||
const command = args[0]
|
||
|
||
if (args.includes("--help") || args.includes("-h")) {
|
||
showHelp()
|
||
} else if (args.includes("--version") || args.includes("-v")) {
|
||
log("tfcode v1.0.0-beta.9")
|
||
} else if (command === "setup") {
|
||
interactiveSetup()
|
||
} else if (command === "validate") {
|
||
;(async () => {
|
||
const config = loadConfig()
|
||
if (!config) {
|
||
error("No credentials. Run: tfcode setup")
|
||
process.exit(1)
|
||
}
|
||
info("Validating...")
|
||
try {
|
||
const result = await runPythonSync("validate", config)
|
||
if (result.success) {
|
||
success("Credentials valid")
|
||
log(` API URL: ${result.base_url}`)
|
||
} else {
|
||
error(`Failed: ${result.error}`)
|
||
process.exit(1)
|
||
}
|
||
} catch (e) {
|
||
error(`Failed: ${e.message}`)
|
||
process.exit(1)
|
||
}
|
||
})()
|
||
} else if (command === "sync") {
|
||
;(async () => {
|
||
const config = loadConfig()
|
||
if (!config) {
|
||
error("No credentials. Run: tfcode setup")
|
||
process.exit(1)
|
||
}
|
||
info("Syncing tools...")
|
||
try {
|
||
const result = await runPythonSync("sync", config)
|
||
if (result.success) {
|
||
saveToolsCache(result)
|
||
success(`Synced ${result.tools.length} tools`)
|
||
if (result.by_type) {
|
||
log("")
|
||
log("By type:")
|
||
for (const [t, c] of Object.entries(result.by_type)) log(` ${t}: ${c}`)
|
||
}
|
||
} else {
|
||
error(`Failed: ${result.error}`)
|
||
process.exit(1)
|
||
}
|
||
} catch (e) {
|
||
error(`Failed: ${e.message}`)
|
||
process.exit(1)
|
||
}
|
||
})()
|
||
} else if (command === "tools" && args[1] === "list") {
|
||
const cached = loadCachedTools()
|
||
if (!cached?.success) {
|
||
error("No tools. Run: tfcode sync")
|
||
process.exit(1)
|
||
}
|
||
let tools = cached.tools
|
||
if (args[2] === "--type" && args[3]) tools = tools.filter((t) => t.tool_type === args[3])
|
||
log(`\n${tools.length} tool(s):\n`)
|
||
for (const t of tools) {
|
||
log(` ${COLORS.cyan}${t.name}${COLORS.reset}`)
|
||
log(` Type: ${t.tool_type}`)
|
||
if (t.description) log(` ${COLORS.dim}${t.description.slice(0, 60)}${COLORS.reset}`)
|
||
log(` Auth: ${t.auth_via}\n`)
|
||
}
|
||
} else if (command === "test-agent") {
|
||
const agentId = args[1]
|
||
if (!agentId) {
|
||
error("Usage: tfcode test-agent <agent-id-or-name>")
|
||
process.exit(1)
|
||
}
|
||
testAgentPrompt(agentId)
|
||
} else if (command === "debug") {
|
||
showDebugInfo()
|
||
} else if (!command) {
|
||
// Show help instead of trying TUI (TUI requires full build)
|
||
showHelp()
|
||
} else {
|
||
error(`Unknown command: ${command}`)
|
||
showHelp()
|
||
process.exit(1)
|
||
}
|