@wirecat/cli-base 0.68.1 → 0.70.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 CHANGED
@@ -8,6 +8,45 @@ 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.70.0 — 01.11.2026
12
+
13
+ ### Added
14
+
15
+ - **Mail words in the vocabulary: the `drafts` resource and `--cc`, `--bcc`, `--reply-all`.** mail-cli saves drafts
16
+ in a mailbox's Drafts folder (`mail <profile> drafts list|create|update|delete`) and addresses e-mail with copies;
17
+ the command lint reported each as a word it did not know.
18
+ - **`--to` takes a chat or an e-mail address, `--subject` takes a reference or a subject line.** The owner ruled
19
+ that mail shares these two words with the messengers and notes instead of renaming them. `--to` is now
20
+ `<chat|address>`, a `string` (was an `id`); `--subject` is `<ref|text>`. The meaning per domain is in each flag's
21
+ text, so `flag-two-meanings` stops reporting mail's use. Mail-cli can drop its overrides for both.
22
+
23
+ ### Fixed
24
+
25
+ - **mail is compared in its own parity group, `mail-shared`, which leaves out `search`.** mail's `search messages` is
26
+ cli-core's mail search, a different command from the messengers' under the same path, and `audit --complete` took
27
+ its twenty-odd differences as inherited. mail is compared on every other shared root, and moves back into `all`
28
+ once its search is aligned with the messengers' or renamed.
29
+
30
+ ## 0.69.0 — 01.11.2026
31
+
32
+ ### Added
33
+
34
+ - **[Publishing](docs/PUBLISHING.md): where a CLI is published beyond npm, and how.** npm, ClawHub, the MCP Registry,
35
+ the Claude plugin marketplace, Hermes, Glama, GitHub and wirecat.dev: each one's setup, what a release does, how to
36
+ prove a publish happened (three jobs go green without publishing), the problems met with their fixes, a checklist
37
+ for a new CLI, and each CLI's status. `new-cli.md` points to the checklist.
38
+
39
+ ### Fixed
40
+
41
+ - **`markdownFiles` passes over a worktree or clone inside the checkout** (a folder with its own `.git`, such as
42
+ `.worktrees/<topic>`). Its pages are another branch's, and their broken links failed cli-template's release check.
43
+ Every CLI's `docs:check` gets this through its own `docs-rules.ts`, with no change there.
44
+
45
+ - **`cli-dev docs-check` reads a heading's anchor as GitHub does when it has `<…>` in code.** It dropped
46
+ `` `v<version>` `` as an HTML tag and wanted `tag-v-only`, where GitHub makes `tag-vversion-only`. So a working link
47
+ failed and a broken one passed. Two links in `architecture.md` were broken that way and are fixed. After this
48
+ release a CLI's `docs:check` may find links of its own that never worked on GitHub.
49
+
11
50
  ## 0.68.1 — 31.10.2026
12
51
 
13
52
  ### Added
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 |
@@ -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
- /** GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. */
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
- /** Every `.md` under `directory`, not descending into a directory named in `skip`. */
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. */
@@ -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
- /** GitHub's heading anchor: lower case, only letters, digits, `-`, `_` and spaces kept, spaces to `-`. */
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
- .replace(/<[^>]+>/g, "")
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
- /** Every `.md` under `directory`, not descending into a directory named in `skip`. */
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
  : [];
@@ -68,19 +68,35 @@ export const defineParityGroups = (input) => parseRegistry("parity groups", v.ar
68
68
  .map((entry) => `${group.name}: "${entry.target}" has \`absent\`, which measures the values itself; drop \`expected\``),
69
69
  ]),
70
70
  ]);
71
+ const MEMO_HAS_NO_PROFILES = [
72
+ "mcp serve",
73
+ "mcp revoke",
74
+ "mcp server start",
75
+ "mcp server status",
76
+ "mcp server stop",
77
+ ].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
+ }));
71
84
  export const PARITY_GROUPS = defineParityGroups([
72
85
  { name: "messengers", members: ["tg", "max"], covers: "tree", differences: [] },
73
86
  {
74
87
  name: "all",
75
- members: ["tg", "max", "memo", "drive", "zm", "wa", "mail"],
88
+ members: ["tg", "max", "memo", "drive", "zm", "wa"],
76
89
  covers: ["commands", "doctor", "skill", "mcp", "upgrade", "search", "notes", "meetings"],
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
- })),
90
+ differences: MEMO_HAS_NO_PROFILES,
91
+ },
92
+ // mail's `search messages` is cli-core's mail search, not the messengers' command: --complete took their 20-odd
93
+ // differences as inherited (https://github.com/WireCatLabs/cli-base/issues/390). mail is compared everywhere else
94
+ // and moves back into "all" once its search is aligned with the messengers' or renamed.
95
+ {
96
+ name: "mail-shared",
97
+ members: ["tg", "max", "memo", "drive", "zm", "wa", "mail"],
98
+ covers: ["commands", "doctor", "skill", "mcp", "upgrade", "notes", "meetings"],
99
+ differences: MEMO_HAS_NO_PROFILES,
84
100
  },
85
101
  // gcal is compared on what it already shares with every CLI; doctor, mcp, search and skill wait until the published
86
102
  // CLIs agree there (https://github.com/WireCatLabs/cli-base/issues/390), then gcal moves back into "all".
@@ -0,0 +1,2 @@
1
+ import type { VocabularyFlag } from "./vocabulary.js";
2
+ export declare const MAIL_FLAGS: VocabularyFlag[];
@@ -0,0 +1,22 @@
1
+ export const MAIL_FLAGS = [
2
+ {
3
+ name: "--cc",
4
+ value: "<address>",
5
+ role: "value",
6
+ type: "string",
7
+ meaning: "an e-mail address that gets a copy; repeat it for more",
8
+ },
9
+ {
10
+ name: "--bcc",
11
+ value: "<address>",
12
+ role: "value",
13
+ type: "string",
14
+ meaning: "an e-mail address that gets a blind copy; repeat it for more",
15
+ },
16
+ {
17
+ name: "--reply-all",
18
+ role: "value",
19
+ type: "switch",
20
+ meaning: "with --reply-to, an e-mail reply also goes to everyone the message went to",
21
+ },
22
+ ];
@@ -442,7 +442,13 @@ export const WHAT_FLAGS = [
442
442
  meaning: "keep a token an operation returns only in this bot profile's keyring; never print it",
443
443
  },
444
444
  { name: "--task", value: "<id>", role: "target", type: "id", meaning: "a task, by id" },
445
- { name: "--to", value: "<chat>", role: "target", type: "id", meaning: "the chat it goes to" },
445
+ {
446
+ name: "--to",
447
+ value: "<chat|address>",
448
+ role: "target",
449
+ type: "string",
450
+ meaning: "who it goes to: a chat in a messenger, an e-mail address in mail; repeat it for more addresses",
451
+ },
446
452
  {
447
453
  name: "--topic-name",
448
454
  value: "<name>",
@@ -889,10 +895,10 @@ export const WHAT_FLAGS = [
889
895
  { name: "--sticker", value: "<id>", role: "value", type: "id", meaning: "send this sticker, alone" },
890
896
  {
891
897
  name: "--subject",
892
- value: "<ref>",
898
+ value: "<ref|text>",
893
899
  role: "value",
894
900
  type: "string",
895
- meaning: "what it is about, as a reference",
901
+ meaning: "what it is about: a reference in a note or task, the subject line in mail",
896
902
  replaces: ["--about"],
897
903
  },
898
904
  {
@@ -1,6 +1,7 @@
1
1
  import * as v from "valibot";
2
2
  import { CALENDAR_FLAGS } from "./flags-calendar.js";
3
3
  import { HOW_FLAGS } from "./flags-how.js";
4
+ import { MAIL_FLAGS } from "./flags-mail.js";
4
5
  import { WHAT_FLAGS } from "./flags-what.js";
5
6
  import { duplicates, parseRegistry, text, word } from "./registry.js";
6
7
  export const FLAG_TYPES = [
@@ -212,6 +213,7 @@ const collections = [
212
213
  ["chats", "conversations in a messenger"],
213
214
  ["contacts", "identities in a messenger's contact list"],
214
215
  ["messages", "posts in a messenger chat, or emails"],
216
+ ["drafts", "e-mails saved in a mailbox's Drafts folder, written and not sent"],
215
217
  ["polls", "polls in a chat"],
216
218
  ["reactions", "reactions to a message"],
217
219
  ["recipients", "who a message goes to"],
@@ -359,5 +361,5 @@ export const VOCABULARY = defineVocabulary({
359
361
  ],
360
362
  views: views.map(([entry, meaning]) => ({ word: entry, meaning })),
361
363
  roots: roots.map(([entry, meaning]) => ({ word: entry, meaning })),
362
- flags: [...HOW_FLAGS, ...WHAT_FLAGS, ...CALENDAR_FLAGS],
364
+ flags: [...HOW_FLAGS, ...WHAT_FLAGS, ...CALENDAR_FLAGS, ...MAIL_FLAGS],
363
365
  });
@@ -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-srcindexts)).
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-srcindexts).
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
- - **Exceptions:** generated commands (`bot api`) are exempt from the naming rules. Known gap, as of 2026-10-27: gcal's
129
- `events create` offers `--id` and no `--operation-id`, so its `outcome_unknown` names the event id instead of an
130
- `operation_id` (WC-LIFE-9, WC-OUT-10).
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. memo's `mail status` answers with it, and the messenger CLIs' `store jobs show` answers with it as
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.
@@ -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.68.1",
3
+ "version": "0.70.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",