@wirecat/cli-base 0.40.3 → 0.41.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/README.md +1 -0
- package/dist/commands/document.d.ts.map +1 -1
- package/dist/commands/document.js +1 -0
- package/dist/commands/document.js.map +1 -1
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +6 -4
- package/dist/commands/index.js.map +1 -1
- package/dist/standards/vocabulary.d.ts.map +1 -1
- package/dist/standards/vocabulary.js +1 -0
- package/dist/standards/vocabulary.js.map +1 -1
- package/docs/standards/README.md +111 -0
- package/docs/standards/accounts.md +148 -0
- package/docs/standards/architecture.md +214 -0
- package/docs/standards/ci.md +159 -0
- package/docs/standards/commands.md +302 -0
- package/docs/standards/docs.md +214 -0
- package/docs/standards/layout.md +128 -0
- package/docs/standards/lifecycle.md +143 -0
- package/docs/standards/mcp.md +207 -0
- package/docs/standards/new-cli.md +111 -0
- package/docs/standards/output.md +256 -0
- package/docs/standards/permissions.md +295 -0
- package/docs/standards/releases.md +191 -0
- package/docs/standards/security.md +260 -0
- package/docs/standards/store.md +177 -0
- package/docs/standards/style.md +119 -0
- package/docs/standards/testing.md +222 -0
- package/docs/standards/toolchain.md +232 -0
- package/package.json +4 -3
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Output
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
What a command prints and how it fails: data on stdout and nothing else in machine mode, the envelopes, errors and
|
|
6
|
+
exit codes, partial answers, field projection and retries.
|
|
7
|
+
|
|
8
|
+
Rule ids on this page are `WC-OUT-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
9
|
+
[index](README.md). What a command declares about itself is on the [commands](commands.md) page. How a process ends
|
|
10
|
+
on a signal is on the [lifecycle](lifecycle.md) page.
|
|
11
|
+
|
|
12
|
+
"Machine mode" means `--json` or `--jsonl`. The shapes below are Valibot schemas in cli-base
|
|
13
|
+
[`src/standards/output.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/output.ts) and
|
|
14
|
+
[`src/standards/errors.ts`](https://github.com/WireCatLabs/cli-base/blob/main/src/standards/errors.ts). They also emit
|
|
15
|
+
JSON Schema 2020-12.
|
|
16
|
+
|
|
17
|
+
## Streams
|
|
18
|
+
|
|
19
|
+
### WC-OUT-1 · Put data on stdout and nothing else in machine mode
|
|
20
|
+
|
|
21
|
+
- **Why:** agents and scripts parse stdout, and one stray line breaks every caller.
|
|
22
|
+
- **Enforced by:** review only.
|
|
23
|
+
- **Example:** `tg chats list --json | jq .items` gets one JSON value. Notes, warnings, progress and next-step hints go
|
|
24
|
+
to stderr. cli-base's renderer sends every diagnostic to stderr in every mode, and in machine mode it drops the
|
|
25
|
+
`✓` and `·` marks.
|
|
26
|
+
- **Exceptions:** an explicit `--help` or `--version` prints text on stdout and exits 0.
|
|
27
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-88.
|
|
28
|
+
|
|
29
|
+
### WC-OUT-2 · Build the human view from the same facts as the machine answer
|
|
30
|
+
|
|
31
|
+
- **Why:** machine output stays one stable value, and the human view can change freely.
|
|
32
|
+
- **Enforced by:** review only.
|
|
33
|
+
- **Example:** the command builds one answer object. `--json` prints it whole, and the pretty view draws its items.
|
|
34
|
+
- **Exceptions:** none.
|
|
35
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
36
|
+
|
|
37
|
+
### WC-OUT-3 · Fail loudly for what the person asked for, and quietly, with a reason on stderr, for the rest
|
|
38
|
+
|
|
39
|
+
- **Why:** a side task that breaks must not hide the answer, and a main task that breaks must not look like success.
|
|
40
|
+
- **Enforced by:** review only.
|
|
41
|
+
- **Example:** a send that fails exits non-zero. A side check nobody asked for that fails writes one line to stderr,
|
|
42
|
+
and the command keeps its own exit code. Once a write is under way, a run log that cannot be written is a warning
|
|
43
|
+
and never changes the exit code.
|
|
44
|
+
- **Exceptions:** none.
|
|
45
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-97, NEED-133.
|
|
46
|
+
|
|
47
|
+
## Envelopes
|
|
48
|
+
|
|
49
|
+
### WC-OUT-4 · Answer every JSON request with one envelope: a list or one thing
|
|
50
|
+
|
|
51
|
+
- **Why:** a caller reads `items` or `item`, `page` and `meta` in the same place in every command of every CLI.
|
|
52
|
+
- **Enforced by:** `cli-core: src/cli/envelope.test.ts` · `cli-core: src/mcp/output-schema.test.ts`.
|
|
53
|
+
- **Example:** a list is `{ items, page, next?, missing?, meta }`. One thing is `{ item, next?, missing?, meta }`.
|
|
54
|
+
cli-core builds them with `listOf` and `itemOf`, and its `renderAnswer` refuses any other shape. A list never
|
|
55
|
+
answers a bare array.
|
|
56
|
+
- **Exceptions:** a document that is not about the owner's data is printed whole, with no envelope: `commands --json`
|
|
57
|
+
and a `--dry-run` preview.
|
|
58
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-358 (its older list shape is replaced by
|
|
59
|
+
this one), NEED-431.
|
|
60
|
+
|
|
61
|
+
### WC-OUT-5 · Say in `page` how the source pages
|
|
62
|
+
|
|
63
|
+
- **Why:** a cursor source has no page numbers, and pretending it has them sends the caller to the wrong place.
|
|
64
|
+
- **Enforced by:** review only.
|
|
65
|
+
- **Example:** `{ mode: "page", limit, has_more, next_page? }`, or `{ mode: "cursor", limit, has_more,
|
|
66
|
+
next_cursor? }` for a source with its own position, such as a message history.
|
|
67
|
+
- **Exceptions:** none.
|
|
68
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
69
|
+
|
|
70
|
+
### WC-OUT-6 · End a `--jsonl` stream with one end record
|
|
71
|
+
|
|
72
|
+
- **Why:** a reader can tell a finished stream from a cut one.
|
|
73
|
+
- **Enforced by:** review only.
|
|
74
|
+
- **Example:** one `{"type":"item","data":…}` line per item, then `{"type":"end","complete":true,"page":{…}}`, with
|
|
75
|
+
`issues` when a source failed. A one-thing answer is one item line and the end line.
|
|
76
|
+
- **Exceptions:** a report that is one object may refuse `--jsonl` with the reason `output.one-object`.
|
|
77
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
78
|
+
|
|
79
|
+
### WC-OUT-7 · Put the facts about the run in `meta`
|
|
80
|
+
|
|
81
|
+
- **Why:** `run_id` finds the run later, and `complete` says whether the answer can be trusted as whole.
|
|
82
|
+
- **Enforced by:** review only.
|
|
83
|
+
- **Example:** `{ run_id, duration_ms, accounts?, complete, issues?, truncated? }`. `complete: false` with
|
|
84
|
+
`issues: [{ code, reason?, source }]` says a source failed or was skipped. `truncated` says a size cap cut the
|
|
85
|
+
answer. Neither one means another page exists: that is `page.has_more`.
|
|
86
|
+
- **Exceptions:** none.
|
|
87
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
88
|
+
|
|
89
|
+
### WC-OUT-8 · Exit 0 on a partial answer only when the command declares `partial`
|
|
90
|
+
|
|
91
|
+
- **Why:** a caller that checks only the exit code must not take half an answer for a whole one.
|
|
92
|
+
- **Enforced by:** review only.
|
|
93
|
+
- **Example:** a search across accounts declares `annotate(search, { partial: true })` and exits 0 with
|
|
94
|
+
`meta.complete: false`. Any other command prints what it read, then exits with the code of its first issue.
|
|
95
|
+
- **Exceptions:** none.
|
|
96
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
97
|
+
|
|
98
|
+
### WC-OUT-9 · Offer at most two next steps, and mark a step safe only when it changes nothing on the service
|
|
99
|
+
|
|
100
|
+
- **Why:** an agent may run a safe step unasked, so "safe" must be true.
|
|
101
|
+
- **Enforced by:** review only.
|
|
102
|
+
- **Example:** `next` holds the step to take, then one alternative. Each has `kind` (`command`, or `user_action` with
|
|
103
|
+
no command) and `safe_to_auto_execute`. A step that reads the service and writes only the local store (a sync) is
|
|
104
|
+
safe. A step that reads nothing remotely and writes locally (`store reindex`) is not: it waits to be asked. A step
|
|
105
|
+
is built from ids, counts and cursors the CLI holds, never from text a message, mail or document carried.
|
|
106
|
+
- **Exceptions:** none.
|
|
107
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
108
|
+
|
|
109
|
+
### WC-OUT-10 · Answer a write with what it did and its `operation_id`
|
|
110
|
+
|
|
111
|
+
- **Why:** the id ties the answer to the send journal, and it is how a caller repeats the write safely.
|
|
112
|
+
- **Enforced by:** review only.
|
|
113
|
+
- **Example:** `{ "item": { "operation_id": "…", … }, "meta": { … } }`, with the ids the write touched beside
|
|
114
|
+
`operation_id`. The same id is in the send journal.
|
|
115
|
+
- **Exceptions:** a bot account's send has no `--operation-id`, because no Bot API makes a repeat safe.
|
|
116
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-431.
|
|
117
|
+
|
|
118
|
+
### WC-OUT-11 · Give a shared command the same answer shape in every CLI of its group
|
|
119
|
+
|
|
120
|
+
- **Why:** an agent that reads one CLI's answer can read the other's.
|
|
121
|
+
- **Enforced by:** `same-path-contract` (weekly audit; it does not block a pull request yet) · `same-entity-schema`
|
|
122
|
+
(not wired yet: no CLI passes its schemas to the audit).
|
|
123
|
+
- **Example:** what only one messenger knows goes under `provider_metadata`, never as a top-level field one CLI has and
|
|
124
|
+
the other lacks.
|
|
125
|
+
- **Exceptions:** none.
|
|
126
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
127
|
+
|
|
128
|
+
## Keys and values
|
|
129
|
+
|
|
130
|
+
### WC-OUT-12 · Use snake_case keys, one meaning per key, and the registered identifier names
|
|
131
|
+
|
|
132
|
+
- **Why:** a key that changes name or meaning between commands breaks every parser that relied on it.
|
|
133
|
+
- **Enforced by:** `key-casing` · `identifier-name`.
|
|
134
|
+
- **Example:** `run_id`, never `execution_id` or `log_id`. `operation_id`, never `send_id`. `request_id` and `trace_id`
|
|
135
|
+
are the other registered ids.
|
|
136
|
+
- **Exceptions:** keys that are the owner's or a provider's own words stay as written, such as a note's front matter
|
|
137
|
+
or an email's headers. `key-casing` checks the `commands --json` document today; it checks entity schemas only when
|
|
138
|
+
a CLI passes `--schemas`.
|
|
139
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
140
|
+
|
|
141
|
+
### WC-OUT-13 · Write times, durations, references and empty values one way
|
|
142
|
+
|
|
143
|
+
- **Why:** a caller must not guess whether `0` means "none" or "unknown".
|
|
144
|
+
- **Enforced by:** `nullable-list`.
|
|
145
|
+
- **Example:** a time is an ISO 8601 string with a zone, never epoch milliseconds. A length of time is a number of
|
|
146
|
+
milliseconds in a key ending in `_ms`. A stored thing is named by `ref`. A key that is absent was not asked for or
|
|
147
|
+
is not known. `null` is known to have no value. `[]` is known to be empty, never `null`. `0` is measured, never a
|
|
148
|
+
stand-in for unknown.
|
|
149
|
+
- **Exceptions:** none.
|
|
150
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
151
|
+
|
|
152
|
+
## Errors and exit codes
|
|
153
|
+
|
|
154
|
+
### WC-OUT-14 · Print a failure in machine mode as one JSON object on stderr, with stdout empty
|
|
155
|
+
|
|
156
|
+
- **Why:** a caller tells success from failure without parsing text, and stdout never holds half an answer and an
|
|
157
|
+
error.
|
|
158
|
+
- **Enforced by:** `src/failure.test.ts` · `cli-core: src/cli/agent-contract.test.ts`.
|
|
159
|
+
- **Example:** `{"error":{"code":"not_found","reason":"item.not-found","message":"…","retry":{"safe":false}}}`. The
|
|
160
|
+
keys are `code`, `reason`, `message`, `param?`, `details?`, `next?`, `retry` and `run_id?`. MCP answers the same
|
|
161
|
+
object with `isError`. cli-base's `errorObjectOf` builds it.
|
|
162
|
+
- **Exceptions:** with `--debug`, a second JSON line follows it on stderr: the failure's whole record. Under
|
|
163
|
+
`--jsonl`, lines already printed before a failure stay on stdout.
|
|
164
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
165
|
+
|
|
166
|
+
### WC-OUT-15 · Branch on `code` and `reason`, never on `message`
|
|
167
|
+
|
|
168
|
+
- **Why:** the message is for a person and may change. The code and reason are the contract.
|
|
169
|
+
- **Enforced by:** `same-error-reason` (not wired yet: no CLI passes its reasons to the audit).
|
|
170
|
+
- **Example:** `reason` is a registered dotted id, such as `query.too-large`, defined with `defineReasons` where it is
|
|
171
|
+
raised. `details` keeps only registered keys, and never a `cause`.
|
|
172
|
+
- **Exceptions:** none.
|
|
173
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
174
|
+
|
|
175
|
+
### WC-OUT-16 · Report a failure that is not a `CliError` as `internal_error`, exit 1, with a fixed message
|
|
176
|
+
|
|
177
|
+
- **Why:** a foreign error's own text can carry anything, including data that must not reach the screen.
|
|
178
|
+
- **Enforced by:** `src/failure.test.ts` · `cli-core: src/mcp/result-contract.test.ts`.
|
|
179
|
+
- **Example:** `{"error":{"code":"internal_error","reason":"command.crashed",…}}`. No command can raise this code
|
|
180
|
+
itself.
|
|
181
|
+
- **Exceptions:** none.
|
|
182
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
183
|
+
|
|
184
|
+
### WC-OUT-17 · Show a person one failure line and at most one next step
|
|
185
|
+
|
|
186
|
+
- **Why:** a person reads the first line, and a stack trace tells them nothing they can act on.
|
|
187
|
+
- **Enforced by:** review only.
|
|
188
|
+
- **Example:** `✗ nothing found by that name` and then `→ list the chats: tg work chats list`. `--debug` adds the
|
|
189
|
+
whole record, cause and stack included.
|
|
190
|
+
- **Exceptions:** none.
|
|
191
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
192
|
+
|
|
193
|
+
### WC-OUT-18 · Exit with cli-base's code for each kind of failure
|
|
194
|
+
|
|
195
|
+
- **Why:** a script branches on `$?` without parsing text, and the numbers are the same in every CLI.
|
|
196
|
+
- **Enforced by:** `src/exit-codes.test.ts`.
|
|
197
|
+
- **Example:** 0 ok · 1 internal error · 2 validation error · 3 configuration error · 4 authentication error ·
|
|
198
|
+
5 permission error · 6 not found · 7 confirmation required · 8 rate limited · 9 timeout · 10 network error ·
|
|
199
|
+
11 provider error · 12 provider unavailable · 13 invalid response · 14 outcome unknown · 130 cancelled.
|
|
200
|
+
- **Exceptions:** on cli-core's `run()`, a one-shot command stopped by SIGTERM exits 143. A `persistent` command
|
|
201
|
+
stopped by Ctrl-C or SIGTERM exits 0. A closed pipe exits 0. See the [lifecycle](lifecycle.md) page.
|
|
202
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
203
|
+
|
|
204
|
+
### WC-OUT-19 · Report a wrong command line as `validation_error`, exit 2
|
|
205
|
+
|
|
206
|
+
- **Why:** an unknown option and a missing argument are failures like any other, in the same shape.
|
|
207
|
+
- **Enforced by:** review only.
|
|
208
|
+
- **Example:** an unknown command or option, a missing argument or a missing required option prints the error object
|
|
209
|
+
and exits 2. So does a group named without a leaf.
|
|
210
|
+
- **Exceptions:** an explicit `--help` or `--version` exits 0.
|
|
211
|
+
- **Applies to:** CLIs that run on cli-core's `run()`. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
212
|
+
|
|
213
|
+
### WC-OUT-20 · Mark a failure safe to retry only when repeating it cannot do harm
|
|
214
|
+
|
|
215
|
+
- **Why:** an agent repeats a failure marked safe, so a write that may have landed must never be marked safe.
|
|
216
|
+
- **Enforced by:** `src/failure.test.ts`.
|
|
217
|
+
- **Example:** every error has `retry: { safe, after_ms? }`. A transient failure (`timeout`, `rate_limited`,
|
|
218
|
+
`network_error`, `provider_unavailable`) is safe for a command that writes nothing on the service, or for a write
|
|
219
|
+
that never left (`sent: false`). A sync that only writes the local store is safe to repeat. `outcome_unknown` is
|
|
220
|
+
never safe: repeat that write only with its `--operation-id`.
|
|
221
|
+
- **Exceptions:** cli-base's `retryOf`, used on its own, also counts a local write as a write, so it is stricter
|
|
222
|
+
than cli-core's runner.
|
|
223
|
+
- **Applies to:** all CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
224
|
+
|
|
225
|
+
## Size and projection
|
|
226
|
+
|
|
227
|
+
### WC-OUT-21 · Keep only the asked fields with `--json=a,b`, and keep the envelope
|
|
228
|
+
|
|
229
|
+
- **Why:** an agent pays for every byte it reads, but it still needs to know the answer's limits.
|
|
230
|
+
- **Enforced by:** review only.
|
|
231
|
+
- **Example:** `tg chats list --json=items.id,items.title`. `source.title` picks a nested field. The keys `page`,
|
|
232
|
+
`next`, `missing`, `meta`, `coverage` and `operation_id` stay. A field that does not exist is left out. A bare
|
|
233
|
+
`--json` at a terminal lists the fields of a command that declares its answer, instead of printing them all.
|
|
234
|
+
- **Exceptions:** numeric indexes and wildcards are not supported.
|
|
235
|
+
- **Applies to:** CLIs that run on cli-core's `run()`; the projector itself (`projectFields`) is cli-base's.
|
|
236
|
+
**Since:** cli-base 0.41.0. **Rulings:** NEED-761.
|
|
237
|
+
|
|
238
|
+
### WC-OUT-22 · Fail visibly when machine output passes its size cap
|
|
239
|
+
|
|
240
|
+
- **Why:** a silently cut JSON answer looks whole.
|
|
241
|
+
- **Enforced by:** `cli-core: src/cli/execution.test.ts` · `src/failure.test.ts`.
|
|
242
|
+
- **Example:** machine output stops at 4 MiB by default. `--max-output-bytes <bytes>` changes it, and 0 turns the cap
|
|
243
|
+
off. Past the cap the command fails with `invalid_response`, reason `output.too-large`, `retry.safe: false`, and
|
|
244
|
+
`partial_output` says whether some output was already printed.
|
|
245
|
+
- **Exceptions:** file exports keep their own streaming limits.
|
|
246
|
+
- **Applies to:** CLIs that run on cli-core's `run()`; zoom uses cli-base's `createOutputBuffer` for the same cap.
|
|
247
|
+
**Since:** cli-base 0.41.0. **Rulings:** none.
|
|
248
|
+
|
|
249
|
+
## Sources
|
|
250
|
+
|
|
251
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
252
|
+
holds.
|
|
253
|
+
|
|
254
|
+
- [cli-core `STANDARD.md`](https://github.com/WireCatLabs/cli-core/blob/main/docs/dev/STANDARD.md): agent execution
|
|
255
|
+
contract, output
|
|
256
|
+
- [cli-base API reference](https://github.com/WireCatLabs/cli-base/blob/main/docs/REFERENCE.md)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Permissions
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
What a CLI may do without asking: permission levels and keys, their defaults, the limits no level lifts, the write
|
|
6
|
+
guard and its journal.
|
|
7
|
+
|
|
8
|
+
Two words used on this page:
|
|
9
|
+
|
|
10
|
+
- A **write** is anything that changes something outside the local store: a message, a reaction, a group change, an
|
|
11
|
+
account change.
|
|
12
|
+
- The **write guard** is the one check every write passes before it leaves the computer. cli-core builds it
|
|
13
|
+
(`sendGuard`), from the profile's settings.
|
|
14
|
+
|
|
15
|
+
Rule ids on this page are `WC-PERM-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
16
|
+
[index](README.md).
|
|
17
|
+
|
|
18
|
+
### Who follows these rules today
|
|
19
|
+
|
|
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
|
+
has its own policy and guard, with its own `--read-only` flag; zoom only reads. Where this page says "every CLI that
|
|
22
|
+
writes", drive is a known gap.
|
|
23
|
+
|
|
24
|
+
WC-PERM-10 and WC-PERM-11 need cli-core 0.321.0, which tg, max and wa pin.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
### WC-PERM-1 · `permissions` is the only access setting
|
|
29
|
+
|
|
30
|
+
A profile's `permissions` is a JSON object. Each key is a command path, and each value is a level. It holds the same
|
|
31
|
+
way for a command the owner types and for an agent over MCP. A config file that still has the old `read_only` or
|
|
32
|
+
`allow` settings is refused as an unknown setting.
|
|
33
|
+
|
|
34
|
+
- **Why:** one setting means one place to look when the owner asks "what may this profile do".
|
|
35
|
+
- **Enforced by:** review only.
|
|
36
|
+
- **Example:** `{ "permissions": { "messages": "allow", "messages.delete": "ask", "contacts": "readonly" } }`.
|
|
37
|
+
- **Exceptions:** the recipient list (WC-PERM-14), the hourly limit (WC-PERM-15) and the file guard
|
|
38
|
+
([WC-SECR-9](security.md)) are separate settings with separate jobs.
|
|
39
|
+
- **Applies to:** every CLI that writes. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
40
|
+
|
|
41
|
+
### WC-PERM-2 · There are four levels: `deny`, `readonly`, `ask`, `allow`
|
|
42
|
+
|
|
43
|
+
- `deny` — nothing, not even a read. The command answers `permission_error` (exit 5) before it connects, and an agent
|
|
44
|
+
is not offered the command.
|
|
45
|
+
- `readonly` — reads work, writes answer `permission_error`. On a key that only writes, such as `messages.delete`, it
|
|
46
|
+
is the same as `deny`.
|
|
47
|
+
- `ask` — a write asks first. See WC-PERM-9 for a terminal and [WC-MCP-7](mcp.md) for an agent.
|
|
48
|
+
- `allow` — goes ahead and never asks.
|
|
49
|
+
- **Why:** four words cover every choice the owner makes, and each one reads the same for every command.
|
|
50
|
+
- **Enforced by:** review only.
|
|
51
|
+
- **Example:** `tg work config set permissions.messages readonly`.
|
|
52
|
+
- **Exceptions:** none.
|
|
53
|
+
- **Applies to:** every CLI that writes. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
54
|
+
|
|
55
|
+
### WC-PERM-3 · A key is a command path, and the most specific key wins
|
|
56
|
+
|
|
57
|
+
Keys are dotted command paths, such as `messages`, `messages.delete` or `chats.members.remove`. There is no wildcard.
|
|
58
|
+
A key starts with a resource, so a misspelled key is refused, not ignored. Every command maps to exactly one key.
|
|
59
|
+
|
|
60
|
+
- **Why:** the owner names exactly what they mean, and a typo cannot silently allow more.
|
|
61
|
+
- **Enforced by:** review only.
|
|
62
|
+
- **Example:** `messages: readonly` with `messages.send: allow` lets the profile send but not edit or delete.
|
|
63
|
+
- **Exceptions:** see WC-PERM-4 and WC-PERM-5 for commands that map to another key or to none.
|
|
64
|
+
- **Applies to:** every CLI that writes. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
65
|
+
|
|
66
|
+
### WC-PERM-4 · A command that shows messages is checked as `messages`
|
|
67
|
+
|
|
68
|
+
A command outside `messages` that prints what people wrote, or who sent what, is checked against `messages` too.
|
|
69
|
+
|
|
70
|
+
- **Why:** `deny messages` must stop every way of reading messages, not only the `messages` commands.
|
|
71
|
+
- **Enforced by:** review only.
|
|
72
|
+
- **Example:** `inbox`, `review`, `watch`, `serve`, `conversations`, `attachments list`, `chats documents`, `mentions`,
|
|
73
|
+
`contacts context`, `contacts timeline` and the `search` commands that read messages.
|
|
74
|
+
- **Exceptions:** `search notes` reads only the owner's notes.
|
|
75
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
76
|
+
|
|
77
|
+
### WC-PERM-5 · Housekeeping is never gated
|
|
78
|
+
|
|
79
|
+
Commands that look after the tool and its files, and never show a message, have no key. No level stops them.
|
|
80
|
+
|
|
81
|
+
- **Why:** a profile that denies everything must still be able to fix its own config and log in.
|
|
82
|
+
- **Enforced by:** review only.
|
|
83
|
+
- **Example:** `config`, `session`, `doctor`, `commands`, `complete`, `upgrade`, `skill`, `models`, `server`,
|
|
84
|
+
`schedule`, `runs`, `sends`, `recipients`, `flood`, `mcp`, `replies`, and `store info|check|migrate|backup|restore|
|
|
85
|
+
repair|copies`. Under `bot`: `auth`, `list`, `sends`, `recipients`, `mcp`.
|
|
86
|
+
- **Exceptions:** what a schedule or a reply rule does is checked by its own key when it runs (`replies.send`).
|
|
87
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
88
|
+
|
|
89
|
+
### WC-PERM-6 · The built-in defaults ask only before what cannot be undone
|
|
90
|
+
|
|
91
|
+
With no `permissions` set, a profile may do almost everything. The built-in defaults are:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"messages.delete": "ask", "bot.messages.delete": "ask", "account.sessions.end": "ask",
|
|
96
|
+
"topics.enable": "ask", "topics.delete": "ask", "chats.delete": "ask", "chats.clear": "ask",
|
|
97
|
+
"replies.send": "deny"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A default is never made stricter without the owner's word.
|
|
102
|
+
|
|
103
|
+
- **Why:** the tool should work without questions; only a deletion, a logout or an automatic reply needs one.
|
|
104
|
+
- **Enforced by:** `cli-core: src/sends/permissions.test.ts`.
|
|
105
|
+
- **Example:** a new profile sends at once, and asks before `messages delete`.
|
|
106
|
+
- **Exceptions:** `replies.send` is `deny`: a reply rule writes to people with nobody typing a command, so it waits for
|
|
107
|
+
the owner's own `allow`.
|
|
108
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-568.
|
|
109
|
+
|
|
110
|
+
### WC-PERM-7 · A built-in default only ever tightens
|
|
111
|
+
|
|
112
|
+
Against a broader key the owner set, the stricter of the two holds. A key the owner set at the same depth or deeper
|
|
113
|
+
wins. In the config file, a key in a nearer section also hides that key and every key under it in farther sections.
|
|
114
|
+
|
|
115
|
+
- **Why:** `messages: allow` must not remove the question before a deletion by accident.
|
|
116
|
+
- **Enforced by:** `cli-core: src/sends/permissions.test.ts`.
|
|
117
|
+
- **Example:** `messages: allow` still asks before `messages delete`; `messages.delete: allow` does not. A profile's
|
|
118
|
+
`messages: readonly` is not undone by `messages.delete: allow` in `defaults`.
|
|
119
|
+
- **Exceptions:** none.
|
|
120
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
121
|
+
|
|
122
|
+
### WC-PERM-8 · A permission path that moved is refused, not ignored
|
|
123
|
+
|
|
124
|
+
When a command path moves, a config that still names the old path stops the command with `configuration_error` and
|
|
125
|
+
says what to rename.
|
|
126
|
+
|
|
127
|
+
- **Why:** a `deny` under an old path would stop working without anyone noticing.
|
|
128
|
+
- **Enforced by:** review only.
|
|
129
|
+
- **Example:** `messages.search` is refused with "rename … to search.messages"; `messages.stats` with
|
|
130
|
+
"rename … to stats.messages.show".
|
|
131
|
+
- **Exceptions:** none.
|
|
132
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
133
|
+
|
|
134
|
+
### WC-PERM-9 · `ask` at a terminal is a y/N question, default no
|
|
135
|
+
|
|
136
|
+
The question shows what will change. A flag answers it: `--allow-dangerous` for a deletion nobody gets back, `--yes`
|
|
137
|
+
for every other write. With no terminal and no flag, the command answers `confirmation_required` (exit 7).
|
|
138
|
+
|
|
139
|
+
- **Why:** a deletion needs a flag a person has to mean, not a habit.
|
|
140
|
+
- **Enforced by:** review only.
|
|
141
|
+
- **Example:** `tg work messages delete @me 42 --allow-dangerous`.
|
|
142
|
+
- **Exceptions:** `--allow-dangerous` is the flag for `messages.delete`, `bot.messages.delete`, `topics.delete`,
|
|
143
|
+
`chats.delete` and `chats.clear`; every other key takes `--yes`.
|
|
144
|
+
- **Applies to:** every CLI that writes. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
145
|
+
|
|
146
|
+
### WC-PERM-10 · A flag confirms; it never grants
|
|
147
|
+
|
|
148
|
+
`--yes` and `--allow-dangerous` only answer `ask`. They never lift `deny` or `readonly`. An `mcp` flag never grants
|
|
149
|
+
either ([WC-MCP-8](mcp.md#wc-mcp-8--no-mcp-flag-grants-access)).
|
|
150
|
+
|
|
151
|
+
- **Why:** a flag an agent can add must not be a way around the owner's settings.
|
|
152
|
+
- **Enforced by:** `cli-core: src/cli/messenger/messenger.test.ts`.
|
|
153
|
+
- **Example:** with `messages: readonly`, `messages send … --yes` is still refused.
|
|
154
|
+
- **Exceptions:** none.
|
|
155
|
+
- **Applies to:** every CLI that writes. **Since:** cli-base 0.41.0. **Rulings:** NEED-1423.
|
|
156
|
+
|
|
157
|
+
### WC-PERM-11 · Some limits no level lifts
|
|
158
|
+
|
|
159
|
+
An agent never deletes a message for everyone, and never ends the owner's other sessions.
|
|
160
|
+
|
|
161
|
+
- **Why:** both hurt people other than the owner, or lock the owner out, and cannot be undone.
|
|
162
|
+
- **Enforced by:** `cli-core: src/mcp/mcp.test.ts`.
|
|
163
|
+
- **Example:** MCP `messages delete` has no `for_everyone` argument. A messenger whose every delete is for everyone
|
|
164
|
+
sets `Messenger.deleteForMe: false`, and there the MCP delete refuses with `permission_error` without connecting.
|
|
165
|
+
No MCP command ends sessions; `account sessions end` exists only on the command line.
|
|
166
|
+
- **Exceptions:** the owner still has `messages delete --for-everyone` and `account sessions end` on the command line.
|
|
167
|
+
The sessions limit holds today only because no agent command was written for it.
|
|
168
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-202, NEED-1423.
|
|
169
|
+
|
|
170
|
+
### WC-PERM-12 · `<PREFIX>_READ_ONLY` refuses every write
|
|
171
|
+
|
|
172
|
+
When this variable is set, every write answers `permission_error`, whatever `permissions` or a flag say.
|
|
173
|
+
|
|
174
|
+
- **Why:** an unattended agent needs one switch that the config file and `--permission` cannot undo.
|
|
175
|
+
- **Enforced by:** review only.
|
|
176
|
+
- **Example:** `schedule` starts an agent with `MAX_READ_ONLY=1`, so it reads and drafts and cannot send.
|
|
177
|
+
- **Exceptions:** none.
|
|
178
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
179
|
+
|
|
180
|
+
### WC-PERM-13 · Every write passes the write guard before it leaves
|
|
181
|
+
|
|
182
|
+
The guard checks, in order: the read-only lock, the permission level, the recipient list, a flood hold from the
|
|
183
|
+
messenger, and the hourly limit. Then it reserves a line in the send journal. A background server runs the same guard
|
|
184
|
+
before every write it forwards, and so does an MCP server.
|
|
185
|
+
|
|
186
|
+
- **Why:** a model that was talked into sending by a message it read must be stopped by code, not by its own judgement.
|
|
187
|
+
- **Enforced by:** `cli-core: src/sends/guard.test.ts`.
|
|
188
|
+
- **Example:** `messages send`, `reactions add`, `chats members add` and `polls vote` all call the guard.
|
|
189
|
+
- **Exceptions:** the guard does not stop an agent that can edit the config file itself. Run an unattended agent under
|
|
190
|
+
`<PREFIX>_READ_ONLY` or `<PREFIX>_PROFILE_LOCK`, somewhere it cannot write the config.
|
|
191
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-161, NEED-269, NEED-772.
|
|
192
|
+
|
|
193
|
+
### WC-PERM-14 · The recipient list limits which chats a profile writes to
|
|
194
|
+
|
|
195
|
+
Each profile may have a list of chats, kept in a state file and changed with `recipients add` and `recipients clear`.
|
|
196
|
+
|
|
197
|
+
- No file means the check is off.
|
|
198
|
+
- A file with no chats means nowhere.
|
|
199
|
+
- A file that cannot be read refuses every write with `configuration_error`; it never opens up.
|
|
200
|
+
- A person added to a group must have a one-to-one chat on the list.
|
|
201
|
+
- A write to a chat not on the list answers `confirmation_required`, with the command that adds it.
|
|
202
|
+
- **Why:** the owner can fence an agent into a few chats without changing any level.
|
|
203
|
+
- **Enforced by:** `cli-core: src/sends/guard.test.ts`.
|
|
204
|
+
- **Example:** `max work recipients add 123456`.
|
|
205
|
+
- **Exceptions:** a bot has its own list.
|
|
206
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-159, NEED-305.
|
|
207
|
+
|
|
208
|
+
### WC-PERM-15 · The hourly limit counts what puts something in front of a person
|
|
209
|
+
|
|
210
|
+
`sends_per_hour` is 30 for a personal account by default. A bot has no limit unless its `bot` section sets one.
|
|
211
|
+
|
|
212
|
+
- Counted: a message, a forward, an edit, each deleted message, a pin that notifies, a new group, each person added or
|
|
213
|
+
let in, a join, and each imported phone number.
|
|
214
|
+
- Not counted: a reaction, a quiet pin, a title change.
|
|
215
|
+
- A scheduled message counts in the hour it is sent. One batch larger than the limit is refused.
|
|
216
|
+
- Over the limit, the write answers `rate_limited` with the time the next one is possible.
|
|
217
|
+
- **Why:** many writes at once is what the messenger reads as automation, and what bans an account.
|
|
218
|
+
- **Enforced by:** `cli-core: src/sends/guard.test.ts`.
|
|
219
|
+
- **Example:** `{ "defaults": { "sends_per_hour": 10 } }`.
|
|
220
|
+
- **Exceptions:** none.
|
|
221
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-168, NEED-282, NEED-305.
|
|
222
|
+
|
|
223
|
+
### WC-PERM-16 · A retry of an unknown outcome counts once
|
|
224
|
+
|
|
225
|
+
When a write's outcome was unknown, a retry with the same send id in the same chat is not counted again. A retry under
|
|
226
|
+
another `--send-as` identity is refused, because it may post twice.
|
|
227
|
+
|
|
228
|
+
- **Why:** the provider delivers one message for both tries, so the limit must count one.
|
|
229
|
+
- **Enforced by:** review only.
|
|
230
|
+
- **Example:** `messages send … --operation-id 7f3c…` after `outcome_unknown`.
|
|
231
|
+
- **Exceptions:** none.
|
|
232
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-277.
|
|
233
|
+
|
|
234
|
+
### WC-PERM-17 · The send journal records every write attempt, and never its content
|
|
235
|
+
|
|
236
|
+
Each attempt gets a line: time, profile, chat id, kind, action, count and outcome (`reserved`, `sent`,
|
|
237
|
+
`outcome_unknown`, `refused`, `failed`). A line never holds the text, a title, a new value, or a person's id. A failure
|
|
238
|
+
to write the journal after a send only warns.
|
|
239
|
+
|
|
240
|
+
- **Why:** the journal is what the hourly limit counts and what the owner audits; content in it would be a second copy
|
|
241
|
+
of private messages. After a send, an error would invite a second send.
|
|
242
|
+
- **Enforced by:** `cli-core: src/sends/guard.test.ts` · `cli-core: src/sends/journal.test.ts`.
|
|
243
|
+
- **Example:** `{ "at": "…", "profile": "work", "chatId": "…", "kind": "message", "outcome": "sent" }`.
|
|
244
|
+
- **Exceptions:** none.
|
|
245
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-161.
|
|
246
|
+
|
|
247
|
+
### WC-PERM-18 · A flood hold stops writes until it ends
|
|
248
|
+
|
|
249
|
+
When the messenger says the account may not write for a while, the profile holds every counted write until that time.
|
|
250
|
+
Reads still work. The owner lifts the hold with `flood clear`.
|
|
251
|
+
|
|
252
|
+
- **Why:** writing through a hold makes the messenger's penalty longer, or permanent.
|
|
253
|
+
- **Enforced by:** review only.
|
|
254
|
+
- **Example:** `max work doctor --online` shows the hold; `max work flood clear` lifts it once it is over.
|
|
255
|
+
- **Exceptions:** none.
|
|
256
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
257
|
+
|
|
258
|
+
### WC-PERM-19 · Reading never marks anything read unless the command asks
|
|
259
|
+
|
|
260
|
+
- **Why:** the other side sees a chat marked read; that is a write the owner did not ask for.
|
|
261
|
+
- **Enforced by:** review only.
|
|
262
|
+
- **Example:** `inbox --mark-read`, or `catch_up_marks_read: true` in the config, both checked as `chats.mark-read`.
|
|
263
|
+
- **Exceptions:** none.
|
|
264
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-566.
|
|
265
|
+
|
|
266
|
+
### WC-PERM-20 · A bot's keys are the personal keys under `bot`
|
|
267
|
+
|
|
268
|
+
`bot.messages.send` and `bot.chats.members.remove` are bot keys; `bot` alone covers every bot command. A profile can
|
|
269
|
+
allow its bot what it does not allow its personal account, and the other way round.
|
|
270
|
+
|
|
271
|
+
- **Why:** a bot and a personal account do different jobs and need different limits.
|
|
272
|
+
- **Enforced by:** review only.
|
|
273
|
+
- **Example:** `{ "messages": "readonly", "bot.messages.send": "allow" }`.
|
|
274
|
+
- **Exceptions:** bot housekeeping is never gated (WC-PERM-5).
|
|
275
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-294.
|
|
276
|
+
|
|
277
|
+
### WC-PERM-21 · Group actions happen only inside a command the owner typed
|
|
278
|
+
|
|
279
|
+
Moderation rules use the same four levels for each kind of action (delete, remove, accept, decline). `readonly` means
|
|
280
|
+
"report it, do nothing". A background `serve` never polls or moderates groups.
|
|
281
|
+
|
|
282
|
+
- **Why:** typing the command is the owner's consent for what the rules do.
|
|
283
|
+
- **Enforced by:** review only.
|
|
284
|
+
- **Example:** `tg work chats moderate @group` acts under `chats.moderate`; `tg work bot chats moderate` under
|
|
285
|
+
`bot.chats.moderate`.
|
|
286
|
+
- **Exceptions:** an agent may run the same check over MCP, under the same levels. A rule's level is meant to be no
|
|
287
|
+
looser than the profile's `chats.moderate`; the code does not check that yet.
|
|
288
|
+
- **Applies to:** messenger CLIs. **Since:** cli-base 0.41.0. **Rulings:** NEED-306, NEED-310.
|
|
289
|
+
|
|
290
|
+
## Sources
|
|
291
|
+
|
|
292
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
293
|
+
holds.
|
|
294
|
+
|
|
295
|
+
- [cli-core `STANDARD.md`, "Permissions"](https://github.com/WireCatLabs/cli-core/blob/main/docs/dev/STANDARD.md#permissions)
|