@nexusbloom/cli 0.2.6 → 0.2.8
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 +115 -13
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -101,8 +101,12 @@ 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
|
-
|
|
104
|
+
let tools = await fetchToolsWithFallback() || [];
|
|
105
|
+
// Merge in local tools from filesystem
|
|
106
|
+
const localTools = scanLocalTools();
|
|
107
|
+
const localSlugs = new Set(localTools.map(t => t.slug));
|
|
108
|
+
tools = [...localTools, ...tools.filter(t => !localSlugs.has(t.slug))];
|
|
109
|
+
if (tools.length === 0) {
|
|
106
110
|
if (jsonMode) return console.log(JSON.stringify({ tools: [] }));
|
|
107
111
|
console.log(chalk.yellow("No tools found. Set NEXUSBLOOM_SUPABASE_ANON_KEY or deploy the API."));
|
|
108
112
|
return;
|
|
@@ -120,7 +124,7 @@ program
|
|
|
120
124
|
if (jsonMode) return console.log(JSON.stringify({ tools: filtered }, null, 2));
|
|
121
125
|
console.log(chalk.bold(`\n ${filtered.length} tool${filtered.length > 1 ? "s" : ""} available\n`));
|
|
122
126
|
filtered.forEach((t) => {
|
|
123
|
-
const typeBadge = t.type === "community" ? chalk.dim("[community]") : "";
|
|
127
|
+
const typeBadge = t.type === "community" ? chalk.dim("[community]") : t.type === "local" ? chalk.cyan("[local]") : "";
|
|
124
128
|
console.log(` ${chalk.cyan((t.slug || "").padEnd(30))} ${chalk.dim((t.description || "").slice(0, 50))} ${typeBadge}`);
|
|
125
129
|
});
|
|
126
130
|
console.log();
|
|
@@ -286,20 +290,40 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
286
290
|
ensureCacheDir();
|
|
287
291
|
const cacheFile = path.join(CACHE_DIR, `${slug}.json`);
|
|
288
292
|
|
|
289
|
-
// Try loading from
|
|
293
|
+
// Try loading from local filesystem first (fastest, most up-to-date)
|
|
290
294
|
let manifest, coreLogicSource;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
const localToolPath = findLocalTool(slug);
|
|
296
|
+
if (localToolPath) {
|
|
297
|
+
if (!jsonMode) console.log(chalk.dim(` Using local tool: ${slug}`));
|
|
298
|
+
const source = fs.readFileSync(localToolPath, "utf-8");
|
|
299
|
+
const manifestMatch = source.match(/export\s+const\s+MANIFEST\s*=\s*({[\s\S]*?});/);
|
|
300
|
+
if (manifestMatch) {
|
|
301
|
+
try {
|
|
302
|
+
manifest = new Function("return " + manifestMatch[1])();
|
|
303
|
+
} catch { /* will fall through to cache/API */ }
|
|
304
|
+
}
|
|
305
|
+
coreLogicSource = source;
|
|
306
|
+
// Save to cache for next time
|
|
307
|
+
if (manifest) {
|
|
308
|
+
fs.writeFileSync(cacheFile, JSON.stringify({ manifest, coreLogicSource, cachedAt: new Date().toISOString() }), "utf-8");
|
|
299
309
|
}
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
//
|
|
312
|
+
// Try loading from cache second
|
|
313
|
+
if (!manifest || !coreLogicSource) {
|
|
314
|
+
if (fs.existsSync(cacheFile)) {
|
|
315
|
+
try {
|
|
316
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
317
|
+
manifest = cached.manifest;
|
|
318
|
+
coreLogicSource = cached.coreLogicSource;
|
|
319
|
+
if (!jsonMode) console.log(chalk.dim(` Using cached tool: ${slug}`));
|
|
320
|
+
} catch {
|
|
321
|
+
// Cache corrupted
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Fetch from API or Supabase if still not found
|
|
303
327
|
if (!manifest || !coreLogicSource) {
|
|
304
328
|
if (!jsonMode) console.log(chalk.dim(` Fetching tool: ${slug}...`));
|
|
305
329
|
|
|
@@ -443,6 +467,18 @@ program
|
|
|
443
467
|
} catch { /* fallback failed */ }
|
|
444
468
|
}
|
|
445
469
|
|
|
470
|
+
// Fallback: check local filesystem
|
|
471
|
+
if (!manifest) {
|
|
472
|
+
const localPath = findLocalTool(slug);
|
|
473
|
+
if (localPath) {
|
|
474
|
+
const source = fs.readFileSync(localPath, "utf-8");
|
|
475
|
+
const match = source.match(/export\s+const\s+MANIFEST\s*=\s*({[\s\S]*?});/);
|
|
476
|
+
if (match) {
|
|
477
|
+
try { manifest = new Function("return " + match[1])(); } catch {}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
446
482
|
if (!manifest) {
|
|
447
483
|
if (jsonMode) return console.log(JSON.stringify({ error: `Tool "${slug}" not found.` }));
|
|
448
484
|
console.error(chalk.red(`Tool "${slug}" not found.`));
|
|
@@ -976,6 +1012,72 @@ async function fetchToolsWithFallback() {
|
|
|
976
1012
|
|
|
977
1013
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
978
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* Search the local filesystem for a tool v2.js file.
|
|
1017
|
+
* Checks common locations relative to CWD.
|
|
1018
|
+
*/
|
|
1019
|
+
function findLocalTool(slug) {
|
|
1020
|
+
const cwd = process.cwd();
|
|
1021
|
+
const candidates = [
|
|
1022
|
+
path.join(cwd, "src", "tools", slug, "v2.js"),
|
|
1023
|
+
path.join(cwd, "tools", slug, "v2.js"),
|
|
1024
|
+
path.join(cwd, slug, "v2.js"),
|
|
1025
|
+
path.join(cwd, "apps", "main-site", "frontend", "src", "tools", slug, "v2.js"),
|
|
1026
|
+
path.join(cwd, "..", "apps", "main-site", "frontend", "src", "tools", slug, "v2.js"),
|
|
1027
|
+
];
|
|
1028
|
+
try {
|
|
1029
|
+
const config = loadConfig();
|
|
1030
|
+
const toolsDir = config.toolsDir;
|
|
1031
|
+
if (toolsDir && !candidates.some(c => c.includes(toolsDir))) {
|
|
1032
|
+
candidates.unshift(path.join(cwd, toolsDir, slug, "v2.js"));
|
|
1033
|
+
}
|
|
1034
|
+
} catch {}
|
|
1035
|
+
for (const candidate of candidates) {
|
|
1036
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1037
|
+
}
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Scan the local filesystem for all tool v2.js files.
|
|
1043
|
+
* Returns array of { slug, name, description, type: "local" }.
|
|
1044
|
+
*/
|
|
1045
|
+
function scanLocalTools() {
|
|
1046
|
+
const cwd = process.cwd();
|
|
1047
|
+
const searchDirs = [
|
|
1048
|
+
path.join(cwd, "src", "tools"),
|
|
1049
|
+
path.join(cwd, "tools"),
|
|
1050
|
+
path.join(cwd, "apps", "main-site", "frontend", "src", "tools"),
|
|
1051
|
+
];
|
|
1052
|
+
const tools = [];
|
|
1053
|
+
for (const dir of searchDirs) {
|
|
1054
|
+
if (!fs.existsSync(dir)) continue;
|
|
1055
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1056
|
+
for (const entry of entries) {
|
|
1057
|
+
if (!entry.isDirectory()) continue;
|
|
1058
|
+
const v2Path = path.join(dir, entry.name, "v2.js");
|
|
1059
|
+
if (!fs.existsSync(v2Path)) continue;
|
|
1060
|
+
const source = fs.readFileSync(v2Path, "utf-8");
|
|
1061
|
+
const match = source.match(/export\s+const\s+MANIFEST\s*=\s*({[\s\S]*?});/);
|
|
1062
|
+
if (!match) continue;
|
|
1063
|
+
try {
|
|
1064
|
+
const m = new Function("return " + match[1])();
|
|
1065
|
+
tools.push({
|
|
1066
|
+
slug: m.slug || entry.name,
|
|
1067
|
+
name: m.name || entry.name,
|
|
1068
|
+
description: m.short_description || "",
|
|
1069
|
+
category: m.category || "uncategorized",
|
|
1070
|
+
runtime: m.runtime || "both",
|
|
1071
|
+
version: m.version || "1.0.0",
|
|
1072
|
+
icon: m.icon || "🔧",
|
|
1073
|
+
type: "local",
|
|
1074
|
+
});
|
|
1075
|
+
} catch {}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return tools;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
979
1081
|
function readStdin() {
|
|
980
1082
|
return new Promise((resolve) => {
|
|
981
1083
|
const chunks = [];
|