@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/LICENSE +21 -0
- package/bin/tfcode.js +163 -564
- package/package.json +19 -19
- package/postinstall.mjs +232 -0
- package/README.md +0 -146
- package/scripts/postinstall.cjs +0 -171
- package/scripts/quickstart.cjs +0 -66
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
|
-
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
140
|
+
log(`${COLORS.dim}This is your workspace UUID${COLORS.reset}`);
|
|
271
141
|
log('');
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
336
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
629
|
-
error(`
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
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
|
+
}
|