@wirecat/cli-base 0.52.0 → 0.54.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 +22 -0
- package/config/wirecat/biome.base.json +17 -0
- package/config/wirecat/manifest.json +10 -3
- package/config/wirecat/no-sqlite-driver.grit +15 -0
- package/config/wirecat/release-lock.sh +10 -1
- package/dist/dev/lint.js +2 -2
- package/docs/standards/README.md +5 -4
- package/docs/standards/architecture.md +18 -0
- package/docs/standards/data-model.md +36 -14
- package/docs/standards/kit-surface.md +50 -0
- package/docs/standards/new-cli.md +72 -91
- package/docs/standards/releases.md +11 -6
- package/docs/standards/security.md +16 -1
- package/docs/standards/store.md +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,27 @@ 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.54.0 — 25.10.2026
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **A Kit surface page in the standards** ([Kit surface](docs/standards/kit-surface.md)) lists the cli-base and cli-core exports that `@wirecat/cli-template` is built on. A change that makes a CLI change its code for one of them ships with a migration note here and with the template already updated, so `npx @wirecat/cli-template upgrade` brings it. Nothing changes in code.
|
|
16
|
+
- **The New CLI page starts with `npx @wirecat/cli-template new`** ([New CLI](docs/standards/new-cli.md)). It gives the command with every flag, the four steps that still need a person, and the same "Done when" checks. A CLI started by hand still passes the same checks.
|
|
17
|
+
|
|
18
|
+
### Changed — may break callers
|
|
19
|
+
|
|
20
|
+
- **The release lock is one per package, not one per machine** (`WC-REL-3`, [Releases](docs/standards/releases.md)). Two releases of the same package still never run at once on one machine, from any checkout or worktree; releases of different packages now run side by side. The lock is `leemour-release-<package>.lock`, named from `package.json`, so a `package.json` without a name refuses the release. A CLI picks this up by re-running community's `adopt-standards.py --only release-lock`; until then it keeps the old shared lock.
|
|
21
|
+
|
|
22
|
+
## 0.53.0 — 25.10.2026
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **A rule for the provider registry** (`WC-ARCH-21`, [Architecture](docs/standards/architecture.md)): a CLI registers its provider in the store on every run, and a row whose provider no one registered is skipped, never searched as messages. It matches cli-core's stored registry. The Data model, Store and Security pages also list what the approved Wave 3 design adds and is not built yet: files, calendars, contacts by address, one link log and review list, a CLI's own SQLite file, and the sign-in module. The Data model page now names the attendee table `calendar_entry_participants`, lists `meeting_participants` among the known gaps, and names cli-core's schema test as the check of `WC-DATA-2` to `WC-DATA-4`.
|
|
27
|
+
|
|
28
|
+
### Changed — may break callers
|
|
29
|
+
|
|
30
|
+
- **Every handwritten list needs a query declaration.** The command lint checks lists that lack answer metadata too. Release this with the matching cli-core list-query changes and consumer updates.
|
|
31
|
+
|
|
11
32
|
## 0.52.0 — 25.10.2026
|
|
12
33
|
|
|
13
34
|
### Added
|
|
@@ -16,6 +37,7 @@ Every entry says what changed as a caller sees it, why, and what to watch for
|
|
|
16
37
|
- **`cli-dev <command> --help`** prints the usage, as `cli-dev --help` does, instead of failing on an unknown option.
|
|
17
38
|
|
|
18
39
|
- **A data-model page in the standards** (`WC-DATA-1` to `WC-DATA-7`, [Data model](docs/standards/data-model.md)). It says which records cli-core owns and which stay private to a CLI, keeps a source's records (a calendar entry, a Drive file, a mail contact) apart from the owner's entities (an event, a document, a person), puts every provider id in `external_id`, lists the standard field names, and makes every link record how it was made, with no link when two candidates tie. Nothing changes in code; older tables follow the rules when they next change.
|
|
40
|
+
|
|
19
41
|
## 0.51.1 — 24.10.2026
|
|
20
42
|
|
|
21
43
|
### Fixed
|
|
@@ -120,6 +120,23 @@
|
|
|
120
120
|
"!**/fixtures/**"
|
|
121
121
|
]
|
|
122
122
|
},
|
|
123
|
+
{
|
|
124
|
+
"path": "./config/wirecat/no-sqlite-driver.grit",
|
|
125
|
+
"includes": [
|
|
126
|
+
"**",
|
|
127
|
+
"!**/*.test.*",
|
|
128
|
+
"!**/*.spec.*",
|
|
129
|
+
"!**/test/**",
|
|
130
|
+
"!**/tests/**",
|
|
131
|
+
"!**/__tests__/**",
|
|
132
|
+
"!**/fixtures/**",
|
|
133
|
+
"!**/testing/**",
|
|
134
|
+
"!**/scripts/**",
|
|
135
|
+
"!**/bench/**",
|
|
136
|
+
"!**/performance/**",
|
|
137
|
+
"!**/contracts/**"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
123
140
|
{
|
|
124
141
|
"path": "./config/wirecat/no-unbounded-spread.grit",
|
|
125
142
|
"includes": [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": "https://github.com/WireCatLabs/community",
|
|
3
3
|
"revision": "b22b7ede5931a4058fc763e0a7d6bfe0fbdb87c8",
|
|
4
4
|
"files": {
|
|
5
|
-
"biome.base.json": "
|
|
5
|
+
"biome.base.json": "63760337a2e68522ad75e0ba163635785a01ec34dbd3cf338a09a4145932cf2a",
|
|
6
6
|
"cspell.json": "8241122ed4d461976a838099b265ef8f2689a066608257b6fdc250d9b49b58aa",
|
|
7
7
|
"lefthook.yml": "2f65a2e3103006d43f6d6f0a07ab333c132286157763222a59a5a22676acd391",
|
|
8
8
|
"markdownlint.json": "5ed7d6980054c539f3f3c0e47a090f1a725b15be2c486a769a836bc6cd48ad2d",
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"no-shell-true.grit": "d3943cfa1d7ebbf7be0fa22c2aaf2b7d7aa9ff462f2eb14a7798632764e3875c",
|
|
13
13
|
"no-unbounded-spread.grit": "f4178e5b74212fef58fec8916b6783d9f4f192ac618aa4ced041c4167bfbf81b",
|
|
14
14
|
"release-lock-info.mjs": "4abab1f01ceee7d4c709222e923c9a6fc634bcf7b8b64d745a9ac495993310e0",
|
|
15
|
-
"release-lock.sh": "
|
|
15
|
+
"release-lock.sh": "bef364092c4ab595f3b9b4130e23570c8306223228cdfa3e9384230f3b7f4a49",
|
|
16
16
|
"rumdl.toml": "048b929176f13234324f5825d1e811397f1491b9cb6684a1953e268e2c2ed863",
|
|
17
|
-
"check.mjs": "34b266411eaab7707030915f645308e86784b1574b21fa44ca974861a90c9ce1"
|
|
17
|
+
"check.mjs": "34b266411eaab7707030915f645308e86784b1574b21fa44ca974861a90c9ce1",
|
|
18
|
+
"no-sqlite-driver.grit": "19e1f510a88ddd9df44d6db43c1c9b603c3e5bbec03a3f65305b72eac7f09389"
|
|
18
19
|
},
|
|
19
20
|
"packageManager": "pnpm@11.20.0",
|
|
20
21
|
"node": ">=22",
|
|
@@ -26,5 +27,11 @@
|
|
|
26
27
|
"cspell": "10.3.6",
|
|
27
28
|
"lefthook": "2.1.16",
|
|
28
29
|
"markdownlint-cli2": "0.23.3"
|
|
30
|
+
},
|
|
31
|
+
"fileRevisions": {
|
|
32
|
+
"release-lock.sh": "49e630a99d758e3e113749bef2049b258c7262e3",
|
|
33
|
+
"release-lock-info.mjs": "49e630a99d758e3e113749bef2049b258c7262e3",
|
|
34
|
+
"biome.base.json": "06c612c609d870f0ba00010612e0413f65340408",
|
|
35
|
+
"no-sqlite-driver.grit": "06c612c609d870f0ba00010612e0413f65340408"
|
|
29
36
|
}
|
|
30
37
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`import $_ from $source`,
|
|
5
|
+
`import $source`,
|
|
6
|
+
`export { $_ } from $source`,
|
|
7
|
+
`export * from $source`,
|
|
8
|
+
`export * as $_ from $source`,
|
|
9
|
+
`export type { $_ } from $source`,
|
|
10
|
+
`import($source)`,
|
|
11
|
+
`require($source)`
|
|
12
|
+
} where {
|
|
13
|
+
$source <: r"^[\"'`](?:node:sqlite|bun:sqlite|better-sqlite3|drizzle-orm)(?:/.*)?[\"'`]$",
|
|
14
|
+
register_diagnostic(span=$source, message="The shared store belongs to cli-core (WC-STORE-5): a CLI opens no SQLite driver and writes no SQL against it. Ask cli-core for a store method.", severity="error")
|
|
15
|
+
}
|
|
@@ -8,7 +8,16 @@ wirecat_release_lock() {
|
|
|
8
8
|
echo "release locking requires flock" >&2
|
|
9
9
|
return 1
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
# Keyed by package name, not folder: one package releases from several checkouts and worktrees.
|
|
12
|
+
_wirecat_release_key=$(node -e '
|
|
13
|
+
const { name } = JSON.parse(require("node:fs").readFileSync("package.json", "utf8"))
|
|
14
|
+
if (typeof name !== "string" || !name) throw Error("package.json has no name")
|
|
15
|
+
process.stdout.write(name.replace(/^@/, "").replace(/[^A-Za-z0-9._-]+/g, "-"))
|
|
16
|
+
' 2>/dev/null) && [ -n "$_wirecat_release_key" ] || {
|
|
17
|
+
echo "release locking needs a package name in ./package.json" >&2
|
|
18
|
+
return 1
|
|
19
|
+
}
|
|
20
|
+
_wirecat_release_lock="${XDG_RUNTIME_DIR:-/tmp}/leemour-release-$_wirecat_release_key.lock"
|
|
12
21
|
exec 9>>"$_wirecat_release_lock"
|
|
13
22
|
if flock -E 75 -w "$_wirecat_release_wait" 9; then
|
|
14
23
|
printf '%s pid %s since %s\n' "$(basename "$PWD")" "$$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$_wirecat_release_lock.who"
|
package/dist/dev/lint.js
CHANGED
|
@@ -342,10 +342,10 @@ const CHECKS = {
|
|
|
342
342
|
.map(({ target, option }) => ({ target, message: `${longName(option.flags)} has no role in the vocabulary` }));
|
|
343
343
|
},
|
|
344
344
|
"list-without-query": (document) => handwrittenLeaves(document)
|
|
345
|
-
.filter((command) => command.name === "list" && command.
|
|
345
|
+
.filter((command) => command.name === "list" && command.query === undefined)
|
|
346
346
|
.map((command) => ({
|
|
347
347
|
target: label(command),
|
|
348
|
-
message:
|
|
348
|
+
message: "list declares no query",
|
|
349
349
|
})),
|
|
350
350
|
"query-example": (document) => walk(document.commands).flatMap((command) => command.query
|
|
351
351
|
? exampleProblems(command.query).map(({ field, problem }) => ({
|
package/docs/standards/README.md
CHANGED
|
@@ -9,8 +9,8 @@ It ships in the `@wirecat/cli-base` package. A CLI that depends on cli-base has
|
|
|
9
9
|
|
|
10
10
|
## How to use this for a new CLI
|
|
11
11
|
|
|
12
|
-
Start with the [New CLI](new-cli.md)
|
|
13
|
-
|
|
12
|
+
Start with the [New CLI](new-cli.md) page: `npx @wirecat/cli-template new` makes the repository, and the page lists
|
|
13
|
+
the four steps that need a person. A new CLI is ready for its first release when every check under "Done when" passes.
|
|
14
14
|
|
|
15
15
|
## Which rule wins
|
|
16
16
|
|
|
@@ -32,7 +32,7 @@ rules it keeps. A new CLI is ready for its first release when every check under
|
|
|
32
32
|
| [MCP](mcp.md) | `MCP` | 13 | The MCP tools, how they come from commands, what each permission shows |
|
|
33
33
|
| [Accounts](accounts.md) | `ACCT` | 11 | The explicit profile, reads across accounts, scope flags, linked identities |
|
|
34
34
|
| [Permissions](permissions.md) | `PERM` | 21 | Levels, keys, defaults, hard limits, the write guard and its journal |
|
|
35
|
-
| [Architecture](architecture.md) | `ARCH` |
|
|
35
|
+
| [Architecture](architecture.md) | `ARCH` | 17 | Layers, dependency direction, vendor types, what goes in which package |
|
|
36
36
|
| [Lifecycle](lifecycle.md) | `LIFE` | 13 | Opening late, who closes, one-shot exits, long-running commands, the `serve` relay, resumable syncs |
|
|
37
37
|
| [Store](store.md) | `STORE` | 14 | Store areas, account scope, migrations, test isolation |
|
|
38
38
|
| [Data model](data-model.md) | `DATA` | 7 | Core entities, entities and source records, `external_id`, field names, links and duplicates |
|
|
@@ -44,7 +44,8 @@ rules it keeps. A new CLI is ready for its first release when every check under
|
|
|
44
44
|
| [CI](ci.md) | `CI` | 12 | Hooks, the pull request budget, full validation, monthly runs |
|
|
45
45
|
| [Time](time.md) | `TIME` | 6 | UTC in the store and in answers, the zone at input and display edges, date-only filters, tests in another zone |
|
|
46
46
|
| [Style](style.md) | `STYLE` | 10 | Comments, TypeScript habits, commit messages |
|
|
47
|
-
| [New CLI](new-cli.md) | — | — |
|
|
47
|
+
| [New CLI](new-cli.md) | — | — | Starting a new CLI from the template: the command, the four manual steps, the checks |
|
|
48
|
+
| [Kit surface](kit-surface.md) | — | — | The cli-base and cli-core exports the template is built on, and how a change to one reaches you |
|
|
48
49
|
|
|
49
50
|
Each count leaves out the ids that moved or merged (see "How ids work").
|
|
50
51
|
|
|
@@ -204,6 +204,24 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
204
204
|
- **Exceptions:** none.
|
|
205
205
|
- **Applies to:** cli-base. **Since:** cli-base 0.41.0. **Rulings:** NEED-15.
|
|
206
206
|
|
|
207
|
+
### WC-ARCH-21 · A CLI registers its provider in the store; an unknown provider's rows are skipped, never guessed
|
|
208
|
+
|
|
209
|
+
Every run, a CLI writes its provider's entry — `{ name, kind, aliases, command }` — with `registerProvider` from
|
|
210
|
+
`@wirecat/cli-core/store`. cli-core reads its built-in providers plus the stored entries when the store opens, so
|
|
211
|
+
every other CLI knows the new provider's kind without a cli-core release. A row whose provider no entry names is left
|
|
212
|
+
out of search and listed under `skipped`, with the hint to run that CLI once.
|
|
213
|
+
|
|
214
|
+
- **Why:** a new source joins search and memo without editing cli-core, and a provider nobody described is never read
|
|
215
|
+
as something it is not (until this rule, an unlisted provider was searched as a messenger).
|
|
216
|
+
- **Enforced by:** `cli-core: src/store/sqlite/providers.test.ts`.
|
|
217
|
+
- **Example:** cal registers `{ name: "google-calendar", kind: "events", aliases: ["cal"], command: "cal" }` each
|
|
218
|
+
time it starts. On a machine where cal has never run, a search across accounts lists cal's accounts under
|
|
219
|
+
`skipped` instead of reading their rows as messages.
|
|
220
|
+
- **Exceptions:** the built-in providers of cli-core need no entry. Two entries that disagree on a kind are an error
|
|
221
|
+
naming both.
|
|
222
|
+
- **Applies to:** cli-core and every CLI with a provider of its own. **Since:** cli-base 0.53.1. **Rulings:** none
|
|
223
|
+
(Wave 3 requirements 3.2).
|
|
224
|
+
|
|
207
225
|
## Sources
|
|
208
226
|
|
|
209
227
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -33,6 +33,13 @@ is rewritten only to follow it:
|
|
|
33
33
|
and nothing joins the two (WC-DATA-6).
|
|
34
34
|
- `events` rows made from Zoom meetings have no source record of their own besides the meeting (WC-DATA-2 holds:
|
|
35
35
|
`meetings` is that record).
|
|
36
|
+
- Local folders are accounts with provider `folder` (an Obsidian vault and a plain folder look the same), and Drive's
|
|
37
|
+
stand-in account uses it too (WC-DATA-3).
|
|
38
|
+
- Contacts live in `identities`, with no address column: an address is only the `external_id` of provider `email`
|
|
39
|
+
(WC-DATA-6).
|
|
40
|
+
- `documents` keeps its source columns (`account_id`, `external_id`, `url`, `mime`, `front_matter`…) next to the
|
|
41
|
+
entity's own fields (WC-DATA-2).
|
|
42
|
+
- `meeting_participants` is unique on the meeting and the contact; its `external_id` is in no unique key (WC-DATA-3).
|
|
36
43
|
|
|
37
44
|
## Rules
|
|
38
45
|
|
|
@@ -59,7 +66,7 @@ to its entity. One entity may combine source records from several sources and ac
|
|
|
59
66
|
|
|
60
67
|
- **Why:** the same meeting in two calendars and in Zoom is one event to the owner; the same document in Drive and on
|
|
61
68
|
disk is one document.
|
|
62
|
-
- **Enforced by:**
|
|
69
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`, for the entity tables only; which table is which kind is review.
|
|
63
70
|
- **Example:** a Google Calendar entry is a `calendar_entries` row with `event_id`; the Zoom recording of that meeting
|
|
64
71
|
is a `meetings` row with the same `event_id`.
|
|
65
72
|
- **Exceptions:** an entity the owner makes has no source record; a source record may wait unlinked until matching
|
|
@@ -73,7 +80,7 @@ record has no account). Never in a column named after the provider (`google_id`,
|
|
|
73
80
|
`metadata`. A second id the provider also uses gets a named column of its own (`ical_uid`).
|
|
74
81
|
|
|
75
82
|
- **Why:** one place to look for every provider's id, and one unique key to update a record by.
|
|
76
|
-
- **Enforced by:**
|
|
83
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`.
|
|
77
84
|
- **Example:** a Drive file: `files.external_id = '<Drive file id>'`; a Google calendar entry:
|
|
78
85
|
`calendar_entries.external_id = '<event id>'`, `ical_uid = '<iCalUID>'`.
|
|
79
86
|
- **Exceptions:** a local file has no provider id; its `external_id` is its path relative to its folder root.
|
|
@@ -103,7 +110,7 @@ Times are UTC milliseconds ([Time](time.md)).
|
|
|
103
110
|
|
|
104
111
|
- **Why:** a reader of any table knows what a field holds, and a new table does not invent a third name for "edited
|
|
105
112
|
at".
|
|
106
|
-
- **Enforced by:**
|
|
113
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`.
|
|
107
114
|
- **Example:** `files.external_updated_at`, not `modified_at` or `modifiedAt` in JSON.
|
|
108
115
|
- **Exceptions:** existing columns keep their names until their table changes for another reason.
|
|
109
116
|
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1732.
|
|
@@ -141,17 +148,32 @@ link is made and the pair goes to the owner's review list. Title or time overlap
|
|
|
141
148
|
|
|
142
149
|
## Planned — not built yet
|
|
143
150
|
|
|
144
|
-
From the Wave 3 requirements
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (cli-private
|
|
152
|
+
`plans/2026-10-24-open-cli-core-design.md`, sections 3.3–3.6). Each item moves into the rules in the pull request that
|
|
153
|
+
builds it.
|
|
154
|
+
|
|
155
|
+
- **Files.** A `files` table holds the source records of `documents`: one row per item in one place (a local file, a
|
|
156
|
+
Drive file or folder, later a Notion page). It has `account_id`, `external_id` (a local file's path relative to its
|
|
157
|
+
root), `kind` (`file`, `folder`, `page`), `name`, `path` and `path_folded` from the account root, `parent_id` to the
|
|
158
|
+
folder's own row, `document_id`, the file facts and the provider's times (NEED-1739).
|
|
159
|
+
- **Matching a file to a document:** the same account and `external_id` is the same file; a `content_hash` equal to
|
|
160
|
+
the preferred file of exactly one live document joins that document; more than one candidate goes to the review
|
|
161
|
+
list; otherwise a new document. The document shows its most recently changed file, unless the owner pins one.
|
|
162
|
+
- **Folder accounts by kind.** Provider `obsidian` for a vault (front matter, tags, aliases and wiki links kept as
|
|
163
|
+
Obsidian's own metadata), `local` for a plain folder; Drive files under a real `google-drive` account. memo's vault
|
|
164
|
+
files are documents; `notes` are only the owner's notes on entities (NEED-1738).
|
|
165
|
+
- **Calendars.** `calendars` and `calendar_entries` (source records) feed `events` and `event_series` (entities).
|
|
166
|
+
Attendees are `calendar_entry_participants`; changes to tracked fields go to `calendar_entry_revisions` first. An
|
|
167
|
+
entry joins an event by, in order: the same Google id; the same series slot (`original_starts_at`); the same
|
|
168
|
+
`ical_uid` in another calendar; a Zoom event with the same `join_key` and an overlapping time; else a new event.
|
|
169
|
+
- **History on delete.** An event deleted in Google keeps its past occurrences with `source_deleted_at`; future ones
|
|
170
|
+
become `cancelled`. Nothing is removed without an explicit owner command (NEED-1764).
|
|
171
|
+
- **Contacts by address.** A lower-cased `identities.email` column; contacts with the same address link to one
|
|
172
|
+
person (method `email`) unless the owner said otherwise; two persons with one address go to the review list.
|
|
173
|
+
- **One link log and one review list.** `entity_link_events` logs link changes for events and documents in the shape
|
|
174
|
+
of `identity_link_events`; one review list of ambiguous pairs for every entity, resolved by the owner.
|
|
175
|
+
- **A CLI's own SQLite file**, opened through a cli-core helper ([Store](store.md), NEED-1737).
|
|
176
|
+
- **Later:** renaming `identities` to `contacts`; dropping `documents`' source columns once nothing reads them.
|
|
155
177
|
|
|
156
178
|
## Sources
|
|
157
179
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Kit surface
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
These are the parts of cli-core and cli-base that the template is built on. A CLI started with
|
|
6
|
+
`npx @wirecat/cli-template new` imports them from its first commit. When one of them changes in a way your code must
|
|
7
|
+
follow, the changelog gives the steps, and the template is already updated, so `npx @wirecat/cli-template upgrade`
|
|
8
|
+
brings the change into your CLI.
|
|
9
|
+
|
|
10
|
+
This page is a reference, not a rule page: it assigns no rule ids. How to start a CLI from the template is on the
|
|
11
|
+
[New CLI](new-cli.md) page. How a release is made is on the [Releases](releases.md) page.
|
|
12
|
+
|
|
13
|
+
## What the template imports
|
|
14
|
+
|
|
15
|
+
The list is what the template's `src/` imports today, type imports included.
|
|
16
|
+
|
|
17
|
+
| Package and path | Exports |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `@wirecat/cli-base` | `catchCrashes`, `CliError`, `configFilePath`, `mcpErrorResult`, `resolvePaths` |
|
|
20
|
+
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `describeProgram` |
|
|
21
|
+
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `itemOf`, `LOCAL_READ`, `outputFor`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
22
|
+
| `@wirecat/cli-core/mcp` | `McpCommand`, `registerCommands`, `runCommandForAgent`, `toolsFromCommands` |
|
|
23
|
+
| `@wirecat/cli-core/sends` | `Level` |
|
|
24
|
+
| `@wirecat/cli-core/update` | `appUpdate` |
|
|
25
|
+
| `@wirecat/cli-core/testing` | `ARGV_LOG`, `argvLogSetup` |
|
|
26
|
+
|
|
27
|
+
## When one of them changes
|
|
28
|
+
|
|
29
|
+
A change that makes a CLI change its code for one of these exports ships with two things:
|
|
30
|
+
|
|
31
|
+
1. **A migration note in the changelog**, under "Changed — may break callers": what to change in your code, step by
|
|
32
|
+
step. See [Documents](docs.md) for the changelog's shape.
|
|
33
|
+
2. **A green cli-template job before the pull request merges.** cli-base and cli-core build the template against
|
|
34
|
+
each pull request, so the template is updated in the same change, and `upgrade` has the new version to bring.
|
|
35
|
+
|
|
36
|
+
## How your CLI stays current
|
|
37
|
+
|
|
38
|
+
- **Exact versions.** The template depends on `@wirecat/cli-base` and `@wirecat/cli-core` at exact versions, the
|
|
39
|
+
normal setup for every WireCat CLI. A CLI made from it starts with the same pins.
|
|
40
|
+
- **Dependabot.** The template's `dependabot.yml` checks `@wirecat/*` every day and opens one pull request for the
|
|
41
|
+
shared packages within a day of a release. Its CI shows whether anything else needs to change.
|
|
42
|
+
- **`upgrade`.** In your CLI, with a clean git working tree:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npx @wirecat/cli-template upgrade
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For each template file that changed since your version, it asks: apply (merge the change into your file, keeping
|
|
49
|
+
your edits), replace, skip or diff. Without a terminal, `--apply`, `--replace` or `--skip` answers for every file,
|
|
50
|
+
and `--dry-run` lists what each file would get. Then re-run community's `adopt-standards.py` for `config/wirecat/`.
|
|
@@ -1,100 +1,81 @@
|
|
|
1
1
|
# New CLI
|
|
2
2
|
|
|
3
|
-
<!-- cspell:ignore rumdl whatsapp -->
|
|
4
|
-
|
|
5
3
|
## Scope
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
How to start a new WireCat CLI: one command that copies the template, the four steps that need a person, and the
|
|
6
|
+
checks that prove the result. A new CLI is done when every check in "Done when" passes.
|
|
9
7
|
|
|
10
8
|
This page is a checklist, not a rule page: it assigns no rule ids. The rules are on the area pages it links. How to
|
|
11
|
-
read a rule and which rule wins are in the [index](README.md).
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
store at a temporary directory, and coverage thresholds. Proved by: `pnpm test`. See [Testing](testing.md) and
|
|
82
|
-
[Store](store.md).
|
|
83
|
-
4. **`contracts/plan.json`** and its reviewed snapshots for `cli-contract` (cli-testing): help, discovery JSON, MCP
|
|
84
|
-
tools and the no-leak check. Proved by: `pnpm contracts:check`. See [Testing](testing.md).
|
|
85
|
-
5. **The test matrix**: `docs/dev/test-matrix.md`, built by `cli-dev test-matrix`. Proved by:
|
|
86
|
-
`cli-dev test-matrix --check`.
|
|
87
|
-
6. **The user pages**: `README.md`, `docs/meta.json` and the shared page set, with `docs/commands.md` generated from
|
|
88
|
-
the program. Proved by: `cli-dev docs-check --pages` and the CLI's `generate:check`. See [Documents](docs.md).
|
|
89
|
-
7. **`CHANGELOG.md`** with `## Unreleased` at the top and the shared headings, and
|
|
90
|
-
**`.github/workflows/changelog-guard.yml`** calling cli-base's `changelog-guard.yml` at a pinned commit. Proved by:
|
|
91
|
-
`cli-dev docs-check` and the guard on the first pull request. See [Documents](docs.md).
|
|
92
|
-
8. **`SECURITY.md`**: how to report a vulnerability, linking cli-testing's security method. See
|
|
93
|
-
[Security](security.md).
|
|
94
|
-
9. **`bin/release`**, **`release.yml`** and a release skill, copied from the nearest CLI. `bin/release` takes the
|
|
95
|
-
release lock (WC-REL-3) and npm's trusted publisher settings name `release.yml`. A messenger CLI also checks a signed
|
|
96
|
-
release report (WC-REL-10). See [Releases](releases.md).
|
|
97
|
-
10. **`src/version.ts`** kept in step with `package.json`. Proved by: `pnpm version:check`.
|
|
9
|
+
read a rule and which rule wins are in the [index](README.md). The parts of cli-base and cli-core the template is
|
|
10
|
+
built on are on the [Kit surface](kit-surface.md) page.
|
|
11
|
+
|
|
12
|
+
## Start with the template
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx @wirecat/cli-template new cal-cli --command cal --package @wirecat/cal-cli --owner WireCatLabs \
|
|
16
|
+
--category calendar --content-lang en,ru --sign-in oauth --api official-client --filters time,person,text \
|
|
17
|
+
--never-store tokens,secret-links --untrusted names,links
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
In a terminal, `new` asks for every answer you leave out. Without a terminal, a missing answer is an error that lists
|
|
21
|
+
the choices. Nothing is guessed.
|
|
22
|
+
|
|
23
|
+
| Flag | What it sets | Choices |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `<name>-cli` | the repository folder | any name |
|
|
26
|
+
| `--command` | the command word people type | one word |
|
|
27
|
+
| `--package` | the npm package name | a scoped name, such as `@wirecat/cal-cli` |
|
|
28
|
+
| `--owner` | the GitHub owner of the repository | an owner |
|
|
29
|
+
| `--category` | what the CLI reads | `messenger`, `mail`, `notes`, `files`, `calendar`, `meetings` |
|
|
30
|
+
| `--content-lang` | the languages of the content | a list, such as `en,ru` |
|
|
31
|
+
| `--sign-in` | how the CLI signs in | `oauth`, `token`, `session`, `none` |
|
|
32
|
+
| `--api` | what the service offers | `official-client`, `official-docs`, `unofficial` |
|
|
33
|
+
| `--filters` | how people narrow a list | a list of `time`, `person`, `container`, `text`, `status` |
|
|
34
|
+
| `--never-store` | what the store must never keep | a list of `tokens`, `codes`, `secret-links`, `content` |
|
|
35
|
+
| `--untrusted` | what text comes from other people | a list of `messages`, `files`, `links`, `names`, or `none` |
|
|
36
|
+
|
|
37
|
+
Every choice question also takes `other:<your answer>`.
|
|
38
|
+
|
|
39
|
+
`new` copies the template of its own package version into `<name>-cli/`, renames the command word everywhere, runs
|
|
40
|
+
`git init`, records the template version in `template.json`, writes `docs/dev/DESIGN.md` from your answers and names
|
|
41
|
+
a reference CLI for your category in `AGENTS.md`. It does not install packages, push, create the repository or
|
|
42
|
+
publish. Then:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
cd cal-cli && pnpm install && pnpm ci:quick
|
|
46
|
+
git add -A && git commit -m "chore: start from @wirecat/cli-template"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explicitly, such as `cal personal doctor`.
|
|
50
|
+
The reference CLI is the one to compare with when a step is unclear.
|
|
51
|
+
|
|
52
|
+
The copy already holds every file this handbook asks for: the required root commands (`commands`, `doctor`,
|
|
53
|
+
`skill show`, `skill install`, `mcp`, `upgrade`) and `config` from cli-core, the MCP server with a tool for every
|
|
54
|
+
command, writes included, the community presets in `config/wirecat/`, the TypeScript, Biome and vitest setup,
|
|
55
|
+
`contracts/`, `lint/`, the test matrix, the user pages, `CHANGELOG.md`, `SECURITY.md`, `bin/release`, and the
|
|
56
|
+
`pr.yml`, `ci.yml`, `main.yml`, `standards.yml`, `changelog-guard.yml`, `monthly.yml` and `release.yml` workflows.
|
|
57
|
+
The rules they keep are on the area pages: [Commands](commands.md), [MCP](mcp.md), [Accounts](accounts.md),
|
|
58
|
+
[Permissions](permissions.md), [Testing](testing.md), [Documents](docs.md), [Releases](releases.md) and [CI](ci.md).
|
|
59
|
+
|
|
60
|
+
Later template changes come in with `npx @wirecat/cli-template upgrade`, file by file: apply, replace, skip or diff.
|
|
61
|
+
See [Kit surface](kit-surface.md#how-your-cli-stays-current).
|
|
62
|
+
|
|
63
|
+
## Four steps that need a person
|
|
64
|
+
|
|
65
|
+
`new` prints these at the end.
|
|
66
|
+
|
|
67
|
+
1. **Create the repository** under the owner, with a ruleset on the default branch: no deletion, no force push,
|
|
68
|
+
changes only through a pull request, required status checks, and no bypass. Add the ruleset after the first
|
|
69
|
+
commit is on `main`, since a pull request rule leaves an empty repository no way to get one, and the required
|
|
70
|
+
check names exist only once CI has run. See [Releases](releases.md).
|
|
71
|
+
2. **Set npm trusted publishing** for the new package, naming `release.yml`, and add any secrets the CI needs. See
|
|
72
|
+
[WC-REL-11](releases.md#wc-rel-11--publish-from-github-actions-with-npm-trusted-publishing).
|
|
73
|
+
3. **Pick a monthly cron day** no other repository uses. `.github/workflows/monthly.yml` holds a placeholder that CI
|
|
74
|
+
rejects. See [CI](ci.md).
|
|
75
|
+
4. **WireCat CLIs only: join the shared lists.** A pull request to cli-base's `PARITY_GROUPS` (at least the `all`
|
|
76
|
+
group, which compares the shared root commands), a line in cli-core's `WIRECAT_TOOLS` and a line in cli-docs'
|
|
77
|
+
`tools.json`. A messenger also joins cli-core's `parity.json`. A CLI outside WireCatLabs keeps its parity groups
|
|
78
|
+
in its own repository and runs `cli-dev audit --groups <file>`. See [Testing](testing.md).
|
|
98
79
|
|
|
99
80
|
## Done when
|
|
100
81
|
|
|
@@ -32,9 +32,10 @@ on the [CI](ci.md) page.
|
|
|
32
32
|
- **Exceptions:** none.
|
|
33
33
|
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
34
34
|
|
|
35
|
-
### WC-REL-3 · `bin/release` takes
|
|
35
|
+
### WC-REL-3 · `bin/release` takes its package's release lock before it builds or publishes
|
|
36
36
|
|
|
37
|
-
- **Why:** two sessions published at the same time on one machine
|
|
37
|
+
- **Why:** two sessions published the same package at the same time on one machine. Different packages do not
|
|
38
|
+
conflict, so each package has its own lock and they release side by side.
|
|
38
39
|
- **Enforced by:** `<app>: bin/release`.
|
|
39
40
|
- **Example:**
|
|
40
41
|
|
|
@@ -44,9 +45,12 @@ on the [CI](ci.md) page.
|
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
The helper comes from the community presets (`adopt-standards.py --only release-lock`). It holds
|
|
47
|
-
`${XDG_RUNTIME_DIR:-/tmp}/leemour-release
|
|
48
|
-
|
|
49
|
-
-
|
|
48
|
+
`${XDG_RUNTIME_DIR:-/tmp}/leemour-release-<key>.lock` with `flock` on file descriptor 9 until the release ends.
|
|
49
|
+
`<key>` is the `name` in `package.json`, made safe for a file name: `@wirecat/cli-core` locks
|
|
50
|
+
`leemour-release-wirecat-cli-core.lock`. The key is the package, not the folder, so every checkout and worktree
|
|
51
|
+
of one package shares the lock. A `package.json` without a name refuses the release.
|
|
52
|
+
- **Exceptions:** none.
|
|
53
|
+
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.41.0. **Rulings:** NEED-854, ASK-336.
|
|
50
54
|
|
|
51
55
|
### WC-REL-4 · Never break the release lock; wait for the holder and try again
|
|
52
56
|
|
|
@@ -95,7 +99,8 @@ on the [CI](ci.md) page.
|
|
|
95
99
|
- **Enforced by:** review only.
|
|
96
100
|
- **Example:** tg-cli can release an hour after its last version, and cli-core can ship a breaking change on the same
|
|
97
101
|
day as the previous one. Bump the library in every CLI that uses it on the day it ships.
|
|
98
|
-
- **Exceptions:** none. The release lock (WC-REL-3) still keeps two releases on one machine from
|
|
102
|
+
- **Exceptions:** none. The release lock (WC-REL-3) still keeps two releases of one package on one machine from
|
|
103
|
+
running at once.
|
|
99
104
|
- **Applies to:** all CLIs and shared libraries. **Since:** cli-base 0.52.0. **Rulings:** NEED-1741.
|
|
100
105
|
|
|
101
106
|
### WC-REL-9 · Merged into [WC-REL-8](#wc-rel-8--any-package-may-release-at-any-time)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Security
|
|
2
2
|
|
|
3
|
-
<!-- cspell:words zizmor -->
|
|
3
|
+
<!-- cspell:words zizmor PKCE -->
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
|
|
@@ -271,6 +271,21 @@ not carried out. A name that matches is not proof of who someone is.
|
|
|
271
271
|
tells an agent about it is [WC-MCP-11](mcp.md#wc-mcp-11--the-server-tells-the-agent-that-message-text-is-data).
|
|
272
272
|
- **Applies to:** all CLIs. **Since:** cli-base 0.44.0. **Rulings:** none.
|
|
273
273
|
|
|
274
|
+
## Planned — not built yet
|
|
275
|
+
|
|
276
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (sections 3.4 and 3.7). Each item moves into the
|
|
277
|
+
rules in the pull request that builds it.
|
|
278
|
+
|
|
279
|
+
- **One sign-in module** in cli-core, `@wirecat/cli-core/oauth`, with no store and no search: authorize (a loopback
|
|
280
|
+
address on `127.0.0.1` or a relay page, `state`, PKCE S256, a timeout, cancel, a paste fallback), exchange, refresh
|
|
281
|
+
and revoke, and a token keeper with a lock per profile across processes. A CLI declares its provider and may
|
|
282
|
+
replace any step, but keeps the explicit profile, the lock, and tokens that are never printed (NEED-1734).
|
|
283
|
+
- **Keys in one place:** one keyring naming scheme for new entries; today's entries of drive and zm are read as they
|
|
284
|
+
are, so no one signs in again.
|
|
285
|
+
- **Meeting secrets never reach the store:** passcodes, PINs, passwords and access codes from a calendar's conference
|
|
286
|
+
data, and the query string of any join URL. Lines of a description that carry a passcode, password or PIN are
|
|
287
|
+
removed.
|
|
288
|
+
|
|
274
289
|
## Sources
|
|
275
290
|
|
|
276
291
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
package/docs/standards/store.md
CHANGED
|
@@ -185,6 +185,17 @@ messages), or other people's data.
|
|
|
185
185
|
- **Exceptions:** none.
|
|
186
186
|
- **Applies to:** all CLIs that use the store, and cli-core. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
187
187
|
|
|
188
|
+
## Planned — not built yet
|
|
189
|
+
|
|
190
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (NEED-1737). It moves into the rules in the pull
|
|
191
|
+
request that builds the helper.
|
|
192
|
+
|
|
193
|
+
- **A CLI's own tables live in its own SQLite file**, next to the shared store, with its own migrations. They refer to
|
|
194
|
+
core rows by id only, and no other CLI opens that file. A cli-core helper opens it (the path, the migrations, the
|
|
195
|
+
same driver); it is built when the first CLI needs private tables.
|
|
196
|
+
- **WC-STORE-5 gets a check:** a grit plugin, `no-sqlite-driver`, that refuses a CLI's import of a SQLite driver or
|
|
197
|
+
of `drizzle-orm`. cli-core, which owns the store, turns it off.
|
|
198
|
+
|
|
188
199
|
## Sources
|
|
189
200
|
|
|
190
201
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.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",
|