burgee 0.12.1 → 0.13.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 CHANGED
@@ -20,7 +20,8 @@
20
20
  </p>
21
21
 
22
22
  <p align="center">
23
- Docs: <a href="https://burgee.interlace.tools/docs/packages/burgee">https://burgee.interlace.tools/docs/packages/burgee</a>
23
+ Docs: <a href="https://burgee.interlace.tools/docs/packages/burgee">https://burgee.interlace.tools/docs/packages/burgee</a><br />
24
+ Migrating from: <a href="https://burgee.interlace.tools/docs/vs/commander">commander</a> · <a href="https://burgee.interlace.tools/docs/vs/yargs">yargs</a>
24
25
  </p>
25
26
 
26
27
  A **burgee** is the small swallowtail flag a boat flies to say which club or fleet it
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /** The result as `--format=agent` prints it: one line per record, each ending in a newline. */
7
+ export declare function agentLines(data: unknown): string;
@@ -0,0 +1,32 @@
1
+ const SPACE = /\s+/g;
2
+ const UNSAFE = /^$|[ =",\p{Cc}]/u;
3
+ const text = (value) => String(value).replace(SPACE, ' ').trim();
4
+ function scalar(value) {
5
+ const s = text(value);
6
+ return UNSAFE.test(s) ? JSON.stringify(s) : s;
7
+ }
8
+ const isScalar = (value) => value === null || typeof value !== 'object';
9
+ const children = (value, key) => Object.entries(value).map(([k, v]) => [key === '' ? k : `${key}.${k}`, v]);
10
+ function line(record) {
11
+ if (isScalar(record))
12
+ return text(record);
13
+ const out = [];
14
+ const stack = children(record, '').reverse();
15
+ for (let next = stack.pop(); next !== undefined; next = stack.pop()) {
16
+ const [key, value] = next;
17
+ if (isScalar(value))
18
+ out.push(`${key}=${scalar(value)}`);
19
+ else if (Array.isArray(value) && value.every(isScalar))
20
+ out.push(`${key}=${value.map(scalar).join(',')}`);
21
+ else
22
+ for (const child of children(value, key).reverse())
23
+ stack.push(child);
24
+ }
25
+ return out.join(' ');
26
+ }
27
+ export function agentLines(data) {
28
+ const json = JSON.stringify(data);
29
+ const value = json === undefined ? null : JSON.parse(json);
30
+ const records = value === null ? [] : [value].flat();
31
+ return records.map((r) => `${line(r)}\n`).join('');
32
+ }
package/dist/argv.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The readings of argv that both the engine and its lazily loaded surfaces make, in one place so
3
+ * they cannot disagree: where options end, which flag asks for `--json`, and whether a surface
4
+ * might be asked for.
5
+ */
6
+ /** The part of argv the parser will read as options: everything before `--`. */
7
+ export declare function beforeTerminator(argv: readonly string[]): readonly string[];
8
+ /** `--json`, or `--json=<fields>` (N14). */
9
+ export declare function isJsonFlag(arg: string): boolean;
10
+ /**
11
+ * Whether argv could be asking for a surface — the cheap test the engine runs on every
12
+ * invocation, so `surfaces.js` loads only when it might answer (U5). It admits a superset:
13
+ * `serve` there still checks each surface's own condition (a program that defines its own
14
+ * `completion`, `config explain` without config) and answers `false` when none applies.
15
+ */
16
+ export declare function mayServe(argv: readonly string[]): boolean;
package/dist/argv.js ADDED
@@ -0,0 +1,12 @@
1
+ export function beforeTerminator(argv) {
2
+ const at = argv.indexOf('--');
3
+ return at === -1 ? argv : argv.slice(0, at);
4
+ }
5
+ export function isJsonFlag(arg) {
6
+ return arg === '--json' || arg.startsWith('--json=');
7
+ }
8
+ export function mayServe(argv) {
9
+ const head = beforeTerminator(argv);
10
+ const first = argv[0];
11
+ return first === '__complete' || first === 'completion' || first === 'help' || head[0] === '--mcp' || (head[0] === 'config' && head[1] === 'explain') || head.includes('--schema');
12
+ }
@@ -151,6 +151,8 @@ export declare class Command extends EventEmitter {
151
151
  _deprecationWarned: boolean;
152
152
  /** burgee: set for the duration of a parse that injected the streams or the exit. */
153
153
  _burgee: Burgee | undefined;
154
+ /** burgee: the behavioural floor, read on the root and turned on by `.burgee({ floor: true })` (J3). */
155
+ _floor: boolean;
154
156
  constructor(name?: string);
155
157
  /** Copy settings useful to share between the root and its subcommands. */
156
158
  copyInheritedSettings(sourceCommand: Command): this;
@@ -365,6 +367,16 @@ export declare class Command extends EventEmitter {
365
367
  _burgeeSurface(userArgs: string[]): boolean | Promise<boolean>;
366
368
  /** The surfaces after `completion`: `--schema` is synchronous, `--mcp` serves until stdin closes. */
367
369
  _burgeeSurfaceRest(head: string[]): boolean | Promise<boolean>;
370
+ /**
371
+ * burgee: turn on what changes commander's observable behaviour, in one call (J3, D-121). Off
372
+ * by default, because commander's own suite asserts the old behaviour. With `floor: true` a
373
+ * usage error exits 2 (E1) rather than 1, and an action that throws or rejects prints one
374
+ * `error:` line and sets its E1 exit code rather than escaping as a stack. A program that
375
+ * called `exitOverride()` took its exits over and keeps them.
376
+ */
377
+ burgee(options: {
378
+ floor?: boolean;
379
+ }): this;
368
380
  /** Additive, and the point of the whole exercise: plugins commander has never had (#2505, unlanded). */
369
381
  use(plugin: Plugin): this;
370
382
  /** Inject the streams and the exit for one parse; returns commander's own parse options. */
@@ -102,6 +102,7 @@ export class Command extends EventEmitter {
102
102
  _deprecated = undefined;
103
103
  _deprecationWarned = false;
104
104
  _burgee = undefined;
105
+ _floor = false;
105
106
  constructor(name) {
106
107
  super();
107
108
  this._args = this.registeredArguments;
@@ -304,7 +305,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
304
305
  if (this._exitCallback) {
305
306
  this._exitCallback(new CommanderError(exitCode, code, message));
306
307
  }
307
- return host.exit(exitCode);
308
+ return host.exit(this._root()._floor ? e1({ code, exitCode }) : exitCode);
308
309
  }
309
310
  action(fn) {
310
311
  const listener = (args) => {
@@ -1479,7 +1480,8 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1479
1480
  _burgeeSurfaceRest(head) {
1480
1481
  const root = this._root();
1481
1482
  if (head.includes('--schema') && !root._declares('--schema')) {
1482
- root._outputConfiguration.writeOut(`${machineJson(schemaOf(this.manifest), head)}\n`);
1483
+ const shadows = ['--json', '--mcp', 'completion'].filter((name) => root._declares(name) || root._findCommand(name) !== undefined);
1484
+ root._outputConfiguration.writeOut(`${machineJson({ ...schemaOf(this.manifest), ...(shadows.length > 0 && { shadows }) }, head)}\n`);
1483
1485
  return true;
1484
1486
  }
1485
1487
  if (head[0] === '--mcp' && !root._declares('--mcp')) {
@@ -1495,6 +1497,10 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1495
1497
  }
1496
1498
  return false;
1497
1499
  }
1500
+ burgee(options) {
1501
+ this._root()._floor = options.floor === true;
1502
+ return this;
1503
+ }
1498
1504
  use(plugin) {
1499
1505
  this.manifest.use(plugin);
1500
1506
  return this;
@@ -1560,11 +1566,11 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1560
1566
  this._burgee = undefined;
1561
1567
  };
1562
1568
  const fail = (err) => {
1563
- const json = this._burgee?.json === true && !(err instanceof CommanderError);
1564
- if (json)
1569
+ const report = (this._burgee?.json === true || (this._floor && this._exitCallback === null)) && !(err instanceof CommanderError);
1570
+ if (report)
1565
1571
  host.exitCode = this._reportHandlerFailure(err);
1566
1572
  done();
1567
- if (!json)
1573
+ if (!report)
1568
1574
  throw err;
1569
1575
  };
1570
1576
  try {
package/dist/compat.d.ts CHANGED
@@ -63,5 +63,27 @@ export declare const DROP_INS: readonly DropIn[];
63
63
  * `scripts/migrate-drop-ins-lock.test.ts` holds each equal to the oracle's.
64
64
  */
65
65
  export declare const GRADED_VERSIONS: Readonly<Record<string, string>>;
66
+ /**
67
+ * C1 — the majors of each incumbent its drop-in claims, per incumbent package, highest last.
68
+ *
69
+ * A major is on this list only when the incumbent's **own suite at that major** grades the
70
+ * drop-in level with the incumbent itself (D-137), so the list is a measurement and not a
71
+ * range somebody believed. The current major is the one in `GRADED_VERSIONS`. An older one
72
+ * needs a row in `compat-oracle`'s `PREVIOUS_MAJORS` — the suite vendored at that major's last
73
+ * tag and graded in CI by `compat.yml`'s ratchet job — whose baseline passes as many cases as
74
+ * its control does.
75
+ *
76
+ * Two older majors are graded today and **neither is claimed**, because neither is level:
77
+ * commander 14 grades 1329 / 1331 (15 names the surplus argument in the excess-arguments
78
+ * message, 14 does not) and yargs 17 grades 191 / 794 (17's `require('yargs')` is a
79
+ * singleton, which 18 removed). The measurements are on the compatibility page; the claim
80
+ * waits for the number.
81
+ *
82
+ * `migrate` reads this, and not `GRADED_VERSIONS`, to decide a project is on a major it may
83
+ * rewrite — so a major that becomes level here is a major `migrate` serves, with no second
84
+ * edit. `scripts/supported-majors-lock.test.ts` holds every entry to `GRADED_VERSIONS` and to
85
+ * the oracle's graded rows.
86
+ */
87
+ export declare const SUPPORTED_MAJORS: Readonly<Record<string, readonly number[]>>;
66
88
  /** Level: the drop-in passes every case the incumbent passes against its own suite (D-137). */
67
89
  export declare const isLevel: (host: string) => boolean;
package/dist/compat.js CHANGED
@@ -86,6 +86,35 @@ export const GRADED_VERSIONS = {
86
86
  yargs: '18.1.0',
87
87
  'yargs-parser': '22.0.0',
88
88
  };
89
+ export const SUPPORTED_MAJORS = {
90
+ '@clack/prompts': [1],
91
+ '@inquirer/core': [12],
92
+ 'ansi-escapes': [7],
93
+ boxen: [8],
94
+ chalk: [6],
95
+ 'cli-table3': [0],
96
+ commander: [15],
97
+ cosmiconfig: [10],
98
+ 'cross-spawn': [7],
99
+ dotenv: [17],
100
+ 'exit-hook': [5],
101
+ lilconfig: [3],
102
+ 'log-update': [8],
103
+ meow: [14],
104
+ ora: [9],
105
+ rc: [1],
106
+ 'restore-cursor': [5],
107
+ 'signal-exit': [4],
108
+ 'slice-ansi': [7],
109
+ 'string-width': [8],
110
+ 'strip-ansi': [7],
111
+ 'term-img': [7],
112
+ 'terminal-link': [5],
113
+ which: [7],
114
+ 'wrap-ansi': [10],
115
+ yargs: [18],
116
+ 'yargs-parser': [22],
117
+ };
89
118
  export const isLevel = (host) => {
90
119
  const row = GRADED[host];
91
120
  return row !== undefined && row.passed >= row.control;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * V6 — the config file and the package.json field, as precedence layers, for a program that
3
+ * opted into config discovery.
4
+ *
5
+ * Its own module since 2026-09-24 (U5): `execute.ts` imports it only when the manifest declares
6
+ * `config`, so a program that never reads a config file carries none of this — as it already
7
+ * carried none of `seniority/config`, which this reaches the same way.
8
+ */
9
+ import { type Layers } from 'seniority/precedence';
10
+ import { type Package } from './pkg.js';
11
+ /** The config file and the package.json field, for a program that opted in; loaded lazily (K6). */
12
+ export declare function configLayers(name: string, values: Record<string, unknown>, io: {
13
+ cwd: string;
14
+ env: Record<string, string | undefined>;
15
+ pkg: Package | undefined;
16
+ }): Promise<Pick<Layers, 'config' | 'pkg'>>;
@@ -0,0 +1,19 @@
1
+ function packageLayer(pkg, name) {
2
+ if (pkg === undefined || name === undefined)
3
+ return undefined;
4
+ const field = pkg.data[name];
5
+ return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
6
+ }
7
+ export async function configLayers(name, values, io) {
8
+ const { discover } = await import('seniority/config');
9
+ const explicit = values['config'];
10
+ const disabled = values['noConfig'] === true;
11
+ const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
12
+ const out = {};
13
+ if (loaded !== undefined)
14
+ out.config = { path: loaded.chain.join(' ← '), data: loaded.data };
15
+ const pkg = disabled ? undefined : packageLayer(io.pkg, name);
16
+ if (pkg !== undefined)
17
+ out.pkg = pkg;
18
+ return out;
19
+ }
package/dist/execute.d.ts CHANGED
@@ -108,8 +108,6 @@ export interface RunOptions {
108
108
  /** The entry file, whose nearest package.json owns the program's version (V4); `process.argv[1]` otherwise. */
109
109
  entry?: string;
110
110
  }
111
- /** The part of argv the parser will read as options: everything before `--`. */
112
- export declare function beforeTerminator(argv: readonly string[]): readonly string[];
113
111
  export declare function execute(manifest: Manifest, opts?: RunOptions & {
114
112
  root?: string[];
115
113
  from?: 'node' | 'user';
@@ -131,4 +129,4 @@ export declare function runCommand(manifest: Manifest, argv: readonly string[],
131
129
  * through `execute`, so there is exactly one code path from argv to exit.
132
130
  */
133
131
  export declare function run<S extends OptionSpecs>(target: Command<S> | Manifest, opts?: RunOptions): Promise<void>;
134
- export {};
132
+ export { beforeTerminator } from './argv.js';
package/dist/execute.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { dirname } from 'node:path';
2
2
  import { parseArgs } from 'node:util';
3
- import { ConfigError, resolve as resolveLayers } from 'seniority/precedence';
3
+ import { resolve as resolveLayers } from 'seniority/precedence';
4
4
  import { detectAgent } from './agent.js';
5
+ import { beforeTerminator, isJsonFlag, mayServe } from './argv.js';
5
6
  import { checkCommand } from './definition.js';
6
7
  import { ExitCode, isExitCode } from './exit-code.js';
7
8
  import { Manifest, relationsOf } from './manifest.js';
@@ -9,7 +10,7 @@ import { camel, kebab } from './names.js';
9
10
  import { nearestPackage } from './pkg.js';
10
11
  import { host } from './runtime.js';
11
12
  import { detachedTeardown, processTeardown } from './shutdown.js';
12
- import { AuthError, checkRelations, coerce, UsageError } from './validate.js';
13
+ import { coerce, UsageError } from './validate.js';
13
14
  function helpFields(c) {
14
15
  const node = {};
15
16
  if (c.description !== undefined)
@@ -152,31 +153,12 @@ function canonical(values, specs, tokens) {
152
153
  }
153
154
  return out;
154
155
  }
155
- function packageLayer(pkg, name) {
156
- if (pkg === undefined || name === undefined)
157
- return undefined;
158
- const field = pkg.data[name];
159
- return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
160
- }
161
- async function configLayers(name, values, io) {
162
- const { discover } = await import('seniority/config');
163
- const explicit = values['config'];
164
- const disabled = values['noConfig'] === true;
165
- const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
166
- const out = {};
167
- if (loaded !== undefined)
168
- out.config = { path: loaded.chain.join(' ← '), data: loaded.data };
169
- const pkg = disabled ? undefined : packageLayer(io.pkg, name);
170
- if (pkg !== undefined)
171
- out.pkg = pkg;
172
- return out;
173
- }
174
156
  async function resolution(manifest, specs, values, io) {
175
157
  const layers = { flags: values, env: io.env };
176
158
  if (manifest.envPrefix !== undefined)
177
159
  layers.envPrefix = manifest.envPrefix;
178
160
  if (manifest.config !== undefined)
179
- Object.assign(layers, await configLayers(manifest.config.name, values, io));
161
+ Object.assign(layers, await (await import('./config-layers.js')).configLayers(manifest.config.name, values, io));
180
162
  return resolveLayers(specs, layers);
181
163
  }
182
164
  async function resolveValues(manifest, specs, values, io) {
@@ -206,192 +188,12 @@ function splitPositionals(tokens) {
206
188
  }
207
189
  return { positionals, passthrough };
208
190
  }
209
- function isParseArgsFailure(cause) {
210
- if (!(cause instanceof Error))
211
- return false;
212
- const { code } = cause;
213
- return typeof code === 'string' && code.startsWith('ERR_PARSE_ARGS_');
214
- }
215
- function exitSignal(cause) {
216
- const code = cause?.code;
217
- return typeof code === 'number' && isExitCode(code) ? code : undefined;
218
- }
219
- const CLASSIFIED = [
220
- [UsageError, ExitCode.USAGE],
221
- [AuthError, ExitCode.AUTH],
222
- [ConfigError, ExitCode.CONFIG],
223
- ];
224
- const NAMED = new Map([
225
- ['USAGE', ExitCode.USAGE],
226
- ['CONFIG', ExitCode.CONFIG],
227
- ['CANCELLED', ExitCode.CANCELLED],
228
- ['AUTH', ExitCode.AUTH],
229
- ]);
230
- function namedCode(cause) {
231
- return NAMED.get(cause?.code);
232
- }
233
- function messageOf(cause) {
234
- if (cause instanceof Error)
235
- return cause.message;
236
- const said = cause?.message;
237
- return typeof said === 'string' ? said : String(cause);
238
- }
239
- function carried(cause) {
240
- const { hint, fix } = (cause ?? {});
241
- return {
242
- ...(typeof hint === 'string' ? { hint } : {}),
243
- ...(typeof fix === 'string' ? { fix } : {}),
244
- };
245
- }
246
- async function describeFailure(cause, argv, node) {
247
- const signal = exitSignal(cause);
248
- if (signal !== undefined)
249
- return { code: signal, message: '', silent: true };
250
- const message = messageOf(cause);
251
- if (cause instanceof ActionRequired)
252
- return { code: ExitCode.CANCELLED, message, action: cause.spec, ...(cause.spec.hint === undefined ? {} : { hint: cause.spec.hint }) };
253
- const named = CLASSIFIED.find(([Class]) => cause instanceof Class);
254
- if (named !== undefined)
255
- return { code: named[1], message, ...carried(cause) };
256
- const own = cause?.constructor?.[Symbol.for('burgee.exitCode')];
257
- if (typeof own === 'number')
258
- return { code: own, message, ...carried(cause) };
259
- const byName = namedCode(cause);
260
- if (byName !== undefined)
261
- return { code: byName, message, ...carried(cause) };
262
- if (isParseArgsFailure(cause)) {
263
- const explain = await import('./unknown-option.js');
264
- const dash = explain.singleDashHint(argv);
265
- if (dash !== undefined)
266
- return { code: ExitCode.USAGE, message, hint: dash };
267
- const better = explain.unknownOption(cause, Object.keys(node?.options ?? {}).map(kebab));
268
- return { code: ExitCode.USAGE, message, hint: 'run --help to see the available options', ...better };
269
- }
270
- return { code: ExitCode.RUNTIME, message };
271
- }
272
- function textFailure(failure) {
273
- const hint = failure.hint === undefined ? '' : `hint: ${failure.hint}\n`;
274
- const fix = failure.fix === undefined ? '' : `fix: ${failure.fix}\n`;
275
- if (failure.action !== undefined) {
276
- const next = (failure.action.next ?? []).map((n) => ` ${n.command} ${n.when}\n`).join('');
277
- return `action required (${failure.action.reason}): ${failure.message}\n${next === '' ? '' : `next:\n${next}`}${hint}`;
278
- }
279
- return `error: ${failure.message}\n${hint}${fix}`;
280
- }
281
- function runnableNext(manifest, spec, json) {
282
- const program = manifest.rootPath.join(' ');
283
- return (spec.next ?? []).map((n) => ({ command: `${program} ${n.command}${json && !n.command.includes('--json') ? ' --json' : ''}`, when: n.when }));
284
- }
285
- const HELP_FLAGS = new Set(['--help', '-h']);
286
- async function helpDocumentOf(manifest, node) {
287
- const { commandSchemaOf, typedName } = await import('./schema.js');
288
- const root = manifest.rootPath;
289
- const children = manifest.commands
290
- .filter((c) => c.path.length === node.path.length + 1 && c.path.slice(0, node.path.length).join(' ') === node.path.join(' '))
291
- .map((c) => typedName(c, root));
292
- return {
293
- schemaVersion: 1,
294
- ...commandSchemaOf(node, root),
295
- ...(children.length === 0 ? {} : { commands: children }),
296
- };
297
- }
298
191
  const HELP_WIDTH = 100;
299
192
  const processExit = (code) => host.exit(code);
300
193
  async function leave(io, code) {
301
194
  await io.teardown.run(code);
302
195
  io.exit(code);
303
196
  }
304
- export function beforeTerminator(argv) {
305
- const at = argv.indexOf('--');
306
- return at === -1 ? argv : argv.slice(0, at);
307
- }
308
- function rootNode(manifest, root) {
309
- return manifest.find(root) ?? { path: root, options: {} };
310
- }
311
- const renderHelp = async (manifest, node, io) => {
312
- const help = await import('./help.js');
313
- return help.renderHelp(manifest, node, { width: io.width, color: help.colorFor(io.env, detectAgent(io.env, io.tty).interactive) });
314
- };
315
- const machineJson = async (...args) => (await import('./schema.js')).machineJson(...args);
316
- async function unresolved({ manifest, root, io }, argv, at) {
317
- const node = at ?? rootNode(manifest, root);
318
- const typed = argv.slice(node.path.length - root.length);
319
- const first = typed[0] ?? '';
320
- if (typed.length > 0 && HELP_FLAGS.has(first)) {
321
- if (beforeTerminator(typed).some(isJsonFlag))
322
- return { text: `${await machineJson(await helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
323
- return { text: await renderHelp(manifest, node, io), code: ExitCode.OK };
324
- }
325
- if (first === '--version' || first === '-V')
326
- return { text: `${versionOf(manifest, io)}\n`, code: ExitCode.OK };
327
- if (typed.length === 0)
328
- return { text: await renderHelp(manifest, node, io), code: ExitCode.USAGE };
329
- throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
330
- }
331
- async function completion(manifest, argv, io) {
332
- if (argv[0] !== 'completion' || manifest.find([...manifest.rootPath, 'completion']) !== undefined)
333
- return false;
334
- const { renderCompletion, renderFigSpec, SHELLS } = await import('./completions.js');
335
- const shell = argv[1] ?? '';
336
- if (shell === 'fig') {
337
- io.out.write(`${JSON.stringify(renderFigSpec(manifest), null, 2)}\n`);
338
- return true;
339
- }
340
- const known = SHELLS.find((s) => s === shell);
341
- if (known === undefined)
342
- throw new UsageError(`unknown shell "${shell}"`, `completion ${SHELLS.join('|')}|fig`);
343
- io.out.write(renderCompletion(manifest, known));
344
- return true;
345
- }
346
- async function surface(manifest, argv, io) {
347
- const head = beforeTerminator(argv);
348
- if (argv[0] === '__complete') {
349
- await (await import('./complete-dynamic.js')).completeDynamic(manifest, argv.slice(1), (t) => io.out.write(t));
350
- return true;
351
- }
352
- const root = manifest.rootPath;
353
- if (head[0] === 'config' && head[1] === 'explain' && manifest.config !== undefined && manifest.find([...root, 'config', 'explain']) === undefined) {
354
- const { explainConfig } = await import('./config-explain.js');
355
- io.out.write(await explainConfig(manifest, head.slice(2), (specs, flags) => resolution(manifest, specs, flags, io)));
356
- return true;
357
- }
358
- if (await completion(manifest, argv, io))
359
- return true;
360
- if (argv[0] === 'help') {
361
- io.out.write(await helpCommand(manifest, argv.slice(1), manifest.rootPath, io));
362
- return true;
363
- }
364
- if (head.includes('--schema')) {
365
- const { schemaSurface } = await import('./schema-surface.js');
366
- io.out.write(`${await machineJson(await schemaSurface(manifest, argv), head)}\n`);
367
- return true;
368
- }
369
- if (head[0] === '--mcp') {
370
- const invoke = async (args) => {
371
- const out = [];
372
- const err = [];
373
- let code = 0;
374
- await execute(manifest, {
375
- argv: args,
376
- env: io.env,
377
- stdout: { write: (s) => out.push(s) },
378
- stderr: { write: (s) => err.push(s) },
379
- exit: (c) => {
380
- code = c;
381
- },
382
- });
383
- return { stdout: out.join(''), stderr: err.join(''), code };
384
- };
385
- const { serveMcp } = await import('./mcp.js');
386
- await serveMcp(manifest, { input: io.stdin, output: io.out, invoke });
387
- return true;
388
- }
389
- return false;
390
- }
391
- async function helpCommand(manifest, argv, root, io) {
392
- const { node } = manifest.resolve(argv, root);
393
- return renderHelp(manifest, node ?? rootNode(manifest, root), io);
394
- }
395
197
  function changedOf(node, data) {
396
198
  const value = isPlainObject(data) ? data['changed'] : undefined;
397
199
  if (typeof value === 'boolean')
@@ -405,18 +207,10 @@ function exitCodeOf(data) {
405
207
  const code = isPlainObject(data) ? data['exitCode'] : undefined;
406
208
  return isExitCode(code) ? code : ExitCode.OK;
407
209
  }
408
- function isJsonFlag(arg) {
409
- return arg === '--json' || arg.startsWith('--json=');
410
- }
411
- function versionOf(manifest, io) {
412
- const declared = manifest.version ?? (typeof io.pkg?.data['version'] === 'string' ? io.pkg.data['version'] : undefined);
413
- if (declared === undefined)
414
- throw new ConfigError('no version declared', 'pass version to defineProgram, or set "version" in the owning package.json');
415
- return declared;
416
- }
210
+ const readsLazily = (arg) => arg.startsWith('--json=') || arg === '--format=agent';
417
211
  async function dispatch(manifest, { node, rest: typed, name }, io) {
418
- const select = typed.some((a) => a.startsWith('--json=')) ? await import('./fields.js') : undefined;
419
- const { args: rest, fields } = select?.jsonFields(typed) ?? { args: typed };
212
+ const select = typed.some(readsLazily) ? await import('./fields.js') : undefined;
213
+ const { args: rest, fields, lines } = select?.jsonFields(typed, node) ?? { args: typed };
420
214
  if (fields?.length === 0)
421
215
  return { json: true, text: `${JSON.stringify(select?.listFields(node))}\n` };
422
216
  if (fields !== undefined)
@@ -424,17 +218,17 @@ async function dispatch(manifest, { node, rest: typed, name }, io) {
424
218
  const parsed = parseArgs({ args: rest, options: toParseConfig(node.options, manifest.config !== undefined), allowPositionals: true, strict: true, tokens: true });
425
219
  const flags = canonical(parsed.values, node.options, parsed.tokens);
426
220
  const json = flags.json === true;
427
- if (flags.help === true && json)
428
- return { json, text: `${await machineJson(await helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
429
221
  if (flags.help === true)
430
- return { json, text: await renderHelp(manifest, node, io) };
222
+ return { json, text: await (await import('./surfaces.js')).helpFor(manifest, node, io, json) };
431
223
  if (flags.version === true)
432
- return { json, text: `${versionOf(manifest, io)}\n` };
224
+ return { json, text: `${(await import('./surfaces.js')).versionOf(manifest, io)}\n` };
433
225
  const resolved = await resolveValues(manifest, node.options, flags, io);
434
226
  if (resolved.explainText !== undefined)
435
227
  return { json, text: resolved.explainText };
436
228
  const { provenance } = resolved;
437
- checkRelations(relationsOf(node), resolved.values, provenance);
229
+ const relations = relationsOf(node);
230
+ if (relations.length > 0)
231
+ (await import('./relations.js')).checkRelations(relations, resolved.values, provenance);
438
232
  const values = await coerce(node.options, resolved.values);
439
233
  const { positionals, passthrough } = splitPositionals(parsed.tokens);
440
234
  requirePositionals(node, positionals);
@@ -447,7 +241,7 @@ async function dispatch(manifest, { node, rest: typed, name }, io) {
447
241
  await manifest.fire('postRun', name, values);
448
242
  const changed = changedOf(node, data);
449
243
  const selected = fields === undefined || select === undefined ? data : select.selectFields(data, fields);
450
- return { json, data: selected, provenance, ...(changed === undefined ? {} : { changed }) };
244
+ return { json, lines, data: selected, provenance, ...(changed === undefined ? {} : { changed }) };
451
245
  }
452
246
  function requirePositionals(node, positionals) {
453
247
  const required = (node.arguments ?? []).filter((a) => a.required !== false && a.variadic !== true);
@@ -475,29 +269,16 @@ async function emit(io, outcome) {
475
269
  }
476
270
  const meta = { provenance: outcome.provenance ?? {}, ...(outcome.changed === undefined ? {} : { changed: outcome.changed }) };
477
271
  const envelope = { ok: true, data: outcome.data, meta };
478
- io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : `${render(outcome.data)}\n`);
272
+ io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : (outcome.lines?.(outcome.data) ?? `${render(outcome.data)}\n`));
479
273
  return await leave(io, exitCodeOf(outcome.data));
480
274
  }
481
275
  async function report(cause, { manifest, io, argv, json, name }) {
482
- const failure = await describeFailure(cause, argv, resolveCommand(manifest, argv) ?? undefined);
276
+ const { describeFailure, failureText } = await import('./failure.js');
277
+ const failure = await describeFailure(cause, argv, resolveCommand(manifest, argv) ?? undefined, cause instanceof ActionRequired ? cause.spec : undefined);
483
278
  if (failure.silent === true)
484
279
  return await leave(io, failure.code);
485
280
  await manifest.fire('onError', name, {});
486
- if (failure.action !== undefined) {
487
- const next = runnableNext(manifest, failure.action, json);
488
- const rendered = { ...failure, action: { ...failure.action, next } };
489
- const body = { ok: false, status: 'action_required', reason: failure.action.reason, message: failure.message, next, hint: failure.hint, error: { code: failure.code, message: failure.message } };
490
- if (json)
491
- io.out.write(`${JSON.stringify(body)}\n`);
492
- else
493
- io.err.write(textFailure(rendered));
494
- return await leave(io, failure.code);
495
- }
496
- const body = { code: failure.code, message: failure.message, hint: failure.hint, ...(failure.fix === undefined ? {} : { fix: failure.fix }) };
497
- if (json)
498
- io.out.write(`${JSON.stringify({ ok: false, error: body })}\n`);
499
- else
500
- io.err.write(textFailure(failure));
281
+ (json ? io.out : io.err).write(failureText(failure, manifest, json));
501
282
  return await leave(io, failure.code);
502
283
  }
503
284
  function ioOf(opts) {
@@ -528,11 +309,11 @@ export async function execute(manifest, opts = {}) {
528
309
  try {
529
310
  argv = manifest.declares('parse') ? await manifest.parse([...typed]) : typed;
530
311
  json = beforeTerminator(argv).some(isJsonFlag);
531
- if (await surface(manifest, argv, io))
312
+ if (mayServe(argv) && (await (await import('./surfaces.js')).serve(manifest, argv, io, { resolution: (specs, flags) => resolution(manifest, specs, flags, io), execute })))
532
313
  return await leave(io, ExitCode.OK);
533
314
  const { node, rest } = manifest.resolve(argv, root);
534
315
  if (node?.run === undefined) {
535
- const { text, code } = await unresolved({ manifest, root, io }, argv, node);
316
+ const { text, code } = await (await import('./surfaces.js')).unresolved({ manifest, root, io }, argv, node);
536
317
  (code === ExitCode.OK ? io.out : io.err).write(text);
537
318
  return await leave(io, code);
538
319
  }
@@ -569,3 +350,4 @@ export async function run(target, opts = {}) {
569
350
  });
570
351
  return await execute(manifest, opts);
571
352
  }
353
+ export { beforeTerminator } from './argv.js';
@@ -0,0 +1,26 @@
1
+ import { type ExitCode as ExitCodeType } from './exit-code.js';
2
+ import { type ActionRequiredSpec, type CommandNode, type Manifest } from './manifest.js';
3
+ export interface Failure {
4
+ code: ExitCodeType;
5
+ message: string;
6
+ hint?: string;
7
+ /** E3 — the exact command or flag to run next, where one exists. Never a guess. */
8
+ fix?: string;
9
+ /** An exit signal: honour the code, print nothing. */
10
+ silent?: boolean;
11
+ /** N11: the caller must act; carried into the envelope with the runnable `next[]`. */
12
+ action?: ActionRequiredSpec;
13
+ }
14
+ /**
15
+ * E2/E3 — a usage error never prints a stack, a runtime failure never prints help.
16
+ *
17
+ * `action` is the spec of a `ctx.actionRequired(…)` unwind, which the engine recognises by its
18
+ * own class and hands over; the class stays in `execute.ts` because a handler throws it on the
19
+ * startup path.
20
+ */
21
+ export declare function describeFailure(cause: unknown, argv: string[], node: CommandNode | undefined, action: ActionRequiredSpec | undefined): Promise<Failure>;
22
+ /**
23
+ * What a described failure prints: under `--json` the envelope, for stdout; otherwise the prose,
24
+ * for stderr. The caller picks the stream by the same `json` it passes here (O1, D-140).
25
+ */
26
+ export declare function failureText(failure: Failure, manifest: Manifest, json: boolean): string;