@nuucognition/ops-cli 0.1.0-dev.1 → 0.1.0-dev.3

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 CHANGED
@@ -39,7 +39,7 @@ org
39
39
  ├── section (role backlog) the backlog of the project
40
40
  ├── section a list of tasks with no deliverable yet
41
41
  ├── deliverable done when a person marks it done
42
- │ └── section ─ task ─ subtask ─ subtask … (a tree of at most 6 levels)
42
+ │ └── section ─ task ─ subtask (one level: a subtask has no subtasks)
43
43
  └── deployment (live, paused, retired; a free version label)
44
44
  └── section (role inbox) the tasks that come up while it runs
45
45
  section (role backlog) of the org the master backlog
@@ -52,8 +52,9 @@ section (role backlog) of the org the master backlog
52
52
  - **No closer and no "Delivered" task.** `ops deliverable done|reopen`.
53
53
  - **The backlog is a section** (role `backlog`). The backlog folders of
54
54
  before the revamp became sections (Main became the backlog, Queue a section
55
- with the role `queue`). `ops folder` stays hidden for old callers.
56
- - **Subtasks** are full tasks with a parent. A list shows root tasks with a
55
+ with the role `queue`). The hidden `ops folder` group is gone (0.1.0-dev.3).
56
+ - **Subtasks** are full tasks with a parent. They have one level: a subtask
57
+ has no subtasks (`subtask_too_deep`). A list shows root tasks with a
57
58
  count (`1/3 subtasks`). A move takes the subtree. A delete of a task with
58
59
  subtasks needs `--with-subtasks`.
59
60
  - **Deployments are abstract.** A live deployment has an **inbox**. Inbox
@@ -126,11 +127,48 @@ links both bins (`ops` → `bin/ops-prod.js`, `ops-dev` → `bin/ops.js`). Do no
126
127
  expect `ops-dev` on PATH outside the monorepo. The published package has only
127
128
  `ops`.
128
129
 
130
+ ### Update: `ops update`
131
+
132
+ ```bash
133
+ ops update --check # is a newer ops available? It installs and builds nothing
134
+ ops update # get the newer ops
135
+ ops update --check --json
136
+ ```
137
+
138
+ `ops update` finds how ops is installed:
139
+
140
+ - **An npm install** reads the dist-tags of `@nuucognition/ops-cli` (the
141
+ registry of `npm_config_registry`, else registry.npmjs.org). The newest
142
+ release is `latest`, and also `next` when this install is a prerelease.
143
+ When it is newer, `ops update` runs `npm install -g @nuucognition/ops-cli@<version>`.
144
+ - **A build of a checkout** (ndv) compares the bundle (`dist`, or `dist-dev`
145
+ for `ops-dev`) with the source of the checkout. When the source is newer,
146
+ `ops update` runs `ndv build ops prod` (or `ndv build ops`). It does not
147
+ pull the checkout.
148
+
149
+ **The update notice.** An npm install checks the registry at most once a
150
+ day, in a background process, so a command never waits for the network.
151
+ When a newer release is available, a command that exits 0 shows one line on
152
+ stderr, at most once a day:
153
+
154
+ ```
155
+ ops 0.1.0-dev.4 is available (this machine has 0.1.0-dev.3). Next: ops update
156
+ ```
157
+
158
+ The notice is only for a person at a terminal. It does not show in a pipe,
159
+ with `--json`, in CI, in an Orbh session, or in a test. To turn it off, set
160
+ `OPS_NO_UPDATE_CHECK=1`. The state file is in the cache folder of the system
161
+ (`~/Library/Caches/nuucognition/ops/` on macOS). A build of a checkout gets
162
+ no notice from the registry: its launcher warns when the bundle is older
163
+ than the source (`Next: ops update`).
164
+
129
165
  ### Before the first write: which build and which data
130
166
 
131
167
  **The `ops` on PATH can be the prod build.** A prod build always uses prod
132
168
  data and ignores `OPS_DATA_MODE`: `OPS_DATA_MODE=dev ops task create …` with a
133
- prod build **writes to prod data**. So, before the first write:
169
+ prod build **writes to prod data**. **A dev build also uses PROD data by
170
+ default** (Task 415): set `OPS_DATA_MODE=dev` for dev data. So, before the
171
+ first write:
134
172
 
135
173
  1. Run `ops mode` and read two lines: `build mode` (`prod` or `dev`) and
136
174
  `data mode`. Write only when the data mode is the data that the work needs.
@@ -158,14 +196,14 @@ turns the check off.
158
196
 
159
197
  ## Modes
160
198
 
161
- `ops` follows the Strike rule. There are two modes, and they always move
162
- together:
199
+ There are two modes. The build mode says which code runs. The data mode says
200
+ which deployment and which credential that code uses:
163
201
 
164
202
  | | Build mode | Data mode |
165
203
  |---|---|---|
166
204
  | Says | which code runs | which deployment and which credential |
167
205
  | Prod build (`bin/ops-prod.js`, `dist/`) | prod | always prod |
168
- | Dev build (`bin/ops.js`, `dist-dev/`, source) | dev | dev by default |
206
+ | Dev build (`bin/ops.js`, `dist-dev/`, source) | dev | prod by default, as `nuu` (Task 415) |
169
207
 
170
208
  | Data mode | Gateway site | Credential | Web links |
171
209
  |---|---|---|---|
@@ -174,18 +212,28 @@ together:
174
212
 
175
213
  A dev build reads its data mode in this order:
176
214
 
177
- 1. `OPS_DATA_MODE=dev|prod` (one run; a prod build ignores it).
178
- 2. `~/.nuucognition/ops-dev-data.json` (set with `ops mode dev|prod`).
179
- 3. `dev`.
215
+ 1. `OPS_DATA_MODE=dev|prod` (ops only, one run).
216
+ 2. A test run (`node --test`, Vitest): dev. A suite never uses prod data.
217
+ 3. `NUU_DATA_MODE=dev|prod` (the variable of cli-core, one run).
218
+ 4. `~/.nuucognition/dev-data.json`, the shared file of cli-core (Task 1051).
219
+ `ops mode dev|prod` and `flint env use dev|prod` write it, so the dev
220
+ builds of ops and flint use the same data.
221
+ 5. `prod`: with no choice, a dev build uses the sign-in of `nuu login`.
222
+
223
+ A prod build reads neither the variables nor the file. A value that is not
224
+ `dev` or `prod` is not a choice. Before Task 415 the dev build read
225
+ `~/.nuucognition/ops-dev-data.json` and used dev data by default. No build
226
+ reads that file now, and `ops mode` says so when it is still on disk.
180
227
 
181
- A test run (`node --test`) stays on dev data unless `OPS_DATA_MODE` says
182
- otherwise. A prod build reads neither the variable nor the file.
228
+ A dev build on prod data writes one note on stderr before each command
229
+ (`note: this dev build uses PROD data …`; no note with `--json`, and none for
230
+ `ops mode`). Each write of such a run is a real write that people see.
183
231
 
184
232
  ```bash
185
- ops mode # build mode, data mode, site, credential file
186
- ops mode prod # dev build only: use prod data from now on
187
- ops mode dev # dev build only: back to dev data
188
- OPS_DATA_MODE=prod node apps/cli-ops/bin/ops.js whoami # the dev launcher, prod data, this run only
233
+ ops mode # build mode, data mode and its source, site, credential file
234
+ ops mode dev # dev build only: dev data from now on (the shared file)
235
+ ops mode prod # dev build only: back to prod data
236
+ OPS_DATA_MODE=dev node apps/cli-ops/bin/ops.js whoami # the dev launcher, dev data, this run only
189
237
  ```
190
238
 
191
239
  ## Auth
@@ -200,7 +248,10 @@ or `$NUU_HOME`):
200
248
  | dev | `~/.nuucognition/auth.local.json` | `http://localhost:3000` | a dev login, or `ops login` of a dev build in dev data |
201
249
 
202
250
  So a machine that ran `nuu login` or `flint login` needs no other step for
203
- the published `ops`. The code is
251
+ `ops`: the prod build, and a dev build with no choice of data mode, use
252
+ `auth.json`. Each Ops deployment trusts one CLI issuer (`NUU_CLI_ISSUER`):
253
+ the dev deployment trusts only the local account service, so dev data needs
254
+ its own sign-in. The code is
204
255
  `requireValidAuth(resolveEnv("ops", { mode: dataMode() }))` of cli-core.
205
256
  `ops login` runs the browser sign-in of cli-core (the same flow as
206
257
  `flint login`) and writes the credential file of the current data mode.
@@ -211,14 +262,15 @@ token.
211
262
 
212
263
  ```bash
213
264
  ops login # browser sign-in for the current data mode (a person only)
214
- ops whoami # user, email, data mode, active org
265
+ ops whoami # user, email, sign-in (for example "device login; does not expire"), data mode, active org
215
266
  ops mode # the credential file and who is signed in
216
267
  ```
217
268
 
218
269
  When the credential is missing or does not refresh, a command fails with
219
- `error auth_required: …` and exit code 3, and names the login command. An
220
- agent cannot sign in: it asks the operator to run `nuu login` (or
221
- `ops login`).
270
+ `error auth_required: Not signed in for <mode> data.` and exit code 3. An
271
+ agent cannot sign in. On prod data the hint asks the operator for
272
+ `nuu login` (or `ops login`). On dev data it asks for `ops login` of the dev
273
+ build (the local account service), and names the way back to prod data.
222
274
 
223
275
  ## Orgs
224
276
 
@@ -239,7 +291,7 @@ A record argument takes any of these forms:
239
291
  |---|---|---|
240
292
  | Convex ID | `k57b2m4x9q8r7t6y5u4i3o2p1a0s9d8f` | `agent.resolve` (kind, org, title, breadcrumb) |
241
293
  | Web URL | `https://ops.nuucognition.com/me/tasks/<t>`, `…/projects/<p>/deliverables/<d>?task=<t>` | the ID in the path or query, then `agent.resolve` |
242
- | Ops address | `@v1/acme/task/<id>#ops`, `@v1/acme/task.dev/<id>#ops`, `@v1/acme/section/<id>#ops`, `@v1/acme/deployment/<id>#ops`, `@v1/acme/project/alpha#ops`, `@v1/acme/project/alpha/deliverable/v1#ops`, `@v1/acme/taskgroup/<id>#ops`, `@v1/acme/notepad/<id>#ops` | `agent.resolve` (the object that the address names now); other text that starts with `@` is a title |
294
+ | Ops address | `@v1/acme/task/<id>#ops`, `@v1/acme/section/<id>#ops`, `@v1/acme/deployment/<id>#ops`, `@v1/acme/project/alpha#ops`, `@v1/acme/project/alpha/deliverable/v1#ops`, `@v1/acme/taskgroup/<id>#ops`, `@v1/acme/notepad/<id>#ops` | `agent.resolve` (the object that the address names now); an old task address with the type word `task.dev` (`@v1/acme/task.dev/<id>#ops`, in old Flint task notes) names the same task; other text that starts with `@` is a title |
243
295
  | Title | `"Write the docs"` | `agent.searchTasks` for tasks; the headers of the org (`headers.forOrg`) for projects, deliverables, sections, and deployments; the list queries for the other kinds |
244
296
  | Path | `"Alpha › v1 › Backend"`, `"Alpha > v1"`, `"Alpha / Backlog"` | the path of a place in the headers (`›`, `>`, and `/` are the same separator) |
245
297
 
@@ -256,13 +308,14 @@ An ID, URL, or address never needs a scope.
256
308
 
257
309
  `agent.resolve` reads each kind of record: a project, a deliverable, a
258
310
  section, a task, a task group, a backlog folder, a plan, a step, a deadline,
259
- a day label, a deployment, a blocker, and a notepad (a blocker and a notepad
311
+ a deployment, a blocker, and a notepad (a blocker and a notepad
260
312
  from fleet ops-cli-publish-1023; a deleted notepad, and a personal notepad of
261
313
  another person, are not found). Five kinds have an address row: a project, a
262
314
  deliverable, a task, a task group, and a notepad (the display address is the
263
- alias, else the id form). A section and a deployment have the id form only.
264
- A plan, a step, a deadline, a day label, and a blocker have no address: use
265
- the ID. `ops address <record>` prints the address of a record, and the
315
+ alias, else the id form). A section, a deployment, and a blocker have the id
316
+ form only (a blocker: `@v1/<org>/blocker/<id>#ops`, R32 of ADDRESS.md; a
317
+ backend of before it gives none). A plan, a step, and a deadline have no
318
+ address: use the ID. `ops address <record>` prints the address of a record, and the
266
319
  record that an address names now.
267
320
 
268
321
  ### Places (`--to` of `task move`)
@@ -271,7 +324,7 @@ record that an address names now.
271
324
  |---|---|
272
325
  | `section:<ref>` | the section |
273
326
  | `project:<ref>` | the backlog section of the project (made on first need) |
274
- | `deliverable:<ref>` | the first open section of the deliverable (made when none) |
327
+ | `deliverable:<ref>` | the main section of the deliverable (role `main`, with the name of the deliverable; made when none, Task 406) |
275
328
  | `deployment:<ref>` | the inbox section of the deployment |
276
329
  | `parent:<task>` (or `task:<task>`) | under the task, as a subtask |
277
330
  | `backlog` | the backlog section of the project of the task |
@@ -283,7 +336,8 @@ record that an address names now.
283
336
  A **link spec** (deadline links, step links, title links, blocker links) is
284
337
  `kind:ref`, a URL, an address, or an ID: `task:"Write docs"`, `step:<id>`,
285
338
  `project:Alpha`, `https://…/plans/<id>`, `@v1/acme/task/<id>#ops`. A title
286
- needs the kind prefix.
339
+ needs the kind prefix. Only a blocker link can be a deployment
340
+ (`deployment:acme.com`).
287
341
 
288
342
  ## Output and errors
289
343
 
@@ -310,12 +364,17 @@ Gateway codes pass through: `unauthenticated`, `org_unauthorized`,
310
364
  `reorder_anchor_outside`, `position_unknown`, `confirm_required`,
311
365
  `place_ambiguous`, `place_not_found`, `deployment_no_inbox`. Codes of the
312
366
  revamp from the service: `task_has_subtasks` (a delete needs
313
- `--with-subtasks`), `subtask_too_deep` (6 levels), `subtask_cycle`.
367
+ `--with-subtasks`), `subtask_too_deep` (one level: a subtask has no
368
+ subtasks), `subtask_cycle`. The share refusals keep their codes:
369
+ `not-reachable`, `same-org`, `personal-target`, `org-unknown`,
370
+ `self-target`, `person-unknown`. A hold refuses a write with `held`.
314
371
 
315
372
  An op that a deployment of Ops does not have yet fails with
316
- `agent_unknown_operation` and names the deploy. Where a former op can do the
317
- work (a section of a deliverable, a move to a section or a backlog), the
318
- command uses it on such a deployment.
373
+ `agent_unknown_operation` and names the deploy. The CLI needs a deployment
374
+ with the current ops: since 0.1.0-dev.3 it has no fallback to the former ops
375
+ of before the revamp (a move through `tasks.place`, a close through
376
+ `tasks.update`, the section writes of `subsections.*`, the old project
377
+ snapshot).
319
378
 
320
379
  The guards exit 2: `confirm_required` (a delete that removes other records
321
380
  or cannot be undone needs `--yes`: project, deliverable, section, deployment, plan, blocker),
@@ -337,6 +396,7 @@ reads stdin.
337
396
  | `ops login [--verbose]` | `ops login` |
338
397
  | `ops whoami` | `ops whoami --json` |
339
398
  | `ops mode [dev\|prod]` | `ops mode prod` |
399
+ | `ops update [--check]` — get the newer ops: an npm install gets the newest npm release, a build of a checkout is built again (see Update) | `ops update --check` |
340
400
  | `ops ops [--area <area\|module>]` — the gateway manifest (no credential needed); `--area` matches the manifest area (`history`) or the op module (`waybill`) | `ops ops --area waybill` |
341
401
  | `ops call <op> [json\|-] [--query\|--mutation\|--action] [--inject-org]` — any allowlisted op; always prints JSON; the manifest gives the kind (`--action` is for `route.suggest`) | `ops call plans.listLite '{}' --inject-org` |
342
402
 
@@ -347,7 +407,8 @@ reads stdin.
347
407
  | `ops headers [--project <p>] [--archived]` — the map of the org (`headers.forOrg`): each project, its sections, its deliverables and their sections, its deployments and their sections, then the sections and deployments of the org; each with its id, role, done mark, and address | `ops headers --project Alpha` |
348
408
  | `ops route <text> [--want task\|idea\|any] [--no-smart] [--limit 1-8]` — the route decision: `route.suggest` (an action: Jev with a limit of 1.5 s, else the text match), or with `--no-smart` the query twin `route.candidates`; each line: the score, the `kind:<id>` form, the path, the reason, and the address; `--json`: `{orgSlug, op, model, latencyMs, intent, candidates[]}`, where `op` is the op that answered (`route.suggest` or `route.candidates`) and each candidate has `place {kind, id, title, path}`, `score`, `confidence` (the confidence of the decision model, when it gave one), `reason`, and `address`. **A score below 0.3, or a reason that is only "A recent project", means that no place fits**: name a place, or make one (see the skill `create_task` of the Ops shard) | `ops route "Fix the login bug on Safari"` |
349
409
  | `ops search <text> [--type task,section,deliverable,project,deployment] [--include-done] [--limit 1-50]` — `opsSearch.search`: tasks and places in one list, each with its path and address | `ops search billing --type section,deliverable` |
350
- | `ops address <record> [--kind project\|deliverable\|section\|task\|deployment\|taskgroup\|notepad\|plan\|step\|deadline\|blocker]` — the address of a record: `addresses.of` gives the display address, the id address, the `nuu://ops/<type>/<id>` URI, and `canRename` (the owner rights) of a project, a deliverable, a task, a task group, or a notepad; a section and a deployment have the id form (from the headers); the other kinds print `none`. For an address, `addresses.find` sets `moved` when the text is an old alias or an old form. A title needs `--kind`; `--json`: `{kind, id, orgSlug, title, address, idAddress, uri, canRename, moved}` | `ops address @v1/acme/project/alpha#ops` |
410
+ | `ops address <record> [--kind project\|deliverable\|section\|task\|deployment\|taskgroup\|notepad\|plan\|step\|deadline\|blocker]` — the address of a record: `addresses.of` gives the display address, the id address, the `nuu://ops/<type>/<id>` URI, and `canRename` (the owner rights) of a project, a deliverable, a task, a task group, or a notepad; a section and a deployment have the id form (from the headers), and a blocker has the id form (from `blockers.get`); the other kinds print `none`. For an address, `addresses.find` sets `moved` when the text is an old alias or an old form. A title needs `--kind`; `--json`: `{kind, id, orgSlug, title, address, idAddress, uri, canRename, moved}` | `ops address @v1/acme/project/alpha#ops` |
411
+ | `ops address rename <record> (<name> \| --clear) [--kind …]` — `addresses.rename`: set the alias of the address of a project, a deliverable, a task, a task group, or a notepad (the owner rights are needed); `--clear` removes the alias, and the id address stays; the previous address still names the record (a moved address); a refusal keeps its code (for example a taken name); another kind is `address_not_renamable`; `--json`: `{kind, id, orgSlug, address, previous}` | `ops address rename <projectId> billing-v2` |
351
412
  | `ops people search <text> [--limit 1-50]` and `ops people show <@handle\|email\|user_…>` — `people.search`, `people.getByHandle`, `people.getByEmail`, and `people.get` in the scope of the active org: the name, the handle, the email, and the Clerk user id of each person that the caller can see (the personal workspace shows only the caller); none is `person_not_found`. `ops member list` gives the `users` rows (the owners and assignees of tasks) | `ops people search ada` |
352
413
 
353
414
  ### org, member
@@ -364,7 +425,7 @@ reads stdin.
364
425
  | Command | Example |
365
426
  |---|---|
366
427
  | `ops project list [--status active\|paused\|archived\|all]` — each with its address | `ops project list --status active` |
367
- | `ops project show <project>` — its sections (the backlog first), its deliverables and deployments with their sections, and the root tasks, each with its address; a deployment of before the revamp gives the former snapshot (deliverables, sections, the backlog folders) | `ops project show Alpha` |
428
+ | `ops project show <project>` — its sections (the backlog first), its deliverables and deployments with their sections, and the root tasks, each with its address | `ops project show Alpha` |
368
429
  | `ops project create <name> [--description] [--kind project\|operation] [--start] [--end] [--next-update] [--context <md>] [--deployment <d>]` — `--deployment` is the deployment that the project ships to (`originDeploymentId`, a link: the two records stay independent) | `ops project create "Alpha" --next-update +7d` |
369
430
  | `ops project update <project> [--name] [--description] [--next-update <date\|none>] [--status] [--kind] [--start <date\|none>] [--end <date\|none>] [--deployment <d\|none>]` | `ops project update Alpha --status paused` |
370
431
  | `ops project link <project> <uri...>` / `ops project unlink <project> <uri>` — the linked things of a project: NUU Connect URIs (`nuu://<app>/<type>/<id>`) of other NUU apps (`projectLinks.link`, `projectLinks.unlink`); each URI is stored once, at most 50; a bad URI refuses with `connect_bad_request`; `--json` gives `{id,connectRefs}` | `ops project link Alpha nuu://chat/thread/abc` |
@@ -381,6 +442,7 @@ reads stdin.
381
442
  | `ops deliverable create <title> --project <project> [--due <date>] [--section <title>] [--deployment <d>] [--context <md>]` — no closer and no task; `--section` adds a first section | `ops deliverable create "v1 launch" --project Alpha --section Backend` |
382
443
  | `ops deliverable update <deliverable> [--title] [--deployment <d\|none>] [--from-deployment <d\|none>]` — `deliverables.setDeployment` | `ops deliverable update "v1.1" --deployment Production` |
383
444
  | `ops deliverable done <deliverable>` / `ops deliverable reopen <deliverable>` — `deliverables.setDone`: a person decides; the sections do not | `ops deliverable done "Alpha › v1 launch"` |
445
+ | `ops deliverable move <deliverable> --to <project> [--project <p>]` — `deliverables.move`: the deliverable goes to another project with its sections and tasks, and its address moves too; the links to a deployment of the old project are removed, and the text says how many | `ops deliverable move "v1 launch" --to Beta` |
384
446
  | `ops deliverable rename <deliverable> <title>` | `ops deliverable rename "v1 launch" "v1.0 launch"` |
385
447
  | `ops deliverable context show\|set <deliverable> …` — `deliverables.setContext` | `ops deliverable context set "v1 launch" "The first public release"` |
386
448
  | `ops deliverable delete <deliverable> --yes` — deletes its sections and tasks; refuses without `--yes`; only an owner or an admin of the org can delete it (an org member gets `owner-only`) | `ops deliverable delete "v1.0 launch" --yes` |
@@ -400,8 +462,8 @@ reads stdin.
400
462
  | `ops section move <section> --to <project:<p>\|deliverable:<d>\|deployment:<d>\|org>` — `sections.moveHome`; the tasks go with it; a section with a role does not move | `ops section move Ideas --to deliverable:"Alpha › v2"` |
401
463
  | `ops section reorder <section> (--before <s>\|--after <s>\|--top\|--bottom)` — inside its home (`sections.reorder` takes every section of the home) | `ops section reorder Backend --top` |
402
464
  | `ops section deps set\|add\|remove <section> [deps...]` — the sections it comes after | `ops section deps set QA Backend Frontend` |
403
- | `ops section priority <section> on\|off` | `ops section priority Backend on` |
404
465
  | `ops section archive\|unarchive <sections...>` — `sections.archive` | `ops section archive Backend` |
466
+ | `ops section deployment set <section> <deployment>` / `ops section deployment clear <section>` — `subsections.setDeployment`: the deployment that the section ships to (its shipping link, a field of its own, not its home); a name is a deployment of the project of the section or a deployment of the org (no project), and two matches are `deployment_ambiguous` (exit 2) | `ops section deployment set Backend Production` |
405
467
  | `ops section context show\|set <section> …` — `sections.update` | `ops section context set Backend "The API and the queue"` |
406
468
  | `ops section delete <section> --yes` — deletes its tasks; refuses without `--yes` | `ops section delete Backend --yes` |
407
469
  | `ops section to-task <section> [--into <section>]` — an empty section becomes a task | `ops section to-task "Tiny fix"` |
@@ -410,20 +472,28 @@ reads stdin.
410
472
 
411
473
  | Command | Example |
412
474
  |---|---|
413
- | `ops task list [--project \| --deliverable \| --section \| --deployment \| --master \| --mine [--all-orgs]] [--backlog] [--status open\|todo\|doing\|done\|all] [--priority <p\|none>] [--owner <me\|member\|none>] [--type <type>] [--stage <stage>] [--limit n]` — root tasks, each with its place and address; `--section` keeps the order of the section and gives the subtask counts (`sections.tasks`); `--deployment` lists the inbox (`deployments.inbox`); default `--status open`; more than one scope flag is a usage error | `ops task list --section "Alpha › v1 › Backend" --status all` |
414
- | `ops task list --all [--type <type>] [--stage <stage>] [--status …] [--limit n]` — every task of the org, placed and unplaced, newest first, as task summaries (address, type, stage, home label); `--limit` is the total (default 200) | `ops task list --all --type task.dev --stage reviewed --json` |
415
- | `ops task list --unplaced [--type <type>] [--stage <stage>]` — the tasks with no home; when the tasks need more bytes than one read (`task_read_too_large`), the CLI reads the pages of `tasks.listAllPage`; a page that is not the last and does not move the cursor stops the list (`pagination_stalled`, exit 1, no rows) | `ops task list --unplaced` |
416
- | `ops task show <task>` — also the address, the parent, the subtasks (the tree with the done count; `--json`: `subtasks`), the type and the stage, the outcome, the fields, the lineage, the task groups, the related links, the attachments, and the stage history; when the incoming lineage stops at its byte budget, `--json` has `linksCut: true` and the text says so; a task with a Flint task note gives the lines `note` (the label, else the address) and `note link` (the open link), and `--json` has `note`, `noteLabel`, and `noteUrl` at the top (each `null` when the task has no note) | `ops task show @v1/test/task.dev/<id>#ops` |
475
+ | `ops task list [--project \| --deliverable \| --section \| --deployment \| --master \| --mine [--all-orgs]] [--backlog] [--status open\|todo\|doing\|done\|all] [--priority <p\|none>] [--owner <me\|member\|none>] [--limit n]` — root tasks, each with its place and address; `--section` keeps the order of the section and gives the subtask counts (`sections.tasks`); `--deployment` lists the inbox (`deployments.inbox`); default `--status open`; more than one scope flag is a usage error | `ops task list --section "Alpha › v1 › Backend" --status all` |
476
+ | `ops task list --all [--status …] [--limit n]` — every task of the org, placed and unplaced, newest first, as task summaries (address, home label); `--limit` is the total (default 200) | `ops task list --all --status done --json` |
477
+ | `ops task list --unplaced [--status …] [--limit n]` — the tasks with no home; when the tasks need more bytes than one read (`task_read_too_large`), the CLI reads the pages of `tasks.listAllPage`; a page that is not the last and does not move the cursor stops the list (`pagination_stalled`, exit 1, no rows) | `ops task list --unplaced` |
478
+ | `ops task show <task>` — also the address, the parent, the people looped in (a `looped in` line with each name and users ID; `--json`: `loopedIn: [{userId, name}]`, `name` is `null` for a person who is not a member; an empty list when no person is looped in), the subtasks (the tree with the done count; `--json`: `subtasks`), the lineage, the task groups, the related links, and the attachments; when the incoming lineage stops at its byte budget, `--json` has `linksCut: true` and the text says so; a task with a Flint task note gives the lines `note` (the label, else the address) and `note link` (the open link), and `--json` has `note`, `noteLabel`, and `noteUrl` at the top (each `null` when the task has no note); the open review requests (the reviewer, the requester, the age, and the note; `--json`: `reviews: {open, closed}`, from `reviews.forTask`; a deployment without the op gives no `reviews`) | `ops task show @v1/test/task/<id>#ops` |
417
479
  | `ops task search <query> [--limit 1-100]` | `ops task search docs --limit 5` |
418
- | `ops task create <title> [--section <s> \| --project <p> \| --deliverable <d> \| --deployment <d> \| --parent <task> \| --route [text] \| --master \| --unplaced] [--tag feedback\|hotfix\|ongoing\|recurring] [--every day\|week\|month] [--type <type> [--stage <stage>]] [--repo <name>] [--note <address> [--note-label <text>]] [--body <md\|-> \| --body-file <path>] [--priority low\|medium\|high\|urgent] [--assign <me\|member>] [--top]` — default: the master backlog, at the end; `--project` is its backlog section, `--deliverable` its first open section, `--deployment` its inbox, `--parent` a subtask; `--route` asks `route.suggest` with the text (default: the title) and prints the place, the score, and the reason; `--stage` is the first stage of a typed task (default: the first stage of the type; an unknown stage refuses with `task_stage_unknown`); the output gives the stored title (a trailing digit of the title is the rarity: "Fix login 1" is stored as "Fix login"), the address, and the section | `ops task create "Login fails on Safari" --deployment Production --tag hotfix` |
480
+ | `ops task create <title> [--section <s> \| --project <p> \| --deliverable <d> \| --deployment <d> \| --parent <task> \| --route [text] \| --master \| --unplaced] [--tag feedback\|hotfix\|ongoing\|recurring] [--every day\|week\|month] [--repo <name>] [--note <address> [--note-label <text>]] [--body <md\|-> \| --body-file <path>] [--priority low\|medium\|high\|urgent] [--assign <me\|member>] [--top]` — default: the master backlog, at the end; `--project` is its backlog section, `--deliverable` its main section (role `main`, with the name of the deliverable; the service makes it, Task 406), `--deployment` its inbox, `--parent` a subtask; `--route` asks `route.suggest` with the text (default: the title) and prints the place, the score, and the reason; `--repo` adds a `repo` related link (`taskRelations.add`); the output gives the stored title (a trailing digit of the title is the rarity: "Fix login 1" is stored as "Fix login"), the address, and the section | `ops task create "Login fails on Safari" --deployment Production --tag hotfix` |
419
481
  | `ops task update <task> [--title] [--body <md\|-> \| --body-file <path>] [--priority <p\|none>] [--assign <member\|none>] [--rarity <1-7\|none>] [--tag <tag\|none>] [--every day\|week\|month\|none]` — `--tag` and `--every` need `tasks.update` with `tag` and `recurrence` (MODEL; a deployment without them refuses with `agent_invalid_input`) | `ops task update "Write docs" --body-file notes.md` |
420
482
  | `ops task start <task>` — doing | `ops task start "Write docs"` |
421
- | `ops task done <task>` — the same as `close` with the outcome `completed` | `ops task done "Write docs"` |
422
- | `ops task reopen <task>` — doing; the outcome goes, the stage stays | `ops task reopen "Write docs"` |
423
- | `ops task move <tasks...> --to <place> [--priority <p\|none>]` — one task: `tasks.move`; more tasks: `tasks.moveMany`; each takes its subtree; the places are in [Places](#places---to-of-task-move); the former `--subsection`, `--backlog`, and `--folder` still work, hidden | `ops task move <a> <b> --to section:"Alpha › v1 › Backend"` |
483
+ | `ops task done <task>` — the same as `close` with no result | `ops task done "Write docs"` |
484
+ | `ops task reopen <task>` — doing | `ops task reopen "Write docs"` |
485
+ | `ops task move <tasks...> --to <place> [--priority <p\|none>]` — one task: `tasks.move`; more tasks: `tasks.moveMany`; each takes its subtree; the places are in [Places](#places---to-of-task-move); the former `--subsection` and `--backlog` still work, hidden (`--folder` is gone) | `ops task move <a> <b> --to section:"Alpha › v1 › Backend"` |
424
486
  | `ops task reorder <task> (--before <t>\|--after <t>\|--top\|--bottom)` | `ops task reorder "Write docs" --after "Draft outline"` |
425
487
  | `ops task delete <task> [--with-subtasks]` — the service refuses a task with subtasks without the flag (`task_has_subtasks`) | `ops task delete "Write docs"` |
426
488
  | `ops task to-section <task> [--deliverable <d>]` — `to-subsection` is its former name | `ops task to-section "Big item" --deliverable "v1 launch"` |
489
+ | `ops task review request <task> [--to <member>] [--note <md\|->]` — `reviews.request`: ask a person to review the task (see [review](#review)); `--to` takes a member as `--assign` does; with no `--to`, the reviewer is the owner of the task (a task with no owner: you in the personal workspace, `review_no_reviewer` in a team org); a second request to the same reviewer while one is open changes its note, and the text says `again`; the request loops the reviewer into the task (see `ops task loop`), unless the reviewer is the owner; `--json`: `{itemId, id, orgSlug, reviewerUserId, again, note?}` | `ops task review request <task> --to @ana --note "Check the copy"` |
490
+ | `ops task review approve <task> [--note <md\|->] [--keep-open]` — `reviews.resolve` with the verdict `approved` on the open request where you are the reviewer; the task closes, unless `--keep-open`; the note goes to the requester; `--json`: `{itemId, id, orgSlug, verdict, closed, reopened, note?}` | `ops task review approve <task>` |
491
+ | `ops task review changes <task> [--note <md\|->]` — the verdict `changes`: the task stays open, and a done task opens again (`reopened: true`) | `ops task review changes <task> --note "The redirect fails on Safari."` |
492
+ | `ops task review dismiss <task> [--note <md\|->]` — the verdict `dismissed`: the task does not change | `ops task review dismiss <task>` |
493
+ | `ops task review cancel <task> [--to <member>]` — `reviews.cancel`: withdraw the open request that you made; when you asked more than one reviewer, `--to` names the reviewer (else `review_ambiguous`, exit 2, with the reviewers as candidates); `--json`: `{itemId, id, orgSlug, reviewerUserId, cancelled: true}` | `ops task review cancel <task>` |
494
+ | `ops task loop add <task> <person>` — `taskLoops.add`: loop a member of the org into the task; the person follows the task beside its owner, hears every comment on it, and gets a notification; `<person>` takes `me` or a member as `--assign` does; a person in the loop already gives `no change`; a task loops in at most 50 people (`loop_full`); a review request loops in the reviewer, and a mention in a comment loops in the mentioned member; `--json`: `{id, orgSlug, userId, added}` | `ops task loop add <task> @ana` |
495
+ | `ops task holds <task>` — `holds.ofTask`: the hold of a task, a short lease of an actor (an agent session or a person) that takes (`take`) or chairs (`chair`) the task: the holder, since when, until when, and the open requests; a hold whose time has passed is `expired` (`live: false`); a write by another actor on a held task is refused with `held`; `--json`: `{taskId, orgSlug, hold, live}` (`hold` is `null` with no hold) | `ops task holds <taskId>` |
496
+ | `ops task loop remove <task> <person>` — `taskLoops.remove`: take a person out of the loop of the task; a person who is not in it gives `no change`; `--json`: `{id, orgSlug, userId, removed}` | `ops task loop remove <task> @ana` |
427
497
 
428
498
  #### Subtasks and the task map
429
499
 
@@ -433,7 +503,8 @@ reads stdin.
433
503
  | `ops task subtask list <task>` — the subtree with the done count (`subtasks.tree`; cut at 200 tasks) | `ops task subtask list <taskId>` |
434
504
  | `ops task subtask move <task> (--under <parent> \| --root) [--index n]` — `subtasks.reparent`; `--root`: a root task in the home of its old root | `ops task subtask move <taskId> --under <parentId>` |
435
505
  | `ops task subtask reorder <task> --index n` — its place among its siblings (0 is first) | `ops task subtask reorder <taskId> --index 0` |
436
- | `ops task subtask indent\|outdent <task>` — under the sibling above it, or one level up | `ops task subtask outdent <taskId>` |
506
+ | `ops task subtask children <task>` — `subtasks.children`: the subtasks as full task rows, in order (at most 200); a share viewer gets the rows that it can read | `ops task subtask children <taskId> --json` |
507
+ | `ops task subtask outdent <task>` — make a subtask a root task again, after its parent. There is no `indent`: subtasks have one level, and a subtask has no subtasks (`subtask_too_deep`) | `ops task subtask outdent <taskId>` |
437
508
  | `ops task map <task>` — `taskMap.get`: the root and the subtree, the home (project, deliverable, deployment, section), the task groups with their members, the lineage (split, merged, continues, after, before), the related links, each with its address, and the web page of the map | `ops task map <taskId> --json` |
438
509
 
439
510
  A task order bucket is one section **and** one priority band (before the
@@ -447,33 +518,28 @@ unplaced task has no order bucket: `ops task reorder` refuses it
447
518
 
448
519
  #### The task primitive v0.1 ((Task) 1287 of the NUU Flint)
449
520
 
450
- A task has one status (`todo`, `doing`, `done`). A done task has one
451
- outcome: `completed`, `dropped`, or `superseded`. A task type (for example
452
- `task.dev@0.1`) adds fields and stages. The stages are a second layer: any
453
- stage can follow any stage. A typed task closes as `completed` only at a
454
- stage that can close (for `task.dev`: `reviewed` or `verified`). Only a
455
- person sets `verified`. A write is by an agent when `ORBH_SESSION_ID` is
456
- set (the CLI sends it as `X-Agent-Session`). A task has 0 or 1 home and 0
457
- or more related links. A task with no home is **unplaced**, and that is
458
- normal.
521
+ A task has one status (`todo`, `doing`, `done`). A done task can have a
522
+ result. A task has no outcome (removed 2026-11-04): a done task is done.
523
+ The CLI sends no outcome; `--outcome` is an unknown option. A task has no
524
+ type and no stage: each task has the address `@v1/<org>/task/<id>#ops`,
525
+ and a task closes at any time. A write is by an agent when
526
+ `ORBH_SESSION_ID` is set (the CLI sends it as `X-Agent-Session`). A task
527
+ has 0 or 1 home and 0 or more related links. A task with no home is
528
+ **unplaced**, and that is normal.
459
529
 
460
530
  | Command | Example |
461
531
  |---|---|
462
- | `ops task stage <task> <stage> [--note <text>]` — `taskStages.set` | `ops task stage <task> reviewed --note "No high finding"` |
463
- | `ops task retype <task> (<type>[@<version>] \| --none) [--type-version <v>]` — `tasks.setType` (amendment 6); a new type takes its active version; the stage and the fields stay only when the new type defines them (the same name and kind); `--none` makes the task untyped; the same type and version changes nothing; a done task refuses with `task_type_done` (reopen it first) | `ops task retype <task> task.dev` |
464
- | `ops task close <task> [--outcome completed\|dropped\|superseded] [--result <md\|->]` — `tasks.close`; default `completed` | `ops task close <task> --outcome dropped` |
532
+ | `ops task close <task> [--result <md\|->]` — `tasks.close`: the status `done`, with an optional result; `--json`: `{id, checkbox: "done", result?}` | `ops task close <task> --result "The pin works on dev."` |
465
533
  | `ops task result <task> <md\|->` — `""` clears it | `ops task result <task> "The pin works on dev."` |
466
534
  | `ops task note <task> <address> [--label <text>]` / `ops task note <task> --clear` — `tasks.setNote` ((Task) 1291): the Flint task note holds the content, and Ops keeps its address, `@v1/<org>/task/<note id>#flint/<flint>` (`flint resolve @v1//task/<n> --json` gives it); a new address with no `--label` clears the label; `--json` gives `{taskId, address, note, noteLabel, noteUrl}`; `noteUrl` is `obsidian://nuu-flint?address=` and the encoded note address, and the NUU Flint plugin opens it; a wrong address refuses with `task_note_invalid` | `ops task note <task> @v1/nuucognition/task/<uuid>#flint/nuu-flint --label "Task 1291 · NUU Flint"` |
467
- | `ops task field <task> <name> <value> [--append \| --remove]` — `tasks.setFields`; `""` clears the field; a list field gets the value as its one item; `--append` adds one item, once; `--remove` (amendment 6) deletes each item equal to the value from a list field in one write, so an append of another person at the same time stays (an item that is not in the list is no change); `--append` and `--remove` together are a usage error | `ops task field <task> wipCommits 1a2b3c4 --remove` |
468
- | `ops task commit <task> <sha> [--repo <name>]` — appends `<sha>` or `<sha> (<repo>)` to `wipCommits` | `ops task commit <task> 1a2b3c4d --repo flint` |
469
535
  | `ops task home <task> (--section <s> \| --backlog [--project <p>] [--folder <f>] \| --none)` — `tasks.home`; `--project` and `--folder` imply `--backlog`; `--backlog` alone is the master backlog; `--none` unplaces the task; for a deliverable, a deployment, or a parent, use `ops task move --to` | `ops task home <task> --backlog --project Alpha` |
470
536
  | `ops task relate <task> <kind> <ref> [--label <text>]` — kinds: `project`, `deliverable` (an ID, URL, or title), `repo` (a name), `flint` (a Flint id), `address` (a v1 address); a second relate changes only the label | `ops task relate <task> repo main` |
471
537
  | `ops task unrelate <task> <kind> <ref>` | `ops task unrelate <task> project Alpha` |
472
538
  | `ops task related <kind> <ref>` — `taskRelations.listForRef`: the tasks with a related link to a `project`, a `deliverable` (an ID, URL, or title), a `repo`, a `flint`, or an `address`, as task summaries | `ops task related repo main` |
473
539
  | `ops task hints [--flint <id>] [--repo <name>] [--text <text>]` — proposed homes from a Flint, a repository, or a text, best first; each line ends with the flags for `ops task home`; for a text, `ops route` is the decision of the revamp | `ops task hints --text "Fix the login page"` |
474
- | `ops task split <task> --part <title> [--part <title> ...] [--keep-open]` — the parts get the home, the type, and the related links; the old task closes as `superseded` | `ops task split <task> --part Backend --part Frontend` |
475
- | `ops task merge <task> <task>... (--into <task> \| --title <title>)` — the open absorbed tasks close as `superseded` | `ops task merge <a> <b> --into <c>` |
476
- | `ops task continue <task> [--title <title>]` — default title `<old title> (continued)`; an open old task closes as `superseded` | `ops task continue <task>` |
540
+ | `ops task split <task> --part <title> [--part <title> ...] [--keep-open]` — the parts get the home and the related links; an open old task closes, unless `--keep-open` | `ops task split <task> --part Backend --part Frontend` |
541
+ | `ops task merge <task> <task>... (--into <task> \| --title <title>)` — the open absorbed tasks close | `ops task merge <a> <b> --into <c>` |
542
+ | `ops task continue <task> [--title <title>]` — default title `<old title> (continued)`; an open old task closes | `ops task continue <task>` |
477
543
  | `ops task chain <task> <task> [<task>...]` — in order; a task is ready when the task before it is done | `ops task chain <first> <second> <third>` |
478
544
  | `ops task unchain <task>` — removes the `after` link of the task | `ops task unchain <task>` |
479
545
  | `ops task link <task> <previous>` — `taskLinks.link`: two tasks that exist; `<task>` continues from `<previous>` (kind `continues-from`); a new link replaces the old one; no task closes (`ops task continue` makes a new task) | `ops task link <task> <previous>` |
@@ -485,10 +551,7 @@ normal.
485
551
 
486
552
  `--json` output: `show` gives the C6 detail with `address` at the top
487
553
  level, and the present keys (`breadcrumb`, `orgSlug`, `url`). `create`,
488
- `split`, `merge`, and `continue` give the new ids and their addresses.
489
- `retype` gives `{taskId, address, type, typeVersion, stage, fields}` after
490
- the write (`null` for an absent value): the address follows the type
491
- (`task.dev/<id>` or `task/<id>`), and an old address still resolves. When
554
+ `split`, `merge`, and `continue` give the new ids and their addresses. When
492
555
  the read of an address after a write fails, the result keeps the new id with
493
556
  `address: null` (exit 0), and stderr names the read to run again: do not run
494
557
  the write again.
@@ -514,13 +577,6 @@ open). The attachments of a group show on each of its tasks.
514
577
  | `ops task group list [--open]` | `ops task group list --open` |
515
578
  | `ops task group delete <group>` — deletes the group and its members, not the tasks | `ops task group delete "Login fixes"` |
516
579
 
517
- ### task type
518
-
519
- | Command | Example |
520
- |---|---|
521
- | `ops task type list` — the active types | `ops task type list` |
522
- | `ops task type show <type>[@<version>] [--type-version <v>]` — the fields and the stages; with no version, the latest active version. The program owns `--version` (it prints the version of the CLI anywhere on the line), so a type version is `<type>@<version>` or `--type-version` | `ops task type show task.dev@0.1` |
523
-
524
580
  ### comment
525
581
 
526
582
  | Command | Example |
@@ -532,16 +588,16 @@ open). The attachments of a group show on each of its tasks.
532
588
  | `ops comment react <task> <commentId> <emoji>` | `ops comment react "Write docs" <commentId> 👍` |
533
589
  | `ops comment unreact <task> <commentId> <emoji>` | `ops comment unreact "Write docs" <commentId> 👍` |
534
590
  | `ops comment count <tasks...>` — the comment count of each task (`updates.counts`, at most 200 tasks, one call for each org); `--json`: `[{taskId, title, count}]` | `ops comment count <taskId> <taskId>` |
591
+ | `ops comment people` — `updates.people`: the people of the active org that a comment can mention, each with its `users` ID, name, handle, and actor ID (`user:<Clerk user ID>`) | `ops comment people --json` |
535
592
 
536
593
  ### backlog
537
594
 
538
595
  | Command | Example |
539
596
  |---|---|
540
- | `ops backlog show [--project <p>] [--all]` — the backlog section of the project (or of the org) by priority band, each task with its address; on a deployment of before the revamp, the former view by folder and band | `ops backlog show --project Alpha` |
597
+ | `ops backlog show [--project <p>] [--all]` — the backlog section of the project (or of the org) by priority band, each task with its address | `ops backlog show --project Alpha` |
541
598
 
542
- `ops folder list|create|rename|reorder|delete` (the backlog folders of
543
- before the revamp) stays hidden for old callers. The migration of the revamp
544
- turns each folder into a section.
599
+ The migration of the revamp turned each backlog folder into a section. The
600
+ hidden `ops folder` group is gone (0.1.0-dev.3).
545
601
 
546
602
  ### deadline
547
603
 
@@ -567,9 +623,11 @@ turns each folder into a section.
567
623
  A blocker says why work waits (Task 376 of the NUU Operations Flint): a
568
624
  title, who or what the work waits on, the next step, an owner, and one date,
569
625
  the next check-up. It links any number of records (project, deliverable,
570
- section, task, step). It gates no status. A blocker has no address: a
571
- `<blocker>` argument is an ID, or a title that matches one blocker of the org
572
- (two matches fail with `blocker_ambiguous`, exit 2, and list the IDs). An
626
+ section, task, step, deployment). It gates no status. A blocker has the id
627
+ address `@v1/<org>/blocker/<id>#ops` (`ops blocker show` prints it). A
628
+ `<blocker>` argument is an address (it names the org of the blocker), an ID,
629
+ or a title that matches one blocker of the org (two matches fail with
630
+ `blocker_ambiguous`, exit 2, and list the IDs). An
573
631
  open blocker is **due** when its check-up date is today or earlier in local
574
632
  time. The CLI sends local dates, also the default check-up date (tomorrow),
575
633
  as the web app does.
@@ -580,7 +638,7 @@ as the web app does.
580
638
  | `ops blocker show <blocker>` — the fields, each link with its path and status (or `broken`), and the log with the actor names | `ops blocker show "No API key"` |
581
639
  | `ops blocker create <title> [--check <date>] [--waiting-on <text>] [--next <text>] [--owner <member>] [--on <record>]...` — default check-up: tomorrow; default owner: you; an ID, URL, or address in `--on` names the org of the blocker | `ops blocker create "No API key" --check +3d --on <taskId>` |
582
640
  | `ops blocker update <blocker> [--title] [--waiting-on <text\|none>] [--next <text\|none>] [--owner <member\|none>]` | `ops blocker update <blockerId> --next "Call the vendor"` |
583
- | `ops blocker attach <blocker> <record>` — a record that is attached changes nothing | `ops blocker attach <blockerId> section:"Alpha › v1 › Backend"` |
641
+ | `ops blocker attach <blocker> <record>` — a record that is attached changes nothing; a record is a project, deliverable, section, task, step, or deployment (`deployment:<name>`) | `ops blocker attach <blockerId> section:"Alpha › v1 › Backend"` |
584
642
  | `ops blocker detach <blocker> <record>` — the ID of a broken link works too | `ops blocker detach <blockerId> <taskId>` |
585
643
  | `ops blocker delay <blocker> <date> [--note <text>]` — a new check-up date (counts as a delay); the same date with a note only logs the note; a closed blocker gives `blocker_closed` | `ops blocker delay <blockerId> +1w --note "Legal needs a week"` |
586
644
  | `ops blocker resolve <blocker> [--note <text>]` — the work can go on | `ops blocker resolve <blockerId> --note "The key came"` |
@@ -596,6 +654,39 @@ Codes from the service: `blocker_not_found`, `blocker_closed`,
596
654
  on dev data: on a deployment without them, the error says that they are not
597
655
  deployed yet.
598
656
 
657
+ ### review
658
+
659
+ The review queue (Task 402 of the NUU Operations Flint). A person or an
660
+ agent asks a person to review a task (`ops task review request`). The
661
+ request goes to the review queue of the reviewer. The request also loops
662
+ the reviewer into the task (`ops task loop`), unless the reviewer is the
663
+ owner. The reviewer approves the work, asks for changes, or dismisses the
664
+ request (`ops task review approve|changes|dismiss`). The requester can
665
+ cancel it (`ops task review cancel`). Your queue also lists the open
666
+ requests that you asked of other people. These verbs take the task, not
667
+ the request: the CLI reads the open requests of the task
668
+ (`reviews.forTask`) and takes the one where you are the reviewer (a
669
+ verdict) or the requester (a cancel). When no request matches, the verb is
670
+ a usage error (exit 2) and writes nothing. A note has at most 2000
671
+ characters; `--note -` reads stdin.
672
+
673
+ | Command | Example |
674
+ |---|---|
675
+ | `ops review list [--org <org>] [--state open\|all]` — `reviews.mine`: your queue; the queue is personal, so with no `--org` it lists every org of yours (the stored default org does not apply); `--org` keeps one org; one line for each open request, oldest first: the age, the task title, the home label (or `unplaced`), the requester, and the task address; when the queue has more than 200 open requests, a line says so; then, when you asked other people for reviews that are still open, the section `you asked for (open, oldest first)` gives one line for each, with the reviewer (`to <name>`) in place of the requester; `--state all` adds the newest closed requests (at most 25) with the verdict, newest first; `--json`: `{open, closed, sent, truncated}` as the service gives it (`sent` is the open requests that you asked of other people; each entry is a request with `task` and `href`; a deployment before `sent` has no field) | `ops review list --state all` |
676
+
677
+ Codes from the service: `review_not_found`, `review_not_open` (the request
678
+ is closed already), `review_not_reviewer` (only the reviewer gives the
679
+ verdict), `review_not_requester` (only the requester cancels; the reviewer
680
+ dismisses), `review_no_reviewer` (a team task with no owner: pass `--to`),
681
+ `review_task_done` (reopen the task first), `review_task_gone`,
682
+ `review_note_too_long`, and `assignee_not_member` (the reviewer). `ops task
683
+ loop add` can refuse with `loop_full` (a task loops in at most 50 people:
684
+ take a person out first), `assignee_not_found`, or `assignee_not_member`
685
+ (the person). The client code `review_ambiguous` is a cancel that matches
686
+ two requests. The ops are new on dev data: on a deployment without them,
687
+ the error says that they are not deployed yet, and `ops task show` gives no
688
+ review requests.
689
+
599
690
  ### plan
600
691
 
601
692
  | Command | Example |
@@ -673,6 +764,121 @@ zero-length move writes nothing. A downstream step with no stored position
673
764
  stays where it is and is reported as `kept` (the UI moves it from a
674
765
  fallback layout that the CLI does not compute).
675
766
 
767
+ ### Trident: stream, workspace, and the board verbs of step
768
+
769
+ The Trident board ((Task) 399 of the NUU Operations Flint) has lanes. A
770
+ lane is a **stream**. A stream is a second home for the same step rows as a
771
+ plan, so the `ops step` verbs work on a step of a plan and on a step of a
772
+ stream. Each step has a **zone**: `planned`, `active`, or `done`. A
773
+ **goal** is a planned step on the **shore** (above the planned steps). A
774
+ **parked** step is off the board (in the Parked menu) and keeps its place,
775
+ state, links, and substream. A **substream** is a named box of steps in one
776
+ stream. A **cross-stream link** joins a step to a step of another stream; it
777
+ gates nothing. The **attachments** of a stream are the projects,
778
+ deliverables, and sections whose open tasks show in the side panel of its
779
+ lane. A **workspace** is a named set of streams of one person: no other
780
+ member sees it.
781
+
782
+ References: a stream takes an ID, a Trident URL (`/<org>/trident?stream=<id>`),
783
+ or a title. A step takes an ID, a URL, or an address; a title or a short ID
784
+ needs `--stream <stream>` (or `--plan <plan>` for a step of a plan). A
785
+ substream takes an ID; a title needs `--stream <stream>`. A workspace takes
786
+ an ID or a title. A step title matches the title that the board shows: the
787
+ title of the one linked record of the step, else its own title.
788
+
789
+ These `ops step` verbs take a stream step and `--stream <stream>`:
790
+ `update`, `assign`, `requires set|add|remove`, `links set|add|remove`,
791
+ `delete`, `split`, `split-beside`, `state`, `done-next`, `move-links`,
792
+ `remove-links`, and `place`. Two do not: `ops step add` adds a step to a
793
+ plan only (use `ops stream step add` for a stream), and `ops step move` is
794
+ for the plan canvas only (a stream step refuses with `node_not_in_plan`;
795
+ use `ops step place`).
796
+
797
+ **Recovery.** A removal that an undo takes back (`stream delete`, `stream
798
+ detach`, `stream step cross remove`, `stream substream ungroup`, `stream
799
+ drawing remove`, `step remove-links`) prints an `undo:` line: the guarded
800
+ undo of the gesture that it made (`ops stream undo <id> --expect <seq> --org
801
+ <org>`, or `ops plan undo` for a plan step), with its label and the history
802
+ command to read first. The CLI reads the head of the stack before and after
803
+ the write: when the head did not move, the command made no gesture (for
804
+ example a drawing ID that the lane does not have, or a link that is gone),
805
+ and it prints `no change:` and no undo. `stream history`, `stream undo`, and
806
+ `stream redo` take the ID or the URL of a deleted stream. `workspace delete`
807
+ has no undo: it prints the `workspace create` command (shell-quoted, with
808
+ `--org`) that makes the workspace again. `plan show` and `stream show` print
809
+ the title that a step title matches.
810
+
811
+ | Command | Example |
812
+ |---|---|
813
+ | `ops stream list [--state active\|paused\|done\|all]` | `ops stream list --state active` |
814
+ | `ops stream show <stream>` — the attachments, the substreams, and the steps by zone (shore, planned, active, done, parked); `--json` prints the raw `streams.get` result | `ops stream show Backend` |
815
+ | `ops stream create <title> [--description]` | `ops stream create Backend` |
816
+ | `ops stream update <stream> [--title] [--description] [--state active\|paused\|done]` | `ops stream update Backend --state paused` |
817
+ | `ops stream delete <stream>` — soft: `stream undo` brings the lane back with its steps | `ops stream delete Backend` |
818
+ | `ops stream attach <stream> <places...>` — `project:<ref>`, `deliverable:<ref>`, `section:<ref>`, an ID, or a URL | `ops stream attach Backend section:"Alpha › v1 › Backend"` |
819
+ | `ops stream detach <stream> [places...] [--all]` | `ops stream detach Backend --all` |
820
+ | `ops stream size <stream> [--width <n\|default>] [--shore-height <n\|default>]` — board units | `ops stream size Backend --width 640` |
821
+ | `ops stream history <stream>` — your undo and redo stacks in the scope `stream:<id>`; a deleted stream by its ID or URL | `ops stream history Backend` |
822
+ | `ops stream undo\|redo <stream> (--expect <seq> \| --dry-run)` — the undo safety of `plan undo`; a deleted stream by its ID or URL | `ops stream undo <streamId> --expect 12` |
823
+ | `ops stream step add <stream> <title> [--after <step>]... [--state planned\|active\|done] [--shore] [--description] [--assign <member>]` | `ops stream step add Backend "Ship the API" --shore` |
824
+ | `ops stream step add-task <stream> <task> [--state planned\|active\|done] [--shore]` — a step that follows the task; `--state` moves the task | `ops stream step add-task Backend "Fix login"` |
825
+ | `ops stream step create-task <step> --to section:<ref>\|project:<ref> [--stream]` — `project:` is its backlog; the step then follows the task | `ops stream step create-task <stepId> --to project:Alpha` |
826
+ | `ops stream step move <steps...> --to <stream> [--stream]` — a link to a step that stays becomes a cross-stream link | `ops stream step move <stepId> --to Frontend` |
827
+ | `ops stream step park\|unpark <steps...> [--stream]` | `ops stream step park Build --stream Backend` |
828
+ | `ops stream step shore\|unshore <steps...> [--stream]` — planned steps only | `ops stream step shore <stepId>` |
829
+ | `ops stream step cross set <step> [steps...] [--stream]` — replace the cross-stream links (steps of other streams, by ID); no steps removes them | `ops stream step cross set <stepId>` |
830
+ | `ops stream step cross add\|remove <step> <steps...> [--stream]` — steps of other streams, by ID | `ops stream step cross add <stepId> <otherStepId>` |
831
+ | `ops stream step substream <steps...> --to <substream\|none> [--stream]` | `ops stream step substream <stepId> --to Auth` |
832
+ | `ops stream substream create <stream> <title> [--step <step>]... [--box <x,y,width,height>]` | `ops stream substream create Backend Auth --step <stepId>` |
833
+ | `ops stream substream rename <substream> <title> [--stream]` | `ops stream substream rename Auth Login --stream Backend` |
834
+ | `ops stream substream park\|unpark <substream> [--stream]` — with its steps | `ops stream substream park <substreamId>` |
835
+ | `ops stream substream place <substream> --box <x,y,width,height> [--stream]` | `ops stream substream place <substreamId> --box 0,0,400,240` |
836
+ | `ops stream substream ungroup <substream> [--stream]` — the steps stay, in no substream | `ops stream substream ungroup Auth --stream Backend` |
837
+ | `ops stream drawing list <stream>` | `ops stream drawing list Backend` |
838
+ | `ops stream drawing add <stream> --kind line\|free\|text --points <x1,y1,...> [--text]` — a line has 2 points, a free line 2 to 1000, a text 1 | `ops stream drawing add Backend --kind text --points 40,80 --text "Freeze"` |
839
+ | `ops stream drawing text <stream> <drawing> <text>` — empty text removes it | `ops stream drawing text Backend <drawingId> "Freeze Friday"` |
840
+ | `ops stream drawing move <stream> <drawings...> [--dx] [--dy]` | `ops stream drawing move Backend <drawingId> --dy 40` |
841
+ | `ops stream drawing remove <stream> <drawings...>` | `ops stream drawing remove Backend <drawingId>` |
842
+ | `ops workspace list` — in their order; the first is ⌃1 | `ops workspace list` |
843
+ | `ops workspace show <workspace>` | `ops workspace show "Launch week"` |
844
+ | `ops workspace create [title] [--stream <stream>]...` | `ops workspace create "Launch week" --stream Backend` |
845
+ | `ops workspace update <workspace> --title <title>` | `ops workspace update "Launch week" --title Launch` |
846
+ | `ops workspace streams set <workspace> [streams...]` — replace the streams, in this order; no streams empties it | `ops workspace streams set Launch Backend Design` |
847
+ | `ops workspace streams add\|remove <workspace> <streams...>` | `ops workspace streams add Launch Design` |
848
+ | `ops workspace reorder <workspaces...>` — each of your workspaces once | `ops workspace reorder Launch "Workspace 1"` |
849
+ | `ops workspace delete <workspace>` — the streams stay; no undo (it prints the `workspace create` command); your last workspace refuses (`workspace_last`) | `ops workspace delete Launch` |
850
+ | `ops step split <step> [--before] [--title] [--start] [--shore] [--assign]` — a new step joined to it; default: after it | `ops step split Build --stream Backend --title Review` |
851
+ | `ops step split-beside <step> [--title] [--assign]` — a new step beside it, with all its links and its state | `ops step split-beside <stepId>` |
852
+ | `ops step state <planned\|active\|done> <steps...> [--shore\|--no-shore]` — put back, start, or finish; a step with one task moves the task | `ops step state active <stepId>` |
853
+ | `ops step done-next <step> [--title] [--assign]` — finish it and start the next step, one undo | `ops step done-next <stepId> --title Deploy` |
854
+ | `ops step move-links --link <from>:<to>... --end from\|to --target <step>` — the links meet at one step; one undo | `ops step move-links --link <a>:<b> --end to --target <c>` |
855
+ | `ops step remove-links <from>:<to>...` — one undo | `ops step remove-links Build:Test --stream Backend` |
856
+ | `ops step place <step> --x <x> --y <y>` — the place on the lane, in board units; the server keeps it in the zone of its state | `ops step place <stepId> --x 120 --y 40` |
857
+
858
+ `ops stream show` prints:
859
+
860
+ ```
861
+ stream <id> Backend [active] 3 steps
862
+ The API work
863
+ attachments: project:<id> Alpha
864
+ size: width default shore 120 drawings 1
865
+ substreams:
866
+ substream <id> Auth 1 steps
867
+ shore (goals): 1
868
+ step <id> Ship the API short k57b
869
+ planned: 0
870
+ active: 1
871
+ step <id> Build short k57c cross <id> in Auth
872
+ done: 0
873
+ parked: 1
874
+ step <id> Old idea short k57d after k57c is task:<id> Fix login
875
+ https://ops.nuucognition.com/<org>/trident?stream=<id>
876
+ ```
877
+
878
+ A deployment without an op of this section answers `agent_unknown_operation`;
879
+ the CLI then says that the op is not deployed there yet and that (Task) 399
880
+ brings it.
881
+
676
882
  ### notify
677
883
 
678
884
  | Command | Example |
@@ -684,6 +890,8 @@ fallback layout that the CLI does not compute).
684
890
  | `ops notify read-all [--this-org]` | `ops notify read-all` |
685
891
  | `ops notify dismiss <id>` | `ops notify dismiss <notificationId>` |
686
892
  | `ops notify read-key <key>` — mark every notification with one collapse key read (`notifications.markKeyRead`) | `ops notify read-key <key>` |
893
+ | `ops notify read-prefix <prefix>` — mark every notification whose collapse key starts with the prefix read (`notifications.markReadByKeyPrefix`): one thing with many keys, for example `thread:<id>:`; an empty prefix is a usage error | `ops notify read-prefix thread:<id>:` |
894
+ | `ops notify dismiss-all [--this-org]` — dismiss every notification (`notifications.dismissAll`): it deletes them, and there is no undo; only when the operator asks | `ops notify dismiss-all --this-org` |
687
895
  | `ops notify prefs list` | `ops notify prefs list` |
688
896
  | `ops notify prefs set <category> on\|off [--channel <c>]` — categories: tasks, deadlines, plans, updates | `ops notify prefs set plans off --channel push` |
689
897
 
@@ -692,14 +900,14 @@ fallback layout that the CLI does not compute).
692
900
  | Command | Example |
693
901
  |---|---|
694
902
  | `ops activity list [--on <id\|url> \| --scope <scope>] [--mine] [--action <a>]... [--text <t>] [--limit n] [--cursor c]` — the Waybill feed of the org, or of one scope | `ops activity list --on <planId> --limit 10` |
695
- | `ops activity row <record> [--limit n] [--cursor c]` — the Waybill history of one row (`waybill.rowHistory`): each event that changed the record, newest first, with actor names. A record is an ID, an address, or a URL of a project, deliverable, section, task, backlog folder, plan, step, deadline, day label, deployment, task group, blocker, or notepad | `ops activity row <deadlineId> --json` |
903
+ | `ops activity row <record> [--limit n] [--cursor c]` — the Waybill history of one row (`waybill.rowHistory`): each event that changed the record, newest first, with actor names. A record is an ID, an address, or a URL of a project, deliverable, section, task, backlog folder, plan, step, deadline, deployment, task group, blocker, or notepad | `ops activity row <deadlineId> --json` |
696
904
  | `ops activity facets [--on <id\|url> \| --scope <scope>] [--limit n]` — the counts of the actors (with names), actions, tables, kinds, and scopes of the newest events (`waybill.facets`); use them as filters of `activity list` | `ops activity facets --on <planId>` |
697
905
 
698
906
  Scopes are `org:<slug>`, `project:<id>`, `deliverable:<id>`,
699
907
  `subsection:<id>`, `backlog:<projectId>`, `backlog:org:<slug>`, and
700
908
  `plan:<id>`. `org:me` and `backlog:org:me` name the personal workspace.
701
909
  `--on` maps a record to its scope (a task to its section or backlog, a
702
- step to its plan, a deadline, day label, or deployment to `org:<slug>`).
910
+ step to its plan, a deadline or deployment to `org:<slug>`).
703
911
 
704
912
  ### history, undo, redo (any scope)
705
913
 
@@ -720,21 +928,13 @@ is in `project:<projectId>`.
720
928
  | `ops favourite list` — your favourites in the active org, in star order (`home.favourites`) | `ops favourite list --json` |
721
929
  | `ops favourite add <record> [--kind project\|deliverable\|section]` — star a record; a title needs `--kind`, an ID or URL does not | `ops favourite add <deliverableId>` |
722
930
  | `ops favourite remove <record> [--kind]` — remove the star; the ID of a deleted record works too | `ops favourite remove <recordId>` |
931
+ | `ops favourite reorder <records...>` — `home.reorderFavourites`: a new order of your favourites (the order of the Pinned favourites): the given record IDs first, in the given order, then the others; a record that is not a favourite is `favourite_not_found` | `ops favourite reorder <recordId> <recordId>` |
723
932
 
724
933
  A record of another kind gives `wrong_kind`. Both ops are idempotent. More
725
934
  than 100 favourites in one org gives `favourites_full`. The ops come from
726
935
  (Task) 316: on a deployment without them, the error says that they are not
727
936
  deployed yet.
728
937
 
729
- ### day-label
730
-
731
- | Command | Example |
732
- |---|---|
733
- | `ops day-label list` | `ops day-label list` |
734
- | `ops day-label create <label> --start <date> [--end <date>] [--color water\|fire\|earth\|air\|sun\|moon\|muted]` | `ops day-label create Offsite --start 2026-10-12 --end 2026-10-14 --color sun` |
735
- | `ops day-label update <day-label> [--label] [--start] [--end] [--color]` | `ops day-label update Offsite --color moon` |
736
- | `ops day-label delete <day-label>` | `ops day-label delete Offsite` |
737
-
738
938
  ### home
739
939
 
740
940
  The homepage reads of the active org. They come from fleet
@@ -756,13 +956,20 @@ says that they are not deployed yet.
756
956
  | `ops notepad show <notepad> [--md]` — the blocks, the mentioned tasks, the scope, and the rev | `ops notepad show "Standup notes"` |
757
957
  | `ops notepad create <title> [--general] [--body <md\|->] [--section s]` — personal unless `--general` | `ops notepad create Ideas --body - < ideas.md` |
758
958
  | `ops notepad rename <notepad> <title>` | `ops notepad rename <notepadId> "Weekly standup"` |
759
- | `ops notepad scope <notepad> (<section> \| --clear)` — link the notepad to one section | `ops notepad scope <notepadId> Backend` |
959
+ | `ops notepad section <notepad> (<section> \| none \| --clear)` — link the notepad to one section, or clear the link (`notepads.setSection`); `scope` is its former name | `ops notepad section <notepadId> Backend` |
760
960
  | `ops notepad share <notepad> (--general \| --personal)` — share it with the org or make it personal (`notepads.setScope`); only its owner can, and a notepad of the personal workspace stays personal | `ops notepad share <notepadId> --general` |
761
961
  | `ops notepad mentions <task>` — the notepads that hold a chip of the task (`notepads.mentioning`) | `ops notepad mentions <taskId>` |
762
962
  | `ops notepad append <notepad> [<md\|->] [--task <task>]` | `ops notepad append <notepadId> --task <taskId>` |
763
963
  | `ops notepad write <notepad> <md\|-> (--expected-rev n \| --force)` | `ops notepad write <notepadId> - --expected-rev 3 < notes.md` |
764
964
  | `ops notepad to-task <notepad> <text> [--section s \| --project p [--folder f] \| --backlog] [--title t] [--keep]` — make a task from a text; a task chip replaces the text | `ops notepad to-task <notepadId> "Plan the demo"` |
765
965
  | `ops notepad move-to-task <notepad> <task> <text> [--copy]` — move a text to the end of a task description | `ops notepad move-to-task <notepadId> <taskId> "The budget is 4k."` |
966
+ | `ops notepad block list <notepad>` — the ID and the text of each block, in order, and the rev | `ops notepad block list <notepadId>` |
967
+ | `ops notepad block add <notepad> <text\|-> [--after <block>]` — `notepads.addBlock`: a new text block after a block, or at the top; `--json` gives the new `blockId` | `ops notepad block add <notepadId> "Call Sam" --after <blockId>` |
968
+ | `ops notepad block edit <notepad> <block> <text\|-> [--expect <text>]` — `notepads.updateBlock`: replace the text of one block when its text is still the text that the CLI read (or `--expect`), else `notepad_text_conflict`; an empty text removes the block | `ops notepad block edit <notepadId> <blockId> "Call Sam on Monday"` |
969
+ | `ops notepad block remove <notepad> <block>` — `notepads.removeBlock`; notepad blocks have no Waybill scope, so the text prints the undo: `ops call notepads.setBlocks '<the blocks of before, at the new rev>' --mutation` (it works while the notepad is unchanged); `--json` has `previousBlocks`, `rev`, and `undo` | `ops notepad block remove <notepadId> <blockId>` |
970
+ | `ops notepad block move <notepad> <block> (--after <block> \| --top)` — `notepads.moveBlock` | `ops notepad block move <notepadId> <blockId> --top` |
971
+ | `ops notepad make-task <notepad> <text\|-> [--title t]` — `notepads.makeTask`: a task from a line (the first line is the title), in the section of the notepad, else the backlog of the org; the notepad text does not change; the text prints the undo command, with the state quoted for the shell and `--org` of the notepad | `ops notepad make-task <notepadId> "Call Sam about the beta"` |
972
+ | `ops notepad undo-make-task <notepad> <task> --expect <state>` — `notepads.undoMakeTask`: delete the task when it is unchanged (`taskState` of the make-task result), else `notepad_undo_conflict` | `ops notepad undo-make-task <notepadId> <taskId> --expect '<state>'` |
766
973
  | `ops notepad delete <notepad>` — a soft delete | `ops notepad delete <notepadId>` |
767
974
 
768
975
  A `<notepad>` is a web URL (`.../notepads/<id>`), an Ops address
@@ -770,9 +977,8 @@ A `<notepad>` is a web URL (`.../notepads/<id>`), an Ops address
770
977
  the owner of a shared (general) notepad can delete it; make it personal
771
978
  first with `ops notepad share <notepad> --personal`, or ask the owner.
772
979
 
773
- The block ops (`notepads.addBlock`, `updateBlock`, `removeBlock`,
774
- `moveBlock`, `setBlocks`, `insertTask`, `makeTask`, `undoMakeTask`,
775
- `undoGesture`) have no typed command: use `ops call <op> '<json>'`.
980
+ The ops `notepads.setBlocks`, `insertTask`, and `undoGesture` have no typed
981
+ command: use `ops call <op> '<json>'`.
776
982
 
777
983
  ### day
778
984
 
@@ -784,11 +990,57 @@ or an offset like `+1d`.
784
990
  |---|---|
785
991
  | `ops day show [date]` — the done count and the tasks in plan order | `ops day show tomorrow` |
786
992
  | `ops day add <task...> [--date d] [--top] [--source-org o]` | `ops day add <taskId> --date tomorrow` |
993
+ | `ops day new <title> [--date d] [--top]` — create a task and add it to the plan of the day: in the backlog of your personal workspace, or of the org of `--org` | `ops day new "Call Sam" --top` |
787
994
  | `ops day remove <task...> [--date d]` | `ops day remove <taskId>` |
788
995
  | `ops day reorder <task> (--before t \| --after t \| --top \| --bottom) [--date d]` | `ops day reorder <taskId> --top` |
789
996
  | `ops day carry [--date d]` — add the unfinished tasks of the 7 days before the date | `ops day carry` |
790
997
  | `ops day candidates [--date d]` — the open tasks that you can plan (`dayPlans.candidates`): your own tasks of every org and the tasks of your personal workspace; `--date` marks the planned ones | `ops day candidates --date today --json` |
791
998
 
999
+ ### share
1000
+
1001
+ A share gives another org, or one person, access to a project, a
1002
+ deliverable, or a task, as `reader` or `editor`. A share of a project or a
1003
+ deliverable reaches the records under it. Only a person with `admin` on the
1004
+ record manages its shares. A `<record>` is `task:<ref>`, `project:<ref>`,
1005
+ `deliverable:<ref>`, an ID, an address, or a web URL.
1006
+
1007
+ | Command | Example |
1008
+ |---|---|
1009
+ | `ops share list <record>` — `shares.forEntity`: who reaches the record and why (the owner org, the creator, the org, each share), each line with its permission, a share with its share ID, an inherited line with the record it comes from; your level, and if you can manage the shares | `ops share list task:<taskId>` |
1010
+ | `ops share access <record>` — `shares.viewerAccess`: how you reach the record (`member` or `share`), your level, and if you can manage its shares | `ops share access project:Alpha` |
1011
+ | `ops share add <record> --to org:<slug>\|person:<user ID> [--permission reader\|editor]` — `shares.share` (default `reader`); a share to the same org or person again changes its permission; a person is the Clerk user ID `user_…` (`ops people search` and `ops comment people` show it); `--json`: `{kind, id, subject, shareId, created, changed, permission}` | `ops share add task:<taskId> --to org:acme` |
1012
+ | `ops share permission <share> reader\|editor` — `shares.setPermission` | `ops share permission <shareId> editor` |
1013
+ | `ops share remove <share> [--record <record>]` — `shares.unshare`; it sends no notice, and there is no undo (shares have no Waybill scope); with `--record`, the CLI reads the subject and the permission of the share first and prints the `share add` command that gives the access back (`--json`: `restore`) | `ops share remove <shareId> --record task:<taskId>` |
1014
+ | `ops share with` — `shares.sharedWith`: the records that other orgs shared with the active workspace, newest first (`--org` picks the workspace; the personal workspace gives the shares to you) | `ops share with --org me` |
1015
+
1016
+ ### calendar and event
1017
+
1018
+ The calendars of a workspace and their events (Task 385). A calendar of the
1019
+ personal workspace is personal; a calendar of a team workspace belongs to
1020
+ its org. Each person has a default calendar for new events. An event is
1021
+ timed (a start and an end in a time zone) or all day (whole days). **A
1022
+ period is an all-day event of more than one day** (`--date` and `--until`).
1023
+ An event can repeat; an edit or a delete of one occurrence takes
1024
+ `--occurrence <day>` and `--scope this|following|all`. An `<event>` is an
1025
+ event ID, or the occurrence ID `<eventId>:<day>` of `event list`. A
1026
+ `<calendar>` is an ID or a name.
1027
+
1028
+ | Command | Example |
1029
+ |---|---|
1030
+ | `ops calendar list` — the calendars of the active workspace, oldest first; `default` marks your default calendar (`calendar.listCalendars`) | `ops calendar list` |
1031
+ | `ops calendar show <calendar>` | `ops calendar show Events` |
1032
+ | `ops calendar create <name> [--color water\|fire\|earth\|air\|sun\|moon\|muted]` — `calendar.createCalendar` (default `water`) | `ops calendar create "Releases" --color fire` |
1033
+ | `ops calendar update <calendar> [--name] [--color]` — `calendar.updateCalendar` | `ops calendar update Releases --color sun` |
1034
+ | `ops calendar delete <calendar> --yes` — `calendar.removeCalendar`: it deletes each event of the calendar; refuses without `--yes`; the calendars and the events are in the Waybill scope of the org, so the text prints the undo: `ops history --scope org:<slug>`, then `ops undo --scope org:<slug> --expect <seq>` | `ops calendar delete Releases --yes` |
1035
+ | `ops calendar default <calendar>` — `calendar.setDefaultCalendar`: your new events go there | `ops calendar default Releases` |
1036
+ | `ops event list [--from <day>] [--to <day> \| --days <n>] [--calendar <c>] [--tz <zone>]` — `calendar.listEvents`: the occurrences in a window of 1 to 100 days (default: 7 days from today), each repeat expanded; `--to` is not included | `ops event list --days 14` |
1037
+ | `ops event show <event>` — `calendar.getEvent`: the fields, the repeat, the people, the guests, and the attached tasks and deadlines; `<eventId>:<day>` shows that occurrence with its own fields (the time, the calendar, the place, the notes, the link, the colour, the people, and the guests, read from `calendar.listEvents`), and the repeat rule and the attachments of the series; a day that is not an occurrence, or a day of an event that does not repeat, is `event_invalid_key` (exit 2) | `ops event show <eventId>` |
1038
+ | `ops event create <title> (--start <datetime> (--end <datetime> \| --duration <30m\|1h…>) [--tz <zone>] \| --date <day> [--until <day>]) [--calendar] [--repeat daily\|weekly\|monthly\|yearly [--every n] [--weekdays mon,wed] [--month-by day\|weekday] [--count n \| --repeat-until <day>]] [--notes] [--place] [--link <url>] [--color] [--person <member>]... [--guest "Name <email>"]... [--attach task:<ref>\|deadline:<ref>]...` — `calendar.createEvent`; a datetime is `YYYY-MM-DDTHH:mm` in `--tz` (default: the zone of this machine), `tomorrow 09:00` (a day word is a date in `--tz`, also for `--repeat-until`), or an ISO time with an offset (a day that does not exist is a usage error); at a clock change the CLI reads a time as the service does: a time in a gap moves forward by the change, and a time in an overlap is the first one | `ops event create "Offsite" --date 2026-11-16 --until 2026-11-18` |
1039
+ | `ops event update <event> [--title] [--start --end\|--duration --tz \| --date --until] [--calendar] [--repeat … \| --repeat none] [--notes] [--place] [--link] [--color] [--person]... [--guest]... [--no-guests] [--occurrence <day>] [--scope this\|following\|all]` — `calendar.updateEvent`: only the given fields; `none` clears `--notes`, `--place`, `--link`, `--color`, and `--repeat`; `--person` and `--guest` replace the lists; a day word of `--repeat-until` is a date in the zone of the event (of the new time, else of the stored event; an all-day event: the date of this machine). It takes no `--attach`: use `ops event attach` and `ops event detach` | `ops event update <eventId> --place none` |
1040
+ | `ops event delete <event> [--occurrence <day>] [--scope this\|following\|all]` — `calendar.removeEvent`; the text prints the undo in the Waybill scope of the org (`ops history --scope org:<slug>`, then `ops undo --scope org:<slug> --expect <seq>`) | `ops event delete <eventId>:2026-11-20 --scope this` |
1041
+ | `ops event attach <event> task:<ref>\|deadline:<ref>` / `ops event detach <event> …` — `calendar.attach`, `calendar.detach` (of a series: all its occurrences) | `ops event attach <eventId> task:<taskId>` |
1042
+ | `ops event for task:<ref>\|deadline:<ref>` — `calendar.eventsForRecord`: the events that a task or a deadline is attached to, soonest first | `ops event for task:<taskId>` |
1043
+
792
1044
  ### deployment
793
1045
 
794
1046
  A deployment is abstract: a name, a free version label, a status (`live`,
@@ -814,8 +1066,9 @@ optional check schedule. A live deployment has an inbox section.
814
1066
  The program owns `--version` (it prints the version of the CLI anywhere on
815
1067
  the line), so the label is `--version-label`. The version chain of before the
816
1068
  revamp is deprecated: `--previous`, `--url`, `--identifier`, `--not-owned`,
817
- `list --chains`, `end-chain`, and `reopen-chain` stay hidden, print a note,
818
- and work while the service keeps them.
1069
+ and `list --chains` stay hidden, print a note, and work while the service
1070
+ keeps them. The hidden `end-chain` and `reopen-chain` are gone
1071
+ (0.1.0-dev.3).
819
1072
 
820
1073
  ### query
821
1074
 
@@ -846,9 +1099,9 @@ The rules:
846
1099
  `subsectionId`. Run `ops query describe <table>` first.
847
1100
  - Each row belongs to the org. A table that holds a secret, a token, a
848
1101
  credential, a push subscription, or a Strike sync internal is not
849
- readable, and neither is a table with no org field (`users`, `day_plans`,
850
- `task_types`). Use the typed commands for them (`ops member list`,
851
- `ops day`, `ops task type`).
1102
+ readable, and neither is a table with no org field (`users`,
1103
+ `day_plans`). Use the typed commands for them (`ops member list`,
1104
+ `ops day`).
852
1105
  - A page holds at most `--limit` rows (default 50, max 500), and one call
853
1106
  reads at most 5000 documents. When the output ends with `more: --cursor
854
1107
  <c>`, give that cursor to read the next page, or use `--all`. `--all` and