@fastagent-sh/fastagent 0.13.0 → 0.15.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 (238) hide show
  1. package/README.md +5 -5
  2. package/dist/agent.d.ts +20 -0
  3. package/dist/agent.js +9 -0
  4. package/dist/channels/context-buffer.d.ts +30 -0
  5. package/dist/channels/context-buffer.js +98 -0
  6. package/dist/channels/control.d.ts +28 -0
  7. package/dist/channels/control.js +214 -0
  8. package/dist/channels/feishu/card.js +1 -1
  9. package/dist/channels/feishu/context-buffer.d.ts +43 -0
  10. package/dist/channels/feishu/context-buffer.js +72 -0
  11. package/dist/channels/feishu/crypto.d.ts +4 -2
  12. package/dist/channels/feishu/crypto.js +4 -2
  13. package/dist/channels/feishu/feishu-api.d.ts +15 -7
  14. package/dist/channels/feishu/feishu-api.js +22 -4
  15. package/dist/channels/feishu/feishu.d.ts +38 -16
  16. package/dist/channels/feishu/feishu.js +286 -151
  17. package/dist/channels/feishu/invoke-turn.d.ts +24 -31
  18. package/dist/channels/feishu/invoke-turn.js +61 -62
  19. package/dist/channels/feishu/model.d.ts +98 -0
  20. package/dist/channels/feishu/model.js +9 -0
  21. package/dist/channels/feishu/normalize.d.ts +23 -0
  22. package/dist/channels/feishu/normalize.js +132 -0
  23. package/dist/channels/feishu/owned-threads.d.ts +7 -0
  24. package/dist/channels/feishu/owned-threads.js +47 -0
  25. package/dist/channels/feishu/parse.d.ts +21 -103
  26. package/dist/channels/feishu/parse.js +35 -145
  27. package/dist/channels/feishu/preview.d.ts +4 -7
  28. package/dist/channels/feishu/preview.js +26 -142
  29. package/dist/channels/feishu/register-app.d.ts +2 -1
  30. package/dist/channels/feishu/register-webhook.d.ts +2 -1
  31. package/dist/channels/feishu/scaffold/channel.ts +10 -3
  32. package/dist/channels/feishu/setup-mode.d.ts +8 -0
  33. package/dist/channels/feishu/setup-mode.js +2 -0
  34. package/dist/channels/feishu/ws-ingress.d.ts +28 -0
  35. package/dist/channels/feishu/ws-ingress.js +136 -0
  36. package/dist/channels/github/github.js +8 -6
  37. package/dist/channels/http.d.ts +14 -0
  38. package/dist/channels/http.js +35 -2
  39. package/dist/channels/invoke-turn-kit.d.ts +65 -0
  40. package/dist/channels/invoke-turn-kit.js +87 -0
  41. package/dist/channels/lark/lark.d.ts +4 -2
  42. package/dist/channels/lark/lark.js +4 -1
  43. package/dist/channels/lark/onboard.d.ts +8 -4
  44. package/dist/channels/lark/onboard.js +8 -0
  45. package/dist/channels/lark/scaffold/channel.ts +10 -3
  46. package/dist/channels/preview-kit.d.ts +109 -0
  47. package/dist/channels/preview-kit.js +183 -0
  48. package/dist/channels/seen.d.ts +5 -0
  49. package/dist/channels/seen.js +35 -0
  50. package/dist/channels/slack/bot-auth.d.ts +15 -0
  51. package/dist/channels/slack/bot-auth.js +146 -0
  52. package/dist/channels/slack/config-api.d.ts +60 -0
  53. package/dist/channels/slack/config-api.js +149 -0
  54. package/dist/channels/slack/context-buffer.d.ts +24 -0
  55. package/dist/channels/slack/context-buffer.js +37 -0
  56. package/dist/channels/slack/invoke-turn.d.ts +19 -0
  57. package/dist/channels/slack/invoke-turn.js +63 -0
  58. package/dist/channels/slack/manifest.d.ts +49 -0
  59. package/dist/channels/slack/manifest.js +69 -0
  60. package/dist/channels/slack/model.d.ts +67 -0
  61. package/dist/channels/slack/model.js +2 -0
  62. package/dist/channels/slack/onboard.d.ts +41 -0
  63. package/dist/channels/slack/onboard.js +120 -0
  64. package/dist/channels/slack/onboarding-state.d.ts +31 -0
  65. package/dist/channels/slack/onboarding-state.js +69 -0
  66. package/dist/channels/slack/owned-threads.d.ts +6 -0
  67. package/dist/channels/slack/owned-threads.js +43 -0
  68. package/dist/channels/slack/parse.d.ts +23 -0
  69. package/dist/channels/slack/parse.js +81 -0
  70. package/dist/channels/slack/preview.d.ts +24 -0
  71. package/dist/channels/slack/preview.js +359 -0
  72. package/dist/channels/slack/reaction.d.ts +24 -0
  73. package/dist/channels/slack/reaction.js +62 -0
  74. package/dist/channels/slack/register-webhook.d.ts +10 -0
  75. package/dist/channels/slack/register-webhook.js +49 -0
  76. package/dist/channels/slack/scaffold/channel.ts +33 -0
  77. package/dist/channels/slack/scaffold/slack-send.ts +171 -0
  78. package/dist/channels/slack/setup-server.d.ts +17 -0
  79. package/dist/channels/slack/setup-server.js +103 -0
  80. package/dist/channels/slack/slack-api.d.ts +77 -0
  81. package/dist/channels/slack/slack-api.js +415 -0
  82. package/dist/channels/slack/slack.d.ts +58 -0
  83. package/dist/channels/slack/slack.js +451 -0
  84. package/dist/channels/slack/welcomed.d.ts +5 -0
  85. package/dist/channels/slack/welcomed.js +32 -0
  86. package/dist/channels/state.js +3 -3
  87. package/dist/channels/stop-command.d.ts +6 -0
  88. package/dist/channels/stop-command.js +36 -0
  89. package/dist/channels/tasks.d.ts +13 -0
  90. package/dist/channels/tasks.js +10 -0
  91. package/dist/channels/telegram/context-buffer.d.ts +8 -17
  92. package/dist/channels/telegram/context-buffer.js +6 -85
  93. package/dist/channels/telegram/invoke-turn.d.ts +5 -22
  94. package/dist/channels/telegram/invoke-turn.js +11 -58
  95. package/dist/channels/telegram/preview.d.ts +4 -7
  96. package/dist/channels/telegram/preview.js +24 -142
  97. package/dist/channels/telegram/telegram.js +23 -9
  98. package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
  99. package/dist/channels/{feishu/text.js → text.js} +1 -1
  100. package/dist/channels/turn-queue.js +1 -1
  101. package/dist/channels/turn-store.d.ts +1 -1
  102. package/dist/channels/turn-store.js +2 -3
  103. package/dist/cli/add-feishu.d.ts +27 -0
  104. package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
  105. package/dist/cli/add-slack.d.ts +10 -0
  106. package/dist/cli/add-slack.js +204 -0
  107. package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
  108. package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
  109. package/dist/cli/commands/add.d.ts +13 -0
  110. package/dist/cli/commands/add.js +274 -0
  111. package/dist/cli/commands/attach.d.ts +82 -0
  112. package/dist/cli/commands/attach.js +559 -0
  113. package/dist/cli/commands/chat.d.ts +4 -0
  114. package/dist/cli/commands/chat.js +21 -0
  115. package/dist/cli/commands/deploy.d.ts +15 -0
  116. package/dist/cli/commands/deploy.js +394 -0
  117. package/dist/cli/commands/dev.d.ts +11 -0
  118. package/dist/cli/commands/dev.js +82 -0
  119. package/dist/cli/commands/fire.d.ts +7 -0
  120. package/dist/cli/commands/fire.js +45 -0
  121. package/dist/cli/commands/info.d.ts +7 -0
  122. package/dist/cli/commands/info.js +108 -0
  123. package/dist/cli/commands/init.d.ts +8 -0
  124. package/dist/cli/commands/init.js +81 -0
  125. package/dist/cli/commands/invoke.d.ts +7 -0
  126. package/dist/cli/commands/invoke.js +28 -0
  127. package/dist/cli/commands/login.d.ts +6 -0
  128. package/dist/cli/commands/login.js +52 -0
  129. package/dist/cli/commands/models.d.ts +1 -0
  130. package/dist/cli/commands/models.js +15 -0
  131. package/dist/cli/commands/schedule.d.ts +12 -0
  132. package/dist/cli/commands/schedule.js +89 -0
  133. package/dist/cli/commands/start.d.ts +10 -0
  134. package/dist/cli/commands/start.js +97 -0
  135. package/dist/cli/commands/tool.d.ts +1 -0
  136. package/dist/cli/commands/tool.js +38 -0
  137. package/dist/cli/fail.d.ts +17 -0
  138. package/dist/cli/fail.js +32 -0
  139. package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
  140. package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
  141. package/dist/cli/kernel.d.ts +90 -0
  142. package/dist/cli/kernel.js +190 -0
  143. package/dist/cli/models-view.d.ts +21 -0
  144. package/dist/cli/models-view.js +66 -0
  145. package/dist/cli/program.d.ts +11 -0
  146. package/dist/cli/program.js +479 -0
  147. package/dist/cli/serve.d.ts +48 -0
  148. package/dist/cli/serve.js +248 -0
  149. package/dist/cli/shared.d.ts +44 -0
  150. package/dist/cli/shared.js +246 -0
  151. package/dist/cli.js +8 -1329
  152. package/dist/collect.d.ts +14 -3
  153. package/dist/collect.js +24 -0
  154. package/dist/core.d.ts +3 -1
  155. package/dist/core.js +2 -0
  156. package/dist/deploy/docker/plan.d.ts +45 -0
  157. package/dist/deploy/docker/plan.js +141 -0
  158. package/dist/deploy/docker/run.d.ts +40 -0
  159. package/dist/deploy/docker/run.js +126 -0
  160. package/dist/deploy/fly/plan.d.ts +3 -1
  161. package/dist/deploy/fly/plan.js +12 -7
  162. package/dist/deploy/fly/run.d.ts +5 -4
  163. package/dist/deploy/fly/run.js +16 -5
  164. package/dist/deploy/preflight.d.ts +10 -4
  165. package/dist/deploy/preflight.js +57 -12
  166. package/dist/deploy/railway/plan.d.ts +3 -1
  167. package/dist/deploy/railway/plan.js +12 -4
  168. package/dist/deploy/railway/run.d.ts +5 -4
  169. package/dist/deploy/railway/run.js +15 -5
  170. package/dist/deploy/runner.d.ts +6 -2
  171. package/dist/deploy/runner.js +1 -0
  172. package/dist/deploy/secrets.d.ts +2 -1
  173. package/dist/deploy/secrets.js +23 -3
  174. package/dist/dev-supervisor.d.ts +0 -2
  175. package/dist/dev-supervisor.js +7 -3
  176. package/dist/engines/pi/auth.js +160 -46
  177. package/dist/engines/pi/channel.d.ts +22 -16
  178. package/dist/engines/pi/channel.js +90 -60
  179. package/dist/engines/pi/chat.d.ts +4 -16
  180. package/dist/engines/pi/chat.js +8 -188
  181. package/dist/engines/pi/config.d.ts +23 -9
  182. package/dist/engines/pi/config.js +35 -5
  183. package/dist/engines/pi/create.d.ts +36 -7
  184. package/dist/engines/pi/create.js +63 -22
  185. package/dist/engines/pi/harness.d.ts +65 -1
  186. package/dist/engines/pi/harness.js +166 -2
  187. package/dist/engines/pi/invoke.d.ts +56 -3
  188. package/dist/engines/pi/invoke.js +340 -20
  189. package/dist/engines/pi/login.d.ts +11 -0
  190. package/dist/engines/pi/login.js +17 -5
  191. package/dist/engines/pi/models.d.ts +56 -10
  192. package/dist/engines/pi/models.js +61 -23
  193. package/dist/engines/pi/search-tools.d.ts +10 -0
  194. package/dist/engines/pi/search-tools.js +138 -0
  195. package/dist/engines/pi/session-builder.d.ts +16 -0
  196. package/dist/engines/pi/session-builder.js +308 -0
  197. package/dist/engines/pi/session-control.d.ts +50 -0
  198. package/dist/engines/pi/session-control.js +604 -0
  199. package/dist/engines/pi/sessions.d.ts +17 -2
  200. package/dist/engines/pi/sessions.js +9 -0
  201. package/dist/engines/pi/tool-context.d.ts +46 -11
  202. package/dist/engines/pi/tool-context.js +11 -9
  203. package/dist/engines/pi/tool.d.ts +35 -6
  204. package/dist/engines/pi/tool.js +47 -1
  205. package/dist/engines/pi/wake-tool.d.ts +0 -3
  206. package/dist/engines/pi/wake-tool.js +9 -7
  207. package/dist/engines/pi/workspace.d.ts +56 -1
  208. package/dist/engines/pi/workspace.js +75 -16
  209. package/dist/feishu.d.ts +1 -1
  210. package/dist/feishu.js +1 -1
  211. package/dist/host/node.d.ts +23 -6
  212. package/dist/host/node.js +5 -4
  213. package/dist/index.d.ts +1 -0
  214. package/dist/index.js +1 -0
  215. package/dist/lark.d.ts +1 -1
  216. package/dist/lark.js +1 -1
  217. package/dist/observe.js +3 -0
  218. package/dist/pi.d.ts +6 -4
  219. package/dist/pi.js +2 -1
  220. package/dist/scaffold/add-channel.d.ts +19 -6
  221. package/dist/scaffold/add-channel.js +127 -16
  222. package/dist/scaffold/templates/fastagent.config.mjs +5 -3
  223. package/dist/schedule/wakeups.d.ts +0 -3
  224. package/dist/schedule/wakeups.js +1 -1
  225. package/dist/session-remote.d.ts +53 -0
  226. package/dist/session-remote.js +336 -0
  227. package/dist/session.d.ts +265 -0
  228. package/dist/session.js +37 -0
  229. package/dist/slack.d.ts +2 -0
  230. package/dist/slack.js +2 -0
  231. package/dist/tunnel.d.ts +7 -4
  232. package/dist/tunnel.js +21 -10
  233. package/package.json +22 -7
  234. package/dist/channels/feishu/seen.d.ts +0 -5
  235. package/dist/channels/feishu/seen.js +0 -47
  236. package/dist/cli-add-feishu.d.ts +0 -8
  237. package/dist/cli-models.d.ts +0 -11
  238. package/dist/cli-models.js +0 -20
@@ -14,6 +14,10 @@ export async function runInvokeStream(events, out, err) {
14
14
  if (event.isError)
15
15
  err(`[tool] ${toolName.get(event.id) ?? event.id} failed`);
16
16
  break;
17
+ case "retrying":
18
+ // Operator-facing: include the reason (channels show a neutral customer line instead).
19
+ err(`[fastagent] transient failure — retrying (${event.attempt}/${event.maxAttempts} in ${event.delayMs}ms): ${event.reason}`);
20
+ break;
17
21
  case "failed":
18
22
  err(`[fastagent] failed: ${event.details}${event.retryable ? " (retryable)" : ""}`);
19
23
  exitCode = 1;
@@ -0,0 +1,90 @@
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
+ 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
+ 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;
90
+ export {};
@@ -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,21 @@
1
+ import type { ProviderAuthStatus } from "../engines/pi/models.ts";
2
+ /** One first-run picker entry (@clack/prompts option shape). */
3
+ export interface ModelPickerOption {
4
+ value: string;
5
+ label: string;
6
+ hint: string;
7
+ }
8
+ /**
9
+ * The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
10
+ * status — ready first (usable now, with the credential source so "which account pays" is visible at
11
+ * the decision point), then the rest with their remedy. Order within each group preserves `specs`
12
+ * (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
13
+ * fail visibly.
14
+ */
15
+ export declare function buildModelPickerOptions(specs: string[], statuses: Map<string, ProviderAuthStatus>): ModelPickerOption[];
16
+ /** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
17
+ * substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
18
+ export declare function formatModelsCommand(specs: string[], search?: string): {
19
+ lines: string[];
20
+ error?: string;
21
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * CLI presenter for the model-facing commands: `fastagent models [search]` output and the first-run
3
+ * picker's option list. Output DECISIONS live here, out of the engine config layer (config.ts owns
4
+ * model resolution / listModels), so this process-boundary behavior is unit-testable without
5
+ * spawning the CLI.
6
+ */
7
+ import { providerOf } from "../engines/pi/config.js";
8
+ /** The remedy hint for a non-ready provider, by what picking it actually does: an OAuth flow →
9
+ * "login required"; an interactive key prompt → "API key required"; no flow at all → the env var. */
10
+ function remedy(login) {
11
+ if (login === "oauth")
12
+ return "login required";
13
+ return login === "api_key" ? "API key required" : "API key required — set the provider's env var";
14
+ }
15
+ /**
16
+ * The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
17
+ * status — ready first (usable now, with the credential source so "which account pays" is visible at
18
+ * the decision point), then the rest with their remedy. Order within each group preserves `specs`
19
+ * (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
20
+ * fail visibly.
21
+ */
22
+ export function buildModelPickerOptions(specs, statuses) {
23
+ const ready = [];
24
+ const rest = [];
25
+ for (const spec of specs) {
26
+ const status = statuses.get(providerOf(spec));
27
+ if (status?.state === "ready") {
28
+ ready.push({ value: spec, label: spec, hint: status.source ? `ready — ${status.source}` : "ready" });
29
+ }
30
+ else if (status?.state === "broken") {
31
+ // A broken stored credential OWNS the provider (env is consulted only when nothing is stored),
32
+ // so with no login flow the remedy is fixing the store — not the env var (which can't win here).
33
+ rest.push({
34
+ value: spec,
35
+ label: spec,
36
+ hint: status.login === "none"
37
+ ? `stored auth unusable: ${status.message} — fix or remove the stored credential`
38
+ : `${remedy(status.login)} — stored auth unusable: ${status.message}`,
39
+ });
40
+ }
41
+ else if (status) {
42
+ rest.push({ value: spec, label: spec, hint: remedy(status.login) });
43
+ }
44
+ else {
45
+ // Unreachable when `specs` and `statuses` come from the same Models (every listed provider is
46
+ // probed); if a caller ever mixes sources, promise nothing — neutral wording, no login claim.
47
+ rest.push({ value: spec, label: spec, hint: "auth required" });
48
+ }
49
+ }
50
+ return [...ready, ...rest];
51
+ }
52
+ /** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
53
+ * substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
54
+ export function formatModelsCommand(specs, search) {
55
+ if (!search)
56
+ return { lines: specs };
57
+ const q = search.toLowerCase();
58
+ const matches = specs.filter((spec) => spec.toLowerCase().includes(q));
59
+ if (matches.length === 0)
60
+ return { lines: matches, error: `no model matches "${search}"` };
61
+ // Rank a PROVIDER-name match (query in the part before "/") above an incidental model-id match, so
62
+ // `models anthropic` leads with anthropic/* rather than burying it under amazon-bedrock/anthropic.*
63
+ // and google-vertex/…-anthropic-… (which only match in the model id). Order within each group is kept.
64
+ const providerMatch = (spec) => providerOf(spec).toLowerCase().includes(q);
65
+ return { lines: [...matches.filter(providerMatch), ...matches.filter((s) => !providerMatch(s))] };
66
+ }
@@ -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>;