@nexusbloom/cli 0.1.3 → 0.1.6
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 +2 -3
- package/src/index.js +197 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexusbloom/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "NexusBloom CLI — run tools and workflows from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"commander": "^12.0.0",
|
|
22
22
|
"chalk": "^5.3.0",
|
|
23
|
-
"ora": "^8.0.0"
|
|
24
|
-
"conf": "^12.0.0"
|
|
23
|
+
"ora": "^8.0.0"
|
|
25
24
|
}
|
|
26
25
|
}
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* nxb chat <message> — Natural language routing
|
|
14
14
|
* nxb info <slug> — Show tool schema
|
|
15
15
|
* nxb transform <slug> — Pipe stdin through a tool
|
|
16
|
-
* nxb config set <key>
|
|
16
|
+
* nxb config set <key> [value] — Set config (interactive if value omitted)
|
|
17
17
|
* nxb config get [key] — Get config
|
|
18
18
|
* nxb cache clear — Clear local tool cache
|
|
19
19
|
*/
|
|
@@ -21,16 +21,61 @@
|
|
|
21
21
|
import { program } from "commander";
|
|
22
22
|
import chalk from "chalk";
|
|
23
23
|
import { createRequire } from "module";
|
|
24
|
-
import Conf from "conf";
|
|
25
24
|
import fs from "fs";
|
|
26
25
|
import path from "path";
|
|
27
26
|
import { fileURLToPath } from "url";
|
|
27
|
+
import readline from "readline";
|
|
28
28
|
|
|
29
29
|
const require = createRequire(import.meta.url);
|
|
30
30
|
const { version } = require("../package.json");
|
|
31
31
|
|
|
32
32
|
const API_BASE = process.env.NEXUSBLOOM_API_URL || "https://www.nexusbloom.dev";
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
// ─── Simple file-based config (avoids `conf` dependency issues) ───────────
|
|
35
|
+
const CONFIG_DIR = path.join(process.env.XDG_CONFIG_HOME || path.join(require("os").homedir(), ".config"), "nexusbloom");
|
|
36
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
37
|
+
|
|
38
|
+
function ensureConfigDir() {
|
|
39
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
40
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadConfig() {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
47
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
48
|
+
}
|
|
49
|
+
} catch { /* ignore corrupt config */ }
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function saveConfig(store) {
|
|
54
|
+
ensureConfigDir();
|
|
55
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(store, null, 2), "utf-8");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function configGet(key) {
|
|
59
|
+
const store = loadConfig();
|
|
60
|
+
if (key) return store[key];
|
|
61
|
+
return store;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function configSet(key, value) {
|
|
65
|
+
const store = loadConfig();
|
|
66
|
+
store[key] = value;
|
|
67
|
+
saveConfig(store);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function promptValue(key) {
|
|
71
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
rl.question(chalk.cyan(` Enter value for "${key}": `), (answer) => {
|
|
74
|
+
rl.close();
|
|
75
|
+
resolve(answer.trim());
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
34
79
|
|
|
35
80
|
// ─── Cache directory for local tool execution ────────────────────────────────
|
|
36
81
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -120,6 +165,20 @@ program
|
|
|
120
165
|
.action(async (slug, args, opts, cmd) => {
|
|
121
166
|
const jsonMode = cmd.parent.opts().json;
|
|
122
167
|
|
|
168
|
+
// ── Resolve abbreviation ────────────────────────────────────────────
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(`${API_BASE}/api/tools`);
|
|
171
|
+
const { tools } = await res.json();
|
|
172
|
+
const slugs = (tools || []).map((t) => t.slug);
|
|
173
|
+
const abbrMap = generateAbbreviations(slugs);
|
|
174
|
+
if (abbrMap[slug] && slugs.includes(abbrMap[slug])) {
|
|
175
|
+
if (!jsonMode) console.log(chalk.dim(` Resolved: ${slug} → ${abbrMap[slug]}`));
|
|
176
|
+
slug = abbrMap[slug];
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Silently ignore abbreviation resolution failures
|
|
180
|
+
}
|
|
181
|
+
|
|
123
182
|
// ── Build input body ────────────────────────────────────────────────
|
|
124
183
|
let body = {};
|
|
125
184
|
|
|
@@ -162,7 +221,7 @@ program
|
|
|
162
221
|
}
|
|
163
222
|
|
|
164
223
|
// ── Remote API execution ────────────────────────────────────────────
|
|
165
|
-
const apiKey = opts.key || process.env.NEXUSBLOOM_API_KEY ||
|
|
224
|
+
const apiKey = opts.key || process.env.NEXUSBLOOM_API_KEY || configGet("apiKey") || configGet("key");
|
|
166
225
|
if (!apiKey) {
|
|
167
226
|
if (jsonMode) return console.log(JSON.stringify({ error: "API key required. Set NEXUSBLOOM_API_KEY, pass --key, or run `nxb config set key <your-key>`." }));
|
|
168
227
|
console.error(chalk.red("API key required. Set NEXUSBLOOM_API_KEY, pass --key, or run `nxb config set key <your-key>`."));
|
|
@@ -224,7 +283,7 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
224
283
|
process.exit(1);
|
|
225
284
|
}
|
|
226
285
|
const data = await res.json();
|
|
227
|
-
manifest = data.manifest || data;
|
|
286
|
+
manifest = data.data?.manifest || data.manifest || data;
|
|
228
287
|
|
|
229
288
|
// Fetch the v2 source separately
|
|
230
289
|
const sourceRes = await fetch(`${API_BASE}/api/run/${slug}?source=true`);
|
|
@@ -361,9 +420,10 @@ program
|
|
|
361
420
|
return;
|
|
362
421
|
}
|
|
363
422
|
const data = await res.json();
|
|
364
|
-
const manifest = data.manifest || data;
|
|
423
|
+
const manifest = data.data?.manifest || data.manifest || data;
|
|
365
424
|
if (jsonMode) return console.log(JSON.stringify(manifest, null, 2));
|
|
366
|
-
|
|
425
|
+
const toolName = manifest.name || manifest.slug || slug;
|
|
426
|
+
console.log(chalk.bold(`\n ${toolName}`));
|
|
367
427
|
console.log(` ${chalk.dim(manifest.short_description || "")}`);
|
|
368
428
|
console.log(` Version: ${chalk.cyan(manifest.version || "1.0.0")}`);
|
|
369
429
|
console.log(` Runtime: ${chalk.cyan(manifest.runtime || "browser")}`);
|
|
@@ -400,7 +460,7 @@ program
|
|
|
400
460
|
.option("--local", "Execute locally (faster for repeated use)")
|
|
401
461
|
.action(async (slug, opts, cmd) => {
|
|
402
462
|
const jsonMode = cmd.parent.opts().json;
|
|
403
|
-
const apiKey = opts.key || process.env.NEXUSBLOOM_API_KEY ||
|
|
463
|
+
const apiKey = opts.key || process.env.NEXUSBLOOM_API_KEY || configGet("apiKey") || configGet("key");
|
|
404
464
|
|
|
405
465
|
const input = await readStdin();
|
|
406
466
|
if (!input) {
|
|
@@ -459,11 +519,18 @@ const configCmd = program
|
|
|
459
519
|
|
|
460
520
|
configCmd
|
|
461
521
|
.command("set")
|
|
462
|
-
.description("Set a config value")
|
|
522
|
+
.description("Set a config value (prompts for value if omitted)")
|
|
463
523
|
.argument("<key>", "Config key (key, default-tool)")
|
|
464
|
-
.argument("
|
|
465
|
-
.action((key, value) => {
|
|
466
|
-
|
|
524
|
+
.argument("[value]", "Config value (prompted if not provided)")
|
|
525
|
+
.action(async (key, value) => {
|
|
526
|
+
if (!value) {
|
|
527
|
+
value = await promptValue(key);
|
|
528
|
+
}
|
|
529
|
+
if (!value) {
|
|
530
|
+
console.log(chalk.yellow(" No value provided. Aborted."));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
configSet(key, value);
|
|
467
534
|
console.log(chalk.green(` Set ${key} to "${value}"`));
|
|
468
535
|
});
|
|
469
536
|
|
|
@@ -472,15 +539,15 @@ configCmd
|
|
|
472
539
|
.description("Get a config value")
|
|
473
540
|
.argument("[key]", "Config key (omit to show all)")
|
|
474
541
|
.action((key) => {
|
|
542
|
+
const store = loadConfig();
|
|
475
543
|
if (key) {
|
|
476
|
-
const value =
|
|
544
|
+
const value = store[key];
|
|
477
545
|
if (value === undefined) {
|
|
478
546
|
console.log(chalk.yellow(` ${key} is not set`));
|
|
479
547
|
} else {
|
|
480
548
|
console.log(` ${key}: ${value}`);
|
|
481
549
|
}
|
|
482
550
|
} else {
|
|
483
|
-
const store = config.store;
|
|
484
551
|
if (Object.keys(store).length === 0) {
|
|
485
552
|
console.log(chalk.yellow(" No config values set."));
|
|
486
553
|
} else {
|
|
@@ -495,7 +562,7 @@ configCmd
|
|
|
495
562
|
.command("list")
|
|
496
563
|
.description("List all config values (alias for config get)")
|
|
497
564
|
.action(() => {
|
|
498
|
-
const store =
|
|
565
|
+
const store = loadConfig();
|
|
499
566
|
if (Object.keys(store).length === 0) {
|
|
500
567
|
console.log(chalk.yellow(" No config values set."));
|
|
501
568
|
} else {
|
|
@@ -546,6 +613,121 @@ cacheCmd
|
|
|
546
613
|
|
|
547
614
|
program.parse(process.argv);
|
|
548
615
|
|
|
616
|
+
// ─── Abbreviation generator ──────────────────────────────────────────────────
|
|
617
|
+
|
|
618
|
+
function generateAbbreviations(slugs) {
|
|
619
|
+
const abbrMap = {};
|
|
620
|
+
const used = new Set();
|
|
621
|
+
|
|
622
|
+
for (const slug of slugs) {
|
|
623
|
+
// Try first letter of each word
|
|
624
|
+
const parts = slug.split("-");
|
|
625
|
+
let abbr = parts.map((p) => p[0]).join("").toLowerCase();
|
|
626
|
+
|
|
627
|
+
// If taken, try longer combinations
|
|
628
|
+
if (used.has(abbr) || abbr.length < 2) {
|
|
629
|
+
for (let len = 2; len <= slug.length; len++) {
|
|
630
|
+
abbr = slug.slice(0, len);
|
|
631
|
+
if (!used.has(abbr)) break;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
used.add(abbr);
|
|
636
|
+
abbrMap[slug] = abbr;
|
|
637
|
+
abbrMap[abbr] = slug;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return abbrMap;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ─── `nxb abbr` — show tool abbreviations ────────────────────────────────────
|
|
644
|
+
|
|
645
|
+
program
|
|
646
|
+
.command("abbr")
|
|
647
|
+
.description("Show tool abbreviations")
|
|
648
|
+
.action(async () => {
|
|
649
|
+
try {
|
|
650
|
+
const res = await fetch(`${API_BASE}/api/tools`);
|
|
651
|
+
const { tools } = await res.json();
|
|
652
|
+
const slugs = (tools || []).map((t) => t.slug);
|
|
653
|
+
const abbrMap = generateAbbreviations(slugs);
|
|
654
|
+
|
|
655
|
+
console.log(chalk.bold("\n Tool Abbreviations:\n"));
|
|
656
|
+
slugs.forEach((slug) => {
|
|
657
|
+
console.log(` ${chalk.cyan(abbrMap[slug].padEnd(15))} → ${chalk.dim(slug)}`);
|
|
658
|
+
});
|
|
659
|
+
console.log();
|
|
660
|
+
} catch (err) {
|
|
661
|
+
console.error(chalk.red("Failed to fetch tools:"), err.message);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// ─── `nxb json` — create JSON input for tools ───────────────────────────────
|
|
666
|
+
|
|
667
|
+
program
|
|
668
|
+
.command("json")
|
|
669
|
+
.description("Create JSON input for tools (pipe into nxb run)")
|
|
670
|
+
.option("-t, --template <slug>", "Generate JSON template for a specific tool")
|
|
671
|
+
.option("-i, --interactive", "Interactive mode - prompts for each field")
|
|
672
|
+
.action(async (opts, cmd) => {
|
|
673
|
+
const jsonMode = cmd.parent.opts().json;
|
|
674
|
+
|
|
675
|
+
if (opts.template) {
|
|
676
|
+
// Fetch tool schema
|
|
677
|
+
const res = await fetch(`${API_BASE}/api/run/${opts.template}`);
|
|
678
|
+
if (!res.ok) {
|
|
679
|
+
console.error(chalk.red(`Tool "${opts.template}" not found`));
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
const data = await res.json();
|
|
683
|
+
const manifest = data.data?.manifest || data.manifest || data;
|
|
684
|
+
const schema = manifest.input_schema || {};
|
|
685
|
+
|
|
686
|
+
if (opts.interactive) {
|
|
687
|
+
// Interactive mode
|
|
688
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
689
|
+
const ask = (q) => new Promise((r) => rl.question(q, r));
|
|
690
|
+
const result = {};
|
|
691
|
+
|
|
692
|
+
if (schema.properties) {
|
|
693
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
694
|
+
const required = schema.required?.includes(key);
|
|
695
|
+
const hint = prop.description ? ` (${prop.description})` : "";
|
|
696
|
+
const def = prop.default ? ` [${prop.default}]` : "";
|
|
697
|
+
const answer = await ask(chalk.cyan(` ${key}${hint}${def}: `));
|
|
698
|
+
const val = answer || prop.default;
|
|
699
|
+
if (val) result[key] = prop.type === "number" ? Number(val) : val;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
rl.close();
|
|
703
|
+
if (jsonMode) return console.log(JSON.stringify(result, null, 2));
|
|
704
|
+
console.log(JSON.stringify(result));
|
|
705
|
+
} else {
|
|
706
|
+
// Generate template with defaults
|
|
707
|
+
const template = {};
|
|
708
|
+
if (schema.properties) {
|
|
709
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
710
|
+
if (prop.default !== undefined) {
|
|
711
|
+
template[key] = prop.default;
|
|
712
|
+
} else if (prop.type === "string") {
|
|
713
|
+
template[key] = prop.enum ? prop.enum[0] : "";
|
|
714
|
+
} else if (prop.type === "number" || prop.type === "integer") {
|
|
715
|
+
template[key] = 0;
|
|
716
|
+
} else if (prop.type === "boolean") {
|
|
717
|
+
template[key] = false;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (jsonMode) return console.log(JSON.stringify(template, null, 2));
|
|
722
|
+
console.log(JSON.stringify(template, null, 2));
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
console.log(chalk.yellow("Usage: nxb json --template <slug> [--interactive]"));
|
|
726
|
+
console.log(chalk.dim(" Generate JSON input for a tool that can be piped into nxb run"));
|
|
727
|
+
console.log(chalk.dim(" Example: nxb json --template summarize | nxb run summarize --input @-"));
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
549
731
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
550
732
|
|
|
551
733
|
function readStdin() {
|