@toothfairyai/tfcode 1.0.0-beta.9 → 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,7 +1,5 @@
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
5
  import { join, dirname } from 'path';
@@ -28,109 +26,25 @@ const COLORS = {
28
26
  magenta: '\x1b[35m'
29
27
  };
30
28
 
31
- function log(msg) {
32
- console.log(msg);
33
- }
34
-
35
- function success(msg) {
36
- console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`);
37
- }
38
-
39
- function error(msg) {
40
- console.error(`${COLORS.red}✗${COLORS.reset} ${msg}`);
41
- }
42
-
43
- function info(msg) {
44
- console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`);
45
- }
46
-
47
- function question(rl, prompt, hidden = false) {
48
- return new Promise((resolve) => {
49
- if (hidden && process.stdin.isTTY) {
50
- // Use simpler approach for hidden input
51
- process.stdout.write(prompt);
52
-
53
- let input = '';
54
- const stdin = process.stdin;
55
-
56
- stdin.setRawMode(true);
57
- stdin.setEncoding('utf8');
58
- stdin.resume();
59
-
60
- const onKeypress = (str) => {
61
- if (str === '\n' || str === '\r' || str === '\u0004') {
62
- stdin.setRawMode(false);
63
- stdin.pause();
64
- stdin.removeListener('data', onKeypress);
65
- process.stdout.write('\n');
66
- resolve(input);
67
- } else if (str === '\u0003') {
68
- process.stdout.write('\n');
69
- process.exit();
70
- } else if (str === '\u007F' || str === '\b') {
71
- input = input.slice(0, -1);
72
- } else if (str.length === 1 && str.charCodeAt(0) >= 32) {
73
- input += str;
74
- }
75
- };
76
-
77
- stdin.on('data', onKeypress);
78
- } else {
79
- rl.question(prompt, (answer) => {
80
- resolve(answer.trim());
81
- });
82
- }
83
- });
84
- }
85
-
86
- function select(prompt, options) {
87
- return new Promise((resolve) => {
88
- log('');
89
- log(prompt);
90
- log('');
91
- options.forEach((opt, i) => {
92
- log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`);
93
- });
94
- log('');
95
-
96
- // Create a fresh readline for select
97
- const rlSelect = readline.createInterface({
98
- input: process.stdin,
99
- output: process.stdout
100
- });
101
-
102
- rlSelect.question('Select (1-' + options.length + '): ', (answer) => {
103
- rlSelect.close();
104
- const idx = parseInt(answer.trim()) - 1;
105
- resolve(idx >= 0 && idx < options.length ? idx : 0);
106
- });
107
- });
108
- }
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}`); }
109
33
 
110
34
  function ensureConfigDir() {
111
- if (!existsSync(TFCODE_DIR)) {
112
- mkdirSync(TFCODE_DIR, { recursive: true });
113
- }
35
+ if (!existsSync(TFCODE_DIR)) mkdirSync(TFCODE_DIR, { recursive: true });
114
36
  }
115
37
 
116
38
  function loadConfig() {
117
- // Priority: env vars > config file
118
39
  const envConfig = {
119
40
  workspace_id: process.env.TF_WORKSPACE_ID,
120
41
  api_key: process.env.TF_API_KEY,
121
42
  region: process.env.TF_REGION
122
43
  };
123
-
124
- if (envConfig.workspace_id && envConfig.api_key) {
125
- return envConfig;
126
- }
127
-
44
+ if (envConfig.workspace_id && envConfig.api_key) return envConfig;
128
45
  if (existsSync(CONFIG_FILE)) {
129
- try {
130
- return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
131
- } catch {}
46
+ try { return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')); } catch {}
132
47
  }
133
-
134
48
  return null;
135
49
  }
136
50
 
@@ -145,107 +59,46 @@ function runPythonSync(method, config = null) {
145
59
  const region = config?.region || process.env.TF_REGION || 'au';
146
60
 
147
61
  const pythonCode = `
148
- import json
149
- import sys
150
- import os
151
-
62
+ import json, sys, os
152
63
  try:
153
64
  os.environ["TF_WORKSPACE_ID"] = "${wsId}"
154
65
  os.environ["TF_API_KEY"] = "${apiKey}"
155
66
  os.environ["TF_REGION"] = "${region}"
156
-
157
67
  from tf_sync.config import load_config, validate_credentials, Region
158
68
  from tf_sync.tools import sync_tools
159
69
  from tf_sync.config import get_region_urls
160
70
 
161
71
  method = "${method}"
162
-
163
72
  if method == "validate":
164
73
  config = load_config()
165
74
  result = validate_credentials(config)
166
75
  urls = get_region_urls(config.region)
167
- print(json.dumps({
168
- "success": result.success,
169
- "workspace_id": result.workspace_id,
170
- "workspace_name": result.workspace_name,
171
- "error": result.error,
172
- "base_url": urls["base_url"]
173
- }))
174
-
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"]}))
175
77
  elif method == "sync":
176
78
  config = load_config()
177
79
  result = sync_tools(config)
178
-
179
- tools_data = []
180
- for tool in result.tools:
181
- tools_data.append({
182
- "id": tool.id,
183
- "name": tool.name,
184
- "description": tool.description,
185
- "tool_type": tool.tool_type.value,
186
- "request_type": tool.request_type.value if tool.request_type else None,
187
- "url": tool.url,
188
- "auth_via": tool.auth_via
189
- })
190
-
191
- print(json.dumps({
192
- "success": result.success,
193
- "tools": tools_data,
194
- "by_type": result.by_type,
195
- "error": result.error
196
- }))
197
-
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}))
198
82
  except Exception as e:
199
83
  print(json.dumps({"success": False, "error": str(e)}))
200
- sys.exit(0)
201
84
  `;
202
85
 
203
86
  return new Promise((resolve, reject) => {
204
- const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3';
205
- const proc = spawn(pythonPath, ['-c', pythonCode], {
206
- env: { ...process.env }
207
- });
208
-
209
- let stdout = '';
210
- let stderr = '';
211
-
212
- proc.stdout.on('data', (data) => {
213
- stdout += data.toString();
214
- });
215
-
216
- proc.stderr.on('data', (data) => {
217
- stderr += data.toString();
218
- });
219
-
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);
220
91
  proc.on('close', (code) => {
221
- if (code !== 0 && !stdout) {
222
- reject(new Error(`Python sync failed: ${stderr}`));
223
- return;
224
- }
225
-
226
- try {
227
- const result = JSON.parse(stdout.trim());
228
- resolve(result);
229
- } catch (e) {
230
- reject(new Error(`Failed to parse Python output: ${stdout}\nstderr: ${stderr}`));
231
- }
232
- });
233
-
234
- proc.on('error', (err) => {
235
- 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}`)); }
236
94
  });
95
+ proc.on('error', reject);
237
96
  });
238
97
  }
239
98
 
240
99
  function loadCachedTools() {
241
- if (!existsSync(TOOLS_FILE)) {
242
- return null;
243
- }
244
- try {
245
- return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8'));
246
- } catch {
247
- return null;
248
- }
100
+ if (!existsSync(TOOLS_FILE)) return null;
101
+ try { return JSON.parse(readFileSync(TOOLS_FILE, 'utf-8')); } catch { return null; }
249
102
  }
250
103
 
251
104
  function saveToolsCache(tools) {
@@ -253,6 +106,24 @@ function saveToolsCache(tools) {
253
106
  writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
254
107
  }
255
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
+
256
127
  async function interactiveSetup() {
257
128
  log('');
258
129
  log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
@@ -265,435 +136,163 @@ async function interactiveSetup() {
265
136
  log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`);
266
137
  log('');
267
138
 
268
- // Step 1: Workspace ID
269
139
  log(`${COLORS.bold}Step 1: Workspace ID${COLORS.reset}`);
270
- 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}`);
271
141
  log('');
272
-
273
- const workspaceId = await new Promise((resolve) => {
274
- const rl = readline.createInterface({
275
- input: process.stdin,
276
- output: process.stdout
277
- });
278
- rl.question('Enter your Workspace ID: ', (answer) => {
279
- rl.close();
280
- resolve(answer.trim());
281
- });
282
- });
283
-
284
- if (!workspaceId) {
285
- error('Workspace ID is required');
286
- process.exit(1);
287
- }
288
-
142
+ const workspaceId = await question('Enter your Workspace ID: ');
143
+ if (!workspaceId) { error('Workspace ID is required'); process.exit(1); }
289
144
  log('');
290
145
 
291
- // Step 2: API Key
292
146
  log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`);
293
147
  log(`${COLORS.dim}Paste or type your API key${COLORS.reset}`);
294
148
  log('');
295
-
296
- const apiKey = await new Promise((resolve) => {
297
- const rl = readline.createInterface({
298
- input: process.stdin,
299
- output: process.stdout
300
- });
301
-
302
- // Simple approach - just show the input (API keys are long anyway)
303
- // This allows paste and works reliably
304
- rl.question('Enter your API Key: ', (answer) => {
305
- rl.close();
306
- resolve(answer.trim());
307
- });
308
- });
309
-
310
- if (!apiKey) {
311
- error('API Key is required');
312
- process.exit(1);
313
- }
314
-
149
+ const apiKey = await question('Enter your API Key: ');
150
+ if (!apiKey) { error('API Key is required'); process.exit(1); }
315
151
  log('');
316
152
 
317
- // Step 3: Region
318
153
  log(`${COLORS.bold}Step 3: Region${COLORS.reset}`);
319
154
  const regions = ['dev (Development)', 'au (Australia)', 'eu (Europe)', 'us (United States)'];
320
155
  const regionIdx = await select('Select your region:', regions);
321
- const regions_map = ['dev', 'au', 'eu', 'us'];
322
- const region = regions_map[regionIdx];
156
+ const region = ['dev', 'au', 'eu', 'us'][regionIdx];
323
157
 
324
158
  log('');
325
159
  log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
326
160
  log('');
327
-
328
- // Summary
329
161
  log(`${COLORS.bold}Summary:${COLORS.reset}`);
330
162
  log(` Workspace ID: ${workspaceId}`);
331
163
  log(` API Key: ***${apiKey.slice(-4)}`);
332
164
  log(` Region: ${region}`);
333
165
  log('');
334
166
 
335
- const confirm = await new Promise((resolve) => {
336
- const rl = readline.createInterface({
337
- input: process.stdin,
338
- output: process.stdout
339
- });
340
- rl.question('Save these credentials? (Y/n): ', (answer) => {
341
- rl.close();
342
- resolve(answer.trim());
343
- });
344
- });
167
+ const confirm = await question('Save these credentials? (Y/n): ');
168
+ if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') { log('Setup cancelled.'); return; }
345
169
 
346
- if (confirm.toLowerCase() !== 'n' && confirm.toLowerCase() !== 'no') {
347
- const config = { workspace_id: workspaceId, api_key: apiKey, region };
348
- saveConfig(config);
349
- success('Credentials saved to ~/.tfcode/config.json');
350
- log('');
351
-
352
- // Validate
353
- const testNow = await new Promise((resolve) => {
354
- const rl = readline.createInterface({
355
- input: process.stdin,
356
- output: process.stdout
357
- });
358
- rl.question('Validate credentials now? (Y/n): ', (answer) => {
359
- rl.close();
360
- resolve(answer.trim());
361
- });
362
- });
363
-
364
- if (testNow.toLowerCase() !== 'n' && testNow.toLowerCase() !== 'no') {
365
- log('');
366
- 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}`);
367
188
  log('');
368
189
 
369
- try {
370
- const result = await runPythonSync('validate', config);
371
-
372
- if (result.success) {
373
- success('Credentials valid!');
374
- log(` API URL: ${result.base_url}`);
375
- log(` Workspace ID: ${result.workspace_id}`);
376
- log('');
377
-
378
- const syncNow = await new Promise((resolve) => {
379
- const rl = readline.createInterface({
380
- input: process.stdin,
381
- output: process.stdout
382
- });
383
- rl.question('Sync tools now? (Y/n): ', (answer) => {
384
- rl.close();
385
- resolve(answer.trim());
386
- });
387
- });
388
-
389
- if (syncNow.toLowerCase() !== 'n' && syncNow.toLowerCase() !== 'no') {
390
- log('');
391
- info('Syncing tools...');
392
- log('');
393
-
394
- try {
395
- const syncResult = await runPythonSync('sync', config);
396
-
397
- if (syncResult.success) {
398
- saveToolsCache(syncResult);
399
- success(`Synced ${syncResult.tools.length} tools`);
400
-
401
- if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
402
- log('');
403
- log('By type:');
404
- for (const [type, count] of Object.entries(syncResult.by_type)) {
405
- log(` ${type}: ${count}`);
406
- }
407
- }
408
- } else {
409
- error(`Sync failed: ${syncResult.error}`);
410
- }
411
- } catch (e) {
412
- error(`Sync failed: ${e.message}`);
413
- }
414
- }
415
- } else {
416
- error(`Validation failed: ${result.error}`);
417
- log('');
418
- log(`${COLORS.dim}Check your credentials and try again with: tfcode setup${COLORS.reset}`);
419
- }
420
- } catch (e) {
421
- error(`Validation failed: ${e.message}`);
422
- log('');
423
- log(`${COLORS.dim}Make sure Python 3.10+ and toothfairyai SDK are installed:${COLORS.reset}`);
424
- log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
425
- }
426
- }
427
-
428
- log('');
429
- log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
430
- log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`);
431
- log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
432
- log('');
433
- log('You can now use tfcode:');
434
- log('');
435
- log(` ${COLORS.cyan}tfcode validate${COLORS.reset} ${COLORS.dim}Check credentials${COLORS.reset}`);
436
- log(` ${COLORS.cyan}tfcode sync${COLORS.reset} ${COLORS.dim}Sync tools${COLORS.reset}`);
437
- log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} ${COLORS.dim}List your tools${COLORS.reset}`);
438
- log('');
439
-
440
- } else {
441
- log('Setup cancelled.');
442
- }
443
- }
444
-
445
- const cli = yargs(hideBin(process.argv))
446
- .scriptName('tfcode')
447
- .wrap(100)
448
- .help()
449
- .alias('help', 'h')
450
- .command({
451
- command: '*',
452
- describe: 'start tfcode TUI',
453
- handler: () => {
454
- // Try to run the full TUI
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
193
  log('');
515
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
516
- log(`${COLORS.bold} tfcode - Quick Start Guide${COLORS.reset}`);
517
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
194
+ info('Syncing tools...');
518
195
  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
- log('');
544
- log(' Need help? https://toothfairyai.com/developers/tfcode');
545
- log('');
546
- }
547
- })
548
- .command({
549
- command: 'validate',
550
- describe: 'validate ToothFairyAI credentials',
551
- handler: async () => {
552
- const config = loadConfig();
553
196
 
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
-
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
+ }