@wirecat/cli-base 0.68.0 → 0.69.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 +31 -0
- package/README.md +1 -0
- package/dist/dev/audit.d.ts +2 -0
- package/dist/dev/audit.js +26 -8
- package/dist/dev/main.js +2 -1
- package/dist/release/markdown.d.ts +8 -2
- package/dist/release/markdown.js +12 -4
- package/dist/standards/audit.d.ts +3 -0
- package/dist/standards/audit.js +16 -2
- package/docs/standards/accounts.md +5 -2
- package/docs/standards/architecture.md +2 -2
- package/docs/standards/commands.md +4 -4
- package/docs/standards/lifecycle.md +2 -2
- package/docs/standards/mcp.md +5 -1
- package/docs/standards/new-cli.md +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,37 @@ 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.69.0 — 01.11.2026
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **[Publishing](docs/PUBLISHING.md): where a CLI is published beyond npm, and how.** npm, ClawHub, the MCP Registry,
|
|
16
|
+
the Claude plugin marketplace, Hermes, Glama, GitHub and wirecat.dev: each one's setup, what a release does, how to
|
|
17
|
+
prove a publish happened (three jobs go green without publishing), the problems met with their fixes, a checklist
|
|
18
|
+
for a new CLI, and each CLI's status. `new-cli.md` points to the checklist.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`markdownFiles` passes over a worktree or clone inside the checkout** (a folder with its own `.git`, such as
|
|
23
|
+
`.worktrees/<topic>`). Its pages are another branch's, and their broken links failed cli-template's release check.
|
|
24
|
+
Every CLI's `docs:check` gets this through its own `docs-rules.ts`, with no change there.
|
|
25
|
+
|
|
26
|
+
- **`cli-dev docs-check` reads a heading's anchor as GitHub does when it has `<…>` in code.** It dropped
|
|
27
|
+
`` `v<version>` `` as an HTML tag and wanted `tag-v-only`, where GitHub makes `tag-vversion-only`. So a working link
|
|
28
|
+
failed and a broken one passed. Two links in `architecture.md` were broken that way and are fixed. After this
|
|
29
|
+
release a CLI's `docs:check` may find links of its own that never worked on GitHub.
|
|
30
|
+
|
|
31
|
+
## 0.68.1 — 31.10.2026
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **The audit accepts a flag that one CLI lacks by design, by name.** A parity difference may carry `absent: "<cli>"`
|
|
36
|
+
with a target of `<path> <flag>` and no CLI list; it covers every finding where only that CLI lacks the flag and the
|
|
37
|
+
others agree, so another CLI joining the group does not make it stale. `PARITY_GROUPS` uses it to declare that memo
|
|
38
|
+
has no profiles of its own (no `--profiles` on `mcp serve`, `mcp revoke`, `mcp server start|status|stop`); memo
|
|
39
|
+
selects other CLIs' accounts with `--source` and `--account`. A run without the absent CLI does not call it stale.
|
|
40
|
+
WC-MCP-9 and WC-ACCT-1 say so. See [#390](https://github.com/WireCatLabs/cli-base/issues/390).
|
|
41
|
+
|
|
11
42
|
## 0.68.0 — 31.10.2026
|
|
12
43
|
|
|
13
44
|
### Removed
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ the command tree as data — the parts every CLI needs and nobody should write t
|
|
|
22
22
|
| [Architecture](docs/dev/ARCHITECTURE.md) | The entry points, the lines this package does not cross, who consumes what |
|
|
23
23
|
| [API reference](docs/REFERENCE.md) | What each entry point holds, with examples |
|
|
24
24
|
| [Shared tooling](docs/TOOLING.md) | The TypeScript, Biome, lefthook, docs and CI setup a CLI takes from here, and `cli-dev` |
|
|
25
|
+
| [Publishing](docs/PUBLISHING.md) | Where a CLI is published beyond npm, how to prove it, and what has gone wrong |
|
|
25
26
|
| [Testing](docs/dev/TESTING.md) | The checks, and what each one is for |
|
|
26
27
|
| [Conventions](docs/dev/CONVENTIONS.md) | How code here is written |
|
|
27
28
|
| [Standards handbook](docs/standards/README.md) | The rules every WireCat CLI follows, one page per area and a new-CLI checklist |
|
package/dist/dev/audit.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface Finding {
|
|
|
26
26
|
/** `<clis> <what>`: `max+tg chats list --limit`, `zm upgrade`, `tg: chats grab` for a lint violation. */
|
|
27
27
|
target: string;
|
|
28
28
|
message: string;
|
|
29
|
+
/** What each CLI showed, for a finding about one flag or output; `absent` when it lacks the flag. */
|
|
30
|
+
values?: Record<string, string>;
|
|
29
31
|
}
|
|
30
32
|
export interface ScoreRow {
|
|
31
33
|
cli: string;
|
package/dist/dev/audit.js
CHANGED
|
@@ -74,6 +74,7 @@ const samePathContract = (inputs, groups) => {
|
|
|
74
74
|
clis: having,
|
|
75
75
|
target: `${named(having)} ${path}${what}`,
|
|
76
76
|
message: describe(values),
|
|
77
|
+
values: Object.fromEntries(values),
|
|
77
78
|
});
|
|
78
79
|
};
|
|
79
80
|
const kinds = new Map([...commands].map(([cli, command]) => [
|
|
@@ -253,18 +254,35 @@ export const auditClis = (inputs, options, rules = AUDIT_RULES) => {
|
|
|
253
254
|
options.capabilities &&
|
|
254
255
|
capabilityDifference(finding.target, finding.clis, inputs.map((input) => input.document), options.capabilities))
|
|
255
256
|
.map((finding) => `${finding.rule} ${finding.target}`));
|
|
256
|
-
|
|
257
|
+
// An `absent` difference names no CLI list: it stands for every finding where only that CLI lacks the flag.
|
|
258
|
+
const differences = options.groups.flatMap((group) => group.differences.flatMap((entry) => {
|
|
259
|
+
if (!entry.absent)
|
|
260
|
+
return [{ group: group.name, ...entry }];
|
|
261
|
+
const matching = findings.filter((finding) => {
|
|
262
|
+
if (finding.rule !== entry.rule || finding.target !== `${named(finding.clis)} ${entry.target}`)
|
|
263
|
+
return false;
|
|
264
|
+
const others = Object.entries(finding.values ?? {}).filter(([cli]) => cli !== entry.absent);
|
|
265
|
+
return (finding.values?.[entry.absent] === "absent" && new Set(others.map(([, value]) => value)).size === 1);
|
|
266
|
+
});
|
|
267
|
+
if (matching.length === 0 && clis.includes(entry.absent))
|
|
268
|
+
problems.push(`stale difference in ${group.name}, no such divergence — remove it: ${entry.rule} ${entry.target} (absent in ${entry.absent})`);
|
|
269
|
+
return matching.map((finding) => ({
|
|
270
|
+
...entry,
|
|
271
|
+
group: group.name,
|
|
272
|
+
target: finding.target,
|
|
273
|
+
expected: finding.message,
|
|
274
|
+
}));
|
|
275
|
+
}));
|
|
276
|
+
const declared = new Map(differences.map((entry) => [`${entry.rule} ${entry.target}`, entry.group]));
|
|
257
277
|
const found = new Set(findings.map((finding) => `${finding.rule} ${finding.target}`));
|
|
258
278
|
for (const [key, group] of declared)
|
|
259
279
|
if (!found.has(key))
|
|
260
280
|
problems.push(`stale difference in ${group}, no such divergence — remove it: ${key}`);
|
|
261
|
-
for (const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
problems.push(`difference in ${group.name} must match the exact current values: ${difference.rule} ${difference.target}`);
|
|
267
|
-
}
|
|
281
|
+
for (const difference of differences) {
|
|
282
|
+
const finding = findings.find((one) => one.rule === difference.rule && one.target === difference.target);
|
|
283
|
+
if (finding && difference.expected !== finding.message) {
|
|
284
|
+
declared.delete(`${difference.rule} ${difference.target}`);
|
|
285
|
+
problems.push(`difference in ${difference.group} must match the exact current values: ${difference.rule} ${difference.target}`);
|
|
268
286
|
}
|
|
269
287
|
}
|
|
270
288
|
for (const group of options.groups)
|
package/dist/dev/main.js
CHANGED
|
@@ -575,7 +575,8 @@ const auditRun = (args, io) => {
|
|
|
575
575
|
group: v.string(),
|
|
576
576
|
rule: v.string(),
|
|
577
577
|
target: v.string(),
|
|
578
|
-
expected: v.string(),
|
|
578
|
+
expected: v.optional(v.string()),
|
|
579
|
+
absent: v.optional(v.string()),
|
|
579
580
|
reason: v.string(),
|
|
580
581
|
}));
|
|
581
582
|
const parsed = v.safeParse(schema, readJson(at(values.differences)));
|
|
@@ -22,10 +22,16 @@ export declare const changelogProblems: (text: string, { version, release, headi
|
|
|
22
22
|
version?: string;
|
|
23
23
|
release: boolean;
|
|
24
24
|
}) => string[];
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. An HTML tag is
|
|
27
|
+
* dropped, but `<…>` inside a code span is text: `v<version>` gives `vversion`.
|
|
28
|
+
*/
|
|
26
29
|
export declare const slug: (heading: string) => string;
|
|
27
30
|
export declare const withoutCode: (text: string) => string;
|
|
28
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Every `.md` under `directory`, not descending into a directory named in `skip`, nor into one with its own `.git`:
|
|
33
|
+
* a worktree or clone inside the checkout is another branch's files, and its broken links are not this one's.
|
|
34
|
+
*/
|
|
29
35
|
export declare const markdownFiles: (directory: string, skip?: ReadonlySet<string>) => string[];
|
|
30
36
|
export type DocsRules = {
|
|
31
37
|
/** Absolute paths; a missing one is passed over. */
|
package/dist/release/markdown.js
CHANGED
|
@@ -73,10 +73,15 @@ export const changelogProblems = (text, { version, release, headings, unreleased
|
|
|
73
73
|
problems.push(`CHANGELOG.md: no section for ${version}`);
|
|
74
74
|
return problems;
|
|
75
75
|
};
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. An HTML tag is
|
|
78
|
+
* dropped, but `<…>` inside a code span is text: `v<version>` gives `vversion`.
|
|
79
|
+
*/
|
|
77
80
|
export const slug = (heading) => heading
|
|
78
81
|
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
79
|
-
.
|
|
82
|
+
.split(/(`[^`]*`)/)
|
|
83
|
+
.map((part) => (part.startsWith("`") ? part : part.replace(/<[^>]+>/g, "")))
|
|
84
|
+
.join("")
|
|
80
85
|
.toLowerCase()
|
|
81
86
|
.replace(/[^\p{L}\p{M}\p{N}\-_ ]/gu, "")
|
|
82
87
|
.replace(/ /g, "-");
|
|
@@ -105,7 +110,10 @@ const anchorsOf = (text) => {
|
|
|
105
110
|
anchors.add(name);
|
|
106
111
|
return anchors;
|
|
107
112
|
};
|
|
108
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Every `.md` under `directory`, not descending into a directory named in `skip`, nor into one with its own `.git`:
|
|
115
|
+
* a worktree or clone inside the checkout is another branch's files, and its broken links are not this one's.
|
|
116
|
+
*/
|
|
109
117
|
export const markdownFiles = (directory, skip = new Set()) => existsSync(directory)
|
|
110
118
|
? readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
111
119
|
if (skip.has(entry.name))
|
|
@@ -115,7 +123,7 @@ export const markdownFiles = (directory, skip = new Set()) => existsSync(directo
|
|
|
115
123
|
// like .claude/loop.md, and the directory listing still reports what lies underneath.
|
|
116
124
|
const stats = statSync(path, { throwIfNoEntry: false });
|
|
117
125
|
if (entry.isDirectory() && stats?.isDirectory())
|
|
118
|
-
return markdownFiles(path, skip);
|
|
126
|
+
return existsSync(join(path, ".git")) ? [] : markdownFiles(path, skip);
|
|
119
127
|
return stats?.isFile() && entry.name.endsWith(".md") ? [path] : [];
|
|
120
128
|
})
|
|
121
129
|
: [];
|
|
@@ -22,8 +22,11 @@ declare const parityGroupSchema: v.StrictObjectSchema<{
|
|
|
22
22
|
/** Permanent differences, each with its reason; they count against the override budget of the group. */
|
|
23
23
|
readonly differences: v.ArraySchema<v.StrictObjectSchema<{
|
|
24
24
|
readonly rule: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a lower-case word, words joined by -">]>;
|
|
25
|
+
/** `<clis> <path> <flag>`; with `absent`, only `<path> <flag>`: the CLI list is whoever else has it. */
|
|
25
26
|
readonly target: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
26
27
|
readonly expected: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>;
|
|
28
|
+
/** The CLI that lacks the path or flag by design; the rest must agree, so other CLIs joining do not stale it. */
|
|
29
|
+
readonly absent: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a lower-case word, words joined by -">]>, undefined>;
|
|
27
30
|
readonly reason: v.SchemaWithPipe<readonly [v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, v.MinLengthAction<string, 10, "say why, in a sentence">]>;
|
|
28
31
|
}, undefined>, undefined>;
|
|
29
32
|
}, undefined>;
|
package/dist/standards/audit.js
CHANGED
|
@@ -51,14 +51,22 @@ const parityGroupSchema = v.strictObject({
|
|
|
51
51
|
/** Permanent differences, each with its reason; they count against the override budget of the group. */
|
|
52
52
|
differences: v.array(v.strictObject({
|
|
53
53
|
rule: word,
|
|
54
|
+
/** `<clis> <path> <flag>`; with `absent`, only `<path> <flag>`: the CLI list is whoever else has it. */
|
|
54
55
|
target: text,
|
|
55
56
|
expected: v.optional(text),
|
|
57
|
+
/** The CLI that lacks the path or flag by design; the rest must agree, so other CLIs joining do not stale it. */
|
|
58
|
+
absent: v.optional(word),
|
|
56
59
|
reason: v.pipe(text, v.minLength(10, "say why, in a sentence")),
|
|
57
60
|
})),
|
|
58
61
|
});
|
|
59
62
|
export const defineParityGroups = (input) => parseRegistry("parity groups", v.array(parityGroupSchema), input, (groups) => [
|
|
60
63
|
...duplicates(groups.map((group) => group.name)).map((name) => `"${name}" is listed twice`),
|
|
61
|
-
...groups.flatMap((group) =>
|
|
64
|
+
...groups.flatMap((group) => [
|
|
65
|
+
...duplicates(group.differences.map((entry) => `${entry.rule} ${entry.target}`)).map((both) => `${group.name}: "${both}" is listed twice`),
|
|
66
|
+
...group.differences
|
|
67
|
+
.filter((entry) => entry.absent && entry.expected)
|
|
68
|
+
.map((entry) => `${group.name}: "${entry.target}" has \`absent\`, which measures the values itself; drop \`expected\``),
|
|
69
|
+
]),
|
|
62
70
|
]);
|
|
63
71
|
export const PARITY_GROUPS = defineParityGroups([
|
|
64
72
|
{ name: "messengers", members: ["tg", "max"], covers: "tree", differences: [] },
|
|
@@ -66,7 +74,13 @@ export const PARITY_GROUPS = defineParityGroups([
|
|
|
66
74
|
name: "all",
|
|
67
75
|
members: ["tg", "max", "memo", "drive", "zm", "wa", "mail"],
|
|
68
76
|
covers: ["commands", "doctor", "skill", "mcp", "upgrade", "search", "notes", "meetings"],
|
|
69
|
-
differences: []
|
|
77
|
+
differences: ["mcp serve", "mcp revoke", "mcp server start", "mcp server status", "mcp server stop"].map((path) => ({
|
|
78
|
+
rule: "same-path-contract",
|
|
79
|
+
target: `${path} --profiles`,
|
|
80
|
+
absent: "memo",
|
|
81
|
+
reason: "memo has no profiles: it reads other CLIs' stores and selects their accounts with --source and --account " +
|
|
82
|
+
"(https://github.com/WireCatLabs/cli-base/issues/390)",
|
|
83
|
+
})),
|
|
70
84
|
},
|
|
71
85
|
// gcal is compared on what it already shares with every CLI; doctor, mcp, search and skill wait until the published
|
|
72
86
|
// CLIs agree there (https://github.com/WireCatLabs/cli-base/issues/390), then gcal moves back into "all".
|
|
@@ -14,7 +14,9 @@ Rule ids on this page are `WC-ACCT-<n>`. How to read a rule, how ids work and wh
|
|
|
14
14
|
### Who follows these rules today
|
|
15
15
|
|
|
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
|
-
and `--account` (WC-ACCT-7).
|
|
17
|
+
and `--account` (WC-ACCT-7). memo never creates profiles of its own: it reads the stores of other CLIs and selects
|
|
18
|
+
their accounts with `--source email:<name>` or `--account telegram:1`, so it has no `--profiles` either
|
|
19
|
+
([cli-base#390](https://github.com/WireCatLabs/cli-base/issues/390), owner decision 2026-10-31). zoom resolves its own profile word by the same rules (`zm <profile> …`, `ZM_PROFILE`,
|
|
18
20
|
`ZM_PROFILE_LOCK`) from zoom-cli 0.35.0, with cli-core's `liftProfile` and `usableProfileName`. drive still has its own
|
|
19
21
|
profile code: it registers its own profiles. That is a known gap, not an exception. gcal resolves the profile through
|
|
20
22
|
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.
|
|
@@ -48,7 +50,8 @@ profile.
|
|
|
48
50
|
- **Example:** `max work bot messages send …` uses the same first word as `max work chats list`. `tg --json chats list`
|
|
49
51
|
still works, because `--json` is an option.
|
|
50
52
|
- **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.
|
|
53
|
+
place of the first word, never with it. memo has no profiles of its own, so it has neither the first word nor
|
|
54
|
+
`--profiles`; it selects the accounts of other CLIs with `--source` and `--account`.
|
|
52
55
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-84, NEED-294, NEED-1881.
|
|
53
56
|
|
|
54
57
|
### WC-ACCT-3 · A missing profile is an error that lists the choices
|
|
@@ -121,7 +121,7 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
121
121
|
- **Example:** in cli-core, `src/tasks/` and `src/meetings/` are entered only through their `index.ts`, `cli/`, `mcp/`
|
|
122
122
|
and `testing/` entries; its `biome.json` refuses the rest. Every repository refuses `@wirecat/<package>/src/…` and `/dist/…` outside tests. A
|
|
123
123
|
package's own entry points are its `exports`
|
|
124
|
-
([WC-LAY-5](layout.md#wc-lay-5--add-an-entry-point-as-an-exports-entry-built-from-
|
|
124
|
+
([WC-LAY-5](layout.md#wc-lay-5--add-an-entry-point-as-an-exports-entry-built-from-srcnameindexts)).
|
|
125
125
|
- **Exceptions:** a test inside a module may import what it tests; a test outside the module uses the entry point.
|
|
126
126
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0.
|
|
127
127
|
**Rulings:** none.
|
|
@@ -208,7 +208,7 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
208
208
|
- **Why:** a CLI that only speaks a socket must not depend on an HTTP stack.
|
|
209
209
|
- **Enforced by:** review only.
|
|
210
210
|
- **Example:** a helper that reads a status code or a header goes to `@wirecat/cli-base/http`. How an entry point
|
|
211
|
-
is added is [WC-LAY-5](layout.md#wc-lay-5--add-an-entry-point-as-an-exports-entry-built-from-
|
|
211
|
+
is added is [WC-LAY-5](layout.md#wc-lay-5--add-an-entry-point-as-an-exports-entry-built-from-srcnameindexts).
|
|
212
212
|
- **Exceptions:** none.
|
|
213
213
|
- **Applies to:** cli-base. **Since:** cli-base 0.41.0. **Rulings:** NEED-15.
|
|
214
214
|
|
|
@@ -124,10 +124,10 @@ catalogue and parity manifest stay in cli-core
|
|
|
124
124
|
`--max-output-bytes` the size of the answer. A budget changes how far the command gets, never which items one page
|
|
125
125
|
of the answer holds. A retry after `outcome_unknown` is `--operation-id`: the attempt in the send journal. The
|
|
126
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
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
before the create, so a retry finds it at the provider. A CLI whose provider takes such an id may offer both. gcal
|
|
128
|
+
offers only `--operation-id` (from gcal 0.9.0): its `operation_id` is also the event's id at Google, so one value
|
|
129
|
+
serves both.
|
|
130
|
+
- **Exceptions:** generated commands (`bot api`) are exempt from the naming rules.
|
|
131
131
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
132
132
|
|
|
133
133
|
### WC-CMD-11 · Name an option in plain words, with the kind of value in the name
|
|
@@ -210,8 +210,8 @@ before its deadline.
|
|
|
210
210
|
- **Example:** `progressOf` from `@wirecat/cli-core/background` builds
|
|
211
211
|
`{ job, profile, done, total, remaining, state, updated_at, cursor_age_ms }`, and `progressLine` prints it on one
|
|
212
212
|
line in text. `state` is `partial` or `complete`; `total` and `remaining` are `null` when the source does not say how
|
|
213
|
-
much there is.
|
|
214
|
-
`progress`.
|
|
213
|
+
much there is. The mail CLI's `mail <profile> status` answers with it, and the messenger CLIs' `store jobs show`
|
|
214
|
+
answers with it as `progress`.
|
|
215
215
|
- **Exceptions:** none. drive and zm print no progress shape yet; that is a known gap. Whether the last scheduled run
|
|
216
216
|
failed is a different question, answered by `recordRun` and `failingNotice` in cli-core's schedules.
|
|
217
217
|
- **Applies to:** every CLI with a sync. **Since:** cli-base 0.43.0. **Rulings:** none.
|
package/docs/standards/mcp.md
CHANGED
|
@@ -155,7 +155,11 @@ and lists the profiles served. Each call follows its own profile's permissions.
|
|
|
155
155
|
- **Example:** `max work mcp serve`, `max mcp serve --profiles work,personal` or `max mcp serve --profiles all`.
|
|
156
156
|
Under the last two, a call reads `max_read { "profile": "work", "command": "inbox", "arguments": {} }`.
|
|
157
157
|
`max mcp serve` alone is refused, and its message lists `personal` and `work`.
|
|
158
|
-
- **Exceptions:** the bot server needs no held connection: each Bot API call is plain HTTPS.
|
|
158
|
+
- **Exceptions:** the bot server needs no held connection: each Bot API call is plain HTTPS. memo has no profiles of
|
|
159
|
+
its own, so `--profiles` stays absent on its `mcp serve`, `mcp revoke` and `mcp server start`, `status` and `stop`;
|
|
160
|
+
it reads other CLIs' stores and selects their accounts with `--source email:<name>` or `--account telegram:1`
|
|
161
|
+
([cli-base#390](https://github.com/WireCatLabs/cli-base/issues/390), owner decision 2026-10-31). The audit declares
|
|
162
|
+
this once, as a difference of memo, not of the other CLIs.
|
|
159
163
|
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1881.
|
|
160
164
|
|
|
161
165
|
### WC-MCP-10 · A tool and its command give the same answer and the same error
|
|
@@ -117,6 +117,9 @@ installing that command; nothing else is registered anywhere.
|
|
|
117
117
|
`tools.json`. A messenger also joins cli-core's `parity.json`. A CLI outside WireCatLabs keeps its parity groups
|
|
118
118
|
in its own repository and runs `cli-dev audit --groups <file>`. See [Testing](testing.md).
|
|
119
119
|
|
|
120
|
+
The listings beyond npm (ClawHub, the MCP Registry, the Claude plugin, the docs site, Glama) are a checklist in
|
|
121
|
+
[Publishing](../PUBLISHING.md#a-new-cli).
|
|
122
|
+
|
|
120
123
|
## Done when
|
|
121
124
|
|
|
122
125
|
All of these pass on `main`, before the first release:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.0",
|
|
4
4
|
"description": "The parts every command line tool needs: output modes, terminal rendering, error model, exit codes, credentials, clocks",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|