@namzu/cli 11.0.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +390 -46
  2. package/dist/cli.d.ts +15 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +132 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/acp.d.ts +29 -0
  7. package/dist/commands/acp.d.ts.map +1 -0
  8. package/dist/commands/acp.js +156 -0
  9. package/dist/commands/acp.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +12 -1
  11. package/dist/commands/doctor.d.ts.map +1 -1
  12. package/dist/commands/doctor.js +18 -2
  13. package/dist/commands/doctor.js.map +1 -1
  14. package/dist/commands/drain.d.ts +2 -2
  15. package/dist/commands/drain.d.ts.map +1 -1
  16. package/dist/commands/drain.js +31 -8
  17. package/dist/commands/drain.js.map +1 -1
  18. package/dist/commands/run-flags.d.ts.map +1 -1
  19. package/dist/commands/run-flags.js +33 -0
  20. package/dist/commands/run-flags.js.map +1 -1
  21. package/dist/commands/run-stream.d.ts +3 -3
  22. package/dist/commands/run-stream.d.ts.map +1 -1
  23. package/dist/commands/run-stream.js +27 -10
  24. package/dist/commands/run-stream.js.map +1 -1
  25. package/dist/commands/run.d.ts.map +1 -1
  26. package/dist/commands/run.js +50 -4
  27. package/dist/commands/run.js.map +1 -1
  28. package/dist/commands/types.d.ts +10 -0
  29. package/dist/commands/types.d.ts.map +1 -1
  30. package/dist/config/load.d.ts +64 -0
  31. package/dist/config/load.d.ts.map +1 -1
  32. package/dist/config/load.js +137 -20
  33. package/dist/config/load.js.map +1 -1
  34. package/dist/config/schema.d.ts +51 -0
  35. package/dist/config/schema.d.ts.map +1 -1
  36. package/dist/config/schema.js.map +1 -1
  37. package/dist/context/capabilities.d.ts +88 -0
  38. package/dist/context/capabilities.d.ts.map +1 -0
  39. package/dist/context/capabilities.js +160 -0
  40. package/dist/context/capabilities.js.map +1 -0
  41. package/dist/context/sandbox.d.ts +13 -0
  42. package/dist/context/sandbox.d.ts.map +1 -1
  43. package/dist/context/sandbox.js +15 -0
  44. package/dist/context/sandbox.js.map +1 -1
  45. package/dist/doctor/checks/index.d.ts +6 -1
  46. package/dist/doctor/checks/index.d.ts.map +1 -1
  47. package/dist/doctor/checks/index.js +45 -2
  48. package/dist/doctor/checks/index.js.map +1 -1
  49. package/dist/doctor/checks/invariants.d.ts +23 -0
  50. package/dist/doctor/checks/invariants.d.ts.map +1 -0
  51. package/dist/doctor/checks/invariants.js +79 -0
  52. package/dist/doctor/checks/invariants.js.map +1 -0
  53. package/dist/doctor/checks/logging.d.ts +10 -0
  54. package/dist/doctor/checks/logging.d.ts.map +1 -0
  55. package/dist/doctor/checks/logging.js +69 -0
  56. package/dist/doctor/checks/logging.js.map +1 -0
  57. package/dist/doctor/checks/session-export.d.ts +21 -0
  58. package/dist/doctor/checks/session-export.d.ts.map +1 -0
  59. package/dist/doctor/checks/session-export.js +62 -0
  60. package/dist/doctor/checks/session-export.js.map +1 -0
  61. package/dist/doctor/checks/telemetry.d.ts +15 -28
  62. package/dist/doctor/checks/telemetry.d.ts.map +1 -1
  63. package/dist/doctor/checks/telemetry.js +34 -54
  64. package/dist/doctor/checks/telemetry.js.map +1 -1
  65. package/dist/doctor/checks/vault.d.ts +4 -20
  66. package/dist/doctor/checks/vault.d.ts.map +1 -1
  67. package/dist/doctor/checks/vault.js +58 -19
  68. package/dist/doctor/checks/vault.js.map +1 -1
  69. package/dist/doctor/registry.d.ts.map +1 -1
  70. package/dist/doctor/registry.js +13 -6
  71. package/dist/doctor/registry.js.map +1 -1
  72. package/dist/index.d.ts +3 -2
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +3 -2
  75. package/dist/index.js.map +1 -1
  76. package/dist/integrations/files/attachment-store.d.ts +44 -0
  77. package/dist/integrations/files/attachment-store.d.ts.map +1 -0
  78. package/dist/integrations/files/attachment-store.js +86 -0
  79. package/dist/integrations/files/attachment-store.js.map +1 -0
  80. package/dist/integrations/providers/credential-provider.d.ts +39 -0
  81. package/dist/integrations/providers/credential-provider.d.ts.map +1 -0
  82. package/dist/integrations/providers/credential-provider.js +74 -0
  83. package/dist/integrations/providers/credential-provider.js.map +1 -0
  84. package/dist/integrations/providers/discover.d.ts.map +1 -1
  85. package/dist/integrations/providers/discover.js +15 -3
  86. package/dist/integrations/providers/discover.js.map +1 -1
  87. package/dist/integrations/providers/oauth.d.ts.map +1 -1
  88. package/dist/integrations/providers/oauth.js +24 -1
  89. package/dist/integrations/providers/oauth.js.map +1 -1
  90. package/dist/integrations/sessions/store.d.ts +2 -2
  91. package/dist/integrations/sessions/store.d.ts.map +1 -1
  92. package/dist/integrations/sessions/store.js +21 -9
  93. package/dist/integrations/sessions/store.js.map +1 -1
  94. package/dist/integrations/subagents/runtime.d.ts +3 -3
  95. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  96. package/dist/integrations/subagents/runtime.js +11 -11
  97. package/dist/integrations/subagents/runtime.js.map +1 -1
  98. package/dist/integrations/telemetry/session-export.d.ts +99 -0
  99. package/dist/integrations/telemetry/session-export.d.ts.map +1 -0
  100. package/dist/integrations/telemetry/session-export.js +119 -0
  101. package/dist/integrations/telemetry/session-export.js.map +1 -0
  102. package/dist/logging.d.ts +79 -0
  103. package/dist/logging.d.ts.map +1 -0
  104. package/dist/logging.js +67 -0
  105. package/dist/logging.js.map +1 -0
  106. package/dist/permissions/rules.d.ts +3 -3
  107. package/dist/permissions/rules.d.ts.map +1 -1
  108. package/dist/permissions/rules.js +1 -1
  109. package/dist/permissions/rules.js.map +1 -1
  110. package/dist/skills/store.d.ts +1 -1
  111. package/dist/skills/store.js +1 -1
  112. package/dist/tui/App.d.ts.map +1 -1
  113. package/dist/tui/App.js +92 -6
  114. package/dist/tui/App.js.map +1 -1
  115. package/dist/tui/StatusBar.d.ts +1 -1
  116. package/dist/tui/StatusBar.d.ts.map +1 -1
  117. package/dist/tui/agent.d.ts +54 -24
  118. package/dist/tui/agent.d.ts.map +1 -1
  119. package/dist/tui/agent.js +327 -124
  120. package/dist/tui/agent.js.map +1 -1
  121. package/dist/tui/index.d.ts.map +1 -1
  122. package/dist/tui/index.js +17 -6
  123. package/dist/tui/index.js.map +1 -1
  124. package/dist/tui/log-pane.d.ts +48 -0
  125. package/dist/tui/log-pane.d.ts.map +1 -0
  126. package/dist/tui/log-pane.js +106 -0
  127. package/dist/tui/log-pane.js.map +1 -0
  128. package/dist/tui/slashCommands.d.ts +134 -11
  129. package/dist/tui/slashCommands.d.ts.map +1 -1
  130. package/dist/tui/slashCommands.js +169 -15
  131. package/dist/tui/slashCommands.js.map +1 -1
  132. package/dist/tui/types.d.ts +11 -2
  133. package/dist/tui/types.d.ts.map +1 -1
  134. package/package.json +8 -6
package/README.md CHANGED
@@ -1,79 +1,423 @@
1
- # @namzu/cli
1
+ <!-- okf
2
+ type: Reference
3
+ title: "@namzu/cli"
4
+ description: >-
5
+ The terminal agent for @namzu/sdk and the operator commands around it. Bare
6
+ namzu opens an interactive session; the same binary runs one prompt headlessly,
7
+ streams events for a host UI, drains parked runs and diagnoses the machine.
8
+ Separate from the kernel because none of this is something a library should own.
9
+ tags: [readme, package, cli, terminal-agent, operator]
10
+ timestamp: 2026-08-17T00:00:00Z
11
+ status: active
12
+ diataxis: reference
13
+ -->
2
14
 
3
- Operator CLI for the [Namzu](https://namzu.ai) agent platform.
15
+ <div align="center">
4
16
 
5
- Dual-purpose:
6
- - **Standalone bin** — `npx @namzu/cli doctor` (or after install: `namzu doctor`).
7
- - **Library** — `import { runDoctor, registerDoctorCheck } from '@namzu/cli'` for embedded usage where consumer code wants to invoke the doctor in its own process so app-registered checks are visible.
17
+ <h1>@namzu/cli</h1>
18
+
19
+ **A terminal coding agent built on [`@namzu/sdk`](https://www.npmjs.com/package/@namzu/sdk), from the same public API you get.**
20
+
21
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
22
+ [![npm](https://img.shields.io/npm/v/@namzu/cli.svg?label=%40namzu%2Fcli)](https://www.npmjs.com/package/@namzu/cli)
23
+
24
+ [Install](#install) · [Commands](#commands) · [Headless runs](#headless-runs) · [Configuration](#configuration) · [Doctor](#namzu-doctor) · [Library](#as-a-library)
25
+
26
+ </div>
27
+
28
+ ---
29
+
30
+ ## What this is
31
+
32
+ Bare `namzu` opens an interactive terminal agent. The same binary is scriptable:
33
+ one prompt and a printed reply, one prompt and a stream of newline-delimited
34
+ events for a host UI, a session's history as JSON, a health report, a pass over
35
+ runs some other process left parked.
36
+
37
+ It is built entirely on `@namzu/sdk`, in the same repository, out of the public
38
+ API — it exists as much to prove the kernel as to be used. The kernel renders no
39
+ UI, reads no config file and owns no terminal; everything in that sentence is
40
+ this package's job.
41
+
42
+ It is also a library. `runCli` is the whole shell as one call, and the doctor
43
+ registry, the config cascade, the output formatter and the capability probe are
44
+ exported on their own, for a host that wants the operator surface inside its own
45
+ process rather than behind a subprocess boundary.
8
46
 
9
47
  ## Install
10
48
 
11
49
  ```bash
12
- pnpm add -D @namzu/cli
13
- # or, for one-off invocations:
14
- pnpm dlx @namzu/cli doctor
50
+ npm install -g @namzu/cli # the binary
51
+ npx @namzu/cli # run it once without installing
52
+ ```
53
+
54
+ There is also an installer, which checks for Node 20+, installs the package and
55
+ then verifies the binary answers before claiming success. If the global prefix is
56
+ not writable it retries into `~/.namzu` and names the one line to add to your
57
+ profile; it never re-runs itself with elevated privileges.
58
+
59
+ ```bash
60
+ curl -fsSL https://raw.githubusercontent.com/cogitave/namzu/main/install.sh | sh
61
+ # Windows
62
+ irm https://raw.githubusercontent.com/cogitave/namzu/main/install.ps1 | iex
63
+ ```
64
+
65
+ Node 20 or newer, either way.
66
+
67
+ Installing it brings the kernel and four model drivers — `@namzu/anthropic`,
68
+ `@namzu/openai`, `@namzu/openrouter`, `@namzu/ollama` — plus `@namzu/files`.
69
+ These are ordinary dependencies rather than peers, so a fresh install can already
70
+ reach any of those four services, given a credential where the service wants one.
71
+ `@namzu/telemetry`, `@namzu/sandbox` and `@namzu/computer-use` are **not**
72
+ installed with it; they are the optional capabilities `namzu doctor` probes for.
73
+
74
+ To embed it instead of installing the binary:
75
+
76
+ ```bash
77
+ pnpm add @namzu/cli
15
78
  ```
16
79
 
80
+ ## The interactive session
81
+
82
+ With a terminal attached, bare `namzu` launches the terminal UI. Without one — a
83
+ pipe, a CI step — it prints a single line saying an interactive session needs a
84
+ terminal and exits `0`, so a script that reaches the bare binary by accident does
85
+ not hang against a renderer with nothing to render into.
86
+
87
+ Three things happen on the way in, and they are the difference between a toy and
88
+ something you point at a real repository:
89
+
90
+ - **A folder nobody has trusted is not one it works in.** Launching in an
91
+ unfamiliar working directory stops and asks, because reading files, running
92
+ commands and editing code there is what it is about to be able to do. Accepting
93
+ the prompt trusts the folder permanently; `--trust` accepts it for one run and
94
+ deliberately does not remember.
95
+ - **The repository gets to state how it wants work done.** `AGENTS.md` is read
96
+ from the working directory upward to the repository root, outermost first, so
97
+ the file nearest the work has the final word. The files that were loaded are
98
+ named on stderr, and one that was skipped is named with its reason — a refusal
99
+ that says nothing is indistinguishable from a project that declared nothing.
100
+ - **It connects the tool servers you declare.** Each server's tools arrive
101
+ prefixed with its name (`mcp_tickets_create`), so two servers offering `search`
102
+ do not collide. A server that fails to start is named with its reason: the
103
+ interactive session reports and carries on, because a person can read the line
104
+ and decide, and a headless run refuses, because nobody is watching.
105
+
106
+ Inside the session: `/help`, `/tools`, `/skills`, `/skill`, `/resume`,
107
+ `/provider`, `/model`, `/permissions`, `/cost`, `/memory`, `/remember`,
108
+ `/expand`, `/init`, `/login`, `/logout`, `/clear`, `/feedback`, `/quit`,
109
+ `/exit`. Commands the kernel's own registry contributes are merged in beside
110
+ them; a name claimed by both raises an error rather than letting one silently
111
+ shadow the other.
112
+
17
113
  ## Commands
18
114
 
19
- ### `namzu doctor`
115
+ | Command | What it does |
116
+ |---|---|
117
+ | `namzu` | The interactive terminal agent |
118
+ | `namzu run <prompt…>` | One prompt, headless. The reply goes to stdout, status lines to stderr |
119
+ | `namzu run-stream <prompt…>` | The same run, one JSON event per line, for a host UI that renders progress |
120
+ | `namzu history --session <id>` | That session's persisted messages, as JSON |
121
+ | `namzu skills-json` | The skills discovered for a working directory, as JSON |
122
+ | `namzu providers-json` | Providers and their per-provider models, as JSON |
123
+ | `namzu doctor` | Health checks against this machine |
124
+ | `namzu login` / `namzu logout` | Store, or remove, a provider subscription credential |
125
+ | `namzu drain` | Continue runs another process left behind — one pass, then exit |
126
+ | `namzu eval` | Run eval suites and set an exit code |
127
+ | `namzu acp` | Speak the agent-client protocol over this process's stdio |
128
+ | `namzu serve` | Answers that there is no daemon: a run is an ordinary process |
129
+ | `namzu skills` | **Not implemented.** Prints a marker naming the milestone that will implement it, rather than answering "unknown command" |
130
+
131
+ Options that belong to the program rather than to a command go **before** the
132
+ subcommand: `-f, --format text|json|yaml`, `-q, --quiet`, `-v, --verbose`,
133
+ `--log-format pretty|json`, `--dangerously-skip-permissions` (alias `--yolo`),
134
+ `-V, --version`. `namzu run "…" --verbose` is the order a person types and it is
135
+ refused, but the refusal names the option as positional — "try `namzu --verbose
136
+ <command> …`" — rather than handing back the generic advice about prompts that
137
+ begin with a dash, which is about the wrong half of that command line.
138
+
139
+ `namzu drain` deserves one sentence, because its shape is a decision rather than
140
+ a limitation: namzu has no daemon, so continuing parked runs is a command your
141
+ scheduler invokes, not a service that sits there. It takes every run under a
142
+ `--tenant`/`--project`/`--session` scope that no worker currently holds,
143
+ continues it from its last checkpoint, releases it, and exits. A run parked on a
144
+ human decision is reported, never resumed past — the answer belongs to a person,
145
+ and a drainer that continued without it would discard the question the run
146
+ stopped to ask.
20
147
 
21
- Run health checks against the local Namzu environment.
148
+ ## Headless runs
22
149
 
150
+ `namzu run` and `namzu run-stream` are the same one-shot differing only in how
151
+ they print, and they share one argument parser, so an option honoured by one is
152
+ honoured by the other.
153
+
154
+ ```bash
155
+ namzu run "what does this repository build?"
156
+ echo "summarise this" | namzu run
157
+ cat notes.txt | namzu run "summarise this"
158
+ namzu run --cwd ../service --gate 'pnpm typecheck' --gate 'pnpm test' "fix the failing test"
23
159
  ```
24
- namzu doctor # human-readable, all categories
25
- namzu doctor --json # machine-readable JSON
26
- namzu doctor --category sandbox,runtime # filter by category
27
- namzu doctor --per-check-timeout 8000 # raise per-check timeout
28
- namzu doctor --verbose # include failure detail
160
+
161
+ Piped input is used rather than discarded. With no prompt argument it *is* the
162
+ prompt; alongside one it is appended as material the question is about, fenced in
163
+ a `<stdin>` tag so the last line of a file cannot run into the request. `namzu
164
+ run -` reads the prompt from stdin explicitly. Everything that is not an option
165
+ is the prompt — and an option this parser does not recognise is refused rather
166
+ than read aloud to the model, which is the worst available response to a typo.
167
+
168
+ | Option | What it does |
169
+ |---|---|
170
+ | `--cwd <path>` | Directory the agent works in. A path that is missing or is not a directory is refused, never silently ignored |
171
+ | `--provider <id>` | Replaces the provider chain with this provider alone |
172
+ | `--model <id>` | Re-models the existing primary and leaves the rest of the chain intact |
173
+ | `--skills <a,b,c>` | Load these skills as context for the turn, resolved under `--cwd` |
174
+ | `--session <id>` | Bind `run-stream` (and `history`) to a session |
175
+ | `--continue`, `-c` | Resume the most recent conversation here (`run`) |
176
+ | `--resume <id>` | Resume that conversation and no other (`run`) |
177
+ | `--gate <command>` | Must exit `0` before the run may settle. Repeatable; they run in order and stop at the first failure |
178
+ | `--gate-retries <n>` | Fix attempts a failing gate allows. Default `3` |
179
+ | `--permission-mode <m>` | `prompt`, `auto` or `strict` — what happens to a call no rule decided. `auto` when there is nobody to ask |
180
+ | `--trust` | Accept this working directory for this run only |
181
+ | `--yolo` | Alias of `--dangerously-skip-permissions`: resolves undecided calls to `auto`. It does **not** imply `--trust` |
182
+ | `--` | End of options; everything after it is the prompt verbatim |
183
+
184
+ `--continue` and `--resume` are `run` options and are refused by `run-stream`
185
+ rather than ignored. Neither ever falls back to starting a fresh conversation:
186
+ somebody who asked for a specific one and got a new one that looks the same finds
187
+ out several turns later, having already acted on it.
188
+
189
+ **`--gate` is the unattended-operator flag.** The run is not allowed to settle
190
+ until every gate command exits `0`. A failure comes back to the model as the next
191
+ turn, naming the command, the exit code and the output; a gate is not re-run when
192
+ the answer changed nothing on disk, because "the workspace is unchanged" is a
193
+ different instruction from repeating a failure the model has already been shown.
194
+ When the attempts run out the run stops with `answer_rejected` and a non-zero
195
+ exit — never a green run over a red build.
196
+
197
+ The two commands report failure differently, on purpose, because their callers
198
+ listen for different things. `run` answers a shell: `0` on a reply, `1` on a
199
+ failed or unfinished run (including one stopped by a budget, a timeout, an
200
+ iteration cap or a blocking guardrail, where the partial text still prints), `2`
201
+ when no prompt was supplied, `64` when an argument is wrong, `77` when the folder
202
+ has not been trusted and nothing ran. `run-stream` answers a line-scanning host,
203
+ so every failure is an `error` event on stdout and the exit code says only
204
+ whether the caller could reach the run by sending something else: `0` when they
205
+ could, `1` when they could not, `77` for the untrusted folder that only a person
206
+ can change.
207
+
208
+ ## Configuration
209
+
210
+ Highest precedence first:
211
+
212
+ 1. Command-line flags
213
+ 2. `NAMZU_*` environment variables
214
+ 3. `./namzu.config.json` — the project's
215
+ 4. `~/.namzu/config.yaml` — the user's
216
+ 5. Built-in defaults (`format: 'text'`, `quiet: false`)
217
+
218
+ A file that is not there contributes nothing, and that is a default. A file that
219
+ **is** there and cannot be established — invalid YAML or JSON, a permission
220
+ error, a top level that is not a mapping — stops the CLI with exit `78` instead
221
+ of continuing on settings it failed to read. That refusal is load-bearing:
222
+ `permissions` is read from these files, so an unreadable config degrading to `{}`
223
+ would turn an operator's deny list into approval of the same calls, on the one
224
+ path where nobody is watching.
225
+
226
+ | Key | Shape | Notes |
227
+ |---|---|---|
228
+ | `format` | `'text' \| 'json' \| 'yaml'` | Default `text`. Also `NAMZU_FORMAT` |
229
+ | `quiet` | `boolean` | Default `false`. Also `NAMZU_QUIET` (`1`/`true`/`0`/`false`) |
230
+ | `permissions` | tool → effect, or tool → { pattern → effect } | Effects are `allow`, `ask`, `deny`. Absent means every mutating tool prompts |
231
+ | `mcpServers` | name → `{ command, args }` or `{ url }` | Tools arrive prefixed with the server's name |
232
+ | `sandbox` | `{ enabled?, requireIsolation? }` | `enabled` defaults to **on**. `requireIsolation` lists the controls (`filesystem`, `network`, `process`) this machine must actually enforce, or the run refuses to start |
233
+ | `telemetry` | `{ sessionExport?: { destination, eventTypes?, redactors? } }` | Writes run events to a JSONL file. `redactors: []` means no redaction and has to be written to mean it |
234
+
235
+ Only `format` and `quiet` are settable from the environment. `telemetry` is
236
+ deliberately not: a variable in a shell profile could otherwise start exporting
237
+ conversation content with nothing in the config file to show for it. Separately,
238
+ `NAMZU_LOG_LEVEL` and `NAMZU_LOG_FORMAT` govern the log records on stderr rather
239
+ than this config, and `--verbose` / `--quiet` on the command line beat them.
240
+
241
+ ```json
242
+ {
243
+ "permissions": {
244
+ "bash": { "git status*": "allow", "git push*": "deny", "*": "ask" },
245
+ "write": "ask"
246
+ },
247
+ "mcpServers": {
248
+ "tickets": { "command": "node", "args": ["./tickets-server.js"] }
249
+ },
250
+ "sandbox": { "requireIsolation": ["filesystem", "network"] }
251
+ }
29
252
  ```
30
253
 
31
- **Exit codes** (sysexits-aligned):
254
+ A pattern ending in `<space>*` also matches the bare command, so `git push *`
255
+ covers `git push`. A line that cannot be compiled is reported by name and the
256
+ rest still load — a permission somebody believes is in force and which was
257
+ silently dropped is the worst outcome available here.
258
+
259
+ **A permission mode only decides the calls no rule decided.** A rule that denied
260
+ a call already stopped it and a rule that allowed one never asked, so neither
261
+ reaches the mode: `--permission-mode` can never reopen a `deny`. The
262
+ dangerous-pattern floor sits above both, and no mode reaches that either — which
263
+ is why `--yolo` promises more than it delivers, on purpose.
32
264
 
33
- | Code | Name | Meaning |
34
- | ---: | --------------------- | ------------------------------------------- |
35
- | `0` | `EXIT_OK` | All checks passed (or no failure produced). |
36
- | `1` | `EXIT_FAIL` | One or more checks reported `fail`. |
37
- | `2` | `EXIT_NO_CONFIG` | No checks registered (Namzu not set up). |
38
- | `70` | `EXIT_INTERNAL_ERROR` | sysexits `EX_SOFTWARE`; CLI bug. |
265
+ ## `namzu doctor`
39
266
 
40
- **Built-in checks** ship intentionally conservative; consumers register their own via `registerDoctorCheck()`:
267
+ ```bash
268
+ namzu doctor # human-readable, every category
269
+ namzu doctor --json # machine-readable report
270
+ namzu doctor --category sandbox,runtime # sandbox, providers, vault, telemetry, runtime, plugins, custom
271
+ namzu doctor --per-check-timeout 8000 # default 5000
272
+ namzu doctor --wall-clock-timeout 20000 # default 10000
273
+ namzu doctor --verbose # repeat the failures, with their messages
274
+ ```
41
275
 
42
- - `sandbox.platform` — darwin sandbox-exec presence; warn on win32; inconclusive on linux.
43
- - `runtime.cwd-writable`, `runtime.tmpdir-writable` — `fs.access(W_OK)` probes.
44
- - `telemetry.installed` — pass if `@namzu/telemetry` is dynamically importable.
45
- - `vault.registered`, `providers.registered` — inconclusive with "register your own" guidance.
276
+ The built-in checks, in the order they are reported:
46
277
 
47
- ## Library API
278
+ | Check | Category | What it establishes |
279
+ |---|---|---|
280
+ | `sandbox.platform` | `sandbox` | What this host will actually confine — asked of the local sandbox provider, not answered from a table keyed on the OS name |
281
+ | `runtime.cwd-writable` | `runtime` | `W_OK` on the working directory |
282
+ | `runtime.tmpdir-writable` | `runtime` | `W_OK` on the temp directory |
283
+ | `providers.registered` | `providers` | Skipped: there is no provider auto-discovery, so a host registers its own check |
284
+ | `providers.credentials` | `providers` | Which credential sources were scanned, and what each yielded |
285
+ | `providers.chain` | `providers` | Which of the credentials found are actually wired into the chain, member by member |
286
+ | `vault.registered` | `vault` | Each registered credential provider's refs — *described*, never resolved, because this output gets pasted into issues |
287
+ | `sandbox.installed` | `sandbox` | `@namzu/sandbox`: absent, present, or installed and failing to load |
288
+ | `files.installed` | `custom` | `@namzu/files`, same three states |
289
+ | `computer-use.installed` | `custom` | `@namzu/computer-use`, same three states |
290
+ | `telemetry.installed` | `telemetry` | `@namzu/telemetry`, same three states |
291
+ | `logging.pipeline` | `custom` | What the log pipeline did to the records every check above just produced — dropped, redacted, truncated |
292
+ | `runtime.invariants` | `runtime` | Every registered invariant, folded with its violation counter |
293
+ | `telemetry.session-export` | `telemetry` | What this invocation's configuration would send off the machine, in a sentence |
294
+
295
+ Those four `*.installed` rows are the tri-state capability probe, not a
296
+ `try { await import() } catch`. Resolving and loading are asked separately so
297
+ that "not installed" and "installed and broken" cannot collapse into one answer:
298
+ the first is an optional package legitimately absent, the second is a machine
299
+ running degraded, and telling somebody who already has the package to install it
300
+ is useless advice.
301
+
302
+ Exit codes:
303
+
304
+ | Code | Meaning |
305
+ |---|---|
306
+ | `0` | Every check answered, and none of them failed |
307
+ | `1` | One or more checks reported `fail` |
308
+ | `2` | No checks registered — namzu is not configured here |
309
+ | `64` | An argument to `doctor` is wrong. Distinct from `70`: `70` says this CLI is broken and is worth a bug report, `64` says the invocation is |
310
+ | `69` | A check could not answer — it timed out, was aborted, or what it reads threw. Separate from `0` because a report that did not manage to look tells you nothing about the part it did look at, and separate from `1` because nothing was established to have failed |
311
+ | `70` | Internal CLI error |
312
+
313
+ A `skipped` check never moves the code off `0`. An optional package absent or a
314
+ registry with nothing to discover is an ordinary state of a healthy machine, and
315
+ a diagnostic that went non-zero on every healthy machine would be switched off
316
+ within a week.
317
+
318
+ The full page, including what each status word means, is
319
+ [`docs/cli/doctor.md`](../../docs/cli/doctor.md).
320
+
321
+ ## As a library
322
+
323
+ The whole shell, as one call:
48
324
 
49
325
  ```ts
50
- import { runDoctor, registerDoctorCheck, builtInDoctorChecks } from '@namzu/cli'
51
- import { ProviderRegistry } from '@namzu/sdk'
326
+ import { runCli } from '@namzu/cli'
327
+
328
+ process.exit(await runCli({ argv: process.argv }))
329
+ ```
330
+
331
+ The reason to run the doctor in-process rather than shelling out to the binary is
332
+ visibility: `registerDoctorCheck` writes to a process-wide registry, so a check
333
+ your application registers is only seen by a `runDoctor()` in the same process.
334
+
335
+ ```ts
336
+ import { registerDoctorCheck, runDoctor } from '@namzu/cli'
52
337
 
53
- // Register a custom check that walks YOUR provider registry
54
338
  registerDoctorCheck({
55
- id: 'app.providers.reachable',
56
- category: 'providers',
57
- run: async () => {
58
- const providers = ProviderRegistry.getAll()
59
- const results = await Promise.all(
60
- providers.map((p) => p.doctorCheck?.() ?? { status: 'inconclusive' as const }),
61
- )
62
- const failed = results.filter((r) => r.status === 'fail')
63
- return failed.length > 0
64
- ? { status: 'fail', message: `${failed.length} provider(s) failed reachability` }
65
- : { status: 'pass', message: `${providers.length} provider(s) reachable` }
66
- },
339
+ id: 'app.queue.reachable',
340
+ category: 'custom',
341
+ run: async () => {
342
+ const url = process.env.QUEUE_URL
343
+ if (!url) return { status: 'skipped', message: 'QUEUE_URL is not set' }
344
+ const response = await fetch(`${url}/health`)
345
+ return response.ok
346
+ ? { status: 'pass', message: `queue answered ${response.status}` }
347
+ : {
348
+ status: 'fail',
349
+ message: `queue answered ${response.status}`,
350
+ remediation: 'Check QUEUE_URL and the broker credentials.',
351
+ }
352
+ },
67
353
  })
68
354
 
69
355
  const report = await runDoctor()
70
356
  process.exit(report.exit)
71
357
  ```
72
358
 
73
- ## Architecture
359
+ `createDoctorRegistry()` returns an isolated registry for a test, and
360
+ `runDoctor({ registry })` runs against it instead of the singleton.
361
+ `builtInDoctorChecks` is the array the binary registers, exported so an embedder
362
+ can start from the same set. The individual checks are exported too —
363
+ `sandboxPlatformCheck`, `cwdWritableCheck`, `tmpdirWritableCheck`,
364
+ `providersRegisteredCheck`, `credentialSourcesCheck`, `providerChainCheck`,
365
+ `vaultRegisteredCheck`, `sandboxInstalledCheck`, `filesInstalledCheck`,
366
+ `computerUseInstalledCheck`, `telemetryInstalledCheck` — for registering a subset.
367
+
368
+ **Why the split runs where it does.** The doctor's protocol types
369
+ (`DoctorCheck`, `DoctorCheckResult`, `DoctorReport`, `DoctorStatus`) live in
370
+ `@namzu/sdk`, so a provider, a vault or a sandbox can implement a
371
+ `doctorCheck?()` hook against them without depending on an operator application.
372
+ The registry, the runner, the formatting and the exit codes live here, because
373
+ those are operator-facing concerns. The kernel owns the contract; this package
374
+ owns the presentation.
375
+
376
+ Also exported, and each of them is what the binary itself uses rather than a
377
+ parallel implementation:
378
+
379
+ ```ts
380
+ import {
381
+ createFormatter,
382
+ loadConfigWithProvenance,
383
+ NAMZU_OPTIONAL_CAPABILITIES,
384
+ probeCapabilities,
385
+ } from '@namzu/cli'
386
+
387
+ const { config, provenance } = loadConfigWithProvenance()
388
+ console.log(config.format, provenance.format) // e.g. 'json' { kind: 'env', variable: 'NAMZU_FORMAT' }
389
+
390
+ const out = createFormatter('json', { quiet: false })
391
+ out.print({ ready: true })
392
+
393
+ console.log(NAMZU_OPTIONAL_CAPABILITIES)
394
+ // ['@namzu/sandbox', '@namzu/files', '@namzu/computer-use', '@namzu/telemetry']
395
+
396
+ for (const probe of await probeCapabilities()) {
397
+ console.log(probe.specifier, probe.state) // 'present' | 'absent' | 'broken'
398
+ }
399
+ ```
400
+
401
+ `ConfigProvenance` names which cascade layer won each key, down to *which*
402
+ `NAMZU_*` variable it was — "env" alone would not tell an operator what to
403
+ change. `loadConfig()` is the same cascade without the provenance.
404
+ `probeOptionalPackage(specifier)` probes one package instead of all four.
405
+ `registerCommand` / `registerAll` add a `CommandDef` to a Commander program, and
406
+ `DEFAULT_CONFIG`, `ConfigLoadError`, `isFormatName` and `runDoctorCommand` round
407
+ out the surface.
408
+
409
+ ## Status
410
+
411
+ Published — the badge above is live, so it cannot go stale here — and dogfooded
412
+ in this repository, whose own `.namzu/` runtime state is written by this binary.
74
413
 
75
- The doctor's protocol types (`DoctorCheck`, `DoctorCheckResult`, `DoctorReport`, `DoctorStatus`) live in `@namzu/sdk` so kernel components (providers, vaults, sandboxes) can implement `doctorCheck?()` hooks against them. The runtime (registry, runner, output formatting, exit codes) lives here in `@namzu/cli` because it's operator-facing concerns. This is the protocol/runtime split: the kernel owns the contract, the operator surface owns the presentation.
414
+ **Majors move quickly.** *Any* backward-incompatible change to a public API is
415
+ treated as a major however small the diff, so the version number tracks the
416
+ surface rather than the size of the work, and it climbs faster than you may
417
+ expect. Pin your dependency and read the changelog. The library surface listed
418
+ above is held by a baseline check in CI, so a symbol cannot quietly leave the
419
+ barrel between releases — but it can leave loudly, in a major.
76
420
 
77
421
  ## License
78
422
 
79
- MIT
423
+ MIT.
package/dist/cli.d.ts CHANGED
@@ -6,9 +6,24 @@
6
6
  * errors to sysexits-aligned exit codes. The bootstrap in `bin.ts` calls
7
7
  * this and exits with the returned code.
8
8
  */
9
+ import { type ConfigProvenance } from './config/load.js';
10
+ import type { NamzuCliConfig } from './config/schema.js';
9
11
  export interface RunCliOptions {
10
12
  /** Argv with the leading `node` + script path, matching `process.argv` shape. */
11
13
  readonly argv: readonly string[];
12
14
  }
13
15
  export declare function runCli(opts: RunCliOptions): Promise<number>;
16
+ /**
17
+ * The CLI-process third of the boot narrative — the two other thirds
18
+ * (`namzu.sandbox.resolved`/`.provider.resolved`/`.capability.*`/
19
+ * `.discovery.completed`/`.boot.ready`, and the SDK's own
20
+ * `namzu.migration.completed`) belong to `createAgentSession`
21
+ * (`tui/agent.ts`) and `query()` respectively, because they describe facts
22
+ * an agent session resolves — `doctor` and `login` never reach either.
23
+ *
24
+ * Exported (not re-exported from `./index.ts`) so `__tests__/` can drive it
25
+ * directly with a hand-built `ConfigProvenance` and a capturing sink,
26
+ * without needing a live Commander parse or a real config cascade on disk.
27
+ */
28
+ export declare function emitBootNarrative(provenance: ConfigProvenance, config: NamzuCliConfig): void;
14
29
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiDH,MAAM,WAAW,aAAa;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAqHjE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA+BH,OAAO,EAEN,KAAK,gBAAgB,EAGrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AA6BxD,MAAM,WAAW,aAAa;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA6JjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CA0E5F"}