@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.
- package/CHANGELOG.md +232 -0
- package/dist/codegen/discovery.d.ts +3 -3
- package/dist/codegen/discovery.js +18 -9
- package/dist/codegen/index.d.ts +3 -1
- package/dist/codegen/index.js +3 -1
- package/dist/codegen/json-schema.d.ts +8 -0
- package/dist/codegen/json-schema.js +78 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/dev/bundle.d.ts +31 -0
- package/dist/dev/bundle.js +280 -0
- package/dist/dev/main.js +48 -1
- package/dist/skill/index.d.ts +1 -1
- package/dist/skill/index.js +2 -2
- package/dist/standards/audit.js +8 -0
- package/dist/standards/flags-calendar.d.ts +2 -0
- package/dist/standards/flags-calendar.js +72 -0
- package/dist/standards/flags-how.js +7 -1
- package/dist/standards/flags-what.js +7 -0
- package/dist/standards/vocabulary.js +13 -2
- package/docs/standards/README.md +5 -5
- package/docs/standards/accounts.md +44 -11
- package/docs/standards/architecture.md +19 -12
- package/docs/standards/ci.md +33 -12
- package/docs/standards/commands.md +35 -10
- package/docs/standards/data-model.md +5 -1
- package/docs/standards/docs.md +4 -1
- package/docs/standards/kit-surface.md +6 -5
- package/docs/standards/layout.md +32 -0
- package/docs/standards/lifecycle.md +69 -8
- package/docs/standards/mcp.md +48 -24
- package/docs/standards/new-cli.md +30 -16
- package/docs/standards/output.md +13 -2
- package/docs/standards/permissions.md +46 -3
- package/docs/standards/releases.md +21 -5
- package/docs/standards/security.md +16 -8
- package/docs/standards/store.md +4 -3
- package/docs/standards/testing.md +19 -8
- package/docs/standards/time.md +8 -1
- package/docs/standards/toolchain.md +24 -18
- package/package.json +6 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Scope
|
|
4
4
|
|
|
5
5
|
How a CLI chooses an account: the profile is always explicit, a read across accounts covers all of them and says so,
|
|
6
|
-
|
|
6
|
+
scope flags work the same everywhere, and sign-in has the same four commands in every CLI.
|
|
7
7
|
|
|
8
8
|
A **profile** is the name of one login of a CLI on one computer, for example `personal` or `work`. One profile can
|
|
9
9
|
hold a personal session, a bot token, or both.
|
|
@@ -16,7 +16,8 @@ Rule ids on this page are `WC-ACCT-<n>`. How to read a rule, how ids work and wh
|
|
|
16
16
|
cli-core resolves the profile for tg, max and wa. WC-ACCT-1 needs cli-core 0.319.0 or newer. memo has no messenger profile: its shared commands run without one, and they pick accounts with `--provider`
|
|
17
17
|
and `--account` (WC-ACCT-7). zoom resolves its own profile word by the same rules (`zm <profile> …`, `ZM_PROFILE`,
|
|
18
18
|
`ZM_PROFILE_LOCK`) from zoom-cli 0.35.0, with cli-core's `liftProfile` and `usableProfileName`. drive still has its own
|
|
19
|
-
profile code: it registers its own profiles. That is a known gap, not an exception.
|
|
19
|
+
profile code: it registers its own profiles. That is a known gap, not an exception. gcal resolves the profile through
|
|
20
|
+
cli-core's `run()`, like tg, max and wa. Which CLIs lack a sign-in command of WC-ACCT-12 is listed in that rule.
|
|
20
21
|
|
|
21
22
|
## Rules
|
|
22
23
|
|
|
@@ -31,9 +32,11 @@ on the computer.
|
|
|
31
32
|
- **Example:** `tg personal chats list`, or `TG_PROFILE=personal tg chats list`. When no profile is named and the
|
|
32
33
|
config file still has `default_profile`, cli-core stops and says which profile to type. `config set default_profile`
|
|
33
34
|
is refused.
|
|
34
|
-
- **Exceptions:** a command that touches no account runs without a profile, for example `
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
- **Exceptions:** a command that touches no account runs without a profile, for example `session list`
|
|
36
|
+
([WC-ACCT-12](#wc-acct-12--sign-in-is-session-start-status-and-end-and-session-list-names-the-profiles)),
|
|
37
|
+
`config show` and `commands`; tg, max and wa still name their list `account list`. `mcp serve` may name its profiles with `--profiles <names|all>` instead
|
|
38
|
+
([WC-MCP-9](mcp.md#wc-mcp-9--a-server-serves-the-profiles-it-is-told-with-one-connection-for-each)); `all` is the owner's explicit choice, never a fallback.
|
|
39
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-84, NEED-1881.
|
|
37
40
|
|
|
38
41
|
### WC-ACCT-2 · The profile is a word, never a flag
|
|
39
42
|
|
|
@@ -44,8 +47,9 @@ profile.
|
|
|
44
47
|
- **Enforced by:** `cli-core: src/cli/profile.test.ts`.
|
|
45
48
|
- **Example:** `max work bot messages send …` uses the same first word as `max work chats list`. `tg --json chats list`
|
|
46
49
|
still works, because `--json` is an option.
|
|
47
|
-
- **Exceptions:**
|
|
48
|
-
|
|
50
|
+
- **Exceptions:** `mcp serve --profiles <names|all>` names the profiles one server serves ([WC-MCP-9](mcp.md#wc-mcp-9--a-server-serves-the-profiles-it-is-told-with-one-connection-for-each)). It is used in
|
|
51
|
+
place of the first word, never with it.
|
|
52
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-84, NEED-294, NEED-1881.
|
|
49
53
|
|
|
50
54
|
### WC-ACCT-3 · A missing profile is an error that lists the choices
|
|
51
55
|
|
|
@@ -57,8 +61,8 @@ CLI with a `setup` command suggests it, and any other CLI suggests the command t
|
|
|
57
61
|
- **Enforced by:** `cli-core: src/cli/settings.test.ts` · `cli-core: src/cli/program.test.ts`.
|
|
58
62
|
- **Example:** ``name a profile: `tg <profile> …` — profiles here: personal, work (or set TG_PROFILE)``. With none,
|
|
59
63
|
tg, which has `setup`, says to ``create one with `tg personal setup` (any name in place of personal)``. A CLI
|
|
60
|
-
without `setup`, such as one made from cli-template, answers `
|
|
61
|
-
on this computer yet — any name works, such as `
|
|
64
|
+
without `setup`, such as one made from cli-template, answers `gcal events list` with ``name a profile: there is none
|
|
65
|
+
on this computer yet — any name works, such as `gcal personal events list` (or set GCAL_PROFILE)`` (cli-core 0.424.0
|
|
62
66
|
and later).
|
|
63
67
|
- **Exceptions:** none.
|
|
64
68
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
@@ -78,8 +82,8 @@ top-level command, such as `chats` or `bot`, is refused when the profile is crea
|
|
|
78
82
|
|
|
79
83
|
### WC-ACCT-5 · A profile lock pins a process to one profile
|
|
80
84
|
|
|
81
|
-
When `<PREFIX>_PROFILE_LOCK` is set, a first word
|
|
82
|
-
`permission_error`.
|
|
85
|
+
When `<PREFIX>_PROFILE_LOCK` is set, a first word, `<PREFIX>_PROFILE` or `mcp serve --profiles` that names any other
|
|
86
|
+
profile is refused with `permission_error`. `--profiles all` names every profile, so it is refused too.
|
|
83
87
|
|
|
84
88
|
- **Why:** an agent must not escape its guards by naming a profile with fewer of them.
|
|
85
89
|
- **Enforced by:** `cli-core: src/cli/settings.test.ts`.
|
|
@@ -155,6 +159,35 @@ inferred from a matching name, and `unlink` undoes it.
|
|
|
155
159
|
- **Exceptions:** none.
|
|
156
160
|
- **Applies to:** all CLIs. **Since:** cli-base 0.43.1. **Rulings:** none.
|
|
157
161
|
|
|
162
|
+
### WC-ACCT-12 · Sign-in is `session start`, `status` and `end`, and `session list` names the profiles
|
|
163
|
+
|
|
164
|
+
Every CLI that signs in has four commands under `session`:
|
|
165
|
+
|
|
166
|
+
- `<cli> <profile> session start` signs the profile in.
|
|
167
|
+
- `<cli> <profile> session status` says whether this profile is signed in, and as whom.
|
|
168
|
+
- `<cli> <profile> session end` signs the profile out and forgets its login.
|
|
169
|
+
- `<cli> session list` takes no profile and lists the profiles on this computer.
|
|
170
|
+
|
|
171
|
+
`status` answers about one profile; `list` answers which profiles exist. Neither one connects to more than it must:
|
|
172
|
+
`list` reads only what the computer holds.
|
|
173
|
+
|
|
174
|
+
- **Why:** the owner and an agent look for sign-in in the same place in every CLI. Today each CLI has a different
|
|
175
|
+
part of the set, so an agent that learned one CLI guesses wrong in the next.
|
|
176
|
+
- **Enforced by:** review only.
|
|
177
|
+
- **Example:** `gcal personal session status` answers whether the `personal` profile holds a Google login;
|
|
178
|
+
`gcal session list` lists `personal` and `work` with no profile named.
|
|
179
|
+
- **Exceptions:** known gaps, as of 2026-10-27:
|
|
180
|
+
- tg, max and wa have `session start` and `session end`, no `session status`, and list their profiles with
|
|
181
|
+
`account list` instead of `session list`.
|
|
182
|
+
- drive has `session start`, `session list` and `session end`, and no `session status`.
|
|
183
|
+
- gcal has `session start`, `session status` and `session end`, and no `session list`.
|
|
184
|
+
- zm has `session start` and `session status`, and no `session end` or `session list`.
|
|
185
|
+
- memo signs in per mail account with `mail add` and has no `session` group.
|
|
186
|
+
|
|
187
|
+
A CLI that adds a missing command adds it under `session`; a renamed path keeps no alias
|
|
188
|
+
([WC-CMD-8](commands.md#wc-cmd-8--keep-no-aliases-a-renamed-path-stops-working-in-the-release-that-renames-it)).
|
|
189
|
+
- **Applies to:** every CLI that signs in. **Since:** cli-base 0.65.0. **Rulings:** NEED-1884.
|
|
190
|
+
|
|
158
191
|
## Sources
|
|
159
192
|
|
|
160
193
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -20,7 +20,9 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
20
20
|
- **Service:** a use case, written once, that both a command and an MCP tool call.
|
|
21
21
|
- **Composition root:** the place in a CLI where real implementations are chosen and connected, usually
|
|
22
22
|
`program.ts` and the command context.
|
|
23
|
-
- **Vendor type:** a type from a provider library, such as `@mtcute/*` or `ws
|
|
23
|
+
- **Vendor type:** a type from a provider library, such as `@mtcute/*` or `ws`, and a type generated from a
|
|
24
|
+
provider's API description, such as gcal's and drive's types under `src/generated/` from a Google Discovery
|
|
25
|
+
document: both change when the provider changes, not when we do.
|
|
24
26
|
|
|
25
27
|
## Rules
|
|
26
28
|
|
|
@@ -57,7 +59,9 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
57
59
|
`src/adapter/` maps the client onto the shared messenger port. A field that only one provider has travels in
|
|
58
60
|
`providerMetadata`.
|
|
59
61
|
- **Exceptions:** a bot slice that talks to an official HTTP API keeps its own transport, beside the personal
|
|
60
|
-
account's adapter, never above it.
|
|
62
|
+
account's adapter, never above it. Generated API types may also be used by the generated `api <method>` commands,
|
|
63
|
+
which are the provider's own API by design; the shared ports and services never see them. In gcal,
|
|
64
|
+
`calendar-source.ts` and `calendar-writer.ts` translate the generated event type into cli-core's calendar entry.
|
|
61
65
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-12.
|
|
62
66
|
|
|
63
67
|
### WC-ARCH-4 · Put code in the lowest shared package that can hold it
|
|
@@ -77,14 +81,16 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
77
81
|
|
|
78
82
|
### WC-ARCH-5 · Build a feature that another provider could have as a shared command behind a capability
|
|
79
83
|
|
|
80
|
-
- **Why:** a command written once in cli-core gives every
|
|
84
|
+
- **Why:** a command written once in cli-core gives every provider of a kind the same path, flags and output.
|
|
81
85
|
- **Enforced by:** review only.
|
|
82
86
|
- **Example:** topics and polls are port groups and shared commands in cli-core. A messenger that lacks a capability
|
|
83
87
|
refuses the command; it does not silently do less. A bot is another messenger with fewer capabilities, and tg and
|
|
84
|
-
max share the bot code.
|
|
85
|
-
|
|
86
|
-
or
|
|
87
|
-
- **
|
|
88
|
+
max share the bot code. Calendars have the same shape: cli-core's `addCalendarCommands` builds `calendars`,
|
|
89
|
+
`sync`, `status`, `events` and `search` over the `CalendarSource` and `CalendarWriter` ports, and mounts
|
|
90
|
+
`calendars busy` or the event writes only when the provider has them.
|
|
91
|
+
- **Exceptions:** a feature stays in one CLI only when the other providers cannot have it, such as MAX's socket server,
|
|
92
|
+
Telegram's app registration, or gcal's generated `api` commands.
|
|
93
|
+
- **Applies to:** messenger CLIs (tg, max, wa) and calendar CLIs (gcal). **Since:** cli-base 0.41.0. **Rulings:** NEED-820, NEED-440,
|
|
88
94
|
NEED-301, NEED-444.
|
|
89
95
|
|
|
90
96
|
### WC-ARCH-6 · Replace a use case, not a command
|
|
@@ -93,9 +99,10 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
93
99
|
- **Enforced by:** review only.
|
|
94
100
|
- **Example:** `Messenger.services` receives the shared services and returns only the ones it changes:
|
|
95
101
|
`services: (base) => ({ messages: { ...base.messages, list: maxList } })`. To add a subcommand, a CLI calls
|
|
96
|
-
`addCommand` on the command a factory returns.
|
|
102
|
+
`addCommand` on the command a factory returns. A calendar CLI changes what the shared calendar commands do through
|
|
103
|
+
the source and writer it passes to `addCalendarCommands`, never by writing its own `sync` or `events list`.
|
|
97
104
|
- **Exceptions:** none.
|
|
98
|
-
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
105
|
+
- **Applies to:** messenger CLIs and calendar CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
99
106
|
|
|
100
107
|
### WC-ARCH-7 · Keep policy in the service; a command or MCP handler only parses input and presents the result
|
|
101
108
|
|
|
@@ -215,9 +222,9 @@ out of search and listed under `skipped`, with the hint to run that CLI once.
|
|
|
215
222
|
- **Why:** a new source joins search and memo without editing cli-core, and a provider nobody described is never read
|
|
216
223
|
as something it is not (until this rule, an unlisted provider was searched as a messenger).
|
|
217
224
|
- **Enforced by:** `cli-core: src/store/sqlite/providers.test.ts`.
|
|
218
|
-
- **Example:**
|
|
219
|
-
time it
|
|
220
|
-
`skipped` instead of reading their rows as messages.
|
|
225
|
+
- **Example:** gcal registers `{ name: "google-calendar", kind: "events", aliases: ["gcal", "calendar"], command:
|
|
226
|
+
"gcal" }` each time it opens the store. On a machine where gcal has never run, a search across accounts lists
|
|
227
|
+
gcal's accounts under `skipped` instead of reading their rows as messages.
|
|
221
228
|
- **Exceptions:** the built-in providers of cli-core need no entry. Two entries that disagree on a kind are an error
|
|
222
229
|
naming both.
|
|
223
230
|
- **Applies to:** cli-core and every CLI with a provider of its own. **Since:** cli-base 0.53.0. **Rulings:** none
|
package/docs/standards/ci.md
CHANGED
|
@@ -10,8 +10,9 @@ Rule ids on this page are `WC-CI-<n>`. How to read a rule, how ids work and whic
|
|
|
10
10
|
[Releases page](releases.md); which scanners must exist and how workflows are hardened is on the
|
|
11
11
|
[Security page](security.md).
|
|
12
12
|
|
|
13
|
-
"The
|
|
14
|
-
|
|
13
|
+
"The WireCat repositories" on this page means every WireCat CLI (today tg, max, memo, drive, zoom, wa and gcal; a
|
|
14
|
+
CLI made with `npx @wirecat/cli-template new` counts from its first commit), cli-base, cli-core, cli-testing, cli-docs,
|
|
15
|
+
plus community itself. cli-template follows every rule here as a CLI does
|
|
15
16
|
([New CLI](new-cli.md#the-template-repository-itself)).
|
|
16
17
|
|
|
17
18
|
## Rules
|
|
@@ -24,7 +25,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
24
25
|
`gitleaks git --staged --redact`. A clone without gitleaks gets a warning and can still commit; CI scans anyway.
|
|
25
26
|
The `prepare` script installs the hooks; `LEFTHOOK=0 git commit` skips them for one commit.
|
|
26
27
|
- **Exceptions:** none.
|
|
27
|
-
- **Applies to:** the
|
|
28
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
28
29
|
|
|
29
30
|
### WC-CI-2 · Add no push hook, and no hook that installs, builds, checks types, generates or tests
|
|
30
31
|
|
|
@@ -34,7 +35,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
34
35
|
does not run `standards:check`, the full lint, spelling, a build or the test suite on each commit or push; it runs a
|
|
35
36
|
targeted check when it needs one.
|
|
36
37
|
- **Exceptions:** none. cli-base `config/lefthook.yml` still has a pre-push typecheck, but no repository extends it.
|
|
37
|
-
- **Applies to:** the
|
|
38
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
38
39
|
|
|
39
40
|
### WC-CI-3 · Run the fast standards job on every pull request and every push to `main`
|
|
40
41
|
|
|
@@ -45,7 +46,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
45
46
|
commit cancels the run it replaces. The job times out after five minutes, a ceiling for a stalled setup, not an
|
|
46
47
|
expected time.
|
|
47
48
|
- **Exceptions:** none. Runner queues and cold downloads are outside the budget.
|
|
48
|
-
- **Applies to:** the
|
|
49
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
49
50
|
|
|
50
51
|
### WC-CI-4 · Keep `ci:quick` to early bug checks that finish within two minutes
|
|
51
52
|
|
|
@@ -61,7 +62,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
61
62
|
- **Exceptions:** never put live-account suites, agent suites or platform jobs here. Full tests and coverage run in
|
|
62
63
|
the pull request validation job ([WC-CI-5](#wc-ci-5--run-the-full-linux-validation-on-every-pull-request-and-keep-platform-suites-out)),
|
|
63
64
|
not in `ci:quick`.
|
|
64
|
-
- **Applies to:** the
|
|
65
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
65
66
|
|
|
66
67
|
### WC-CI-5 · Run the full Linux validation on every pull request, and keep platform suites out
|
|
67
68
|
|
|
@@ -139,10 +140,13 @@ cannot pass yet; the `skip` input names it.
|
|
|
139
140
|
|
|
140
141
|
The shared workflows are not in the npm package, because CI runs the pinned commit, not the installed copy. To see
|
|
141
142
|
what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/…@<commit>` lines in `ci.yml`,
|
|
142
|
-
`release.yml` and `monthly.yml`, and opens each workflow file at that commit in cli-base.
|
|
143
|
+
`release.yml` and `monthly.yml`, and opens each workflow file at that commit in cli-base. The fresh-agent audit on the
|
|
144
|
+
[New CLI](new-cli.md#done-when) page is given these files for this reason, beside the handbook and the repository;
|
|
145
|
+
its prompt in cli-template says so from cli-template 0.8.0. A CLI on an older template takes the prompt with
|
|
146
|
+
`upgrade`.
|
|
143
147
|
|
|
144
148
|
- **Why:** one shared workflow keeps the full suite the same everywhere, and a pin keeps a cli-base change from
|
|
145
|
-
breaking
|
|
149
|
+
breaking every repository at once.
|
|
146
150
|
- **Enforced by:** `ci.yml:ci`.
|
|
147
151
|
- **Example:** `uses: WireCatLabs/cli-base/.github/workflows/node-ci.yml@<commit> # v0.31.0`, with `cli: true` for
|
|
148
152
|
the CLI steps (WC-CI-7), the project's extra checks in the `checks` input and `bun-run` for the Bun job. A job that only one repository needs stays beside it in
|
|
@@ -158,12 +162,29 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
158
162
|
- **Example:** `monthly.yml`, from the community template, calls the repository's `ci.yml` and the pinned cli-base
|
|
159
163
|
`release-checks.yml`. Each repository has its own minute, between 02:17 and 03:20 UTC, so runs do not overlap. No
|
|
160
164
|
check compares the minutes across repositories; review keeps them apart. A CLI made from cli-template starts with
|
|
161
|
-
the template's own minute and changes it (see [New CLI](new-cli.md)).
|
|
165
|
+
the template's own minute and changes it (see [New CLI](new-cli.md)). Taken minutes, from each `monthly.yml` on
|
|
166
|
+
the default branch:
|
|
167
|
+
|
|
168
|
+
| UTC | Repository |
|
|
169
|
+
|---|---|
|
|
170
|
+
| 02:17 | cli-base |
|
|
171
|
+
| 02:24 | whatsapp-cli |
|
|
172
|
+
| 02:27 | gcal-cli |
|
|
173
|
+
| 02:31 | cli-memo |
|
|
174
|
+
| 02:38 | cli-core |
|
|
175
|
+
| 02:45 | drive-cli |
|
|
176
|
+
| 02:52 | cli-testing |
|
|
177
|
+
| 02:59 | max-cli |
|
|
178
|
+
| 03:02 | cli-template |
|
|
179
|
+
| 03:06 | tg-cli |
|
|
180
|
+
| 03:13 | zoom-cli |
|
|
181
|
+
| 03:20 | cli-docs |
|
|
182
|
+
|
|
162
183
|
- **Exceptions:** cli-core also builds and probes its native packages in their own workflows, after the window:
|
|
163
184
|
`onnx.yml` at 03:27 and `sqlite.yml` at 03:37 UTC. A scheduled run of either never publishes; only a manual run
|
|
164
185
|
with `publish` does. community has no build, so it runs only the release security checks, at 03:47 UTC. A monthly
|
|
165
186
|
run never publishes a package or deploys the site.
|
|
166
|
-
- **Applies to:** the
|
|
187
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
167
188
|
|
|
168
189
|
### WC-CI-10 · Run heavy security checks only at release, monthly or on request
|
|
169
190
|
|
|
@@ -177,7 +198,7 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
177
198
|
job ([WC-CI-5](#wc-ci-5--run-the-full-linux-validation-on-every-pull-request-and-keep-platform-suites-out)); it
|
|
178
199
|
takes seconds. The dependency audit, `zizmor` and CodeQL stay out. Do not add a heavy scanner to an editor hook, a
|
|
179
200
|
stop hook or an ordinary pull request job.
|
|
180
|
-
- **Applies to:** the
|
|
201
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** sc:9.
|
|
181
202
|
|
|
182
203
|
### WC-CI-11 · Moved to [WC-SECR-20](security.md#wc-secr-20--pin-each-third-party-action-to-a-commit-and-give-each-workflow-read-only-defaults)
|
|
183
204
|
|
|
@@ -195,7 +216,7 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
195
216
|
release changes `main` the same way
|
|
196
217
|
([WC-REL-2](releases.md#wc-rel-2--main-changes-only-through-a-pull-request-also-during-a-release)).
|
|
197
218
|
- **Exceptions:** none.
|
|
198
|
-
- **Applies to:** the
|
|
219
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** NEED-381.
|
|
199
220
|
|
|
200
221
|
### WC-CI-13 · Start no live messenger or account test from a workflow
|
|
201
222
|
|
|
@@ -75,10 +75,12 @@ catalogue and parity manifest stay in cli-core
|
|
|
75
75
|
- **Why:** an option that turns a command into a different report hides that report from anyone reading the tree.
|
|
76
76
|
- **Enforced by:** review only.
|
|
77
77
|
- **Example:** ranking is `stats messages top`, not a ranking mode on `search messages`. Options choose the scope,
|
|
78
|
-
measure, order or format of the same action. No command both shows and changes something
|
|
78
|
+
measure, order or format of the same action. No command both shows and changes something; saving what it read
|
|
79
|
+
into the local store is not a change ([WC-CMD-17](#wc-cmd-17--declare-effects-on-every-command)).
|
|
79
80
|
- **Exceptions:** a documented preparatory fetch such as `--sync-first`. It keeps its own permissions and reports
|
|
80
81
|
its outcome.
|
|
81
|
-
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:**
|
|
82
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1880 (`mcp revoke` is its own command; it
|
|
83
|
+
replaces NEED-1864, which kept `--revoke` as an option on `mcp`).
|
|
82
84
|
|
|
83
85
|
### WC-CMD-7 · Never let two commands overlap
|
|
84
86
|
|
|
@@ -116,9 +118,16 @@ catalogue and parity manifest stay in cli-core
|
|
|
116
118
|
- **Why:** `--limit` must mean "how many items" in every command of every CLI.
|
|
117
119
|
- **Enforced by:** `flag-synonym` · `flag-two-meanings` · `same-flag-meaning` (weekly audit; it does not block a pull
|
|
118
120
|
request yet).
|
|
119
|
-
- **Example:** `--limit <n>`, never `--max`, `--take` or `--count`. `--cursor <cursor>`, never `--offset`. How
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
- **Example:** `--limit <n>`, never `--max`, `--take` or `--count`. `--cursor <cursor>`, never `--offset`. How big
|
|
122
|
+
the answer is: `--limit`, `--page` and `--page-size`; a count of pages is never a paging option. How much work a
|
|
123
|
+
command may do is a `budget` option: `--max-pages <pages>` caps the pages a sync reads from the source, and
|
|
124
|
+
`--max-output-bytes` the size of the answer. A budget changes how far the command gets, never which items one page
|
|
125
|
+
of the answer holds. A retry after `outcome_unknown` is `--operation-id`: the attempt in the send journal. The
|
|
126
|
+
calendar vocabulary's `--id <id>` is a different value: the provider's own id for a thing being created, chosen
|
|
127
|
+
before the create, so a retry finds it at the provider. A CLI whose provider takes such an id may offer both.
|
|
128
|
+
- **Exceptions:** generated commands (`bot api`) are exempt from the naming rules. Known gap, as of 2026-10-27: gcal's
|
|
129
|
+
`events create` offers `--id` and no `--operation-id`, so its `outcome_unknown` names the event id instead of an
|
|
130
|
+
`operation_id` (WC-LIFE-9, WC-OUT-10).
|
|
122
131
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
123
132
|
|
|
124
133
|
### WC-CMD-11 · Name an option in plain words, with the kind of value in the name
|
|
@@ -128,7 +137,10 @@ catalogue and parity manifest stay in cli-core
|
|
|
128
137
|
- **Example:** `--operation-id`, not `--cid`. `--before-id` takes a message id, `--after-time` a time, `--before-n` a
|
|
129
138
|
count: two kinds are two options. A length of time is a `<duration>` with a unit (`500ms`, `30s`, `2m`, `4h`, `1d`),
|
|
130
139
|
never a bare number.
|
|
131
|
-
- **Exceptions:**
|
|
140
|
+
- **Exceptions:** `--duration-minutes <minutes>` (zm's meeting length, gcal's `calendars busy`) takes a bare count of
|
|
141
|
+
minutes. It is a known gap in the vocabulary, as of 2026-10-27: it stays until the vocabulary has
|
|
142
|
+
`--duration <duration>`, and then moves in the release that adds it, under "Changed — may break callers". No new
|
|
143
|
+
option follows it.
|
|
132
144
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
133
145
|
|
|
134
146
|
### WC-CMD-12 · Give every option a role from the vocabulary, and filter a list with its query
|
|
@@ -187,13 +199,25 @@ Each of these is set with `annotate(command, { … })` from `@wirecat/cli-base/c
|
|
|
187
199
|
|
|
188
200
|
### WC-CMD-17 · Declare `effects` on every command
|
|
189
201
|
|
|
202
|
+
A command **writes** when it changes something on the service (`remote: "write"`), or when it changes the local
|
|
203
|
+
store or files without reading the service first (`remote: "none"`, `local: "write"`, such as `store reindex` or
|
|
204
|
+
`config set`). A command that reads the service and only saves what it read into the owner's local store
|
|
205
|
+
(`{ remote: "read", local: "write" }`, such as `sync` or `calendars list`) is a **read**: on the command line, in
|
|
206
|
+
[WC-CMD-6](#wc-cmd-6--give-one-action-or-one-report-one-leaf-command), over MCP
|
|
207
|
+
([WC-MCP-4](mcp.md#wc-mcp-4--read-runs-only-reading-commands-and-write-only-writing-ones)) and as a next step
|
|
208
|
+
([WC-OUT-9](output.md#wc-out-9--offer-at-most-two-next-steps-and-mark-a-step-safe-only-when-it-changes-nothing-on-the-service)).
|
|
209
|
+
|
|
190
210
|
- **Why:** retries and unasked next steps are judged on effects, and a command without them counts as a write.
|
|
191
211
|
- **Enforced by:** `missing-command-effects` · `cli-core: src/cli/effects.test.ts` · `cli-memo: src/effects.test.ts`.
|
|
192
212
|
- **Example:** `annotate(send, { effects: { remote: "write", local: "write" } })`. Each side is `none`, `read` or
|
|
193
213
|
`write`. A sync that reads the service and writes the local store is `{ remote: "read", local: "write" }`.
|
|
194
214
|
- **Exceptions:** none. Without `effects`, the error handler never marks a failure safe to retry, and a next step
|
|
195
|
-
that runs the command is never safe to run unasked.
|
|
196
|
-
|
|
215
|
+
that runs the command is never safe to run unasked. Known gap, as of 2026-10-27: cli-base's `writes()`
|
|
216
|
+
(`src/standards/effects.ts`) still counts every local write as a write, so cli-core's MCP router serves `sync` and
|
|
217
|
+
`calendars list` through the `write` tool. The follow-up makes `writes()` true for `remote: "write"`, or for
|
|
218
|
+
`local: "write"` when `remote` is `none`.
|
|
219
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0 (what counts as a read: cli-base 0.65.0). **Rulings:** the
|
|
220
|
+
owner's ruling on what counts as a read over MCP (2026-10-27).
|
|
197
221
|
|
|
198
222
|
### WC-CMD-18 · Declare `lifetime` and `cooperative` instead of matching command names
|
|
199
223
|
|
|
@@ -327,8 +351,9 @@ The references, each for its own part:
|
|
|
327
351
|
[WC-OUT-18](output.md#wc-out-18--exit-with-cli-bases-code-for-each-kind-of-failure), prompts follow
|
|
328
352
|
[WC-CMD-15](#wc-cmd-15--never-prompt-when-no-person-can-answer), and a renamed path keeps no alias
|
|
329
353
|
([WC-CMD-8](#wc-cmd-8--keep-no-aliases-a-renamed-path-stops-working-in-the-release-that-renames-it)), unlike the
|
|
330
|
-
deprecation advice of the Command Line Interface Guidelines. A read may have documented local effects, such as
|
|
331
|
-
run, so a failed command does not promise that it wrote nothing
|
|
354
|
+
deprecation advice of the Command Line Interface Guidelines. A read may have documented local effects, such as
|
|
355
|
+
storing what it read or recording a failed run, so a failed command does not promise that it wrote nothing; what
|
|
356
|
+
counts as a read is in [WC-CMD-17](#wc-cmd-17--declare-effects-on-every-command). A write is never retried on its own, and a tool's
|
|
332
357
|
whole payload is never logged. The `stats` root and its resources are the project's own choice.
|
|
333
358
|
- **Exceptions:** the CLI Agent Spec's rule that help goes to stderr when stdout is not a terminal is not adopted. The
|
|
334
359
|
Agent Client Protocol links an editor to a coding agent; a WireCat CLI is a tool an agent calls, so it adds no
|
|
@@ -37,6 +37,8 @@ is rewritten only to follow it:
|
|
|
37
37
|
entity's own fields (WC-DATA-2).
|
|
38
38
|
- `meeting_participants` is unique on the meeting and the contact; its `external_id` is in no unique key (WC-DATA-3).
|
|
39
39
|
|
|
40
|
+
The calendar tables that gcal fills through cli-core (`calendars`, `calendar_entries`) follow them from the start.
|
|
41
|
+
|
|
40
42
|
## Rules
|
|
41
43
|
|
|
42
44
|
### WC-DATA-1 · A record more than one tool needs is a core entity in cli-core; a CLI's own tables stay private
|
|
@@ -73,7 +75,9 @@ to its entity. One entity may combine source records from several sources and ac
|
|
|
73
75
|
### WC-DATA-3 · A provider's id goes in `external_id`
|
|
74
76
|
|
|
75
77
|
The id a provider gives a record is stored in `external_id`, unique with `account_id` (or with `provider` where the
|
|
76
|
-
record has no account).
|
|
78
|
+
record has no account). Where the provider's id is unique only inside a container the record belongs to, it is unique
|
|
79
|
+
with that container instead: a calendar entry's `external_id` is unique with its `calendar_id`, because the same event
|
|
80
|
+
id appears in each calendar that holds a copy of the event, and the calendar is unique with `account_id`. Never in a column named after the provider (`google_id`, `file_id`) and never only in
|
|
77
81
|
`metadata`. A second id the provider also uses gets a named column of its own (`ical_uid`).
|
|
78
82
|
|
|
79
83
|
- **Why:** one place to look for every provider's id, and one unique key to update a record by.
|
package/docs/standards/docs.md
CHANGED
|
@@ -37,7 +37,10 @@ Rule ids on this page are `WC-DOC-<n>`. How to read a rule, how ids work and whi
|
|
|
37
37
|
- **Enforced by:** review only.
|
|
38
38
|
- **Example:** a claim that turns out wrong is rewritten to the current fact, with no "correction" note and no dated
|
|
39
39
|
"measured on" trail. A finished plan or handoff is deleted. A done backlog or cleanup line is deleted.
|
|
40
|
-
- **Exceptions:** `CHANGELOG.md` is the one place with version history.
|
|
40
|
+
- **Exceptions:** `CHANGELOG.md` is the one place with version history. A template-owned file (`AGENTS.md`,
|
|
41
|
+
`docs/dev/TESTING.md`, the skills under `docs/dev/skills/` and the others
|
|
42
|
+
[New CLI](new-cli.md) lists) that has turned false is fixed in cli-template, never only in the CLI: `upgrade` brings
|
|
43
|
+
the fix to every CLI, and a local edit would make `upgrade` ask about it each time.
|
|
41
44
|
- **Applies to:** all CLIs, user and developer pages. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
42
45
|
|
|
43
46
|
### WC-DOC-4 · A generated page is never edited by hand
|
|
@@ -12,17 +12,18 @@ This page is a reference, not a rule page: it assigns no rule ids. How to start
|
|
|
12
12
|
|
|
13
13
|
## What the template imports
|
|
14
14
|
|
|
15
|
-
The list is what the template's `src/` and `scripts/` import today, type imports
|
|
15
|
+
The list is what the template's `src/` and `scripts/` import today, type imports, re-exports and imports loaded on
|
|
16
|
+
demand included. `kit/` imports
|
|
16
17
|
nothing from either package.
|
|
17
18
|
|
|
18
19
|
| Package and path | Exports |
|
|
19
20
|
|---|---|
|
|
20
21
|
| `@wirecat/cli-base` | `catchCrashes`, `CliError`, `configFilePath`, `mcpErrorResult`, `resolvePaths` |
|
|
21
|
-
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `describeProgram` |
|
|
22
|
+
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `COMMANDS_PAGE_LABELS` (in `scripts/`), `commandsPage` (in `scripts/`), `describeOptions` (in `scripts/`), `describeProgram` |
|
|
22
23
|
| `@wirecat/cli-base/codegen` | `writeArtifacts` (in `scripts/`) |
|
|
23
|
-
| `@wirecat/cli-base/release` | `DocsRules`, `JOURNAL_IDS`, `markdownFiles`, `packContents` (in `scripts/`) |
|
|
24
|
-
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `itemOf`, `LOCAL_READ`, `outputFor`, `privateFiles`, `profileFree`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `runsCommand`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
25
|
-
| `@wirecat/cli-core/mcp` | `McpCommand`, `registerCommands`, `runCommandForAgent`, `toolsFromCommands` |
|
|
24
|
+
| `@wirecat/cli-base/release` | `DocsRules`, `JOURNAL_IDS`, `markdownFiles`, `packContents` (all in `scripts/`) |
|
|
25
|
+
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandHelpMarkdown` (in `scripts/`), `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `httpTokenFile`, `itemOf`, `LOCAL_READ`, `LOCAL_WRITE`, `MCP_HTTP_DEFAULT_PORT`, `mcpServerCommand`, `outputFor`, `privateFiles`, `profileFree`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `refuseUnderReadOnly`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `runsCommand`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
26
|
+
| `@wirecat/cli-core/mcp` | `httpLockFile`, `MCP_PATH`, `McpCommand`, `portOf`, `publicUrlOf`, `registerCommands`, `revokeAll`, `runCommandForAgent`, `serveOverHttp`, `toolsFromCommands` |
|
|
26
27
|
| `@wirecat/cli-core/sends` | `Level` |
|
|
27
28
|
| `@wirecat/cli-core/update` | `appUpdate` |
|
|
28
29
|
| `@wirecat/cli-core/testing` | `ARGV_LOG`, `argvLogSetup` |
|
package/docs/standards/layout.md
CHANGED
|
@@ -122,6 +122,38 @@ Rule ids on this page are `WC-LAY-<n>`. How to read a rule, how ids work and whi
|
|
|
122
122
|
- **Exceptions:** none.
|
|
123
123
|
- **Applies to:** all CLIs and libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
124
124
|
|
|
125
|
+
### WC-LAY-10 · Ship the bin as a bundle that `cli-dev bundle` builds
|
|
126
|
+
|
|
127
|
+
A command that imports about a thousand module files spends most of its start resolving and reading them. tg's
|
|
128
|
+
bundle halved `--help` and every other short command
|
|
129
|
+
([measurements](https://github.com/WireCatLabs/cli-testing/blob/main/performance/startup/tg-bundle/README.md)).
|
|
130
|
+
|
|
131
|
+
- **Why:** one shared build keeps what tg learned in one place: lazy imports stay lazy, files and packages are found
|
|
132
|
+
where they were installed, and a running server survives an in-place upgrade.
|
|
133
|
+
- **Enforced by:** `<app>: pnpm build` · `<app>: pnpm smoke:node` · `<app>: pnpm smoke:bun`.
|
|
134
|
+
- **Example:** `"build": "tsc --build --force && cli-dev bundle --bin dist/bin/tg.js --external '@mtcute/*' --external undici"`,
|
|
135
|
+
with `esbuild` as a devDependency in cli-base's peer range. `dist/bundle/` holds the bundle, split so every lazy
|
|
136
|
+
`import()` stays a chunk of its own; `dist/bin/tg.js` stays the path `"bin"` names and becomes a loader. What it
|
|
137
|
+
does and checks:
|
|
138
|
+
- The loader answers `--version` from the version built into it, turns the compile cache on, and imports the
|
|
139
|
+
bundle. Under `serve`, `watch` and `mcp` it loads every chunk 2 s after start and resolves imports between chunks
|
|
140
|
+
without looking on disk: an upgrade deletes the old chunks, whose names carry a hash.
|
|
141
|
+
- In the CLI's own modules and in cli-core's and cli-base's (`--anchor` adds a package), `import.meta.url` is the
|
|
142
|
+
module's place before bundling, so skills, fonts, scenarios, migrations and child scripts are found. A package
|
|
143
|
+
they load at run time resolves from that place too (a resolve hook under Node, `Bun.resolveSync` under Bun):
|
|
144
|
+
pnpm installs cli-core's dependencies beside cli-core.
|
|
145
|
+
- A dynamic import of CommonJS (`yaml`) keeps its named exports. Names are kept (`keepNames`), so `commands --json`
|
|
146
|
+
and errors read as before.
|
|
147
|
+
- Native modules, Bun's SQLite and the packages cli-core loads by name stay outside; `--external` adds the CLI's
|
|
148
|
+
own (`*` matches anything).
|
|
149
|
+
- The build fails when a bundled module would look for a file or package from the chunk's place, or when the loader
|
|
150
|
+
names a chunk that was not built. A third-party file read and found harmless is named with `--harmless`.
|
|
151
|
+
- The CLI's smoke test runs the built bin under Node and Bun and checks that the store opens and the document
|
|
152
|
+
readers resolve.
|
|
153
|
+
- **Exceptions:** a CLI moves when its outputs are byte-identical before and after and its start gains 15% or more;
|
|
154
|
+
until then it ships `tsc`'s output. cli-base, cli-core and cli-testing ship libraries, not a bin.
|
|
155
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.64.0. **Rulings:** none.
|
|
156
|
+
|
|
125
157
|
## Sources
|
|
126
158
|
|
|
127
159
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -20,9 +20,13 @@ and [WC-SECR-2](security.md#wc-secr-2--a-log-keeps-only-known-attribute-keys-and
|
|
|
20
20
|
|
|
21
21
|
### Who follows these rules today
|
|
22
22
|
|
|
23
|
-
- **WC-LIFE-15:** tg, max, wa and the template. drive, memo and zm do not keep runs yet; each is adding them on a
|
|
23
|
+
- **WC-LIFE-15:** tg, max, wa, gcal and the template. drive, memo and zm do not keep runs yet; each is adding them on a
|
|
24
24
|
`feat/runs` branch (2026-10-25). zm's `resolveSettings` also returns `keepFailedRuns: false`, which turns off the
|
|
25
25
|
failure log cli-core's `run` would keep.
|
|
26
|
+
- **WC-LIFE-16:** cli-core's shared calendar `sync`, from cli-core 0.464.0 (cli-core #2118); gcal gets it when it
|
|
27
|
+
moves up from cli-core 0.459.0. The deadlines of WC-LIFE-5 come with the same release. The other syncs
|
|
28
|
+
(`messages sync` in tg, max and wa, drive's `sync`, memo's mail import, zm's `recordings sync`) are cooperative but
|
|
29
|
+
spend no request budget yet, and gcal sets no `requestMs` of its own, so it uses the 2 s default.
|
|
26
30
|
|
|
27
31
|
## One-shot commands
|
|
28
32
|
|
|
@@ -74,15 +78,31 @@ and [WC-SECR-2](security.md#wc-secr-2--a-log-keeps-only-known-attribute-keys-and
|
|
|
74
78
|
|
|
75
79
|
### WC-LIFE-5 · Bound every one-shot command by a deadline
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
| Command | At a terminal or in a script | Called over MCP |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| one-shot | 30 s | 30 s |
|
|
84
|
+
| `cooperative` ([WC-LIFE-10](#wc-life-10--mark-a-long-command-cooperative-when-it-should-stop-at-its-own-pace-and-keep-its-work)) | 5 min | 45 s |
|
|
85
|
+
|
|
86
|
+
`--timeout 2m` or the `<PREFIX>_TIMEOUT` variable sets the deadline, higher or lower. Past the deadline a one-shot
|
|
87
|
+
command fails with `timeout` (exit 9).
|
|
88
|
+
|
|
89
|
+
- **Why:** a command that waits for ever on a dead connection never fails, so nobody learns why it hangs. Over MCP
|
|
90
|
+
the client cuts a tool call at about 60 s: the MCP TypeScript SDK's `DEFAULT_REQUEST_TIMEOUT_MSEC` is 60000
|
|
91
|
+
(`@modelcontextprotocol/sdk` 1.32.1, `dist/esm/shared/protocol.js:8`), and users report the same for Claude Desktop
|
|
92
|
+
and ChatGPT. A cooperative command answers at 45 s, so the agent gets the answer and the way to continue instead of
|
|
93
|
+
a cut call. Claude Code waits longer ([its MCP page](https://code.claude.com/docs/en/mcp)), but a CLI does not know
|
|
94
|
+
its client, so it takes the shortest.
|
|
78
95
|
- **Enforced by:** `cli-core: src/cli/deadline.test.ts` · `cli-core: src/cli/program.test.ts`.
|
|
79
|
-
- **Example:**
|
|
80
|
-
|
|
96
|
+
- **Example:** from cli-core 0.464.0, a calendar `sync` in a terminal may run 5 minutes; the same command called over
|
|
97
|
+
MCP stops at 45 seconds with what it saved. cli-core's `DEFAULT_COMMAND_MS`, `DEFAULT_COOPERATIVE_MS` and
|
|
98
|
+
`DEFAULT_AGENT_COOPERATIVE_MS` (`src/cli/execution.ts`) hold the three values, and `run()` picks one when it starts
|
|
99
|
+
the command.
|
|
81
100
|
- **Exceptions:** a `persistent` command has no deadline and ignores `--timeout`. An `interactive` command has none
|
|
82
101
|
while a person is at the terminal; run headless, it keeps the deadline. `--help` and `--version` need none. A
|
|
83
102
|
background fetch started with `<PREFIX>_BACKFILL_JOB` is bounded only by an explicit `--timeout`.
|
|
84
103
|
- **Applies to:** CLIs that run on cli-core's `run()`; other CLIs use cli-base's `createDeadline`. **Since:** cli-base
|
|
85
|
-
0.41.0. **Rulings:**
|
|
104
|
+
0.41.0 (the cooperative deadlines: cli-base 0.65.0, cli-core 0.464.0). **Rulings:** the owner's deadlines and
|
|
105
|
+
request budgets (2026-10-27).
|
|
86
106
|
|
|
87
107
|
### WC-LIFE-6 · Open nothing until an action needs it
|
|
88
108
|
|
|
@@ -131,7 +151,9 @@ and [WC-SECR-2](security.md#wc-secr-2--a-log-keeps-only-known-attribute-keys-and
|
|
|
131
151
|
- **Enforced by:** review only.
|
|
132
152
|
- **Example:** `annotate(fetch, { cooperative: true })`. The runner then does not cut the command when the run is
|
|
133
153
|
aborted from outside. The command passes the signal to every operation that can be cancelled, checks it before
|
|
134
|
-
each write, and stops after the current step. `persistent` commands count as cooperative.
|
|
154
|
+
each write, and stops after the current step. `persistent` commands count as cooperative. A cooperative command
|
|
155
|
+
gets the longer deadline of WC-LIFE-5, and a sync among them spends a request budget (WC-LIFE-16), so it stops
|
|
156
|
+
before the deadline instead of at it.
|
|
135
157
|
- **Exceptions:** none.
|
|
136
158
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
137
159
|
|
|
@@ -147,14 +169,15 @@ and [WC-SECR-2](security.md#wc-secr-2--a-log-keeps-only-known-attribute-keys-and
|
|
|
147
169
|
a CLI's own commands included, connects through `connectFor`: a relayed run gets serve's own adapter, and a run for
|
|
148
170
|
another profile is refused. With no live `serve`, the command connects on its own. A `serve` that offers the socket
|
|
149
171
|
but does not answer gives `provider_unavailable`, never a second connection.
|
|
150
|
-
- **Exceptions:** `serve`, `server`, `bot`, logins, `--offline`, `--dry-run` and `mcp --http` are not relayed.
|
|
172
|
+
- **Exceptions:** `serve`, `server`, `bot`, logins, `--offline`, `--dry-run` and `mcp serve --http` are not relayed.
|
|
151
173
|
- **Applies to:** messenger CLIs with an exclusive connection (wa today). **Since:** cli-base 0.41.0.
|
|
152
174
|
**Rulings:** NEED-1389.
|
|
153
175
|
|
|
154
176
|
## Resumable syncs
|
|
155
177
|
|
|
156
178
|
A long sync — a mailbox, a chat history, a Drive folder, a recording archive — is stopped by a kill, a reboot or a
|
|
157
|
-
provider's limit as often as it ends. These three rules make the next run carry on
|
|
179
|
+
provider's limit as often as it ends. These three rules make the next run carry on, and WC-LIFE-16 stops a run
|
|
180
|
+
before its deadline.
|
|
158
181
|
|
|
159
182
|
### WC-LIFE-12 · Save a long sync's work per item or per page, so a kill loses nothing saved
|
|
160
183
|
|
|
@@ -221,6 +244,44 @@ every value that may be content
|
|
|
221
244
|
- **Exceptions:** none.
|
|
222
245
|
- **Applies to:** all CLIs. **Since:** cli-base 0.56.0. **Rulings:** none.
|
|
223
246
|
|
|
247
|
+
## Request budgets
|
|
248
|
+
|
|
249
|
+
A sync that resumes (WC-LIFE-12 to WC-LIFE-14) also stops on its own before its deadline.
|
|
250
|
+
|
|
251
|
+
### WC-LIFE-16 · A sync spends a request budget sized to its deadline, and its answer says how to continue
|
|
252
|
+
|
|
253
|
+
Before each request to the provider, a sync asks its budget whether one more may go. The budget is
|
|
254
|
+
|
|
255
|
+
`requests = floor((deadline − margin) / requestMs)`, at least one,
|
|
256
|
+
|
|
257
|
+
where `margin` is the larger of 5 s and 15 % of the deadline, and `requestMs` is how long one request of this CLI is
|
|
258
|
+
expected to take: one constant per CLI, `AppIdentity.requestMs` (2 s when it is not set). `--timeout` changes the
|
|
259
|
+
deadline, and the budget is computed again from it; `--max-pages <pages>` can only lower it. The budget also stops
|
|
260
|
+
when the deadline is closer than the margin plus the slowest request so far, and when the run is cancelled.
|
|
261
|
+
|
|
262
|
+
When the budget stops the sync before its end, the sync saves where it is (WC-LIFE-12), exits 0 as a declared
|
|
263
|
+
`partial` answer ([WC-OUT-8](output.md#wc-out-8--exit-0-on-a-partial-answer-only-when-the-command-declares-partial))
|
|
264
|
+
and says in the answer why it stopped and how far it got. Its `next` holds the exact command that continues it, the
|
|
265
|
+
same `sync` with the same budget and timeout, marked safe to run unasked; over MCP the step carries that command and
|
|
266
|
+
its arguments for the `read` tool. A run that moved nothing on offers no step, so an agent cannot loop. The sync's
|
|
267
|
+
status command answers how far it is
|
|
268
|
+
([WC-LIFE-14](#wc-life-14--a-syncs-status-command-says-how-far-it-is-in-one-shape)).
|
|
269
|
+
|
|
270
|
+
- **Why:** a first sync of a large account does not fit one deadline. Without a budget the deadline cuts it mid-page
|
|
271
|
+
and the run loses its round; with one, every run ends with an answer, saves its work and says how to go on. One
|
|
272
|
+
`requestMs` per CLI, in one place, keeps the arithmetic the same in every sync of that CLI.
|
|
273
|
+
- **Enforced by:** `cli-core: src/background/budget.test.ts` · `cli-core: src/calendar/testing/sync-contract.ts`.
|
|
274
|
+
- **Example:** `gcal personal sync` over MCP has 45 s: margin 6.75 s, so with the 2 s default about 19 requests. It
|
|
275
|
+
answers `state: "partial"`, `stopped: "requests"`, `progress: { calendars, calendarsDone, requests, saved }`, and
|
|
276
|
+
`next` with `gcal personal sync`; `gcal personal status` shows each calendar's progress. cli-core builds the budget
|
|
277
|
+
with `runBudgetOf(command, maxPages)` from `@wirecat/cli-core/cli` over `runBudget` in
|
|
278
|
+
`@wirecat/cli-core/background`.
|
|
279
|
+
- **Exceptions:** known gaps, as of 2026-10-27: only the calendar sync spends a budget (see "Who follows"). The
|
|
280
|
+
continuing step names the command, not the MCP tool, and until the WC-CMD-17 follow-up the router sends that
|
|
281
|
+
command to `write`, not `read`.
|
|
282
|
+
- **Applies to:** every CLI with a sync. **Since:** cli-base 0.65.0. **Rulings:** the owner's deadlines and request
|
|
283
|
+
budgets (2026-10-27).
|
|
284
|
+
|
|
224
285
|
## Sources
|
|
225
286
|
|
|
226
287
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|