@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
package/docs/standards/mcp.md
CHANGED
|
@@ -15,9 +15,12 @@ Rule ids on this page are `WC-MCP-<n>`. How to read a rule, how ids work and whi
|
|
|
15
15
|
|
|
16
16
|
### Who follows these rules today
|
|
17
17
|
|
|
18
|
-
tg, max and wa serve cli-core's personal server (`<cli> <profile> mcp`) and bot server (`<cli> <profile> bot mcp`).
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
tg, max and wa serve cli-core's personal server (`<cli> <profile> mcp serve`) and bot server (`<cli> <profile> bot mcp serve`).
|
|
19
|
+
`mcp serve --profiles` ([WC-MCP-9](#wc-mcp-9--a-server-serves-the-profiles-it-is-told-with-one-connection-for-each)) is not
|
|
20
|
+
built yet: cli-core serves one named profile.
|
|
21
|
+
zoom serves the three shared tools over its own commands from zoom-cli 0.35.0 (`zm <profile> mcp serve`, cli-core's
|
|
22
|
+
`toolsFromCommands` and `runCommandForAgent`), and gcal does the same (`gcal <profile> mcp`; it moves to `mcp serve`
|
|
23
|
+
when it takes cli-core 0.463.0). memo and drive have their own servers: memo has hand-written tools, and
|
|
21
24
|
drive builds one tool per command from its command tree and skips commands through its own list. Those two are known
|
|
22
25
|
gaps against these rules.
|
|
23
26
|
|
|
@@ -66,12 +69,22 @@ Every entry in the catalogue must be a command the CLI has.
|
|
|
66
69
|
|
|
67
70
|
Every command says whether it writes. `read` refuses a command that writes, and `write` refuses one that only reads.
|
|
68
71
|
Both declare the answer envelope (a list `{ items, page, … }` or one thing `{ item, … }`) as their `outputSchema`.
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
A command that reads the service and only saves what it read into the owner's local store, such as `sync` or
|
|
73
|
+
`calendars list`, is a read and runs through `read`
|
|
74
|
+
([WC-CMD-17](commands.md#wc-cmd-17--declare-effects-on-every-command) defines it).
|
|
75
|
+
|
|
76
|
+
- **Why:** an agent host can let `read` run freely and ask a person before `write`. `read` carries the MCP
|
|
77
|
+
`readOnlyHint`, which the [MCP schema](https://modelcontextprotocol.io/specification/2025-11-25/schema#toolannotations)
|
|
78
|
+
describes as "the tool does not modify its environment". A local copy of what the service already holds changes
|
|
79
|
+
nothing the owner would have to undo, so a sync stays under it; sending it through `write` would make a host ask a
|
|
80
|
+
person before every refresh.
|
|
71
81
|
- **Enforced by:** `cli-core: src/mcp/mcp.test.ts` · `cli-core: src/mcp/output-schema.test.ts`.
|
|
72
|
-
- **Example:** `max_read { "command": "messages send", … }` is refused.
|
|
73
|
-
|
|
74
|
-
- **
|
|
82
|
+
- **Example:** `max_read { "command": "messages send", … }` is refused. Once the gap below is closed,
|
|
83
|
+
`gcal_read { "command": "sync" }` runs.
|
|
84
|
+
- **Exceptions:** none. Known gap, as of 2026-10-27: cli-core decides with cli-base's `writes()`, which still counts
|
|
85
|
+
the local write, so `sync` runs through `write` until that follow-up lands (WC-CMD-17).
|
|
86
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0 (what counts as a read: cli-base 0.65.0). **Rulings:** the
|
|
87
|
+
owner's ruling on what counts as a read over MCP (2026-10-27).
|
|
75
88
|
|
|
76
89
|
### WC-MCP-5 · Unknown arguments are refused before anything runs
|
|
77
90
|
|
|
@@ -116,29 +129,36 @@ profile's permissions, the recipient list and the hourly limit decide.
|
|
|
116
129
|
|
|
117
130
|
### WC-MCP-8 · No `mcp` flag grants access
|
|
118
131
|
|
|
119
|
-
`mcp --permission <key=level>` may only make a level stricter for that server. An override that would loosen the
|
|
132
|
+
`mcp serve --permission <key=level>` may only make a level stricter for that server. An override that would loosen the
|
|
120
133
|
profile is refused at start-up with `validation_error` naming the key. This includes a parent key that would hide a
|
|
121
|
-
stricter key under it, and lifting a built-in `ask`. `mcp
|
|
134
|
+
stricter key under it, and lifting a built-in `ask`. `mcp add <client> --allow-writes` only confirms, at install time, that
|
|
122
135
|
the owner saw the profile offers writes.
|
|
123
136
|
|
|
124
137
|
- **Why:** whoever writes the agent's MCP config must not get more than the profile allows.
|
|
125
138
|
- **Enforced by:** `cli-core: src/cli/messenger/mcp-command-http.test.ts`.
|
|
126
|
-
- **Example:** `max work mcp --permission messages=readonly` works. `max work mcp --permission messages.delete=allow`
|
|
139
|
+
- **Example:** `max work mcp serve --permission messages=readonly` works. `max work mcp serve --permission messages.delete=allow`
|
|
127
140
|
is refused, because the built-in level is `ask`.
|
|
128
|
-
- **Exceptions:** `bot mcp` takes no `--permission` at all. cli-base's flag registry still describes `--allow-writes`
|
|
141
|
+
- **Exceptions:** `bot mcp serve` takes no `--permission` at all. cli-base's flag registry still describes `--allow-writes`
|
|
129
142
|
as "permit writing"; that wording is a gap.
|
|
130
143
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1423.
|
|
131
144
|
|
|
132
|
-
### WC-MCP-9 · A server
|
|
145
|
+
### WC-MCP-9 · A server serves the profiles it is told, with one connection for each
|
|
133
146
|
|
|
134
|
-
`mcp`
|
|
135
|
-
every
|
|
147
|
+
`mcp serve` serves one named profile (`<cli> <profile> mcp serve`), or the profiles that `--profiles` lists:
|
|
148
|
+
`--profiles work,personal`, or `--profiles all` for every profile on this computer. With neither, it refuses with
|
|
149
|
+
`validation_error` and lists the profiles, like any missing profile ([WC-ACCT-3](accounts.md)). It never serves every
|
|
150
|
+
profile by default. The server opens one messenger connection for each profile it serves, and every call for that
|
|
151
|
+
profile shares it. When it serves more than one profile, each call names its profile; a call without one is refused
|
|
152
|
+
and lists the profiles served. Each call follows its own profile's permissions.
|
|
136
153
|
|
|
137
|
-
- **Why:**
|
|
154
|
+
- **Why:** which accounts an agent can reach is the owner's choice, written in the agent's config. A connection per
|
|
155
|
+
call would break the messenger's pace limits and look unlike the official client.
|
|
138
156
|
- **Enforced by:** review only.
|
|
139
|
-
- **Example:** `max work mcp
|
|
157
|
+
- **Example:** `max work mcp serve`, `max mcp serve --profiles work,personal` or `max mcp serve --profiles all`.
|
|
158
|
+
Under the last two, a call reads `max_read { "profile": "work", "command": "inbox", "arguments": {} }`.
|
|
159
|
+
`max mcp serve` alone is refused, and its message lists `personal` and `work`.
|
|
140
160
|
- **Exceptions:** the bot server needs no held connection: each Bot API call is plain HTTPS.
|
|
141
|
-
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:**
|
|
161
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1881.
|
|
142
162
|
|
|
143
163
|
### WC-MCP-10 · A tool and its command give the same answer and the same error
|
|
144
164
|
|
|
@@ -170,15 +190,16 @@ it carries.
|
|
|
170
190
|
|
|
171
191
|
### WC-MCP-12 · An MCP server over HTTP follows the same permissions and short-lived logins
|
|
172
192
|
|
|
173
|
-
`mcp --http` serves on `127.0.0.1` for agents in a browser, behind the owner's tunnel. Writes follow the profile's
|
|
193
|
+
`mcp serve --http` serves on `127.0.0.1` for agents in a browser, behind the owner's tunnel. Writes follow the profile's
|
|
174
194
|
permissions. An access token lives one hour and a refresh token 30 days, replaced on each use. To log in, a browser app
|
|
175
|
-
needs a one-time code that the owner reads in a terminal.
|
|
195
|
+
needs a one-time code that the owner reads in a terminal. `mcp revoke` ends every login.
|
|
176
196
|
|
|
177
197
|
- **Why:** a server reachable from a browser must not be a softer way in than the local one.
|
|
178
198
|
- **Enforced by:** review only.
|
|
179
|
-
- **Example:** `max work mcp --http --public-url https://<name>.ts.net`.
|
|
199
|
+
- **Example:** `max work mcp serve --http --public-url https://<name>.ts.net`.
|
|
180
200
|
- **Exceptions:** none.
|
|
181
|
-
- **Applies to:** messenger CLIs
|
|
201
|
+
- **Applies to:** every CLI that offers `mcp serve --http`: the messenger CLIs, cli-template and every CLI made from
|
|
202
|
+
it. **Since:** cli-base 0.41.0. **Rulings:** NEED-594, NEED-774.
|
|
182
203
|
|
|
183
204
|
### WC-MCP-13 · A tool's hints and descriptions never stand in for the permission check
|
|
184
205
|
|
|
@@ -213,8 +234,11 @@ cli-core builds its personal and bot MCP catalogues from the command tree with `
|
|
|
213
234
|
- **One server per subtree.** The personal server is built from the program without its `bot` group; the bot server
|
|
214
235
|
from the `bot` group. Both keep three tools (WC-MCP-1).
|
|
215
236
|
- **The profile still decides at run time** what is listed (WC-MCP-6).
|
|
216
|
-
- **
|
|
217
|
-
|
|
237
|
+
- **Two snapshots pin what agents see.** `contracts/mcp-tools.json` pins the tools themselves: with three tools
|
|
238
|
+
(WC-MCP-1) that is three names and their schemas, which do not change when a command is added. So each CLI also
|
|
239
|
+
pins the command catalogue behind the tools, every command `tools_search` can return and whether it runs through
|
|
240
|
+
`read` or `write`, in `contracts/mcp-catalogue.json`. A command that becomes visible to agents then shows up as a diff in
|
|
241
|
+
its pull request. tg and max keep both; wa, zm, gcal and drive pin only the tools, a known gap as of 2026-10-27.
|
|
218
242
|
|
|
219
243
|
## Sources
|
|
220
244
|
|
|
@@ -12,7 +12,7 @@ built on are on the [Kit surface](kit-surface.md) page.
|
|
|
12
12
|
## Start with the template
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npx @wirecat/cli-template new
|
|
15
|
+
npx @wirecat/cli-template new gcal-cli --command gcal --package @wirecat/gcal-cli --owner WireCatLabs \
|
|
16
16
|
--category calendar --content-lang en,ru --sign-in oauth --api official-client --filters time,person,text \
|
|
17
17
|
--never-store tokens,secret-links --untrusted names,links
|
|
18
18
|
```
|
|
@@ -24,7 +24,7 @@ the choices. Nothing is guessed.
|
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| `<name>-cli` | the repository folder | any name |
|
|
26
26
|
| `--command` | the command word people type | one word |
|
|
27
|
-
| `--package` | the npm package name | a scoped name, such as `@wirecat/
|
|
27
|
+
| `--package` | the npm package name | a scoped name, such as `@wirecat/gcal-cli` |
|
|
28
28
|
| `--owner` | the GitHub owner of the repository | an owner |
|
|
29
29
|
| `--category` | what the CLI reads | `messenger`, `mail`, `notes`, `files`, `calendar`, `meetings` |
|
|
30
30
|
| `--content-lang` | the languages of the content | a list, such as `en,ru` |
|
|
@@ -42,17 +42,17 @@ a reference CLI for your category in `AGENTS.md`. It does not install packages,
|
|
|
42
42
|
publish. Then:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
cd
|
|
45
|
+
cd gcal-cli && pnpm install && pnpm ci:quick
|
|
46
46
|
git add -A && git commit -m "chore: start from @wirecat/cli-template"
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explicitly, such as `
|
|
49
|
+
Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explicitly, such as `gcal personal doctor`.
|
|
50
50
|
The reference CLI is the one to compare with when a step is unclear.
|
|
51
51
|
|
|
52
52
|
The copy already holds every file this handbook asks for: the required root commands (`commands`, `doctor`,
|
|
53
53
|
`skill show`, `skill install`, `mcp`, `upgrade`) and `config` from cli-core, the run log and its `runs` commands
|
|
54
|
-
([WC-LIFE-15](lifecycle.md#wc-life-15--keep-a-run-log-through-cli-core-and-read-it-back-with-runs)), the MCP server with
|
|
55
|
-
command, writes included, the community presets in `config/wirecat/`, the TypeScript, Biome and vitest setup,
|
|
54
|
+
([WC-LIFE-15](lifecycle.md#wc-life-15--keep-a-run-log-through-cli-core-and-read-it-back-with-runs)), the MCP server with its three tools
|
|
55
|
+
([WC-MCP-1](mcp.md#wc-mcp-1--a-server-offers-three-tools-not-one-per-command)) that reach every command, writes included, the community presets in `config/wirecat/`, the TypeScript, Biome and vitest setup,
|
|
56
56
|
`contracts/`, `lint/`, the test matrix, the user pages, `CHANGELOG.md`, `SECURITY.md`, `bin/release`, and the
|
|
57
57
|
`pr.yml`, `ci.yml`, `main.yml`, `standards.yml`, `changelog-guard.yml`, `monthly.yml` and `release.yml` workflows.
|
|
58
58
|
The rules they keep are on the area pages: [Commands](commands.md), [MCP](mcp.md), [Accounts](accounts.md),
|
|
@@ -61,6 +61,15 @@ The rules they keep are on the area pages: [Commands](commands.md), [MCP](mcp.md
|
|
|
61
61
|
Later template changes come in with `npx @wirecat/cli-template upgrade`, file by file: apply, replace, skip or diff.
|
|
62
62
|
See [Kit surface](kit-surface.md#how-your-cli-stays-current).
|
|
63
63
|
|
|
64
|
+
The template's `template.json` splits the copy in two. A **seed** (`DESIGN.md`, `CLAUDE.md`, `README.md`, the user
|
|
65
|
+
pages, `src/`) is the CLI's own from the first commit: rewrite it freely. A **template-owned** file (`AGENTS.md`,
|
|
66
|
+
`docs/dev/TESTING.md`, `docs/dev/reference-clis.md`, the skills under `docs/dev/skills/`, `.github/`) holds only what
|
|
67
|
+
is true for every CLI made from the template. Do not rewrite one to fit this CLI: put the CLI's own facts in a seed.
|
|
68
|
+
A template-owned page that is wrong for this CLI is a gap in cli-template, under
|
|
69
|
+
[WC-DOC-3](docs.md#wc-doc-3--every-document-states-current-facts-rewrite-a-wrong-sentence-in-place) like any other
|
|
70
|
+
wrong sentence: fix it there, and `upgrade` brings the fix. Change it in the CLI only where the CLI must differ for
|
|
71
|
+
good; `upgrade` then asks before it touches the change.
|
|
72
|
+
|
|
64
73
|
## Four steps that need a person
|
|
65
74
|
|
|
66
75
|
`new` prints these at the end.
|
|
@@ -69,12 +78,14 @@ See [Kit surface](kit-surface.md#how-your-cli-stays-current).
|
|
|
69
78
|
changes only through a pull request, required status checks, and no bypass. Add the ruleset after the first
|
|
70
79
|
commit is on `main`, since a pull request rule leaves an empty repository no way to get one, and the required
|
|
71
80
|
check names exist only once CI has run. See [Releases](releases.md).
|
|
72
|
-
2. **Set npm trusted publishing** for the new package, naming `release.yml`, and add any secrets the CI needs.
|
|
81
|
+
2. **Set npm trusted publishing** for the new package, naming `release.yml`, and add any secrets the CI needs. npm
|
|
82
|
+
offers this only once the package exists, so the owner publishes the first version with `bin/release --local`
|
|
83
|
+
and sets it right after. See
|
|
73
84
|
[WC-REL-11](releases.md#wc-rel-11--publish-from-github-actions-with-npm-trusted-publishing).
|
|
74
|
-
3. **Pick a monthly cron minute** between 02:17 and 03:20 UTC that
|
|
75
|
-
`.github/workflows/monthly.yml` keeps the template's own minute, 03:02, and no check compares minutes across
|
|
76
|
-
repositories, so change it by hand. See
|
|
85
|
+
3. **Pick a monthly cron minute** between 02:17 and 03:20 UTC that is not in the list of taken minutes under
|
|
77
86
|
[WC-CI-9](ci.md#wc-ci-9--run-full-validation-and-release-security-on-the-first-day-of-each-month-without-publishing).
|
|
87
|
+
The copy of `.github/workflows/monthly.yml` keeps the template's own minute, 03:02, and no check compares minutes
|
|
88
|
+
across repositories, so change it by hand, and add the new minute to the list in the step 4 pull request.
|
|
78
89
|
4. **WireCat CLIs only: join the shared lists.** A pull request to cli-base's `PARITY_GROUPS` (at least the `all`
|
|
79
90
|
group, which compares the shared root commands), a line in cli-core's `WIRECAT_TOOLS` and a line in cli-docs'
|
|
80
91
|
`tools.json`. A messenger also joins cli-core's `parity.json`. A CLI outside WireCatLabs keeps its parity groups
|
|
@@ -89,17 +100,20 @@ All of these pass on `main`, before the first release:
|
|
|
89
100
|
- `cli-dev lint` against the built CLI. It also makes the CLI fail once in an empty home and checks that
|
|
90
101
|
`runs list` shows the failed run
|
|
91
102
|
- `cli-dev audit --for <cli> --lint <cli>=lint` with no new finding, run with the command trees of the CLIs in
|
|
92
|
-
the same parity groups. `--lint` judges the CLI's lint rules by its own `lint/` folder.
|
|
93
|
-
other CLIs
|
|
94
|
-
|
|
103
|
+
the same parity groups (`pnpm audit:commands`). `--lint` judges the CLI's lint rules by its own `lint/` folder.
|
|
104
|
+
What it shares with the other CLIs has no baseline: `audit/baseline` does not accept a shared-contract finding
|
|
105
|
+
(cli-base 0.51.0). Fix each one, or, where the CLI must differ for good, the pull request that adds it to a parity
|
|
106
|
+
group declares the exact difference with its reason in that group's `differences`
|
|
95
107
|
- `pnpm contracts:check`
|
|
96
108
|
- `cli-dev test-matrix --check`
|
|
97
109
|
- `pnpm docs:check`, with `--pages`, and the CLI's `generate:check`
|
|
98
110
|
- the changelog guard on a pull request
|
|
99
111
|
- `pnpm release:check`
|
|
100
112
|
|
|
101
|
-
The last test is a fresh agent. Give it only this handbook's [index](README.md)
|
|
102
|
-
|
|
113
|
+
The last test is a fresh agent. Give it only this handbook's [index](README.md), the new repository and the cli-base
|
|
114
|
+
workflow files its own workflows pin
|
|
115
|
+
([WC-CI-8](ci.md#wc-ci-8--call-cli-base-node-ciyml-from-ciyml-pinned-to-a-tag-or-commit)), and ask it to audit the
|
|
116
|
+
CLI. Every gap it lists is either a gap in the CLI, to fix before the first release, or a hole in the
|
|
103
117
|
handbook, to fix here.
|
|
104
118
|
|
|
105
119
|
## The template repository itself
|
|
@@ -118,7 +132,7 @@ upgrade, and no script or agent reads its answers.
|
|
|
118
132
|
| [WC-DOC-12](docs.md#wc-doc-12--a-runnable-command-in-a-document-has-no-placeholder-in-it) | Exempt only for the placeholder names `clidev`, `clidev-cli`, `@wirecat/clidev-cli` and `WireCatLabs/clidev-cli`, and the upper-case prefix `CLIDEV_` (as in `CLIDEV_PROFILE`). | `new` replaces each of them with the CLI's own name. Any other placeholder is still a gap. |
|
|
119
133
|
| [WC-DOC-17](docs.md#wc-doc-17--each-entry-says-what-changed-for-the-caller-why-and-what-to-watch-for) | A changelog entry may name a file path. | The template's readers are CLI authors, who edit those files or take them with `upgrade`; for them a path is what they see. |
|
|
120
134
|
|
|
121
|
-
Every other rule that applies to all CLIs, or to the
|
|
135
|
+
Every other rule that applies to all CLIs, or to the WireCat repositories, applies to cli-template as it is written,
|
|
122
136
|
with one limit for the copy tool. It follows the rules for code and the repository: Toolchain, Layout, Style,
|
|
123
137
|
Testing, Security and Documents. It has no profile, no machine mode, no MCP server, no store and no agent reading its
|
|
124
138
|
answers, so the rules for a CLI's commands and runtime do not reach it: Commands, Output, MCP, Accounts, Permissions,
|
package/docs/standards/output.md
CHANGED
|
@@ -93,7 +93,11 @@ JSON Schema 2020-12.
|
|
|
93
93
|
- **Why:** a caller that checks only the exit code must not take half an answer for a whole one.
|
|
94
94
|
- **Enforced by:** review only.
|
|
95
95
|
- **Example:** a search across accounts declares `annotate(search, { partial: true })` and exits 0 with
|
|
96
|
-
`meta.complete: false`. Any other command prints what it read
|
|
96
|
+
`meta.complete: false`. Any other command prints the whole envelope of what it read on stdout, with
|
|
97
|
+
`meta.complete: false` and its `issues`, then exits with the code of its first issue. Such an answer is an answer,
|
|
98
|
+
not a failure, so [WC-OUT-14](#wc-out-14--print-a-failure-in-machine-mode-as-one-json-object-on-stderr-with-stdout-empty)
|
|
99
|
+
does not apply to it: a caller reads stdout whenever it is not empty, also after a non-zero exit. cli-core's
|
|
100
|
+
`renderAnswer` does this.
|
|
97
101
|
- **Exceptions:** none.
|
|
98
102
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
99
103
|
|
|
@@ -103,7 +107,8 @@ JSON Schema 2020-12.
|
|
|
103
107
|
- **Enforced by:** review only.
|
|
104
108
|
- **Example:** `next` holds the step to take, then one alternative. Each has `kind` (`command`, or `user_action` with
|
|
105
109
|
no command) and `safe_to_auto_execute`. A step that reads the service and writes only the local store (a sync) is
|
|
106
|
-
safe
|
|
110
|
+
safe; it counts as a read, also over MCP
|
|
111
|
+
([WC-CMD-17](commands.md#wc-cmd-17--declare-effects-on-every-command)). A step that reads nothing remotely and writes locally (`store reindex`) is not: it waits to be asked. A step
|
|
107
112
|
is built from ids, counts and cursors the CLI holds, never from text a message, mail or document carried. A CLI
|
|
108
113
|
answer's command step also has `line`, the whole command with the profile (`tg personal mcp config`), because
|
|
109
114
|
`command` is the bare path an MCP router takes and a copied path without the profile fails.
|
|
@@ -160,6 +165,12 @@ JSON Schema 2020-12.
|
|
|
160
165
|
|
|
161
166
|
### WC-OUT-14 · Print a failure in machine mode as one JSON object on stderr, with stdout empty
|
|
162
167
|
|
|
168
|
+
A failure is a run that has no answer: the command threw. A command that answers with what it could read and
|
|
169
|
+
`meta.complete: false` has not failed, even when it exits non-zero; that case is
|
|
170
|
+
[WC-OUT-8](#wc-out-8--exit-0-on-a-partial-answer-only-when-the-command-declares-partial). So a non-zero exit does not
|
|
171
|
+
mean stdout is empty: stdout empty and one JSON object on stderr is a failure; an envelope on stdout is an answer,
|
|
172
|
+
whole or partial.
|
|
173
|
+
|
|
163
174
|
- **Why:** a caller tells success from failure without parsing text, and stdout never holds half an answer and an
|
|
164
175
|
error.
|
|
165
176
|
- **Enforced by:** `src/failure.test.ts` · `cli-core: src/cli/agent-contract.test.ts`.
|
|
@@ -20,8 +20,9 @@ Rule ids on this page are `WC-PERM-<n>`. How to read a rule, how ids work and wh
|
|
|
20
20
|
tg, max and wa use cli-core's permissions and write guard in full. memo uses them for the shared commands only. drive
|
|
21
21
|
has its own policy and guard, with its own `--read-only` flag. zoom has per-profile `permissions` from zoom-cli 0.35.0,
|
|
22
22
|
read with cli-core's `levelFor`; its writes change Zoom meetings, its built-in default is `remote-meetings: ask`, and
|
|
23
|
-
it has no send journal or recipient list, since it sends no messages.
|
|
24
|
-
|
|
23
|
+
it has no send journal or recipient list, since it sends no messages. gcal declares its own keys through cli-core's
|
|
24
|
+
`commandPermissions` and runs its event writes through cli-core's write guard (WC-PERM-22). Where this page says
|
|
25
|
+
"every CLI that writes", drive is a known gap.
|
|
25
26
|
|
|
26
27
|
WC-PERM-10 and WC-PERM-11 need cli-core 0.321.0, which tg, max and wa pin. WC-PERM-12 as written needs cli-core
|
|
27
28
|
0.337.0; before it, the lock let through some local writes that overwrite what the owner saved. A command other
|
|
@@ -114,7 +115,8 @@ A default is never made stricter without the owner's word.
|
|
|
114
115
|
- **Exceptions:** where the messenger has replies, `replies.send` is `deny`: a reply rule writes to people with nobody
|
|
115
116
|
typing a command, so it waits for the owner's own `allow`. A messenger with no `replies` group, such as wa, has no
|
|
116
117
|
use for the key.
|
|
117
|
-
- **Applies to:** messenger CLIs.
|
|
118
|
+
- **Applies to:** messenger CLIs. A CLI that is not a messenger sets its own defaults (WC-PERM-22). **Since:** cli-base
|
|
119
|
+
0.41.0. **Rulings:** NEED-568.
|
|
118
120
|
|
|
119
121
|
### WC-PERM-7 · A built-in default only ever tightens
|
|
120
122
|
|
|
@@ -310,6 +312,47 @@ Moderation rules use the same four levels for each kind of action (delete, remov
|
|
|
310
312
|
looser than the profile's `chats.moderate`; the code does not check that yet.
|
|
311
313
|
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-306, NEED-310.
|
|
312
314
|
|
|
315
|
+
### WC-PERM-22 · A CLI that is not a messenger declares its own keys and defaults with `commandPermissions`
|
|
316
|
+
|
|
317
|
+
A messenger's keys come from its resources (`messages`, `chats`). Any other CLI names its own with cli-core's
|
|
318
|
+
`commandPermissions({ keyOf, extra, defaults })`, passed as `settingsFor(app, { permissions })` (cli-core 0.450.0):
|
|
319
|
+
|
|
320
|
+
- **Keys.** Each command is checked against its own path (`events.create`), the default `keyOf`. Housekeeping has no
|
|
321
|
+
key (WC-PERM-5). `extra` adds the keys a command checks inside itself, such as one per generated API method
|
|
322
|
+
(`api.calendar.events.insert`). A key that names no command is refused when it is set or saved.
|
|
323
|
+
- **Defaults.** `defaults` holds the CLI's built-in levels, beside cli-core's. They only ever tighten (WC-PERM-7).
|
|
324
|
+
Like WC-PERM-6 for the messengers, and as tg and drive do: an ordinary change is `allow`; a delete is `ask`; a
|
|
325
|
+
write that mails or notifies people other than the owner, or deletes a whole series from their calendars, is a key
|
|
326
|
+
of its own, `deny` until the owner allows it.
|
|
327
|
+
Sending a message is not this kind of write; a messenger CLI keeps WC-PERM-6.
|
|
328
|
+
- **Shared keys on every path.** A key a shared command checks inside itself, such as `events.notify` or
|
|
329
|
+
`events.delete.series`, is checked by every path that can do the same thing, the CLI's own generic API commands
|
|
330
|
+
included. A gcal `api` call that deletes an event with `sendUpdates: "all"` checks `events.notify` as well as
|
|
331
|
+
its own `api.calendar.events.delete`; otherwise the generic path is a way round the shared key.
|
|
332
|
+
- **Read-only lock and guard.** `<PREFIX>_READ_ONLY` refuses every write to the service, as in WC-PERM-12. Every
|
|
333
|
+
write runs through cli-core's write guard with `permissionDefaults: configuration.permissions.defaults`
|
|
334
|
+
(cli-core 0.455.0), so `ask` is a y/N question at a terminal (WC-PERM-9) and every attempt goes into the send journal
|
|
335
|
+
(WC-PERM-17). A CLI that sends no messages has no recipient list or hourly limit to set.
|
|
336
|
+
|
|
337
|
+
- **Why:** one permission model for every CLI; a CLI-own layer drifts from the shared one and is audited by nobody.
|
|
338
|
+
- **Enforced by:** `cli-core: src/cli/command-permissions.test.ts` · `cli-core: src/calendar/cli/writes.test.ts`.
|
|
339
|
+
- **Example:** a calendar CLI takes cli-core's `CALENDAR_WRITE_DEFAULTS` and `CALENDAR_WRITE_KEYS`
|
|
340
|
+
(`@wirecat/cli-core/calendar`, cli-core 0.458.0). By this rule `events create`, `update` and `respond` are `allow`
|
|
341
|
+
and `events delete` is `ask`. `events.delete.series` (every occurrence of a repeating series, in every attendee's
|
|
342
|
+
calendar) and `events.notify` (the provider mails the attendees) are `deny` until the owner allows them: over MCP
|
|
343
|
+
`ask` runs without a question, and each reaches people other than the owner. gcal adds its own keys for its generated
|
|
344
|
+
`api <method>` commands: sharing a calendar is `ask` (NEED-1857), and emptying the main calendar
|
|
345
|
+
(`api.calendar.calendars.clear`) is `deny`.
|
|
346
|
+
- **Exceptions:** known gaps, as of 2026-10-27:
|
|
347
|
+
- cli-core's `CALENDAR_WRITE_DEFAULTS` (`src/calendar/writer.ts`) still has `events create`, `update` and
|
|
348
|
+
`respond` at `ask`.
|
|
349
|
+
- gcal's generated `api` commands default every write to `ask`, ordinary changes included, and check neither
|
|
350
|
+
`events.notify` nor `events.delete.series`.
|
|
351
|
+
- drive keeps its own policy and `--read-only` flag, zoom reads its own `remote-meetings` key with `levelFor`, and
|
|
352
|
+
memo uses cli-core's permissions for the shared commands only. None of them declares `commandPermissions` yet.
|
|
353
|
+
- **Applies to:** every CLI that writes and is not a messenger. **Since:** cli-base 0.64.0 (the levels and the shared
|
|
354
|
+
keys on every path: cli-base 0.65.0). **Rulings:** NEED-1857, NEED-1883.
|
|
355
|
+
|
|
313
356
|
## Sources
|
|
314
357
|
|
|
315
358
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -125,7 +125,11 @@ on the [CI](ci.md) page.
|
|
|
125
125
|
- **Example:** `bin/release` starts `release.yml` and follows it. Only the `publish` job has the OIDC permission (the
|
|
126
126
|
short-lived identity npm trusts), and it runs no project code: it only runs `npm publish` on the validated file.
|
|
127
127
|
- **Exceptions:** npm trusts the workflow by file name: renaming `release.yml` stops publishing until npm's trusted
|
|
128
|
-
publisher settings are updated.
|
|
128
|
+
publisher settings are updated. npm sets a trusted publisher on the package's settings page
|
|
129
|
+
([npm docs](https://docs.npmjs.com/trusted-publishers)), which a package has only once it is published, so its
|
|
130
|
+
first version is published with `bin/release --local` by the owner, from a clean `main`. Right after it, the owner
|
|
131
|
+
adds the trusted publisher on npmjs.com (step 2 of [New CLI](new-cli.md#four-steps-that-need-a-person)), and every
|
|
132
|
+
later version goes through `release.yml`.
|
|
129
133
|
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
130
134
|
|
|
131
135
|
### WC-REL-12 · Publish only after full validation and the release security checks pass
|
|
@@ -181,12 +185,24 @@ on the [CI](ci.md) page.
|
|
|
181
185
|
|
|
182
186
|
### WC-REL-16 · The package ships only the files it names
|
|
183
187
|
|
|
188
|
+
A CLI's `files` holds only what the installed CLI runs from or shows its user: `dist`, the agent skill (`skills`),
|
|
189
|
+
`README.md`, `LICENSE`, and files its code reads or runs after install, such as `scenarios` for `guide`, an `install`
|
|
190
|
+
script, third-party notices and licenses. `CHANGELOG.md` may ship too. It never holds `src/`, tests, fakes or
|
|
191
|
+
fixtures, `scripts/`, `.github/`, `bin/release`, `config/`, `lint/`, `contracts/`, `docs/dev/` or `template.json`.
|
|
192
|
+
|
|
184
193
|
- **Why:** a stray file in the package can leak local data and grows every install.
|
|
185
194
|
- **Enforced by:** `node-ci.yml:check` · `<app>: pnpm package:check` · `whatsapp-cli: pnpm release:check`.
|
|
186
|
-
- **Example:** `package.json` `files` lists what ships.
|
|
187
|
-
`@wirecat/cli-base/release`
|
|
188
|
-
|
|
189
|
-
`
|
|
195
|
+
- **Example:** `package.json` `files` lists what ships. `packContents(root, allowed, said)` from
|
|
196
|
+
`@wirecat/cli-base/release` fails on any path in `npm pack --dry-run` outside `allowed`. It checks the pack against
|
|
197
|
+
the list it is given, not against this rule: a CLI that passes its own `files` list as `allowed`, as the template's
|
|
198
|
+
`scripts/smoke.ts` does, checks only that npm packs what `files` says, and cannot fail on a wrong `files` list.
|
|
199
|
+
`npm pack --dry-run` in gcal-cli (2026-10-27) packs 256 files, `src/`, `scripts/`, `.github/`, `config/`,
|
|
200
|
+
`contracts/`, `lint/` and `template.json` among them, because its `files` list names them; its check cannot see it. The check this rule needs
|
|
201
|
+
refuses the forbidden paths above whatever `files` says; it belongs in cli-template, so every CLI gets it by
|
|
202
|
+
`upgrade` ([cli-template#77](https://github.com/WireCatLabs/cli-template/issues/77)).
|
|
203
|
+
- **Exceptions:** cli-template's own package ships the skeleton's sources and configs, since `new` copies them; the
|
|
204
|
+
`files` list `new` writes into a CLI follows this rule. Each CLI's `package:check` runs `packContents`; tg and max
|
|
205
|
+
as `release-check.ts --package-only`.
|
|
190
206
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
191
207
|
|
|
192
208
|
## Sources
|
|
@@ -67,8 +67,9 @@ name. Any other top-level key is written as `[not allowed]`. A key that names co
|
|
|
67
67
|
### WC-SECR-4 · Secrets come from the environment, then the OS keyring, then a private file
|
|
68
68
|
|
|
69
69
|
cli-base's `Credentials` reads the environment variable first (for CI), then the OS keyring, then a file with mode
|
|
70
|
-
`0600`. The keyring service is the
|
|
71
|
-
|
|
70
|
+
`0600`. The keyring service is the CLI's app name, `appName` in its identity (`max-cli`, `gcal-cli`, `cli-memo`), not
|
|
71
|
+
the command word people type (`max`, `gcal`). When the config directory comes from the environment, the service name
|
|
72
|
+
is scoped to that directory (`isolated: true`).
|
|
72
73
|
|
|
73
74
|
- **Why:** the keyring is the safest store on the computer, and a throwaway config directory must not overwrite the
|
|
74
75
|
owner's real keyring entry.
|
|
@@ -274,7 +275,7 @@ general words.
|
|
|
274
275
|
asks for it on that job only. The `zizmor` scan in `release-checks.yml` reports high findings at release, but does
|
|
275
276
|
not fail on them.
|
|
276
277
|
- **Exceptions:** none.
|
|
277
|
-
- **Applies to:** the
|
|
278
|
+
- **Applies to:** the WireCat repositories: every CLI, cli-base, cli-core, cli-testing, cli-docs and community; and
|
|
278
279
|
cli-template.
|
|
279
280
|
**Since:** cli-base 0.41.0. **Rulings:** none.
|
|
280
281
|
|
|
@@ -309,16 +310,23 @@ store and runs no search. A CLI declares its provider and may replace any step.
|
|
|
309
310
|
printed. A CLI that signs in another way (a bot token, a session) does not use the module.
|
|
310
311
|
- **Applies to:** every CLI that signs in with OAuth. **Since:** cli-base 0.57.1. **Rulings:** NEED-1734.
|
|
311
312
|
|
|
312
|
-
### WC-SECR-23 · Meeting passcodes and the query of a join link never reach the store
|
|
313
|
+
### WC-SECR-23 · Meeting passcodes and the query of a join link never reach the store, the output or an agent
|
|
313
314
|
|
|
314
|
-
- **Why:** a passcode, PIN or join-link query lets anyone who reads the store
|
|
315
|
+
- **Why:** a passcode, PIN or join-link query lets anyone who reads the store, a terminal log or an agent's
|
|
316
|
+
transcript join the meeting.
|
|
315
317
|
- **Enforced by:** `cli-core: src/store/calendar.test.ts`.
|
|
316
318
|
- **Example:** a calendar entry's description loses each line that carries a passcode, password or PIN, and every
|
|
317
319
|
link loses its query string, so `join_url` holds `https://zoom.us/j/123` without `?pwd=`. A meeting is matched by
|
|
318
320
|
its room (`join_key`, such as `zoom:<number>`), never by a link that carries the passcode.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
The same holds for what a command prints and what an MCP tool answers, also when the answer comes straight from
|
|
322
|
+
the provider and never touched the store: a generic API command removes the provider's passcode fields (Google's
|
|
323
|
+
`conferenceData.entryPoints[].passcode`, `pin`, `accessCode`, `password`), the query of every join link and the
|
|
324
|
+
dial sequence after a phone number, and the passcode lines of a description or a location.
|
|
325
|
+
- **Exceptions:** a title that names a PIN or a password is kept: it is the meeting's name. Known gap, as of
|
|
326
|
+
2026-10-27: gcal's `api` answers drop the conference entry points' secrets (`src/conference.ts`) but keep the
|
|
327
|
+
description and location as Google sent them.
|
|
328
|
+
- **Applies to:** cli-core and every CLI that stores or shows meetings or calendar entries. **Since:** cli-base 0.57.1
|
|
329
|
+
(output and agent answers: cli-base 0.65.0). **Rulings:** none (Wave 3 requirements 3.4).
|
|
322
330
|
|
|
323
331
|
## Sources
|
|
324
332
|
|
package/docs/standards/store.md
CHANGED
|
@@ -30,7 +30,7 @@ Related pages: which account a read covers, and how a read across accounts is as
|
|
|
30
30
|
`%LOCALAPPDATA%\WireCat\Data\` on Windows). Rows are keyed by provider and account. `storePath` is the only code
|
|
31
31
|
that reads `MESSAGING_STORE` and `WIRECAT_STATE_DIR`.
|
|
32
32
|
- **Exceptions:** none. Older documents that name `messages.db` describe a store that is no longer used.
|
|
33
|
-
- **Applies to:** CLIs that keep local data through cli-core (tg, max, wa, memo, drive, zoom). **Since:** cli-base 0.41.0.
|
|
33
|
+
- **Applies to:** CLIs that keep local data through cli-core (tg, max, wa, memo, drive, zoom, gcal). **Since:** cli-base 0.41.0.
|
|
34
34
|
**Rulings:** NEED-383.
|
|
35
35
|
|
|
36
36
|
### WC-STORE-2 · Reach the data through its area, and add a new kind of record as a new area
|
|
@@ -52,8 +52,9 @@ Related pages: which account a read covers, and how a read across accounts is as
|
|
|
52
52
|
accounts on purpose asks for the whole store with `crossAccount(deps, reason)`; see [Accounts](accounts.md).
|
|
53
53
|
- **Exceptions:** `forAccount` binds only the account areas (accounts, chats, sync, messages, attachments, roster,
|
|
54
54
|
people, conversations, vectors, counters, tags, mentions). The `chats`, `conversations` and `attachments` services
|
|
55
|
-
use the bound store; the other services still pair `deps.store()` with `deps.account()`.
|
|
56
|
-
|
|
55
|
+
use the bound store; the other services still pair `deps.store()` with `deps.account()`. The calendar areas
|
|
56
|
+
(calendars, calendar entries, their sync cursors) are not bound either: cli-core's calendar commands pass
|
|
57
|
+
`accountId` to every call, a known gap as of 2026-10-27. New code uses the bound store.
|
|
57
58
|
- **Applies to:** cli-core and every CLI that adds a service. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
58
59
|
|
|
59
60
|
### WC-STORE-4 · Reach the store through its public module, never a table module
|
|
@@ -44,7 +44,7 @@ and which run after merge is in [CI](ci.md).
|
|
|
44
44
|
file, listed after the shared one, adds only what is its own, such as a default profile.
|
|
45
45
|
- **Exceptions:** keep coverage reports and debug traces outside the sandbox, with bounded retention. Never clean
|
|
46
46
|
another session's scratch directory as test teardown.
|
|
47
|
-
- **Applies to:** all CLIs and the shared packages. cli-base, cli-core,
|
|
47
|
+
- **Applies to:** all CLIs and the shared packages. cli-base, cli-core, every CLI and cli-template load the shared
|
|
48
48
|
setup first in `setupFiles`; cli-testing does not use it yet. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
49
49
|
|
|
50
50
|
### WC-TEST-2 · No test opens a real credential store
|
|
@@ -94,7 +94,9 @@ and which run after merge is in [CI](ci.md).
|
|
|
94
94
|
- **Enforced by:** review only.
|
|
95
95
|
- **Example:** waits go through an injected `SleepLike`, and `fakeClock()` from `@wirecat/cli-base/testing` records
|
|
96
96
|
them without waiting. A test harness passes `sleep: async () => {}`. `pnpm test:slow` lists the 20 slowest tests
|
|
97
|
-
and the 10 slowest files.
|
|
97
|
+
and the 10 slowest files. A test may wait for a local process or server it started, for example polling its
|
|
98
|
+
status with `vi.waitFor` and a timeout shorter than the test's own; that waits for an event, not for time. A
|
|
99
|
+
fixed sleep or `setTimeout` is still a wait for real time.
|
|
98
100
|
- **Exceptions:** none. A new wait in the code gets a seam, never a longer test timeout.
|
|
99
101
|
- **Applies to:** all CLIs and the shared packages. **Since:** cli-base 0.41.0. **Rulings:** dev-process:4.
|
|
100
102
|
|
|
@@ -169,9 +171,14 @@ and which run after merge is in [CI](ci.md).
|
|
|
169
171
|
- **Enforced by:** `node-ci.yml:check` · `<app>: pnpm test:matrix:check`.
|
|
170
172
|
- **Example:** under vitest, `run()` appends each parsed command path and option name (never a value) to
|
|
171
173
|
`coverage/argv.jsonl`. `pnpm test:matrix` turns that into `docs/dev/test-matrix.md` and fails on a command or
|
|
172
|
-
option with neither a test nor a reason. A
|
|
173
|
-
function directly does not
|
|
174
|
-
|
|
174
|
+
option with neither a test nor a reason. A test counts only when it runs the command through `run()`: a test that
|
|
175
|
+
calls a function directly records no command line, so the matrix does not see it. A reason says what covers the
|
|
176
|
+
command instead: the live scenario that runs it, or, for a generated group, the two tests below.
|
|
177
|
+
- **Exceptions:** commands generated from a service's API description, such as drive's and gcal's
|
|
178
|
+
`api <method>` from a Google Discovery snapshot, share one reason for the whole group (`api *` and its options).
|
|
179
|
+
It names two tests: one that checks every generated command's options against the snapshot, and one that drives a
|
|
180
|
+
generated command through `run()` into the executor every generated command shares. A hand-written command in the
|
|
181
|
+
same CLI still needs its own test or live scenario.
|
|
175
182
|
- **Applies to:** all CLIs. memo's check fails only on a stale page, not yet on an option with no test.
|
|
176
183
|
**Since:** cli-base 0.41.0. **Rulings:** NEED-359.
|
|
177
184
|
|
|
@@ -192,9 +199,13 @@ and which run after merge is in [CI](ci.md).
|
|
|
192
199
|
- **Enforced by:** `node-ci.yml:check` · `<app>: pnpm contracts:upgrade`.
|
|
193
200
|
- **Example:** `pnpm contracts:upgrade` runs the previous published package and this build on the same synthetic
|
|
194
201
|
state and checks that the data survives. Each store migration also has its own upgrade test (WC-STORE-9).
|
|
195
|
-
- **Exceptions:** a CLI with no local state, or with no published version yet. Local state is data the CLI
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
- **Exceptions:** a CLI with no local state, or with no published version yet. Local state is data the CLI writes
|
|
203
|
+
and reads back in a later run, such as a store, a session or a cache. The shared store counts, also when cli-core's
|
|
204
|
+
code writes it, because the CLI's release decides which cli-core and so which migrations run: gcal's calendars and
|
|
205
|
+
events in the store are its local state. A config file, run records, the
|
|
206
|
+
update check, MCP logins and lock files that only cli-core writes do not count. Tests still keep them in the
|
|
207
|
+
sandboxed home, like every other path
|
|
208
|
+
([WC-TEST-1](#wc-test-1--point-every-path-a-test-could-write-at-a-temporary-directory)).
|
|
198
209
|
- **Applies to:** all CLIs with local state. wa has no upgrade contract yet. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
199
210
|
|
|
200
211
|
### WC-TEST-15 · A refactor keeps `commands --json` and the MCP tool list as they were
|
package/docs/standards/time.md
CHANGED
|
@@ -76,7 +76,14 @@ local midnight. The answer gives the window it used, in UTC.
|
|
|
76
76
|
- **Why:** "messages of 1 September" means the owner's day, not the UTC day, which starts hours earlier or later.
|
|
77
77
|
- **Enforced by:** review only.
|
|
78
78
|
- **Example:** in `Asia/Vladivostok`, `--since-time 2026-09-01` gives `meta.window.since` = `2026-08-31T14:00:00.000Z`.
|
|
79
|
-
-
|
|
79
|
+
A thing that has only dates, such as an all-day event (`start_date` 2026-09-01, `end_date` 2026-09-02, the end
|
|
80
|
+
not included), matches by comparing local dates, never UTC moments: it is in the window when its dates overlap the
|
|
81
|
+
local dates the window covers. So in `Europe/Madrid` a filter for 1 September returns the all-day event of
|
|
82
|
+
1 September and not the one of 31 August, whatever zone the event's calendar is in.
|
|
83
|
+
- **Exceptions:** none. Known gap, as of 2026-10-27, read from the code and not run: cli-core's event filter
|
|
84
|
+
(`src/store/sqlite/calendar.ts`, the `events` query) compares `starts_at` and `ends_at` with the UTC window for
|
|
85
|
+
all-day events too, and gcal stores an all-day event's start as midnight in its calendar's zone. The two agree
|
|
86
|
+
when the calendar's zone is the zone in use, and miss by a day at the edges when it is not.
|
|
80
87
|
- **Applies to:** all CLIs. **Since:** cli-base 0.48.0. **Rulings:** none.
|
|
81
88
|
|
|
82
89
|
### WC-TIME-6 · Run the tests in a zone that is not UTC, and prove stored values do not depend on it
|