@toothfairyai/tfcode 1.0.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/tfcode.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import yargs from 'yargs';
4
- import { hideBin } from 'yargs/helpers';
5
3
  import { spawn } from 'child_process';
6
4
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
7
- import { join } from 'path';
5
+ import { join, dirname } from 'path';
8
6
  import { homedir } from 'os';
9
7
  import * as readline from 'readline';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
10
12
 
11
13
  const TFCODE_DIR = join(homedir(), '.tfcode');
12
14
  const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
@@ -24,109 +26,25 @@ const COLORS = {
24
26
  magenta: '\x1b[35m'
25
27
  };
26
28
 
27
- function log(msg) {
28
- console.log(msg);
29
- }
30
-
31
- function success(msg) {
32
- console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`);
33
- }
34
-
35
- function error(msg) {
36
- console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`);
37
- }
38
-
39
- function info(msg) {
40
- console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`);
41
- }
42
-
43
- function question(rl, prompt, hidden = false) {
44
- return new Promise((resolve) => {
45
- if (hidden && process.stdin.isTTY) {
46
- // Use simpler approach for hidden input
47
- process.stdout.write(prompt);
48
-
49
- let input = '';
50
- const stdin = process.stdin;
51
-
52
- stdin.setRawMode(true);
53
- stdin.setEncoding('utf8');
54
- stdin.resume();
55
-
56
- const onKeypress = (str) => {
57
- if (str === '\n' || str === '\r' || str === '\u0004') {
58
- stdin.setRawMode(false);
59
- stdin.pause();
60
- stdin.removeListener('data', onKeypress);
61
- process.stdout.write('\n');
62
- resolve(input);
63
- } else if (str === '\u0003') {
64
- process.stdout.write('\n');
65
- process.exit();
66
- } else if (str === '\u007F' || str === '\b') {
67
- input = input.slice(0, -1);
68
- } else if (str.length === 1 && str.charCodeAt(0) >= 32) {
69
- input += str;
70
- }
71
- };
72
-
73
- stdin.on('data', onKeypress);
74
- } else {
75
- rl.question(prompt, (answer) => {
76
- resolve(answer.trim());
77
- });
78
- }
79
- });
80
- }
81
-
82
- function select(prompt, options) {
83
- return new Promise((resolve) => {
84
- log('');
85
- log(prompt);
86
- log('');
87
- options.forEach((opt, i) => {
88
- log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`);
89
- });
90
- log('');
91
-
92
- // Create a fresh readline for select
93
- const rlSelect = readline.createInterface({
94
- input: process.stdin,
95
- output: process.stdout
96
- });
97
-
98
- rlSelect.question('Select (1-' + options.length + '): ', (answer) => {
99
- rlSelect.close();
100
- const idx = parseInt(answer.trim()) - 1;
101
- resolve(idx >= 0 && idx < options.length ? idx : 0);
102
- });
103
- });
104
- }
29
+ function log(msg) { console.log(msg); }
30
+ function success(msg) { console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`); }
31
+ function error(msg) { console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`); }
32
+ function info(msg) { console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`); }
105
33
 
106
34
  function ensureConfigDir() {
107
- if (!existsSync(TFCODE_DIR)) {
108
- mkdirSync(TFCODE_DIR, { recursive: true });
109
- }
35
+ if (!existsSync(TFCODE_DIR)) mkdirSync(TFCODE_DIR, { recursive: true });
110
36
  }
111
37
 
112
38
  function loadConfig() {
113
- // Priority: env vars > config file
114
39
  const envConfig = {
115
40
  workspace_id: process.env.TF_WORKSPACE_ID,
116
41
  api_key: process.env.TF_API_KEY,
117
42
  region: process.env.TF_REGION
118
43
  };
119
-
120
- if (envConfig.workspace_id && envConfig.api_key) {
121
- return envConfig;
122
- }
123
-
44
+ if (envConfig.workspace_id && envConfig.api_key) return envConfig;
124
45
  if (existsSync(CONFIG_FILE)) {
125
- try {
126
- return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
127
- } catch {}
46
+ try { return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')); } catch {}
128
47
  }
129
-
130
48
  return null;
131
49
  }
132
50
 
@@ -141,107 +59,46 @@ function runPythonSync(method, config = null) {
141
59
  const region = config?.region || process.env.TF_REGION || 'au';
142
60
 
143
61
  const pythonCode = `
144
- import json
145
- import sys
146
- import os
147
-
62
+ import json, sys, os
148
63
  try:
149
64
  os.environ["TF_WORKSPACE_ID"] = "${wsId}"
150
65
  os.environ["TF_API_KEY"] = "${apiKey}"
151
66
  os.environ["TF_REGION"] = "${region}"
152
-
153
67
  from tf_sync.config import load_config, validate_credentials, Region
154
68
  from tf_sync.tools import sync_tools
155
69
  from tf_sync.config import get_region_urls
156
70
 
157
71
  method = "${method}"
158
-
159
72
  if method == "validate":
160
73
  config = load_config()
161
74
  result = validate_credentials(config)
162
75
  urls = get_region_urls(config.region)
163
- print(json.dumps({
164
- "success": result.success,
165
- "workspace_id": result.workspace_id,
166
- "workspace_name": result.workspace_name,
167
- "error": result.error,
168
- "base_url": urls["base_url"]
169
- }))
170
-
76
+ print(json.dumps({"success": result.success, "workspace_id": result.workspace_id, "workspace_name": result.workspace_name, "error": result.error, "base_url": urls["base_url"]}))
171
77
  elif method == "sync":
172
78
  config = load_config()
173
79
  result = sync_tools(config)
174
-
175
- tools_data = []
176
- for tool in result.tools:
177
- tools_data.append({
178
- "id": tool.id,
179
- "name": tool.name,
180
- "description": tool.description,
181
- "tool_type": tool.tool_type.value,
182
- "request_type": tool.request_type.value if tool.request_type else None,
183
- "url": tool.url,
184
- "auth_via": tool.auth_via
185
- })
186
-
187
- print(json.dumps({
188
- "success": result.success,
189
- "tools": tools_data,
190
- "by_type": result.by_type,
191
- "error": result.error
192
- }))
193
-
80
+ 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]
81
+ print(json.dumps({"success": result.success, "tools": tools_data, "by_type": result.by_type, "error": result.error}))
194
82
  except Exception as e:
195
83
  print(json.dumps({"success": False, "error": str(e)}))
196
- sys.exit(0)
197
84
  `;
198
85
 
199
86
  return new Promise((resolve, reject) => {
200
- const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3';
201
- const proc = spawn(pythonPath, ['-c', pythonCode], {
202
- env: { ...process.env }
203
- });
204
-
205
- let stdout = '';
206
- let stderr = '';
207
-
208
- proc.stdout.on('data', (data) => {
209
- stdout += data.toString();
210
- });
211
-
212
- proc.stderr.on('data', (data) => {
213
- stderr += data.toString();
214
- });
215
-
87
+ const proc = spawn(process.env.TFCODE_PYTHON_PATH || 'python3', ['-c', pythonCode], { env: { ...process.env } });
88
+ let stdout = '', stderr = '';
89
+ proc.stdout.on('data', (d) => stdout += d);
90
+ proc.stderr.on('data', (d) => stderr += d);
216
91
  proc.on('close', (code) => {
217
- if (code !== 0 && !stdout) {
218
- reject(new Error(`Python sync failed: ${stderr}`));
219
- return;
220
- }
221
-
222
- try {
223
- const result = JSON.parse(stdout.trim());
224
- resolve(result);
225
- } catch (e) {
226
- reject(new Error(`Failed to parse Python output: ${stdout}\nstderr: ${stderr}`));
227
- }
228
- });
229
-
230
- proc.on('error', (err) => {
231
- reject(err);
92
+ if (code !== 0 && !stdout) reject(new Error(`Python failed: ${stderr}`));
93
+ else try { resolve(JSON.parse(stdout.trim())); } catch (e) { reject(new Error(`Parse error: ${stdout}`)); }
232
94
  });
95
+ proc.on('error', reject);
233
96
  });
234
97
  }
235
98
 
236
99
  function loadCachedTools() {
237
- if (!existsSync(TOOLS_FILE)) {
238
- return null;
239
- }
240
- try {
241
- return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8'));
242
- } catch {
243
- return null;
244
- }
100
+ if (!existsSync(TOOLS_FILE)) return null;
101
+ try { return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); } catch { return null; }
245
102
  }
246
103
 
247
104
  function saveToolsCache(tools) {
@@ -249,6 +106,24 @@ function saveToolsCache(tools) {
249
106
  writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
250
107
  }
251
108
 
109
+ async function question(prompt) {
110
+ return new Promise((resolve) => {
111
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
112
+ rl.question(prompt, (answer) => { rl.close(); resolve(answer.trim()); });
113
+ });
114
+ }
115
+
116
+ async function select(prompt, options) {
117
+ log('');
118
+ log(prompt);
119
+ log('');
120
+ options.forEach((opt, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`));
121
+ log('');
122
+ const answer = await question('Select (1-' + options.length + '): ');
123
+ const idx = parseInt(answer) - 1;
124
+ return idx >= 0 && idx < options.length ? idx : 0;
125
+ }
126
+
252
127
  async function interactiveSetup() {
253
128
  log('');
254
129
  log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
@@ -261,439 +136,163 @@ async function interactiveSetup() {
261
136
  log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`);
262
137
  log('');
263
138
 
264
- // Step 1: Workspace ID
265
139
  log(`${COLORS.bold}Step 1: Workspace ID${COLORS.reset}`);
266
- log(`${COLORS.dim}This is your workspace UUID (e.g., 12345678-1234-1234-1234-123456789012)${COLORS.reset}`);
140
+ log(`${COLORS.dim}This is your workspace UUID${COLORS.reset}`);
267
141
  log('');
268
-
269
- const workspaceId = await new Promise((resolve) => {
270
- const rl = readline.createInterface({
271
- input: process.stdin,
272
- output: process.stdout
273
- });
274
- rl.question('Enter your Workspace ID: ', (answer) => {
275
- rl.close();
276
- resolve(answer.trim());
277
- });
278
- });
279
-
280
- if (!workspaceId) {
281
- error('Workspace ID is required');
282
- process.exit(1);
283
- }
284
-
142
+ const workspaceId = await question('Enter your Workspace ID: ');
143
+ if (!workspaceId) { error('Workspace ID is required'); process.exit(1); }
285
144
  log('');
286
145
 
287
- // Step 2: API Key
288
146
  log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`);
289
147
  log(`${COLORS.dim}Paste or type your API key${COLORS.reset}`);
290
148
  log('');
291
-
292
- const apiKey = await new Promise((resolve) => {
293
- const rl = readline.createInterface({
294
- input: process.stdin,
295
- output: process.stdout
296
- });
297
-
298
- // Simple approach - just show the input (API keys are long anyway)
299
- // This allows paste and works reliably
300
- rl.question('Enter your API Key: ', (answer) => {
301
- rl.close();
302
- resolve(answer.trim());
303
- });
304
- });
305
-
306
- if (!apiKey) {
307
- error('API Key is required');
308
- process.exit(1);
309
- }
310
-
149
+ const apiKey = await question('Enter your API Key: ');
150
+ if (!apiKey) { error('API Key is required'); process.exit(1); }
311
151
  log('');
312
152
 
313
- // Step 3: Region
314
153
  log(`${COLORS.bold}Step 3: Region${COLORS.reset}`);
315
154
  const regions = ['dev (Development)', 'au (Australia)', 'eu (Europe)', 'us (United States)'];
316
155
  const regionIdx = await select('Select your region:', regions);
317
- const regions_map = ['dev', 'au', 'eu', 'us'];
318
- const region = regions_map[regionIdx];
156
+ const region = ['dev', 'au', 'eu', 'us'][regionIdx];
319
157
 
320
158
  log('');
321
159
  log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
322
160
  log('');
323
-
324
- // Summary
325
161
  log(`${COLORS.bold}Summary:${COLORS.reset}`);
326
162
  log(` Workspace ID: ${workspaceId}`);
327
163
  log(` API Key: ***${apiKey.slice(-4)}`);
328
164
  log(` Region: ${region}`);
329
165
  log('');
330
166
 
331
- const confirm = await new Promise((resolve) => {
332
- const rl = readline.createInterface({
333
- input: process.stdin,
334
- output: process.stdout
335
- });
336
- rl.question('Save these credentials? (Y/n): ', (answer) => {
337
- rl.close();
338
- resolve(answer.trim());
339
- });
340
- });
167
+ const confirm = await question('Save these credentials? (Y/n): ');
168
+ if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') { log('Setup cancelled.'); return; }
341
169
 
342
- if (confirm.toLowerCase() !== 'n' && confirm.toLowerCase() !== 'no') {
343
- const config = { workspace_id: workspaceId, api_key: apiKey, region };
344
- saveConfig(config);
345
- success('Credentials saved to ~/.tfcode/config.json');
346
- log('');
347
-
348
- // Validate
349
- const testNow = await new Promise((resolve) => {
350
- const rl = readline.createInterface({
351
- input: process.stdin,
352
- output: process.stdout
353
- });
354
- rl.question('Validate credentials now? (Y/n): ', (answer) => {
355
- rl.close();
356
- resolve(answer.trim());
357
- });
358
- });
359
-
360
- if (testNow.toLowerCase() !== 'n' && testNow.toLowerCase() !== 'no') {
361
- log('');
362
- info('Validating credentials...');
170
+ const config = { workspace_id: workspaceId, api_key: apiKey, region };
171
+ saveConfig(config);
172
+ success('Credentials saved to ~/.tfcode/config.json');
173
+ log('');
174
+
175
+ const testNow = await question('Validate credentials now? (Y/n): ');
176
+ if (testNow.toLowerCase() === 'n' || testNow.toLowerCase() === 'no') return;
177
+
178
+ log('');
179
+ info('Validating credentials...');
180
+ log('');
181
+
182
+ try {
183
+ const result = await runPythonSync('validate', config);
184
+ if (result.success) {
185
+ success('Credentials valid!');
186
+ log(` API URL: ${result.base_url}`);
187
+ log(` Workspace ID: ${result.workspace_id}`);
363
188
  log('');
364
189
 
365
- try {
366
- const result = await runPythonSync('validate', config);
367
-
368
- if (result.success) {
369
- success('Credentials valid!');
370
- log(` API URL: ${result.base_url}`);
371
- log(` Workspace ID: ${result.workspace_id}`);
372
- log('');
373
-
374
- const syncNow = await new Promise((resolve) => {
375
- const rl = readline.createInterface({
376
- input: process.stdin,
377
- output: process.stdout
378
- });
379
- rl.question('Sync tools now? (Y/n): ', (answer) => {
380
- rl.close();
381
- resolve(answer.trim());
382
- });
383
- });
384
-
385
- if (syncNow.toLowerCase() !== 'n' && syncNow.toLowerCase() !== 'no') {
386
- log('');
387
- info('Syncing tools...');
388
- log('');
389
-
390
- try {
391
- const syncResult = await runPythonSync('sync', config);
392
-
393
- if (syncResult.success) {
394
- saveToolsCache(syncResult);
395
- success(`Synced ${syncResult.tools.length} tools`);
396
-
397
- if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
398
- log('');
399
- log('By type:');
400
- for (const [type, count] of Object.entries(syncResult.by_type)) {
401
- log(` ${type}: ${count}`);
402
- }
403
- }
404
- } else {
405
- error(`Sync failed: ${syncResult.error}`);
406
- }
407
- } catch (e) {
408
- error(`Sync failed: ${e.message}`);
409
- }
410
- }
411
- } else {
412
- error(`Validation failed: ${result.error}`);
413
- log('');
414
- log(`${COLORS.dim}Check your credentials and try again with: tfcode setup${COLORS.reset}`);
415
- }
416
- } catch (e) {
417
- error(`Validation failed: ${e.message}`);
418
- log('');
419
- log(`${COLORS.dim}Make sure Python 3.10+ and toothfairyai SDK are installed:${COLORS.reset}`);
420
- log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
421
- }
422
- }
423
-
424
- log('');
425
- log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
426
- log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`);
427
- log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
428
- log('');
429
- log('You can now use tfcode:');
430
- log('');
431
- log(` ${COLORS.cyan}tfcode validate${COLORS.reset} ${COLORS.dim}Check credentials${COLORS.reset}`);
432
- log(` ${COLORS.cyan}tfcode sync${COLORS.reset} ${COLORS.dim}Sync tools${COLORS.reset}`);
433
- log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} ${COLORS.dim}List your tools${COLORS.reset}`);
434
- log('');
435
-
436
- } else {
437
- log('Setup cancelled.');
438
- }
439
- }
440
-
441
- const cli = yargs(hideBin(process.argv))
442
- .scriptName('tfcode')
443
- .wrap(100)
444
- .help()
445
- .alias('help', 'h')
446
- .command({
447
- command: '*',
448
- describe: 'start tfcode TUI',
449
- handler: async () => {
450
- // Try to run the full TUI
451
- const { spawn } = await import('child_process');
452
- const { existsSync } = await import('fs');
453
- const { join } = await import('path');
454
-
455
- // Check if we're in the source repo
456
- const possiblePaths = [
457
- join(__dirname, '..', 'src', 'index.ts'),
458
- join(__dirname, '..', '..', '..', 'src', 'index.ts'),
459
- ];
460
-
461
- let srcPath = null;
462
- for (const p of possiblePaths) {
463
- if (existsSync(p)) {
464
- srcPath = p;
465
- break;
466
- }
467
- }
190
+ const syncNow = await question('Sync tools now? (Y/n): ');
191
+ if (syncNow.toLowerCase() === 'n' || syncNow.toLowerCase() === 'no') return;
468
192
 
469
- if (srcPath) {
470
- // Run via bun or tsx
471
- const runner = process.env.TFCODE_RUNNER || (existsSync('/usr/local/bin/bun') ? 'bun' : 'npx');
472
- const args = runner === 'bun' ? ['run', srcPath] : ['tsx', srcPath];
473
-
474
- const child = spawn(runner, args, {
475
- stdio: 'inherit',
476
- cwd: __dirname,
477
- env: { ...process.env }
478
- });
479
-
480
- child.on('exit', (code) => {
481
- process.exit(code || 0);
482
- });
483
- } else {
484
- // Not in source repo - show message
485
- log('');
486
- log(`${COLORS.bold}${COLORS.cyan}tfcode${COLORS.reset} - ToothFairyAI's AI coding assistant`);
487
- log('');
488
- log('The full TUI requires the compiled binary.');
489
- log('');
490
- log('Available commands:');
491
- log(` ${COLORS.cyan}tfcode setup${COLORS.reset} Interactive credential setup`);
492
- log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Test your credentials`);
493
- log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools from workspace`);
494
- log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List synced tools`);
495
- log('');
496
- log(`${COLORS.dim}For full TUI, build from source:${COLORS.reset}`);
497
- log(`${COLORS.dim} git clone https://github.com/ToothFairyAI/tfcode${COLORS.reset}`);
498
- log(`${COLORS.dim} cd tfcode/packages/tfcode && bun install && bun run src/index.ts${COLORS.reset}`);
499
- log('');
500
- }
501
- }
502
- })
503
- .command({
504
- command: 'setup',
505
- describe: 'interactive credential setup',
506
- handler: async () => {
507
- await interactiveSetup();
508
- }
509
- })
510
- .command({
511
- command: 'quickstart',
512
- describe: 'show quick start guide',
513
- handler: () => {
514
- log('');
515
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
516
- log(`${COLORS.bold} tfcode - Quick Start Guide${COLORS.reset}`);
517
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
518
- log('');
519
- log('Welcome to tfcode! Follow these steps to get started:');
520
- log('');
521
- log(`${COLORS.cyan}OPTION A: Interactive Setup (Recommended)${COLORS.reset}`);
522
- log(`${COLORS.dim} tfcode setup${COLORS.reset}`);
523
- log('');
524
- log(`${COLORS.cyan}OPTION B: Manual Setup${COLORS.reset}`);
525
- log(`${COLORS.dim} export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`);
526
- log(`${COLORS.dim} export TF_API_KEY="your-api-key"${COLORS.reset}`);
527
- log(`${COLORS.dim} export TF_REGION="au"${COLORS.reset}`);
528
- log('');
529
- log(`${COLORS.cyan}Then:${COLORS.reset}`);
530
- log(`${COLORS.dim} tfcode validate${COLORS.reset}`);
531
- log(`${COLORS.dim} tfcode sync${COLORS.reset}`);
532
- log('');
533
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
534
- log('');
535
- log(' Commands:');
536
- log('');
537
- log(' tfcode setup Interactive credential setup');
538
- log(' tfcode validate Test your credentials');
539
- log(' tfcode sync Sync tools from workspace');
540
- log(' tfcode tools list Show all your tools');
541
- log('');
542
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
543
193
  log('');
544
- log(' Need help? https://toothfairyai.com/developers/tfcode');
194
+ info('Syncing tools...');
545
195
  log('');
546
- }
547
- })
548
- .command({
549
- command: 'validate',
550
- describe: 'validate ToothFairyAI credentials',
551
- handler: async () => {
552
- const config = loadConfig();
553
-
554
- if (!config) {
555
- error('No credentials found.');
556
- log('');
557
- log('Run interactive setup:');
558
- log(` ${COLORS.cyan}tfcode setup${COLORS.reset}`);
559
- log('');
560
- log('Or set environment variables:');
561
- log(` ${COLORS.dim}export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`);
562
- log(` ${COLORS.dim}export TF_API_KEY="your-api-key"${COLORS.reset}`);
563
- process.exit(1);
564
- return;
565
- }
566
-
567
- info('Validating ToothFairyAI credentials...');
568
- log('');
569
-
570
- try {
571
- const result = await runPythonSync('validate', config);
572
-
573
- if (result.success) {
574
- success('Credentials valid');
575
- if (result.base_url) {
576
- log(`${COLORS.dim} API URL: ${result.base_url}${COLORS.reset}`);
577
- }
578
- if (result.workspace_id) {
579
- log(`${COLORS.dim} Workspace ID: ${result.workspace_id}${COLORS.reset}`);
580
- }
581
- } else {
582
- error(`Validation failed: ${result.error || 'Unknown error'}`);
583
- process.exit(1);
584
- }
585
- } catch (e) {
586
- error(`Failed to validate: ${e.message}`);
587
- log('');
588
- log(`${COLORS.dim}Make sure Python 3.10+ and toothfairyai SDK are installed:${COLORS.reset}`);
589
- log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
590
- process.exit(1);
591
- }
592
- }
593
- })
594
- .command({
595
- command: 'sync',
596
- describe: 'sync tools from ToothFairyAI workspace',
597
- handler: async () => {
598
- const config = loadConfig();
599
196
 
600
- if (!config) {
601
- error('No credentials found. Run: tfcode setup');
602
- process.exit(1);
603
- return;
604
- }
605
-
606
- info('Syncing tools from ToothFairyAI workspace...');
607
- log('');
608
-
609
- try {
610
- const result = await runPythonSync('sync', config);
611
-
612
- if (result.success) {
613
- saveToolsCache(result);
614
- success(`Synced ${result.tools.length} tools`);
197
+ const syncResult = await runPythonSync('sync', config);
198
+ if (syncResult.success) {
199
+ saveToolsCache(syncResult);
200
+ success(`Synced ${syncResult.tools.length} tools`);
201
+ if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
615
202
  log('');
616
-
617
- if (result.by_type && Object.keys(result.by_type).length > 0) {
618
- log('By type:');
619
- for (const [type, count] of Object.entries(result.by_type)) {
620
- log(` ${type}: ${count}`);
621
- }
622
- log('');
623
- }
624
- } else {
625
- error(`Sync failed: ${result.error || 'Unknown error'}`);
626
- process.exit(1);
203
+ log('By type:');
204
+ for (const [type, count] of Object.entries(syncResult.by_type)) log(` ${type}: ${count}`);
627
205
  }
628
- } catch (e) {
629
- error(`Failed to sync: ${e.message}`);
630
- process.exit(1);
206
+ } else {
207
+ error(`Sync failed: ${syncResult.error}`);
631
208
  }
209
+ } else {
210
+ error(`Validation failed: ${result.error}`);
632
211
  }
633
- })
634
- .command({
635
- command: 'tools',
636
- describe: 'manage tools',
637
- builder: (yargs) => {
638
- return yargs
639
- .command({
640
- command: 'list',
641
- describe: 'list synced tools',
642
- builder: (yargs) => {
643
- return yargs.option('type', {
644
- type: 'string',
645
- describe: 'filter by type (api_function)'
646
- });
647
- },
648
- handler: (args) => {
649
- const cached = loadCachedTools();
650
-
651
- if (!cached || !cached.success) {
652
- error('No tools synced. Run \'tfcode sync\' first.');
653
- process.exit(1);
654
- return;
655
- }
656
-
657
- let tools = cached.tools;
658
-
659
- if (args.type) {
660
- tools = tools.filter(t => t.tool_type === args.type);
661
- }
662
-
663
- if (tools.length === 0) {
664
- log('No tools found.');
665
- return;
666
- }
667
-
668
- log('');
669
- log(`${tools.length} tool(s):`);
670
- log('');
212
+ } catch (e) {
213
+ error(`Failed: ${e.message}`);
214
+ }
215
+
216
+ log('');
217
+ log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
218
+ log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`);
219
+ log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
220
+ log('');
221
+ log('Commands:');
222
+ log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Check credentials`);
223
+ log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools`);
224
+ log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List tools`);
225
+ log('');
226
+ }
671
227
 
672
- for (const tool of tools) {
673
- log(` ${COLORS.cyan}${tool.name}${COLORS.reset}`);
674
- log(` Type: ${tool.tool_type}`);
675
- if (tool.description) {
676
- log(` ${COLORS.dim}${tool.description.slice(0, 60)}${tool.description.length > 60 ? '...' : ''}${COLORS.reset}`);
677
- }
678
- log(` Auth: ${tool.auth_via}`);
679
- log('');
680
- }
681
- }
682
- })
683
- .demandCommand();
684
- },
685
- handler: () => {}
686
- })
687
- .demandCommand()
688
- .strict()
689
- .fail((msg, err) => {
690
- if (msg) {
691
- error(msg);
692
- }
693
- if (err) {
694
- error(err.message);
695
- }
696
- process.exit(1);
697
- });
228
+ function showHelp() {
229
+ log('');
230
+ log(`${COLORS.bold}tfcode${COLORS.reset} - ToothFairyAI's AI coding agent`);
231
+ log('');
232
+ log('Commands:');
233
+ log(` ${COLORS.cyan}tfcode setup${COLORS.reset} Interactive credential setup`);
234
+ log(` ${COLORS.cyan}tfcode validate${COLORS.reset} Test credentials`);
235
+ log(` ${COLORS.cyan}tfcode sync${COLORS.reset} Sync tools from workspace`);
236
+ log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} List synced tools`);
237
+ log(` ${COLORS.cyan}tfcode --help${COLORS.reset} Show this help`);
238
+ log(` ${COLORS.cyan}tfcode --version${COLORS.reset} Show version`);
239
+ log('');
240
+ log(`${COLORS.dim}For full TUI, run from source:${COLORS.reset}`);
241
+ log(`${COLORS.dim} bun run packages/tfcode/src/index.ts${COLORS.reset}`);
242
+ log('');
243
+ }
698
244
 
699
- cli.parse();
245
+ const args = process.argv.slice(2);
246
+ const command = args[0];
247
+
248
+ if (args.includes('--help') || args.includes('-h')) {
249
+ showHelp();
250
+ } else if (args.includes('--version') || args.includes('-v')) {
251
+ log('tfcode v1.0.0-beta.9');
252
+ } else if (command === 'setup') {
253
+ interactiveSetup();
254
+ } else if (command === 'validate') {
255
+ (async () => {
256
+ const config = loadConfig();
257
+ if (!config) { error('No credentials. Run: tfcode setup'); process.exit(1); }
258
+ info('Validating...');
259
+ try {
260
+ const result = await runPythonSync('validate', config);
261
+ if (result.success) { success('Credentials valid'); log(` API URL: ${result.base_url}`); }
262
+ else { error(`Failed: ${result.error}`); process.exit(1); }
263
+ } catch (e) { error(`Failed: ${e.message}`); process.exit(1); }
264
+ })();
265
+ } else if (command === 'sync') {
266
+ (async () => {
267
+ const config = loadConfig();
268
+ if (!config) { error('No credentials. Run: tfcode setup'); process.exit(1); }
269
+ info('Syncing tools...');
270
+ try {
271
+ const result = await runPythonSync('sync', config);
272
+ if (result.success) {
273
+ saveToolsCache(result);
274
+ success(`Synced ${result.tools.length} tools`);
275
+ if (result.by_type) { log(''); log('By type:'); for (const [t, c] of Object.entries(result.by_type)) log(` ${t}: ${c}`); }
276
+ } else { error(`Failed: ${result.error}`); process.exit(1); }
277
+ } catch (e) { error(`Failed: ${e.message}`); process.exit(1); }
278
+ })();
279
+ } else if (command === 'tools' && args[1] === 'list') {
280
+ const cached = loadCachedTools();
281
+ if (!cached?.success) { error('No tools. Run: tfcode sync'); process.exit(1); }
282
+ let tools = cached.tools;
283
+ if (args[3] === '--type' && args[4]) tools = tools.filter(t => t.tool_type === args[4]);
284
+ log(`\n${tools.length} tool(s):\n`);
285
+ for (const t of tools) {
286
+ log(` ${COLORS.cyan}${t.name}${COLORS.reset}`);
287
+ log(` Type: ${t.tool_type}`);
288
+ if (t.description) log(` ${COLORS.dim}${t.description.slice(0, 60)}${COLORS.reset}`);
289
+ log(` Auth: ${t.auth_via}\n`);
290
+ }
291
+ } else if (!command) {
292
+ // Show help instead of trying TUI (TUI requires full build)
293
+ showHelp();
294
+ } else {
295
+ error(`Unknown command: ${command}`);
296
+ showHelp();
297
+ process.exit(1);
298
+ }