burgee 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/README.md +1 -0
- package/dist/check.d.ts +22 -0
- package/dist/check.js +35 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +12 -1
- package/dist/commander/command.js +5 -2
- package/dist/config.d.ts +10 -0
- package/dist/config.js +2 -0
- package/dist/definition.d.ts +13 -4
- package/dist/definition.js +9 -3
- package/dist/execute.d.ts +1 -0
- package/dist/execute.js +40 -26
- package/dist/exit-code.d.ts +17 -1
- package/dist/exit-code.js +1 -0
- package/dist/help-entry.d.ts +2 -0
- package/dist/help-entry.js +1 -0
- package/dist/help.d.ts +12 -0
- package/dist/help.js +6 -0
- package/dist/index.d.ts +32 -7
- package/dist/index.js +1 -7
- package/dist/mcp-entry.d.ts +2 -0
- package/dist/mcp-entry.js +1 -0
- package/dist/meow/parse.d.ts +21 -0
- package/dist/meow/parse.js +43 -0
- package/dist/meow/present.d.ts +35 -0
- package/dist/meow/present.js +58 -0
- package/dist/meow/types.d.ts +47 -0
- package/dist/meow/types.js +3 -0
- package/dist/meow/validate.d.ts +35 -0
- package/dist/meow/validate.js +144 -0
- package/dist/meow.d.ts +6 -0
- package/dist/meow.js +146 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +1 -1
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +3 -0
- package/dist/schema-entry.d.ts +3 -0
- package/dist/schema-entry.js +2 -0
- package/dist/schema.json +1 -1
- package/dist/testing-helpers.js +3 -1
- package/dist/validate.d.ts +15 -0
- package/dist/validate.js +10 -0
- package/dist/yargs/factory.js +7 -2
- package/package.json +32 -7
package/README.md
CHANGED
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ExitCode } from './exit-code.js';
|
|
2
|
+
export interface CheckReport {
|
|
3
|
+
name: string;
|
|
4
|
+
commands: {
|
|
5
|
+
path: string;
|
|
6
|
+
description: string;
|
|
7
|
+
effects: string;
|
|
8
|
+
}[];
|
|
9
|
+
hooks: {
|
|
10
|
+
stage: 'preRun' | 'postRun' | 'onError';
|
|
11
|
+
applies: string;
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
export interface CheckRefusal {
|
|
15
|
+
refused: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
fix: string;
|
|
19
|
+
};
|
|
20
|
+
exitCode: ExitCode;
|
|
21
|
+
}
|
|
22
|
+
export declare function checkPlugin(file: string): Promise<CheckReport | CheckRefusal>;
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { ExitCode } from './exit-code.js';
|
|
4
|
+
import { Manifest } from './manifest.js';
|
|
5
|
+
import { PluginError, validate } from './plugin.js';
|
|
6
|
+
const STAGES = ['preRun', 'postRun', 'onError'];
|
|
7
|
+
const refuse = (code, message, fix) => ({ refused: { code, message, fix }, exitCode: ExitCode.RUNTIME });
|
|
8
|
+
export async function checkPlugin(file) {
|
|
9
|
+
const loaded = (await import(pathToFileURL(resolve(file)).href));
|
|
10
|
+
const plugin = loaded.default ?? loaded;
|
|
11
|
+
try {
|
|
12
|
+
validate(plugin, []);
|
|
13
|
+
new Manifest().use(plugin);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error instanceof PluginError)
|
|
17
|
+
return refuse(error.code, error.message, error.fix);
|
|
18
|
+
if (error instanceof Error)
|
|
19
|
+
return refuse('E_PLUGIN_SCHEMA', error.message, 'fix the contributed command the message names; it is checked exactly as one of the program’s own');
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
const { name, commands = [], hooks = {} } = plugin;
|
|
23
|
+
const report = {
|
|
24
|
+
name,
|
|
25
|
+
commands: commands.map((c) => ({ path: c.path.join(' '), description: c.description ?? '', effects: String(c.effects ?? 'undeclared') })),
|
|
26
|
+
hooks: STAGES.filter((stage) => hooks[stage] !== undefined).map((stage) => ({
|
|
27
|
+
stage,
|
|
28
|
+
applies: hooks[stage]?.filter?.command === undefined ? 'every command' : `commands matching ${String(hooks[stage]?.filter?.command)}`,
|
|
29
|
+
})),
|
|
30
|
+
};
|
|
31
|
+
if (report.commands.length === 0 && report.hooks.length === 0) {
|
|
32
|
+
return refuse('E_NO_CONTRIBUTION', `${name} registers, but contributes nothing burgee reads`, 'add `commands` or `hooks` — a key another package in the family reads is allowed in the same object, but `burgee check` cannot show it');
|
|
33
|
+
}
|
|
34
|
+
return report;
|
|
35
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -81,4 +81,10 @@ export declare const migrateCommand: import("./execute.js").Command<{
|
|
|
81
81
|
readonly description: "migrate even though the git tree has uncommitted changes";
|
|
82
82
|
};
|
|
83
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* `burgee check <plugin-file>` — the feedback loop PRINCIPLES 7 asks every extension surface
|
|
86
|
+
* for, and the one burgee did not have. See `check.ts`: this one returns its report as data, so
|
|
87
|
+
* `--json` is the form an agent that just wrote a plugin reads.
|
|
88
|
+
*/
|
|
89
|
+
export declare const pluginCheckCommand: import("./execute.js").Command<import("./execute.js").OptionSpecs>;
|
|
84
90
|
export declare const program: import("./manifest.js").Manifest;
|
package/dist/cli.js
CHANGED
|
@@ -133,9 +133,20 @@ export const migrateCommand = defineCommand({
|
|
|
133
133
|
return await migrate({ dir: positionals[0] ?? host.cwd(), dryRun: options.dryRun === true, force: options.force === true });
|
|
134
134
|
},
|
|
135
135
|
});
|
|
136
|
+
export const pluginCheckCommand = defineCommand({
|
|
137
|
+
name: 'check',
|
|
138
|
+
description: 'Validate a burgee plugin, register it into a throwaway program, and report what it contributes',
|
|
139
|
+
arguments: [{ name: 'file', description: 'the plugin module to check' }],
|
|
140
|
+
effects: 'read_only',
|
|
141
|
+
examples: [
|
|
142
|
+
{ command: 'burgee check ./my-plugin.mjs', description: 'what the plugin contributes, or why it was refused' },
|
|
143
|
+
{ command: 'burgee check ./my-plugin.mjs --json', description: 'the same, as data; exit 1 on a refusal' },
|
|
144
|
+
],
|
|
145
|
+
run: async ({ positionals }) => (await import('./check.js')).checkPlugin(positionals[0] ?? ''),
|
|
146
|
+
});
|
|
136
147
|
export const program = defineProgram({
|
|
137
148
|
name: 'burgee',
|
|
138
149
|
description: 'The agent-native CLI framework, and the tools that come with it',
|
|
139
|
-
commands: [brandCommand, devCommand, migrateCommand],
|
|
150
|
+
commands: [brandCommand, devCommand, migrateCommand, pluginCheckCommand],
|
|
140
151
|
});
|
|
141
152
|
run(program);
|
|
@@ -5,7 +5,6 @@ import { stripVTControlCharacters } from 'node:util';
|
|
|
5
5
|
import * as crossSpawn from 'bellpull/cross-spawn';
|
|
6
6
|
import { ExitCode } from '../exit-code.js';
|
|
7
7
|
import { Manifest } from '../manifest.js';
|
|
8
|
-
import { serveMcp } from '../mcp.js';
|
|
9
8
|
import { host } from '../runtime.js';
|
|
10
9
|
import { machineJson, schemaOf } from '../schema.js';
|
|
11
10
|
import { suggestSimilar } from '../suggest.js';
|
|
@@ -1473,7 +1472,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1473
1472
|
await root.parseAsync(args, { from: 'user', stdout: { write: (s) => out.push(s) }, stderr: { write: (s) => err.push(s) }, exit: (c) => void (code = c) });
|
|
1474
1473
|
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
1475
1474
|
};
|
|
1476
|
-
return serveMcp(this.manifest, { input: host.stdin, output: { write: writeOut }, invoke }).then(() => true);
|
|
1475
|
+
return import('../mcp.js').then(async ({ serveMcp }) => serveMcp(this.manifest, { input: host.stdin, output: { write: writeOut }, invoke })).then(() => true);
|
|
1477
1476
|
}
|
|
1478
1477
|
return false;
|
|
1479
1478
|
}
|
|
@@ -1565,6 +1564,10 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1565
1564
|
.then(async (value) => {
|
|
1566
1565
|
await manifest.fire('postRun', name, options);
|
|
1567
1566
|
settle(value);
|
|
1567
|
+
})
|
|
1568
|
+
.catch(async (cause) => {
|
|
1569
|
+
await manifest.fire('onError', name, options);
|
|
1570
|
+
throw cause;
|
|
1568
1571
|
});
|
|
1569
1572
|
}
|
|
1570
1573
|
_warnDeprecated() {
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `burgee/config` — the configuration layer, by itself.
|
|
3
|
+
*
|
|
4
|
+
* Precedence, provenance and `--explain` come from `seniority`, the package whose job that
|
|
5
|
+
* is. They were re-exported from the root barrel until the barrel's cost was measured
|
|
6
|
+
* (see `index.ts`): 3,135 bundled bytes on the startup path of every program, for a
|
|
7
|
+
* surface a program only touches when it wants to read or explain its own configuration.
|
|
8
|
+
*/
|
|
9
|
+
export { ConfigError, envName, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from 'seniority/precedence';
|
|
10
|
+
export { explain } from 'seniority/explain';
|
package/dist/config.js
ADDED
package/dist/definition.d.ts
CHANGED
|
@@ -47,8 +47,17 @@ export declare function checkDefinition(name: string, options: Record<string, Op
|
|
|
47
47
|
* second copy of the guard beside `use()`; it is that there is one guard and both callers
|
|
48
48
|
* reach it, which is the only arrangement a reader can check by looking.
|
|
49
49
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* It takes the declaration whole, for exactly that reason. It took `effects` and `runs` as
|
|
51
|
+
* required arguments so a caller could not decline a check by writing nothing; D1 would have
|
|
52
|
+
* made that five, and a sixth field would make it six. Reading the object means a field the
|
|
53
|
+
* door checks is one no caller has to remember to forward — including whether it runs.
|
|
53
54
|
*/
|
|
54
|
-
export declare function checkCommand(name: string,
|
|
55
|
+
export declare function checkCommand(name: string, declared: Declared): void;
|
|
56
|
+
/** What the door reads of a command: a first-party declaration and a plugin's have the same fields. */
|
|
57
|
+
export interface Declared {
|
|
58
|
+
options?: Record<string, OptionSpec>;
|
|
59
|
+
effects?: unknown;
|
|
60
|
+
deprecated?: boolean | string;
|
|
61
|
+
run?: unknown;
|
|
62
|
+
load?: unknown;
|
|
63
|
+
}
|
package/dist/definition.js
CHANGED
|
@@ -5,6 +5,7 @@ export const WITHHELD = 'withheld';
|
|
|
5
5
|
const DECLARED = [...EFFECTS, WITHHELD];
|
|
6
6
|
const RESERVED = new Set(['json', 'help', 'schema', 'mcp', 'version', 'explain']);
|
|
7
7
|
function checkSpec(name, key, spec, options) {
|
|
8
|
+
checkDeprecated(`option "${key}" of "${name}"`, spec.deprecated);
|
|
8
9
|
if ((spec.minimum !== undefined || spec.maximum !== undefined || spec.integer !== undefined) && spec.type !== 'number') {
|
|
9
10
|
throw new Error(`burgee: option "${key}" of "${name}" declares a numeric bound but is not a number`);
|
|
10
11
|
}
|
|
@@ -49,7 +50,12 @@ function checkEffects(name, effects, runs) {
|
|
|
49
50
|
throw new Error(`burgee: command "${name}" declares effects ${JSON.stringify(effects)}, which is not an effects; use ${DECLARED.join(', ')}`);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
function checkDeprecated(what, deprecated) {
|
|
54
|
+
if (deprecated === true || deprecated === '')
|
|
55
|
+
throw new Error(`burgee: ${what} is deprecated with no replacement; name it, e.g. deprecated: '--force'`);
|
|
56
|
+
}
|
|
57
|
+
export function checkCommand(name, declared) {
|
|
58
|
+
checkDeprecated(`command "${name}"`, declared.deprecated);
|
|
59
|
+
checkDefinition(name, declared.options ?? {});
|
|
60
|
+
checkEffects(name, declared.effects, declared.run !== undefined || declared.load !== undefined);
|
|
55
61
|
}
|
package/dist/execute.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface Command<S extends OptionSpecs = OptionSpecs> {
|
|
|
32
32
|
group?: string;
|
|
33
33
|
epilogue?: string;
|
|
34
34
|
hidden?: boolean;
|
|
35
|
+
/** What replaces this command, e.g. `'deploy'`: shown in help, `--schema` and the warning. `true` alone is refused (D1). */
|
|
35
36
|
deprecated?: boolean | string;
|
|
36
37
|
/**
|
|
37
38
|
* What running it does to the world (N6). Declaring one of the three is what exposes the
|
package/dist/execute.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import { ConfigError,
|
|
3
|
+
import { ConfigError, resolve as resolveLayers } from 'seniority/precedence';
|
|
4
4
|
import { detectAgent } from './agent.js';
|
|
5
5
|
import { checkCommand } from './definition.js';
|
|
6
6
|
import { ExitCode, isExitCode } from './exit-code.js';
|
|
7
|
-
import { renderHelp } from './help.js';
|
|
8
7
|
import { Manifest, relationsOf } from './manifest.js';
|
|
9
|
-
import { serveMcp } from './mcp.js';
|
|
10
8
|
import { camel, kebab } from './names.js';
|
|
11
9
|
import { nearestPackage } from './pkg.js';
|
|
12
10
|
import { host } from './runtime.js';
|
|
13
|
-
import { commandSchemaOf, machineJson, schemaOf, summaryOf, typedName } from './schema.js';
|
|
14
11
|
import { detachedTeardown, processTeardown } from './shutdown.js';
|
|
15
|
-
import { checkRelations, coerce, UsageError } from './validate.js';
|
|
12
|
+
import { AuthError, checkRelations, coerce, UsageError } from './validate.js';
|
|
16
13
|
function helpFields(c) {
|
|
17
14
|
const node = {};
|
|
18
15
|
if (c.description !== undefined)
|
|
@@ -38,7 +35,7 @@ function helpFields(c) {
|
|
|
38
35
|
return node;
|
|
39
36
|
}
|
|
40
37
|
export function defineCommand(command) {
|
|
41
|
-
checkCommand(command.name, command
|
|
38
|
+
checkCommand(command.name, command);
|
|
42
39
|
return command;
|
|
43
40
|
}
|
|
44
41
|
function addTree(manifest, parent, commands) {
|
|
@@ -182,7 +179,7 @@ async function resolveValues(manifest, specs, values, io) {
|
|
|
182
179
|
const out = { values: resolution.values, provenance: resolution.provenance };
|
|
183
180
|
const asked = values['explain'];
|
|
184
181
|
if (typeof asked === 'string')
|
|
185
|
-
out.explainText = explain(asked, resolution);
|
|
182
|
+
out.explainText = (await import('seniority/explain')).explain(asked, resolution);
|
|
186
183
|
for (const [name, spec] of Object.entries(specs)) {
|
|
187
184
|
if (out.values[name] === undefined && spec.required === true && out.explainText === undefined) {
|
|
188
185
|
throw new UsageError(`missing required option --${kebab(name)}`, `pass --${kebab(name)} <value>`);
|
|
@@ -214,6 +211,18 @@ function exitSignal(cause) {
|
|
|
214
211
|
const code = cause?.code;
|
|
215
212
|
return typeof code === 'number' && isExitCode(code) ? code : undefined;
|
|
216
213
|
}
|
|
214
|
+
const CLASSIFIED = [
|
|
215
|
+
[UsageError, ExitCode.USAGE],
|
|
216
|
+
[AuthError, ExitCode.AUTH],
|
|
217
|
+
[ConfigError, ExitCode.CONFIG],
|
|
218
|
+
];
|
|
219
|
+
function carried(cause) {
|
|
220
|
+
const { hint, fix } = (cause ?? {});
|
|
221
|
+
return {
|
|
222
|
+
...(typeof hint === 'string' ? { hint } : {}),
|
|
223
|
+
...(typeof fix === 'string' ? { fix } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
217
226
|
async function describeFailure(cause, argv, node) {
|
|
218
227
|
const signal = exitSignal(cause);
|
|
219
228
|
if (signal !== undefined)
|
|
@@ -221,12 +230,9 @@ async function describeFailure(cause, argv, node) {
|
|
|
221
230
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
222
231
|
if (cause instanceof ActionRequired)
|
|
223
232
|
return { code: ExitCode.CANCELLED, message, action: cause.spec, ...(cause.spec.hint === undefined ? {} : { hint: cause.spec.hint }) };
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (cause instanceof ConfigError) {
|
|
228
|
-
return { code: ExitCode.CONFIG, message, ...(cause.hint === undefined ? {} : { hint: cause.hint }) };
|
|
229
|
-
}
|
|
233
|
+
const named = CLASSIFIED.find(([Class]) => cause instanceof Class);
|
|
234
|
+
if (named !== undefined)
|
|
235
|
+
return { code: named[1], message, ...carried(cause) };
|
|
230
236
|
if (isParseArgsFailure(cause)) {
|
|
231
237
|
const explain = await import('./unknown-option.js');
|
|
232
238
|
const dash = explain.singleDashHint(argv);
|
|
@@ -251,7 +257,8 @@ function runnableNext(manifest, spec, json) {
|
|
|
251
257
|
return (spec.next ?? []).map((n) => ({ command: `${program} ${n.command}${json && !n.command.includes('--json') ? ' --json' : ''}`, when: n.when }));
|
|
252
258
|
}
|
|
253
259
|
const HELP_FLAGS = new Set(['--help', '-h']);
|
|
254
|
-
function helpDocumentOf(manifest, node) {
|
|
260
|
+
async function helpDocumentOf(manifest, node) {
|
|
261
|
+
const { commandSchemaOf, typedName } = await import('./schema.js');
|
|
255
262
|
const root = manifest.rootPath;
|
|
256
263
|
const children = manifest.commands
|
|
257
264
|
.filter((c) => c.path.length === node.path.length + 1 && c.path.slice(0, node.path.length).join(' ') === node.path.join(' '))
|
|
@@ -275,19 +282,24 @@ export function beforeTerminator(argv) {
|
|
|
275
282
|
function rootNode(manifest, root) {
|
|
276
283
|
return manifest.find(root) ?? { path: root, options: {} };
|
|
277
284
|
}
|
|
278
|
-
|
|
285
|
+
const renderHelp = async (manifest, node, io) => {
|
|
286
|
+
const help = await import('./help.js');
|
|
287
|
+
return help.renderHelp(manifest, node, { width: io.width, color: help.colorFor(io.env, detectAgent(io.env, io.tty).interactive) });
|
|
288
|
+
};
|
|
289
|
+
const machineJson = async (...args) => (await import('./schema.js')).machineJson(...args);
|
|
290
|
+
async function unresolved({ manifest, root, io }, argv, at) {
|
|
279
291
|
const node = at ?? rootNode(manifest, root);
|
|
280
292
|
const typed = argv.slice(node.path.length - root.length);
|
|
281
293
|
const first = typed[0] ?? '';
|
|
282
294
|
if (typed.length > 0 && HELP_FLAGS.has(first)) {
|
|
283
295
|
if (beforeTerminator(typed).includes('--json'))
|
|
284
|
-
return { text: `${machineJson(helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
|
|
285
|
-
return { text: renderHelp(manifest, node,
|
|
296
|
+
return { text: `${await machineJson(await helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
|
|
297
|
+
return { text: await renderHelp(manifest, node, io), code: ExitCode.OK };
|
|
286
298
|
}
|
|
287
299
|
if (first === '--version' || first === '-V')
|
|
288
300
|
return { text: `${versionOf(manifest, io)}\n`, code: ExitCode.OK };
|
|
289
301
|
if (typed.length === 0)
|
|
290
|
-
return { text: renderHelp(manifest, node,
|
|
302
|
+
return { text: await renderHelp(manifest, node, io), code: ExitCode.USAGE };
|
|
291
303
|
throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
|
|
292
304
|
}
|
|
293
305
|
async function completion(manifest, argv, io) {
|
|
@@ -310,11 +322,11 @@ async function surface(manifest, argv, io) {
|
|
|
310
322
|
if (await completion(manifest, argv, io))
|
|
311
323
|
return true;
|
|
312
324
|
if (argv[0] === 'help') {
|
|
313
|
-
io.out.write(helpCommand(manifest, argv.slice(1), manifest.rootPath, io
|
|
325
|
+
io.out.write(await helpCommand(manifest, argv.slice(1), manifest.rootPath, io));
|
|
314
326
|
return true;
|
|
315
327
|
}
|
|
316
328
|
if (head.includes('--schema')) {
|
|
317
|
-
io.out.write(`${machineJson(schemaSurface(manifest, argv), head)}\n`);
|
|
329
|
+
io.out.write(`${await machineJson(await schemaSurface(manifest, argv), head)}\n`);
|
|
318
330
|
return true;
|
|
319
331
|
}
|
|
320
332
|
if (head[0] === '--mcp') {
|
|
@@ -333,13 +345,15 @@ async function surface(manifest, argv, io) {
|
|
|
333
345
|
});
|
|
334
346
|
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
335
347
|
};
|
|
348
|
+
const { serveMcp } = await import('./mcp.js');
|
|
336
349
|
await serveMcp(manifest, { input: io.stdin, output: io.out, invoke });
|
|
337
350
|
return true;
|
|
338
351
|
}
|
|
339
352
|
return false;
|
|
340
353
|
}
|
|
341
354
|
const SCHEMA_BUDGET = 48_000;
|
|
342
|
-
function schemaSurface(manifest, argv) {
|
|
355
|
+
async function schemaSurface(manifest, argv) {
|
|
356
|
+
const { commandSchemaOf, schemaOf, summaryOf } = await import('./schema.js');
|
|
343
357
|
const { node } = manifest.resolve(beforeTerminator(argv).filter((a) => a !== '--schema' && !a.startsWith('--format=')), manifest.rootPath);
|
|
344
358
|
if (node?.run !== undefined)
|
|
345
359
|
return commandSchemaOf(node, manifest.rootPath);
|
|
@@ -347,9 +361,9 @@ function schemaSurface(manifest, argv) {
|
|
|
347
361
|
const budget = manifest.schemaBudget ?? SCHEMA_BUDGET;
|
|
348
362
|
return JSON.stringify(full).length <= budget ? full : summaryOf(manifest, budget);
|
|
349
363
|
}
|
|
350
|
-
function helpCommand(manifest, argv, root,
|
|
364
|
+
async function helpCommand(manifest, argv, root, io) {
|
|
351
365
|
const { node } = manifest.resolve(argv, root);
|
|
352
|
-
return renderHelp(manifest, node ?? rootNode(manifest, root),
|
|
366
|
+
return renderHelp(manifest, node ?? rootNode(manifest, root), io);
|
|
353
367
|
}
|
|
354
368
|
function changedOf(node, data) {
|
|
355
369
|
const value = isPlainObject(data) ? data['changed'] : undefined;
|
|
@@ -375,9 +389,9 @@ async function dispatch(manifest, { node, rest, name }, io) {
|
|
|
375
389
|
const flags = canonical(parsed.values, node.options, parsed.tokens);
|
|
376
390
|
const json = flags.json === true;
|
|
377
391
|
if (flags.help === true && json)
|
|
378
|
-
return { json, text: `${machineJson(helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
|
|
392
|
+
return { json, text: `${await machineJson(await helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
|
|
379
393
|
if (flags.help === true)
|
|
380
|
-
return { json, text: renderHelp(manifest, node,
|
|
394
|
+
return { json, text: await renderHelp(manifest, node, io) };
|
|
381
395
|
if (flags.version === true)
|
|
382
396
|
return { json, text: `${versionOf(manifest, io)}\n` };
|
|
383
397
|
const resolved = await resolveValues(manifest, node.options, flags, io);
|
|
@@ -469,7 +483,7 @@ export async function execute(manifest, opts = {}) {
|
|
|
469
483
|
return await leave(io, ExitCode.OK);
|
|
470
484
|
const { node, rest } = manifest.resolve(argv, root);
|
|
471
485
|
if (node?.run === undefined) {
|
|
472
|
-
const { text, code } = unresolved({ manifest, root, io }, argv, node);
|
|
486
|
+
const { text, code } = await unresolved({ manifest, root, io }, argv, node);
|
|
473
487
|
(code === ExitCode.OK ? io.out : io.err).write(text);
|
|
474
488
|
return await leave(io, code);
|
|
475
489
|
}
|
package/dist/exit-code.d.ts
CHANGED
|
@@ -10,9 +10,25 @@ export declare const ExitCode: {
|
|
|
10
10
|
readonly CONFIG: 3;
|
|
11
11
|
/** The user or caller cancelled. */
|
|
12
12
|
readonly CANCELLED: 4;
|
|
13
|
+
/**
|
|
14
|
+
* E6 — the far side said no: a credential is missing, expired, or refused.
|
|
15
|
+
*
|
|
16
|
+
* Its own code because it is the most actionable one in the survey. `RUNTIME` means *it
|
|
17
|
+
* failed, read the message*; `AUTH` means *log in and run it again*, and a script or an
|
|
18
|
+
* agent can branch on that without parsing prose. `USAGE` says fix the script, `CONFIG`
|
|
19
|
+
* says fix the runner, and this says fix the credential — three different responses that
|
|
20
|
+
* collapsed into one code before it existed.
|
|
21
|
+
*
|
|
22
|
+
* **5, where `gh` uses 4.** Four is `CANCELLED` here and has been since the contract was
|
|
23
|
+
* written, and moving a published code to match another tool's is a breaking change for
|
|
24
|
+
* every consumer that already branches on it. The survey's other citation, `aws` v2, uses
|
|
25
|
+
* 252/253/254 and agrees with nobody either; what matters is that the code is stable and
|
|
26
|
+
* documented, not that it matches a particular neighbour.
|
|
27
|
+
*/
|
|
28
|
+
readonly AUTH: 5;
|
|
13
29
|
/** SIGINT after the terminal was restored (E5). */
|
|
14
30
|
readonly SIGINT: 130;
|
|
15
31
|
};
|
|
16
32
|
export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
|
|
17
|
-
/** True for the
|
|
33
|
+
/** True for the seven codes in the contract and nothing else. */
|
|
18
34
|
export declare function isExitCode(n: unknown): n is ExitCode;
|
package/dist/exit-code.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderHelp } from './help.js';
|
package/dist/help.d.ts
CHANGED
|
@@ -24,6 +24,18 @@ export interface HelpOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
theme?: HelpTheme;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether the engine colours help (O2). `FORCE_COLOR` decides when set — `0` and `false`
|
|
29
|
+
* off, anything else, the empty string included, on — so it overrides a pipe and
|
|
30
|
+
* `NO_COLOR` both, as Node's own `getColorDepth` does. Otherwise colour needs someone to
|
|
31
|
+
* see it: an interactive terminal (the caller's answer, in which a detected agent is not
|
|
32
|
+
* one, N12), no non-empty `NO_COLOR`, and a `TERM` other than `dumb`.
|
|
33
|
+
*
|
|
34
|
+
* It lives here, not in the engine, so the startup path pays for none of it (W4).
|
|
35
|
+
* Not `tty.WriteStream.prototype.hasColors(env)`, which gives the same answers: with
|
|
36
|
+
* both variables set it calls `process.emitWarning`, and this runs under an injected env.
|
|
37
|
+
*/
|
|
38
|
+
export declare function colorFor(env: Record<string, string | undefined>, interactive: boolean): boolean;
|
|
27
39
|
/**
|
|
28
40
|
* Word-wrap one paragraph; lines the author indented are kept verbatim (yargs #2120).
|
|
29
41
|
*
|
package/dist/help.js
CHANGED
|
@@ -3,6 +3,12 @@ import { width as displayWidth, widest } from 'linegauge';
|
|
|
3
3
|
import { flagsOf, kebab } from './names.js';
|
|
4
4
|
const identity = (s) => s;
|
|
5
5
|
const PLAIN = { heading: identity, command: identity, flag: identity, value: identity };
|
|
6
|
+
export function colorFor(env, interactive) {
|
|
7
|
+
const force = env['FORCE_COLOR'];
|
|
8
|
+
if (force !== undefined)
|
|
9
|
+
return force !== '0' && force !== 'false';
|
|
10
|
+
return interactive && !env['NO_COLOR'] && env['TERM'] !== 'dumb';
|
|
11
|
+
}
|
|
6
12
|
const DEFAULTS = {
|
|
7
13
|
heading: (s) => styleText('bold', s, { validateStream: false }),
|
|
8
14
|
command: (s) => styleText('bold', s, { validateStream: false }),
|
package/dist/index.d.ts
CHANGED
|
@@ -4,15 +4,40 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The public entry, and only that. The execution core lives in execute.ts so a
|
|
6
6
|
* façade can import it without pulling this barrel.
|
|
7
|
+
*
|
|
8
|
+
* ## What is not here, and why (D-093, reversed on a measurement)
|
|
9
|
+
*
|
|
10
|
+
* This barrel used to re-export the value half of `help.ts`, `mcp.ts`, `schema.ts`,
|
|
11
|
+
* `plugin.ts`, `manifest.ts` and `seniority/precedence` as a convenience. A re-export is
|
|
12
|
+
* not free: it makes those modules live for every consumer of `burgee`, whether or not
|
|
13
|
+
* anything reads them. `execute.ts` loads each one behind an `await import()`, so the only
|
|
14
|
+
* thing keeping them on the startup path was this file.
|
|
15
|
+
*
|
|
16
|
+
* Measured 2026-09-21 with `--splitting --outdir` over the transitive closure of `import`
|
|
17
|
+
* statements — the initial load a consumer actually pays:
|
|
18
|
+
*
|
|
19
|
+
* - `import { run } from 'burgee'` — **44,663 bytes**
|
|
20
|
+
* - the same program against `execute.ts` directly — **31,047 bytes**
|
|
21
|
+
* - cold start, `burgee ÷ cac` — **2.113 → 1.717**
|
|
22
|
+
*
|
|
23
|
+
* D-093 declined this split on a cold-start argument it did not have the number for. The
|
|
24
|
+
* number says 13,616 bytes and 19% of startup, so the split lands: every value moved here
|
|
25
|
+
* has a subpath of its own (`burgee/help`, `burgee/mcp`, `burgee/schema`, `burgee/plugin`,
|
|
26
|
+
* `burgee/config`), which is where a program that wants it should say so.
|
|
27
|
+
*
|
|
28
|
+
* **Every `type` stays.** A type re-export is erased and costs a consumer nothing, so the
|
|
29
|
+
* whole type surface is still importable from `burgee` and no typed program has to move.
|
|
7
30
|
*/
|
|
8
31
|
export { ExitCode, isExitCode, type ExitCode as ExitCodeValue } from './exit-code.js';
|
|
9
32
|
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
33
|
export { checkCommand, checkDefinition } from './definition.js';
|
|
11
|
-
export { camel, kebab, UsageError } from './validate.js';
|
|
34
|
+
export { AuthError, camel, kebab, UsageError } from './validate.js';
|
|
12
35
|
export { AGENT_PROBES, detectAgent, type AgentProbe, type Detection } from './agent.js';
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export
|
|
36
|
+
export type { HelpOptions, HelpTheme, HelpToken } from './help.js';
|
|
37
|
+
export type { Invoke, ServeOptions, Tool, ToolAnnotations } from './mcp.js';
|
|
38
|
+
export type { Candidate, Layers, Provenance, Resolution, Source } from 'seniority/precedence';
|
|
39
|
+
export type { CommandSchema, JsonSchema, ProgramSchema, SchemaSummary } from './schema.js';
|
|
40
|
+
export type { PluginErrorCode } from './plugin.js';
|
|
41
|
+
export type {
|
|
42
|
+
/** The class itself lives at `burgee/schema`; the *type* stays here so `defineProgram`'s return type is nameable. */
|
|
43
|
+
Manifest, ArgumentSpec, CommandNode, Effects, Example, Hook, LazyModule, OptionSpec, ActionRequiredSpec, Plugin, Relation, RunContext, StandardResult, StandardSchemaV1, } from './manifest.js';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
export { ExitCode, isExitCode } from './exit-code.js';
|
|
2
2
|
export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, } from './execute.js';
|
|
3
3
|
export { checkCommand, checkDefinition } from './definition.js';
|
|
4
|
-
export { camel, kebab, UsageError } from './validate.js';
|
|
4
|
+
export { AuthError, camel, kebab, UsageError } from './validate.js';
|
|
5
5
|
export { AGENT_PROBES, detectAgent } from './agent.js';
|
|
6
|
-
export { renderHelp } from './help.js';
|
|
7
|
-
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
|
|
8
|
-
export { ConfigError, envName, explain, resolve, screaming } from 'seniority/precedence';
|
|
9
|
-
export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf } from './schema.js';
|
|
10
|
-
export { CONTRACT, definePlugin, PluginError } from './plugin.js';
|
|
11
|
-
export { Manifest, } from './manifest.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AnyFlag, type Options } from './types.js';
|
|
2
|
+
/** Where a command run's parent arguments end and the child's begin. */
|
|
3
|
+
export interface Split {
|
|
4
|
+
parent: string[];
|
|
5
|
+
input: string[];
|
|
6
|
+
command?: string;
|
|
7
|
+
unknownCommand?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every flag name a caller may write, and the canonical name each maps to. */
|
|
10
|
+
export declare function aliasMap(flags: Record<string, AnyFlag>): Record<string, string[]>;
|
|
11
|
+
export declare function typeofDefault(value: unknown): 'string' | 'boolean' | 'number' | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Where the parent's arguments end and the command's begin.
|
|
14
|
+
*
|
|
15
|
+
* The first token the parser reads as positional is the command word; everything after it in
|
|
16
|
+
* the *raw* argv is the child's, unparsed. `--` is not a fence here — the suite states that
|
|
17
|
+
* `-- --unknown run` reports `Unknown command: --unknown`, so a post-separator word is a
|
|
18
|
+
* candidate command like any other.
|
|
19
|
+
*/
|
|
20
|
+
export declare function splitAtCommand(argv: string[], parserOptions: Record<string, unknown>, opts: Options): Split;
|
|
21
|
+
/** stderr and exit 2 — how meow ends a run the flags do not support. */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import parser, { decamelize } from '../yargs-parser.js';
|
|
2
|
+
export function aliasMap(flags) {
|
|
3
|
+
const out = {};
|
|
4
|
+
for (const [name, spec] of Object.entries(flags)) {
|
|
5
|
+
const names = [];
|
|
6
|
+
if (typeof spec.shortFlag === 'string')
|
|
7
|
+
names.push(spec.shortFlag);
|
|
8
|
+
if (typeof spec.alias === 'string')
|
|
9
|
+
names.push(spec.alias);
|
|
10
|
+
for (const extra of spec.aliases ?? [])
|
|
11
|
+
names.push(extra);
|
|
12
|
+
const decamelized = decamelize(name, '-');
|
|
13
|
+
if (decamelized !== name)
|
|
14
|
+
names.push(decamelized);
|
|
15
|
+
if (names.length > 0)
|
|
16
|
+
Object.defineProperty(out, name, { value: names, writable: true, enumerable: true, configurable: true });
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
export function typeofDefault(value) {
|
|
21
|
+
if (typeof value === 'boolean')
|
|
22
|
+
return 'boolean';
|
|
23
|
+
if (typeof value === 'number')
|
|
24
|
+
return 'number';
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
return 'string';
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return typeofDefault(value[0]);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function splitAtCommand(argv, parserOptions, opts) {
|
|
32
|
+
const probe = parser.detailed(argv, parserOptions);
|
|
33
|
+
const first = probe.argv['_'][0];
|
|
34
|
+
if (first === undefined)
|
|
35
|
+
return { parent: argv, input: [] };
|
|
36
|
+
const word = String(first);
|
|
37
|
+
const at = argv.indexOf(word);
|
|
38
|
+
const parent = at === -1 ? argv : argv.slice(0, at);
|
|
39
|
+
const input = at === -1 ? [] : argv.slice(at + 1);
|
|
40
|
+
if (!(opts.commands ?? []).includes(word))
|
|
41
|
+
return { parent, input, unknownCommand: word };
|
|
42
|
+
return { parent, input, command: word };
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Options } from './types.js';
|
|
2
|
+
/** The nearest `package.json` above the caller's module, which is what `importMeta` is for. */
|
|
3
|
+
/** The nearest `package.json` above the caller's module, which is what `importMeta` is for. */
|
|
4
|
+
export declare function readPackageUp(importMeta: ImportMeta | undefined): Record<string, unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* meow's help block.
|
|
7
|
+
*
|
|
8
|
+
* Indented by `helpIndent` (2 by default) — but only when there is more than one line to
|
|
9
|
+
* indent, which is why `{description: false, help: 'single line'}` comes back flush. The
|
|
10
|
+
* suite states both shapes and they disagree about the indent, not about the text.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* meow's help block.
|
|
14
|
+
*
|
|
15
|
+
* Indented by `helpIndent` (2 by default) — but only when there is more than one line to
|
|
16
|
+
* indent, which is why `{description: false, help: 'single line'}` comes back flush. The
|
|
17
|
+
* suite states both shapes and they disagree about the indent, not about the text.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildHelp(options: Options, pkg: Record<string, unknown>): string;
|
|
20
|
+
/** `common-tags`' `stripIndent`, for the template literals meow's callers write help in. */
|
|
21
|
+
/** `common-tags`' `stripIndent`, for the template literals meow's callers write help in. */
|
|
22
|
+
export declare function stripIndent(text: string): string;
|
|
23
|
+
/** Every flag name a caller may write, and the canonical name each maps to. */
|
|
24
|
+
/**
|
|
25
|
+
* The half of `normalize-package-data` meow's callers can see.
|
|
26
|
+
*
|
|
27
|
+
* `bin` as a string becomes `{ [name]: path }` — the suite reads `cli.pkg.bin['browser-sync']`
|
|
28
|
+
* — and an absent `version` becomes `''`. A copy, because `pkg normalization is lazy` asserts
|
|
29
|
+
* the object the caller passed in is untouched.
|
|
30
|
+
*/
|
|
31
|
+
export declare function normalizePackage(pkg: Record<string, unknown>): Record<string, unknown>;
|
|
32
|
+
/** meow renames the process after the binary it is, which `ps` and a crash report both read. */
|
|
33
|
+
/** meow renames the process after the binary it is, which `ps` and a crash report both read. */
|
|
34
|
+
export declare function setProcessTitle(pkg: Record<string, unknown>): void;
|
|
35
|
+
/** An own property under a key the caller chose — never the prototype setter. */
|