@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.
Files changed (2) hide show
  1. package/package.json +2 -3
  2. 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",
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> <value> — Set config
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
- const config = new Conf({ projectName: "nexusbloom" });
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 || config.get("apiKey");
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
- console.log(chalk.bold(`\n ${manifest.name}`));
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 || config.get("apiKey");
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("<value>", "Config value")
465
- .action((key, value) => {
466
- config.set(key, value);
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 = config.get(key);
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 = config.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() {