feat: tfcode beta

This commit is contained in:
Gab
2026-03-24 14:01:32 +11:00
parent 4596310485
commit 4380efd658
10 changed files with 971 additions and 332 deletions

View File

@@ -0,0 +1,333 @@
#!/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 { homedir } from 'os';
const TFCODE_DIR = join(homedir(), '.tfcode');
const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
const COLORS = {
reset: '\x1b[0m',
bold: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
cyan: '\x1b[36m',
dim: '\x1b[90m',
yellow: '\x1b[33m'
};
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 runPythonSync(method) {
const pythonCode = `
import json
import sys
import os
try:
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 = []
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
}))
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();
});
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);
});
});
}
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;
}
}
function saveToolsCache(tools) {
ensureConfigDir();
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);
});
cli.parse();