@toothfairyai/tfcode 1.0.0-beta.9 → 1.0.1

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,699 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ const { spawn } = require("child_process")
3
+ const path = require("path")
4
+ const fs = require("fs")
2
5
 
3
- import yargs from 'yargs';
4
- import { hideBin } from 'yargs/helpers';
5
- import { spawn } from 'child_process';
6
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
7
- import { join, dirname } from 'path';
8
- import { homedir } from 'os';
9
- import * as readline from 'readline';
10
- import { fileURLToPath } from 'url';
6
+ const binDir = path.join(__dirname, "bin")
7
+ const binary = process.platform === "win32" ? "tfcode.exe" : "tfcode"
8
+ const binaryPath = path.join(binDir, binary)
11
9
 
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
-
15
- const TFCODE_DIR = join(homedir(), '.tfcode');
16
- const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
17
- const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
18
- const CONFIG_FILE = join(TFCODE_DIR, 'config.json');
19
-
20
- const COLORS = {
21
- reset: '\x1b[0m',
22
- bold: '\x1b[1m',
23
- green: '\x1b[32m',
24
- red: '\x1b[31m',
25
- cyan: '\x1b[36m',
26
- dim: '\x1b[90m',
27
- yellow: '\x1b[33m',
28
- magenta: '\x1b[35m'
29
- };
30
-
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
- }
109
-
110
- function ensureConfigDir() {
111
- if (!existsSync(TFCODE_DIR)) {
112
- mkdirSync(TFCODE_DIR, { recursive: true });
113
- }
114
- }
115
-
116
- function loadConfig() {
117
- // Priority: env vars > config file
118
- const envConfig = {
119
- workspace_id: process.env.TF_WORKSPACE_ID,
120
- api_key: process.env.TF_API_KEY,
121
- region: process.env.TF_REGION
122
- };
123
-
124
- if (envConfig.workspace_id && envConfig.api_key) {
125
- return envConfig;
126
- }
127
-
128
- if (existsSync(CONFIG_FILE)) {
129
- try {
130
- return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
131
- } catch {}
132
- }
133
-
134
- return null;
135
- }
136
-
137
- function saveConfig(config) {
138
- ensureConfigDir();
139
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
10
+ if (!fs.existsSync(binaryPath)) {
11
+ console.error("tfcode binary not found. The postinstall script should have downloaded it.")
12
+ console.error("Try running: npm rebuild @toothfairyai/tfcode")
13
+ process.exit(1)
140
14
  }
141
15
 
142
- function runPythonSync(method, config = null) {
143
- const wsId = config?.workspace_id || process.env.TF_WORKSPACE_ID || '';
144
- const apiKey = config?.api_key || process.env.TF_API_KEY || '';
145
- const region = config?.region || process.env.TF_REGION || 'au';
146
-
147
- const pythonCode = `
148
- import json
149
- import sys
150
- import os
151
-
152
- try:
153
- os.environ["TF_WORKSPACE_ID"] = "${wsId}"
154
- os.environ["TF_API_KEY"] = "${apiKey}"
155
- os.environ["TF_REGION"] = "${region}"
156
-
157
- from tf_sync.config import load_config, validate_credentials, Region
158
- from tf_sync.tools import sync_tools
159
- from tf_sync.config import get_region_urls
160
-
161
- method = "${method}"
162
-
163
- if method == "validate":
164
- config = load_config()
165
- result = validate_credentials(config)
166
- 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
-
175
- elif method == "sync":
176
- config = load_config()
177
- 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
-
198
- except Exception as e:
199
- print(json.dumps({"success": False, "error": str(e)}))
200
- sys.exit(0)
201
- `;
202
-
203
- 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
-
220
- 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);
236
- });
237
- });
238
- }
239
-
240
- 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
- }
249
- }
250
-
251
- function saveToolsCache(tools) {
252
- ensureConfigDir();
253
- writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
254
- }
255
-
256
- async function interactiveSetup() {
257
- log('');
258
- log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
259
- log(`${COLORS.bold}${COLORS.magenta} tfcode Setup${COLORS.reset}`);
260
- log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
261
- log('');
262
- log('This will guide you through setting up your ToothFairyAI credentials.');
263
- log('');
264
- log(`${COLORS.dim}You can find your credentials at:${COLORS.reset}`);
265
- log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`);
266
- log('');
267
-
268
- // Step 1: Workspace ID
269
- 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}`);
271
- 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
-
289
- log('');
290
-
291
- // Step 2: API Key
292
- log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`);
293
- log(`${COLORS.dim}Paste or type your API key${COLORS.reset}`);
294
- 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
-
315
- log('');
316
-
317
- // Step 3: Region
318
- log(`${COLORS.bold}Step 3: Region${COLORS.reset}`);
319
- const regions = ['dev (Development)', 'au (Australia)', 'eu (Europe)', 'us (United States)'];
320
- const regionIdx = await select('Select your region:', regions);
321
- const regions_map = ['dev', 'au', 'eu', 'us'];
322
- const region = regions_map[regionIdx];
323
-
324
- log('');
325
- log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
326
- log('');
327
-
328
- // Summary
329
- log(`${COLORS.bold}Summary:${COLORS.reset}`);
330
- log(` Workspace ID: ${workspaceId}`);
331
- log(` API Key: ***${apiKey.slice(-4)}`);
332
- log(` Region: ${region}`);
333
- log('');
334
-
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
- });
345
-
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...');
367
- log('');
368
-
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
- }
468
-
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
- 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
-
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`);
615
- 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);
627
- }
628
- } catch (e) {
629
- error(`Failed to sync: ${e.message}`);
630
- process.exit(1);
631
- }
632
- }
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('');
671
-
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
- });
16
+ const child = spawn(binaryPath, process.argv.slice(2), {
17
+ stdio: "inherit",
18
+ env: process.env
19
+ })
698
20
 
699
- cli.parse();
21
+ child.on("exit", (code) => process.exit(code || 0))