From 7c015708cbffb8e8789462ae4e291221ee4064df Mon Sep 17 00:00:00 2001 From: Gab Date: Tue, 24 Mar 2026 14:29:24 +1100 Subject: [PATCH] feat: tfcode --- packages/tfcode/README.md | 59 ++-- packages/tfcode/bin/tfcode.js | 513 ++++++++++++++++------------------ packages/tfcode/package.json | 2 +- 3 files changed, 279 insertions(+), 295 deletions(-) mode change 100644 => 100755 packages/tfcode/bin/tfcode.js diff --git a/packages/tfcode/README.md b/packages/tfcode/README.md index bcadd8257..7371c9f8c 100644 --- a/packages/tfcode/README.md +++ b/packages/tfcode/README.md @@ -17,7 +17,7 @@ ## Installation ```bash -npm install -g tfcode +npm install -g @toothfairyai/tfcode@beta ``` The installer will: @@ -26,6 +26,21 @@ The installer will: ## Quick Start +### Option A: Interactive Setup (Recommended) + +```bash +# Run interactive setup +tfcode setup +``` + +This will guide you through entering your credentials step by step: +1. Enter your Workspace ID +2. Enter your API Key (hidden as you type) +3. Select your region +4. Validate and sync automatically + +### Option B: Manual Setup + ```bash # 1. Set your ToothFairyAI credentials export TF_WORKSPACE_ID="your-workspace-id" @@ -38,21 +53,20 @@ tfcode validate # 3. Sync tools from your workspace tfcode sync -# 4. Start coding! -tfcode +# 4. List your tools +tfcode tools list ``` ## Commands | Command | Description | |---------|-------------| -| `tfcode` | Start the AI coding assistant | +| `tfcode setup` | **Interactive credential setup** | | `tfcode quickstart` | Show quick start guide | | `tfcode validate` | Test your credentials | | `tfcode sync` | Sync tools from workspace | | `tfcode tools list` | List synced tools | -| `tfcode tools list --type mcp` | List MCP servers | -| `tfcode tools credentials --set` | Set API key for a tool | +| `tfcode tools list --type api_function` | Filter by type | ## Regions @@ -65,7 +79,17 @@ tfcode ## Configuration -Credentials can be set via environment variables: +Credentials are stored in `~/.tfcode/config.json`: + +```json +{ + "workspace_id": "your-workspace-id", + "api_key": "your-api-key", + "region": "au" +} +``` + +You can also set credentials via environment variables (takes priority over config file): ```bash export TF_WORKSPACE_ID="your-workspace-id" @@ -73,18 +97,6 @@ export TF_API_KEY="your-api-key" export TF_REGION="au" ``` -Or in `~/.tfcode/tfcode.json`: - -```json -{ - "toothfairy": { - "workspace_id": "your-workspace-id", - "api_key": "your-api-key", - "region": "au" - } -} -``` - ## Getting Your Credentials 1. Log in to [ToothFairyAI](https://app.toothfairyai.com) @@ -104,12 +116,19 @@ Install Python: - Check your API key is correct - Generate a new key in ToothFairyAI Settings → API Keys +- Run `tfcode setup` to re-enter credentials ### "Failed to validate: Connection test failed" Try a different region: +- Run `tfcode setup` and select a different region +- Or set `TF_REGION="eu"` or `TF_REGION="us"` + +### "No credentials found" + +Run interactive setup: ```bash -export TF_REGION="eu" # or us, au +tfcode setup ``` ## Documentation diff --git a/packages/tfcode/bin/tfcode.js b/packages/tfcode/bin/tfcode.js old mode 100644 new mode 100755 index 09bdcba1d..086d9bad2 --- a/packages/tfcode/bin/tfcode.js +++ b/packages/tfcode/bin/tfcode.js @@ -1,15 +1,19 @@ #!/usr/bin/env node -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; import { spawn } from 'child_process'; import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; -import { join } from 'path'; +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', @@ -18,133 +22,83 @@ const COLORS = { red: '\x1b[31m', cyan: '\x1b[36m', dim: '\x1b[90m', - yellow: '\x1b[33m' + yellow: '\x1b[33m', + magenta: '\x1b[35m' }; -function log(msg) { - console.log(msg); +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 success(msg) { - console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`); +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 error(msg) { - console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`); +function saveConfig(config) { + ensureConfigDir(); + writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); } -function info(msg) { - console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`); -} - -function runPythonSync(method) { +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 -import sys -import os - +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"] - })) - + 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 = [] - for tool in result.tools: - tools_data.append({ - "id": tool.id, - "name": tool.name, - "description": tool.description, - "tool_type": tool.tool_type.value, - "request_type": tool.request_type.value if tool.request_type else None, - "url": tool.url, - "auth_via": tool.auth_via - }) - - print(json.dumps({ - "success": result.success, - "tools": tools_data, - "by_type": result.by_type, - "error": result.error - })) - + 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} 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)})) -sys.exit(0) `; return new Promise((resolve, reject) => { - const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3'; - const proc = spawn(pythonPath, ['-c', pythonCode], { - env: { - ...process.env, - PYTHONPATH: process.env.TFCODE_PYTHONPATH || '' - } - }); - - let stdout = ''; - let stderr = ''; - - proc.stdout.on('data', (data) => { - stdout += data.toString(); - }); - - proc.stderr.on('data', (data) => { - stderr += data.toString(); - }); - + 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 sync failed: ${stderr}`)); - return; - } - - try { - const result = JSON.parse(stdout.trim()); - resolve(result); - } catch (e) { - reject(new Error(`Failed to parse Python output: ${stdout}\nstderr: ${stderr}`)); - } - }); - - proc.on('error', (err) => { - reject(err); + 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 ensureConfigDir() { - if (!existsSync(TFCODE_DIR)) { - mkdirSync(TFCODE_DIR, { recursive: true }); - } -} - function loadCachedTools() { - if (!existsSync(TOOLS_FILE)) { - return null; - } - try { - return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); - } catch { - return null; - } + if (!existsSync(TOOLS_FILE)) return null; + try { return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); } catch { return null; } } function saveToolsCache(tools) { @@ -152,182 +106,193 @@ function saveToolsCache(tools) { writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2)); } -const cli = yargs(hideBin(process.argv)) - .scriptName('tfcode') - .wrap(100) - .help() - .alias('help', 'h') - .command({ - command: 'quickstart', - describe: 'show quick start guide', - handler: () => { - log(''); - log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); - log(`${COLORS.bold} tfcode - Quick Start Guide${COLORS.reset}`); - log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); - log(''); - log('Welcome to tfcode! Follow these steps to get started:'); - log(''); - log(`${COLORS.cyan}STEP 1: Set Your Credentials${COLORS.reset}`); - log(`${COLORS.dim} export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`); - log(`${COLORS.dim} export TF_API_KEY="your-api-key"${COLORS.reset}`); - log(`${COLORS.dim} export TF_REGION="au"${COLORS.reset}`); - log(`${COLORS.dim} Regions: dev, au, eu, us${COLORS.reset}`); - log(''); - log(`${COLORS.cyan}STEP 2: Validate Connection${COLORS.reset}`); - log(`${COLORS.dim} tfcode validate${COLORS.reset}`); - log(''); - log(`${COLORS.cyan}STEP 3: Sync Your Tools${COLORS.reset}`); - log(`${COLORS.dim} tfcode sync${COLORS.reset}`); - log(''); - log(`${COLORS.cyan}STEP 4: Start Coding!${COLORS.reset}`); - log(`${COLORS.dim} tfcode${COLORS.reset}`); - log(''); - log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); - log(''); - log(' Useful Commands:'); - log(''); - log(' tfcode validate Test your credentials'); - log(' tfcode sync Sync tools from workspace'); - log(' tfcode tools list Show all your tools'); - log(''); - log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`); - log(''); - log(' Need help? https://toothfairyai.com/developers/tfcode'); - log(''); - } - }) - .command({ - command: 'validate', - describe: 'validate ToothFairyAI credentials', - handler: async () => { - info('Validating ToothFairyAI credentials...'); - log(''); - - try { - const result = await runPythonSync('validate'); - - if (result.success) { - success('Credentials valid'); - if (result.base_url) { - log(`${COLORS.dim} API URL: ${result.base_url}${COLORS.reset}`); - } - if (result.workspace_id) { - log(`${COLORS.dim} Workspace ID: ${result.workspace_id}${COLORS.reset}`); - } - } else { - error(`Validation failed: ${result.error || 'Unknown error'}`); - log(''); - log(`${COLORS.dim}Check your credentials:${COLORS.reset}`); - log(`${COLORS.dim} TF_WORKSPACE_ID: ${process.env.TF_WORKSPACE_ID || 'not set'}${COLORS.reset}`); - log(`${COLORS.dim} TF_API_KEY: ${process.env.TF_API_KEY ? '***' + process.env.TF_API_KEY.slice(-4) : 'not set'}${COLORS.reset}`); - log(`${COLORS.dim} TF_REGION: ${process.env.TF_REGION || 'au (default)'}${COLORS.reset}`); - process.exit(1); - } - } catch (e) { - error(`Failed to validate: ${e.message}`); - log(''); - log(`${COLORS.dim}Make sure Python 3.10+ and the ToothFairyAI SDK are installed:${COLORS.reset}`); - log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`); - process.exit(1); - } - } - }) - .command({ - command: 'sync', - describe: 'sync tools from ToothFairyAI workspace', - handler: async () => { - info('Syncing tools from ToothFairyAI workspace...'); - log(''); - - try { - const result = await runPythonSync('sync'); - - if (result.success) { - saveToolsCache(result); - success(`Synced ${result.tools.length} tools`); - log(''); - - if (result.by_type && Object.keys(result.by_type).length > 0) { - log('By type:'); - for (const [type, count] of Object.entries(result.by_type)) { - log(` ${type}: ${count}`); - } - log(''); - } - } else { - error(`Sync failed: ${result.error || 'Unknown error'}`); - process.exit(1); - } - } catch (e) { - error(`Failed to sync: ${e.message}`); - process.exit(1); - } - } - }) - .command({ - command: 'tools', - describe: 'manage tools', - builder: (yargs) => { - return yargs - .command({ - command: 'list', - describe: 'list synced tools', - builder: (yargs) => { - return yargs.option('type', { - type: 'string', - describe: 'filter by type (api_function)' - }); - }, - handler: (args) => { - const cached = loadCachedTools(); - - if (!cached || !cached.success) { - error('No tools synced. Run \'tfcode sync\' first.'); - process.exit(1); - return; - } - - let tools = cached.tools; - - if (args.type) { - tools = tools.filter(t => t.tool_type === args.type); - } - - if (tools.length === 0) { - log('No tools found.'); - return; - } - - log(''); - log(`${tools.length} tool(s):`); - log(''); - - for (const tool of tools) { - log(` ${COLORS.cyan}${tool.name}${COLORS.reset}`); - log(` Type: ${tool.tool_type}`); - if (tool.description) { - log(` ${COLORS.dim}${tool.description.slice(0, 60)}${tool.description.length > 60 ? '...' : ''}${COLORS.reset}`); - } - log(` Auth: ${tool.auth_via}`); - log(''); - } - } - }) - .demandCommand(); - }, - handler: () => {} - }) - .demandCommand() - .strict() - .fail((msg, err) => { - if (msg) { - error(msg); - } - if (err) { - error(err.message); - } - process.exit(1); +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()); }); }); +} -cli.parse(); \ No newline at end of file +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 --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(''); +} + +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[3] === '--type' && args[4]) tools = tools.filter(t => t.tool_type === args[4]); + 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) { + // Show help instead of trying TUI (TUI requires full build) + showHelp(); +} else { + error(`Unknown command: ${command}`); + showHelp(); + process.exit(1); +} \ No newline at end of file diff --git a/packages/tfcode/package.json b/packages/tfcode/package.json index fc5480ebc..cf996a073 100644 --- a/packages/tfcode/package.json +++ b/packages/tfcode/package.json @@ -1,6 +1,6 @@ { "name": "@toothfairyai/tfcode", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.9", "description": "ToothFairyAI's official AI coding agent", "keywords": ["toothfairyai", "ai", "coding", "cli"], "author": "ToothFairyAI",