mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
feat: tfcode
This commit is contained in:
parent
4380efd658
commit
7c015708cb
@ -17,7 +17,7 @@
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g tfcode
|
npm install -g @toothfairyai/tfcode@beta
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer will:
|
The installer will:
|
||||||
@ -26,6 +26,21 @@ The installer will:
|
|||||||
|
|
||||||
## Quick Start
|
## 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
|
```bash
|
||||||
# 1. Set your ToothFairyAI credentials
|
# 1. Set your ToothFairyAI credentials
|
||||||
export TF_WORKSPACE_ID="your-workspace-id"
|
export TF_WORKSPACE_ID="your-workspace-id"
|
||||||
@ -38,21 +53,20 @@ tfcode validate
|
|||||||
# 3. Sync tools from your workspace
|
# 3. Sync tools from your workspace
|
||||||
tfcode sync
|
tfcode sync
|
||||||
|
|
||||||
# 4. Start coding!
|
# 4. List your tools
|
||||||
tfcode
|
tfcode tools list
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `tfcode` | Start the AI coding assistant |
|
| `tfcode setup` | **Interactive credential setup** |
|
||||||
| `tfcode quickstart` | Show quick start guide |
|
| `tfcode quickstart` | Show quick start guide |
|
||||||
| `tfcode validate` | Test your credentials |
|
| `tfcode validate` | Test your credentials |
|
||||||
| `tfcode sync` | Sync tools from workspace |
|
| `tfcode sync` | Sync tools from workspace |
|
||||||
| `tfcode tools list` | List synced tools |
|
| `tfcode tools list` | List synced tools |
|
||||||
| `tfcode tools list --type mcp` | List MCP servers |
|
| `tfcode tools list --type api_function` | Filter by type |
|
||||||
| `tfcode tools credentials <name> --set` | Set API key for a tool |
|
|
||||||
|
|
||||||
## Regions
|
## Regions
|
||||||
|
|
||||||
@ -65,7 +79,17 @@ tfcode
|
|||||||
|
|
||||||
## Configuration
|
## 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
|
```bash
|
||||||
export TF_WORKSPACE_ID="your-workspace-id"
|
export TF_WORKSPACE_ID="your-workspace-id"
|
||||||
@ -73,18 +97,6 @@ export TF_API_KEY="your-api-key"
|
|||||||
export TF_REGION="au"
|
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
|
## Getting Your Credentials
|
||||||
|
|
||||||
1. Log in to [ToothFairyAI](https://app.toothfairyai.com)
|
1. Log in to [ToothFairyAI](https://app.toothfairyai.com)
|
||||||
@ -104,12 +116,19 @@ Install Python:
|
|||||||
|
|
||||||
- Check your API key is correct
|
- Check your API key is correct
|
||||||
- Generate a new key in ToothFairyAI Settings → API Keys
|
- Generate a new key in ToothFairyAI Settings → API Keys
|
||||||
|
- Run `tfcode setup` to re-enter credentials
|
||||||
|
|
||||||
### "Failed to validate: Connection test failed"
|
### "Failed to validate: Connection test failed"
|
||||||
|
|
||||||
Try a different region:
|
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
|
```bash
|
||||||
export TF_REGION="eu" # or us, au
|
tfcode setup
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
513
packages/tfcode/bin/tfcode.js
Normal file → Executable file
513
packages/tfcode/bin/tfcode.js
Normal file → Executable file
@ -1,15 +1,19 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import yargs from 'yargs';
|
|
||||||
import { hideBin } from 'yargs/helpers';
|
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { homedir } from 'os';
|
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 TFCODE_DIR = join(homedir(), '.tfcode');
|
||||||
const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
|
const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
|
||||||
const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
|
const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
|
||||||
|
const CONFIG_FILE = join(TFCODE_DIR, 'config.json');
|
||||||
|
|
||||||
const COLORS = {
|
const COLORS = {
|
||||||
reset: '\x1b[0m',
|
reset: '\x1b[0m',
|
||||||
@ -18,133 +22,83 @@ const COLORS = {
|
|||||||
red: '\x1b[31m',
|
red: '\x1b[31m',
|
||||||
cyan: '\x1b[36m',
|
cyan: '\x1b[36m',
|
||||||
dim: '\x1b[90m',
|
dim: '\x1b[90m',
|
||||||
yellow: '\x1b[33m'
|
yellow: '\x1b[33m',
|
||||||
|
magenta: '\x1b[35m'
|
||||||
};
|
};
|
||||||
|
|
||||||
function log(msg) {
|
function log(msg) { console.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) {
|
function loadConfig() {
|
||||||
console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`);
|
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) {
|
function saveConfig(config) {
|
||||||
console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`);
|
ensureConfigDir();
|
||||||
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function info(msg) {
|
function runPythonSync(method, config = null) {
|
||||||
console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`);
|
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';
|
||||||
function runPythonSync(method) {
|
|
||||||
const pythonCode = `
|
const pythonCode = `
|
||||||
import json
|
import json, sys, os
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
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.config import load_config, validate_credentials, Region
|
||||||
from tf_sync.tools import sync_tools
|
from tf_sync.tools import sync_tools
|
||||||
from tf_sync.config import get_region_urls
|
from tf_sync.config import get_region_urls
|
||||||
|
|
||||||
method = "${method}"
|
method = "${method}"
|
||||||
|
|
||||||
if method == "validate":
|
if method == "validate":
|
||||||
config = load_config()
|
config = load_config()
|
||||||
result = validate_credentials(config)
|
result = validate_credentials(config)
|
||||||
urls = get_region_urls(config.region)
|
urls = get_region_urls(config.region)
|
||||||
print(json.dumps({
|
print(json.dumps({"success": result.success, "workspace_id": result.workspace_id, "workspace_name": result.workspace_name, "error": result.error, "base_url": urls["base_url"]}))
|
||||||
"success": result.success,
|
|
||||||
"workspace_id": result.workspace_id,
|
|
||||||
"workspace_name": result.workspace_name,
|
|
||||||
"error": result.error,
|
|
||||||
"base_url": urls["base_url"]
|
|
||||||
}))
|
|
||||||
|
|
||||||
elif method == "sync":
|
elif method == "sync":
|
||||||
config = load_config()
|
config = load_config()
|
||||||
result = sync_tools(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} for t in result.tools]
|
||||||
tools_data = []
|
print(json.dumps({"success": result.success, "tools": tools_data, "by_type": result.by_type, "error": result.error}))
|
||||||
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:
|
except Exception as e:
|
||||||
print(json.dumps({"success": False, "error": str(e)}))
|
print(json.dumps({"success": False, "error": str(e)}))
|
||||||
sys.exit(0)
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3';
|
const proc = spawn(process.env.TFCODE_PYTHON_PATH || 'python3', ['-c', pythonCode], { env: { ...process.env } });
|
||||||
const proc = spawn(pythonPath, ['-c', pythonCode], {
|
let stdout = '', stderr = '';
|
||||||
env: {
|
proc.stdout.on('data', (d) => stdout += d);
|
||||||
...process.env,
|
proc.stderr.on('data', (d) => stderr += d);
|
||||||
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) => {
|
proc.on('close', (code) => {
|
||||||
if (code !== 0 && !stdout) {
|
if (code !== 0 && !stdout) reject(new Error(`Python failed: ${stderr}`));
|
||||||
reject(new Error(`Python sync failed: ${stderr}`));
|
else try { resolve(JSON.parse(stdout.trim())); } catch (e) { reject(new Error(`Parse error: ${stdout}`)); }
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
proc.on('error', reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureConfigDir() {
|
|
||||||
if (!existsSync(TFCODE_DIR)) {
|
|
||||||
mkdirSync(TFCODE_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadCachedTools() {
|
function loadCachedTools() {
|
||||||
if (!existsSync(TOOLS_FILE)) {
|
if (!existsSync(TOOLS_FILE)) return null;
|
||||||
return null;
|
try { return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); } catch { return null; }
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8'));
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveToolsCache(tools) {
|
function saveToolsCache(tools) {
|
||||||
@ -152,182 +106,193 @@ function saveToolsCache(tools) {
|
|||||||
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
|
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cli = yargs(hideBin(process.argv))
|
async function question(prompt) {
|
||||||
.scriptName('tfcode')
|
return new Promise((resolve) => {
|
||||||
.wrap(100)
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
.help()
|
rl.question(prompt, (answer) => { rl.close(); resolve(answer.trim()); });
|
||||||
.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();
|
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);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@toothfairyai/tfcode",
|
"name": "@toothfairyai/tfcode",
|
||||||
"version": "1.0.0-beta.1",
|
"version": "1.0.0-beta.9",
|
||||||
"description": "ToothFairyAI's official AI coding agent",
|
"description": "ToothFairyAI's official AI coding agent",
|
||||||
"keywords": ["toothfairyai", "ai", "coding", "cli"],
|
"keywords": ["toothfairyai", "ai", "coding", "cli"],
|
||||||
"author": "ToothFairyAI",
|
"author": "ToothFairyAI",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user