@wirecat/cli-base 0.63.0 → 0.65.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 (42) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/dist/codegen/discovery.d.ts +3 -3
  3. package/dist/codegen/discovery.js +18 -9
  4. package/dist/codegen/index.d.ts +3 -1
  5. package/dist/codegen/index.js +3 -1
  6. package/dist/codegen/json-schema.d.ts +8 -0
  7. package/dist/codegen/json-schema.js +78 -0
  8. package/dist/commands/index.d.ts +1 -1
  9. package/dist/commands/index.js +1 -1
  10. package/dist/credentials.d.ts +1 -1
  11. package/dist/dev/bundle.d.ts +31 -0
  12. package/dist/dev/bundle.js +280 -0
  13. package/dist/dev/main.js +48 -1
  14. package/dist/skill/index.d.ts +1 -1
  15. package/dist/skill/index.js +2 -2
  16. package/dist/standards/audit.js +8 -0
  17. package/dist/standards/flags-calendar.d.ts +2 -0
  18. package/dist/standards/flags-calendar.js +72 -0
  19. package/dist/standards/flags-how.js +7 -1
  20. package/dist/standards/flags-what.js +7 -0
  21. package/dist/standards/vocabulary.js +13 -2
  22. package/docs/standards/README.md +5 -5
  23. package/docs/standards/accounts.md +44 -11
  24. package/docs/standards/architecture.md +19 -12
  25. package/docs/standards/ci.md +33 -12
  26. package/docs/standards/commands.md +35 -10
  27. package/docs/standards/data-model.md +5 -1
  28. package/docs/standards/docs.md +4 -1
  29. package/docs/standards/kit-surface.md +6 -5
  30. package/docs/standards/layout.md +32 -0
  31. package/docs/standards/lifecycle.md +69 -8
  32. package/docs/standards/mcp.md +48 -24
  33. package/docs/standards/new-cli.md +30 -16
  34. package/docs/standards/output.md +13 -2
  35. package/docs/standards/permissions.md +46 -3
  36. package/docs/standards/releases.md +21 -5
  37. package/docs/standards/security.md +16 -8
  38. package/docs/standards/store.md +4 -3
  39. package/docs/standards/testing.md +19 -8
  40. package/docs/standards/time.md +8 -1
  41. package/docs/standards/toolchain.md +24 -18
  42. package/package.json +6 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,238 @@ Versions before 0.8.0 are in the [git tags](https://github.com/WireCatLabs/cli-b
8
8
  Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
9
9
  [`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).
10
10
 
11
+ ## 0.65.0 — 27.10.2026
12
+
13
+ ### Added
14
+
15
+ - **Calendar words for moving an event and for "this and all following".** Flag `--following` (`target`, switch):
16
+ this occurrence and every later one of a repeating event (`events update|delete --following`), beside `--series`.
17
+ `--scope` already means personal or work, so it is not reused. Flag `--to-calendar <id>` (`target`, id): the
18
+ calendar an event moves to (`events move --to-calendar`); `--calendar` keeps its meaning, the calendar the event is
19
+ on. The verb `move` now says what moves in general: a file to a folder, an event to a calendar. cli-core's shared
20
+ calendar commands use all three; gcal-cli carries `lint/overrides.json` entries for them until this release.
21
+ - **Every CLI that signs in has `session start`, `session status`, `session end` and a profile-free `session list`.**
22
+ [WC-ACCT-12](docs/standards/accounts.md#wc-acct-12--sign-in-is-session-start-status-and-end-and-session-list-names-the-profiles)
23
+ is the owner's ruling: `status` says whether one profile is signed in, `list` which profiles exist. Each
24
+ CLI's missing commands are listed as known gaps: tg, max and wa lack `session status` and list profiles with
25
+ `account list`; drive lacks `session status`; gcal lacks `session list`; zm lacks `session end` and `session list`;
26
+ memo has no `session` group.
27
+ - **A sync spends a request budget sized to its deadline and says how to continue.**
28
+ [WC-LIFE-16](docs/standards/lifecycle.md#wc-life-16--a-sync-spends-a-request-budget-sized-to-its-deadline-and-its-answer-says-how-to-continue)
29
+ describes what cli-core 0.464.0 (cli-core #2118) builds for the calendar sync: `floor((deadline − margin) /
30
+ requestMs)` requests, one `requestMs` per CLI, `--max-pages` only lowering it, a partial stop that exits 0 with the
31
+ exact command to continue, and progress in the sync's status command. The other syncs and gcal's own `requestMs`
32
+ are listed as gaps.
33
+
34
+ - **The command vocabulary has the verbs `serve` (run a server in this process and answer its clients until stopped)
35
+ and `revoke` (take back what was granted, so it stops working).** cli-core 0.463.0 names the MCP server
36
+ `mcp serve` and gives forgetting browser logins its own command, `mcp revoke`; `revoke` also covers
37
+ `chats link revoke` and the `consents revoke` commands that were baselined as unknown verbs.
38
+
39
+ ### Changed — may break callers
40
+
41
+ - **A command that reads the service and only saves what it read is a read, also over MCP.**
42
+ [WC-CMD-17](docs/standards/commands.md#wc-cmd-17--declare-effects-on-every-command) defines a write, and WC-CMD-6,
43
+ WC-CMD-28, [WC-MCP-4](docs/standards/mcp.md#wc-mcp-4--read-runs-only-reading-commands-and-write-only-writing-ones)
44
+ and WC-OUT-9 point at it: `{ remote: "read", local: "write" }` (`sync`, `calendars list`) runs through the `read`
45
+ tool; `{ remote: "none", local: "write" }` (`store reindex`) stays a write. cli-base's `writes()` still counts every
46
+ local write, so cli-core serves `sync` through `write` until a follow-up changes it; watch for that release, which
47
+ moves those commands between tools.
48
+ - **Deadlines: 30 s for a one-shot command, 5 min for a cooperative one at a terminal and 45 s over MCP.**
49
+ [WC-LIFE-5](docs/standards/lifecycle.md#wc-life-5--bound-every-one-shot-command-by-a-deadline) and WC-LIFE-10 say
50
+ so, as cli-core 0.464.0 builds it, because MCP clients cut a tool call at about 60 s.
51
+ - **A non-messenger CLI allows ordinary writes, asks before deletes, and denies mail to other people and deleting
52
+ a whole series.**
53
+ [WC-PERM-22](docs/standards/permissions.md#wc-perm-22--a-cli-that-is-not-a-messenger-declares-its-own-keys-and-defaults-with-commandpermissions)
54
+ follows the owner's ruling, and a CLI's generic API path must check the shared keys (`events.notify`,
55
+ `events.delete.series`) too. Its example said sharing a calendar is `deny`; the owner made it `ask`. cli-core's
56
+ `CALENDAR_WRITE_DEFAULTS` and gcal's `api` defaults do not follow it yet; both are listed as gaps.
57
+ - **Meeting passcodes stay out of command output and agent answers, not only out of the store.**
58
+ [WC-SECR-23](docs/standards/security.md) covers answers that come straight from the provider: gcal's `api` answers
59
+ still keep a description's passcode lines, a listed gap.
60
+ - **Each CLI pins the command catalogue behind its MCP tools.** The [MCP](docs/standards/mcp.md) page no longer
61
+ claims `mcp-tools.json` pins the catalogue: with three tools it does not. `contracts/mcp-catalogue.json` does; tg and
62
+ max have it, and wa, zm, gcal and drive do not yet.
63
+
64
+ - **An MCP server serves the profiles it is told, and refuses without them.**
65
+ [WC-MCP-9](docs/standards/mcp.md#wc-mcp-9--a-server-serves-the-profiles-it-is-told-with-one-connection-for-each)
66
+ now allows one named profile (`<cli> <profile> mcp serve`), a list (`<cli> mcp serve --profiles work,personal`) or
67
+ every profile (`<cli> mcp serve --profiles all`), with one messenger connection per served profile. With no profile
68
+ and no `--profiles`, `mcp serve` fails with `validation_error` and lists the profiles; it never serves every profile
69
+ by default. With several profiles, each call names its profile. WC-ACCT-1, WC-ACCT-2 and WC-ACCT-5 say how
70
+ `--profiles` fits the profile rules, and a profile lock refuses `--profiles all`. The flag registry has `--profiles`.
71
+ The owner ruled it; cli-core does not build `--profiles` yet.
72
+ - **The handbook names the MCP server `mcp serve` and has no `mcp --revoke` exception.** The
73
+ [MCP](docs/standards/mcp.md) and [Lifecycle](docs/standards/lifecycle.md) pages say `mcp serve`, `mcp serve --http`,
74
+ `mcp serve --permission`, `bot mcp serve` and `mcp revoke`, as cli-core 0.463.0 builds them.
75
+ [WC-CMD-6](docs/standards/commands.md#wc-cmd-6--give-one-action-or-one-report-one-leaf-command) no longer lets
76
+ `--revoke` stay an option: forgetting every browser login is the `mcp revoke` command. A CLI with its own server
77
+ (drive, memo, zoom) moves to the same shape.
78
+ - **A lint baseline entry for `serve` or `revoke` goes stale, and `cli-dev lint` fails on it.** cli-core lists
79
+ `bot mcp serve`, `mcp serve`, `mcp revoke` and `replies consents revoke` in `lint/baseline/unknown-verb.json`;
80
+ tg-cli and max-cli list `replies consents revoke`. Remove those entries in the same change that takes this version.
81
+ - **The HTTP MCP server rule covers every CLI that offers `mcp serve --http`, not only the messenger CLIs.**
82
+ [WC-MCP-12](docs/standards/mcp.md#wc-mcp-12--an-mcp-server-over-http-follows-the-same-permissions-and-short-lived-logins)
83
+ now applies to cli-template and every CLI made from it, which have offered `mcp serve --http` with no rule over it.
84
+ The template already follows it; a CLI that changed its copy of the HTTP server checks it against the rule.
85
+
86
+ ### Fixed
87
+
88
+ - **`cli-dev audit` compares gcal only on the commands it already shares with every CLI, for now.** cli-base 0.64.0
89
+ put gcal into the `all` parity group, and gcal's release audit then blocked on 20 `same-path-contract` differences
90
+ in `doctor`, `mcp`, `search` and `skill` that already exist among the other six published CLIs and that gcal
91
+ cannot remove alone (gcal-cli#13). gcal leaves `all` and has its own group, `gcal-shared`, with every CLI over
92
+ `commands`, `upgrade`, `notes` and `meetings`. It rejoins `all` once tg, max, wa, drive, zoom and memo release on
93
+ cli-core's shared `mcp serve`, `doctor` and `skill` ([#390](https://github.com/WireCatLabs/cli-base/issues/390)).
94
+ The other CLIs' audits are unchanged: a path is compared across every member of every group that covers it.
95
+
96
+ - **`cli-dev bundle`'s loader finds a server after a flag: `<cli> personal --read-only mcp serve`.** It looked for
97
+ `serve`, `watch` or `mcp` in the first three words of the command, and since cli-core 0.463 named the server
98
+ `mcp serve`, a flag before it pushed `serve` to the fourth word. Such a server loaded no chunks ahead and could fail
99
+ after an in-place upgrade (measured on drive: 91 of 138 chunks loaded, instead of 138). The loader now skips flags
100
+ and looks at four words. Rebuild a CLI with this release to take it.
101
+ - **The handbook no longer contradicts itself on gcal's audit findings.**
102
+ - WC-OUT-8 and WC-OUT-14: an answer with `meta.complete: false` is an answer on stdout, also after a non-zero
103
+ exit; only a run with no answer prints one JSON error with stdout empty.
104
+ - WC-CMD-10 names `--max-pages` a `budget` option, not a paging one, and says why the calendar `--id` exists beside
105
+ `--operation-id`; gcal's `events create`, with `--id` and no `--operation-id`, is a listed gap. WC-CMD-11 lists `--duration-minutes` as a known gap until the vocabulary has
106
+ `--duration <duration>`.
107
+ - WC-REL-16 says `packContents` checks the pack against the list it is given, so a check that passes the `files`
108
+ list itself cannot fail on a wrong list; the fix belongs in cli-template.
109
+ - WC-TIME-5 matches an all-day event by local dates, not UTC moments; cli-core's event filter is a listed gap.
110
+ - WC-ARCH-3 counts generated API types as vendor types, WC-ARCH-5 and WC-ARCH-6 cover calendar CLIs, and
111
+ WC-ARCH-21's example is gcal's real provider entry.
112
+ - WC-TOOL-16 puts a word waiting for the vocabulary in `lint/overrides.json` with a link and a date, never in the
113
+ baseline. The [Documents](docs/standards/docs.md#wc-doc-3--every-document-states-current-facts-rewrite-a-wrong-sentence-in-place)
114
+ rule on current facts sends a false template-owned file to cli-template.
115
+ - WC-DATA-3 makes a calendar entry's `external_id` unique per calendar, WC-STORE-3 lists the calendar areas as not
116
+ bound, WC-TEST-12 says what a reason names, and WC-TEST-14 counts the shared store as local state.
117
+ - The "who follows" lists name gcal.
118
+
119
+ - **The handbook's pages on the template match cli-template 0.9.0.** The [Kit surface](docs/standards/kit-surface.md)
120
+ table lists `LOCAL_WRITE` and `revokeAll`, which the template now imports, and counts imports loaded on demand.
121
+ [New CLI](docs/standards/new-cli.md) says the MCP server has three tools that reach every command, not a tool for
122
+ every command.
123
+ - **The handbook says where a lint violation that a shared package forces goes, and which cli-core files are not a
124
+ CLI's local state.** [WC-TOOL-16](docs/standards/toolchain.md) puts such a violation in the CLI's lint baseline
125
+ until the shared package catches up.
126
+ [WC-TEST-14](docs/standards/testing.md#wc-test-14--check-the-upgrade-from-the-previous-published-version) names
127
+ the update check, MCP logins and lock files that only cli-core writes as outside the upgrade contract; tests still
128
+ keep them in the sandboxed home.
129
+
130
+ ## 0.64.0 — 27.10.2026
131
+
132
+ ### Added
133
+
134
+ - **`cli-dev bundle --bin dist/bin/<cli>.js` builds the published bin as an esbuild bundle, the way tg-cli 0.100.0
135
+ ships.** It moves tg's `scripts/bundle.ts` into cli-base so every CLI builds the same way: code splitting that keeps
136
+ each lazy `import()` lazy; `import.meta.url` and run-time package lookups that stay at the original module's place
137
+ in the CLI, cli-core and cli-base; CommonJS named exports for a dynamic `import("yaml")`; kept names; native and
138
+ by-name packages left outside. The bin becomes a loader that answers `--version`, turns the compile cache on and,
139
+ under `serve`, `watch` and `mcp`, preloads every chunk so a running server survives an in-place upgrade. The build
140
+ fails when a bundled module would look in the wrong place. Add `esbuild` (an optional peer, `^0.28.0`) as a
141
+ devDependency and name the CLI's own outside packages with `--external`. The rule is
142
+ [WC-LAY-10](docs/standards/layout.md#wc-lay-10--ship-the-bin-as-a-bundle-that-cli-dev-bundle-builds).
143
+ - **WC-PERM-22 on the [Permissions](docs/standards/permissions.md) page: a CLI that is not a messenger declares its
144
+ own permission keys and defaults with cli-core's `commandPermissions`** (cli-core 0.450.0). It says how such a CLI
145
+ keys its commands, picks built-in defaults for what its writes do, and runs writes through the read-only lock and the
146
+ write guard. The example is a calendar: event writes ask, deleting a whole series and mailing attendees are denied
147
+ until the owner allows them. The page covered messenger CLIs only, and WC-PERM-6's "ask only before what cannot be
148
+ undone" read as if it applied to a calendar too; it now says it is the messengers' list. drive, zoom and memo do
149
+ not declare `commandPermissions` yet; the rule names them as known gaps.
150
+ - **`cli-dev audit` compares gcal (`@wirecat/gcal-cli`, the Google Calendar CLI) in the `all` parity group**, next to
151
+ tg, max, memo, drive, zm and wa, so its shared root commands are held to the same options and summaries. A run
152
+ without gcal's command tree skips it, as before; the weekly audit adds it once gcal is published on npm.
153
+ - **The command vocabulary has the words a calendar CLI needs.** Verbs `unsync` (stop syncing a thing; what is
154
+ stored stays), `review` (list what waits for the owner's decision) and `resolve` (settle one such thing with the
155
+ owner's choice); collections `calendars`, `events` and `people`. `events` was only a view word, so a command group
156
+ named `events` was reported by `unknown-resource`.
157
+ - **The vocabulary has the flags and the verb of calendar writes** (cli-core's `events create | update | delete |
158
+ respond`). Flags `--end-time`, `--start-date`, `--end-date`, `--location`, `--attendee` (repeat it for more),
159
+ `--id` (the id a create is made with) as `value`; `--calendar` and `--series` as `target`; `--no-notify` as `mode`.
160
+ `--notify` now means telling the people a change concerns — a chat's members or an event's attendees. Verb
161
+ `respond` (answer an invitation). Without them `option-role` and `unknown-verb` reported every calendar CLI's
162
+ write commands.
163
+ - **The vocabulary has the words of free/busy and repeating events** (cli-core's `calendars busy` and
164
+ `events create | update --repeat`). View `busy` (when calendars or people are taken, without what takes them);
165
+ flags `--repeat <rule>` (an RFC 5545 rule) as `value` and `--hours <from-to>` (the working hours to look in) as
166
+ `target`. gcal-cli overrides the four lint reports they cause until it takes this release.
167
+
168
+ - **`requestSchema(operation, definitions)` in `@wirecat/cli-base/codegen` builds the JSON Schema of a generated
169
+ operation's request body**, every referenced definition under `$defs`, or returns `null` when the operation takes
170
+ no body. drive-cli and gcal-cli each kept an identical copy for `api show`. Pass a manifest operation and the
171
+ `definitions` that `definitionsGenerator` wrote.
172
+
173
+ ### Changed — may break callers
174
+
175
+ - **A lint baseline entry or override for a word that is now in the vocabulary goes stale, and `cli-dev lint` fails
176
+ on it.** This release adds `unsync`, `review`, `resolve`, `calendars`, `events` and `people`. zoom-cli lists
177
+ `events` in `lint/baseline/unknown-resource.json`; gcal-cli lists `calendars`, `events`, `people`,
178
+ `calendars unsync`, `links review` and `links resolve` in its baseline; drive-cli overrides `unknown-verb` for
179
+ `comments resolve`, and cli-memo for `search people`, in `lint/overrides.json`. Remove those entries in the same
180
+ change that takes this version.
181
+ - **The calendar write words make gcal-cli's write baseline stale too.** Its `lint/baseline/option-role.json` (20
182
+ entries under `events create | update | delete | respond`) and `events respond` in `unknown-verb.json` go stale
183
+ with this release; remove them when gcal takes it. No other CLI's baseline or overrides on `main` name these words.
184
+ - **The free/busy and repeat words make four of gcal-cli's overrides stale.** `lint/overrides.json` names
185
+ `calendars busy` (`unknown-verb`), `calendars busy --hours` and `events create | update --repeat` (`option-role`);
186
+ remove them when gcal takes this release.
187
+
188
+ - **`describeAlone` is no longer exported from `@wirecat/cli-base/commands`.** It was made public by accident in
189
+ 0.62.0; it is a helper of the shared-command check and only works with a map kept for one check. No known caller
190
+ imports it. Describe a whole tree with `describeProgram` instead.
191
+
192
+ ### Fixed
193
+
194
+ - **Handbook corrections from gcal-cli's first-release audit.**
195
+ - The Toolchain, CI, Security, Testing and Store pages listed "the ten repositories" and "the six CLIs" without
196
+ gcal. They now say "the WireCat repositories": every WireCat CLI, a new one from its first commit, and the shared
197
+ repositories. `gcal-cli` is a known repository name for "Enforced by".
198
+ - WC-SECR-4 said the keyring service is the command's name. It is the CLI's app name (`max-cli`, `gcal-cli`,
199
+ `cli-memo`), as cli-core passes it.
200
+ - WC-REL-16 says what a CLI's package `files` holds: `dist`, `skills`, `README.md`, `LICENSE` and what the CLI
201
+ reads or runs after install (`scenarios`, `install`, notices); `CHANGELOG.md` may ship. Never `src/`, tests,
202
+ scripts, workflows, `bin/release`, `config/`, `lint/`, `contracts/` or `template.json`.
203
+ - WC-REL-11 names its one exception: a package's first version, published by the owner with `bin/release --local`,
204
+ since npm sets a trusted publisher only for a package that exists. Step 2 of
205
+ [New CLI](docs/standards/new-cli.md) says the same.
206
+ - [New CLI](docs/standards/new-cli.md) says which copied files a CLI rewrites (seeds) and which it leaves to
207
+ cli-template (template-owned), and that a template-owned page wrong for the CLI is fixed in cli-template.
208
+ - "Done when" told authors to add a new CLI's shared audit findings to `audit/baseline`, which has not accepted
209
+ them since cli-base 0.51.0. It now says to fix each one, or declare an exact difference in the parity group.
210
+ - WC-CI-8 says the fresh-agent audit is given the pinned cli-base workflows.
211
+ - WC-TEST-12 has one exception: commands generated from an API description share one reason that names an
212
+ options-against-the-snapshot test and a test that drives a generated command through `run()`.
213
+
214
+ - **`syncDiscovery` writes the same snapshot for the same revision of a Google API.** Google serves one revision with
215
+ its object keys in a different order on each download, so two syncs gave different files, hashes and generated
216
+ code. The snapshot is now the parsed document with its keys sorted and 2-space indentation; values and array order
217
+ are unchanged. `sha256` is stable; `sourceSha256` is still the hash of the bytes Google served, so it can change
218
+ between syncs. The first sync after upgrading rewrites each snapshot once, and `pnpm generate` then reorders the
219
+ generated types, schemas and manifest once.
220
+ - **`skill show` now names every agent `skill install` serves: Claude Code, Codex, Gemini CLI, OpenClaw, and Hermes
221
+ on request.** Its help named only the first three, so OpenClaw and Hermes users could not tell the skill was meant
222
+ for them.
223
+ - **The [CI](docs/standards/ci.md) page lists the monthly cron minutes WireCat repositories already use**, under
224
+ WC-CI-9, and [New CLI](docs/standards/new-cli.md) step 3 points to it. Finding a free minute meant reading
225
+ `monthly.yml` in every repository. A new CLI adds its minute to the list in the same pull request that joins the
226
+ parity groups.
227
+ - **The [Kit surface](docs/standards/kit-surface.md) page lists every cli-core and cli-base export cli-template
228
+ 0.7.0 imports**, among them the ones for MCP over HTTP: `mcpServerCommand`, `serveOverHttp`, `httpLockFile` and
229
+ the rest. The page left them out, so a change to them would not have been seen as one that reaches every new CLI.
230
+ - **[New CLI](docs/standards/new-cli.md) lets the fresh audit agent read the cli-base workflow files a CLI's
231
+ workflows pin.** WC-CI-8 tells an audit to read them, but the audit was limited to the handbook and the
232
+ repository. A release audit prompt kept in a CLI repository needs the same change.
233
+ - **WC-TEST-5 on the [Testing](docs/standards/testing.md) page says that a test may wait for a local process or
234
+ server it started**, polling with a timeout shorter than the test's own; a fixed sleep is still not allowed. It
235
+ was unclear whether such a wait counts as waiting for real time.
236
+ - **WC-CMD-6 on the [Commands](docs/standards/commands.md) page names `mcp --revoke` as an exception.** The rule says
237
+ an option never turns a command into a different action, and `--revoke` does: it forgets every browser login
238
+ instead of starting a server. The owner kept it an option on `mcp` because it belongs with the HTTP server's login
239
+ state. No other option gets this exception.
240
+ - **The handbook's examples start `gcal-cli` with the command `gcal`, not `cal`**, the system calendar program on macOS
241
+ and many Linux systems. A CLI named after it would shadow it.
242
+
11
243
  ## 0.63.0 — 27.10.2026
12
244
 
13
245
  ### Added
@@ -33,7 +33,7 @@ export interface DiscoveryProvenance {
33
33
  version: string;
34
34
  /** Of the committed file; `readDiscovery` refuses a snapshot that no longer matches it. */
35
35
  sha256: string;
36
- /** Of the bytes Google served. Equal to `sha256` unless the file was normalized after download. */
36
+ /** Of the bytes Google served. Google varies its key order, so this one can change while `sha256` does not. */
37
37
  sourceSha256: string;
38
38
  normalization: string;
39
39
  }
@@ -43,8 +43,8 @@ export interface SyncDiscoveryOptions {
43
43
  fetch?: typeof globalThis.fetch;
44
44
  }
45
45
  /**
46
- * Downloads each Discovery document byte for byte and records where it came from. Run by hand, then review
47
- * the diff: the build never fetches. `provenance.json` keeps the order of `sources`, and that order is
46
+ * Downloads each Discovery document, sorts its object keys so the same revision always gives the same file, and
47
+ * records where it came from. Run by hand, then review the diff: the build never fetches. `provenance.json` keeps the order of `sources`, and that order is
48
48
  * the order of the generated schemas.
49
49
  */
50
50
  export declare const syncDiscovery: (sources: Readonly<Record<string, string>>, options: SyncDiscoveryOptions) => Promise<Record<string, DiscoveryProvenance>>;
@@ -240,9 +240,18 @@ export const adaptDiscovery = (documents, options = {}) => {
240
240
  };
241
241
  };
242
242
  const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
243
+ /** Google serves the same revision with its keys in a different order each time; arrays keep theirs. */
244
+ const sortKeys = (value) => Array.isArray(value)
245
+ ? value.map(sortKeys)
246
+ : object(value)
247
+ ? Object.fromEntries(Object.keys(value)
248
+ .sort()
249
+ .map((key) => [key, sortKeys(value[key])]))
250
+ : value;
251
+ const NORMALIZATION = "object keys sorted, 2-space JSON; values and array order unchanged";
243
252
  /**
244
- * Downloads each Discovery document byte for byte and records where it came from. Run by hand, then review
245
- * the diff: the build never fetches. `provenance.json` keeps the order of `sources`, and that order is
253
+ * Downloads each Discovery document, sorts its object keys so the same revision always gives the same file, and
254
+ * records where it came from. Run by hand, then review the diff: the build never fetches. `provenance.json` keeps the order of `sources`, and that order is
246
255
  * the order of the generated schemas.
247
256
  */
248
257
  export const syncDiscovery = async (sources, options) => {
@@ -257,20 +266,20 @@ export const syncDiscovery = async (sources, options) => {
257
266
  const document = JSON.parse(raw);
258
267
  if (!object(document) || document.name !== service)
259
268
  throw new Error(`${source} is not the Discovery document of ${service}`);
260
- const hash = sha256(raw);
261
- downloaded[service] = raw;
269
+ const written = `${JSON.stringify(sortKeys(document), null, 2)}\n`;
270
+ downloaded[service] = written;
262
271
  provenance[service] = {
263
272
  source,
264
273
  revision: String(document.revision),
265
274
  version: String(document.version),
266
- sha256: hash,
267
- sourceSha256: hash,
268
- normalization: "none",
275
+ sha256: sha256(written),
276
+ sourceSha256: sha256(raw),
277
+ normalization: NORMALIZATION,
269
278
  };
270
279
  }
271
280
  mkdirSync(options.dir, { recursive: true });
272
- for (const [service, raw] of Object.entries(downloaded))
273
- writeFileSync(join(options.dir, `${service}.json`), raw);
281
+ for (const [service, text] of Object.entries(downloaded))
282
+ writeFileSync(join(options.dir, `${service}.json`), text);
274
283
  writeFileSync(join(options.dir, "provenance.json"), `${JSON.stringify(provenance, null, 2)}\n`);
275
284
  return provenance;
276
285
  };
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Turns an official API description into committed code: types, Valibot schemas, an operation
3
- * manifest and a coverage page. Build-time only, and format-neutral: it parses no file format and
3
+ * manifest and a coverage page. Build-time, apart from the small helpers a CLI reads the generated
4
+ * files with (`identifier`, `requestSchema`), and format-neutral: it parses no file format and
4
5
  * adds no dependency. `adaptOpenApi` reads an OpenAPI document and `adaptDiscovery` Google Discovery
5
6
  * documents the consumer has parsed; an adapter from Postman or anything else lives in the consumer.
6
7
  */
7
8
  export { adaptDiscovery, type DiscoveryMetadata, type DiscoveryOptions, type DiscoveryProvenance, type DiscoveryResult, type DiscoveryService, readDiscovery, type SyncDiscoveryOptions, syncDiscovery, } from "./discovery.js";
9
+ export { requestSchema } from "./json-schema.js";
8
10
  export { type CoverageOptions, coverageGenerator, definitionsGenerator, type ManifestOperation, manifestGenerator, } from "./manifest.js";
9
11
  export type { ApiBody, ApiModel, ApiOperation, ApiParameter, ApiSchema, Binding, CodeGenerator, Confidence, Discriminator, Effect, GeneratedArtifact, HttpBinding, HttpMethod, Override, RpcBinding, SchemaNode, SourceInfo, SourceReference, } from "./model.js";
10
12
  export { identifier, kebab } from "./names.js";
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Turns an official API description into committed code: types, Valibot schemas, an operation
3
- * manifest and a coverage page. Build-time only, and format-neutral: it parses no file format and
3
+ * manifest and a coverage page. Build-time, apart from the small helpers a CLI reads the generated
4
+ * files with (`identifier`, `requestSchema`), and format-neutral: it parses no file format and
4
5
  * adds no dependency. `adaptOpenApi` reads an OpenAPI document and `adaptDiscovery` Google Discovery
5
6
  * documents the consumer has parsed; an adapter from Postman or anything else lives in the consumer.
6
7
  */
7
8
  export { adaptDiscovery, readDiscovery, syncDiscovery, } from "./discovery.js";
9
+ export { requestSchema } from "./json-schema.js";
8
10
  export { coverageGenerator, definitionsGenerator, manifestGenerator, } from "./manifest.js";
9
11
  export { identifier, kebab } from "./names.js";
10
12
  export { adaptOpenApi, OpenApiError } from "./openapi.js";
@@ -0,0 +1,8 @@
1
+ import type { ManifestOperation } from "./manifest.js";
2
+ import type { SchemaNode } from "./model.js";
3
+ /**
4
+ * The JSON Schema (draft 2020-12) of an operation's request body, built from the `definitions` that
5
+ * `definitionsGenerator` wrote, with every referenced definition under `$defs`; `null` when the operation takes no
6
+ * body. For a CLI's `api show`, so an agent sees what to send before it calls.
7
+ */
8
+ export declare const requestSchema: (operation: Pick<ManifestOperation, "request">, definitions: Readonly<Record<string, SchemaNode>>) => Record<string, unknown> | null;
@@ -0,0 +1,78 @@
1
+ import { identifier } from "./names.js";
2
+ /**
3
+ * The JSON Schema (draft 2020-12) of an operation's request body, built from the `definitions` that
4
+ * `definitionsGenerator` wrote, with every referenced definition under `$defs`; `null` when the operation takes no
5
+ * body. For a CLI's `api show`, so an agent sees what to send before it calls.
6
+ */
7
+ export const requestSchema = (operation, definitions) => {
8
+ if (!operation.request?.schema)
9
+ return null;
10
+ const defs = {};
11
+ const visiting = new Set();
12
+ const node = (value) => {
13
+ const common = { ...(value.description ? { description: value.description } : {}) };
14
+ switch (value.type) {
15
+ case "ref": {
16
+ const name = identifier(value.ref);
17
+ add(name);
18
+ return { ...common, $ref: `#/$defs/${name}` };
19
+ }
20
+ case "unknown":
21
+ return common;
22
+ case "union":
23
+ return { ...common, anyOf: value.of.map(node) };
24
+ case "allOf":
25
+ return { ...common, allOf: value.of.map(node) };
26
+ case "object":
27
+ return {
28
+ ...common,
29
+ type: "object",
30
+ properties: Object.fromEntries(Object.entries(value.properties).map(([key, item]) => [key, node(item)])),
31
+ required: value.required,
32
+ additionalProperties: value.additionalProperties ? node(value.additionalProperties) : true,
33
+ };
34
+ case "array":
35
+ return {
36
+ ...common,
37
+ type: "array",
38
+ items: node(value.items),
39
+ ...(value.minItems === undefined ? {} : { minItems: value.minItems }),
40
+ ...(value.maxItems === undefined ? {} : { maxItems: value.maxItems }),
41
+ };
42
+ case "string":
43
+ return {
44
+ ...common,
45
+ type: "string",
46
+ ...(value.enum ? { enum: value.enum } : {}),
47
+ ...(value.pattern ? { pattern: value.pattern } : {}),
48
+ ...(value.minLength === undefined ? {} : { minLength: value.minLength }),
49
+ ...(value.maxLength === undefined ? {} : { maxLength: value.maxLength }),
50
+ };
51
+ case "boolean":
52
+ return { ...common, type: "boolean" };
53
+ case "integer":
54
+ case "number":
55
+ return {
56
+ ...common,
57
+ type: value.type,
58
+ ...(value.minimum === undefined ? {} : { minimum: value.minimum }),
59
+ ...(value.maximum === undefined ? {} : { maximum: value.maximum }),
60
+ };
61
+ }
62
+ };
63
+ const add = (name) => {
64
+ if (visiting.has(name))
65
+ return;
66
+ visiting.add(name);
67
+ const value = definitions[name];
68
+ if (!value)
69
+ throw new Error(`generated schema reference is missing: ${name}`);
70
+ defs[name] = node(value);
71
+ };
72
+ add(operation.request.schema);
73
+ return {
74
+ $schema: "https://json-schema.org/draft/2020-12/schema",
75
+ $ref: `#/$defs/${operation.request.schema}`,
76
+ $defs: defs,
77
+ };
78
+ };
@@ -12,7 +12,7 @@
12
12
  import type { Command } from "commander";
13
13
  export { type CommandsDocument, type CommandsDocumentAbout, commandsDocument, commandsDocumentJsonSchema, commandsDocumentSchema, parseCommandsDocument, } from "./document.js";
14
14
  export { COMMANDS_PAGE_LABELS, type CommandsPage, type CommandsPageLabels, type CommandsPageText, commandsPage, } from "./page.js";
15
- export * from "./registry.js";
15
+ export { type ArgumentInfo, annotate, annotateOption, type CommandInfo, type CommandLifetime, type CommandMeta, type CommandOutput, type CommandReach, type CommandState, describeOptions, describeProgram, flatten, metaOf, type OptionInfo, type OptionMeta, type Origin, optionMetaOf, stepTargets, } from "./registry.js";
16
16
  /**
17
17
  * Refuses a command path that arrived as one argument — `"projects add" --help`, what a zsh loop over
18
18
  * `$c` passes. Commander settles `--help` for an unknown word before any hook runs and prints the root
@@ -1,7 +1,7 @@
1
1
  import { CliError } from "../errors.js";
2
2
  export { commandsDocument, commandsDocumentJsonSchema, commandsDocumentSchema, parseCommandsDocument, } from "./document.js";
3
3
  export { COMMANDS_PAGE_LABELS, commandsPage, } from "./page.js";
4
- export * from "./registry.js";
4
+ export { annotate, annotateOption, describeOptions, describeProgram, flatten, metaOf, optionMetaOf, stepTargets, } from "./registry.js";
5
5
  /**
6
6
  * Refuses a command path that arrived as one argument — `"projects add" --help`, what a zsh loop over
7
7
  * `$c` passes. Commander settles `--help` for an unknown word before any hook runs and prints the root
@@ -9,7 +9,7 @@ export interface StoredCredential {
9
9
  export interface CredentialsOptions {
10
10
  /** Where the fallback file lives when the keyring cannot be used. */
11
11
  configDir: string;
12
- /** The keyring service name — the command's name, e.g. `max-cli`. */
12
+ /** The keyring service name — the CLI's app name, e.g. `max-cli`, not its command word. */
13
13
  service: string;
14
14
  /**
15
15
  * **True when the config directory came from the environment rather than the OS convention.**
@@ -0,0 +1,31 @@
1
+ export interface BundleOptions {
2
+ /** The CLI's root: `dist/` and `package.json` are under it. */
3
+ root: string;
4
+ /** The compiled bin, relative to `root`, e.g. `dist/bin/tg.js`. It becomes the loader. */
5
+ bin: string;
6
+ /** Packages that stay outside the bundle, besides `DEFAULT_EXTERNAL`. `*` matches anything. */
7
+ external?: readonly string[];
8
+ /** Packages whose modules keep their installed place, besides `DEFAULT_ANCHORED`. */
9
+ anchored?: readonly string[];
10
+ /** Third-party files read and found harmless, besides `DEFAULT_HARMLESS` (a path suffix each). */
11
+ harmless?: readonly string[];
12
+ }
13
+ export interface BundleResult {
14
+ problems: string[];
15
+ /** The chunks a server preloads, as the loader names them. */
16
+ chunks: string[];
17
+ }
18
+ /** Native modules, Bun's SQLite, and what cli-core loads only by name or starts as a worker from its own file. */
19
+ export declare const DEFAULT_EXTERNAL: string[];
20
+ export declare const DEFAULT_ANCHORED: string[];
21
+ export declare const DEFAULT_HARMLESS: string[];
22
+ export declare const LOADER_MARK = "// cli-dev bundle loader\n";
23
+ /**
24
+ * Whether the loader runs a server: `serve`, `watch` or `mcp serve`, or `mcp` alone as before cli-core 0.463. Flags
25
+ * are skipped, so `<cli> personal --read-only mcp serve` counts; four words leave room for one flag's value. It is
26
+ * written into the loader as source, so it uses nothing from outside.
27
+ */
28
+ export declare const isServer: (words: readonly string[]) => boolean;
29
+ /** The bin that replaces the compiled one: `name` is the bundle's entry, `chunks` what a server preloads. */
30
+ export declare const loaderSource: (name: string, version: string, chunks: readonly string[]) => string;
31
+ export declare const bundle: (options: BundleOptions) => Promise<BundleResult>;