burgee 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +61 -9
- package/dist/brand.d.ts +62 -1
- package/dist/brand.js +73 -7
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +17 -1
- package/dist/{commander-argument.js → commander/argument.js} +4 -1
- package/dist/{commander-command.d.ts → commander/command.d.ts} +15 -5
- package/dist/{commander-command.js → commander/command.js} +175 -22
- package/dist/{commander-error.js → commander/error.js} +2 -0
- package/dist/{commander-help.d.ts → commander/help.d.ts} +3 -3
- package/dist/{commander-help.js → commander/help.js} +18 -1
- package/dist/{commander-option.d.ts → commander/option.d.ts} +1 -1
- package/dist/{commander-option.js → commander/option.js} +15 -1
- package/dist/commander.d.ts +8 -8
- package/dist/commander.js +8 -8
- package/dist/completions.js +15 -4
- package/dist/dev.d.ts +47 -0
- package/dist/dev.js +132 -0
- package/dist/execute.d.ts +22 -1
- package/dist/execute.js +75 -24
- package/dist/help.d.ts +19 -7
- package/dist/help.js +50 -21
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +15 -0
- package/dist/manifest.js +12 -1
- package/dist/mcp.d.ts +14 -2
- package/dist/mcp.js +15 -2
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +7 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +11 -0
- package/dist/testing-helpers.d.ts +18 -1
- package/dist/testing-helpers.js +35 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +1 -1
- package/dist/unknown-option.d.ts +9 -0
- package/dist/unknown-option.js +27 -0
- package/dist/{yargs-burgee.d.ts → yargs/burgee.d.ts} +2 -2
- package/dist/{yargs-burgee.js → yargs/burgee.js} +15 -1
- package/dist/{yargs-cliui.js → yargs/cliui.js} +8 -0
- package/dist/{yargs-command.d.ts → yargs/command.d.ts} +5 -5
- package/dist/{yargs-command.js → yargs/command.js} +9 -2
- package/dist/{yargs-completion.d.ts → yargs/completion.d.ts} +3 -3
- package/dist/{yargs-completion.js → yargs/completion.js} +7 -2
- package/dist/{yargs-factory.d.ts → yargs/factory.d.ts} +8 -8
- package/dist/{yargs-factory.js → yargs/factory.js} +63 -13
- package/dist/{yargs-middleware.js → yargs/middleware.js} +6 -1
- package/dist/{yargs-shim.d.ts → yargs/shim.d.ts} +3 -3
- package/dist/{yargs-shim.js → yargs/shim.js} +24 -5
- package/dist/{yargs-usage.d.ts → yargs/usage.d.ts} +1 -1
- package/dist/{yargs-usage.js → yargs/usage.js} +9 -1
- package/dist/{yargs-utils.js → yargs/utils.js} +11 -0
- package/dist/{yargs-validation.d.ts → yargs/validation.d.ts} +1 -1
- package/dist/{yargs-validation.js → yargs/validation.js} +7 -1
- package/dist/{yargs-y18n.js → yargs/y18n.js} +6 -0
- package/dist/yargs-helpers.d.ts +1 -1
- package/dist/yargs-helpers.js +1 -1
- package/dist/yargs.d.ts +4 -4
- package/dist/yargs.js +5 -5
- package/package.json +3 -2
- /package/dist/{commander-argument.d.ts → commander/argument.d.ts} +0 -0
- /package/dist/{commander-error.d.ts → commander/error.d.ts} +0 -0
- /package/dist/{commander-suggest.d.ts → suggest.d.ts} +0 -0
- /package/dist/{commander-suggest.js → suggest.js} +0 -0
- /package/dist/{yargs-cliui.d.ts → yargs/cliui.d.ts} +0 -0
- /package/dist/{yargs-middleware.d.ts → yargs/middleware.d.ts} +0 -0
- /package/dist/{yargs-utils.d.ts → yargs/utils.d.ts} +0 -0
- /package/dist/{yargs-y18n.d.ts → yargs/y18n.d.ts} +0 -0
package/dist/execute.js
CHANGED
|
@@ -8,7 +8,7 @@ import { serveMcp } from './mcp.js';
|
|
|
8
8
|
import { camel, kebab } from './names.js';
|
|
9
9
|
import { nearestPackage } from './pkg.js';
|
|
10
10
|
import { ConfigError, explain, resolve as resolveLayers } from './precedence.js';
|
|
11
|
-
import { commandSchemaOf, schemaOf, summaryOf } from './schema.js';
|
|
11
|
+
import { commandSchemaOf, machineJson, schemaOf, summaryOf } from './schema.js';
|
|
12
12
|
import { checkDefinition, checkRelations, coerce, UsageError } from './validate.js';
|
|
13
13
|
function helpFields(c) {
|
|
14
14
|
const node = {};
|
|
@@ -51,11 +51,15 @@ function addTree(manifest, parent, commands) {
|
|
|
51
51
|
...helpFields(c),
|
|
52
52
|
options: c.options ?? {},
|
|
53
53
|
...(c.run === undefined ? {} : { run: c.run }),
|
|
54
|
+
...(c.load === undefined ? {} : { load: c.load }),
|
|
54
55
|
});
|
|
55
56
|
if (c.commands !== undefined)
|
|
56
57
|
addTree(manifest, path, c.commands);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
export function sharedOptions(name, specs) {
|
|
61
|
+
return Object.fromEntries(Object.entries(specs).map(([key, spec]) => [key, { ...spec, sharedFrom: name }]));
|
|
62
|
+
}
|
|
59
63
|
export function defineProgram(program) {
|
|
60
64
|
const manifest = new Manifest();
|
|
61
65
|
manifest.rootPath = [program.name];
|
|
@@ -112,6 +116,7 @@ function render(value) {
|
|
|
112
116
|
}
|
|
113
117
|
return String(value);
|
|
114
118
|
}
|
|
119
|
+
const NO = 'no-';
|
|
115
120
|
function toParseConfig(specs, withConfig) {
|
|
116
121
|
const config = { json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' }, explain: { type: 'string' } };
|
|
117
122
|
if (withConfig) {
|
|
@@ -124,11 +129,28 @@ function toParseConfig(specs, withConfig) {
|
|
|
124
129
|
...(spec.short === undefined ? {} : { short: spec.short }),
|
|
125
130
|
...(spec.multiple === true ? { multiple: true } : {}),
|
|
126
131
|
};
|
|
132
|
+
if (spec.type === 'boolean')
|
|
133
|
+
config[`${NO}${kebab(name)}`] = { type: 'boolean' };
|
|
127
134
|
}
|
|
128
135
|
return config;
|
|
129
136
|
}
|
|
130
|
-
function canonical(values) {
|
|
131
|
-
|
|
137
|
+
function canonical(values, specs, tokens) {
|
|
138
|
+
const out = {};
|
|
139
|
+
const negatable = (key) => {
|
|
140
|
+
const bare = camel(key.startsWith(NO) ? key.slice(NO.length) : key);
|
|
141
|
+
return specs[bare]?.type === 'boolean' ? bare : '';
|
|
142
|
+
};
|
|
143
|
+
for (const [k, v] of Object.entries(values))
|
|
144
|
+
if (!k.startsWith(NO) || negatable(k) === '')
|
|
145
|
+
out[camel(k)] = v;
|
|
146
|
+
for (const token of tokens) {
|
|
147
|
+
if (token.kind !== 'option')
|
|
148
|
+
continue;
|
|
149
|
+
const bare = negatable(token.name);
|
|
150
|
+
if (bare !== '')
|
|
151
|
+
out[bare] = !token.name.startsWith(NO);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
132
154
|
}
|
|
133
155
|
function packageLayer(pkg, name) {
|
|
134
156
|
if (pkg === undefined || name === undefined)
|
|
@@ -191,18 +213,7 @@ function exitSignal(cause) {
|
|
|
191
213
|
const code = cause?.code;
|
|
192
214
|
return typeof code === 'number' && isExitCode(code) ? code : undefined;
|
|
193
215
|
}
|
|
194
|
-
|
|
195
|
-
function singleDashHint(argv) {
|
|
196
|
-
for (const token of argv) {
|
|
197
|
-
if (token === '--')
|
|
198
|
-
return undefined;
|
|
199
|
-
const found = SINGLE_DASH_WORD.exec(token);
|
|
200
|
-
if (found?.[1] !== undefined)
|
|
201
|
-
return `did you mean --${found[1]}? a single dash introduces one-letter options`;
|
|
202
|
-
}
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
205
|
-
function describeFailure(cause, argv) {
|
|
216
|
+
async function describeFailure(cause, argv, node) {
|
|
206
217
|
const signal = exitSignal(cause);
|
|
207
218
|
if (signal !== undefined)
|
|
208
219
|
return { code: signal, message: '', silent: true };
|
|
@@ -216,7 +227,12 @@ function describeFailure(cause, argv) {
|
|
|
216
227
|
return { code: ExitCode.CONFIG, message, ...(cause.hint === undefined ? {} : { hint: cause.hint }) };
|
|
217
228
|
}
|
|
218
229
|
if (isParseArgsFailure(cause)) {
|
|
219
|
-
|
|
230
|
+
const explain = await import('./unknown-option.js');
|
|
231
|
+
const dash = explain.singleDashHint(argv);
|
|
232
|
+
if (dash !== undefined)
|
|
233
|
+
return { code: ExitCode.USAGE, message, hint: dash };
|
|
234
|
+
const better = explain.unknownOption(cause, Object.keys(node?.options ?? {}));
|
|
235
|
+
return { code: ExitCode.USAGE, message, hint: 'run --help to see the available options', ...better };
|
|
220
236
|
}
|
|
221
237
|
return { code: ExitCode.RUNTIME, message };
|
|
222
238
|
}
|
|
@@ -242,13 +258,16 @@ export function beforeTerminator(argv) {
|
|
|
242
258
|
function rootNode(manifest, root) {
|
|
243
259
|
return manifest.find(root) ?? { path: root, options: {} };
|
|
244
260
|
}
|
|
245
|
-
function unresolved({ manifest, root, io
|
|
261
|
+
function unresolved({ manifest, root, io }, argv, at) {
|
|
246
262
|
const node = at ?? rootNode(manifest, root);
|
|
247
263
|
const typed = argv.slice(node.path.length - root.length);
|
|
248
|
-
|
|
249
|
-
|
|
264
|
+
const first = typed[0] ?? '';
|
|
265
|
+
if (typed.length > 0 && HELP_FLAGS.has(first))
|
|
266
|
+
return { text: renderHelp(manifest, node, { width: io.width }), code: ExitCode.OK };
|
|
267
|
+
if (first === '--version' || first === '-V')
|
|
268
|
+
return { text: `${versionOf(manifest, io)}\n`, code: ExitCode.OK };
|
|
250
269
|
if (typed.length === 0)
|
|
251
|
-
return { text: renderHelp(manifest, node, { width }), code: ExitCode.USAGE };
|
|
270
|
+
return { text: renderHelp(manifest, node, { width: io.width }), code: ExitCode.USAGE };
|
|
252
271
|
throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
|
|
253
272
|
}
|
|
254
273
|
async function completion(manifest, argv, io) {
|
|
@@ -275,7 +294,7 @@ async function surface(manifest, argv, io) {
|
|
|
275
294
|
return true;
|
|
276
295
|
}
|
|
277
296
|
if (head.includes('--schema')) {
|
|
278
|
-
io.out.write(`${
|
|
297
|
+
io.out.write(`${machineJson(schemaSurface(manifest, argv), head)}\n`);
|
|
279
298
|
return true;
|
|
280
299
|
}
|
|
281
300
|
if (head[0] === '--mcp') {
|
|
@@ -301,7 +320,7 @@ async function surface(manifest, argv, io) {
|
|
|
301
320
|
}
|
|
302
321
|
const SCHEMA_BUDGET = 48_000;
|
|
303
322
|
function schemaSurface(manifest, argv) {
|
|
304
|
-
const { node } = manifest.resolve(beforeTerminator(argv).filter((a) => a !== '--schema'), manifest.rootPath);
|
|
323
|
+
const { node } = manifest.resolve(beforeTerminator(argv).filter((a) => a !== '--schema' && !a.startsWith('--format=')), manifest.rootPath);
|
|
305
324
|
if (node?.run !== undefined)
|
|
306
325
|
return commandSchemaOf(node, manifest.rootPath);
|
|
307
326
|
const full = schemaOf(manifest);
|
|
@@ -329,7 +348,7 @@ function versionOf(manifest, io) {
|
|
|
329
348
|
}
|
|
330
349
|
async function dispatch(manifest, { node, rest, name }, io) {
|
|
331
350
|
const parsed = parseArgs({ args: rest, options: toParseConfig(node.options, manifest.config !== undefined), allowPositionals: true, strict: true, tokens: true });
|
|
332
|
-
const flags = canonical(parsed.values);
|
|
351
|
+
const flags = canonical(parsed.values, node.options, parsed.tokens);
|
|
333
352
|
const json = flags.json === true;
|
|
334
353
|
if (flags.help === true)
|
|
335
354
|
return { json, text: renderHelp(manifest, node, { width: io.width }) };
|
|
@@ -342,6 +361,8 @@ async function dispatch(manifest, { node, rest, name }, io) {
|
|
|
342
361
|
checkRelations(node.relations, resolved.values, provenance);
|
|
343
362
|
const values = await coerce(node.options, resolved.values);
|
|
344
363
|
const { positionals, passthrough } = splitPositionals(parsed.tokens);
|
|
364
|
+
requirePositionals(node, positionals);
|
|
365
|
+
warnDeprecated(node, io);
|
|
345
366
|
await manifest.fire('preRun', name, values);
|
|
346
367
|
const exit = (code) => {
|
|
347
368
|
io.exit(code);
|
|
@@ -353,6 +374,25 @@ async function dispatch(manifest, { node, rest, name }, io) {
|
|
|
353
374
|
const changed = changedOf(node, data);
|
|
354
375
|
return { json, data, provenance, ...(changed === undefined ? {} : { changed }) };
|
|
355
376
|
}
|
|
377
|
+
function requirePositionals(node, positionals) {
|
|
378
|
+
const required = (node.arguments ?? []).filter((a) => a.required !== false && a.variadic !== true);
|
|
379
|
+
const missing = required[positionals.length];
|
|
380
|
+
if (positionals.length < required.length && missing !== undefined) {
|
|
381
|
+
throw new UsageError(`missing required argument "${missing.name}"`, `run --help to see what "${node.path.slice(1).join(' ')}" takes`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const warned = new Set();
|
|
385
|
+
function warnDeprecated(node, io) {
|
|
386
|
+
if (node.deprecated === undefined || node.deprecated === false)
|
|
387
|
+
return;
|
|
388
|
+
const key = node.path.join(' ');
|
|
389
|
+
if (warned.has(key))
|
|
390
|
+
return;
|
|
391
|
+
warned.add(key);
|
|
392
|
+
const typed = node.path.slice(1).join(' ') || key;
|
|
393
|
+
const use = typeof node.deprecated === 'string' ? `, use '${node.deprecated}'` : '';
|
|
394
|
+
io.err.write(`warning: '${typed}' is deprecated${use}\n`);
|
|
395
|
+
}
|
|
356
396
|
function emit(io, outcome) {
|
|
357
397
|
if (outcome.text !== undefined) {
|
|
358
398
|
io.out.write(outcome.text);
|
|
@@ -364,7 +404,7 @@ function emit(io, outcome) {
|
|
|
364
404
|
return io.exit(ExitCode.OK);
|
|
365
405
|
}
|
|
366
406
|
async function report(cause, { manifest, io, argv, json, name }) {
|
|
367
|
-
const failure = describeFailure(cause, argv);
|
|
407
|
+
const failure = await describeFailure(cause, argv, resolveCommand(manifest, argv) ?? undefined);
|
|
368
408
|
if (failure.silent === true)
|
|
369
409
|
return io.exit(failure.code);
|
|
370
410
|
await manifest.fire('onError', name, {});
|
|
@@ -418,6 +458,17 @@ export async function execute(manifest, opts = {}) {
|
|
|
418
458
|
return await report(cause, { manifest, io, argv, json, name });
|
|
419
459
|
}
|
|
420
460
|
}
|
|
461
|
+
export function resolveCommand(manifest, argv) {
|
|
462
|
+
const { node } = manifest.resolve(beforeTerminator(argv), manifest.rootPath);
|
|
463
|
+
return node ?? null;
|
|
464
|
+
}
|
|
465
|
+
export async function runCommand(manifest, argv, opts = {}) {
|
|
466
|
+
const out = [];
|
|
467
|
+
const err = [];
|
|
468
|
+
let code = ExitCode.OK;
|
|
469
|
+
await execute(manifest, { ...opts, argv: [...argv], from: 'user', stdout: { write: (s) => out.push(s) }, stderr: { write: (s) => err.push(s) }, exit: (c) => void (code = c) });
|
|
470
|
+
return { code, stdout: out.join(''), stderr: err.join('') };
|
|
471
|
+
}
|
|
421
472
|
export async function run(target, opts = {}) {
|
|
422
473
|
if (target instanceof Manifest)
|
|
423
474
|
return await execute(target, opts);
|
package/dist/help.d.ts
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
+
import type { CommandNode, Manifest } from './manifest.js';
|
|
2
|
+
/** The token names of `roundel`'s R3, typed structurally: burgee never imports them (U13). */
|
|
3
|
+
export type HelpToken = 'error' | 'warn' | 'ok' | 'hint' | 'muted' | 'command' | 'flag' | 'value' | 'heading';
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
5
|
+
* Per-token styling for help (R7). A user who has `roundel` passes its tokens; a user
|
|
6
|
+
* who does not gets the defaults. Help reads `heading`, `command`, `flag` and `value`;
|
|
7
|
+
* the others are accepted so one theme object serves the whole output stack.
|
|
4
8
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
9
|
+
* What the theme does not touch: the command name after `Usage:` and every `$ example`
|
|
10
|
+
* line are rendered plain, whatever the theme says. Styling wraps a finished cell, so a
|
|
11
|
+
* name is measured and padded plain and never coloured in the manifest (yargs #1699).
|
|
8
12
|
*/
|
|
9
|
-
|
|
13
|
+
export type HelpTheme = Partial<Record<HelpToken, (s: string) => string>>;
|
|
10
14
|
export interface HelpOptions {
|
|
11
15
|
/** Columns available; 100 when unknown, never `process.stdout` directly (H3). */
|
|
12
16
|
width?: number;
|
|
13
17
|
/** Show type hints such as `[string]`; off by default (H6). */
|
|
14
18
|
verbose?: boolean;
|
|
19
|
+
/** Apply colour (R7). Off by default: the renderer is pure, so the TTY and NO_COLOR decision stays with the caller. */
|
|
20
|
+
color?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the default styling token by token; read only when `color` is on. See
|
|
23
|
+
* `HelpTheme` for the lines it leaves plain: the `Usage:` command name and `$ example`.
|
|
24
|
+
*/
|
|
25
|
+
theme?: HelpTheme;
|
|
15
26
|
}
|
|
16
27
|
/** Word-wrap one paragraph; lines the author indented are kept verbatim (yargs #2120). */
|
|
17
28
|
export declare function wrap(text: string, width: number): string[];
|
|
18
29
|
/**
|
|
19
30
|
* Render help for one node — a runnable command, a group, or both — as text.
|
|
20
|
-
* Deterministic for a given node and width; a snapshot suite pins it.
|
|
31
|
+
* Deterministic for a given node and width; a snapshot suite pins it. With `color`
|
|
32
|
+
* off — the default — a theme changes nothing; with it on, only ANSI is added.
|
|
21
33
|
*/
|
|
22
34
|
export declare function renderHelp(manifest: Manifest, node: CommandNode, opts?: HelpOptions): string;
|
package/dist/help.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
1
2
|
import { kebab } from './names.js';
|
|
3
|
+
const identity = (s) => s;
|
|
4
|
+
const PLAIN = { heading: identity, command: identity, flag: identity, value: identity };
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
heading: (s) => styleText('bold', s, { validateStream: false }),
|
|
7
|
+
command: (s) => styleText('bold', s, { validateStream: false }),
|
|
8
|
+
flag: (s) => styleText('cyan', s, { validateStream: false }),
|
|
9
|
+
value: (s) => styleText('dim', s, { validateStream: false }),
|
|
10
|
+
};
|
|
11
|
+
function painterFor(paint, kind) {
|
|
12
|
+
if (kind === 'command')
|
|
13
|
+
return paint.command;
|
|
14
|
+
return kind === 'flag' ? paint.flag : paint.value;
|
|
15
|
+
}
|
|
16
|
+
function paintOf(opts) {
|
|
17
|
+
if (opts.color !== true)
|
|
18
|
+
return PLAIN;
|
|
19
|
+
const theme = opts.theme ?? {};
|
|
20
|
+
return {
|
|
21
|
+
heading: theme.heading ?? DEFAULTS.heading,
|
|
22
|
+
command: theme.command ?? DEFAULTS.command,
|
|
23
|
+
flag: theme.flag ?? DEFAULTS.flag,
|
|
24
|
+
value: theme.value ?? DEFAULTS.value,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
2
27
|
const DEFAULT_WIDTH = 100;
|
|
3
28
|
const INDENT = ' ';
|
|
4
29
|
const GUTTER = 2;
|
|
5
30
|
const TERM_SHARE = 0.4;
|
|
6
31
|
const GLOBAL = [
|
|
7
|
-
{ term: '--json', text: 'machine-readable output' },
|
|
8
|
-
{ term: '--help', text: 'show this help' },
|
|
32
|
+
{ term: '--json', text: 'machine-readable output', kind: 'flag' },
|
|
33
|
+
{ term: '--help', text: 'show this help', kind: 'flag' },
|
|
9
34
|
];
|
|
10
35
|
function deprecation(d) {
|
|
11
36
|
if (d === undefined || d === false)
|
|
@@ -41,7 +66,7 @@ function optionTerm(name, spec) {
|
|
|
41
66
|
function optionRows(options, verbose) {
|
|
42
67
|
return Object.entries(options)
|
|
43
68
|
.filter(([, spec]) => spec.hidden !== true)
|
|
44
|
-
.map(([name, spec]) => ({ term: optionTerm(name, spec), text: annotate(spec.description ?? '', spec, verbose) }));
|
|
69
|
+
.map(([name, spec]) => ({ term: optionTerm(name, spec), text: annotate(spec.description ?? '', spec, verbose), kind: 'flag' }));
|
|
45
70
|
}
|
|
46
71
|
const argumentTerm = (a) => {
|
|
47
72
|
const name = a.variadic === true ? `${a.name}...` : a.name;
|
|
@@ -51,6 +76,7 @@ function argumentRows(args) {
|
|
|
51
76
|
return args.map((a) => ({
|
|
52
77
|
term: argumentTerm(a),
|
|
53
78
|
text: [a.description ?? '', a.default === undefined ? '' : `(default: ${a.default})`].filter((p) => p !== '').join(' '),
|
|
79
|
+
kind: 'value',
|
|
54
80
|
}));
|
|
55
81
|
}
|
|
56
82
|
function commandSections(manifest, node) {
|
|
@@ -59,7 +85,7 @@ function commandSections(manifest, node) {
|
|
|
59
85
|
for (const c of children) {
|
|
60
86
|
const heading = c.group ?? 'Commands:';
|
|
61
87
|
const rows = groups.get(heading) ?? [];
|
|
62
|
-
rows.push({ term: c.path[c.path.length - 1] ?? '', text: `${c.summary ?? c.description ?? ''}${deprecation(c.deprecated)}`.trim() });
|
|
88
|
+
rows.push({ term: c.path[c.path.length - 1] ?? '', text: `${c.summary ?? c.description ?? ''}${deprecation(c.deprecated)}`.trim(), kind: 'command' });
|
|
63
89
|
groups.set(heading, rows);
|
|
64
90
|
}
|
|
65
91
|
return [...groups].map(([title, rows]) => ({ title, rows }));
|
|
@@ -67,9 +93,9 @@ function commandSections(manifest, node) {
|
|
|
67
93
|
function environmentRows(options) {
|
|
68
94
|
return Object.entries(options)
|
|
69
95
|
.filter(([, spec]) => spec.env !== undefined && spec.hidden !== true)
|
|
70
|
-
.map(([name, spec]) => ({ term: spec.env ?? '', text: `--${kebab(name)}
|
|
96
|
+
.map(([name, spec]) => ({ term: spec.env ?? '', text: `--${kebab(name)}`, kind: 'value' }));
|
|
71
97
|
}
|
|
72
|
-
function usageLine(node, root, hasChildren) {
|
|
98
|
+
function usageLine(node, root, hasChildren, paint) {
|
|
73
99
|
const shown = node.path.slice(root.length).join(' ') || node.path.join(' ');
|
|
74
100
|
const parts = [shown];
|
|
75
101
|
if (hasChildren)
|
|
@@ -77,7 +103,7 @@ function usageLine(node, root, hasChildren) {
|
|
|
77
103
|
parts.push('[options]');
|
|
78
104
|
for (const a of node.arguments ?? [])
|
|
79
105
|
parts.push(argumentTerm(a));
|
|
80
|
-
return
|
|
106
|
+
return `${paint.heading('Usage:')} ${parts.join(' ')}`;
|
|
81
107
|
}
|
|
82
108
|
export function wrap(text, width) {
|
|
83
109
|
const out = [];
|
|
@@ -103,21 +129,23 @@ function termColumn(rows, width) {
|
|
|
103
129
|
const longest = Math.max(0, ...rows.map((r) => r.term.length));
|
|
104
130
|
return Math.min(longest, Math.floor(width * TERM_SHARE));
|
|
105
131
|
}
|
|
106
|
-
function layout(rows, width, column) {
|
|
132
|
+
function layout(rows, width, column, paint) {
|
|
107
133
|
const textWidth = Math.max(1, width - INDENT.length - column - GUTTER);
|
|
108
134
|
const lines = [];
|
|
109
|
-
for (const { term, text } of rows) {
|
|
135
|
+
for (const { term, text, kind } of rows) {
|
|
136
|
+
const cell = painterFor(paint, kind)(term);
|
|
110
137
|
if (text === '') {
|
|
111
|
-
lines.push(`${INDENT}${
|
|
138
|
+
lines.push(`${INDENT}${cell}`);
|
|
112
139
|
continue;
|
|
113
140
|
}
|
|
114
141
|
const wrapped = wrap(text, textWidth);
|
|
115
142
|
const continuation = INDENT + ' '.repeat(column + GUTTER);
|
|
116
143
|
if (term.length > column) {
|
|
117
|
-
lines.push(`${INDENT}${
|
|
144
|
+
lines.push(`${INDENT}${cell}`, ...wrapped.map((l) => `${continuation}${l}`));
|
|
118
145
|
continue;
|
|
119
146
|
}
|
|
120
|
-
|
|
147
|
+
const pad = ' '.repeat(column + GUTTER - term.length);
|
|
148
|
+
lines.push(`${INDENT}${cell}${pad}${wrapped[0] ?? ''}`, ...wrapped.slice(1).map((l) => `${continuation}${l}`));
|
|
121
149
|
}
|
|
122
150
|
return lines;
|
|
123
151
|
}
|
|
@@ -130,28 +158,29 @@ function exampleLines(examples, width) {
|
|
|
130
158
|
}
|
|
131
159
|
return lines;
|
|
132
160
|
}
|
|
133
|
-
function section(title, body) {
|
|
134
|
-
return body.length === 0 ? [] : [title, ...body, ''];
|
|
161
|
+
function section(title, body, paint) {
|
|
162
|
+
return body.length === 0 ? [] : [paint.heading(title), ...body, ''];
|
|
135
163
|
}
|
|
136
164
|
export function renderHelp(manifest, node, opts = {}) {
|
|
137
165
|
const width = opts.width ?? DEFAULT_WIDTH;
|
|
138
166
|
const verbose = opts.verbose === true;
|
|
167
|
+
const paint = paintOf(opts);
|
|
139
168
|
const root = manifest.rootPath;
|
|
140
169
|
const commands = commandSections(manifest, node);
|
|
141
170
|
const args = argumentRows(node.arguments ?? []);
|
|
142
171
|
const options = optionRows(node.options, verbose);
|
|
143
172
|
const env = environmentRows(node.options);
|
|
144
173
|
const column = termColumn([...args, ...options, ...GLOBAL, ...commands.flatMap((s) => s.rows), ...env], width);
|
|
145
|
-
const lines = [usageLine(node, root, commands.length > 0), ''];
|
|
174
|
+
const lines = [usageLine(node, root, commands.length > 0, paint), ''];
|
|
146
175
|
if (node.description !== undefined)
|
|
147
176
|
lines.push(...wrap(`${node.description}${deprecation(node.deprecated)}`, width), '');
|
|
148
|
-
lines.push(...section('Arguments:', layout(args, width, column)));
|
|
149
|
-
lines.push(...section('Options:', layout(options, width, column)));
|
|
150
|
-
lines.push(...section('Global options:', layout(GLOBAL, width, column)));
|
|
177
|
+
lines.push(...section('Arguments:', layout(args, width, column, paint), paint));
|
|
178
|
+
lines.push(...section('Options:', layout(options, width, column, paint), paint));
|
|
179
|
+
lines.push(...section('Global options:', layout(GLOBAL, width, column, paint), paint));
|
|
151
180
|
for (const s of commands)
|
|
152
|
-
lines.push(...section(s.title, layout(s.rows, width, column)));
|
|
153
|
-
lines.push(...section('Examples:', exampleLines(node.examples ?? [], width)));
|
|
154
|
-
lines.push(...section('Environment:', layout(env, width, column)));
|
|
181
|
+
lines.push(...section(s.title, layout(s.rows, width, column, paint), paint));
|
|
182
|
+
lines.push(...section('Examples:', exampleLines(node.examples ?? [], width), paint));
|
|
183
|
+
lines.push(...section('Environment:', layout(env, width, column, paint), paint));
|
|
155
184
|
if (node.epilogue !== undefined)
|
|
156
185
|
lines.push(...wrap(node.epilogue, width), '');
|
|
157
186
|
return `${lines.join('\n').trimEnd()}\n`;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* façade can import it without pulling this barrel.
|
|
7
7
|
*/
|
|
8
8
|
export { ExitCode, isExitCode, type ExitCode as ExitCodeValue } from './exit-code.js';
|
|
9
|
-
export { defineCommand, defineProgram, execute, run, type AnyCommand, type Command, type CommandContext, type InferOptions, type OptionSpecs, type Program, type RunOptions, } from './execute.js';
|
|
9
|
+
export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, type AnyCommand, type Command, type CommandContext, type InferOptions, type OptionSpecs, type Program, type RunOptions, type RunResult, } from './execute.js';
|
|
10
10
|
export { camel, checkDefinition, kebab, UsageError } from './validate.js';
|
|
11
11
|
export { AGENT_PROBES, detectAgent, type AgentProbe, type Detection } from './agent.js';
|
|
12
|
-
export { renderHelp, type HelpOptions } from './help.js';
|
|
12
|
+
export { renderHelp, type HelpOptions, type HelpTheme, type HelpToken } from './help.js';
|
|
13
13
|
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf, type Invoke, type ServeOptions, type Tool, type ToolAnnotations } from './mcp.js';
|
|
14
14
|
export { ConfigError, envName, explain, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from './precedence.js';
|
|
15
15
|
export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf, type CommandSchema, type JsonSchema, type ProgramSchema, type SchemaSummary } from './schema.js';
|
|
16
|
-
export { definePlugin, Manifest, type ArgumentSpec, type CommandNode, type Effects, type Example, type Hook, type OptionSpec, type ActionRequiredSpec, type Plugin, type Relation, type RunContext, type StandardResult, type StandardSchemaV1, } from './manifest.js';
|
|
16
|
+
export { definePlugin, Manifest, type ArgumentSpec, type CommandNode, type Effects, type Example, type Hook, type LazyModule, type OptionSpec, type ActionRequiredSpec, type Plugin, type Relation, type RunContext, type StandardResult, type StandardSchemaV1, } from './manifest.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ExitCode, isExitCode } from './exit-code.js';
|
|
2
|
-
export { defineCommand, defineProgram, execute, run, } from './execute.js';
|
|
2
|
+
export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, } from './execute.js';
|
|
3
3
|
export { camel, checkDefinition, kebab, UsageError } from './validate.js';
|
|
4
4
|
export { AGENT_PROBES, detectAgent } from './agent.js';
|
|
5
5
|
export { renderHelp } from './help.js';
|
package/dist/manifest.d.ts
CHANGED
|
@@ -53,6 +53,13 @@ export interface OptionSpec {
|
|
|
53
53
|
/** `true` renders `(deprecated)`; a string names the replacement: `(deprecated: use --force)` (yargs #2248). */
|
|
54
54
|
deprecated?: boolean | string;
|
|
55
55
|
hidden?: boolean;
|
|
56
|
+
/** The shared set this option was copied from (M4); `--schema` carries it, help lists the option like any other. */
|
|
57
|
+
sharedFrom?: string;
|
|
58
|
+
}
|
|
59
|
+
/** What a lazily loaded command module exports: the handler as `run` or as the default export (M2). */
|
|
60
|
+
export interface LazyModule {
|
|
61
|
+
default?: (ctx: RunContext) => unknown;
|
|
62
|
+
run?: (ctx: RunContext) => unknown;
|
|
56
63
|
}
|
|
57
64
|
/**
|
|
58
65
|
* What running a command does to the world (N6). Declared, never inferred: it decides
|
|
@@ -133,9 +140,17 @@ export interface CommandNode {
|
|
|
133
140
|
/** Required for a command to be served as an MCP tool (N2, N6). */
|
|
134
141
|
effects?: Effects;
|
|
135
142
|
run?: (ctx: RunContext) => unknown;
|
|
143
|
+
/**
|
|
144
|
+
* The handler's module, imported on dispatch only (M2): the manifest — help, schema,
|
|
145
|
+
* completions, MCP tool list — is complete from this node without loading it. A node
|
|
146
|
+
* with `load` and no `run` gets a `run` that imports on first call.
|
|
147
|
+
*/
|
|
148
|
+
load?: () => Promise<LazyModule>;
|
|
136
149
|
/** Which plugin contributed this, if any. Declared, never diffed (M3). */
|
|
137
150
|
plugin?: string;
|
|
138
151
|
}
|
|
152
|
+
/** `run` for a lazy node: the module is imported on the first call and never before (M2). */
|
|
153
|
+
export declare function lazyRun(load: () => Promise<LazyModule>): (ctx: RunContext) => unknown;
|
|
139
154
|
/** A hook may declare which commands it applies to, as data. */
|
|
140
155
|
export interface HookFilter {
|
|
141
156
|
command?: RegExp;
|
package/dist/manifest.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export function lazyRun(load) {
|
|
2
|
+
let loaded;
|
|
3
|
+
return async (ctx) => {
|
|
4
|
+
loaded ??= load();
|
|
5
|
+
const mod = await loaded;
|
|
6
|
+
const handler = mod.run ?? mod.default;
|
|
7
|
+
if (handler === undefined)
|
|
8
|
+
throw new Error('burgee: a lazy command module must export its handler as run or as the default export');
|
|
9
|
+
return await handler(ctx);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
1
12
|
export function definePlugin(plugin) {
|
|
2
13
|
return plugin;
|
|
3
14
|
}
|
|
@@ -16,7 +27,7 @@ export class Manifest {
|
|
|
16
27
|
config;
|
|
17
28
|
schemaBudget;
|
|
18
29
|
add(node) {
|
|
19
|
-
this.commands.push(node);
|
|
30
|
+
this.commands.push(node.load !== undefined && node.run === undefined ? { ...node, run: lazyRun(node.load) } : node);
|
|
20
31
|
}
|
|
21
32
|
use(plugin) {
|
|
22
33
|
this.plugins.push(plugin);
|
package/dist/mcp.d.ts
CHANGED
|
@@ -33,8 +33,20 @@ export interface ServeOptions {
|
|
|
33
33
|
};
|
|
34
34
|
invoke: Invoke;
|
|
35
35
|
}
|
|
36
|
+
/** A running server: `done` settles when the input closes; `swap` serves a new manifest and says so (W2). */
|
|
37
|
+
export interface McpServer {
|
|
38
|
+
done: Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Serve this manifest (and its invoke) from the next request on, and emit
|
|
41
|
+
* `notifications/tools/list_changed` so a connected client re-lists. A call already in
|
|
42
|
+
* flight finishes against the manifest it started on.
|
|
43
|
+
*/
|
|
44
|
+
swap: (manifest: Manifest, invoke?: Invoke) => void;
|
|
45
|
+
}
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
38
|
-
* a JSON-RPC error with a null id, as the spec asks.
|
|
47
|
+
* Start serving; `done` settles when the input closes. Notifications (no `id`) get no
|
|
48
|
+
* reply; a malformed line gets a JSON-RPC error with a null id, as the spec asks.
|
|
39
49
|
*/
|
|
50
|
+
export declare function startMcp(manifest: Manifest, opts: ServeOptions): McpServer;
|
|
51
|
+
/** Serve until the input closes. */
|
|
40
52
|
export declare function serveMcp(manifest: Manifest, opts: ServeOptions): Promise<void>;
|
package/dist/mcp.js
CHANGED
|
@@ -81,14 +81,27 @@ async function handle(session, request) {
|
|
|
81
81
|
return { error: { code: JSON_RPC_METHOD_NOT_FOUND, message: `method not found: ${request.method}` } };
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
export
|
|
84
|
+
export function startMcp(manifest, opts) {
|
|
85
85
|
const session = {
|
|
86
86
|
manifest,
|
|
87
87
|
invoke: opts.invoke,
|
|
88
88
|
serverInfo: { name: manifest.rootPath.join(' ') || 'burgee', version: manifest.version ?? '0.0.0' },
|
|
89
89
|
};
|
|
90
90
|
const reply = (body) => void opts.output.write(`${JSON.stringify({ jsonrpc: '2.0', ...body })}\n`);
|
|
91
|
-
|
|
91
|
+
const swap = (next, invoke) => {
|
|
92
|
+
session.manifest = next;
|
|
93
|
+
if (invoke !== undefined)
|
|
94
|
+
session.invoke = invoke;
|
|
95
|
+
reply({ method: 'notifications/tools/list_changed' });
|
|
96
|
+
};
|
|
97
|
+
const done = serve(session, opts.input, reply);
|
|
98
|
+
return { done, swap };
|
|
99
|
+
}
|
|
100
|
+
export async function serveMcp(manifest, opts) {
|
|
101
|
+
return await startMcp(manifest, opts).done;
|
|
102
|
+
}
|
|
103
|
+
async function serve(session, input, reply) {
|
|
104
|
+
for await (const line of createInterface({ input, crlfDelay: Infinity })) {
|
|
92
105
|
if (line.trim() === '')
|
|
93
106
|
continue;
|
|
94
107
|
let request;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -3,6 +3,16 @@ import { type ExitCode } from './exit-code.js';
|
|
|
3
3
|
export interface Writer {
|
|
4
4
|
write(chunk: string): unknown;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Time, as the layers above the parser see it (R14 of `cli-output-stack`). `now` is
|
|
8
|
+
* monotonic milliseconds from an arbitrary origin; `schedule` runs `fn` after `ms` and
|
|
9
|
+
* hands back the cancel. Typed structurally so the output stack can accept a `Runtime`
|
|
10
|
+
* without importing one.
|
|
11
|
+
*/
|
|
12
|
+
export interface Clock {
|
|
13
|
+
now(): number;
|
|
14
|
+
schedule(fn: () => void, ms: number): () => void;
|
|
15
|
+
}
|
|
6
16
|
/**
|
|
7
17
|
* The world, as the layers above the parser see it (design R1 of
|
|
8
18
|
* `cli-testing-harness`). Nothing above the parser reads `process.*` directly; it
|
|
@@ -22,6 +32,8 @@ export interface Runtime {
|
|
|
22
32
|
};
|
|
23
33
|
/** Ends the run with an E1 code. In the real runtime this never returns. */
|
|
24
34
|
exit(code: ExitCode): never;
|
|
35
|
+
/** `performance.now` and `setTimeout` in the real runtime; a manual tick in the harness. */
|
|
36
|
+
clock: Clock;
|
|
25
37
|
}
|
|
26
38
|
/** The one place in the layer that touches `process`. Locked by `process-reference.lock.test.ts`. */
|
|
27
39
|
export declare const processRuntime: Runtime;
|
package/dist/runtime.js
CHANGED
package/dist/schema.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface JsonSchemaProperty {
|
|
|
24
24
|
maximum?: number;
|
|
25
25
|
/** The command-line spelling of the option (S5). */
|
|
26
26
|
flag?: string;
|
|
27
|
+
/** The shared set this option was copied from (M4). */
|
|
28
|
+
sharedFrom?: string;
|
|
27
29
|
}
|
|
28
30
|
export interface CommandSchema {
|
|
29
31
|
/** The command as typed, without the program name: `config get`. */
|
|
@@ -32,6 +34,12 @@ export interface CommandSchema {
|
|
|
32
34
|
summary?: string;
|
|
33
35
|
effects?: Effects;
|
|
34
36
|
deprecated?: boolean | string;
|
|
37
|
+
/** The heading it is listed under (M1). */
|
|
38
|
+
group?: string;
|
|
39
|
+
/** Its handler loads on dispatch (M2): this schema was complete without it. */
|
|
40
|
+
lazy?: true;
|
|
41
|
+
/** Which plugin contributed it (M3). */
|
|
42
|
+
plugin?: string;
|
|
35
43
|
arguments: ArgumentSpec[];
|
|
36
44
|
options: Record<string, OptionSpec>;
|
|
37
45
|
examples: Example[];
|
|
@@ -69,3 +77,5 @@ export interface SchemaSummary {
|
|
|
69
77
|
/** Above the budget (N13): every command by name and summary, and the drilling command for one in full. */
|
|
70
78
|
export declare function summaryOf(manifest: Manifest, budget: number): SchemaSummary;
|
|
71
79
|
export declare function schemaOf(manifest: Manifest): ProgramSchema;
|
|
80
|
+
/** The machine document, compact unless a person asked for the readable one. */
|
|
81
|
+
export declare const machineJson: (value: unknown, head: readonly string[]) => string;
|
package/dist/schema.js
CHANGED
|
@@ -30,6 +30,8 @@ function optionProperty(name, spec) {
|
|
|
30
30
|
p.minimum = spec.minimum;
|
|
31
31
|
if (spec.maximum !== undefined)
|
|
32
32
|
p.maximum = spec.maximum;
|
|
33
|
+
if (spec.sharedFrom !== undefined)
|
|
34
|
+
p.sharedFrom = spec.sharedFrom;
|
|
33
35
|
return p;
|
|
34
36
|
}
|
|
35
37
|
export function inputSchemaOf(node) {
|
|
@@ -68,6 +70,12 @@ export function commandSchemaOf(node, root) {
|
|
|
68
70
|
out.effects = node.effects;
|
|
69
71
|
if (node.deprecated !== undefined)
|
|
70
72
|
out.deprecated = node.deprecated;
|
|
73
|
+
if (node.group !== undefined)
|
|
74
|
+
out.group = node.group;
|
|
75
|
+
if (node.load !== undefined)
|
|
76
|
+
out.lazy = true;
|
|
77
|
+
if (node.plugin !== undefined)
|
|
78
|
+
out.plugin = node.plugin;
|
|
71
79
|
return out;
|
|
72
80
|
}
|
|
73
81
|
export function runnable(manifest) {
|
|
@@ -106,3 +114,6 @@ export function schemaOf(manifest) {
|
|
|
106
114
|
out.description = description;
|
|
107
115
|
return out;
|
|
108
116
|
}
|
|
117
|
+
const JSON_PRETTY = '--format=json-pretty';
|
|
118
|
+
const INDENT = 2;
|
|
119
|
+
export const machineJson = (value, head) => JSON.stringify(value, null, head.includes(JSON_PRETTY) ? INDENT : 0);
|