@wirecat/cli-base 0.64.0 → 0.65.1
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 +131 -0
- package/dist/credentials.d.ts +3 -0
- package/dist/credentials.js +13 -3
- package/dist/dev/bundle.d.ts +6 -0
- package/dist/dev/bundle.js +12 -3
- package/dist/dev/land.d.ts +4 -3
- package/dist/dev/land.js +4 -3
- package/dist/dev/main.js +4 -0
- package/dist/standards/audit.js +9 -1
- package/dist/standards/flags-calendar.js +13 -0
- package/dist/standards/flags-what.js +7 -0
- package/dist/standards/vocabulary.js +3 -1
- package/docs/standards/README.md +3 -3
- package/docs/standards/accounts.md +42 -9
- package/docs/standards/architecture.md +19 -12
- package/docs/standards/ci.md +1 -0
- package/docs/standards/commands.md +36 -12
- package/docs/standards/data-model.md +5 -1
- package/docs/standards/docs.md +4 -1
- package/docs/standards/kit-surface.md +5 -4
- package/docs/standards/layout.md +3 -2
- package/docs/standards/lifecycle.md +69 -8
- package/docs/standards/mcp.md +48 -24
- package/docs/standards/new-cli.md +2 -2
- package/docs/standards/output.md +13 -2
- package/docs/standards/permissions.md +24 -11
- package/docs/standards/releases.md +8 -2
- package/docs/standards/security.md +29 -5
- package/docs/standards/store.md +3 -2
- package/docs/standards/testing.md +10 -5
- package/docs/standards/time.md +8 -1
- package/docs/standards/toolchain.md +7 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,137 @@ Versions before 0.8.0 are in the [git tags](https://github.com/WireCatLabs/cli-b
|
|
|
8
8
|
Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
|
|
9
9
|
[`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).
|
|
10
10
|
|
|
11
|
+
## 0.65.1 — 29.10.2026
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`Credentials` with `isolated` no longer writes the system keyring** ([WC-SECR-24](docs/standards/security.md), new:
|
|
16
|
+
a run with moved folders never writes the owner's keyring). A per-folder keyring service (`<app>:<folder>`) was
|
|
17
|
+
leaving one copy of a sign-in per temp folder. A secret goes to the private file in the moved config folder
|
|
18
|
+
instead; one saved in the keyring before is still read, so an existing per-folder sign-in keeps working until its
|
|
19
|
+
next save moves it to the file. A test passing its own keyring (`memoryKeyring()`) is unchanged.
|
|
20
|
+
- **`cli-dev changelog restore` no longer fails a package's first release.** With no `v<x.y.z>` tag yet it says there
|
|
21
|
+
is nothing to restore and exits 0; `bin/release` stopped at "no v<x.y.z> tag to compare the changelog with".
|
|
22
|
+
|
|
23
|
+
## 0.65.0 — 27.10.2026
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Calendar words for moving an event and for "this and all following".** Flag `--following` (`target`, switch):
|
|
28
|
+
this occurrence and every later one of a repeating event (`events update|delete --following`), beside `--series`.
|
|
29
|
+
`--scope` already means personal or work, so it is not reused. Flag `--to-calendar <id>` (`target`, id): the
|
|
30
|
+
calendar an event moves to (`events move --to-calendar`); `--calendar` keeps its meaning, the calendar the event is
|
|
31
|
+
on. The verb `move` now says what moves in general: a file to a folder, an event to a calendar. cli-core's shared
|
|
32
|
+
calendar commands use all three; gcal-cli carries `lint/overrides.json` entries for them until this release.
|
|
33
|
+
- **Every CLI that signs in has `session start`, `session status`, `session end` and a profile-free `session list`.**
|
|
34
|
+
[WC-ACCT-12](docs/standards/accounts.md#wc-acct-12--sign-in-is-session-start-status-and-end-and-session-list-names-the-profiles)
|
|
35
|
+
is the owner's ruling: `status` says whether one profile is signed in, `list` which profiles exist. Each
|
|
36
|
+
CLI's missing commands are listed as known gaps: tg, max and wa lack `session status` and list profiles with
|
|
37
|
+
`account list`; drive lacks `session status`; gcal lacks `session list`; zm lacks `session end` and `session list`;
|
|
38
|
+
memo has no `session` group.
|
|
39
|
+
- **A sync spends a request budget sized to its deadline and says how to continue.**
|
|
40
|
+
[WC-LIFE-16](docs/standards/lifecycle.md#wc-life-16--a-sync-spends-a-request-budget-sized-to-its-deadline-and-its-answer-says-how-to-continue)
|
|
41
|
+
describes what cli-core 0.464.0 (cli-core #2118) builds for the calendar sync: `floor((deadline − margin) /
|
|
42
|
+
requestMs)` requests, one `requestMs` per CLI, `--max-pages` only lowering it, a partial stop that exits 0 with the
|
|
43
|
+
exact command to continue, and progress in the sync's status command. The other syncs and gcal's own `requestMs`
|
|
44
|
+
are listed as gaps.
|
|
45
|
+
|
|
46
|
+
- **The command vocabulary has the verbs `serve` (run a server in this process and answer its clients until stopped)
|
|
47
|
+
and `revoke` (take back what was granted, so it stops working).** cli-core 0.463.0 names the MCP server
|
|
48
|
+
`mcp serve` and gives forgetting browser logins its own command, `mcp revoke`; `revoke` also covers
|
|
49
|
+
`chats link revoke` and the `consents revoke` commands that were baselined as unknown verbs.
|
|
50
|
+
|
|
51
|
+
### Changed — may break callers
|
|
52
|
+
|
|
53
|
+
- **A command that reads the service and only saves what it read is a read, also over MCP.**
|
|
54
|
+
[WC-CMD-17](docs/standards/commands.md#wc-cmd-17--declare-effects-on-every-command) defines a write, and WC-CMD-6,
|
|
55
|
+
WC-CMD-28, [WC-MCP-4](docs/standards/mcp.md#wc-mcp-4--read-runs-only-reading-commands-and-write-only-writing-ones)
|
|
56
|
+
and WC-OUT-9 point at it: `{ remote: "read", local: "write" }` (`sync`, `calendars list`) runs through the `read`
|
|
57
|
+
tool; `{ remote: "none", local: "write" }` (`store reindex`) stays a write. cli-base's `writes()` still counts every
|
|
58
|
+
local write, so cli-core serves `sync` through `write` until a follow-up changes it; watch for that release, which
|
|
59
|
+
moves those commands between tools.
|
|
60
|
+
- **Deadlines: 30 s for a one-shot command, 5 min for a cooperative one at a terminal and 45 s over MCP.**
|
|
61
|
+
[WC-LIFE-5](docs/standards/lifecycle.md#wc-life-5--bound-every-one-shot-command-by-a-deadline) and WC-LIFE-10 say
|
|
62
|
+
so, as cli-core 0.464.0 builds it, because MCP clients cut a tool call at about 60 s.
|
|
63
|
+
- **A non-messenger CLI allows ordinary writes, asks before deletes, and denies mail to other people and deleting
|
|
64
|
+
a whole series.**
|
|
65
|
+
[WC-PERM-22](docs/standards/permissions.md#wc-perm-22--a-cli-that-is-not-a-messenger-declares-its-own-keys-and-defaults-with-commandpermissions)
|
|
66
|
+
follows the owner's ruling, and a CLI's generic API path must check the shared keys (`events.notify`,
|
|
67
|
+
`events.delete.series`) too. Its example said sharing a calendar is `deny`; the owner made it `ask`. cli-core's
|
|
68
|
+
`CALENDAR_WRITE_DEFAULTS` and gcal's `api` defaults do not follow it yet; both are listed as gaps.
|
|
69
|
+
- **Meeting passcodes stay out of command output and agent answers, not only out of the store.**
|
|
70
|
+
[WC-SECR-23](docs/standards/security.md) covers answers that come straight from the provider: gcal's `api` answers
|
|
71
|
+
still keep a description's passcode lines, a listed gap.
|
|
72
|
+
- **Each CLI pins the command catalogue behind its MCP tools.** The [MCP](docs/standards/mcp.md) page no longer
|
|
73
|
+
claims `mcp-tools.json` pins the catalogue: with three tools it does not. `contracts/mcp-catalogue.json` does; tg and
|
|
74
|
+
max have it, and wa, zm, gcal and drive do not yet.
|
|
75
|
+
|
|
76
|
+
- **An MCP server serves the profiles it is told, and refuses without them.**
|
|
77
|
+
[WC-MCP-9](docs/standards/mcp.md#wc-mcp-9--a-server-serves-the-profiles-it-is-told-with-one-connection-for-each)
|
|
78
|
+
now allows one named profile (`<cli> <profile> mcp serve`), a list (`<cli> mcp serve --profiles work,personal`) or
|
|
79
|
+
every profile (`<cli> mcp serve --profiles all`), with one messenger connection per served profile. With no profile
|
|
80
|
+
and no `--profiles`, `mcp serve` fails with `validation_error` and lists the profiles; it never serves every profile
|
|
81
|
+
by default. With several profiles, each call names its profile. WC-ACCT-1, WC-ACCT-2 and WC-ACCT-5 say how
|
|
82
|
+
`--profiles` fits the profile rules, and a profile lock refuses `--profiles all`. The flag registry has `--profiles`.
|
|
83
|
+
The owner ruled it; cli-core does not build `--profiles` yet.
|
|
84
|
+
- **The handbook names the MCP server `mcp serve` and has no `mcp --revoke` exception.** The
|
|
85
|
+
[MCP](docs/standards/mcp.md) and [Lifecycle](docs/standards/lifecycle.md) pages say `mcp serve`, `mcp serve --http`,
|
|
86
|
+
`mcp serve --permission`, `bot mcp serve` and `mcp revoke`, as cli-core 0.463.0 builds them.
|
|
87
|
+
[WC-CMD-6](docs/standards/commands.md#wc-cmd-6--give-one-action-or-one-report-one-leaf-command) no longer lets
|
|
88
|
+
`--revoke` stay an option: forgetting every browser login is the `mcp revoke` command. A CLI with its own server
|
|
89
|
+
(drive, memo, zoom) moves to the same shape.
|
|
90
|
+
- **A lint baseline entry for `serve` or `revoke` goes stale, and `cli-dev lint` fails on it.** cli-core lists
|
|
91
|
+
`bot mcp serve`, `mcp serve`, `mcp revoke` and `replies consents revoke` in `lint/baseline/unknown-verb.json`;
|
|
92
|
+
tg-cli and max-cli list `replies consents revoke`. Remove those entries in the same change that takes this version.
|
|
93
|
+
- **The HTTP MCP server rule covers every CLI that offers `mcp serve --http`, not only the messenger CLIs.**
|
|
94
|
+
[WC-MCP-12](docs/standards/mcp.md#wc-mcp-12--an-mcp-server-over-http-follows-the-same-permissions-and-short-lived-logins)
|
|
95
|
+
now applies to cli-template and every CLI made from it, which have offered `mcp serve --http` with no rule over it.
|
|
96
|
+
The template already follows it; a CLI that changed its copy of the HTTP server checks it against the rule.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **`cli-dev audit` compares gcal only on the commands it already shares with every CLI, for now.** cli-base 0.64.0
|
|
101
|
+
put gcal into the `all` parity group, and gcal's release audit then blocked on 20 `same-path-contract` differences
|
|
102
|
+
in `doctor`, `mcp`, `search` and `skill` that already exist among the other six published CLIs and that gcal
|
|
103
|
+
cannot remove alone (gcal-cli#13). gcal leaves `all` and has its own group, `gcal-shared`, with every CLI over
|
|
104
|
+
`commands`, `upgrade`, `notes` and `meetings`. It rejoins `all` once tg, max, wa, drive, zoom and memo release on
|
|
105
|
+
cli-core's shared `mcp serve`, `doctor` and `skill` ([#390](https://github.com/WireCatLabs/cli-base/issues/390)).
|
|
106
|
+
The other CLIs' audits are unchanged: a path is compared across every member of every group that covers it.
|
|
107
|
+
|
|
108
|
+
- **`cli-dev bundle`'s loader finds a server after a flag: `<cli> personal --read-only mcp serve`.** It looked for
|
|
109
|
+
`serve`, `watch` or `mcp` in the first three words of the command, and since cli-core 0.463 named the server
|
|
110
|
+
`mcp serve`, a flag before it pushed `serve` to the fourth word. Such a server loaded no chunks ahead and could fail
|
|
111
|
+
after an in-place upgrade (measured on drive: 91 of 138 chunks loaded, instead of 138). The loader now skips flags
|
|
112
|
+
and looks at four words. Rebuild a CLI with this release to take it.
|
|
113
|
+
- **The handbook no longer contradicts itself on gcal's audit findings.**
|
|
114
|
+
- WC-OUT-8 and WC-OUT-14: an answer with `meta.complete: false` is an answer on stdout, also after a non-zero
|
|
115
|
+
exit; only a run with no answer prints one JSON error with stdout empty.
|
|
116
|
+
- WC-CMD-10 names `--max-pages` a `budget` option, not a paging one, and says why the calendar `--id` exists beside
|
|
117
|
+
`--operation-id`; gcal's `events create`, with `--id` and no `--operation-id`, is a listed gap. WC-CMD-11 lists `--duration-minutes` as a known gap until the vocabulary has
|
|
118
|
+
`--duration <duration>`.
|
|
119
|
+
- WC-REL-16 says `packContents` checks the pack against the list it is given, so a check that passes the `files`
|
|
120
|
+
list itself cannot fail on a wrong list; the fix belongs in cli-template.
|
|
121
|
+
- WC-TIME-5 matches an all-day event by local dates, not UTC moments; cli-core's event filter is a listed gap.
|
|
122
|
+
- WC-ARCH-3 counts generated API types as vendor types, WC-ARCH-5 and WC-ARCH-6 cover calendar CLIs, and
|
|
123
|
+
WC-ARCH-21's example is gcal's real provider entry.
|
|
124
|
+
- WC-TOOL-16 puts a word waiting for the vocabulary in `lint/overrides.json` with a link and a date, never in the
|
|
125
|
+
baseline. The [Documents](docs/standards/docs.md#wc-doc-3--every-document-states-current-facts-rewrite-a-wrong-sentence-in-place)
|
|
126
|
+
rule on current facts sends a false template-owned file to cli-template.
|
|
127
|
+
- WC-DATA-3 makes a calendar entry's `external_id` unique per calendar, WC-STORE-3 lists the calendar areas as not
|
|
128
|
+
bound, WC-TEST-12 says what a reason names, and WC-TEST-14 counts the shared store as local state.
|
|
129
|
+
- The "who follows" lists name gcal.
|
|
130
|
+
|
|
131
|
+
- **The handbook's pages on the template match cli-template 0.9.0.** The [Kit surface](docs/standards/kit-surface.md)
|
|
132
|
+
table lists `LOCAL_WRITE` and `revokeAll`, which the template now imports, and counts imports loaded on demand.
|
|
133
|
+
[New CLI](docs/standards/new-cli.md) says the MCP server has three tools that reach every command, not a tool for
|
|
134
|
+
every command.
|
|
135
|
+
- **The handbook says where a lint violation that a shared package forces goes, and which cli-core files are not a
|
|
136
|
+
CLI's local state.** [WC-TOOL-16](docs/standards/toolchain.md) puts such a violation in the CLI's lint baseline
|
|
137
|
+
until the shared package catches up.
|
|
138
|
+
[WC-TEST-14](docs/standards/testing.md#wc-test-14--check-the-upgrade-from-the-previous-published-version) names
|
|
139
|
+
the update check, MCP logins and lock files that only cli-core writes as outside the upgrade contract; tests still
|
|
140
|
+
keep them in the sandboxed home.
|
|
141
|
+
|
|
11
142
|
## 0.64.0 — 27.10.2026
|
|
12
143
|
|
|
13
144
|
### Added
|
package/dist/credentials.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export interface CredentialsOptions {
|
|
|
19
19
|
* not — it overwrites the real secret for that account. In `brazecli` that destroyed two working
|
|
20
20
|
* keys on 2026-09-14, and a keyring entry cannot be read back out. Passing `true` here scopes the
|
|
21
21
|
* service name to the directory, making the isolation people already assume they have real.
|
|
22
|
+
*
|
|
23
|
+
* With the system keyring it also keeps new secrets out of it: they go to the file in the moved directory, so a test
|
|
24
|
+
* or worktree run leaves nothing in the owner's keyring (WC-SECR-24). A secret saved there before is still read.
|
|
22
25
|
*/
|
|
23
26
|
isolated?: boolean;
|
|
24
27
|
/** Read before the keyring, for CI. */
|
package/dist/credentials.js
CHANGED
|
@@ -21,6 +21,7 @@ export class Credentials {
|
|
|
21
21
|
#envVar;
|
|
22
22
|
#fileName;
|
|
23
23
|
#warn;
|
|
24
|
+
#fileFirst;
|
|
24
25
|
#warned = false;
|
|
25
26
|
constructor(options) {
|
|
26
27
|
this.#configDir = options.configDir;
|
|
@@ -31,21 +32,30 @@ export class Credentials {
|
|
|
31
32
|
this.#fileName = options.fileName ?? "credentials.json";
|
|
32
33
|
this.#warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
|
|
33
34
|
this.#service = keyringService(options.service, options.configDir, options.isolated);
|
|
35
|
+
this.#fileFirst = options.isolated === true && this.#keyring === systemKeyring;
|
|
34
36
|
}
|
|
35
37
|
read(account) {
|
|
36
38
|
const fromEnv = this.#envVar ? this.#env[this.#envVar]?.trim() : undefined;
|
|
37
39
|
if (fromEnv)
|
|
38
40
|
return { secret: fromEnv, source: "environment" };
|
|
41
|
+
const fromFile = () => {
|
|
42
|
+
const secret = this.#readFile()?.[account]?.secret;
|
|
43
|
+
return secret ? { secret, source: "file" } : undefined;
|
|
44
|
+
};
|
|
45
|
+
if (this.#fileFirst) {
|
|
46
|
+
const found = fromFile();
|
|
47
|
+
if (found)
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
39
50
|
if (this.#storage !== "file") {
|
|
40
51
|
const fromKeyring = this.#tryKeyring(() => this.#keyring.get(this.#service, account));
|
|
41
52
|
if (fromKeyring)
|
|
42
53
|
return { secret: fromKeyring, source: "keyring" };
|
|
43
54
|
}
|
|
44
|
-
|
|
45
|
-
return fromFile ? { secret: fromFile, source: "file" } : undefined;
|
|
55
|
+
return this.#fileFirst ? undefined : fromFile();
|
|
46
56
|
}
|
|
47
57
|
write(account, secret) {
|
|
48
|
-
if (this.#storage !== "file") {
|
|
58
|
+
if (this.#storage !== "file" && !this.#fileFirst) {
|
|
49
59
|
const stored = this.#tryKeyring(() => {
|
|
50
60
|
this.#keyring.set(this.#service, account, secret);
|
|
51
61
|
return true;
|
package/dist/dev/bundle.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export declare const DEFAULT_EXTERNAL: string[];
|
|
|
20
20
|
export declare const DEFAULT_ANCHORED: string[];
|
|
21
21
|
export declare const DEFAULT_HARMLESS: string[];
|
|
22
22
|
export declare const LOADER_MARK = "// cli-dev bundle loader\n";
|
|
23
|
+
/**
|
|
24
|
+
* Whether the loader runs a server: `serve`, `watch` or `mcp serve`, or `mcp` alone as before cli-core 0.463. Flags
|
|
25
|
+
* are skipped, so `<cli> personal --read-only mcp serve` counts; four words leave room for one flag's value. It is
|
|
26
|
+
* written into the loader as source, so it uses nothing from outside.
|
|
27
|
+
*/
|
|
28
|
+
export declare const isServer: (words: readonly string[]) => boolean;
|
|
23
29
|
/** The bin that replaces the compiled one: `name` is the bundle's entry, `chunks` what a server preloads. */
|
|
24
30
|
export declare const loaderSource: (name: string, version: string, chunks: readonly string[]) => string;
|
|
25
31
|
export declare const bundle: (options: BundleOptions) => Promise<BundleResult>;
|
package/dist/dev/bundle.js
CHANGED
|
@@ -166,6 +166,17 @@ const commonJsNames = {
|
|
|
166
166
|
});
|
|
167
167
|
},
|
|
168
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Whether the loader runs a server: `serve`, `watch` or `mcp serve`, or `mcp` alone as before cli-core 0.463. Flags
|
|
171
|
+
* are skipped, so `<cli> personal --read-only mcp serve` counts; four words leave room for one flag's value. It is
|
|
172
|
+
* written into the loader as source, so it uses nothing from outside.
|
|
173
|
+
*/
|
|
174
|
+
export const isServer = (words) => {
|
|
175
|
+
const plain = words.filter((word) => !word.startsWith("-"));
|
|
176
|
+
return plain
|
|
177
|
+
.slice(0, 4)
|
|
178
|
+
.some((word, at) => word === "serve" || word === "watch" || (word === "mcp" && plain[at + 1] === undefined));
|
|
179
|
+
};
|
|
169
180
|
/** The bin that replaces the compiled one: `name` is the bundle's entry, `chunks` what a server preloads. */
|
|
170
181
|
export const loaderSource = (name, version, chunks) => `#!/usr/bin/env node
|
|
171
182
|
${LOADER_MARK}import * as module from "node:module"
|
|
@@ -181,9 +192,7 @@ if (
|
|
|
181
192
|
} else {
|
|
182
193
|
// Before the bundle compiles: the bundle's own call comes after its chunks.
|
|
183
194
|
module.enableCompileCache()
|
|
184
|
-
const serves = words
|
|
185
|
-
.slice(0, 3)
|
|
186
|
-
.some((word, at) => ["serve", "watch"].includes(word) || (word === "mcp" && !/^[^-]/.test(words[at + 1] ?? "")))
|
|
195
|
+
const serves = (${isServer.toString()})(words)
|
|
187
196
|
if (serves && module.registerHooks) {
|
|
188
197
|
const bundle = new URL("../bundle/", import.meta.url).href
|
|
189
198
|
module.registerHooks({
|
package/dist/dev/land.d.ts
CHANGED
|
@@ -39,10 +39,11 @@ export declare const landChange: ({ file, branch, change: changeOf, done, shell,
|
|
|
39
39
|
log: (text: string) => void;
|
|
40
40
|
}) => string | undefined;
|
|
41
41
|
/**
|
|
42
|
-
* The newest `v<x.y.z>` tag in this commit's history and the changelog at it
|
|
43
|
-
* last release does not have its tag, and comparing against an older one would
|
|
42
|
+
* The newest `v<x.y.z>` tag in this commit's history and the changelog at it, or `undefined` before the first release. Tags
|
|
43
|
+
* are fetched first: a clone made before the last release does not have its tag, and comparing against an older one would
|
|
44
|
+
* hide the newest drift.
|
|
44
45
|
*/
|
|
45
46
|
export declare const newestTag: (shell: Shell, path: string) => {
|
|
46
47
|
version: string;
|
|
47
48
|
text: string;
|
|
48
|
-
};
|
|
49
|
+
} | undefined;
|
package/dist/dev/land.js
CHANGED
|
@@ -124,8 +124,9 @@ export const landChange = ({ file, branch, change: changeOf, done, shell, log, }
|
|
|
124
124
|
throw new Error(`main moved under ${branch} twice — run bin/release again`);
|
|
125
125
|
};
|
|
126
126
|
/**
|
|
127
|
-
* The newest `v<x.y.z>` tag in this commit's history and the changelog at it
|
|
128
|
-
* last release does not have its tag, and comparing against an older one would
|
|
127
|
+
* The newest `v<x.y.z>` tag in this commit's history and the changelog at it, or `undefined` before the first release. Tags
|
|
128
|
+
* are fetched first: a clone made before the last release does not have its tag, and comparing against an older one would
|
|
129
|
+
* hide the newest drift.
|
|
129
130
|
*/
|
|
130
131
|
export const newestTag = (shell, path) => {
|
|
131
132
|
const fetched = shell.run("git", ["fetch", "-q", "--tags", "origin"]);
|
|
@@ -141,7 +142,7 @@ export const newestTag = (shell, path) => {
|
|
|
141
142
|
return (y?.[0] ?? 0) - (x?.[0] ?? 0) || (y?.[1] ?? 0) - (x?.[1] ?? 0) || (y?.[2] ?? 0) - (x?.[2] ?? 0);
|
|
142
143
|
});
|
|
143
144
|
if (!version)
|
|
144
|
-
|
|
145
|
+
return undefined;
|
|
145
146
|
const shown = shell.run("git", ["show", `v${version}:${path}`]);
|
|
146
147
|
if (!shown.ok)
|
|
147
148
|
throw new Error(`git show v${version}:${path} failed: ${shown.out}`);
|
package/dist/dev/main.js
CHANGED
|
@@ -133,6 +133,10 @@ const changelog = (args, io) => {
|
|
|
133
133
|
if (action === "restore") {
|
|
134
134
|
const shell = processShell(io.cwd);
|
|
135
135
|
const tag = newestTag(shell, relative(io.cwd, file));
|
|
136
|
+
if (!tag) {
|
|
137
|
+
io.err("no v<x.y.z> tag yet: a first release has nothing to restore");
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
136
140
|
if (!taggedSection(tag.text, tag.version)) {
|
|
137
141
|
io.err(`v${tag.version}'s changelog has no section of its own; nothing to restore`);
|
|
138
142
|
return 0;
|
package/dist/standards/audit.js
CHANGED
|
@@ -64,10 +64,18 @@ export const PARITY_GROUPS = defineParityGroups([
|
|
|
64
64
|
{ name: "messengers", members: ["tg", "max"], covers: "tree", differences: [] },
|
|
65
65
|
{
|
|
66
66
|
name: "all",
|
|
67
|
-
members: ["tg", "max", "memo", "drive", "zm", "wa", "
|
|
67
|
+
members: ["tg", "max", "memo", "drive", "zm", "wa", "mail"],
|
|
68
68
|
covers: ["commands", "doctor", "skill", "mcp", "upgrade", "search", "notes", "meetings"],
|
|
69
69
|
differences: [],
|
|
70
70
|
},
|
|
71
|
+
// gcal is compared on what it already shares with every CLI; doctor, mcp, search and skill wait until the published
|
|
72
|
+
// CLIs agree there (https://github.com/WireCatLabs/cli-base/issues/390), then gcal moves back into "all".
|
|
73
|
+
{
|
|
74
|
+
name: "gcal-shared",
|
|
75
|
+
members: ["tg", "max", "memo", "drive", "zm", "wa", "gcal"],
|
|
76
|
+
covers: ["commands", "upgrade", "notes", "meetings"],
|
|
77
|
+
differences: [],
|
|
78
|
+
},
|
|
71
79
|
]);
|
|
72
80
|
const requiredRootSchema = v.strictObject({ path: v.pipe(v.array(segment), v.minLength(1)), meaning: text });
|
|
73
81
|
export const defineRequiredRoots = (input) => parseRegistry("required roots", v.array(requiredRootSchema), input, (roots) => duplicates(roots.map((root) => root.path.join(" "))).map((path) => `"${path}" is listed twice`));
|
|
@@ -56,4 +56,17 @@ export const CALENDAR_FLAGS = [
|
|
|
56
56
|
type: "switch",
|
|
57
57
|
meaning: "act on the whole repeating event, not only this occurrence",
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
name: "--following",
|
|
61
|
+
role: "target",
|
|
62
|
+
type: "switch",
|
|
63
|
+
meaning: "act on this occurrence and every later one of the repeating event",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "--to-calendar",
|
|
67
|
+
value: "<id>",
|
|
68
|
+
role: "target",
|
|
69
|
+
type: "id",
|
|
70
|
+
meaning: "the calendar it moves to, of the same account",
|
|
71
|
+
},
|
|
59
72
|
];
|
|
@@ -187,6 +187,13 @@ export const WHAT_FLAGS = [
|
|
|
187
187
|
type: "string",
|
|
188
188
|
meaning: "the profile to use, instead of the first word",
|
|
189
189
|
},
|
|
190
|
+
{
|
|
191
|
+
name: "--profiles",
|
|
192
|
+
value: "<names|all>",
|
|
193
|
+
role: "scope",
|
|
194
|
+
type: "list",
|
|
195
|
+
meaning: "the profiles an MCP server serves, comma-separated, or all of them",
|
|
196
|
+
},
|
|
190
197
|
{
|
|
191
198
|
name: "--history-since-time",
|
|
192
199
|
value: "<time>",
|
|
@@ -115,7 +115,9 @@ const verbs = [
|
|
|
115
115
|
["start", "start a running process, or a login session"],
|
|
116
116
|
["stop", "stop a running process"],
|
|
117
117
|
["restart", "stop and start a running process"],
|
|
118
|
+
["serve", "run a server in this process and answer its clients until stopped"],
|
|
118
119
|
["end", "end a login session"],
|
|
120
|
+
["revoke", "take back what was granted, so it stops working: a login, an invite link, a consent"],
|
|
119
121
|
["install", "install a system unit"],
|
|
120
122
|
["uninstall", "remove a system unit"],
|
|
121
123
|
["cancel", "stop a job or a pending thing"],
|
|
@@ -180,7 +182,7 @@ const verbs = [
|
|
|
180
182
|
["reject", "the owner refuses what an agent proposed"],
|
|
181
183
|
["review", "list what waits for the owner's decision"],
|
|
182
184
|
["resolve", "settle one thing that waits for the owner's decision, with the owner's choice"],
|
|
183
|
-
["move", "move a
|
|
185
|
+
["move", "move a thing to another place in the source: a file to a folder, an event to a calendar"],
|
|
184
186
|
["copy", "copy a file in the source"],
|
|
185
187
|
["run", "run a saved job once, now, as its timer would"],
|
|
186
188
|
["claim", "take a thing for a while so no other agent works it at once; the claim lapses by itself"],
|
package/docs/standards/README.md
CHANGED
|
@@ -32,14 +32,14 @@ the four steps that need a person. A new CLI is ready for its first release when
|
|
|
32
32
|
| [Commands](commands.md) | `CMD` | 28 | Paths, vocabulary, flags, arguments, effects, lifetime, query, help, external references |
|
|
33
33
|
| [Output](output.md) | `OUT` | 23 | Data on stdout only, envelopes, errors, exit codes, partial answers, projection |
|
|
34
34
|
| [MCP](mcp.md) | `MCP` | 13 | The MCP tools, how they come from commands, what each permission shows |
|
|
35
|
-
| [Accounts](accounts.md) | `ACCT` |
|
|
35
|
+
| [Accounts](accounts.md) | `ACCT` | 12 | The explicit profile, reads across accounts, scope flags, linked identities, sign-in commands |
|
|
36
36
|
| [Permissions](permissions.md) | `PERM` | 22 | Levels, keys, defaults, hard limits, the write guard and its journal |
|
|
37
37
|
| [Architecture](architecture.md) | `ARCH` | 17 | Layers, dependency direction, vendor types, what goes in which package |
|
|
38
|
-
| [Lifecycle](lifecycle.md) | `LIFE` |
|
|
38
|
+
| [Lifecycle](lifecycle.md) | `LIFE` | 15 | Opening late, who closes, deadlines, long-running commands, the `serve` relay, resumable syncs and their request budget, the run log |
|
|
39
39
|
| [Store](store.md) | `STORE` | 14 | Store areas, account scope, migrations, test isolation |
|
|
40
40
|
| [Data model](data-model.md) | `DATA` | 9 | Core entities, entities and source records, `external_id`, field names, links and duplicates, files, calendar entries |
|
|
41
41
|
| [Testing](testing.md) | `TEST` | 17 | Sandbox, no network, no real waits, leak tests, coverage, contracts, test matrix, parity |
|
|
42
|
-
| [Security](security.md) | `SECR` |
|
|
42
|
+
| [Security](security.md) | `SECR` | 23 | Secrets, logs, keyring, argv, error text, untrusted text, scanners, workflow hardening, reporting, OAuth sign-in, meeting secrets, keyring in moved-folder runs |
|
|
43
43
|
| [Documents](docs.md) | `DOC` | 20 | User pages and developer pages, generated pages, current facts, changelog |
|
|
44
44
|
| [Comparisons](comparisons.md) | `CMP` | 11 | Which tools a comparison page lists, rows as user tasks, agents and safety, where others are better, proof of marks |
|
|
45
45
|
| [Releases](releases.md) | `REL` | 15 | `bin/release`, the release lock, versions, release reports, trusted publishing |
|
|
@@ -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
|
|
|
@@ -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
|
@@ -172,6 +172,7 @@ its prompt in cli-template says so from cli-template 0.8.0. A CLI on an older te
|
|
|
172
172
|
| 02:27 | gcal-cli |
|
|
173
173
|
| 02:31 | cli-memo |
|
|
174
174
|
| 02:38 | cli-core |
|
|
175
|
+
| 02:41 | mail-cli |
|
|
175
176
|
| 02:45 | drive-cli |
|
|
176
177
|
| 02:52 | cli-testing |
|
|
177
178
|
| 02:59 | max-cli |
|