burgee 0.8.0 → 0.9.1
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 +47 -3
- 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 +50 -9
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
<img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License: MIT" />
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
|
+
<p align="center">
|
|
23
|
+
Docs: <a href="https://burgee.interlace.tools/docs/packages/burgee">https://burgee.interlace.tools/docs/packages/burgee</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
22
26
|
A **burgee** is the small swallowtail flag a boat flies to say which club or fleet it
|
|
23
27
|
belongs to — a flag of identity, not of instruction. That is what this framework does for
|
|
24
28
|
a command-line program: a command declares itself once, and every surface is that
|
|
@@ -73,8 +77,10 @@ Nothing here needs keeping in sync, because nothing is written twice.
|
|
|
73
77
|
|
|
74
78
|
## Already on commander?
|
|
75
79
|
|
|
76
|
-
Drop-in compatible with both incumbents, graded by **their own test suites** — 1,
|
|
77
|
-
commander tests and
|
|
80
|
+
Drop-in compatible with both incumbents, graded by **their own test suites** — 1,360 / 1,360
|
|
81
|
+
of commander's tests and 804 / 804 of yargs' on the
|
|
82
|
+
[compatibility page](https://burgee.interlace.tools/docs/compatibility) — with the pass rate
|
|
83
|
+
published and ratcheting:
|
|
78
84
|
|
|
79
85
|
```diff
|
|
80
86
|
- import { Command } from 'commander';
|
|
@@ -119,9 +125,46 @@ help from the manifest, plugins with hook filters, and a `burgee/commander` faç
|
|
|
119
125
|
runs a real commander program. The dev loop, prompts, lazy commands and groups are not
|
|
120
126
|
here yet.
|
|
121
127
|
|
|
122
|
-
Roadmap, architecture and the
|
|
128
|
+
Roadmap, architecture and the 114-requirement floor:
|
|
123
129
|
<https://github.com/ofri-peretz/burgee>
|
|
124
130
|
|
|
131
|
+
## FAQ
|
|
132
|
+
|
|
133
|
+
### Is burgee a commander alternative?
|
|
134
|
+
|
|
135
|
+
Yes — and a yargs alternative. It is drop-in compatible with both: change
|
|
136
|
+
`import { Command } from 'commander'` to `import { Command } from 'burgee/commander'` (or
|
|
137
|
+
`yargs` to `burgee/yargs`) and your code and tests are unchanged. Compatibility is graded by
|
|
138
|
+
each host's own test suite in CI, not asserted. Side by side:
|
|
139
|
+
[burgee vs commander](https://burgee.interlace.tools/docs/vs/commander) and
|
|
140
|
+
[burgee vs yargs](https://burgee.interlace.tools/docs/vs/yargs).
|
|
141
|
+
|
|
142
|
+
### How do I make my CLI usable by an AI agent?
|
|
143
|
+
|
|
144
|
+
Declare it with `defineCommand()` — or keep it on commander or yargs syntax through the
|
|
145
|
+
drop-in front ends. Every command then answers `--json` with one stable envelope,
|
|
146
|
+
`--schema` with the whole command tree as data, and exits `2` when the *command* was wrong
|
|
147
|
+
so an agent knows to rewrite it rather than retry. None of it is written by hand; it is
|
|
148
|
+
the declaration read by a different reader.
|
|
149
|
+
[Your CLI is an agent tool](https://burgee.interlace.tools/docs/agent-surfaces) has each surface.
|
|
150
|
+
|
|
151
|
+
### How do I expose a CLI over MCP?
|
|
152
|
+
|
|
153
|
+
Run it with `--mcp`: the same manifest is served as MCP tools over stdio. A command becomes
|
|
154
|
+
a tool only when it declares its `effects` (`read_only`, `idempotent` or `non_idempotent`),
|
|
155
|
+
so nothing reaches an agent by accident. Register it with any stdio client:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "mcpServers": { "mytool": { "command": "npx", "args": ["mytool", "--mcp"] } } }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Does it have dependencies?
|
|
162
|
+
|
|
163
|
+
None outside this repository. `burgee` installs five packages from its own family —
|
|
164
|
+
`bellpull`, `closeout`, `linegauge`, `roundel` and `seniority` — and each of those takes
|
|
165
|
+
nothing from outside it either: one repository, one release pipeline, one supply chain to
|
|
166
|
+
audit.
|
|
167
|
+
|
|
125
168
|
---
|
|
126
169
|
|
|
127
170
|
Part of the [burgee](https://github.com/ofri-peretz/burgee) family: a CLI on burgee declares
|
|
@@ -141,6 +184,7 @@ Graded by the incumbent's own test suite:
|
|
|
141
184
|
| suite | passing |
|
|
142
185
|
| :-- | --: |
|
|
143
186
|
| `commander` | 1360 / 1360 |
|
|
187
|
+
| `meow` | 132 / 148 |
|
|
144
188
|
| `yargs` | 804 / 804 |
|
|
145
189
|
## Where it sits
|
|
146
190
|
|
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. */
|