@nexusbloom/cli 0.8.0 → 0.9.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/package.json +2 -2
- package/src/index.js +24 -2
- package/src/run.js +35 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexusbloom/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "NexusBloom CLI — run tools and workflows from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"test:watch": "node --test --watch --import ./test/setup.mjs test/*.test.js",
|
|
31
31
|
"lint": "node --check src/index.js"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
package/src/index.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
fetchTools, fetchToolSource, resolveToolSlug,
|
|
41
41
|
generateAbbreviations, normalizeSlug, readStdin,
|
|
42
42
|
} from "./tools.js";
|
|
43
|
-
import { runLocal, runRemote, buildInputBody, runInteractive, reportError } from "./run.js";
|
|
43
|
+
import { runLocal, runRemote, buildInputBody, runInteractive, runInteractiveFor, isExitPromptError, reportError } from "./run.js";
|
|
44
44
|
import listAction from "./list.js";
|
|
45
45
|
import { pipeAction, listTransforms, doctorAction } from "./commands.js";
|
|
46
46
|
import { buildWizard } from "./wizard.js";
|
|
@@ -167,6 +167,21 @@ program
|
|
|
167
167
|
const body = await buildInputBody(opts, args);
|
|
168
168
|
if (body === null) return;
|
|
169
169
|
|
|
170
|
+
// ── No input at all: configure the tool interactively ──
|
|
171
|
+
// `nxb run text-statistics` with nothing to send used to fail schema
|
|
172
|
+
// validation ("Missing required fields: text"), which is a dead end for a
|
|
173
|
+
// human at a terminal. Ask for the fields instead. Only when the body is
|
|
174
|
+
// genuinely empty: any explicit input (--input/--file, key=value, piped
|
|
175
|
+
// stdin) means the caller is scripting, and a prompt there would hang CI.
|
|
176
|
+
if (Object.keys(body).length === 0 && isInteractive()) {
|
|
177
|
+
return await runInteractiveFor(resolved, {
|
|
178
|
+
json: jsonMode,
|
|
179
|
+
tools: allTools,
|
|
180
|
+
local: Boolean(opts.local),
|
|
181
|
+
key: opts.key,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
170
185
|
// ── Local vs remote ──
|
|
171
186
|
if (opts.local || (!opts.key && !process.env.NEXUSBLOOM_API_KEY && !configGet("apiKey") && !configGet("key"))) {
|
|
172
187
|
return await runLocal(resolved, body, { jsonMode, slugResolved: true });
|
|
@@ -710,4 +725,11 @@ function renderToolInfo(manifest, resolved) {
|
|
|
710
725
|
console.log();
|
|
711
726
|
}
|
|
712
727
|
|
|
713
|
-
|
|
728
|
+
// `parse` does not await the async action handlers, so a Ctrl+C inside an
|
|
729
|
+
// inquirer prompt surfaces here as a rejected promise rather than a thrown
|
|
730
|
+
// error. Catching it turns a stack trace into a clean, conventional exit.
|
|
731
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
732
|
+
if (!isExitPromptError(err)) throw err;
|
|
733
|
+
process.stderr.write("\n Cancelled.\n");
|
|
734
|
+
process.exit(130);
|
|
735
|
+
});
|
package/src/run.js
CHANGED
|
@@ -200,6 +200,21 @@ export async function runLocal(slug, input, { jsonMode, slugResolved, quiet } =
|
|
|
200
200
|
return { ok: true, result: res.result, durationMs: res.durationMs };
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* True when an error is inquirer's "the user pressed Ctrl+C" signal.
|
|
205
|
+
*
|
|
206
|
+
* @inquirer/core rejects with an `ExitPromptError` from inside a readline
|
|
207
|
+
* `SIGINT` handler. Because the CLI parses with `program.parse()` — which does
|
|
208
|
+
* not await the async action handlers — that rejection used to escape as an
|
|
209
|
+
* unhandled rejection and dump an inquirer stack trace over the user's
|
|
210
|
+
* terminal. The class is not exported from `@inquirer/prompts`, and importing
|
|
211
|
+
* `@inquirer/core` directly would tie us to a transitive dependency, so it is
|
|
212
|
+
* matched by name.
|
|
213
|
+
*/
|
|
214
|
+
export function isExitPromptError(err) {
|
|
215
|
+
return Boolean(err) && err.name === "ExitPromptError";
|
|
216
|
+
}
|
|
217
|
+
|
|
203
218
|
/** Print an error on stderr (or as JSON) and set a non-zero exit code. */
|
|
204
219
|
export function reportError(jsonMode, message) {
|
|
205
220
|
if (jsonMode) {
|
|
@@ -365,15 +380,30 @@ export async function runInteractive(opts = {}) {
|
|
|
365
380
|
const selected = await promptSelect("Choose a tool:", choices, { pageSize: 15 });
|
|
366
381
|
if (!selected) return;
|
|
367
382
|
|
|
368
|
-
|
|
369
|
-
|
|
383
|
+
return await runInteractiveFor(selected.slug, { ...opts, json: jsonMode, tools });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Interactive configuration for a slug that is already known.
|
|
388
|
+
*
|
|
389
|
+
* Shared by the tool picker above and by `nxb run <slug>` with no input of any
|
|
390
|
+
* kind: a bare `nxb run text-statistics` has nothing to send, so asking for the
|
|
391
|
+
* fields is the useful behaviour rather than failing schema validation with
|
|
392
|
+
* "Missing required fields: text".
|
|
393
|
+
*
|
|
394
|
+
* Returns the executed slug, or undefined when the user backs out or the tool
|
|
395
|
+
* has no schema to configure.
|
|
396
|
+
*/
|
|
397
|
+
export async function runInteractiveFor(slug, opts = {}) {
|
|
398
|
+
const { json: jsonMode, tools: knownTools } = opts;
|
|
399
|
+
const resolved = await resolveToolSlug(slug, knownTools);
|
|
370
400
|
|
|
371
401
|
// 2. Fetch manifest / schema
|
|
372
402
|
const toolInfo = await fetchToolSource(resolved);
|
|
373
403
|
if (!toolInfo?.manifest) {
|
|
374
404
|
console.log(chalk.yellow(` No schema available for "${resolved}". Falling back to raw input.`));
|
|
375
405
|
await runCommand(resolved, {}, { ...opts, json: jsonMode });
|
|
376
|
-
return;
|
|
406
|
+
return resolved;
|
|
377
407
|
}
|
|
378
408
|
|
|
379
409
|
const { manifest } = toolInfo;
|
|
@@ -419,10 +449,11 @@ export async function runInteractive(opts = {}) {
|
|
|
419
449
|
if (!runIt) {
|
|
420
450
|
console.log(chalk.dim(" Command copied to clipboard (if available):"));
|
|
421
451
|
console.log(chalk.gray(` ${fullCommand}`));
|
|
422
|
-
return;
|
|
452
|
+
return resolved;
|
|
423
453
|
}
|
|
424
454
|
|
|
425
455
|
await runCommand(resolved, body, { ...opts, json: jsonMode, resolved });
|
|
456
|
+
return resolved;
|
|
426
457
|
}
|
|
427
458
|
|
|
428
459
|
/** Prompt for a single schema field based on its type. */
|