@toothfairyai/tfcode 1.0.0-beta.1 → 1.0.0-beta.2
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 +307 -32
- package/package.json +1 -1
package/bin/tfcode.js
CHANGED
|
@@ -6,10 +6,12 @@ import { spawn } from 'child_process';
|
|
|
6
6
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
|
+
import * as readline from 'readline';
|
|
9
10
|
|
|
10
11
|
const TFCODE_DIR = join(homedir(), '.tfcode');
|
|
11
12
|
const TOOLS_FILE = join(TFCODE_DIR, 'tools.json');
|
|
12
13
|
const CREDENTIALS_FILE = join(TFCODE_DIR, 'credentials.json');
|
|
14
|
+
const CONFIG_FILE = join(TFCODE_DIR, 'config.json');
|
|
13
15
|
|
|
14
16
|
const COLORS = {
|
|
15
17
|
reset: '\x1b[0m',
|
|
@@ -18,7 +20,8 @@ const COLORS = {
|
|
|
18
20
|
red: '\x1b[31m',
|
|
19
21
|
cyan: '\x1b[36m',
|
|
20
22
|
dim: '\x1b[90m',
|
|
21
|
-
yellow: '\x1b[33m'
|
|
23
|
+
yellow: '\x1b[33m',
|
|
24
|
+
magenta: '\x1b[35m'
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
function log(msg) {
|
|
@@ -37,13 +40,123 @@ function info(msg) {
|
|
|
37
40
|
console.log(`${COLORS.cyan}ℹ${COLORS.reset} ${msg}`);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
function
|
|
43
|
+
function question(rl, prompt, hidden = false) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
if (hidden) {
|
|
46
|
+
const stdin = process.stdin;
|
|
47
|
+
const wasRaw = stdin.isRaw;
|
|
48
|
+
|
|
49
|
+
process.stdout.write(prompt);
|
|
50
|
+
|
|
51
|
+
if (stdin.isTTY) {
|
|
52
|
+
stdin.setRawMode(true);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let input = '';
|
|
56
|
+
|
|
57
|
+
stdin.resume();
|
|
58
|
+
stdin.on('data', function charListener(char) {
|
|
59
|
+
const c = char.toString('utf8');
|
|
60
|
+
|
|
61
|
+
switch (c) {
|
|
62
|
+
case '\n':
|
|
63
|
+
case '\r':
|
|
64
|
+
case '\u0004':
|
|
65
|
+
if (stdin.isTTY) {
|
|
66
|
+
stdin.setRawMode(wasRaw || false);
|
|
67
|
+
}
|
|
68
|
+
stdin.pause();
|
|
69
|
+
stdin.removeListener('data', charListener);
|
|
70
|
+
process.stdout.write('\n');
|
|
71
|
+
resolve(input);
|
|
72
|
+
break;
|
|
73
|
+
case '\u0003':
|
|
74
|
+
process.exit();
|
|
75
|
+
break;
|
|
76
|
+
case '\u007F':
|
|
77
|
+
input = input.slice(0, -1);
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
input += c;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
rl.question(prompt, (answer) => {
|
|
86
|
+
resolve(answer.trim());
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function select(rl, prompt, options) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
log('');
|
|
95
|
+
log(prompt);
|
|
96
|
+
log('');
|
|
97
|
+
options.forEach((opt, i) => {
|
|
98
|
+
log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${opt}`);
|
|
99
|
+
});
|
|
100
|
+
log('');
|
|
101
|
+
|
|
102
|
+
rl.question('Select (1-' + options.length + '): ', (answer) => {
|
|
103
|
+
const idx = parseInt(answer.trim()) - 1;
|
|
104
|
+
if (idx >= 0 && idx < options.length) {
|
|
105
|
+
resolve(idx);
|
|
106
|
+
} else {
|
|
107
|
+
resolve(0);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureConfigDir() {
|
|
114
|
+
if (!existsSync(TFCODE_DIR)) {
|
|
115
|
+
mkdirSync(TFCODE_DIR, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function loadConfig() {
|
|
120
|
+
// Priority: env vars > config file
|
|
121
|
+
const envConfig = {
|
|
122
|
+
workspace_id: process.env.TF_WORKSPACE_ID,
|
|
123
|
+
api_key: process.env.TF_API_KEY,
|
|
124
|
+
region: process.env.TF_REGION
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (envConfig.workspace_id && envConfig.api_key) {
|
|
128
|
+
return envConfig;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (existsSync(CONFIG_FILE)) {
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function saveConfig(config) {
|
|
141
|
+
ensureConfigDir();
|
|
142
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function runPythonSync(method, config = null) {
|
|
146
|
+
const wsId = config?.workspace_id || process.env.TF_WORKSPACE_ID || '';
|
|
147
|
+
const apiKey = config?.api_key || process.env.TF_API_KEY || '';
|
|
148
|
+
const region = config?.region || process.env.TF_REGION || 'au';
|
|
149
|
+
|
|
41
150
|
const pythonCode = `
|
|
42
151
|
import json
|
|
43
152
|
import sys
|
|
44
153
|
import os
|
|
45
154
|
|
|
46
155
|
try:
|
|
156
|
+
os.environ["TF_WORKSPACE_ID"] = "${wsId}"
|
|
157
|
+
os.environ["TF_API_KEY"] = "${apiKey}"
|
|
158
|
+
os.environ["TF_REGION"] = "${region}"
|
|
159
|
+
|
|
47
160
|
from tf_sync.config import load_config, validate_credentials, Region
|
|
48
161
|
from tf_sync.tools import sync_tools
|
|
49
162
|
from tf_sync.config import get_region_urls
|
|
@@ -93,10 +206,7 @@ sys.exit(0)
|
|
|
93
206
|
return new Promise((resolve, reject) => {
|
|
94
207
|
const pythonPath = process.env.TFCODE_PYTHON_PATH || 'python3';
|
|
95
208
|
const proc = spawn(pythonPath, ['-c', pythonCode], {
|
|
96
|
-
env: {
|
|
97
|
-
...process.env,
|
|
98
|
-
PYTHONPATH: process.env.TFCODE_PYTHONPATH || ''
|
|
99
|
-
}
|
|
209
|
+
env: { ...process.env }
|
|
100
210
|
});
|
|
101
211
|
|
|
102
212
|
let stdout = '';
|
|
@@ -130,12 +240,6 @@ sys.exit(0)
|
|
|
130
240
|
});
|
|
131
241
|
}
|
|
132
242
|
|
|
133
|
-
function ensureConfigDir() {
|
|
134
|
-
if (!existsSync(TFCODE_DIR)) {
|
|
135
|
-
mkdirSync(TFCODE_DIR, { recursive: true });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
243
|
function loadCachedTools() {
|
|
140
244
|
if (!existsSync(TOOLS_FILE)) {
|
|
141
245
|
return null;
|
|
@@ -152,11 +256,166 @@ function saveToolsCache(tools) {
|
|
|
152
256
|
writeFileSync(TOOLS_FILE, JSON.stringify(tools, null, 2));
|
|
153
257
|
}
|
|
154
258
|
|
|
259
|
+
async function interactiveSetup() {
|
|
260
|
+
const rl = readline.createInterface({
|
|
261
|
+
input: process.stdin,
|
|
262
|
+
output: process.stdout
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
log('');
|
|
266
|
+
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
267
|
+
log(`${COLORS.bold}${COLORS.magenta} tfcode Setup${COLORS.reset}`);
|
|
268
|
+
log(`${COLORS.bold}${COLORS.magenta}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
269
|
+
log('');
|
|
270
|
+
log('This will guide you through setting up your ToothFairyAI credentials.');
|
|
271
|
+
log('');
|
|
272
|
+
log(`${COLORS.dim}You can find your credentials at:${COLORS.reset}`);
|
|
273
|
+
log(`${COLORS.dim} https://app.toothfairyai.com → Settings → API Keys${COLORS.reset}`);
|
|
274
|
+
log('');
|
|
275
|
+
|
|
276
|
+
// Workspace ID
|
|
277
|
+
log(`${COLORS.bold}Step 1: Workspace ID${COLORS.reset}`);
|
|
278
|
+
log(`${COLORS.dim}This is your workspace UUID (e.g., 12345678-1234-1234-1234-123456789012)${COLORS.reset}`);
|
|
279
|
+
log('');
|
|
280
|
+
const workspaceId = await question(rl, 'Enter your Workspace ID: ');
|
|
281
|
+
|
|
282
|
+
if (!workspaceId) {
|
|
283
|
+
error('Workspace ID is required');
|
|
284
|
+
rl.close();
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
log('');
|
|
289
|
+
|
|
290
|
+
// API Key
|
|
291
|
+
log(`${COLORS.bold}Step 2: API Key${COLORS.reset}`);
|
|
292
|
+
log(`${COLORS.dim}Your API key will be hidden as you type${COLORS.reset}`);
|
|
293
|
+
log('');
|
|
294
|
+
const apiKey = await question(rl, 'Enter your API Key: ', true);
|
|
295
|
+
|
|
296
|
+
if (!apiKey) {
|
|
297
|
+
error('API Key is required');
|
|
298
|
+
rl.close();
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
log('');
|
|
303
|
+
|
|
304
|
+
// Region
|
|
305
|
+
log(`${COLORS.bold}Step 3: Region${COLORS.reset}`);
|
|
306
|
+
const regions = ['dev (Development)', 'au (Australia)', 'eu (Europe)', 'us (United States)'];
|
|
307
|
+
const regionIdx = await select(rl, 'Select your region:', regions);
|
|
308
|
+
const regions_map = ['dev', 'au', 'eu', 'us'];
|
|
309
|
+
const region = regions_map[regionIdx];
|
|
310
|
+
|
|
311
|
+
log('');
|
|
312
|
+
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
313
|
+
log('');
|
|
314
|
+
|
|
315
|
+
// Summary
|
|
316
|
+
log(`${COLORS.bold}Summary:${COLORS.reset}`);
|
|
317
|
+
log(` Workspace ID: ${workspaceId}`);
|
|
318
|
+
log(` API Key: ***${apiKey.slice(-4)}`);
|
|
319
|
+
log(` Region: ${region}`);
|
|
320
|
+
log('');
|
|
321
|
+
|
|
322
|
+
const confirm = await question(rl, 'Save these credentials? (Y/n): ');
|
|
323
|
+
|
|
324
|
+
if (confirm.toLowerCase() !== 'n' && confirm.toLowerCase() !== 'no') {
|
|
325
|
+
const config = { workspace_id: workspaceId, api_key: apiKey, region };
|
|
326
|
+
saveConfig(config);
|
|
327
|
+
success('Credentials saved to ~/.tfcode/config.json');
|
|
328
|
+
log('');
|
|
329
|
+
|
|
330
|
+
// Validate
|
|
331
|
+
const testNow = await question(rl, 'Validate credentials now? (Y/n): ');
|
|
332
|
+
|
|
333
|
+
if (testNow.toLowerCase() !== 'n' && testNow.toLowerCase() !== 'no') {
|
|
334
|
+
log('');
|
|
335
|
+
info('Validating credentials...');
|
|
336
|
+
log('');
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const result = await runPythonSync('validate', config);
|
|
340
|
+
|
|
341
|
+
if (result.success) {
|
|
342
|
+
success('Credentials valid!');
|
|
343
|
+
log(` API URL: ${result.base_url}`);
|
|
344
|
+
log(` Workspace ID: ${result.workspace_id}`);
|
|
345
|
+
log('');
|
|
346
|
+
|
|
347
|
+
const syncNow = await question(rl, 'Sync tools now? (Y/n): ');
|
|
348
|
+
|
|
349
|
+
if (syncNow.toLowerCase() !== 'n' && syncNow.toLowerCase() !== 'no') {
|
|
350
|
+
log('');
|
|
351
|
+
info('Syncing tools...');
|
|
352
|
+
log('');
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const syncResult = await runPythonSync('sync', config);
|
|
356
|
+
|
|
357
|
+
if (syncResult.success) {
|
|
358
|
+
saveToolsCache(syncResult);
|
|
359
|
+
success(`Synced ${syncResult.tools.length} tools`);
|
|
360
|
+
|
|
361
|
+
if (syncResult.by_type && Object.keys(syncResult.by_type).length > 0) {
|
|
362
|
+
log('');
|
|
363
|
+
log('By type:');
|
|
364
|
+
for (const [type, count] of Object.entries(syncResult.by_type)) {
|
|
365
|
+
log(` ${type}: ${count}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
error(`Sync failed: ${syncResult.error}`);
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {
|
|
372
|
+
error(`Sync failed: ${e.message}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
error(`Validation failed: ${result.error}`);
|
|
377
|
+
log('');
|
|
378
|
+
log(`${COLORS.dim}Check your credentials and try again with: tfcode setup${COLORS.reset}`);
|
|
379
|
+
}
|
|
380
|
+
} catch (e) {
|
|
381
|
+
error(`Validation failed: ${e.message}`);
|
|
382
|
+
log('');
|
|
383
|
+
log(`${COLORS.dim}Make sure Python 3.10+ and toothfairyai SDK are installed:${COLORS.reset}`);
|
|
384
|
+
log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
log('');
|
|
389
|
+
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
390
|
+
log(`${COLORS.bold}${COLORS.green} Setup Complete!${COLORS.reset}`);
|
|
391
|
+
log(`${COLORS.bold}${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
392
|
+
log('');
|
|
393
|
+
log('You can now use tfcode:');
|
|
394
|
+
log('');
|
|
395
|
+
log(` ${COLORS.cyan}tfcode validate${COLORS.reset} ${COLORS.dim}Check credentials${COLORS.reset}`);
|
|
396
|
+
log(` ${COLORS.cyan}tfcode sync${COLORS.reset} ${COLORS.dim}Sync tools${COLORS.reset}`);
|
|
397
|
+
log(` ${COLORS.cyan}tfcode tools list${COLORS.reset} ${COLORS.dim}List your tools${COLORS.reset}`);
|
|
398
|
+
log('');
|
|
399
|
+
|
|
400
|
+
} else {
|
|
401
|
+
log('Setup cancelled.');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
rl.close();
|
|
405
|
+
}
|
|
406
|
+
|
|
155
407
|
const cli = yargs(hideBin(process.argv))
|
|
156
408
|
.scriptName('tfcode')
|
|
157
409
|
.wrap(100)
|
|
158
410
|
.help()
|
|
159
411
|
.alias('help', 'h')
|
|
412
|
+
.command({
|
|
413
|
+
command: 'setup',
|
|
414
|
+
describe: 'interactive credential setup',
|
|
415
|
+
handler: async () => {
|
|
416
|
+
await interactiveSetup();
|
|
417
|
+
}
|
|
418
|
+
})
|
|
160
419
|
.command({
|
|
161
420
|
command: 'quickstart',
|
|
162
421
|
describe: 'show quick start guide',
|
|
@@ -168,28 +427,26 @@ const cli = yargs(hideBin(process.argv))
|
|
|
168
427
|
log('');
|
|
169
428
|
log('Welcome to tfcode! Follow these steps to get started:');
|
|
170
429
|
log('');
|
|
171
|
-
log(`${COLORS.cyan}
|
|
430
|
+
log(`${COLORS.cyan}OPTION A: Interactive Setup (Recommended)${COLORS.reset}`);
|
|
431
|
+
log(`${COLORS.dim} tfcode setup${COLORS.reset}`);
|
|
432
|
+
log('');
|
|
433
|
+
log(`${COLORS.cyan}OPTION B: Manual Setup${COLORS.reset}`);
|
|
172
434
|
log(`${COLORS.dim} export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`);
|
|
173
435
|
log(`${COLORS.dim} export TF_API_KEY="your-api-key"${COLORS.reset}`);
|
|
174
436
|
log(`${COLORS.dim} export TF_REGION="au"${COLORS.reset}`);
|
|
175
|
-
log(`${COLORS.dim} Regions: dev, au, eu, us${COLORS.reset}`);
|
|
176
437
|
log('');
|
|
177
|
-
log(`${COLORS.cyan}
|
|
438
|
+
log(`${COLORS.cyan}Then:${COLORS.reset}`);
|
|
178
439
|
log(`${COLORS.dim} tfcode validate${COLORS.reset}`);
|
|
179
|
-
log('');
|
|
180
|
-
log(`${COLORS.cyan}STEP 3: Sync Your Tools${COLORS.reset}`);
|
|
181
440
|
log(`${COLORS.dim} tfcode sync${COLORS.reset}`);
|
|
182
441
|
log('');
|
|
183
|
-
log(`${COLORS.cyan}STEP 4: Start Coding!${COLORS.reset}`);
|
|
184
|
-
log(`${COLORS.dim} tfcode${COLORS.reset}`);
|
|
185
|
-
log('');
|
|
186
442
|
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
187
443
|
log('');
|
|
188
|
-
log('
|
|
444
|
+
log(' Commands:');
|
|
189
445
|
log('');
|
|
190
|
-
log(' tfcode
|
|
191
|
-
log(' tfcode
|
|
192
|
-
log(' tfcode tools
|
|
446
|
+
log(' tfcode setup Interactive credential setup');
|
|
447
|
+
log(' tfcode validate Test your credentials');
|
|
448
|
+
log(' tfcode sync Sync tools from workspace');
|
|
449
|
+
log(' tfcode tools list Show all your tools');
|
|
193
450
|
log('');
|
|
194
451
|
log(`${COLORS.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
195
452
|
log('');
|
|
@@ -201,11 +458,26 @@ const cli = yargs(hideBin(process.argv))
|
|
|
201
458
|
command: 'validate',
|
|
202
459
|
describe: 'validate ToothFairyAI credentials',
|
|
203
460
|
handler: async () => {
|
|
461
|
+
const config = loadConfig();
|
|
462
|
+
|
|
463
|
+
if (!config) {
|
|
464
|
+
error('No credentials found.');
|
|
465
|
+
log('');
|
|
466
|
+
log('Run interactive setup:');
|
|
467
|
+
log(` ${COLORS.cyan}tfcode setup${COLORS.reset}`);
|
|
468
|
+
log('');
|
|
469
|
+
log('Or set environment variables:');
|
|
470
|
+
log(` ${COLORS.dim}export TF_WORKSPACE_ID="your-workspace-id"${COLORS.reset}`);
|
|
471
|
+
log(` ${COLORS.dim}export TF_API_KEY="your-api-key"${COLORS.reset}`);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
204
476
|
info('Validating ToothFairyAI credentials...');
|
|
205
477
|
log('');
|
|
206
478
|
|
|
207
479
|
try {
|
|
208
|
-
const result = await runPythonSync('validate');
|
|
480
|
+
const result = await runPythonSync('validate', config);
|
|
209
481
|
|
|
210
482
|
if (result.success) {
|
|
211
483
|
success('Credentials valid');
|
|
@@ -217,17 +489,12 @@ const cli = yargs(hideBin(process.argv))
|
|
|
217
489
|
}
|
|
218
490
|
} else {
|
|
219
491
|
error(`Validation failed: ${result.error || 'Unknown error'}`);
|
|
220
|
-
log('');
|
|
221
|
-
log(`${COLORS.dim}Check your credentials:${COLORS.reset}`);
|
|
222
|
-
log(`${COLORS.dim} TF_WORKSPACE_ID: ${process.env.TF_WORKSPACE_ID || 'not set'}${COLORS.reset}`);
|
|
223
|
-
log(`${COLORS.dim} TF_API_KEY: ${process.env.TF_API_KEY ? '***' + process.env.TF_API_KEY.slice(-4) : 'not set'}${COLORS.reset}`);
|
|
224
|
-
log(`${COLORS.dim} TF_REGION: ${process.env.TF_REGION || 'au (default)'}${COLORS.reset}`);
|
|
225
492
|
process.exit(1);
|
|
226
493
|
}
|
|
227
494
|
} catch (e) {
|
|
228
495
|
error(`Failed to validate: ${e.message}`);
|
|
229
496
|
log('');
|
|
230
|
-
log(`${COLORS.dim}Make sure Python 3.10+ and
|
|
497
|
+
log(`${COLORS.dim}Make sure Python 3.10+ and toothfairyai SDK are installed:${COLORS.reset}`);
|
|
231
498
|
log(`${COLORS.dim} pip install toothfairyai pydantic httpx rich${COLORS.reset}`);
|
|
232
499
|
process.exit(1);
|
|
233
500
|
}
|
|
@@ -237,11 +504,19 @@ const cli = yargs(hideBin(process.argv))
|
|
|
237
504
|
command: 'sync',
|
|
238
505
|
describe: 'sync tools from ToothFairyAI workspace',
|
|
239
506
|
handler: async () => {
|
|
507
|
+
const config = loadConfig();
|
|
508
|
+
|
|
509
|
+
if (!config) {
|
|
510
|
+
error('No credentials found. Run: tfcode setup');
|
|
511
|
+
process.exit(1);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
240
515
|
info('Syncing tools from ToothFairyAI workspace...');
|
|
241
516
|
log('');
|
|
242
517
|
|
|
243
518
|
try {
|
|
244
|
-
const result = await runPythonSync('sync');
|
|
519
|
+
const result = await runPythonSync('sync', config);
|
|
245
520
|
|
|
246
521
|
if (result.success) {
|
|
247
522
|
saveToolsCache(result);
|