@fastagent-sh/fastagent 0.12.1 → 0.14.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.
Files changed (151) hide show
  1. package/README.md +6 -4
  2. package/dist/channels/feishu/bootstrap-token.d.ts +42 -0
  3. package/dist/channels/feishu/bootstrap-token.js +94 -0
  4. package/dist/channels/feishu/card.d.ts +32 -0
  5. package/dist/channels/feishu/card.js +66 -0
  6. package/dist/channels/feishu/cloud.d.ts +17 -0
  7. package/dist/channels/feishu/cloud.js +19 -0
  8. package/dist/channels/feishu/context-buffer.d.ts +46 -0
  9. package/dist/channels/feishu/context-buffer.js +133 -0
  10. package/dist/channels/feishu/crypto.d.ts +15 -0
  11. package/dist/channels/feishu/crypto.js +43 -0
  12. package/dist/channels/feishu/feishu-api.d.ts +108 -0
  13. package/dist/channels/feishu/feishu-api.js +325 -0
  14. package/dist/channels/feishu/feishu.d.ts +46 -0
  15. package/dist/channels/feishu/feishu.js +472 -0
  16. package/dist/channels/feishu/invoke-turn.d.ts +65 -0
  17. package/dist/channels/feishu/invoke-turn.js +157 -0
  18. package/dist/channels/feishu/model.d.ts +97 -0
  19. package/dist/channels/feishu/model.js +9 -0
  20. package/dist/channels/feishu/normalize.d.ts +22 -0
  21. package/dist/channels/feishu/normalize.js +132 -0
  22. package/dist/channels/feishu/owned-threads.d.ts +11 -0
  23. package/dist/channels/feishu/owned-threads.js +47 -0
  24. package/dist/channels/feishu/parse.d.ts +43 -0
  25. package/dist/channels/feishu/parse.js +65 -0
  26. package/dist/channels/feishu/preview.d.ts +36 -0
  27. package/dist/channels/feishu/preview.js +387 -0
  28. package/dist/channels/feishu/register-app.d.ts +70 -0
  29. package/dist/channels/feishu/register-app.js +141 -0
  30. package/dist/channels/feishu/register-webhook.d.ts +22 -0
  31. package/dist/channels/feishu/register-webhook.js +106 -0
  32. package/dist/channels/feishu/scaffold/channel.ts +41 -0
  33. package/dist/channels/feishu/scaffold/feishu-send.ts +87 -0
  34. package/dist/channels/feishu/seen.d.ts +5 -0
  35. package/dist/channels/feishu/seen.js +47 -0
  36. package/dist/channels/feishu/text.d.ts +13 -0
  37. package/dist/channels/feishu/text.js +63 -0
  38. package/dist/channels/lark/lark.d.ts +15 -0
  39. package/dist/channels/lark/lark.js +10 -0
  40. package/dist/channels/lark/onboard.d.ts +39 -0
  41. package/dist/channels/lark/onboard.js +58 -0
  42. package/dist/channels/lark/scaffold/channel.ts +39 -0
  43. package/dist/channels/lark/scaffold/lark-send.ts +87 -0
  44. package/dist/channels/registration.d.ts +15 -0
  45. package/dist/channels/registration.js +1 -0
  46. package/dist/channels/{telegram/state.js → state.js} +6 -4
  47. package/dist/channels/telegram/context-buffer.js +1 -1
  48. package/dist/channels/telegram/register-webhook.d.ts +4 -1
  49. package/dist/channels/telegram/register-webhook.js +17 -26
  50. package/dist/channels/telegram/telegram.js +2 -2
  51. package/dist/channels/telegram/turn-store.d.ts +8 -21
  52. package/dist/channels/telegram/turn-store.js +11 -130
  53. package/dist/channels/{telegram/turn-queue.js → turn-queue.js} +3 -3
  54. package/dist/channels/turn-store.d.ts +42 -0
  55. package/dist/channels/turn-store.js +139 -0
  56. package/dist/channels/wait-health.d.ts +6 -0
  57. package/dist/channels/wait-health.js +27 -0
  58. package/dist/cli/commands/add.d.ts +9 -0
  59. package/dist/cli/commands/add.js +142 -0
  60. package/dist/cli/commands/chat.d.ts +3 -0
  61. package/dist/cli/commands/chat.js +16 -0
  62. package/dist/cli/commands/deploy.d.ts +13 -0
  63. package/dist/cli/commands/deploy.js +338 -0
  64. package/dist/cli/commands/dev.d.ts +11 -0
  65. package/dist/cli/commands/dev.js +76 -0
  66. package/dist/cli/commands/fire.d.ts +7 -0
  67. package/dist/cli/commands/fire.js +45 -0
  68. package/dist/cli/commands/info.d.ts +7 -0
  69. package/dist/cli/commands/info.js +108 -0
  70. package/dist/cli/commands/init.d.ts +8 -0
  71. package/dist/cli/commands/init.js +81 -0
  72. package/dist/cli/commands/invoke.d.ts +7 -0
  73. package/dist/cli/commands/invoke.js +29 -0
  74. package/dist/cli/commands/login.d.ts +6 -0
  75. package/dist/cli/commands/login.js +63 -0
  76. package/dist/cli/commands/models.d.ts +1 -0
  77. package/dist/cli/commands/models.js +15 -0
  78. package/dist/cli/commands/schedule.d.ts +12 -0
  79. package/dist/cli/commands/schedule.js +89 -0
  80. package/dist/cli/commands/start.d.ts +10 -0
  81. package/dist/cli/commands/start.js +90 -0
  82. package/dist/cli/commands/tool.d.ts +1 -0
  83. package/dist/cli/commands/tool.js +37 -0
  84. package/dist/cli/fail.d.ts +19 -0
  85. package/dist/cli/fail.js +32 -0
  86. package/dist/cli/kernel.d.ts +89 -0
  87. package/dist/cli/kernel.js +190 -0
  88. package/dist/cli/program.d.ts +11 -0
  89. package/dist/cli/program.js +421 -0
  90. package/dist/cli/serve.d.ts +28 -0
  91. package/dist/cli/serve.js +90 -0
  92. package/dist/cli/shared.d.ts +24 -0
  93. package/dist/cli/shared.js +116 -0
  94. package/dist/cli-add-feishu.d.ts +8 -0
  95. package/dist/cli-add-feishu.js +223 -0
  96. package/dist/cli.js +8 -1284
  97. package/dist/deploy/container.js +10 -6
  98. package/dist/deploy/docker/plan.d.ts +45 -0
  99. package/dist/deploy/docker/plan.js +139 -0
  100. package/dist/deploy/docker/run.d.ts +40 -0
  101. package/dist/deploy/docker/run.js +126 -0
  102. package/dist/deploy/fly/plan.d.ts +1 -1
  103. package/dist/deploy/fly/plan.js +15 -4
  104. package/dist/deploy/fly/run.d.ts +7 -4
  105. package/dist/deploy/fly/run.js +26 -5
  106. package/dist/deploy/preflight.js +4 -3
  107. package/dist/deploy/railway/plan.d.ts +1 -1
  108. package/dist/deploy/railway/plan.js +17 -5
  109. package/dist/deploy/railway/run.d.ts +6 -3
  110. package/dist/deploy/railway/run.js +26 -4
  111. package/dist/deploy/registration-gate.d.ts +20 -0
  112. package/dist/deploy/registration-gate.js +20 -0
  113. package/dist/deploy/runner.d.ts +4 -1
  114. package/dist/deploy/runner.js +1 -0
  115. package/dist/deploy/secrets.d.ts +10 -9
  116. package/dist/deploy/secrets.js +15 -14
  117. package/dist/dev-supervisor.js +2 -1
  118. package/dist/engines/pi/auth.js +160 -46
  119. package/dist/engines/pi/chat.js +80 -7
  120. package/dist/engines/pi/config.d.ts +12 -3
  121. package/dist/engines/pi/config.js +16 -1
  122. package/dist/engines/pi/create.d.ts +14 -6
  123. package/dist/engines/pi/create.js +52 -16
  124. package/dist/engines/pi/harness.d.ts +16 -1
  125. package/dist/engines/pi/harness.js +77 -1
  126. package/dist/engines/pi/invoke.d.ts +1 -1
  127. package/dist/engines/pi/invoke.js +37 -2
  128. package/dist/engines/pi/login.js +1 -1
  129. package/dist/engines/pi/search-tools.d.ts +10 -0
  130. package/dist/engines/pi/search-tools.js +138 -0
  131. package/dist/engines/pi/tool-context.d.ts +28 -0
  132. package/dist/engines/pi/tool-context.js +8 -0
  133. package/dist/engines/pi/tool.d.ts +32 -1
  134. package/dist/engines/pi/tool.js +42 -1
  135. package/dist/engines/pi/workspace.d.ts +4 -1
  136. package/dist/engines/pi/workspace.js +3 -1
  137. package/dist/feishu.d.ts +2 -0
  138. package/dist/feishu.js +2 -0
  139. package/dist/lark.d.ts +3 -0
  140. package/dist/lark.js +3 -0
  141. package/dist/open-url.d.ts +2 -0
  142. package/dist/open-url.js +6 -0
  143. package/dist/pi.d.ts +2 -1
  144. package/dist/scaffold/add-channel.d.ts +9 -5
  145. package/dist/scaffold/add-channel.js +75 -7
  146. package/dist/scaffold/templates/fastagent.config.mjs +1 -0
  147. package/dist/tunnel.d.ts +9 -6
  148. package/dist/tunnel.js +48 -31
  149. package/package.json +18 -4
  150. /package/dist/channels/{telegram/state.d.ts → state.d.ts} +0 -0
  151. /package/dist/channels/{telegram/turn-queue.d.ts → turn-queue.d.ts} +0 -0
@@ -0,0 +1,19 @@
1
+ /** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
2
+ export declare function stderrHasColors(): boolean;
3
+ /**
4
+ * The ONE error prefix every error message carries — bold red when stderr renders color, plain
5
+ * otherwise. Errors are the only place the CLI uses color at all.
6
+ */
7
+ export declare function errorPrefix(colors?: boolean): string;
8
+ /**
9
+ * User-fixable startup problems (missing model / bad config / broken definition) are thrown as plain
10
+ * `Error` — print just the message. Anything else (TypeError, non-Error) is a bug: keep the stack.
11
+ * Shared by the kernel and the command modules; exit 1 (runtime failure).
12
+ */
13
+ export declare function failStartup(error: unknown): never;
14
+ /**
15
+ * A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
16
+ * discovered in a command body): print the message and exit 2 — the same class as a parse error.
17
+ * Exit codes follow responsibility, not the layer that happens to discover the problem.
18
+ */
19
+ export declare function failUsage(message: string): never;
@@ -0,0 +1,32 @@
1
+ /** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
2
+ export function stderrHasColors() {
3
+ return process.stderr.isTTY === true && (process.stderr.hasColors?.() ?? false);
4
+ }
5
+ /**
6
+ * The ONE error prefix every error message carries — bold red when stderr renders color, plain
7
+ * otherwise. Errors are the only place the CLI uses color at all.
8
+ */
9
+ export function errorPrefix(colors = stderrHasColors()) {
10
+ return colors ? "\x1b[1;31mError:\x1b[0m" : "Error:";
11
+ }
12
+ /**
13
+ * User-fixable startup problems (missing model / bad config / broken definition) are thrown as plain
14
+ * `Error` — print just the message. Anything else (TypeError, non-Error) is a bug: keep the stack.
15
+ * Shared by the kernel and the command modules; exit 1 (runtime failure).
16
+ */
17
+ export function failStartup(error) {
18
+ if (error instanceof Error && error.constructor === Error)
19
+ console.error(`${errorPrefix()} ${error.message}`);
20
+ else
21
+ console.error(errorPrefix(), error);
22
+ process.exit(1);
23
+ }
24
+ /**
25
+ * A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
26
+ * discovered in a command body): print the message and exit 2 — the same class as a parse error.
27
+ * Exit codes follow responsibility, not the layer that happens to discover the problem.
28
+ */
29
+ export function failUsage(message) {
30
+ console.error(`${errorPrefix()} ${message}`);
31
+ process.exit(2);
32
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * CLI kernel — commands as data. A {@link CommandSpec} carries everything the CLI surface needs
3
+ * (summary, args/flags, examples, narrative notes, a lazy `run`), and {@link buildProgram} renders
4
+ * the set through commander. Commander is called ONLY from this module; the notation specs are
5
+ * written in — docopt-style argument brackets (`<required>`/`[optional]`), the flag DSL
6
+ * (`--auth-path <file>`, `--no-x` negation), and the derived option keys ({@link optionKey}) — is a
7
+ * contract this module owns and validates at build time. Replacing the parser means re-implementing
8
+ * that notation here (one module), not editing the specs.
9
+ *
10
+ * Follows clig.dev: per-command help in four spellings (`-h`/`--help`/`help <cmd>`/bare-with-missing-args),
11
+ * examples in help, "did you mean" suggestions (never auto-run), and one exit-code policy — 0 success,
12
+ * 1 runtime failure (owned by the command bodies), 2 usage error (anything the parser itself rejects).
13
+ */
14
+ import { Command } from "commander";
15
+ /** One positional argument, in commander syntax: `<name>` required, `[dir]` optional. */
16
+ export interface ArgSpec {
17
+ name: string;
18
+ description: string;
19
+ default?: string;
20
+ /** Closed value set — the parser rejects anything else as a usage error (exit 2). */
21
+ choices?: string[];
22
+ }
23
+ /** One flag, in the flag DSL: `--json`, or `--auth-path <file>` for a value-taking flag. */
24
+ export interface FlagSpec {
25
+ flags: string;
26
+ description: string;
27
+ /** Parses but does not appear in help — for retired flags that should still explain themselves. */
28
+ hidden?: boolean;
29
+ /** Mutually exclusive with these {@link optionKey} values — validated at build time. */
30
+ conflicts?: string[];
31
+ }
32
+ export interface ExampleSpec {
33
+ cmd: string;
34
+ note?: string;
35
+ }
36
+ export interface CommandSpec {
37
+ name: string;
38
+ /** One line for the command list in the parent help. */
39
+ summary: string;
40
+ /** Longer description for the command's own help; defaults to `summary`. */
41
+ description?: string;
42
+ args?: ArgSpec[];
43
+ flags?: FlagSpec[];
44
+ /** Shown in an "Examples:" section of the command's help — clig: users reach for examples first. */
45
+ examples?: ExampleSpec[];
46
+ /** Narrative help (behavior, precedence rules, caveats) appended after the generated sections. */
47
+ notes?: string;
48
+ /** A group command (e.g. `schedule`) declares subcommands instead of `run`. */
49
+ subcommands?: CommandSpec[];
50
+ /**
51
+ * The implementation: positional args in declaration order (an optional arg without a default is
52
+ * `undefined`), then the parsed flags. Lazy-import the actual work so `fastagent <cmd>` pays only
53
+ * for the modules that command uses. Runtime failures exit 1 from inside (fail visibly).
54
+ */
55
+ run?: (args: (string | undefined)[], flags: Record<string, unknown>) => Promise<void> | void;
56
+ }
57
+ /** Program-level configuration. Output/exit/width seams let tests drive the program in-process. */
58
+ export interface ProgramOptions {
59
+ /** Printed by `-v`/`--version`. */
60
+ version?: string;
61
+ /** Top-level Examples — rendered by the same Help pipeline as every command's. */
62
+ examples?: ExampleSpec[];
63
+ /** Top-level closing prose (the docs link) — reflowed like any notes. */
64
+ notes?: string;
65
+ /**
66
+ * Fixed help width — a TEST seam. Production omits it: commander then adapts to the terminal
67
+ * (and falls back to 80 when piped), the modern behavior. Our verbatim Examples/notes text is
68
+ * hand-wrapped at ≤78 columns so it reads well at any width ≥ 80 (prose caps, like man pages).
69
+ */
70
+ helpWidth?: number;
71
+ /**
72
+ * Force help colors on/off — a TEST seam. Production omits it: commander detects per stream
73
+ * (color TTY → on; pipe, NO_COLOR, TERM=dumb → off) and strips every SGR code when off.
74
+ */
75
+ colors?: boolean;
76
+ out?: (chunk: string) => void;
77
+ err?: (chunk: string) => void;
78
+ exit?: (code: number) => never;
79
+ }
80
+ /**
81
+ * The option key a flag string yields on the parsed-flags record — THE naming rule specs rely on:
82
+ * camelCase of the long name (`--auth-path` → `authPath`); a `--no-x` flag negates and stores under
83
+ * `x` (absent ⇒ `x !== false`). Owned and enforced here so `conflicts` references and run-body reads
84
+ * answer to one authority, not to an implicit parser behavior. Throws on a flag without a long form
85
+ * (clig: every flag has a full-length spelling).
86
+ */
87
+ export declare function optionKey(flags: string): string;
88
+ /** Build the commander program for `specs`. The CLI entry parses with it; tests inject the IO seams. */
89
+ export declare function buildProgram(specs: readonly CommandSpec[], options?: ProgramOptions): Command;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * CLI kernel — commands as data. A {@link CommandSpec} carries everything the CLI surface needs
3
+ * (summary, args/flags, examples, narrative notes, a lazy `run`), and {@link buildProgram} renders
4
+ * the set through commander. Commander is called ONLY from this module; the notation specs are
5
+ * written in — docopt-style argument brackets (`<required>`/`[optional]`), the flag DSL
6
+ * (`--auth-path <file>`, `--no-x` negation), and the derived option keys ({@link optionKey}) — is a
7
+ * contract this module owns and validates at build time. Replacing the parser means re-implementing
8
+ * that notation here (one module), not editing the specs.
9
+ *
10
+ * Follows clig.dev: per-command help in four spellings (`-h`/`--help`/`help <cmd>`/bare-with-missing-args),
11
+ * examples in help, "did you mean" suggestions (never auto-run), and one exit-code policy — 0 success,
12
+ * 1 runtime failure (owned by the command bodies), 2 usage error (anything the parser itself rejects).
13
+ */
14
+ import { Argument, Command, Help, InvalidArgumentError, Option } from "commander";
15
+ import { errorPrefix } from "./fail.js";
16
+ /**
17
+ * The option key a flag string yields on the parsed-flags record — THE naming rule specs rely on:
18
+ * camelCase of the long name (`--auth-path` → `authPath`); a `--no-x` flag negates and stores under
19
+ * `x` (absent ⇒ `x !== false`). Owned and enforced here so `conflicts` references and run-body reads
20
+ * answer to one authority, not to an implicit parser behavior. Throws on a flag without a long form
21
+ * (clig: every flag has a full-length spelling).
22
+ */
23
+ export function optionKey(flags) {
24
+ const long = flags
25
+ .split(/[\s,|]+/)
26
+ .filter((part) => part.startsWith("--"))
27
+ .at(-1);
28
+ if (!long)
29
+ throw new Error(`flag "${flags}" has no long form (clig: have full-length flags)`);
30
+ let name = long.replace(/^--/, "").replace(/[=<[].*$/, "");
31
+ if (name.startsWith("no-"))
32
+ name = name.slice(3);
33
+ return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
34
+ }
35
+ // Help styling (clig: formatting with intention): section headings are BOLD, nothing in help is
36
+ // colored — the only color in the whole CLI is the red error prefix. The style is always embedded;
37
+ // commander strips every SGR code from the assembled help whenever the target stream has no colors
38
+ // (non-TTY pipe, NO_COLOR, TERM=dumb).
39
+ const title = (s) => `\x1b[1m${s}\x1b[0m`; // bold — section headings
40
+ /** The spec behind each registered command — how the Help renderer reaches Examples/notes. */
41
+ const specOf = new WeakMap();
42
+ /** Build the commander program for `specs`. The CLI entry parses with it; tests inject the IO seams. */
43
+ export function buildProgram(specs, options = {}) {
44
+ const exit = options.exit ?? ((code) => process.exit(code));
45
+ const program = new Command("fastagent");
46
+ program.description("Serve a file-defined agent — persona.md, skills/, tools/, channels/ — as a live service.");
47
+ // The exit-code policy: commander throws only for parse-level events — help/version displays carry
48
+ // exitCode 0 (→ 0), everything else it rejects is a usage error (→ 2). Runtime failures never pass
49
+ // through here; command bodies exit 1 themselves.
50
+ program.exitOverride((err) => exit(err.exitCode === 0 ? 0 : 2));
51
+ program.configureHelp({
52
+ ...(options.helpWidth !== undefined ? { helpWidth: options.helpWidth } : {}),
53
+ styleTitle: title,
54
+ // ONE renderer for the whole page: commander's standard sections, then our Examples/notes —
55
+ // rendered with the SAME helper (helpWidth, styleTitle), so custom sections wrap and style
56
+ // exactly like native ones on any terminal.
57
+ formatHelp: (cmd, helper) => Help.prototype.formatHelp.call(helper, cmd, helper) + extraSections(cmd, helper),
58
+ });
59
+ program.configureOutput({
60
+ ...(options.out ? { writeOut: options.out } : {}),
61
+ ...(options.err ? { writeErr: options.err } : {}),
62
+ ...(options.colors !== undefined
63
+ ? { getOutHasColors: () => options.colors, getErrHasColors: () => options.colors }
64
+ : {}),
65
+ // Every parse-level error carries the ONE unified prefix: bold-red `Error:` (plain when stderr
66
+ // has no colors). Command bodies get the same prefix through failStartup/failUsage.
67
+ outputError: (str, write) => write(str.startsWith("error:") ? `${errorPrefix(options.colors)}${str.slice("error:".length)}` : str),
68
+ });
69
+ program.showSuggestionAfterError(); // "did you mean models?" — suggest only, never run it (clig on DWIM)
70
+ program.showHelpAfterError("(run with --help for usage)");
71
+ if (options.version)
72
+ program.version(options.version, "-v, --version", "print the fastagent version");
73
+ if (options.examples || options.notes) {
74
+ specOf.set(program, { name: "fastagent", summary: "", examples: options.examples, notes: options.notes });
75
+ }
76
+ // Subcommands inherit exitOverride/output/suggestion settings at .command() time — register last.
77
+ for (const spec of specs)
78
+ register(program, spec);
79
+ return program;
80
+ }
81
+ function register(parent, spec) {
82
+ // Validate the spec's option references BEFORE handing anything to commander: every flag must
83
+ // have a long form (optionKey throws), and conflicts must name keys that exist on THIS command —
84
+ // commander matches conflicts by name at parse time, so a typo would otherwise silently never fire.
85
+ const keys = new Set((spec.flags ?? []).map((f) => optionKey(f.flags)));
86
+ for (const f of spec.flags ?? []) {
87
+ for (const target of f.conflicts ?? []) {
88
+ if (!keys.has(target)) {
89
+ throw new Error(`command "${spec.name}": "${f.flags}" conflicts with unknown option key "${target}"`);
90
+ }
91
+ }
92
+ }
93
+ const cmd = parent.command(spec.name);
94
+ specOf.set(cmd, spec);
95
+ cmd.summary(spec.summary);
96
+ cmd.description(spec.description ?? spec.summary);
97
+ for (const a of spec.args ?? []) {
98
+ const arg = new Argument(a.name, a.description);
99
+ if (a.default !== undefined)
100
+ arg.default(a.default);
101
+ if (a.choices)
102
+ arg.choices(a.choices);
103
+ // A required argument means a non-empty VALUE, not just a present token: `invoke ""` must be a
104
+ // usage error, not an empty turn (the old dispatch's falsy guards, kept at the parse boundary).
105
+ // choices args validate membership already ("" is never a member).
106
+ if (a.name.startsWith("<") && !a.choices) {
107
+ arg.argParser((value) => {
108
+ if (value.trim() === "")
109
+ throw new InvalidArgumentError("must not be empty.");
110
+ return value;
111
+ });
112
+ }
113
+ cmd.addArgument(arg);
114
+ }
115
+ for (const f of spec.flags ?? []) {
116
+ const opt = new Option(f.flags, f.description);
117
+ if (f.hidden)
118
+ opt.hideHelp();
119
+ if (f.conflicts)
120
+ opt.conflicts(f.conflicts);
121
+ cmd.addOption(opt);
122
+ }
123
+ for (const sub of spec.subcommands ?? [])
124
+ register(cmd, sub);
125
+ const run = spec.run;
126
+ if (run) {
127
+ cmd.action(async (...invocation) => {
128
+ invocation.pop(); // the Command instance
129
+ const flags = invocation.pop();
130
+ await run(invocation, flags);
131
+ });
132
+ }
133
+ }
134
+ /**
135
+ * The Examples/notes sections a spec appends after commander's standard ones. Examples are
136
+ * preformatted (column-aligned `$ cmd # note` lines); notes are logical prose the renderer
137
+ * reflows to `helper.helpWidth` — the exact width the sections above were wrapped to.
138
+ */
139
+ function extraSections(cmd, helper) {
140
+ const spec = specOf.get(cmd);
141
+ if (!spec || ((spec.examples?.length ?? 0) === 0 && !spec.notes))
142
+ return "";
143
+ const width = helper.helpWidth ?? 80;
144
+ const lines = [];
145
+ if (spec.examples && spec.examples.length > 0) {
146
+ lines.push(helper.styleTitle("Examples:"));
147
+ // Inline, column-aligned notes (`$ cmd # note`) — a note on its own line reads as a stray
148
+ // fragment when neighboring examples have none.
149
+ const w = Math.max(...spec.examples.map((e) => e.cmd.length));
150
+ for (const e of spec.examples) {
151
+ lines.push(e.note ? ` $ ${e.cmd.padEnd(w)} # ${e.note}` : ` $ ${e.cmd}`);
152
+ }
153
+ lines.push("");
154
+ }
155
+ if (spec.notes) {
156
+ lines.push(...reflow(spec.notes, width, helper));
157
+ lines.push("");
158
+ }
159
+ return `\n${lines.join("\n")}`;
160
+ }
161
+ /**
162
+ * Reflow notes to the help width: plain lines are prose (joined and wrapped via the helper's own
163
+ * `boxWrap`); indented lines are preformatted (aligned tables like start's precedence chains) and
164
+ * pass through verbatim; blank lines separate paragraphs.
165
+ */
166
+ function reflow(text, width, helper) {
167
+ const out = [];
168
+ let prose = [];
169
+ const flush = () => {
170
+ if (prose.length > 0) {
171
+ out.push(...helper.boxWrap(prose.join(" "), width).split("\n"));
172
+ prose = [];
173
+ }
174
+ };
175
+ for (const line of text.split("\n")) {
176
+ if (/^\s/.test(line)) {
177
+ flush();
178
+ out.push(line);
179
+ }
180
+ else if (line === "") {
181
+ flush();
182
+ out.push("");
183
+ }
184
+ else {
185
+ prose.push(line);
186
+ }
187
+ }
188
+ flush();
189
+ return out;
190
+ }
@@ -0,0 +1,11 @@
1
+ import { type CommandSpec, type ProgramOptions } from "./kernel.ts";
2
+ /** Registration order = help order — the ORIGINAL usage wall's order, kept verbatim (this is a
3
+ * commander refactor of the same CLI, not a redesign). Exported for the kernel conformance tests. */
4
+ export declare const specs: readonly CommandSpec[];
5
+ /**
6
+ * The production program assembly (specs + the top-level examples/docs). Tests build through THIS —
7
+ * with their IO/width/color seams as overrides — so they exercise the real shape, not a lookalike.
8
+ */
9
+ export declare function buildCliProgram(overrides?: ProgramOptions): import("commander").Command;
10
+ /** Parse and run one CLI invocation (`argv` = process.argv). Usage errors exit 2 via the kernel policy. */
11
+ export declare function runCli(argv: readonly string[]): Promise<void>;