@nuucognition/ops-cli 0.0.0-stage → 0.1.0-dev.1

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
@@ -1,3 +1,915 @@
1
- # Temporary Holding Version
1
+ # ops — the NUU Operations CLI for agents
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `ops` gives an agent full access to the NUU Operations web app through the
4
+ **agent gateway** ((Task) 297). It uses the nuu CLI credential. The gateway
5
+ sends each op to the same public Convex function that the web app calls, so
6
+ access checks, validators, dependency gates, the Waybill log, and
7
+ notifications are the same as in the UI.
8
+
9
+ The CLI is agent-first:
10
+
11
+ - Every command is `ops <noun> <verb>`.
12
+ - Every command takes `--json`. Then stdout holds exactly one JSON document.
13
+ - Text output is short and stable, and always shows IDs.
14
+ - Errors go to stderr as `error <code>: <message>`, then indented hints that
15
+ name the next step, with a non-zero exit code. With `--json`, stderr holds
16
+ exactly one JSON document `{"error": {code, message, exitCode, hints, ...}}`
17
+ instead, and stdout stays empty.
18
+ - A usage error names the help command (`ops <noun> <verb> --help`). An extra
19
+ argument (for example an unquoted comment body) is a usage error. A command
20
+ group with no subcommand (`ops task`) is a usage error that lists the
21
+ subcommands; it prints no help text on stderr.
22
+ - With `DEBUG=1`, the stack trace follows the error lines; with `--json`, it is
23
+ the `stack` field of the JSON error document.
24
+ - An op that a deployment does not have yet (for example a new op on dev only)
25
+ fails with `agent_unknown_operation`, and the message says that the op is
26
+ not deployed there yet.
27
+ - Wherever a command needs a record, it takes an ID, an address, a web URL,
28
+ a title, or a path (`"Alpha › v1 › Backend"`).
29
+ - Every command prints the address of each thing that it shows or makes
30
+ (`@v1/<org>/<kind>/<key>#ops`). An agent keeps the address, not the title.
31
+
32
+ ## The model of Ops
33
+
34
+ The revamp of ops-command-1021 (2026-10-21) gives Ops this model:
35
+
36
+ ```
37
+ org
38
+ └── project (hidden context)
39
+ ├── section (role backlog) the backlog of the project
40
+ ├── section a list of tasks with no deliverable yet
41
+ ├── deliverable done when a person marks it done
42
+ │ └── section ─ task ─ subtask ─ subtask … (a tree of at most 6 levels)
43
+ └── deployment (live, paused, retired; a free version label)
44
+ └── section (role inbox) the tasks that come up while it runs
45
+ section (role backlog) of the org the master backlog
46
+ ```
47
+
48
+ - A **section** holds tasks. It has one home: a deliverable, a project, a
49
+ deployment, or the org. A section is done when all its root tasks are
50
+ done. The former word is **subsection**: `ops subsection …` and
51
+ `--subsection` still work, hidden.
52
+ - **No closer and no "Delivered" task.** `ops deliverable done|reopen`.
53
+ - **The backlog is a section** (role `backlog`). The backlog folders of
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
57
+ count (`1/3 subtasks`). A move takes the subtree. A delete of a task with
58
+ subtasks needs `--with-subtasks`.
59
+ - **Deployments are abstract.** A live deployment has an **inbox**. Inbox
60
+ tasks have a tag: `feedback`, `hotfix`, `ongoing`, or `recurring`. A check
61
+ schedule (`ops deployment schedule`) makes a task "Check <deployment>" in
62
+ the inbox each period. `ops deployment start-deliverable` moves inbox tasks
63
+ into a new deliverable that comes from the deployment. The version chain is
64
+ deprecated.
65
+ - **Hidden context.** Projects, deliverables, sections, and deployments have
66
+ a Markdown context (`ops <kind> context set|show`) that the page does not
67
+ show. Search and the route decision read it.
68
+ - **The map and the route.** `ops headers` prints every place of the org
69
+ with its address. `ops route "<text>"` gives the ranked places for a text
70
+ (with the decision model Jev when Smart suggestions are on, else the text
71
+ match). `ops task create --route` uses the top place.
72
+
73
+ ## A first session for an agent
74
+
75
+ ```bash
76
+ ops whoami # the user, the data mode, the active org
77
+ ops headers --project Alpha # the places of a project, with addresses
78
+ ops task context <task> # read a task before you work on it
79
+ ops route "Fix the login bug on Safari" # where a new task belongs
80
+ ops task create "Fix the login bug on Safari" --route
81
+ ops task create "Write the tests" --parent <task> # a subtask
82
+ ops task move <task> --to section:"Alpha › v1 › Backend"
83
+ ops task done <task>
84
+ ops deployment inbox Production --open # what came up while it runs
85
+ ops deliverable done "Alpha › v1 launch" # a person decides; ask first
86
+ ```
87
+
88
+ Keep the address that a command prints (`@v1/<org>/<kind>/<key>#ops`) and
89
+ name the record by it in the next command: an address never needs a scope
90
+ and never matches two records.
91
+
92
+ ## Install
93
+
94
+ ### From npm (the published package)
95
+
96
+ ```bash
97
+ npm install -g @nuucognition/ops-cli # the `ops` command: the prod build, prod data
98
+ ops --version # the version of the package
99
+ nuu login # or `flint login`, or `ops login`: one NUU sign-in for all three
100
+ ops whoami
101
+ ```
102
+
103
+ The package needs Node.js 24 or newer. On an older Node.js, `ops` prints one
104
+ refusal (`error node_too_old`) and exits 1. The package has one command,
105
+ `ops`. It is the prod build, so it always uses prod data (see Modes). The
106
+ bundle (`dist/index.js`) holds every package that the CLI uses, so the
107
+ package has no runtime dependency. An installed package has no source, so
108
+ the launcher runs no check against a source checkout. `RELEASE.md` tells how
109
+ to publish a new version.
110
+
111
+ ### From the monorepo (ndv)
112
+
113
+ The package is `@nuucognition/ops-cli` in `apps/cli-ops`. ndv builds and
114
+ links it:
115
+
116
+ ```bash
117
+ ndv scan
118
+ ndv use ops dev # builds dist-dev/ and links ~/.nuucognition/ndv/bin/ops -> bin/ops.js
119
+ ndv build ops prod # builds dist/ without a change to the active link
120
+ ```
121
+
122
+ ndv names its link after the tool (`ops`) in both profiles. After
123
+ `ndv use ops dev`, the `ops` command on PATH runs the **dev build**. After
124
+ `ndv use ops prod`, it runs the prod build. Inside the monorepo, pnpm also
125
+ links both bins (`ops` → `bin/ops-prod.js`, `ops-dev` → `bin/ops.js`). Do not
126
+ expect `ops-dev` on PATH outside the monorepo. The published package has only
127
+ `ops`.
128
+
129
+ ### Before the first write: which build and which data
130
+
131
+ **The `ops` on PATH can be the prod build.** A prod build always uses prod
132
+ 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:
134
+
135
+ 1. Run `ops mode` and read two lines: `build mode` (`prod` or `dev`) and
136
+ `data mode`. Write only when the data mode is the data that the work needs.
137
+ 2. Check that the build has the revamp: `ops --help` lists `headers`,
138
+ `route`, `search`, and `section`. When it does not, the build is of before
139
+ the revamp.
140
+
141
+ For DEV data, run the dev launcher of the checkout:
142
+
143
+ ```bash
144
+ OPS_DATA_MODE=dev node "<monorepo>/apps/cli-ops/bin/ops.js" mode # build mode dev, data mode dev (env)
145
+ OPS_DATA_MODE=dev node "<monorepo>/apps/cli-ops/bin/ops.js" headers --org me
146
+ ```
147
+
148
+ The dev launcher (`bin/ops.js`) runs `dist-dev/`. When `dist-dev/` is older
149
+ than the source of this checkout (`src`, `bin`, `package.json`, also
150
+ uncommitted changes), it runs the TypeScript source and prints one note on
151
+ stderr (`note: the ops-dev bundle … is older than its source`; no note with
152
+ `--json`). It also runs the source when there is no build, or with
153
+ `OPS_CLI_FORCE_SOURCE=1`. The prod launcher (`bin/ops-prod.js`) always runs
154
+ `dist/`; when `dist/` is older than the source, it prints one warning on
155
+ stderr (`warning: the ops bundle … is older than the source`). Rebuild with
156
+ `ndv build ops` (dev) or `ndv build ops prod`. `OPS_CLI_NO_STALE_CHECK=1`
157
+ turns the check off.
158
+
159
+ ## Modes
160
+
161
+ `ops` follows the Strike rule. There are two modes, and they always move
162
+ together:
163
+
164
+ | | Build mode | Data mode |
165
+ |---|---|---|
166
+ | Says | which code runs | which deployment and which credential |
167
+ | Prod build (`bin/ops-prod.js`, `dist/`) | prod | always prod |
168
+ | Dev build (`bin/ops.js`, `dist-dev/`, source) | dev | dev by default |
169
+
170
+ | Data mode | Gateway site | Credential | Web links |
171
+ |---|---|---|---|
172
+ | dev | `https://next-dalmatian-912.convex.site` | `~/.nuucognition/auth.local.json` | `http://localhost:3088` |
173
+ | prod | `https://acoustic-weasel-248.convex.site` | `~/.nuucognition/auth.json` | `https://ops.nuucognition.com` |
174
+
175
+ A dev build reads its data mode in this order:
176
+
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`.
180
+
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.
183
+
184
+ ```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
189
+ ```
190
+
191
+ ## Auth
192
+
193
+ `ops` uses the one NUU sign-in of this machine. It reads the same credential
194
+ files as the flint CLI and the nuu CLI, in the NUU home (`~/.nuucognition`,
195
+ or `$NUU_HOME`):
196
+
197
+ | Data mode | Credential | Account service | Sign in with |
198
+ |---|---|---|---|
199
+ | prod | `~/.nuucognition/auth.json` | `https://account.nuucognition.com` | `nuu login`, `flint login`, or `ops login` |
200
+ | dev | `~/.nuucognition/auth.local.json` | `http://localhost:3000` | a dev login, or `ops login` of a dev build in dev data |
201
+
202
+ So a machine that ran `nuu login` or `flint login` needs no other step for
203
+ the published `ops`. The code is
204
+ `requireValidAuth(resolveEnv("ops", { mode: dataMode() }))` of cli-core.
205
+ `ops login` runs the browser sign-in of cli-core (the same flow as
206
+ `flint login`) and writes the credential file of the current data mode.
207
+ When the short session token expires, `ops` refreshes it through the
208
+ account service of that mode and writes the new token to the same file.
209
+ The format of the file is the format of cli-core. The CLI never prints the
210
+ token.
211
+
212
+ ```bash
213
+ ops login # browser sign-in for the current data mode (a person only)
214
+ ops whoami # user, email, data mode, active org
215
+ ops mode # the credential file and who is signed in
216
+ ```
217
+
218
+ 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`).
222
+
223
+ ## Orgs
224
+
225
+ Every command works in one org. The active org is:
226
+
227
+ 1. `--org <slug|name|me>` on the command line,
228
+ 2. else the stored default of the data mode (`~/.nuucognition/ops/<mode>.json`),
229
+ 3. else the personal workspace (its slug is your Clerk user ID).
230
+
231
+ A command on an existing record (given by ID or URL) works in the record's
232
+ own org. It refuses a `--org` that names another org (`org_mismatch`).
233
+
234
+ ## References
235
+
236
+ A record argument takes any of these forms:
237
+
238
+ | Form | Example | How it resolves |
239
+ |---|---|---|
240
+ | Convex ID | `k57b2m4x9q8r7t6y5u4i3o2p1a0s9d8f` | `agent.resolve` (kind, org, title, breadcrumb) |
241
+ | 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 |
243
+ | 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
+ | Path | `"Alpha › v1 › Backend"`, `"Alpha > v1"`, `"Alpha / Backlog"` | the path of a place in the headers (`›`, `>`, and `/` are the same separator) |
245
+
246
+ A title matches in this order: an exact title (any case), then an exact
247
+ path, then a unique ID prefix of 4 or more characters (the short IDs of
248
+ `plan show`), then a unique partial title, then a unique partial path. When
249
+ more than one record matches, the command fails with `error <kind>_ambiguous`
250
+ (exit 2) and lists each candidate with its ID and its path. Pass the ID or
251
+ a longer path. Never guess between candidates.
252
+
253
+ A step title needs `--plan`. A deployment name needs `--project` only on a
254
+ deployment of Ops of before the revamp (the headers name every deployment).
255
+ An ID, URL, or address never needs a scope.
256
+
257
+ `agent.resolve` reads each kind of record: a project, a deliverable, a
258
+ 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
260
+ from fleet ops-cli-publish-1023; a deleted notepad, and a personal notepad of
261
+ another person, are not found). Five kinds have an address row: a project, a
262
+ 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
266
+ record that an address names now.
267
+
268
+ ### Places (`--to` of `task move`)
269
+
270
+ | Form | The place |
271
+ |---|---|
272
+ | `section:<ref>` | the section |
273
+ | `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) |
275
+ | `deployment:<ref>` | the inbox section of the deployment |
276
+ | `parent:<task>` (or `task:<task>`) | under the task, as a subtask |
277
+ | `backlog` | the backlog section of the project of the task |
278
+ | `master` | the backlog section of the org (the master backlog) |
279
+ | `unplaced` | no home |
280
+ | an ID, a URL, or an address | the place of that kind |
281
+ | a bare title or path | a place of any kind in the headers; two matches fail with `place_ambiguous` and their `kind:<id>` forms |
282
+
283
+ A **link spec** (deadline links, step links, title links, blocker links) is
284
+ `kind:ref`, a URL, an address, or an ID: `task:"Write docs"`, `step:<id>`,
285
+ `project:Alpha`, `https://…/plans/<id>`, `@v1/acme/task/<id>#ops`. A title
286
+ needs the kind prefix.
287
+
288
+ ## Output and errors
289
+
290
+ ```bash
291
+ ops task list --json | jq '.[0]._id'
292
+ ops task show "Write docs"
293
+ ```
294
+
295
+ | Exit code | Meaning |
296
+ |---|---|
297
+ | 0 | Success |
298
+ | 1 | The gateway or the record refused the operation |
299
+ | 2 | Usage error, ambiguous reference, or a guard refused (for example `--yes` missing) |
300
+ | 3 | Not signed in (`auth_required`, `unauthenticated`) |
301
+ | 4 | Network error |
302
+
303
+ Gateway codes pass through: `unauthenticated`, `org_unauthorized`,
304
+ `org_frozen`, `agent_unknown_operation`, `agent_wrong_endpoint`,
305
+ `agent_invalid_input`, `*_not_found`, `*_conflict`, `waybill_stale`,
306
+ `waybill_conflict`. Client codes include `usage`, `auth_required`,
307
+ `network`, `bad_response`, `<kind>_ambiguous`, `<kind>_not_found`,
308
+ `wrong_kind`, `org_mismatch`, `undo_state_changed`, `undo_conflict`,
309
+ `expect_required`, `nothing_to_undo`, `nothing_to_redo`,
310
+ `reorder_anchor_outside`, `position_unknown`, `confirm_required`,
311
+ `place_ambiguous`, `place_not_found`, `deployment_no_inbox`. Codes of the
312
+ revamp from the service: `task_has_subtasks` (a delete needs
313
+ `--with-subtasks`), `subtask_too_deep` (6 levels), `subtask_cycle`.
314
+
315
+ 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.
319
+
320
+ The guards exit 2: `confirm_required` (a delete that removes other records
321
+ or cannot be undone needs `--yes`: project, deliverable, section, deployment, plan, blocker),
322
+ `expect_required` and `undo_state_changed` (a replay without the right
323
+ `--expect <seq>`). A list with no rows prints one `no <things>` line; with
324
+ `--json` it prints `[]`.
325
+
326
+ ## Command reference
327
+
328
+ Global options (on every command): `--json`, `--org <slug|name|me>`.
329
+ Dates take `YYYY-MM-DD`, `today`, `tomorrow`, or an offset such as `+3d` or
330
+ `-1w`. Where a value can be cleared, pass `none`. A text argument of `-`
331
+ reads stdin.
332
+
333
+ ### Session and raw access
334
+
335
+ | Command | Example |
336
+ |---|---|
337
+ | `ops login [--verbose]` | `ops login` |
338
+ | `ops whoami` | `ops whoami --json` |
339
+ | `ops mode [dev\|prod]` | `ops mode prod` |
340
+ | `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
+ | `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
+
343
+ ### headers, route, search
344
+
345
+ | Command | Example |
346
+ |---|---|
347
+ | `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
+ | `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
+ | `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` |
351
+ | `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
+
353
+ ### org, member
354
+
355
+ | Command | Example |
356
+ |---|---|
357
+ | `ops org list` — `*` marks the active org | `ops org list` |
358
+ | `ops org use <slug\|name\|me>` — the default of this data mode | `ops org use "NUU Cognition"` |
359
+ | `ops org show [org]` | `ops org show me` |
360
+ | `ops member list` — members of the active org, for assignees | `ops member list --json` |
361
+
362
+ ### project
363
+
364
+ | Command | Example |
365
+ |---|---|
366
+ | `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` |
368
+ | `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
+ | `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
+ | `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` |
371
+ | `ops project context show <project>` / `ops project context set <project> (<md\|-> \| --file <path> \| --clear)` — `projects.setContext` | `ops project context set Alpha --file context.md` |
372
+ | `ops project delete <project> --yes` — deletes everything in it; only an owner or an admin of the org (in the personal workspace: its person) can delete it; an org member gets `owner-only`, and then archives it (`--status archived`) | `ops project delete Alpha --yes` |
373
+
374
+ ### deliverable
375
+
376
+ | Command | Example |
377
+ |---|---|
378
+ | `ops deliverable list [--project <project>] [--open]` — each with its done mark and address | `ops deliverable list --project Alpha --open` |
379
+ | `ops deliverable show <deliverable> [--project]` — the done mark, the deployment it ships to and comes from, its sections and their root tasks | `ops deliverable show "Alpha › v1 launch"` |
380
+ | `ops deliverable status (<deliverable> [--project <p>] \| --project <p>)` — the derived status (`open`, `in-progress`, `blocked`, `completed`) from `deliverableStatus.forDeliverable`, with the status of each section (`blocked`, `ready`, `in-progress`, `done`) and the done upstream sections; `--project` alone gives each deliverable of the project with its status (`deliverableStatus.forProject`) and its last activity (`deliverables.lastActivityForProject`); `partial: true` when the read budget stopped a read | `ops deliverable status "Alpha › v1 launch" --json` |
381
+ | `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
+ | `ops deliverable update <deliverable> [--title] [--deployment <d\|none>] [--from-deployment <d\|none>]` — `deliverables.setDeployment` | `ops deliverable update "v1.1" --deployment Production` |
383
+ | `ops deliverable done <deliverable>` / `ops deliverable reopen <deliverable>` — `deliverables.setDone`: a person decides; the sections do not | `ops deliverable done "Alpha › v1 launch"` |
384
+ | `ops deliverable rename <deliverable> <title>` | `ops deliverable rename "v1 launch" "v1.0 launch"` |
385
+ | `ops deliverable context show\|set <deliverable> …` — `deliverables.setContext` | `ops deliverable context set "v1 launch" "The first public release"` |
386
+ | `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` |
387
+ | `ops deliverable flint set <deliverable> <address> [--flint-id <uuid\|none>] [--project]` — the one Flint place (`deliverables.setFlintPlace`); `deliverable show` prints it | `ops deliverable flint set "v1 launch" @v1//flint/nuu-operations` |
388
+ | `ops deliverable flint clear <deliverable> [--project]` — removes the address and the id | `ops deliverable flint clear "v1 launch"` |
389
+
390
+ ### section
391
+
392
+ `ops subsection …` is a hidden alias of `ops section …`.
393
+
394
+ | Command | Example |
395
+ |---|---|
396
+ | `ops section list [--project <p> \| --deliverable <d> \| --deployment <d>] [--archived]` — `--project`: grouped by home (`sections.listForProject`); no scope: every section of the org with its path | `ops section list --project Alpha` |
397
+ | `ops section show <section> [--project\|--deliverable\|--deployment]` — the home, the address, the web link, and the root tasks with their subtask counts (`sections.get`, `sections.tasks`) | `ops section show "Alpha › v1 › Backend"` |
398
+ | `ops section create <title> (--project <p> \| --deliverable <d> \| --deployment <d> \| --org-wide) [--after <section>] [--context <md>] [--due <date>]` — `sections.create`; `--org-wide` is a section of the org (`--org` picks the org) | `ops section create Ideas --project Alpha` |
399
+ | `ops section update <section> [--title] [--due <date\|none>]` | `ops section update Backend --due none` |
400
+ | `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
+ | `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
+ | `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
+ | `ops section archive\|unarchive <sections...>` — `sections.archive` | `ops section archive Backend` |
405
+ | `ops section context show\|set <section> …` — `sections.update` | `ops section context set Backend "The API and the queue"` |
406
+ | `ops section delete <section> --yes` — deletes its tasks; refuses without `--yes` | `ops section delete Backend --yes` |
407
+ | `ops section to-task <section> [--into <section>]` — an empty section becomes a task | `ops section to-task "Tiny fix"` |
408
+
409
+ ### task
410
+
411
+ | Command | Example |
412
+ |---|---|
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` |
417
+ | `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` |
419
+ | `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
+ | `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"` |
424
+ | `ops task reorder <task> (--before <t>\|--after <t>\|--top\|--bottom)` | `ops task reorder "Write docs" --after "Draft outline"` |
425
+ | `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
+ | `ops task to-section <task> [--deliverable <d>]` — `to-subsection` is its former name | `ops task to-section "Big item" --deliverable "v1 launch"` |
427
+
428
+ #### Subtasks and the task map
429
+
430
+ | Command | Example |
431
+ |---|---|
432
+ | `ops task subtask add <parent> <title> [--body <md\|-> \| --body-file <path>]` — `subtasks.create`; it gets the home of the root task | `ops task subtask add <taskId> "Write the tests"` |
433
+ | `ops task subtask list <task>` — the subtree with the done count (`subtasks.tree`; cut at 200 tasks) | `ops task subtask list <taskId>` |
434
+ | `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
+ | `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>` |
437
+ | `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
+
439
+ A task order bucket is one section **and** one priority band (before the
440
+ revamp: one subsection, or one backlog folder; no folder is Main). `tasks.reorder` takes the complete bucket,
441
+ so `ops task reorder` reads the container, computes the new order of the
442
+ bucket, and posts it. An anchor in another band or folder is refused
443
+ (`reorder_anchor_outside`). `task create --top` creates the task and then
444
+ reorders it to the top of its bucket (two writes, two undo entries). An
445
+ unplaced task has no order bucket: `ops task reorder` refuses it
446
+ (`task_unplaced`).
447
+
448
+ #### The task primitive v0.1 ((Task) 1287 of the NUU Flint)
449
+
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.
459
+
460
+ | Command | Example |
461
+ |---|---|
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` |
465
+ | `ops task result <task> <md\|->` — `""` clears it | `ops task result <task> "The pin works on dev."` |
466
+ | `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
+ | `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
+ | `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
+ | `ops task unrelate <task> <kind> <ref>` | `ops task unrelate <task> project Alpha` |
472
+ | `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
+ | `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>` |
477
+ | `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
+ | `ops task unchain <task>` — removes the `after` link of the task | `ops task unchain <task>` |
479
+ | `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>` |
480
+ | `ops task unlink <task> [--kind continues-from\|after]` — `taskLinks.unlink`: removes the link that the task holds; default `continues-from`; no task closes | `ops task unlink <task> --kind after` |
481
+ | `ops task attach <target> <kind> <ref> [--session-id <id>] [--label <text>] [--state <state>]` — the target is a task or a task group (an ID or an address; a title names a task); kinds: `session`, `fleet`, `note`, `repo`; a second attach updates the label and the state | `ops task attach <address> session <session address> --session-id <id> --state working` |
482
+ | `ops task detach <target> <kind> <ref>` — the row stays as history | `ops task detach <task> repo main` |
483
+ | `ops task session-state <session-id> <state>` — `taskAttachments.setState`: the last known state of an Orbh session on each of its live attachments in the org; gives the count | `ops task session-state <session id> waiting` |
484
+ | `ops task launch accept <launch-id> --station <address> --machine-id <id> (--text-file <path> \| --text-hash <hex>)` — `taskLaunches.accept` (D11): the station workflow accepts a NUU Network launch once, before any work; `--text-file` sends the sha-256 of the exact bytes of the item text; gives the task and the org, and the agent works only on them; refusals: `network_launch_not_found`, `network_launch_stale`, `network_launch_already_accepted`, `network_launch_text_mismatch`, `network_launch_wrong_target` | `ops task launch accept <id> --station <address> --machine-id <id> --text-file item.txt --json` |
485
+
486
+ `--json` output: `show` gives the C6 detail with `address` at the top
487
+ 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
492
+ the read of an address after a write fails, the result keeps the new id with
493
+ `address: null` (exit 0), and stderr names the read to run again: do not run
494
+ the write again.
495
+ `attach` gives `{attachmentId, target: {kind: "task"|"taskgroup", id,
496
+ address}}`. A deployment without an op of the task primitive refuses a new
497
+ verb with `agent_unknown_operation`; `show`, `done`, and `reopen` then keep
498
+ their present behaviour.
499
+
500
+ ### task group
501
+
502
+ A task group is a set of tasks that are done together, usually in one
503
+ session. Each task stays a full task. A task can be in more than one task
504
+ group. A group is open while one of its tasks is open (an empty group is
505
+ open). The attachments of a group show on each of its tasks.
506
+
507
+ | Command | Example |
508
+ |---|---|
509
+ | `ops task group create <title> [--task <task> ...] [--body <md\|->]` — gives the group id and its address | `ops task group create "Login fixes" --task <a> --task <b>` |
510
+ | `ops task group update <group> [--title <title>] [--body <md\|->]` — `taskGroups.update`; give one or both; an empty body clears it | `ops task group update "Login fixes" --title "Login fixes, part 2"` |
511
+ | `ops task group add <group> <task>...` | `ops task group add "Login fixes" <c>` |
512
+ | `ops task group remove <group> <task>...` — the tasks stay | `ops task group remove "Login fixes" <c>` |
513
+ | `ops task group show <group>` — `--json`: `{address, group, tasks, open, attachments, url}`; `url` is the web page of the group (the Groups view of the Tasks page); the address is the field `address` of `taskGroups.get` (a warning on stderr when it is null); when the tasks stop at their byte budget, `--json` has `tasksCut: true` after `tasks` and the text says so | `ops task group show @v1/test/taskgroup/<id>#ops --json` |
514
+ | `ops task group list [--open]` | `ops task group list --open` |
515
+ | `ops task group delete <group>` — deletes the group and its members, not the tasks | `ops task group delete "Login fixes"` |
516
+
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
+ ### comment
525
+
526
+ | Command | Example |
527
+ |---|---|
528
+ | `ops comment list <task> [--events] [--limit n]` | `ops comment list "Write docs" --events` |
529
+ | `ops comment add <task> <body\|->` | `ops comment add "Write docs" "First draft is up."` |
530
+ | `ops comment edit <task> <commentId> <body\|->` | `ops comment edit "Write docs" <commentId> "Second draft."` |
531
+ | `ops comment delete <task> <commentId>` | `ops comment delete "Write docs" <commentId>` |
532
+ | `ops comment react <task> <commentId> <emoji>` | `ops comment react "Write docs" <commentId> 👍` |
533
+ | `ops comment unreact <task> <commentId> <emoji>` | `ops comment unreact "Write docs" <commentId> 👍` |
534
+ | `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>` |
535
+
536
+ ### backlog
537
+
538
+ | Command | Example |
539
+ |---|---|
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` |
541
+
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.
545
+
546
+ ### deadline
547
+
548
+ | Command | Example |
549
+ |---|---|
550
+ | `ops deadline list [--all-orgs] [--mine] [--status open\|todo\|doing\|done\|all]` | `ops deadline list --all-orgs --mine` |
551
+ | `ops deadline show <deadline>` | `ops deadline show "Beta"` |
552
+ | `ops deadline create <title> --due <date> [--after <deadline>]... [--scope <tag>]... [--link <spec>]... [--title-link <spec>] [--assign <member>]...` — `--date` is the hidden former name of `--due` | `ops deadline create Beta --due "2026-11-01 17:00" --link project:Alpha --assign me` |
553
+ | `ops deadline insert [title] --between <earlier> <later> [--due] [--scope]... [--assign]...` | `ops deadline insert "Code freeze" --between Alpha-start Beta` |
554
+ | `ops deadline update <deadline> [--title] [--description] [--due] [--status todo\|doing\|done] [--scope <tag>]... [--clear-scopes] [--title-link <spec\|none>] [--nudge <n>]` | `ops deadline update Beta --status doing` |
555
+ | `ops deadline shift <deadline> (--by <±days\|Nw> \| --to <date>) [--with-downstream]` — one `setDates` call | `ops deadline shift Beta --by 3 --with-downstream` |
556
+ | `ops deadline links set <deadline> [specs...]` | `ops deadline links set Beta project:Alpha` |
557
+ | `ops deadline links add <deadline> <specs...>` | `ops deadline links add Beta task:"Write docs"` |
558
+ | `ops deadline links remove <deadline> <specs...>` | `ops deadline links remove Beta task:"Write docs"` |
559
+ | `ops deadline requires set <deadline> [deadlines...]` | `ops deadline requires set Beta Alpha-start` |
560
+ | `ops deadline requires add <deadline> <deadlines...>` | `ops deadline requires add Beta "Code freeze"` |
561
+ | `ops deadline requires remove <deadline> <deadlines...>` | `ops deadline requires remove Beta "Code freeze"` |
562
+ | `ops deadline assign <deadline> (<members...> [--add\|--remove] \| none)` — `--none` is the hidden former form of `none` | `ops deadline assign Beta me --add` |
563
+ | `ops deadline delete <deadline>` | `ops deadline delete Beta` |
564
+
565
+ ### blocker
566
+
567
+ A blocker says why work waits (Task 376 of the NUU Operations Flint): a
568
+ title, who or what the work waits on, the next step, an owner, and one date,
569
+ 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
573
+ open blocker is **due** when its check-up date is today or earlier in local
574
+ time. The CLI sends local dates, also the default check-up date (tomorrow),
575
+ as the web app does.
576
+
577
+ | Command | Example |
578
+ |---|---|
579
+ | `ops blocker list [--open] [--due] [--on <record>]` — `blockers.list` (the open ones by check-up date, then the 50 newest closed ones), or with `--on` `blockers.forRecord`; each line: the check-up date, the title, the status (`open, due` when due), the owner name, the delays, and the linked records | `ops blocker list --due` |
580
+ | `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
+ | `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
+ | `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"` |
584
+ | `ops blocker detach <blocker> <record>` — the ID of a broken link works too | `ops blocker detach <blockerId> <taskId>` |
585
+ | `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
+ | `ops blocker resolve <blocker> [--note <text>]` — the work can go on | `ops blocker resolve <blockerId> --note "The key came"` |
587
+ | `ops blocker drop <blocker> [--note <text>]` — not a real blocker, or no longer matters | `ops blocker drop <blockerId>` |
588
+ | `ops blocker reopen <blocker> [--check <date>]` — default check-up: tomorrow; an open blocker stays as it is | `ops blocker reopen <blockerId> --check +2d` |
589
+ | `ops blocker delete <blocker> --yes` — removes the blocker and its log; refuses without `--yes` (`confirm_required`); `resolve` or `drop` keeps the log | `ops blocker delete <blockerId> --yes` |
590
+
591
+ Codes from the service: `blocker_not_found`, `blocker_closed`,
592
+ `blocker_check_invalid`, `blocker_title_required`, `blocker_text_too_long`
593
+ (500 characters for the title, the texts, and a note),
594
+ `blocker_link_not_found` (a link must be a record of the org of the blocker),
595
+ `assignee_not_found`, and `assignee_not_member` (the owner). The ops are new
596
+ on dev data: on a deployment without them, the error says that they are not
597
+ deployed yet.
598
+
599
+ ### plan
600
+
601
+ | Command | Example |
602
+ |---|---|
603
+ | `ops plan list [--status active\|completed\|all]` | `ops plan list` |
604
+ | `ops plan show <plan> [--format text\|mermaid]` — `--json` prints the raw `plans.get` result | `ops plan show Launch --format mermaid` |
605
+ | `ops plan create <title> [--description]` | `ops plan create Launch` |
606
+ | `ops plan rename <plan> <title>` | `ops plan rename Launch "Launch v1"` |
607
+ | `ops plan update <plan> [--title] [--description]` | `ops plan update Launch --description "Q4 launch"` |
608
+ | `ops plan complete <plan>` | `ops plan complete Launch` |
609
+ | `ops plan reopen <plan>` | `ops plan reopen Launch` |
610
+ | `ops plan delete <plan> --yes` — every step goes; an undo cannot bring it back; refuses without `--yes` | `ops plan delete Launch --yes` |
611
+ | `ops plan expand-all <plan>` — unfold every folded step | `ops plan expand-all Launch` |
612
+ | `ops plan history <plan>` — your undo and redo stacks with seq numbers | `ops plan history Launch` |
613
+ | `ops plan undo <plan> (--expect <seq> \| --dry-run)` | `ops plan undo Launch --expect 42` |
614
+ | `ops plan redo <plan> (--expect <seq> \| --dry-run)` | `ops plan redo Launch --dry-run` |
615
+
616
+ `ops plan show` prints the Start sentinel, then every step in topological
617
+ order, then the End sentinel:
618
+
619
+ ```
620
+ plan <id> Launch [active] 3 steps
621
+ start @ -300,0 -> k57b2m
622
+ k57b2m [ ] Design @ 0,0 id k57b2m4x9q8r7t6y5u4i3o2p1a0s9d8f
623
+ requires: start
624
+ k57c9a [~] Build (optional) @ 300,0 id k57c9a…
625
+ requires: k57b2m
626
+ links: task k57… "Implement API"
627
+ assignee: Ana Lee (<usersId>)
628
+ deadline: 2026-11-01 Beta <deadlineId>
629
+ then: end
630
+ end @ (auto) <- k57c9a
631
+ ```
632
+
633
+ Each step line shows the short ID (a unique ID prefix; step commands accept
634
+ it with `--plan`), `[ ]` / `[~]` / `[x]`, the title, the optional and folded
635
+ flags, the stored position `x,y` (`(auto)` when the step has none), and the
636
+ full ID.
637
+
638
+ **Undo safety.** `plan undo` and `plan redo` act as you: Waybill replays
639
+ *your* last gesture in the scope `plan:<id>`, whoever made it (the web app,
640
+ another agent, this CLI). The CLI reads the stack first
641
+ (`plans.historyState`), names the gesture, and passes its seq as
642
+ `expectSeq`. Without `--expect <seq>` (and without `--dry-run`) it refuses
643
+ with `expect_required`, names the head, and prints the exact command. With
644
+ `--expect <seq>` it refuses unless that gesture is still the head. The
645
+ server refuses a stale seq too. A refusal is `undo_state_changed` (exit 2),
646
+ and nothing changes. `ops history`, `ops undo`, and `ops redo` do the same
647
+ for any other scope (below).
648
+
649
+ ### step
650
+
651
+ Steps take an ID or URL alone, or a title or short ID with `--plan`.
652
+
653
+ | Command | Example |
654
+ |---|---|
655
+ | `ops step add <plan> <title> [--after <step>]... \| [--before <step>] \| [--between <a> <b>] [--description] [--x --y]` — no flag: a new root. No `--x --y`: the server places the step | `ops step add Launch "Deploy" --after Build` |
656
+ | `ops step update <step> [--plan] [--title] [--description] [--done\|--undone] [--optional\|--required] [--collapse\|--expand]` | `ops step update Build --plan Launch --done` |
657
+ | `ops step assign <step> <member\|none> [--plan]` | `ops step assign Build me --plan Launch` |
658
+ | `ops step requires set <step> [steps...] [--plan]` — no steps: a root | `ops step requires set Deploy Build Test --plan Launch` |
659
+ | `ops step requires add <step> <steps...> [--plan]` | `ops step requires add Deploy Test --plan Launch` |
660
+ | `ops step requires remove <step> <steps...> [--plan]` | `ops step requires remove Deploy Test --plan Launch` |
661
+ | `ops step links set <step> [specs...] [--plan]` — kinds: task, section, deliverable, plan | `ops step links set Build task:"Implement API" --plan Launch` |
662
+ | `ops step links add <step> <specs...> [--plan]` | `ops step links add Build section:Backend --plan Launch` |
663
+ | `ops step links remove <step> <specs...> [--plan]` | `ops step links remove Build section:Backend --plan Launch` |
664
+ | `ops step move <step> (--x <x> --y <y> \| --dx <dx> --dy <dy>) [--with-downstream] [--plan]` | `ops step move Build --dx 120 --with-downstream --plan Launch` |
665
+ | `ops step move start\|end --plan <plan> (--x --y \| --dx --dy)` — the sentinels | `ops step move end --plan Launch --x 1400 --y 0` |
666
+ | `ops step delete <step> [--plan]` — soft; `plan undo` revives it | `ops step delete Deploy --plan Launch` |
667
+
668
+ **The drag rule.** `--with-downstream` matches the web canvas
669
+ (`onNodeDragStop` in `src/components/plans/plan-view.tsx`): the step lands
670
+ on its new spot, and every step after it (its whole branch, folded steps
671
+ included) moves by the same offset, in **one** `plans.setPositions` call. A
672
+ zero-length move writes nothing. A downstream step with no stored position
673
+ stays where it is and is reported as `kept` (the UI moves it from a
674
+ fallback layout that the CLI does not compute).
675
+
676
+ ### notify
677
+
678
+ | Command | Example |
679
+ |---|---|
680
+ | `ops notify list [--unread] [--this-org] [--limit n] [--cursor c]` | `ops notify list --unread` |
681
+ | `ops notify count [--this-org]` | `ops notify count` |
682
+ | `ops notify read <ids...>` | `ops notify read <notificationId>` |
683
+ | `ops notify unread <ids...>` | `ops notify unread <notificationId>` |
684
+ | `ops notify read-all [--this-org]` | `ops notify read-all` |
685
+ | `ops notify dismiss <id>` | `ops notify dismiss <notificationId>` |
686
+ | `ops notify read-key <key>` — mark every notification with one collapse key read (`notifications.markKeyRead`) | `ops notify read-key <key>` |
687
+ | `ops notify prefs list` | `ops notify prefs list` |
688
+ | `ops notify prefs set <category> on\|off [--channel <c>]` — categories: tasks, deadlines, plans, updates | `ops notify prefs set plans off --channel push` |
689
+
690
+ ### activity
691
+
692
+ | Command | Example |
693
+ |---|---|
694
+ | `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` |
696
+ | `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
+
698
+ Scopes are `org:<slug>`, `project:<id>`, `deliverable:<id>`,
699
+ `subsection:<id>`, `backlog:<projectId>`, `backlog:org:<slug>`, and
700
+ `plan:<id>`. `org:me` and `backlog:org:me` name the personal workspace.
701
+ `--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>`).
703
+
704
+ ### history, undo, redo (any scope)
705
+
706
+ | Command | Example |
707
+ |---|---|
708
+ | `ops history (--on <id\|url\|address> \| --scope <scope>)` — your undo and redo stacks in the scope (`waybill.peek`); the scope of a section is `subsection:<id>` (the word of the service) | `ops history --on <taskId> --json` |
709
+ | `ops undo (--on <id\|url> \| --scope <scope>) (--expect <seq> \| --dry-run)` | `ops undo --on <taskId> --expect 12` |
710
+ | `ops redo (--on <id\|url> \| --scope <scope>) (--expect <seq> \| --dry-run)` | `ops redo --scope org:me --dry-run` |
711
+
712
+ The guard is the one of `ops plan undo`. A deliverable maps to
713
+ `deliverable:<id>` (its sections); a change of the deliverable row itself
714
+ is in `project:<projectId>`.
715
+
716
+ ### favourite
717
+
718
+ | Command | Example |
719
+ |---|---|
720
+ | `ops favourite list` — your favourites in the active org, in star order (`home.favourites`) | `ops favourite list --json` |
721
+ | `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
+ | `ops favourite remove <record> [--kind]` — remove the star; the ID of a deleted record works too | `ops favourite remove <recordId>` |
723
+
724
+ A record of another kind gives `wrong_kind`. Both ops are idempotent. More
725
+ than 100 favourites in one org gives `favourites_full`. The ops come from
726
+ (Task) 316: on a deployment without them, the error says that they are not
727
+ deployed yet.
728
+
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
+ ### home
739
+
740
+ The homepage reads of the active org. They come from fleet
741
+ ops-cli-publish-1023 ((Task) 381): on a deployment without them, the error
742
+ says that they are not deployed yet.
743
+
744
+ | Command | Example |
745
+ |---|---|
746
+ | `ops home stats` — your task counts (`home.taskStats`): todo, doing, done, done this week (the week starts on Monday 00:00 local), overdue, due in 7 days, and the next due tasks. The CLI sends the local time of this machine, as the web app does | `ops home stats --org acme --json` |
747
+ | `ops home recents` — the sections that you visited last in the web app, newest first (`home.recents`) | `ops home recents` |
748
+ | `ops home activity [--limit n] [--cursor c]` — your own recent actions as homepage rows (`home.activity`, at most 50 a page) | `ops home activity --limit 10` |
749
+ | `ops home brief <project>` — the brief notepad of a project (`brief.forProject`), or none. The web app writes a brief; read its text with `ops notepad show <notepadId>` | `ops home brief Alpha` |
750
+
751
+ ### notepad
752
+
753
+ | Command | Example |
754
+ |---|---|
755
+ | `ops notepad list [--general \| --personal] [--section s \| --deliverable d \| --project p]` | `ops notepad list --general` |
756
+ | `ops notepad show <notepad> [--md]` — the blocks, the mentioned tasks, the scope, and the rev | `ops notepad show "Standup notes"` |
757
+ | `ops notepad create <title> [--general] [--body <md\|->] [--section s]` — personal unless `--general` | `ops notepad create Ideas --body - < ideas.md` |
758
+ | `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` |
760
+ | `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
+ | `ops notepad mentions <task>` — the notepads that hold a chip of the task (`notepads.mentioning`) | `ops notepad mentions <taskId>` |
762
+ | `ops notepad append <notepad> [<md\|->] [--task <task>]` | `ops notepad append <notepadId> --task <taskId>` |
763
+ | `ops notepad write <notepad> <md\|-> (--expected-rev n \| --force)` | `ops notepad write <notepadId> - --expected-rev 3 < notes.md` |
764
+ | `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
+ | `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."` |
766
+ | `ops notepad delete <notepad>` — a soft delete | `ops notepad delete <notepadId>` |
767
+
768
+ A `<notepad>` is a web URL (`.../notepads/<id>`), an Ops address
769
+ (`@v1/<org>/notepad/<id>#ops`), an ID, or a title in the active org. Only
770
+ the owner of a shared (general) notepad can delete it; make it personal
771
+ first with `ops notepad share <notepad> --personal`, or ask the owner.
772
+
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>'`.
776
+
777
+ ### day
778
+
779
+ The day plan is personal: each command acts on your own plan, with tasks of
780
+ any of your orgs. A date is `YYYY-MM-DD`, `today`, `tomorrow`, `yesterday`,
781
+ or an offset like `+1d`.
782
+
783
+ | Command | Example |
784
+ |---|---|
785
+ | `ops day show [date]` — the done count and the tasks in plan order | `ops day show tomorrow` |
786
+ | `ops day add <task...> [--date d] [--top] [--source-org o]` | `ops day add <taskId> --date tomorrow` |
787
+ | `ops day remove <task...> [--date d]` | `ops day remove <taskId>` |
788
+ | `ops day reorder <task> (--before t \| --after t \| --top \| --bottom) [--date d]` | `ops day reorder <taskId> --top` |
789
+ | `ops day carry [--date d]` — add the unfinished tasks of the 7 days before the date | `ops day carry` |
790
+ | `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
+
792
+ ### deployment
793
+
794
+ A deployment is abstract: a name, a free version label, a status (`live`,
795
+ `paused`, `retired`), notes, a hidden context, an optional project, and an
796
+ optional check schedule. A live deployment has an inbox section.
797
+
798
+ | Command | Example |
799
+ |---|---|
800
+ | `ops deployment list [--project <p>] [--status live\|paused\|retired]` — `deployments.list`: each with its status, version label, schedule, open inbox count, project, and address | `ops deployment list --status live` |
801
+ | `ops deployment show <deployment> [--project]` — `deployments.get`: the schedule, the notes, the inbox with its open tasks, the deliverables that ship to it and the ones that came from its inbox | `ops deployment show Production` |
802
+ | `ops deployment inbox <deployment> [--open]` — `deployments.inbox`: the open tasks in order, then the done ones; add a task with `ops task create --deployment <d> --tag <tag>` | `ops deployment inbox Production --open` |
803
+ | `ops deployment add <deployment> <title> [--body <text>] [--tag feedback\|hotfix\|ongoing\|recurring] [--every day\|week\|month]` — `deployments.addTask`: the Add of the inbox in the web app; the inbox is made when it is absent (also on a paused or retired deployment); `--every` alone gives the tag `recurring` | `ops deployment add Production "Login fails on Safari" --tag hotfix` |
804
+ | `ops deployment sections <deployment> [--open]` — `deployments.sections`: the sections of the deployment other than its inbox (a section that a move put there), in order, each with its root tasks (open first) and addresses; archived sections are left out | `ops deployment sections Production` |
805
+ | `ops deployment create <name> [--project <p>] [--version-label <label>] [--notes] [--context <md>] [--status live\|paused\|retired]` — live by default, so the inbox is made at once | `ops deployment create Production --project Alpha --version-label 1.4` |
806
+ | `ops deployment update <deployment> [--name] [--version-label <label\|none>] [--notes] [--status] [--move-to-project <p\|none>]` | `ops deployment update Production --version-label 1.5` |
807
+ | `ops deployment status <deployment> live\|paused\|retired` — `deployments.setStatus`; `live` makes the inbox | `ops deployment status Production paused` |
808
+ | `ops deployment schedule <deployment> day\|week\|month\|off` — `deployments.setSchedule`; prints the next check | `ops deployment schedule Production week` |
809
+ | `ops deployment check <deployment>` — `deployments.checkNow`: the check-up task now (none when an open one is there) | `ops deployment check Production` |
810
+ | `ops deployment start-deliverable <deployment> <title> (<tasks...> \| --all-open) [--project <p>]` — `deployments.startDeliverable`: a new deliverable that comes from (and ships to) the deployment, with one section that holds the moved tasks; the service names that section "From the inbox" (rename it: `ops section update <section> --title <title>`) | `ops deployment start-deliverable Production "1.5 fixes" --all-open` |
811
+ | `ops deployment context show\|set <deployment> …` — through `deployments.update` | `ops deployment context set Production "The web app at ops.example.com"` |
812
+ | `ops deployment delete <deployment> --yes` — its inbox stays as a plain section | `ops deployment delete Beta --yes` |
813
+
814
+ The program owns `--version` (it prints the version of the CLI anywhere on
815
+ the line), so the label is `--version-label`. The version chain of before the
816
+ 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.
819
+
820
+ ### query
821
+
822
+ The general read query (fleet ops-cli-publish-1023, (Task) 381): read the
823
+ rows of one table of the active org when no typed command answers the
824
+ question. It is read only. It calls `agentQuery.tables`,
825
+ `agentQuery.describe`, and `agentQuery.run`.
826
+
827
+ | Command | Example |
828
+ |---|---|
829
+ | `ops query tables` — the readable tables, with the rule of each (`org`, `user`: only your rows, `notepad`: a personal notepad only for its writer) | `ops query tables` |
830
+ | `ops query describe <table>` — the fields with their types (from the schema; `?` marks an optional field), the indexes of the org, and the org field | `ops query describe tasks` |
831
+ | `ops query <table> [--where <condition>]... [--index <name>] [--order asc\|desc] [--limit 1-500] [--cursor <c>] [--all] [--fields a,b.c] [--count]` — the rows that match every condition, as a compact table; `--json`: `{table, orgSlug, index, rows[], cursor, isDone}` | `ops query tasks --where checkbox=done --where 'priority in high,urgent' --fields title,priority` |
832
+
833
+ The forms of `--where` (quote a condition that has `<`, `>`, `!`, `?`, or a
834
+ space): `field=value`, `field!=value`, `field<value`, `field<=value`,
835
+ `field>value`, `field>=value`, `field~text` (contains: a part of a string or
836
+ an element of an array, no case), `field?` (the field exists), `!field?`
837
+ (the field does not exist), and `field in a,b,c`. A value is a number,
838
+ `true`, `false`, `null`, or a string; a value in double quotes stays a
839
+ string (`'code="42"'`). A field is a name of the schema, or a dotted path
840
+ into a nested object (`context.kind`).
841
+
842
+ The rules:
843
+
844
+ - The names are the names of the schema, not the words of the CLI: a
845
+ section is `deliverable_subsections`, and the section of a task is
846
+ `subsectionId`. Run `ops query describe <table>` first.
847
+ - Each row belongs to the org. A table that holds a secret, a token, a
848
+ 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`).
852
+ - A page holds at most `--limit` rows (default 50, max 500), and one call
853
+ reads at most 5000 documents. When the output ends with `more: --cursor
854
+ <c>`, give that cursor to read the next page, or use `--all`. `--all` and
855
+ `--count` follow the cursor up to 5000 rows.
856
+ - A `contains` condition runs after the read. The CLI then reads pages of
857
+ 500 until it has `--limit` rows; when more rows match, it gives no cursor
858
+ (use `--all` or `--count`).
859
+ - An `=` condition on the fields of an index of the org uses that index
860
+ (`--index` names one); the other conditions filter the rows of the index.
861
+ - A time such as `createdAt` is a number (epoch ms); a date such as
862
+ `dueDate` is a `YYYY-MM-DD` string.
863
+
864
+ ## The gateway
865
+
866
+ - `POST <site>/agent/query`, `POST <site>/agent/mutate`, and (for an action
867
+ such as `route.suggest`) `POST <site>/agent/action`, body `{op, args}`,
868
+ header `Authorization: Bearer <nuu CLI JWT>`.
869
+ - Response `{ok:true, result}` or `{ok:false, error:{code, message, data?}}`.
870
+ - `GET <site>/agent/manifest` returns `{version, ops:[{op, kind, area, summary}]}`
871
+ (`kind` is `query`, `mutation`, or `action`) and needs no credential.
872
+ `ops ops` prints it.
873
+ - The op name is the Convex path with a dot (`plans.createNode`); its args
874
+ are that function's args. Gateway read ops live in `agent.*`:
875
+ `agent.getTask`, `agent.searchTasks`, `agent.resolve`.
876
+
877
+ `ops call` reaches every allowlisted op, also the ones with no dedicated
878
+ command (for example `people.search`, `waybill.rowHistory`,
879
+ `deadlines.linkIndex`).
880
+
881
+ ## Development
882
+
883
+ ```bash
884
+ pnpm --filter @nuucognition/ops-cli typecheck
885
+ pnpm --filter @nuucognition/ops-cli lint
886
+ pnpm --filter @nuucognition/ops-cli test # node:test, a mocked gateway
887
+ pnpm --filter @nuucognition/ops-cli live-check # every family against DEV data, then cleanup
888
+ ```
889
+
890
+ `scripts/live-check.mjs` runs every command family against the dev
891
+ deployment in the personal workspace. It stops unless `ops mode` reports dev
892
+ data, forces `OPS_DATA_MODE=dev`, and deletes every record it creates, also
893
+ after a failure. No check writes or toggles a record that the run did not
894
+ create, and notification reads use `--this-org`. Its last check counts the
895
+ records whose title starts with the run's tag (the tag holds a unique run
896
+ id) and shows each deployment of the run; it must be 0. It also checks the `--json` error rule,
897
+ `history`, `undo --dry-run` and the `expect_required` guard (no replay), the
898
+ `confirm_required` guard of each delete, `--body-file`, `deliverable flint
899
+ set` and `clear` on the run's own deliverable, and the reads of `favourite`,
900
+ `notepad`, and `day` (on a deployment without these ops, the not-deployed
901
+ error passes). To verify a rebuild on
902
+ prod data, in the personal workspace only:
903
+
904
+ ```bash
905
+ OPS_BIN=ops node scripts/live-check.mjs --prod --yes-prod # the build on PATH
906
+ OPS_CLI_FORCE_SOURCE=1 node scripts/live-check.mjs --prod --yes-prod # the source
907
+ ```
908
+
909
+ Source map: `src/modes.ts` (the two modes), `src/context.ts` (auth, orgs),
910
+ `src/gateway.ts` (the client), `src/refs.ts` (references), `src/headers.ts`
911
+ (the headers of an org, paths, and addresses), `src/places.ts` (the places of
912
+ `--to` and of a create), `src/reorder.ts` (bucket reorders),
913
+ `src/plan-graph.ts` (graph, Mermaid, drag rule), `src/undo.ts` (undo guard),
914
+ `src/commands/*` (the command tree). `node --import tsx scripts/help-tree.ts`
915
+ prints the help of every command.