@wirecat/cli-base 0.63.0 → 0.64.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 +113 -0
- package/dist/codegen/discovery.d.ts +3 -3
- package/dist/codegen/discovery.js +18 -9
- package/dist/codegen/index.d.ts +3 -1
- package/dist/codegen/index.js +3 -1
- package/dist/codegen/json-schema.d.ts +8 -0
- package/dist/codegen/json-schema.js +78 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/dev/bundle.d.ts +25 -0
- package/dist/dev/bundle.js +271 -0
- package/dist/dev/main.js +48 -1
- package/dist/skill/index.d.ts +1 -1
- package/dist/skill/index.js +2 -2
- package/dist/standards/audit.js +1 -1
- package/dist/standards/flags-calendar.d.ts +2 -0
- package/dist/standards/flags-calendar.js +59 -0
- package/dist/standards/flags-how.js +7 -1
- package/dist/standards/vocabulary.js +10 -1
- package/docs/standards/README.md +3 -3
- package/docs/standards/accounts.md +2 -2
- package/docs/standards/ci.md +33 -12
- package/docs/standards/commands.md +3 -2
- package/docs/standards/kit-surface.md +3 -3
- package/docs/standards/layout.md +32 -0
- package/docs/standards/new-cli.md +28 -14
- package/docs/standards/permissions.md +33 -3
- package/docs/standards/releases.md +13 -3
- package/docs/standards/security.md +4 -3
- package/docs/standards/store.md +1 -1
- package/docs/standards/testing.md +9 -3
- package/docs/standards/toolchain.md +17 -16
- package/package.json +6 -1
package/dist/dev/main.js
CHANGED
|
@@ -66,7 +66,15 @@ const USAGE = `usage: cli-dev <command> [options]
|
|
|
66
66
|
rules by its own lint/ folder; the rest by --baseline
|
|
67
67
|
(audit/baseline). --groups replaces PARITY_GROUPS with the
|
|
68
68
|
JSON list of parity groups in that file; --differences adds
|
|
69
|
-
to the groups in use
|
|
69
|
+
to the groups in use
|
|
70
|
+
bundle --bin <dist/bin/name.js> [--external <package>]... [--anchor <package>]... [--harmless <path>]...
|
|
71
|
+
bundles the compiled bin with esbuild (a devDependency) into
|
|
72
|
+
dist/bundle/ and turns the bin into its loader. --external keeps
|
|
73
|
+
a package outside (* matches anything); --anchor adds a package
|
|
74
|
+
whose modules keep their installed place, as cli-core's do;
|
|
75
|
+
--harmless names a third-party file whose import.meta or
|
|
76
|
+
__dirname was read and found harmless. Fails when a bundled
|
|
77
|
+
module would look for a file or package in the wrong place`;
|
|
70
78
|
const load = async (cwd, path) => (await import(pathToFileURL(isAbsolute(path) ? path : join(cwd, path)).href));
|
|
71
79
|
const report = (io, problems, ok) => {
|
|
72
80
|
for (const problem of problems)
|
|
@@ -604,6 +612,43 @@ const auditRun = (args, io) => {
|
|
|
604
612
|
io.err(problem);
|
|
605
613
|
return problems.length > 0 ? 1 : 0;
|
|
606
614
|
};
|
|
615
|
+
const bundleCommand = async (args, io) => {
|
|
616
|
+
const { values } = parseArgs({
|
|
617
|
+
args,
|
|
618
|
+
options: {
|
|
619
|
+
bin: { type: "string" },
|
|
620
|
+
external: { type: "string", multiple: true },
|
|
621
|
+
anchor: { type: "string", multiple: true },
|
|
622
|
+
harmless: { type: "string", multiple: true },
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
if (!values.bin) {
|
|
626
|
+
io.err("bundle needs --bin <dist/bin/name.js>");
|
|
627
|
+
return 2;
|
|
628
|
+
}
|
|
629
|
+
// esbuild is an optional peer: the other commands, and a CLI that does not bundle, never load it.
|
|
630
|
+
try {
|
|
631
|
+
await import("esbuild");
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
io.err("cli-dev bundle needs esbuild: add it as a devDependency, in cli-base's peer range");
|
|
635
|
+
return 1;
|
|
636
|
+
}
|
|
637
|
+
const { bundle } = await import("./bundle.js");
|
|
638
|
+
const result = await bundle({
|
|
639
|
+
root: io.cwd,
|
|
640
|
+
bin: values.bin,
|
|
641
|
+
external: values.external ?? [],
|
|
642
|
+
anchored: values.anchor ?? [],
|
|
643
|
+
harmless: values.harmless ?? [],
|
|
644
|
+
});
|
|
645
|
+
if (result.problems.length > 0) {
|
|
646
|
+
io.err(`the bundle would look for these in the wrong place, or is incomplete:\n${result.problems.join("\n")}`);
|
|
647
|
+
return 1;
|
|
648
|
+
}
|
|
649
|
+
io.out(`bundle: ${values.bin} loads dist/bundle/; a server preloads ${result.chunks.length} chunks`);
|
|
650
|
+
return 0;
|
|
651
|
+
};
|
|
607
652
|
export const main = async (argv, io) => {
|
|
608
653
|
const [command, ...args] = argv;
|
|
609
654
|
if (args.includes("--help")) {
|
|
@@ -649,6 +694,8 @@ export const main = async (argv, io) => {
|
|
|
649
694
|
return lint(args, io);
|
|
650
695
|
case "audit":
|
|
651
696
|
return audit(args, io);
|
|
697
|
+
case "bundle":
|
|
698
|
+
return bundleCommand(args, io);
|
|
652
699
|
case "help":
|
|
653
700
|
case "--help":
|
|
654
701
|
case "-h":
|
package/dist/skill/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface SkillApp {
|
|
|
11
11
|
}
|
|
12
12
|
export declare const SKILL_TARGETS: readonly ["claude", "agents"];
|
|
13
13
|
export type SkillTarget = (typeof SKILL_TARGETS)[number];
|
|
14
|
-
/** `~/.claude/skills` is Claude Code's; `~/.agents/skills` is read by Codex
|
|
14
|
+
/** `~/.claude/skills` is Claude Code's; `~/.agents/skills` is read by Codex, Gemini CLI and OpenClaw. */
|
|
15
15
|
export declare const skillPath: (target: SkillTarget, appName: string, env: Env) => string;
|
|
16
16
|
/**
|
|
17
17
|
* `metadata.version` from the frontmatter — where the Agent Skills specification keeps fields of its
|
package/dist/skill/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { executableEnvironment, executableOnPath } from "../executable.js";
|
|
|
16
16
|
import { CHECK_EVERY_MS, isNewer, readNoticeState, writeUpdateState } from "../update/index.js";
|
|
17
17
|
export const SKILL_TARGETS = ["claude", "agents"];
|
|
18
18
|
const homeOf = (env) => env.HOME || env.USERPROFILE || homedir();
|
|
19
|
-
/** `~/.claude/skills` is Claude Code's; `~/.agents/skills` is read by Codex
|
|
19
|
+
/** `~/.claude/skills` is Claude Code's; `~/.agents/skills` is read by Codex, Gemini CLI and OpenClaw. */
|
|
20
20
|
export const skillPath = (target, appName, env) => join(homeOf(env), target === "claude" ? ".claude" : ".agents", "skills", appName, "SKILL.md");
|
|
21
21
|
const FRONTMATTER = /^---(\r?\n)([\s\S]*?\r?\n)?---(\r?\n|$)/;
|
|
22
22
|
const scalar = (key, indent) => new RegExp(`^${indent}${key}:[ \\t]*["']?([^"'\\r\\n]*?)["']?[ \\t]*\\r?\\n?$`);
|
|
@@ -234,7 +234,7 @@ export const skillCommand = (app, skill, environment, more = {}) => {
|
|
|
234
234
|
const named = Object.keys(more);
|
|
235
235
|
const show = annotate(command.command("show"), { effects: { remote: "none", local: "read" } })
|
|
236
236
|
.summary("print the tool's instructions for AI agents")
|
|
237
|
-
.description(`print SKILL.md — \`${app.command} skill install\` puts it where Claude Code, Codex
|
|
237
|
+
.description(`print SKILL.md — \`${app.command} skill install\` puts it where Claude Code, Codex, Gemini CLI and OpenClaw look for it, and where Hermes does on request; --for prints the copy already there`);
|
|
238
238
|
if (named.length > 0)
|
|
239
239
|
show.argument("[name]", `one of the skills shipped for a task: ${named.join(", ")}`);
|
|
240
240
|
show.addOption(new Option("--for <agents>", "print the copy installed for these agents instead of this version's").choices(SKILL_TARGETS));
|
package/dist/standards/audit.js
CHANGED
|
@@ -64,7 +64,7 @@ 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", "gcal"],
|
|
68
68
|
covers: ["commands", "doctor", "skill", "mcp", "upgrade", "search", "notes", "meetings"],
|
|
69
69
|
differences: [],
|
|
70
70
|
},
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const CALENDAR_FLAGS = [
|
|
2
|
+
{ name: "--end-time", value: "<time>", role: "value", type: "time", meaning: "when it ends" },
|
|
3
|
+
{
|
|
4
|
+
name: "--start-date",
|
|
5
|
+
value: "<date>",
|
|
6
|
+
role: "value",
|
|
7
|
+
type: "time",
|
|
8
|
+
meaning: "the first day of something that lasts whole days, such as an all-day event",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "--end-date",
|
|
12
|
+
value: "<date>",
|
|
13
|
+
role: "value",
|
|
14
|
+
type: "time",
|
|
15
|
+
meaning: "the last day of something that lasts whole days, such as an all-day event",
|
|
16
|
+
},
|
|
17
|
+
{ name: "--location", value: "<text>", role: "value", type: "string", meaning: "where it takes place" },
|
|
18
|
+
{
|
|
19
|
+
name: "--attendee",
|
|
20
|
+
value: "<email>",
|
|
21
|
+
role: "value",
|
|
22
|
+
type: "string",
|
|
23
|
+
meaning: "a person invited to the event, by email address; repeat it for more",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "--id",
|
|
27
|
+
value: "<id>",
|
|
28
|
+
role: "value",
|
|
29
|
+
type: "id",
|
|
30
|
+
meaning: "the id to create it with, chosen beforehand, so a retry finds it instead of making a second one",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "--calendar",
|
|
34
|
+
value: "<id>",
|
|
35
|
+
role: "target",
|
|
36
|
+
type: "id",
|
|
37
|
+
meaning: "the calendar it acts on, when the thing is on more than one",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "--repeat",
|
|
41
|
+
value: "<rule>",
|
|
42
|
+
role: "value",
|
|
43
|
+
type: "string",
|
|
44
|
+
meaning: "how it repeats, as an RFC 5545 rule such as FREQ=WEEKLY;BYDAY=MO;COUNT=10",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "--hours",
|
|
48
|
+
value: "<from-to>",
|
|
49
|
+
role: "target",
|
|
50
|
+
type: "string",
|
|
51
|
+
meaning: "the working hours of each day to look in, such as 09:00-17:00, in the owner's time zone",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "--series",
|
|
55
|
+
role: "target",
|
|
56
|
+
type: "switch",
|
|
57
|
+
meaning: "act on the whole repeating event, not only this occurrence",
|
|
58
|
+
},
|
|
59
|
+
];
|
|
@@ -308,7 +308,13 @@ export const HOW_FLAGS = [
|
|
|
308
308
|
meaning: "do not keep this run, whatever the configuration says",
|
|
309
309
|
},
|
|
310
310
|
{ name: "--no-registries", role: "mode", type: "switch", meaning: "do not ask the public ban lists" },
|
|
311
|
-
{
|
|
311
|
+
{
|
|
312
|
+
name: "--notify",
|
|
313
|
+
role: "mode",
|
|
314
|
+
type: "switch",
|
|
315
|
+
meaning: "tell the people it concerns about it: the chat's members, or an event's attendees",
|
|
316
|
+
},
|
|
317
|
+
{ name: "--no-notify", role: "mode", type: "switch", meaning: "tell nobody about it" },
|
|
312
318
|
{ name: "--offline", role: "mode", type: "switch", meaning: "answer from what was recorded and never connect" },
|
|
313
319
|
{
|
|
314
320
|
name: "--operation-id",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
+
import { CALENDAR_FLAGS } from "./flags-calendar.js";
|
|
2
3
|
import { HOW_FLAGS } from "./flags-how.js";
|
|
3
4
|
import { WHAT_FLAGS } from "./flags-what.js";
|
|
4
5
|
import { duplicates, parseRegistry, text, word } from "./registry.js";
|
|
@@ -123,6 +124,7 @@ const verbs = [
|
|
|
123
124
|
"bring the local store up to date from the source: only what changed, or the whole list where the source cannot say; a mirror syncs both ways",
|
|
124
125
|
],
|
|
125
126
|
["fetch", "one bounded pull of one named thing into the store, not a catch-up"],
|
|
127
|
+
["unsync", "stop syncing a thing; what the store already holds stays"],
|
|
126
128
|
["export", "write from the store to a file"],
|
|
127
129
|
["import", "bring a file or list the user gives into the store or the messenger, once"],
|
|
128
130
|
["download", "save a file from the source to this machine"],
|
|
@@ -153,6 +155,7 @@ const verbs = [
|
|
|
153
155
|
["unstar", "take the owner's star off a message"],
|
|
154
156
|
["vote", "answer a poll"],
|
|
155
157
|
["answer", "reply to a button someone pressed under a bot's message"],
|
|
158
|
+
["respond", "answer an invitation: accept, tentatively accept or decline it"],
|
|
156
159
|
["press", "press a button under a bot's message; the bot sees who pressed it"],
|
|
157
160
|
["action", "show what a bot is doing in a chat, such as typing, for a few seconds"],
|
|
158
161
|
["watch", "follow new events as they come, and keep them"],
|
|
@@ -175,6 +178,8 @@ const verbs = [
|
|
|
175
178
|
["acknowledge", "record successful delivery using the exact receipt issued when the item was claimed"],
|
|
176
179
|
["confirm", "the owner accepts what an agent proposed"],
|
|
177
180
|
["reject", "the owner refuses what an agent proposed"],
|
|
181
|
+
["review", "list what waits for the owner's decision"],
|
|
182
|
+
["resolve", "settle one thing that waits for the owner's decision, with the owner's choice"],
|
|
178
183
|
["move", "move a file in the source"],
|
|
179
184
|
["copy", "copy a file in the source"],
|
|
180
185
|
["run", "run a saved job once, now, as its timer would"],
|
|
@@ -241,6 +246,9 @@ const collections = [
|
|
|
241
246
|
["files", "files in the source, such as Google Drive, read and changed there"],
|
|
242
247
|
["comments", "comments people left on a thing in the source: a channel post or a file"],
|
|
243
248
|
["meetings", "stored meetings, from Zoom or another provider"],
|
|
249
|
+
["calendars", "calendars of an account, each synced or not"],
|
|
250
|
+
["events", "calendar entries: meetings, appointments and other things on a calendar"],
|
|
251
|
+
["people", "persons the owner deals with, such as the attendees of events"],
|
|
244
252
|
["recordings", "the files a provider recorded of a meeting"],
|
|
245
253
|
["jobs", "work this tool runs in the background or on a timer"],
|
|
246
254
|
["actions", "what an agent proposes to do; the owner confirms or rejects each"],
|
|
@@ -302,6 +310,7 @@ const views = [
|
|
|
302
310
|
["evidence", "the stored messages or lines a result or a reference rests on"],
|
|
303
311
|
["passage", "a cited transcript line with bounded surrounding lines from the same revision"],
|
|
304
312
|
["agenda", "what needs a look today"],
|
|
313
|
+
["busy", "when calendars or people are taken, without what takes them"],
|
|
305
314
|
["categories", "the categories a thing takes, with what each means"],
|
|
306
315
|
["domains", "the internet domains links point to, most linked first"],
|
|
307
316
|
["places", "the street addresses confirmed as places, most mentioned first"],
|
|
@@ -348,5 +357,5 @@ export const VOCABULARY = defineVocabulary({
|
|
|
348
357
|
],
|
|
349
358
|
views: views.map(([entry, meaning]) => ({ word: entry, meaning })),
|
|
350
359
|
roots: roots.map(([entry, meaning]) => ({ word: entry, meaning })),
|
|
351
|
-
flags: [...HOW_FLAGS, ...WHAT_FLAGS],
|
|
360
|
+
flags: [...HOW_FLAGS, ...WHAT_FLAGS, ...CALENDAR_FLAGS],
|
|
352
361
|
});
|
package/docs/standards/README.md
CHANGED
|
@@ -28,12 +28,12 @@ the four steps that need a person. A new CLI is ready for its first release when
|
|
|
28
28
|
| Page | Area id | Rules | What it covers |
|
|
29
29
|
|---|---|---:|---|
|
|
30
30
|
| [Toolchain](toolchain.md) | `TOOL` | 20 | TypeScript, Biome, grit plugins, size caps, import cycles, spelling, Markdown, Go |
|
|
31
|
-
| [Layout](layout.md) | `LAY` |
|
|
31
|
+
| [Layout](layout.md) | `LAY` | 10 | Repository layout, entry points, exports, file size, one job per file, splitting a unit, the bundled bin |
|
|
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
35
|
| [Accounts](accounts.md) | `ACCT` | 11 | The explicit profile, reads across accounts, scope flags, linked identities |
|
|
36
|
-
| [Permissions](permissions.md) | `PERM` |
|
|
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
38
|
| [Lifecycle](lifecycle.md) | `LIFE` | 14 | Opening late, who closes, one-shot exits, long-running commands, the `serve` relay, resumable syncs, the run log |
|
|
39
39
|
| [Store](store.md) | `STORE` | 14 | Store areas, account scope, migrations, test isolation |
|
|
@@ -84,7 +84,7 @@ Every rule has the same shape. A rule may add a sentence or a short list under i
|
|
|
84
84
|
CLI's `node_modules`;
|
|
85
85
|
- a check in another repository, as `<repo>: <check>`, such as `cli-core: pnpm parity:check` or
|
|
86
86
|
`cli-core: biome noRestrictedImports`. `<repo>` is the repository's name on GitHub: `cli-core`, `cli-testing`,
|
|
87
|
-
`cli-docs`, `community`, `tg-cli`, `max-cli`, `cli-memo`, `drive-cli`, `zoom-cli` or `
|
|
87
|
+
`cli-docs`, `community`, `tg-cli`, `max-cli`, `cli-memo`, `drive-cli`, `zoom-cli`, `whatsapp-cli` or `gcal-cli`. `<app>`
|
|
88
88
|
stands for each CLI the rule applies to, when every one of them runs that check in its own repository.
|
|
89
89
|
- **Since** is the cli-base release whose handbook first carried the rule. A feature can be older than its rule.
|
|
90
90
|
|
|
@@ -57,8 +57,8 @@ CLI with a `setup` command suggests it, and any other CLI suggests the command t
|
|
|
57
57
|
- **Enforced by:** `cli-core: src/cli/settings.test.ts` · `cli-core: src/cli/program.test.ts`.
|
|
58
58
|
- **Example:** ``name a profile: `tg <profile> …` — profiles here: personal, work (or set TG_PROFILE)``. With none,
|
|
59
59
|
tg, which has `setup`, says to ``create one with `tg personal setup` (any name in place of personal)``. A CLI
|
|
60
|
-
without `setup`, such as one made from cli-template, answers `
|
|
61
|
-
on this computer yet — any name works, such as `
|
|
60
|
+
without `setup`, such as one made from cli-template, answers `gcal events list` with ``name a profile: there is none
|
|
61
|
+
on this computer yet — any name works, such as `gcal personal events list` (or set GCAL_PROFILE)`` (cli-core 0.424.0
|
|
62
62
|
and later).
|
|
63
63
|
- **Exceptions:** none.
|
|
64
64
|
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
package/docs/standards/ci.md
CHANGED
|
@@ -10,8 +10,9 @@ Rule ids on this page are `WC-CI-<n>`. How to read a rule, how ids work and whic
|
|
|
10
10
|
[Releases page](releases.md); which scanners must exist and how workflows are hardened is on the
|
|
11
11
|
[Security page](security.md).
|
|
12
12
|
|
|
13
|
-
"The
|
|
14
|
-
|
|
13
|
+
"The WireCat repositories" on this page means every WireCat CLI (today tg, max, memo, drive, zoom, wa and gcal; a
|
|
14
|
+
CLI made with `npx @wirecat/cli-template new` counts from its first commit), cli-base, cli-core, cli-testing, cli-docs,
|
|
15
|
+
plus community itself. cli-template follows every rule here as a CLI does
|
|
15
16
|
([New CLI](new-cli.md#the-template-repository-itself)).
|
|
16
17
|
|
|
17
18
|
## Rules
|
|
@@ -24,7 +25,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
24
25
|
`gitleaks git --staged --redact`. A clone without gitleaks gets a warning and can still commit; CI scans anyway.
|
|
25
26
|
The `prepare` script installs the hooks; `LEFTHOOK=0 git commit` skips them for one commit.
|
|
26
27
|
- **Exceptions:** none.
|
|
27
|
-
- **Applies to:** the
|
|
28
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
28
29
|
|
|
29
30
|
### WC-CI-2 · Add no push hook, and no hook that installs, builds, checks types, generates or tests
|
|
30
31
|
|
|
@@ -34,7 +35,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
34
35
|
does not run `standards:check`, the full lint, spelling, a build or the test suite on each commit or push; it runs a
|
|
35
36
|
targeted check when it needs one.
|
|
36
37
|
- **Exceptions:** none. cli-base `config/lefthook.yml` still has a pre-push typecheck, but no repository extends it.
|
|
37
|
-
- **Applies to:** the
|
|
38
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
38
39
|
|
|
39
40
|
### WC-CI-3 · Run the fast standards job on every pull request and every push to `main`
|
|
40
41
|
|
|
@@ -45,7 +46,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
45
46
|
commit cancels the run it replaces. The job times out after five minutes, a ceiling for a stalled setup, not an
|
|
46
47
|
expected time.
|
|
47
48
|
- **Exceptions:** none. Runner queues and cold downloads are outside the budget.
|
|
48
|
-
- **Applies to:** the
|
|
49
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
49
50
|
|
|
50
51
|
### WC-CI-4 · Keep `ci:quick` to early bug checks that finish within two minutes
|
|
51
52
|
|
|
@@ -61,7 +62,7 @@ cli-testing, cli-docs, plus community itself. cli-template follows every rule he
|
|
|
61
62
|
- **Exceptions:** never put live-account suites, agent suites or platform jobs here. Full tests and coverage run in
|
|
62
63
|
the pull request validation job ([WC-CI-5](#wc-ci-5--run-the-full-linux-validation-on-every-pull-request-and-keep-platform-suites-out)),
|
|
63
64
|
not in `ci:quick`.
|
|
64
|
-
- **Applies to:** the
|
|
65
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
65
66
|
|
|
66
67
|
### WC-CI-5 · Run the full Linux validation on every pull request, and keep platform suites out
|
|
67
68
|
|
|
@@ -139,10 +140,13 @@ cannot pass yet; the `skip` input names it.
|
|
|
139
140
|
|
|
140
141
|
The shared workflows are not in the npm package, because CI runs the pinned commit, not the installed copy. To see
|
|
141
142
|
what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/…@<commit>` lines in `ci.yml`,
|
|
142
|
-
`release.yml` and `monthly.yml`, and opens each workflow file at that commit in cli-base.
|
|
143
|
+
`release.yml` and `monthly.yml`, and opens each workflow file at that commit in cli-base. The fresh-agent audit on the
|
|
144
|
+
[New CLI](new-cli.md#done-when) page is given these files for this reason, beside the handbook and the repository;
|
|
145
|
+
its prompt in cli-template says so from cli-template 0.8.0. A CLI on an older template takes the prompt with
|
|
146
|
+
`upgrade`.
|
|
143
147
|
|
|
144
148
|
- **Why:** one shared workflow keeps the full suite the same everywhere, and a pin keeps a cli-base change from
|
|
145
|
-
breaking
|
|
149
|
+
breaking every repository at once.
|
|
146
150
|
- **Enforced by:** `ci.yml:ci`.
|
|
147
151
|
- **Example:** `uses: WireCatLabs/cli-base/.github/workflows/node-ci.yml@<commit> # v0.31.0`, with `cli: true` for
|
|
148
152
|
the CLI steps (WC-CI-7), the project's extra checks in the `checks` input and `bun-run` for the Bun job. A job that only one repository needs stays beside it in
|
|
@@ -158,12 +162,29 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
158
162
|
- **Example:** `monthly.yml`, from the community template, calls the repository's `ci.yml` and the pinned cli-base
|
|
159
163
|
`release-checks.yml`. Each repository has its own minute, between 02:17 and 03:20 UTC, so runs do not overlap. No
|
|
160
164
|
check compares the minutes across repositories; review keeps them apart. A CLI made from cli-template starts with
|
|
161
|
-
the template's own minute and changes it (see [New CLI](new-cli.md)).
|
|
165
|
+
the template's own minute and changes it (see [New CLI](new-cli.md)). Taken minutes, from each `monthly.yml` on
|
|
166
|
+
the default branch:
|
|
167
|
+
|
|
168
|
+
| UTC | Repository |
|
|
169
|
+
|---|---|
|
|
170
|
+
| 02:17 | cli-base |
|
|
171
|
+
| 02:24 | whatsapp-cli |
|
|
172
|
+
| 02:27 | gcal-cli |
|
|
173
|
+
| 02:31 | cli-memo |
|
|
174
|
+
| 02:38 | cli-core |
|
|
175
|
+
| 02:45 | drive-cli |
|
|
176
|
+
| 02:52 | cli-testing |
|
|
177
|
+
| 02:59 | max-cli |
|
|
178
|
+
| 03:02 | cli-template |
|
|
179
|
+
| 03:06 | tg-cli |
|
|
180
|
+
| 03:13 | zoom-cli |
|
|
181
|
+
| 03:20 | cli-docs |
|
|
182
|
+
|
|
162
183
|
- **Exceptions:** cli-core also builds and probes its native packages in their own workflows, after the window:
|
|
163
184
|
`onnx.yml` at 03:27 and `sqlite.yml` at 03:37 UTC. A scheduled run of either never publishes; only a manual run
|
|
164
185
|
with `publish` does. community has no build, so it runs only the release security checks, at 03:47 UTC. A monthly
|
|
165
186
|
run never publishes a package or deploys the site.
|
|
166
|
-
- **Applies to:** the
|
|
187
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
167
188
|
|
|
168
189
|
### WC-CI-10 · Run heavy security checks only at release, monthly or on request
|
|
169
190
|
|
|
@@ -177,7 +198,7 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
177
198
|
job ([WC-CI-5](#wc-ci-5--run-the-full-linux-validation-on-every-pull-request-and-keep-platform-suites-out)); it
|
|
178
199
|
takes seconds. The dependency audit, `zizmor` and CodeQL stay out. Do not add a heavy scanner to an editor hook, a
|
|
179
200
|
stop hook or an ordinary pull request job.
|
|
180
|
-
- **Applies to:** the
|
|
201
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** sc:9.
|
|
181
202
|
|
|
182
203
|
### WC-CI-11 · Moved to [WC-SECR-20](security.md#wc-secr-20--pin-each-third-party-action-to-a-commit-and-give-each-workflow-read-only-defaults)
|
|
183
204
|
|
|
@@ -195,7 +216,7 @@ what CI runs, an audit reads the `uses: WireCatLabs/cli-base/.github/workflows/
|
|
|
195
216
|
release changes `main` the same way
|
|
196
217
|
([WC-REL-2](releases.md#wc-rel-2--main-changes-only-through-a-pull-request-also-during-a-release)).
|
|
197
218
|
- **Exceptions:** none.
|
|
198
|
-
- **Applies to:** the
|
|
219
|
+
- **Applies to:** the WireCat repositories. **Since:** cli-base 0.41.0. **Rulings:** NEED-381.
|
|
199
220
|
|
|
200
221
|
### WC-CI-13 · Start no live messenger or account test from a workflow
|
|
201
222
|
|
|
@@ -77,8 +77,9 @@ catalogue and parity manifest stay in cli-core
|
|
|
77
77
|
- **Example:** ranking is `stats messages top`, not a ranking mode on `search messages`. Options choose the scope,
|
|
78
78
|
measure, order or format of the same action. No command both shows and changes something.
|
|
79
79
|
- **Exceptions:** a documented preparatory fetch such as `--sync-first`. It keeps its own permissions and reports
|
|
80
|
-
its outcome.
|
|
81
|
-
|
|
80
|
+
its outcome. `mcp --revoke` forgets every login given to a browser app; it stays an option on `mcp` because it
|
|
81
|
+
belongs with the HTTP server's login state.
|
|
82
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-1864.
|
|
82
83
|
|
|
83
84
|
### WC-CMD-7 · Never let two commands overlap
|
|
84
85
|
|
|
@@ -18,11 +18,11 @@ nothing from either package.
|
|
|
18
18
|
| Package and path | Exports |
|
|
19
19
|
|---|---|
|
|
20
20
|
| `@wirecat/cli-base` | `catchCrashes`, `CliError`, `configFilePath`, `mcpErrorResult`, `resolvePaths` |
|
|
21
|
-
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `describeProgram` |
|
|
21
|
+
| `@wirecat/cli-base/commands` | `annotate`, `CommandInfo`, `COMMANDS_PAGE_LABELS` (in `scripts/`), `commandsPage` (in `scripts/`), `describeOptions` (in `scripts/`), `describeProgram` |
|
|
22
22
|
| `@wirecat/cli-base/codegen` | `writeArtifacts` (in `scripts/`) |
|
|
23
23
|
| `@wirecat/cli-base/release` | `DocsRules`, `JOURNAL_IDS`, `markdownFiles`, `packContents` (in `scripts/`) |
|
|
24
|
-
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `itemOf`, `LOCAL_READ`, `outputFor`, `privateFiles`, `profileFree`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `runsCommand`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
25
|
-
| `@wirecat/cli-core/mcp` | `McpCommand`, `registerCommands`, `runCommandForAgent`, `toolsFromCommands` |
|
|
24
|
+
| `@wirecat/cli-core/cli` | `AppIdentity`, `changesAuthorityAt`, `commandHelpMarkdown` (in `scripts/`), `commandsCommand`, `configCommand`, `Configuration`, `createProgram`, `environmentOf`, `GlobalFlags`, `httpTokenFile`, `itemOf`, `LOCAL_READ`, `MCP_HTTP_DEFAULT_PORT`, `mcpServerCommand`, `outputFor`, `privateFiles`, `profileFree`, `ProgramDefinition`, `ProgramOptions`, `readOnlyLockOf`, `refuseUnderReadOnly`, `REMOTE_WRITE`, `renderAnswer`, `rootOf`, `run`, `RunOptions`, `runsCommand`, `settingsFor`, `skillCommand`, `upgradeCommand` |
|
|
25
|
+
| `@wirecat/cli-core/mcp` | `httpLockFile`, `MCP_PATH`, `McpCommand`, `portOf`, `publicUrlOf`, `registerCommands`, `runCommandForAgent`, `serveOverHttp`, `toolsFromCommands` |
|
|
26
26
|
| `@wirecat/cli-core/sends` | `Level` |
|
|
27
27
|
| `@wirecat/cli-core/update` | `appUpdate` |
|
|
28
28
|
| `@wirecat/cli-core/testing` | `ARGV_LOG`, `argvLogSetup` |
|
package/docs/standards/layout.md
CHANGED
|
@@ -122,6 +122,38 @@ Rule ids on this page are `WC-LAY-<n>`. How to read a rule, how ids work and whi
|
|
|
122
122
|
- **Exceptions:** none.
|
|
123
123
|
- **Applies to:** all CLIs and libraries. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
124
124
|
|
|
125
|
+
### WC-LAY-10 · Ship the bin as a bundle that `cli-dev bundle` builds
|
|
126
|
+
|
|
127
|
+
A command that imports about a thousand module files spends most of its start resolving and reading them. tg's
|
|
128
|
+
bundle halved `--help` and every other short command
|
|
129
|
+
([measurements](https://github.com/WireCatLabs/cli-testing/blob/main/performance/startup/tg-bundle/README.md)).
|
|
130
|
+
|
|
131
|
+
- **Why:** one shared build keeps what tg learned in one place: lazy imports stay lazy, files and packages are found
|
|
132
|
+
where they were installed, and a running server survives an in-place upgrade.
|
|
133
|
+
- **Enforced by:** `<app>: pnpm build` · `<app>: pnpm smoke:node` · `<app>: pnpm smoke:bun`.
|
|
134
|
+
- **Example:** `"build": "tsc --build --force && cli-dev bundle --bin dist/bin/tg.js --external '@mtcute/*' --external undici"`,
|
|
135
|
+
with `esbuild` as a devDependency in cli-base's peer range. `dist/bundle/` holds the bundle, split so every lazy
|
|
136
|
+
`import()` stays a chunk of its own; `dist/bin/tg.js` stays the path `"bin"` names and becomes a loader. What it
|
|
137
|
+
does and checks:
|
|
138
|
+
- The loader answers `--version` from the version built into it, turns the compile cache on, and imports the
|
|
139
|
+
bundle. Under `serve`, `watch` and `mcp` it loads every chunk 2 s after start and resolves imports between chunks
|
|
140
|
+
without looking on disk: an upgrade deletes the old chunks, whose names carry a hash.
|
|
141
|
+
- In the CLI's own modules and in cli-core's and cli-base's (`--anchor` adds a package), `import.meta.url` is the
|
|
142
|
+
module's place before bundling, so skills, fonts, scenarios, migrations and child scripts are found. A package
|
|
143
|
+
they load at run time resolves from that place too (a resolve hook under Node, `Bun.resolveSync` under Bun):
|
|
144
|
+
pnpm installs cli-core's dependencies beside cli-core.
|
|
145
|
+
- A dynamic import of CommonJS (`yaml`) keeps its named exports. Names are kept (`keepNames`), so `commands --json`
|
|
146
|
+
and errors read as before.
|
|
147
|
+
- Native modules, Bun's SQLite and the packages cli-core loads by name stay outside; `--external` adds the CLI's
|
|
148
|
+
own (`*` matches anything).
|
|
149
|
+
- The build fails when a bundled module would look for a file or package from the chunk's place, or when the loader
|
|
150
|
+
names a chunk that was not built. A third-party file read and found harmless is named with `--harmless`.
|
|
151
|
+
- The CLI's smoke test runs the built bin under Node and Bun and checks that the store opens and the document
|
|
152
|
+
readers resolve.
|
|
153
|
+
- **Exceptions:** a CLI moves when its outputs are byte-identical before and after and its start gains 15% or more;
|
|
154
|
+
until then it ships `tsc`'s output. cli-base, cli-core and cli-testing ship libraries, not a bin.
|
|
155
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.64.0. **Rulings:** none.
|
|
156
|
+
|
|
125
157
|
## Sources
|
|
126
158
|
|
|
127
159
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -12,7 +12,7 @@ built on are on the [Kit surface](kit-surface.md) page.
|
|
|
12
12
|
## Start with the template
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npx @wirecat/cli-template new
|
|
15
|
+
npx @wirecat/cli-template new gcal-cli --command gcal --package @wirecat/gcal-cli --owner WireCatLabs \
|
|
16
16
|
--category calendar --content-lang en,ru --sign-in oauth --api official-client --filters time,person,text \
|
|
17
17
|
--never-store tokens,secret-links --untrusted names,links
|
|
18
18
|
```
|
|
@@ -24,7 +24,7 @@ the choices. Nothing is guessed.
|
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| `<name>-cli` | the repository folder | any name |
|
|
26
26
|
| `--command` | the command word people type | one word |
|
|
27
|
-
| `--package` | the npm package name | a scoped name, such as `@wirecat/
|
|
27
|
+
| `--package` | the npm package name | a scoped name, such as `@wirecat/gcal-cli` |
|
|
28
28
|
| `--owner` | the GitHub owner of the repository | an owner |
|
|
29
29
|
| `--category` | what the CLI reads | `messenger`, `mail`, `notes`, `files`, `calendar`, `meetings` |
|
|
30
30
|
| `--content-lang` | the languages of the content | a list, such as `en,ru` |
|
|
@@ -42,11 +42,11 @@ a reference CLI for your category in `AGENTS.md`. It does not install packages,
|
|
|
42
42
|
publish. Then:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
cd
|
|
45
|
+
cd gcal-cli && pnpm install && pnpm ci:quick
|
|
46
46
|
git add -A && git commit -m "chore: start from @wirecat/cli-template"
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explicitly, such as `
|
|
49
|
+
Give the agent `docs/dev/DESIGN.md` next. Every example passes a profile explicitly, such as `gcal personal doctor`.
|
|
50
50
|
The reference CLI is the one to compare with when a step is unclear.
|
|
51
51
|
|
|
52
52
|
The copy already holds every file this handbook asks for: the required root commands (`commands`, `doctor`,
|
|
@@ -61,6 +61,15 @@ The rules they keep are on the area pages: [Commands](commands.md), [MCP](mcp.md
|
|
|
61
61
|
Later template changes come in with `npx @wirecat/cli-template upgrade`, file by file: apply, replace, skip or diff.
|
|
62
62
|
See [Kit surface](kit-surface.md#how-your-cli-stays-current).
|
|
63
63
|
|
|
64
|
+
The template's `template.json` splits the copy in two. A **seed** (`DESIGN.md`, `CLAUDE.md`, `README.md`, the user
|
|
65
|
+
pages, `src/`) is the CLI's own from the first commit: rewrite it freely. A **template-owned** file (`AGENTS.md`,
|
|
66
|
+
`docs/dev/TESTING.md`, `docs/dev/reference-clis.md`, the skills under `docs/dev/skills/`, `.github/`) holds only what
|
|
67
|
+
is true for every CLI made from the template. Do not rewrite one to fit this CLI: put the CLI's own facts in a seed.
|
|
68
|
+
A template-owned page that is wrong for this CLI is a gap in cli-template, under
|
|
69
|
+
[WC-DOC-3](docs.md#wc-doc-3--every-document-states-current-facts-rewrite-a-wrong-sentence-in-place) like any other
|
|
70
|
+
wrong sentence: fix it there, and `upgrade` brings the fix. Change it in the CLI only where the CLI must differ for
|
|
71
|
+
good; `upgrade` then asks before it touches the change.
|
|
72
|
+
|
|
64
73
|
## Four steps that need a person
|
|
65
74
|
|
|
66
75
|
`new` prints these at the end.
|
|
@@ -69,12 +78,14 @@ See [Kit surface](kit-surface.md#how-your-cli-stays-current).
|
|
|
69
78
|
changes only through a pull request, required status checks, and no bypass. Add the ruleset after the first
|
|
70
79
|
commit is on `main`, since a pull request rule leaves an empty repository no way to get one, and the required
|
|
71
80
|
check names exist only once CI has run. See [Releases](releases.md).
|
|
72
|
-
2. **Set npm trusted publishing** for the new package, naming `release.yml`, and add any secrets the CI needs.
|
|
81
|
+
2. **Set npm trusted publishing** for the new package, naming `release.yml`, and add any secrets the CI needs. npm
|
|
82
|
+
offers this only once the package exists, so the owner publishes the first version with `bin/release --local`
|
|
83
|
+
and sets it right after. See
|
|
73
84
|
[WC-REL-11](releases.md#wc-rel-11--publish-from-github-actions-with-npm-trusted-publishing).
|
|
74
|
-
3. **Pick a monthly cron minute** between 02:17 and 03:20 UTC that
|
|
75
|
-
`.github/workflows/monthly.yml` keeps the template's own minute, 03:02, and no check compares minutes across
|
|
76
|
-
repositories, so change it by hand. See
|
|
85
|
+
3. **Pick a monthly cron minute** between 02:17 and 03:20 UTC that is not in the list of taken minutes under
|
|
77
86
|
[WC-CI-9](ci.md#wc-ci-9--run-full-validation-and-release-security-on-the-first-day-of-each-month-without-publishing).
|
|
87
|
+
The copy of `.github/workflows/monthly.yml` keeps the template's own minute, 03:02, and no check compares minutes
|
|
88
|
+
across repositories, so change it by hand, and add the new minute to the list in the step 4 pull request.
|
|
78
89
|
4. **WireCat CLIs only: join the shared lists.** A pull request to cli-base's `PARITY_GROUPS` (at least the `all`
|
|
79
90
|
group, which compares the shared root commands), a line in cli-core's `WIRECAT_TOOLS` and a line in cli-docs'
|
|
80
91
|
`tools.json`. A messenger also joins cli-core's `parity.json`. A CLI outside WireCatLabs keeps its parity groups
|
|
@@ -89,17 +100,20 @@ All of these pass on `main`, before the first release:
|
|
|
89
100
|
- `cli-dev lint` against the built CLI. It also makes the CLI fail once in an empty home and checks that
|
|
90
101
|
`runs list` shows the failed run
|
|
91
102
|
- `cli-dev audit --for <cli> --lint <cli>=lint` with no new finding, run with the command trees of the CLIs in
|
|
92
|
-
the same parity groups. `--lint` judges the CLI's lint rules by its own `lint/` folder.
|
|
93
|
-
other CLIs
|
|
94
|
-
|
|
103
|
+
the same parity groups (`pnpm audit:commands`). `--lint` judges the CLI's lint rules by its own `lint/` folder.
|
|
104
|
+
What it shares with the other CLIs has no baseline: `audit/baseline` does not accept a shared-contract finding
|
|
105
|
+
(cli-base 0.51.0). Fix each one, or, where the CLI must differ for good, the pull request that adds it to a parity
|
|
106
|
+
group declares the exact difference with its reason in that group's `differences`
|
|
95
107
|
- `pnpm contracts:check`
|
|
96
108
|
- `cli-dev test-matrix --check`
|
|
97
109
|
- `pnpm docs:check`, with `--pages`, and the CLI's `generate:check`
|
|
98
110
|
- the changelog guard on a pull request
|
|
99
111
|
- `pnpm release:check`
|
|
100
112
|
|
|
101
|
-
The last test is a fresh agent. Give it only this handbook's [index](README.md)
|
|
102
|
-
|
|
113
|
+
The last test is a fresh agent. Give it only this handbook's [index](README.md), the new repository and the cli-base
|
|
114
|
+
workflow files its own workflows pin
|
|
115
|
+
([WC-CI-8](ci.md#wc-ci-8--call-cli-base-node-ciyml-from-ciyml-pinned-to-a-tag-or-commit)), and ask it to audit the
|
|
116
|
+
CLI. Every gap it lists is either a gap in the CLI, to fix before the first release, or a hole in the
|
|
103
117
|
handbook, to fix here.
|
|
104
118
|
|
|
105
119
|
## The template repository itself
|
|
@@ -118,7 +132,7 @@ upgrade, and no script or agent reads its answers.
|
|
|
118
132
|
| [WC-DOC-12](docs.md#wc-doc-12--a-runnable-command-in-a-document-has-no-placeholder-in-it) | Exempt only for the placeholder names `clidev`, `clidev-cli`, `@wirecat/clidev-cli` and `WireCatLabs/clidev-cli`, and the upper-case prefix `CLIDEV_` (as in `CLIDEV_PROFILE`). | `new` replaces each of them with the CLI's own name. Any other placeholder is still a gap. |
|
|
119
133
|
| [WC-DOC-17](docs.md#wc-doc-17--each-entry-says-what-changed-for-the-caller-why-and-what-to-watch-for) | A changelog entry may name a file path. | The template's readers are CLI authors, who edit those files or take them with `upgrade`; for them a path is what they see. |
|
|
120
134
|
|
|
121
|
-
Every other rule that applies to all CLIs, or to the
|
|
135
|
+
Every other rule that applies to all CLIs, or to the WireCat repositories, applies to cli-template as it is written,
|
|
122
136
|
with one limit for the copy tool. It follows the rules for code and the repository: Toolchain, Layout, Style,
|
|
123
137
|
Testing, Security and Documents. It has no profile, no machine mode, no MCP server, no store and no agent reading its
|
|
124
138
|
answers, so the rules for a CLI's commands and runtime do not reach it: Commands, Output, MCP, Accounts, Permissions,
|
|
@@ -20,8 +20,9 @@ Rule ids on this page are `WC-PERM-<n>`. How to read a rule, how ids work and wh
|
|
|
20
20
|
tg, max and wa use cli-core's permissions and write guard in full. memo uses them for the shared commands only. drive
|
|
21
21
|
has its own policy and guard, with its own `--read-only` flag. zoom has per-profile `permissions` from zoom-cli 0.35.0,
|
|
22
22
|
read with cli-core's `levelFor`; its writes change Zoom meetings, its built-in default is `remote-meetings: ask`, and
|
|
23
|
-
it has no send journal or recipient list, since it sends no messages.
|
|
24
|
-
|
|
23
|
+
it has no send journal or recipient list, since it sends no messages. gcal declares its own keys through cli-core's
|
|
24
|
+
`commandPermissions` and runs its event writes through cli-core's write guard (WC-PERM-22). Where this page says
|
|
25
|
+
"every CLI that writes", drive is a known gap.
|
|
25
26
|
|
|
26
27
|
WC-PERM-10 and WC-PERM-11 need cli-core 0.321.0, which tg, max and wa pin. WC-PERM-12 as written needs cli-core
|
|
27
28
|
0.337.0; before it, the lock let through some local writes that overwrite what the owner saved. A command other
|
|
@@ -114,7 +115,8 @@ A default is never made stricter without the owner's word.
|
|
|
114
115
|
- **Exceptions:** where the messenger has replies, `replies.send` is `deny`: a reply rule writes to people with nobody
|
|
115
116
|
typing a command, so it waits for the owner's own `allow`. A messenger with no `replies` group, such as wa, has no
|
|
116
117
|
use for the key.
|
|
117
|
-
- **Applies to:** messenger CLIs.
|
|
118
|
+
- **Applies to:** messenger CLIs. A CLI that is not a messenger sets its own defaults (WC-PERM-22). **Since:** cli-base
|
|
119
|
+
0.41.0. **Rulings:** NEED-568.
|
|
118
120
|
|
|
119
121
|
### WC-PERM-7 · A built-in default only ever tightens
|
|
120
122
|
|
|
@@ -310,6 +312,34 @@ Moderation rules use the same four levels for each kind of action (delete, remov
|
|
|
310
312
|
looser than the profile's `chats.moderate`; the code does not check that yet.
|
|
311
313
|
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-306, NEED-310.
|
|
312
314
|
|
|
315
|
+
### WC-PERM-22 · A CLI that is not a messenger declares its own keys and defaults with `commandPermissions`
|
|
316
|
+
|
|
317
|
+
A messenger's keys come from its resources (`messages`, `chats`). Any other CLI names its own with cli-core's
|
|
318
|
+
`commandPermissions({ keyOf, extra, defaults })`, passed as `settingsFor(app, { permissions })` (cli-core 0.450.0):
|
|
319
|
+
|
|
320
|
+
- **Keys.** Each command is checked against its own path (`events.create`), the default `keyOf`. Housekeeping has no
|
|
321
|
+
key (WC-PERM-5). `extra` adds the keys a command checks inside itself, such as one per generated API method
|
|
322
|
+
(`api.calendar.events.insert`). A key that names no command is refused when it is set or saved.
|
|
323
|
+
- **Defaults.** `defaults` holds the CLI's built-in levels, beside cli-core's. They only ever tighten (WC-PERM-7). A
|
|
324
|
+
CLI picks them for what its writes do, not by WC-PERM-6's messenger list: they ask before a write people other
|
|
325
|
+
than the owner see or cannot easily undo, and deny what an agent must never do unasked.
|
|
326
|
+
- **Read-only lock and guard.** `<PREFIX>_READ_ONLY` refuses every write to the service, as in WC-PERM-12. Every
|
|
327
|
+
write runs through cli-core's write guard with `permissionDefaults: configuration.permissions.defaults`
|
|
328
|
+
(cli-core 0.455.0), so `ask` is a y/N question at a terminal (WC-PERM-9) and every attempt goes into the send journal
|
|
329
|
+
(WC-PERM-17). A CLI that sends no messages has no recipient list or hourly limit to set.
|
|
330
|
+
|
|
331
|
+
- **Why:** one permission model for every CLI; a CLI-own layer drifts from the shared one and is audited by nobody.
|
|
332
|
+
- **Enforced by:** `cli-core: src/cli/command-permissions.test.ts` · `cli-core: src/calendar/cli/writes.test.ts`.
|
|
333
|
+
- **Example:** a calendar CLI takes cli-core's `CALENDAR_WRITE_DEFAULTS` and `CALENDAR_WRITE_KEYS`
|
|
334
|
+
(`@wirecat/cli-core/calendar`, cli-core 0.458.0): `events create`, `update`, `respond` and `delete` ask, since each
|
|
335
|
+
changes a calendar other people may share or be invited from; `events.delete.series` (a whole repeating series) and
|
|
336
|
+
`events.notify` (mail the attendees) are `deny` until the owner allows them. gcal adds its own: a generated
|
|
337
|
+
`api <method>` that writes asks, and one that shares, empties or gives away a calendar is `deny`.
|
|
338
|
+
- **Exceptions:** known gaps: drive keeps its own policy and `--read-only` flag, zoom reads its own
|
|
339
|
+
`remote-meetings` key with `levelFor`, and memo uses cli-core's permissions for the shared commands only. None of
|
|
340
|
+
them declares `commandPermissions` yet.
|
|
341
|
+
- **Applies to:** every CLI that writes and is not a messenger. **Since:** cli-base 0.64.0. **Rulings:** none.
|
|
342
|
+
|
|
313
343
|
## Sources
|
|
314
344
|
|
|
315
345
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|