@namzu/cli 11.0.0 → 12.0.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 +390 -46
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +132 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +29 -0
- package/dist/commands/acp.d.ts.map +1 -0
- package/dist/commands/acp.js +156 -0
- package/dist/commands/acp.js.map +1 -0
- package/dist/commands/doctor.d.ts +12 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/drain.d.ts +2 -2
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +31 -8
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/run-flags.d.ts.map +1 -1
- package/dist/commands/run-flags.js +33 -0
- package/dist/commands/run-flags.js.map +1 -1
- package/dist/commands/run-stream.d.ts +3 -3
- package/dist/commands/run-stream.d.ts.map +1 -1
- package/dist/commands/run-stream.js +27 -10
- package/dist/commands/run-stream.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +50 -4
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/types.d.ts +10 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config/load.d.ts +64 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +137 -20
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/capabilities.d.ts +88 -0
- package/dist/context/capabilities.d.ts.map +1 -0
- package/dist/context/capabilities.js +160 -0
- package/dist/context/capabilities.js.map +1 -0
- package/dist/context/sandbox.d.ts +13 -0
- package/dist/context/sandbox.d.ts.map +1 -1
- package/dist/context/sandbox.js +15 -0
- package/dist/context/sandbox.js.map +1 -1
- package/dist/doctor/checks/index.d.ts +6 -1
- package/dist/doctor/checks/index.d.ts.map +1 -1
- package/dist/doctor/checks/index.js +45 -2
- package/dist/doctor/checks/index.js.map +1 -1
- package/dist/doctor/checks/invariants.d.ts +23 -0
- package/dist/doctor/checks/invariants.d.ts.map +1 -0
- package/dist/doctor/checks/invariants.js +79 -0
- package/dist/doctor/checks/invariants.js.map +1 -0
- package/dist/doctor/checks/logging.d.ts +10 -0
- package/dist/doctor/checks/logging.d.ts.map +1 -0
- package/dist/doctor/checks/logging.js +69 -0
- package/dist/doctor/checks/logging.js.map +1 -0
- package/dist/doctor/checks/session-export.d.ts +21 -0
- package/dist/doctor/checks/session-export.d.ts.map +1 -0
- package/dist/doctor/checks/session-export.js +62 -0
- package/dist/doctor/checks/session-export.js.map +1 -0
- package/dist/doctor/checks/telemetry.d.ts +15 -28
- package/dist/doctor/checks/telemetry.d.ts.map +1 -1
- package/dist/doctor/checks/telemetry.js +34 -54
- package/dist/doctor/checks/telemetry.js.map +1 -1
- package/dist/doctor/checks/vault.d.ts +4 -20
- package/dist/doctor/checks/vault.d.ts.map +1 -1
- package/dist/doctor/checks/vault.js +58 -19
- package/dist/doctor/checks/vault.js.map +1 -1
- package/dist/doctor/registry.d.ts.map +1 -1
- package/dist/doctor/registry.js +13 -6
- package/dist/doctor/registry.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/files/attachment-store.d.ts +44 -0
- package/dist/integrations/files/attachment-store.d.ts.map +1 -0
- package/dist/integrations/files/attachment-store.js +86 -0
- package/dist/integrations/files/attachment-store.js.map +1 -0
- package/dist/integrations/providers/credential-provider.d.ts +39 -0
- package/dist/integrations/providers/credential-provider.d.ts.map +1 -0
- package/dist/integrations/providers/credential-provider.js +74 -0
- package/dist/integrations/providers/credential-provider.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts.map +1 -1
- package/dist/integrations/providers/discover.js +15 -3
- package/dist/integrations/providers/discover.js.map +1 -1
- package/dist/integrations/providers/oauth.d.ts.map +1 -1
- package/dist/integrations/providers/oauth.js +24 -1
- package/dist/integrations/providers/oauth.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts +2 -2
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +21 -9
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +3 -3
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +12 -12
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/telemetry/session-export.d.ts +99 -0
- package/dist/integrations/telemetry/session-export.d.ts.map +1 -0
- package/dist/integrations/telemetry/session-export.js +119 -0
- package/dist/integrations/telemetry/session-export.js.map +1 -0
- package/dist/logging.d.ts +79 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +67 -0
- package/dist/logging.js.map +1 -0
- package/dist/permissions/rules.d.ts +3 -3
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +1 -1
- package/dist/permissions/rules.js.map +1 -1
- package/dist/skills/store.d.ts +1 -1
- package/dist/skills/store.js +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +92 -6
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +1 -1
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/agent.d.ts +54 -24
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +330 -127
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +17 -6
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/log-pane.d.ts +48 -0
- package/dist/tui/log-pane.d.ts.map +1 -0
- package/dist/tui/log-pane.js +106 -0
- package/dist/tui/log-pane.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +134 -11
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +169 -15
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/types.d.ts +11 -2
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,79 +1,423 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
15
|
+
<div align="center">
|
|
4
16
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+

|
|
22
|
+
[](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
|
-
|
|
13
|
-
#
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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"}
|