@nexusbloom/cli 0.2.4 → 0.2.5
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/package.json +1 -1
- package/src/index.js +317 -171
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -101,26 +101,29 @@ program
|
|
|
101
101
|
.option("-s, --search <query>", "Search tools by name or description")
|
|
102
102
|
.action(async (opts, cmd) => {
|
|
103
103
|
const jsonMode = cmd.parent.opts().json || opts.json;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!tools || tools.length === 0) {
|
|
110
|
-
if (jsonMode) return console.log(JSON.stringify({ tools: [] }));
|
|
111
|
-
console.log(chalk.yellow("No tools found."));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (jsonMode) return console.log(JSON.stringify({ tools }, null, 2));
|
|
115
|
-
console.log(chalk.bold(`\n ${tools.length} tool${tools.length > 1 ? "s" : ""} available\n`));
|
|
116
|
-
tools.forEach((t) => {
|
|
117
|
-
console.log(` ${chalk.cyan(t.slug.padEnd(30))} ${chalk.dim((t.description || "").slice(0, 60))}`);
|
|
118
|
-
});
|
|
119
|
-
console.log();
|
|
120
|
-
} catch (err) {
|
|
121
|
-
if (jsonMode) return console.log(JSON.stringify({ error: err.message }));
|
|
122
|
-
console.error(chalk.red("Failed to fetch tools:"), err.message);
|
|
104
|
+
const tools = await fetchToolsWithFallback();
|
|
105
|
+
if (!tools || tools.length === 0) {
|
|
106
|
+
if (jsonMode) return console.log(JSON.stringify({ tools: [] }));
|
|
107
|
+
console.log(chalk.yellow("No tools found. Set NEXUSBLOOM_SUPABASE_ANON_KEY or deploy the API."));
|
|
108
|
+
return;
|
|
123
109
|
}
|
|
110
|
+
let filtered = tools;
|
|
111
|
+
if (opts.search) {
|
|
112
|
+
const q = opts.search.toLowerCase();
|
|
113
|
+
filtered = tools.filter(t =>
|
|
114
|
+
(t.slug || "").includes(q) ||
|
|
115
|
+
(t.name || "").toLowerCase().includes(q) ||
|
|
116
|
+
(t.description || "").toLowerCase().includes(q) ||
|
|
117
|
+
(t.category || "").includes(q)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (jsonMode) return console.log(JSON.stringify({ tools: filtered }, null, 2));
|
|
121
|
+
console.log(chalk.bold(`\n ${filtered.length} tool${filtered.length > 1 ? "s" : ""} available\n`));
|
|
122
|
+
filtered.forEach((t) => {
|
|
123
|
+
const typeBadge = t.type === "community" ? chalk.dim("[community]") : "";
|
|
124
|
+
console.log(` ${chalk.cyan((t.slug || "").padEnd(30))} ${chalk.dim((t.description || "").slice(0, 50))} ${typeBadge}`);
|
|
125
|
+
});
|
|
126
|
+
console.log();
|
|
124
127
|
});
|
|
125
128
|
|
|
126
129
|
// ─── `nxb search <query>` — search tools ─────────────────────────────────────
|
|
@@ -130,26 +133,30 @@ program
|
|
|
130
133
|
.argument("<query>", "Search query")
|
|
131
134
|
.action(async (query, opts, cmd) => {
|
|
132
135
|
const jsonMode = (cmd?.parent?.opts().json) || false;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!tools || tools.length === 0) {
|
|
139
|
-
if (jsonMode) return console.log(JSON.stringify({ tools: [] }));
|
|
140
|
-
console.log(chalk.yellow(`No tools matching "${query}".`));
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (jsonMode) return console.log(JSON.stringify({ tools, query }, null, 2));
|
|
144
|
-
console.log(chalk.bold(`\n ${tools.length} result${tools.length > 1 ? "s" : ""} for "${query}"\n`));
|
|
145
|
-
tools.forEach((t) => {
|
|
146
|
-
console.log(` ${chalk.cyan(t.slug.padEnd(30))} ${chalk.dim((t.description || "").slice(0, 60))}`);
|
|
147
|
-
});
|
|
148
|
-
console.log();
|
|
149
|
-
} catch (err) {
|
|
150
|
-
if (jsonMode) return console.log(JSON.stringify({ error: err.message }));
|
|
151
|
-
console.error(chalk.red("Search failed:"), err.message);
|
|
136
|
+
const tools = await fetchToolsWithFallback();
|
|
137
|
+
if (!tools || tools.length === 0) {
|
|
138
|
+
if (jsonMode) return console.log(JSON.stringify({ tools: [], query }));
|
|
139
|
+
console.log(chalk.yellow(`No tools found matching "${query}".`));
|
|
140
|
+
return;
|
|
152
141
|
}
|
|
142
|
+
const q = query.toLowerCase();
|
|
143
|
+
const filtered = tools.filter(t =>
|
|
144
|
+
(t.slug || "").includes(q) ||
|
|
145
|
+
(t.name || "").toLowerCase().includes(q) ||
|
|
146
|
+
(t.description || "").toLowerCase().includes(q) ||
|
|
147
|
+
(t.category || "").includes(q)
|
|
148
|
+
);
|
|
149
|
+
if (filtered.length === 0) {
|
|
150
|
+
if (jsonMode) return console.log(JSON.stringify({ tools: [], query }));
|
|
151
|
+
console.log(chalk.yellow(`No tools matching "${query}".`));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (jsonMode) return console.log(JSON.stringify({ tools: filtered, query }, null, 2));
|
|
155
|
+
console.log(chalk.bold(`\n ${filtered.length} result${filtered.length > 1 ? "s" : ""} for "${query}"\n`));
|
|
156
|
+
filtered.forEach((t) => {
|
|
157
|
+
console.log(` ${chalk.cyan((t.slug || "").padEnd(30))} ${chalk.dim((t.description || "").slice(0, 60))}`);
|
|
158
|
+
});
|
|
159
|
+
console.log();
|
|
153
160
|
});
|
|
154
161
|
|
|
155
162
|
// ─── `nxb run <slug>` — execute a tool ───────────────────────────────────────
|
|
@@ -166,17 +173,12 @@ program
|
|
|
166
173
|
const jsonMode = cmd.parent.opts().json;
|
|
167
174
|
|
|
168
175
|
// ── Resolve abbreviation ────────────────────────────────────────────
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (!jsonMode) console.log(chalk.dim(` Resolved: ${slug} → ${abbrMap[slug]}`));
|
|
176
|
-
slug = abbrMap[slug];
|
|
177
|
-
}
|
|
178
|
-
} catch {
|
|
179
|
-
// Silently ignore abbreviation resolution failures
|
|
176
|
+
const allTools = await fetchToolsWithFallback();
|
|
177
|
+
const slugs = (allTools || []).map((t) => t.slug);
|
|
178
|
+
const abbrMap = generateAbbreviations(slugs);
|
|
179
|
+
if (abbrMap[slug] && slugs.includes(abbrMap[slug])) {
|
|
180
|
+
if (!jsonMode) console.log(chalk.dim(` Resolved: ${slug} → ${abbrMap[slug]}`));
|
|
181
|
+
slug = abbrMap[slug];
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
// ── Build input body ────────────────────────────────────────────────
|
|
@@ -238,9 +240,8 @@ program
|
|
|
238
240
|
// ── Remote API execution ────────────────────────────────────────────
|
|
239
241
|
const apiKey = opts.key || process.env.NEXUSBLOOM_API_KEY || configGet("apiKey") || configGet("key");
|
|
240
242
|
if (!apiKey) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
process.exit(1);
|
|
243
|
+
// No API key — try local mode automatically
|
|
244
|
+
return await executeLocal(slug, body, jsonMode);
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
try {
|
|
@@ -254,11 +255,9 @@ program
|
|
|
254
255
|
});
|
|
255
256
|
|
|
256
257
|
if (!res.ok && !res.headers.get("content-type")?.includes("json")) {
|
|
257
|
-
|
|
258
|
-
if (jsonMode)
|
|
259
|
-
|
|
260
|
-
console.error(chalk.dim(` Raw response: ${text.slice(0, 200)}`));
|
|
261
|
-
process.exit(1);
|
|
258
|
+
// API returned non-JSON (likely not deployed) — fall back to local
|
|
259
|
+
if (!jsonMode) console.log(chalk.dim(` API not deployed, falling back to local execution...`));
|
|
260
|
+
return await executeLocal(slug, body, jsonMode);
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
const data = await res.json();
|
|
@@ -276,9 +275,9 @@ program
|
|
|
276
275
|
process.exit(1);
|
|
277
276
|
}
|
|
278
277
|
} catch (err) {
|
|
279
|
-
|
|
280
|
-
console.
|
|
281
|
-
|
|
278
|
+
// Network error — fall back to local
|
|
279
|
+
if (!jsonMode) console.log(chalk.dim(` API unreachable, falling back to local execution...`));
|
|
280
|
+
return await executeLocal(slug, body, jsonMode);
|
|
282
281
|
}
|
|
283
282
|
});
|
|
284
283
|
|
|
@@ -323,39 +322,14 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
323
322
|
// API failed, try Supabase fallback
|
|
324
323
|
}
|
|
325
324
|
|
|
326
|
-
// Fallback: query Supabase directly
|
|
325
|
+
// Fallback: query Supabase directly via shared helper
|
|
327
326
|
if (!apiSuccess) {
|
|
328
327
|
try {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
headers: { apikey: supabaseKey, Authorization: `Bearer ${supabaseKey}` }
|
|
335
|
-
});
|
|
336
|
-
const tools = await toolRes.json();
|
|
337
|
-
const toolId = tools?.[0]?.id;
|
|
338
|
-
if (toolId) {
|
|
339
|
-
// Get v2_source from tool_versions
|
|
340
|
-
const versionRes = await fetch(
|
|
341
|
-
`${supabaseUrl}/rest/v1/tool_versions?tool_id=eq.${toolId}&tool_type=eq.builtin&status=eq.published&select=v2_source&order=created_at.desc&limit=1`,
|
|
342
|
-
{ headers: { apikey: supabaseKey, Authorization: `Bearer ${supabaseKey}` } }
|
|
343
|
-
);
|
|
344
|
-
const versions = await versionRes.json();
|
|
345
|
-
const v2Source = versions?.[0]?.v2_source;
|
|
346
|
-
if (v2Source) {
|
|
347
|
-
coreLogicSource = v2Source;
|
|
348
|
-
const manifestMatch = v2Source.match(/export\s+const\s+MANIFEST\s*=\s*({[\s\S]*?});\s*\n/);
|
|
349
|
-
if (manifestMatch) {
|
|
350
|
-
try {
|
|
351
|
-
manifest = JSON.parse(manifestMatch[1].replace(/(\w+):/g, '"$1":').replace(/'/g, '"'));
|
|
352
|
-
} catch {
|
|
353
|
-
manifest = eval("(" + manifestMatch[1] + ")");
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
apiSuccess = true;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
328
|
+
const toolInfo = await fetchToolFromSupabase(slug);
|
|
329
|
+
if (toolInfo) {
|
|
330
|
+
manifest = toolInfo.manifest;
|
|
331
|
+
coreLogicSource = toolInfo.coreLogicSource;
|
|
332
|
+
apiSuccess = true;
|
|
359
333
|
}
|
|
360
334
|
} catch {
|
|
361
335
|
// Supabase fallback also failed
|
|
@@ -363,8 +337,8 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
363
337
|
}
|
|
364
338
|
|
|
365
339
|
if (!apiSuccess) {
|
|
366
|
-
if (jsonMode) return console.log(JSON.stringify({ error: `Tool "${slug}" not found.
|
|
367
|
-
console.error(chalk.red(`Tool "${slug}" not found.
|
|
340
|
+
if (jsonMode) return console.log(JSON.stringify({ error: `Tool "${slug}" not found. Set NEXUSBLOOM_SUPABASE_ANON_KEY or run \`nxb config set supabaseKey <your-anon-key>\` for offline mode.` }));
|
|
341
|
+
console.error(chalk.red(`Tool "${slug}" not found. Set NEXUSBLOOM_SUPABASE_ANON_KEY or run \`nxb config set supabaseKey <your-anon-key>\` for offline mode.`));
|
|
368
342
|
process.exit(1);
|
|
369
343
|
}
|
|
370
344
|
|
|
@@ -376,22 +350,21 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
376
350
|
|
|
377
351
|
if (!coreLogicSource) {
|
|
378
352
|
// Fallback: try to extract coreLogic from the manifest itself
|
|
379
|
-
if (jsonMode) return console.log(JSON.stringify({ error: "No v2 source available for local execution.
|
|
380
|
-
console.error(chalk.yellow("No v2 source available for local execution.
|
|
353
|
+
if (jsonMode) return console.log(JSON.stringify({ error: "No v2 source available for local execution. Set NEXUSBLOOM_SUPABASE_ANON_KEY or run `nxb config set supabaseKey <your-key>`." }));
|
|
354
|
+
console.error(chalk.yellow("No v2 source available for local execution. Set NEXUSBLOOM_SUPABASE_ANON_KEY or run `nxb config set supabaseKey <your-key>`."));
|
|
381
355
|
process.exit(1);
|
|
382
356
|
}
|
|
383
357
|
|
|
384
358
|
// Execute coreLogic locally
|
|
385
359
|
try {
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
const isAsync = fnMatch[0].startsWith("export async");
|
|
360
|
+
// Strip imports and exports, then wrap the full module scope in an IIFE
|
|
361
|
+
// that returns coreLogic — this preserves module-scoped helpers (e.g. generateField)
|
|
362
|
+
const cleanSource = coreLogicSource
|
|
363
|
+
.replace(/^import\s+.*?;?\s*$/gm, "") // Remove import statements
|
|
364
|
+
.replace(/^export\s+/gm, ""); // Remove export keywords
|
|
365
|
+
const wrappedSource = `(function() {\n${cleanSource}\nreturn coreLogic;\n})()`;
|
|
366
|
+
const coreLogicFn = new Function("return " + wrappedSource)();
|
|
367
|
+
const isAsync = /export\s+async\s+function\s+coreLogic/.test(coreLogicSource);
|
|
395
368
|
|
|
396
369
|
// Validate input against manifest
|
|
397
370
|
const schema = manifest.input_schema || {};
|
|
@@ -413,17 +386,16 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
413
386
|
}
|
|
414
387
|
|
|
415
388
|
// Create and execute the function
|
|
416
|
-
const coreLogic = new Function("return " + fnSource)();
|
|
417
389
|
const timeoutMs = manifest.timeout_ms || 5000;
|
|
418
390
|
let result;
|
|
419
391
|
|
|
420
392
|
if (isAsync) {
|
|
421
393
|
result = await Promise.race([
|
|
422
|
-
|
|
394
|
+
coreLogicFn(input),
|
|
423
395
|
new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
424
396
|
]);
|
|
425
397
|
} else {
|
|
426
|
-
result =
|
|
398
|
+
result = coreLogicFn(input);
|
|
427
399
|
}
|
|
428
400
|
|
|
429
401
|
if (jsonMode) return console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
@@ -445,53 +417,62 @@ program
|
|
|
445
417
|
const jsonMode = (cmd?.parent?.opts().json) || false;
|
|
446
418
|
|
|
447
419
|
// Resolve abbreviation
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
420
|
+
const allTools = await fetchToolsWithFallback();
|
|
421
|
+
const slugs = (allTools || []).map((t) => t.slug);
|
|
422
|
+
const abbrMap = generateAbbreviations(slugs);
|
|
423
|
+
if (abbrMap[slug] && slugs.includes(abbrMap[slug])) {
|
|
424
|
+
if (!jsonMode) console.log(chalk.dim(` Resolved: ${slug} → ${abbrMap[slug]}`));
|
|
425
|
+
slug = abbrMap[slug];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Try to get manifest — API first, then Supabase fallback
|
|
429
|
+
let manifest = null;
|
|
458
430
|
try {
|
|
459
431
|
const res = await fetch(`${API_BASE}/api/run/${slug}`);
|
|
460
|
-
if (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return;
|
|
432
|
+
if (res.ok) {
|
|
433
|
+
const data = await res.json();
|
|
434
|
+
manifest = data.data?.manifest || data.manifest || data;
|
|
464
435
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
436
|
+
} catch { /* API failed */ }
|
|
437
|
+
|
|
438
|
+
if (!manifest) {
|
|
439
|
+
// Try Supabase fallback
|
|
440
|
+
try {
|
|
441
|
+
const toolInfo = await fetchToolFromSupabase(slug);
|
|
442
|
+
if (toolInfo) manifest = toolInfo.manifest;
|
|
443
|
+
} catch { /* fallback failed */ }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (!manifest) {
|
|
447
|
+
if (jsonMode) return console.log(JSON.stringify({ error: `Tool "${slug}" not found.` }));
|
|
448
|
+
console.error(chalk.red(`Tool "${slug}" not found.`));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (jsonMode) return console.log(JSON.stringify(manifest, null, 2));
|
|
453
|
+
const toolName = manifest.name || manifest.slug || slug;
|
|
454
|
+
console.log(chalk.bold(`\n ${toolName}`));
|
|
455
|
+
console.log(` ${chalk.dim(manifest.short_description || "")}`);
|
|
456
|
+
console.log(` Version: ${chalk.cyan(manifest.version || "1.0.0")}`);
|
|
457
|
+
console.log(` Runtime: ${chalk.cyan(manifest.runtime || "browser")}`);
|
|
458
|
+
console.log(` Pricing: ${chalk.cyan(manifest.pricing || "free")}`);
|
|
459
|
+
if (manifest.input_schema?.properties) {
|
|
460
|
+
console.log(chalk.bold(`\n Input Parameters:`));
|
|
461
|
+
const props = manifest.input_schema.properties;
|
|
462
|
+
const required = manifest.input_schema.required || [];
|
|
463
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
464
|
+
const req = required.includes(name) ? chalk.red(" *required") : "";
|
|
465
|
+
console.log(` ${chalk.cyan(name)} (${prop.type})${req}`);
|
|
466
|
+
if (prop.description) console.log(` ${chalk.dim(prop.description)}`);
|
|
483
467
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
468
|
+
}
|
|
469
|
+
if (manifest.output_schema?.properties) {
|
|
470
|
+
console.log(chalk.bold(`\n Output:`));
|
|
471
|
+
for (const [name, prop] of Object.entries(manifest.output_schema.properties)) {
|
|
472
|
+
console.log(` ${chalk.cyan(name)} (${prop.type})`);
|
|
489
473
|
}
|
|
490
|
-
console.log();
|
|
491
|
-
} catch (err) {
|
|
492
|
-
if (jsonMode) return console.log(JSON.stringify({ error: err.message }));
|
|
493
|
-
console.error(chalk.red("Failed to fetch tool info:"), err.message);
|
|
494
474
|
}
|
|
475
|
+
console.log();
|
|
495
476
|
});
|
|
496
477
|
|
|
497
478
|
// ─── `nxb transform <slug>` — pipe stdin through a tool ──────────────────────
|
|
@@ -538,6 +519,10 @@ program
|
|
|
538
519
|
},
|
|
539
520
|
body: JSON.stringify(body),
|
|
540
521
|
});
|
|
522
|
+
if (!res.ok && !res.headers.get("content-type")?.includes("json")) {
|
|
523
|
+
if (!jsonMode) console.log(chalk.dim(` API not deployed, falling back to local execution...`));
|
|
524
|
+
return await executeLocal(slug, body, jsonMode);
|
|
525
|
+
}
|
|
541
526
|
const data = await res.json();
|
|
542
527
|
if (data.success) {
|
|
543
528
|
const output = typeof data.data === "string" ? data.data : JSON.stringify(data.data, null, 2);
|
|
@@ -549,9 +534,8 @@ program
|
|
|
549
534
|
process.exit(1);
|
|
550
535
|
}
|
|
551
536
|
} catch (err) {
|
|
552
|
-
if (jsonMode)
|
|
553
|
-
|
|
554
|
-
process.exit(1);
|
|
537
|
+
if (!jsonMode) console.log(chalk.dim(` API unreachable, falling back to local execution...`));
|
|
538
|
+
return await executeLocal(slug, body, jsonMode);
|
|
555
539
|
}
|
|
556
540
|
});
|
|
557
541
|
|
|
@@ -688,11 +672,13 @@ program
|
|
|
688
672
|
.description("Show tool abbreviations")
|
|
689
673
|
.argument("[query]", "Search/filter abbreviations (e.g. 'ip' shows all IP-related tools)")
|
|
690
674
|
.action(async (query, opts, cmd) => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
675
|
+
const tools = await fetchToolsWithFallback();
|
|
676
|
+
if (!tools || tools.length === 0) {
|
|
677
|
+
console.log(chalk.yellow(" No tools available. Set NEXUSBLOOM_SUPABASE_ANON_KEY or deploy the API."));
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const slugs = tools.map((t) => t.slug);
|
|
681
|
+
const abbrMap = generateAbbreviations(slugs);
|
|
696
682
|
|
|
697
683
|
// Filter by query if provided
|
|
698
684
|
let filtered = slugs;
|
|
@@ -708,15 +694,12 @@ program
|
|
|
708
694
|
}
|
|
709
695
|
|
|
710
696
|
console.log(chalk.bold(`\n Tool Abbreviations${query ? ` matching "${query}"` : ""}:\n`));
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
} catch (err) {
|
|
718
|
-
console.error(chalk.red("Failed to fetch tools:"), err.message);
|
|
719
|
-
}
|
|
697
|
+
filtered.forEach((slug) => {
|
|
698
|
+
const tool = (tools || []).find(t => t.slug === slug);
|
|
699
|
+
const name = tool?.name || slug;
|
|
700
|
+
console.log(` ${chalk.cyan(abbrMap[slug].padEnd(15))} → ${chalk.dim(slug.padEnd(30))} ${chalk.gray(name)}`);
|
|
701
|
+
});
|
|
702
|
+
console.log();
|
|
720
703
|
});
|
|
721
704
|
|
|
722
705
|
// ─── `nxb json` — create JSON input for tools ───────────────────────────────
|
|
@@ -732,14 +715,25 @@ program
|
|
|
732
715
|
const jsonMode = cmd.parent.opts().json;
|
|
733
716
|
|
|
734
717
|
if (opts.template) {
|
|
735
|
-
// Fetch tool schema
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
718
|
+
// Fetch tool schema — try API first, then Supabase
|
|
719
|
+
let manifest = null;
|
|
720
|
+
try {
|
|
721
|
+
const res = await fetch(`${API_BASE}/api/run/${opts.template}`);
|
|
722
|
+
if (res.ok) {
|
|
723
|
+
const data = await res.json();
|
|
724
|
+
manifest = data.data?.manifest || data.manifest || data;
|
|
725
|
+
}
|
|
726
|
+
} catch { /* API failed */ }
|
|
727
|
+
|
|
728
|
+
if (!manifest) {
|
|
729
|
+
const toolInfo = await fetchToolFromSupabase(opts.template);
|
|
730
|
+
if (toolInfo) manifest = toolInfo.manifest;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (!manifest) {
|
|
734
|
+
console.error(chalk.red(`Tool "${opts.template}" not found. Set NEXUSBLOOM_SUPABASE_ANON_KEY or deploy the API.`));
|
|
739
735
|
process.exit(1);
|
|
740
736
|
}
|
|
741
|
-
const data = await res.json();
|
|
742
|
-
const manifest = data.data?.manifest || data.manifest || data;
|
|
743
737
|
const schema = manifest.input_schema || {};
|
|
744
738
|
|
|
745
739
|
let result = {};
|
|
@@ -828,6 +822,158 @@ program
|
|
|
828
822
|
}
|
|
829
823
|
});
|
|
830
824
|
|
|
825
|
+
// ─── Supabase Helpers ─────────────────────────────────────────────────────────
|
|
826
|
+
|
|
827
|
+
const SUPABASE_URL_DEFAULT = "https://dhxaqatmuprehrkszxzs.supabase.co";
|
|
828
|
+
const SUPABASE_ANON_KEY_DEFAULT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRoeGFxYXRtdXByZWhya3N6eHpzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzMxNjIzNTQsImV4cCI6MjA4ODczODM1NH0.pq7DxW66ETQt6QlIAu2WiLO08tfA7JHQVPd2SvvA3sM";
|
|
829
|
+
|
|
830
|
+
function getSupabaseKey() {
|
|
831
|
+
return process.env.NEXUSBLOOM_SUPABASE_ANON_KEY
|
|
832
|
+
|| configGet("supabaseKey")
|
|
833
|
+
|| SUPABASE_ANON_KEY_DEFAULT;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function getSupabaseUrl() {
|
|
837
|
+
return process.env.NEXUSBLOOM_SUPABASE_URL
|
|
838
|
+
|| SUPABASE_URL_DEFAULT;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function supabaseHeaders() {
|
|
842
|
+
const key = getSupabaseKey();
|
|
843
|
+
return { apikey: key, Authorization: `Bearer ${key}` };
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Fetch tool list from Supabase directly (fallback when API is down).
|
|
848
|
+
* Returns array of {slug, name, short_description, category, runtime, version, icon} or null.
|
|
849
|
+
*/
|
|
850
|
+
async function fetchToolsFromSupabase() {
|
|
851
|
+
const headers = supabaseHeaders();
|
|
852
|
+
const supabaseUrl = getSupabaseUrl();
|
|
853
|
+
try {
|
|
854
|
+
const res = await fetch(
|
|
855
|
+
`${supabaseUrl}/rest/v1/tools?select=slug,name,short_description,tool_type,runtime,version,icon&published=eq.true&order=name.asc`,
|
|
856
|
+
{ headers }
|
|
857
|
+
);
|
|
858
|
+
if (!res.ok) return null;
|
|
859
|
+
const tools = await res.json();
|
|
860
|
+
if (!Array.isArray(tools)) return null;
|
|
861
|
+
|
|
862
|
+
// Also fetch community tools
|
|
863
|
+
const communityRes = await fetch(
|
|
864
|
+
`${supabaseUrl}/rest/v1/community_tools?select=slug,name,short_description,category,version,icon,author_id&status=eq.published&order=name.asc`,
|
|
865
|
+
{ headers }
|
|
866
|
+
);
|
|
867
|
+
const communityTools = communityRes.ok ? (await communityRes.json()) : [];
|
|
868
|
+
|
|
869
|
+
const allTools = [
|
|
870
|
+
...tools.map(t => ({
|
|
871
|
+
slug: t.slug,
|
|
872
|
+
name: t.name,
|
|
873
|
+
description: t.short_description || "",
|
|
874
|
+
category: t.tool_type || "uncategorized",
|
|
875
|
+
runtime: t.runtime || "both",
|
|
876
|
+
version: t.version || "1.0.0",
|
|
877
|
+
icon: t.icon || "🔧",
|
|
878
|
+
type: "builtin",
|
|
879
|
+
})),
|
|
880
|
+
...communityTools.map(t => ({
|
|
881
|
+
slug: t.slug,
|
|
882
|
+
name: t.name,
|
|
883
|
+
description: t.short_description || "",
|
|
884
|
+
category: t.category || "uncategorized",
|
|
885
|
+
runtime: "both",
|
|
886
|
+
version: t.version || "1.0.0",
|
|
887
|
+
icon: t.icon || "🛠️",
|
|
888
|
+
type: "community",
|
|
889
|
+
author_id: t.author_id,
|
|
890
|
+
})),
|
|
891
|
+
];
|
|
892
|
+
|
|
893
|
+
return allTools;
|
|
894
|
+
} catch {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Fetch a single tool's v2_source from Supabase (fallback).
|
|
901
|
+
* Returns {manifest, coreLogicSource, v2Source} or null.
|
|
902
|
+
*/
|
|
903
|
+
async function fetchToolFromSupabase(slug) {
|
|
904
|
+
const headers = supabaseHeaders();
|
|
905
|
+
const supabaseUrl = getSupabaseUrl();
|
|
906
|
+
try {
|
|
907
|
+
// Try builtin tools first
|
|
908
|
+
const toolRes = await fetch(
|
|
909
|
+
`${supabaseUrl}/rest/v1/tools?slug=eq.${slug}&select=id&published=eq.true`,
|
|
910
|
+
{ headers }
|
|
911
|
+
);
|
|
912
|
+
let tools = await toolRes.json();
|
|
913
|
+
let toolId = tools?.[0]?.id;
|
|
914
|
+
let toolType = "builtin";
|
|
915
|
+
|
|
916
|
+
// Fall back to community tools
|
|
917
|
+
if (!toolId) {
|
|
918
|
+
const comRes = await fetch(
|
|
919
|
+
`${supabaseUrl}/rest/v1/community_tools?slug=eq.${slug}&select=id&status=eq.published`,
|
|
920
|
+
{ headers }
|
|
921
|
+
);
|
|
922
|
+
const comTools = await comRes.json();
|
|
923
|
+
toolId = comTools?.[0]?.id;
|
|
924
|
+
toolType = "community";
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (!toolId) return null;
|
|
928
|
+
|
|
929
|
+
const versionRes = await fetch(
|
|
930
|
+
`${supabaseUrl}/rest/v1/tool_versions?tool_id=eq.${toolId}&tool_type=eq.${toolType}&status=eq.published&select=v2_source&order=created_at.desc&limit=1`,
|
|
931
|
+
{ headers }
|
|
932
|
+
);
|
|
933
|
+
const versions = await versionRes.json();
|
|
934
|
+
const v2Source = versions?.[0]?.v2_source;
|
|
935
|
+
if (!v2Source) return null;
|
|
936
|
+
|
|
937
|
+
const manifestMatch = v2Source.match(/export\s+const\s+MANIFEST\s*=\s*({[\s\S]*?});\s*\n/);
|
|
938
|
+
let manifest = null;
|
|
939
|
+
if (manifestMatch) {
|
|
940
|
+
try {
|
|
941
|
+
manifest = new Function("return " + manifestMatch[1])();
|
|
942
|
+
} catch {
|
|
943
|
+
try {
|
|
944
|
+
manifest = JSON.parse(manifestMatch[1].replace(/(\w+):/g, '"$1":').replace(/'/g, '"'));
|
|
945
|
+
} catch {
|
|
946
|
+
manifest = eval("(" + manifestMatch[1] + ")");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return { manifest, coreLogicSource: v2Source, v2Source };
|
|
951
|
+
} catch {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Try API first, fall back to Supabase. Returns array or null.
|
|
958
|
+
*/
|
|
959
|
+
async function fetchToolsWithFallback() {
|
|
960
|
+
// Try API first
|
|
961
|
+
try {
|
|
962
|
+
const url = new URL("/api/tools", API_BASE);
|
|
963
|
+
const res = await fetch(url);
|
|
964
|
+
const ct = res.headers.get("content-type") || "";
|
|
965
|
+
if (ct.includes("json")) {
|
|
966
|
+
const { tools } = await res.json();
|
|
967
|
+
if (Array.isArray(tools) && tools.length > 0) return tools;
|
|
968
|
+
}
|
|
969
|
+
} catch { /* API failed */ }
|
|
970
|
+
|
|
971
|
+
// Fall back to Supabase
|
|
972
|
+
const supabaseTools = await fetchToolsFromSupabase();
|
|
973
|
+
if (supabaseTools) return supabaseTools;
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
|
|
831
977
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
832
978
|
|
|
833
979
|
function readStdin() {
|