@dashclaw/cli 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,183 +1,210 @@
1
- # @dashclaw/cli
2
-
3
- Terminal client for [DashClaw](https://dashclaw.io) — approve agent actions and diagnose your instance without leaving the shell.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g @dashclaw/cli
9
- ```
10
-
11
- ## Configure
12
-
13
- The CLI resolves config in this order:
14
-
15
- 1. Environment variables (`DASHCLAW_BASE_URL`, `DASHCLAW_API_KEY`, optional `DASHCLAW_AGENT_ID`)
16
- 2. Saved config at `~/.dashclaw/config.json` (mode `600`)
17
- 3. Interactive prompt (first run)
18
-
19
- On first run, if neither env vars nor a saved config are present, the CLI walks you through setup and offers to save the values to `~/.dashclaw/config.json`. Env vars always override saved values.
20
-
21
- ```bash
22
- # Option A: env vars (one-shot or CI)
23
- export DASHCLAW_BASE_URL="https://your-dashclaw.example.com"
24
- export DASHCLAW_API_KEY="oc_live_..."
25
-
26
- # Option B: interactive first run (persists)
27
- dashclaw doctor
28
- # → DashClaw instance URL: ...
29
- # → API key: ********
30
- # → Save to ~/.dashclaw/config.json? [Y/n]
31
-
32
- # Later: remove the saved config
33
- dashclaw logout
34
- ```
35
-
36
- Optionally set `DASHCLAW_AGENT_ID` (defaults to `cli-operator`) for audit attribution.
37
-
38
- ## Commands
39
-
40
- ### `dashclaw approvals`
41
-
42
- Interactive inbox for all pending approval requests. Use arrow keys to navigate, `A` to approve, `D` to deny, `O` to open the replay link, `Q` to quit.
43
-
44
- ### `dashclaw approve <actionId>`
45
-
46
- Approve a single action by ID.
47
-
48
- ```bash
49
- dashclaw approve act_01h... --reason "Verified change window"
50
- ```
51
-
52
- ### `dashclaw deny <actionId>`
53
-
54
- Deny a single action by ID.
55
-
56
- ```bash
57
- dashclaw deny act_01h... --reason "Outside change window"
58
- ```
59
-
60
- ### `dashclaw install claude`
61
-
62
- Provision DashClaw governance into Claude Code without cloning the repo.
63
-
64
- ```bash
65
- dashclaw install claude # prompts for endpoint + API key
66
- dashclaw install claude --endpoint <url> --key <oc_live_...>
67
- dashclaw install claude --trial # browser signup on a hosted instance, paste the key
68
- ```
69
-
70
- Flow: preflight (`/api/health` + an authenticated read — nothing is written until it passes), hooks downloaded from your instance's `/downloads/dashclaw-claude-code-hooks.zip` (or copied from a repo checkout), `python3`/`python` resolved automatically, managed hook entries merged into `~/.claude/settings.json` (`.dashclaw-bak` backup, replace-on-reinstall), credentials written to `~/.dashclaw/claude-hooks/.env` (mode 600 — no secret lands in settings.json). Installs in **observe mode**; flip to enforce by setting `DASHCLAW_HOOK_MODE=enforce` in that `.env`. For `--trial`, set `DASHCLAW_HOSTED_URL` (or `--endpoint`) to the hosted instance if it isn't prompted.
71
-
72
- ### `dashclaw cost`
73
-
74
- Spend readback from `GET /api/finops/spend` — your own Claude Code cost or the fleet rollup, straight from the terminal.
75
-
76
- ```bash
77
- dashclaw cost # Claude Code spend, last 7 days (defaults)
78
- dashclaw cost --lens claude-code --period 30d
79
- dashclaw cost --lens fleet --period 90d # agent LLM cost + x402 purchases
80
- ```
81
-
82
- Outputs an aligned table (total, sessions, cache savings, by-project breakdown for the Claude-Code lens; Agent LLM / x402 / Total for the Fleet lens) plus a one-line summary. Unconfigured exits non-zero with a `dashclaw install claude` hint; bad `--lens`/`--period` values are rejected with usage text.
83
-
84
- ### `dashclaw doctor`
85
-
86
- Diagnose your DashClaw instance **and this machine**. Remote checks cover database, configuration, auth, deployment, SDK reachability, governance, and data hygiene; local checks cover what the server can't see — a stale compiled `mcp-server/lib`, `.gitattributes` drift, a local DB schema behind code, a disabled OpenClaw runtime plugin, a stale global CLI shim, broken Claude hook installs, and leaked machine-scope `DASHCLAW_*` env vars.
87
-
88
- ```bash
89
- dashclaw doctor # report-only (DEFAULT applies nothing)
90
- dashclaw doctor --fix # apply safe auto-fixes, re-check, report what changed
91
- dashclaw doctor --json # JSON output for CI/scripts (includes local checks)
92
- dashclaw doctor --category database,config # filter remote checks
93
- dashclaw doctor --repo /path/to/dashclaw # point repo checks at a checkout
94
- dashclaw doctor --no-fix # accepted no-op alias (report-only is the default)
95
- ```
96
-
97
- > **Changed in 0.4.0:** `dashclaw doctor` no longer applies remote auto-fixes by defaultit reports and prints would-fix entries. Pass `--fix` to apply. Exit codes are unchanged (0 healthy, 1 otherwise).
98
-
99
- Detect-only classes are never auto-fixed: leaked machine env vars (removal instructions printed) and OpenClaw gateway configs (remediation text printed). The `.gitattributes` restore runs only when the diff is provably line-ending/whitespace-only. Remote fixes go through `POST /api/doctor/fix`; fixes that need server filesystem access remain self-hoster territory via `npm run doctor` (also report-only by default now, same `--fix` opt-in).
100
-
101
- ### `dashclaw code`
102
-
103
- Subcommand group for Code Sessions (Claude Code analytics, ported from AgentLens). Three actions:
104
-
105
- ```bash
106
- dashclaw code ingest # walk ~/.claude/projects and POST every .jsonl
107
- dashclaw code ingest --dry-run # preview the file list and payload shape without POSTing
108
- dashclaw code ingest --projects-dir <p> # override the default projects directory (also reads $CLAUDE_PROJECTS_DIR)
109
-
110
- dashclaw code memo --project <slug> # show the most recent weekly memo for a project
111
- dashclaw code memo --project <slug> --save # write the memo to ./memos/<iso-week>.md
112
-
113
- dashclaw code apply <manifest-id> --dest=<project-cwd> # apply an Optimal Files manifest locally
114
- dashclaw code apply <manifest-id> --dest=<project-cwd> --dry-run
115
- ```
116
-
117
- `ingest` stream-reads each session JSONL line-by-line and ships raw lines; large request bodies are brotli-compressed on the wire (via the `x-dashclaw-encoding: br` header — no base64 inflation) to fit Vercel's 4.5 MB per-request limit. It retries 429s and 5xxs with exponential backoff and throttles 150 ms between POSTs. Files over 40 MB raw are skipped with a `too_large` log entry. Never logs raw transcript content.
118
-
119
- There is also `dashclaw code ingest-codex [--dry-run]`, which backfills Codex CLI transcripts from `~/.codex/sessions` (`--sessions-dir <path>` to override; `--out <dir>` for the local output directory).
120
-
121
- `apply` fetches a manifest from `/api/code-sessions/manifests/<id>`, re-runs the secret scan, and writes the bundled files to `--dest`. Existing files get a three-way merge via the section-aware markdown merger; new files are written directly. Refuses any path outside `--dest` (path-traversal guard).
122
-
123
- ### `dashclaw install codex`
124
-
125
- Provision DashClaw governance into the Codex CLI: writes an AGENTS.md governance block into the target project and (optionally) wires Codex's notify config.
126
-
127
- ```bash
128
- dashclaw install codex --project <path> # default: current directory
129
- dashclaw install codex --approval-policy on-request
130
- dashclaw install codex --include-notify # also wire notify dashclaw codex notify
131
- ```
132
-
133
- ### `dashclaw codex notify '<json>'`
134
-
135
- Records a Codex turn-complete event as a DashClaw action record. Called by Codex's notify config (wired by `install codex --include-notify`); always exits 0 so Codex never sees an error from the spawn.
136
-
137
- ### `dashclaw prompts`
138
-
139
- Prompt-library client: `list [--category C]`, `get <id>`, `versions <id>`, `render <templateId> [--version-id <vid>] [--var key=value] [--record]`, `create --name N --description D --category C` (admin), `add-version <id> --content C` (admin), `activate <id> <vid>` (admin), `stats [--template-id X]`.
140
-
141
- ### `dashclaw inbox`
142
-
143
- Agent message inbox: `inbox list [--unread] [--limit N]`, `inbox read <id> [...]`, `inbox archive <id> [...]`.
144
-
145
- ### `dashclaw behavior`
146
-
147
- Behavior Learning readback: `behavior status` (local recorder sample counts) and `behavior suggestions [--agent-id <id>]` (evidence-backed policy suggestions).
148
-
149
- ### `dashclaw posture` / `dashclaw next`
150
-
151
- `posture` prints the governance posture score and the remediation queue; `posture resolve <key> [--snooze | --accept-risk] [--note "..."]` dispositions a finding. `next` prints the single top open governance gap and its fix.
152
-
153
- ### `dashclaw env`
154
-
155
- Run a command with managed secrets injected in-memory (values never touch disk or the terminal):
156
-
157
- ```bash
158
- dashclaw env [--agent <id>] -- node my-agent.js # inject managed secrets into the child env
159
- dashclaw env # list secret NAMES + count (never values)
160
- ```
161
-
162
- Fail-closed: if the secrets fetch fails, the child is not spawned.
163
-
164
- ### `dashclaw logout`
165
-
166
- Remove the saved config at `~/.dashclaw/config.json`.
167
-
168
- ### `dashclaw version`
169
-
170
- Print the CLI version (`--version` / `-v` also work).
171
-
172
- ### `dashclaw help`
173
-
174
- Show all commands and flags.
175
-
176
- ## Exit codes
177
-
178
- - `0` healthy
179
- - `1` — warnings present, failures, or the instance was unreachable
180
-
181
- ## License
182
-
183
- MIT.
1
+ # @dashclaw/cli
2
+
3
+ Terminal client for [DashClaw](https://dashclaw.io) — approve agent actions and diagnose your instance without leaving the shell.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @dashclaw/cli
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ The CLI resolves config in this order:
14
+
15
+ 1. Environment variables (`DASHCLAW_BASE_URL`, `DASHCLAW_API_KEY`, optional `DASHCLAW_AGENT_ID`)
16
+ 2. Saved config at `~/.dashclaw/config.json` (mode `600`)
17
+ 3. Interactive prompt (first run)
18
+
19
+ On first run, if neither env vars nor a saved config are present, the CLI walks you through setup and offers to save the values to `~/.dashclaw/config.json`. Env vars always override saved values.
20
+
21
+ ```bash
22
+ # Option A: env vars (one-shot or CI)
23
+ export DASHCLAW_BASE_URL="https://your-dashclaw.example.com"
24
+ export DASHCLAW_API_KEY="oc_live_..."
25
+
26
+ # Option B: interactive first run (persists)
27
+ dashclaw doctor
28
+ # → DashClaw instance URL: ...
29
+ # → API key: ********
30
+ # → Save to ~/.dashclaw/config.json? [Y/n]
31
+
32
+ # Later: remove the saved config
33
+ dashclaw logout
34
+ ```
35
+
36
+ Optionally set `DASHCLAW_AGENT_ID` (defaults to `cli-operator`) for audit attribution.
37
+
38
+ ## Commands
39
+
40
+ ### `dashclaw up`
41
+
42
+ Install + start a local DashClaw (one command, resumable).
43
+
44
+ ```bash
45
+ npx dashclaw up
46
+ npx dashclaw up --update # upgrade an existing install in place
47
+ npx dashclaw up --yes # non-interactive, accept all defaults
48
+ npx dashclaw up --no-browser # skip opening /setup in the browser
49
+ npx dashclaw up --db docker # force Docker Postgres
50
+ npx dashclaw up --db embedded # force embedded Postgres (~40 MB download)
51
+ npx dashclaw up --db <url> # use an existing postgresql:// connection string
52
+ npx dashclaw up --dir <path> # install to a custom directory (default: ~/.dashclaw)
53
+ npx dashclaw up --port <n> # bind to a custom port (default: 3000)
54
+ npx dashclaw up --source-dir <path> # use a local repo checkout instead of the published tarball
55
+ ```
56
+
57
+ Re-running `npx dashclaw up` on an existing install boots the server without re-provisioning. Failures checkpoint and resume from where they stopped.
58
+
59
+ ### `dashclaw down`
60
+
61
+ Stop the local server (and the Docker DB if `up` started it).
62
+
63
+ ```bash
64
+ npx dashclaw down
65
+ ```
66
+
67
+ ### `dashclaw approvals`
68
+
69
+ Interactive inbox for all pending approval requests. Use arrow keys to navigate, `A` to approve, `D` to deny, `O` to open the replay link, `Q` to quit.
70
+
71
+ ### `dashclaw approve <actionId>`
72
+
73
+ Approve a single action by ID.
74
+
75
+ ```bash
76
+ dashclaw approve act_01h... --reason "Verified change window"
77
+ ```
78
+
79
+ ### `dashclaw deny <actionId>`
80
+
81
+ Deny a single action by ID.
82
+
83
+ ```bash
84
+ dashclaw deny act_01h... --reason "Outside change window"
85
+ ```
86
+
87
+ ### `dashclaw install claude`
88
+
89
+ Provision DashClaw governance into Claude Code without cloning the repo.
90
+
91
+ ```bash
92
+ dashclaw install claude # prompts for endpoint + API key
93
+ dashclaw install claude --endpoint <url> --key <oc_live_...>
94
+ dashclaw install claude --trial # browser signup on hosted.dashclaw.io, paste the key
95
+ ```
96
+
97
+ Flow: preflight (`/api/health` + an authenticated read — nothing is written until it passes), hooks downloaded from your instance's `/downloads/dashclaw-claude-code-hooks.zip` (or copied from a repo checkout), `python3`/`python` resolved automatically, managed hook entries merged into `~/.claude/settings.json` (`.dashclaw-bak` backup, replace-on-reinstall), credentials written to `~/.dashclaw/claude-hooks/.env` (mode 600no secret lands in settings.json). Installs in **observe mode**; flip to enforce by setting `DASHCLAW_HOOK_MODE=enforce` in that `.env`. `--trial` defaults to the public trial at `https://hosted.dashclaw.io`; point it at your own hosted instance with `--endpoint <url>` (or `DASHCLAW_HOSTED_URL`).
98
+
99
+ ### `dashclaw cost`
100
+
101
+ Spend readback from `GET /api/finops/spend` — your own Claude Code cost or the fleet rollup, straight from the terminal.
102
+
103
+ ```bash
104
+ dashclaw cost # Claude Code spend, last 7 days (defaults)
105
+ dashclaw cost --lens claude-code --period 30d
106
+ dashclaw cost --lens fleet --period 90d # agent LLM cost + x402 purchases
107
+ ```
108
+
109
+ Outputs an aligned table (total, sessions, cache savings, by-project breakdown for the Claude-Code lens; Agent LLM / x402 / Total for the Fleet lens) plus a one-line summary. Unconfigured exits non-zero with a `dashclaw install claude` hint; bad `--lens`/`--period` values are rejected with usage text.
110
+
111
+ ### `dashclaw doctor`
112
+
113
+ Diagnose your DashClaw instance **and this machine**. Remote checks cover database, configuration, auth, deployment, SDK reachability, governance, and data hygiene; local checks cover what the server can't see — a stale compiled `mcp-server/lib`, `.gitattributes` drift, a local DB schema behind code, a disabled OpenClaw runtime plugin, a stale global CLI shim, broken Claude hook installs, and leaked machine-scope `DASHCLAW_*` env vars.
114
+
115
+ ```bash
116
+ dashclaw doctor # report-only (DEFAULT — applies nothing)
117
+ dashclaw doctor --fix # apply safe auto-fixes, re-check, report what changed
118
+ dashclaw doctor --json # JSON output for CI/scripts (includes local checks)
119
+ dashclaw doctor --category database,config # filter remote checks
120
+ dashclaw doctor --repo /path/to/dashclaw # point repo checks at a checkout
121
+ dashclaw doctor --no-fix # accepted no-op alias (report-only is the default)
122
+ ```
123
+
124
+ > **Changed in 0.4.0:** `dashclaw doctor` no longer applies remote auto-fixes by default — it reports and prints would-fix entries. Pass `--fix` to apply. Exit codes are unchanged (0 healthy, 1 otherwise).
125
+
126
+ Detect-only classes are never auto-fixed: leaked machine env vars (removal instructions printed) and OpenClaw gateway configs (remediation text printed). The `.gitattributes` restore runs only when the diff is provably line-ending/whitespace-only. Remote fixes go through `POST /api/doctor/fix`; fixes that need server filesystem access remain self-hoster territory via `npm run doctor` (also report-only by default now, same `--fix` opt-in).
127
+
128
+ ### `dashclaw code`
129
+
130
+ Subcommand group for Code Sessions (Claude Code analytics, ported from AgentLens). Three actions:
131
+
132
+ ```bash
133
+ dashclaw code ingest # walk ~/.claude/projects and POST every .jsonl
134
+ dashclaw code ingest --dry-run # preview the file list and payload shape without POSTing
135
+ dashclaw code ingest --projects-dir <p> # override the default projects directory (also reads $CLAUDE_PROJECTS_DIR)
136
+
137
+ dashclaw code memo --project <slug> # show the most recent weekly memo for a project
138
+ dashclaw code memo --project <slug> --save # write the memo to ./memos/<iso-week>.md
139
+
140
+ dashclaw code apply <manifest-id> --dest=<project-cwd> # apply an Optimal Files manifest locally
141
+ dashclaw code apply <manifest-id> --dest=<project-cwd> --dry-run
142
+ ```
143
+
144
+ `ingest` stream-reads each session JSONL line-by-line and ships raw lines; large request bodies are brotli-compressed on the wire (via the `x-dashclaw-encoding: br` header — no base64 inflation) to fit Vercel's 4.5 MB per-request limit. It retries 429s and 5xxs with exponential backoff and throttles 150 ms between POSTs. Files over 40 MB raw are skipped with a `too_large` log entry. Never logs raw transcript content.
145
+
146
+ There is also `dashclaw code ingest-codex [--dry-run]`, which backfills Codex CLI transcripts from `~/.codex/sessions` (`--sessions-dir <path>` to override; `--out <dir>` for the local output directory).
147
+
148
+ `apply` fetches a manifest from `/api/code-sessions/manifests/<id>`, re-runs the secret scan, and writes the bundled files to `--dest`. Existing files get a three-way merge via the section-aware markdown merger; new files are written directly. Refuses any path outside `--dest` (path-traversal guard).
149
+
150
+ ### `dashclaw install codex`
151
+
152
+ Provision DashClaw governance into the Codex CLI: writes an AGENTS.md governance block into the target project and (optionally) wires Codex's notify config.
153
+
154
+ ```bash
155
+ dashclaw install codex --project <path> # default: current directory
156
+ dashclaw install codex --approval-policy on-request
157
+ dashclaw install codex --include-notify # also wire notify → dashclaw codex notify
158
+ ```
159
+
160
+ ### `dashclaw codex notify '<json>'`
161
+
162
+ Records a Codex turn-complete event as a DashClaw action record. Called by Codex's notify config (wired by `install codex --include-notify`); always exits 0 so Codex never sees an error from the spawn.
163
+
164
+ ### `dashclaw prompts`
165
+
166
+ Prompt-library client: `list [--category C]`, `get <id>`, `versions <id>`, `render <templateId> [--version-id <vid>] [--var key=value] [--record]`, `create --name N --description D --category C` (admin), `add-version <id> --content C` (admin), `activate <id> <vid>` (admin), `stats [--template-id X]`.
167
+
168
+ ### `dashclaw inbox`
169
+
170
+ Agent message inbox: `inbox list [--unread] [--limit N]`, `inbox read <id> [...]`, `inbox archive <id> [...]`.
171
+
172
+ ### `dashclaw behavior`
173
+
174
+ Behavior Learning readback: `behavior status` (local recorder sample counts) and `behavior suggestions [--agent-id <id>]` (evidence-backed policy suggestions).
175
+
176
+ ### `dashclaw posture` / `dashclaw next`
177
+
178
+ `posture` prints the governance posture score and the remediation queue; `posture resolve <key> [--snooze | --accept-risk] [--note "..."]` dispositions a finding. `next` prints the single top open governance gap and its fix.
179
+
180
+ ### `dashclaw env`
181
+
182
+ Run a command with managed secrets injected in-memory (values never touch disk or the terminal):
183
+
184
+ ```bash
185
+ dashclaw env [--agent <id>] -- node my-agent.js # inject managed secrets into the child env
186
+ dashclaw env # list secret NAMES + count (never values)
187
+ ```
188
+
189
+ Fail-closed: if the secrets fetch fails, the child is not spawned.
190
+
191
+ ### `dashclaw logout`
192
+
193
+ Remove the saved config at `~/.dashclaw/config.json`.
194
+
195
+ ### `dashclaw version`
196
+
197
+ Print the CLI version (`--version` / `-v` also work).
198
+
199
+ ### `dashclaw help`
200
+
201
+ Show all commands and flags.
202
+
203
+ ## Exit codes
204
+
205
+ - `0` — healthy
206
+ - `1` — warnings present, failures, or the instance was unreachable
207
+
208
+ ## License
209
+
210
+ MIT.
package/bin/dashclaw.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  } from '../lib/code/ingest-codex.js';
23
23
  import { installCodex, codexConfigPath, codexHooksDir } from '../lib/codex/install.js';
24
24
  import { installClaude } from '../lib/claude/install.js';
25
+ import { upCommand, runDown, resolveBaseDir } from '../lib/up/index.js';
25
26
  import { runCost } from '../lib/cost.js';
26
27
  import { runCodexNotify } from '../lib/codex/notify.js';
27
28
  import { apiRequest } from '../lib/api.js';
@@ -78,9 +79,17 @@ async function cmdHelp() {
78
79
  ${bold('DashClaw CLI')} — terminal approval client
79
80
 
80
81
  ${bold('Usage:')}
82
+ dashclaw up Install + start a local DashClaw (one command, resumable)
83
+ --update Re-fetch + rebuild from the latest published version
84
+ --yes Non-interactive (auto-pick DB, auto-connect Claude Code)
85
+ --db docker|embedded|url Database mode (default: prompt; docker if detected)
86
+ --port <n> Server port (default: 3000)
87
+ --no-browser Do not open the browser when ready
88
+ dashclaw down Stop the local DashClaw server (and Docker DB if we started it)
81
89
  dashclaw approvals Interactive approval inbox
82
90
  dashclaw approve <actionId> [--reason] Approve an action
83
91
  dashclaw deny <actionId> [--reason] Deny an action
92
+ dashclaw halt on|off|status [--reason] Org kill switch: halt/resume every governed action (admin)
84
93
  dashclaw doctor Diagnose your instance + this machine (report-only)
85
94
  --fix Apply safe auto-fixes, then re-check and report
86
95
  --json Output as JSON (for CI/scripts)
@@ -101,7 +110,8 @@ ${bold('Usage:')}
101
110
  --allow-redactions Write files that contain redacted secret patterns
102
111
  --overwrite Clobber existing .NEW side-by-side files
103
112
  dashclaw install claude [--trial] Provision DashClaw governance into Claude Code
104
- --endpoint <url> Your DashClaw instance URL (or DASHCLAW_BASE_URL)
113
+ --endpoint <url> Your DashClaw instance URL (or DASHCLAW_BASE_URL;
114
+ --trial defaults to https://hosted.dashclaw.io)
105
115
  --key <key> API key (or DASHCLAW_API_KEY; --trial prompts via browser signup)
106
116
  --agent-id <id> Agent id for governed actions (default: claude-code)
107
117
  dashclaw install codex Provision DashClaw governance into Codex CLI
@@ -447,6 +457,54 @@ async function cmdInstallCodex() {
447
457
  }
448
458
  }
449
459
 
460
+ async function cmdHalt() {
461
+ const sub = args[1];
462
+ if (!['on', 'off', 'status'].includes(sub || '')) {
463
+ console.error('Usage: dashclaw halt on|off|status [--reason "<why>"]');
464
+ process.exitCode = 1;
465
+ return;
466
+ }
467
+ try {
468
+ if (sub === 'status') {
469
+ const { halt } = await apiRequest({ baseUrl, apiKey }, 'GET', '/api/halt');
470
+ if (halt?.halted) {
471
+ console.log(`
472
+ ${red('HALTED')} — every governed action for this org is blocked.`);
473
+ console.log(` By: ${halt.actor || 'admin'}`);
474
+ console.log(` Reason: ${halt.reason || '(none given)'}`);
475
+ console.log(` Since: ${halt.at || 'unknown'}
476
+ `);
477
+ } else {
478
+ console.log(`
479
+ ${green('Running')} — org is not halted.
480
+ `);
481
+ }
482
+ return;
483
+ }
484
+ const halted = sub === 'on';
485
+ const reason = getFlag('--reason') || null;
486
+ const { halt } = await apiRequest({ baseUrl, apiKey }, 'POST', '/api/halt', { body: { halted, reason } });
487
+ if (halted) {
488
+ console.log(`
489
+ ${red('HALTED')} — every governed action for this org now blocks immediately.`);
490
+ if (halt?.reason) console.log(` Reason: ${halt.reason}`);
491
+ console.log(` Resume with: dashclaw halt off
492
+ `);
493
+ } else {
494
+ console.log(`
495
+ ${green('Resumed')} — guard evaluation is back to normal.
496
+ `);
497
+ }
498
+ } catch (err) {
499
+ if (err.status === 401 || err.status === 403) {
500
+ console.error('Rejected (admin access required). The kill switch needs an admin API key.');
501
+ } else {
502
+ console.error(`Halt request failed: ${err.message}`);
503
+ }
504
+ process.exitCode = 1;
505
+ }
506
+ }
507
+
450
508
  async function cmdCost() {
451
509
  const lens = getFlag('--lens') || 'claude-code';
452
510
  const period = getFlag('--period') || '7d';
@@ -507,6 +565,31 @@ async function cmdInstall() {
507
565
  }
508
566
  }
509
567
 
568
+ // -- up / down (one-command local install) -----------------------------------
569
+
570
+ async function cmdUp() {
571
+ try {
572
+ await upCommand(args.slice(1));
573
+ } catch (err) {
574
+ console.error(red(`Error: ${err.message}`));
575
+ process.exitCode = 1;
576
+ }
577
+ }
578
+
579
+ async function cmdDown() {
580
+ try {
581
+ // Parse --dir so `dashclaw down --dir /path` targets the right state directory.
582
+ const downArgv = args.slice(1);
583
+ const dirIdx = downArgv.indexOf('--dir');
584
+ const dir = dirIdx !== -1 ? downArgv[dirIdx + 1] : null;
585
+ const baseDir = resolveBaseDir({ dir });
586
+ await runDown({ baseDir });
587
+ } catch (err) {
588
+ console.error(red(`Error: ${err.message}`));
589
+ process.exitCode = 1;
590
+ }
591
+ }
592
+
510
593
  // -- codex subcommand group --------------------------------------------------
511
594
  //
512
595
  // `dashclaw codex notify '<json>'` is invoked by Codex's legacy notify config.
@@ -1215,7 +1298,7 @@ async function cmdEnv() {
1215
1298
 
1216
1299
  // -- Router -------------------------------------------------------------------
1217
1300
 
1218
- const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'code', 'prompts', 'inbox', 'behavior', 'posture', 'next', 'env']);
1301
+ const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'code', 'prompts', 'inbox', 'behavior', 'posture', 'next', 'env', 'halt']);
1219
1302
  // `install` deliberately omitted: provisioning hooks and AGENTS.md shouldn't
1220
1303
  // require the user to have already configured API keys. If config happens to
1221
1304
  // be present, install will pick up baseUrl for the AGENTS.md instance link.
@@ -1259,6 +1342,8 @@ const COMMAND_HANDLERS = {
1259
1342
  doctor: cmdDoctor,
1260
1343
  logout: cmdLogout,
1261
1344
  code: cmdCode,
1345
+ up: cmdUp,
1346
+ down: cmdDown,
1262
1347
  install: cmdInstall,
1263
1348
  codex: cmdCodex,
1264
1349
  prompts: cmdPrompts,
@@ -1266,6 +1351,7 @@ const COMMAND_HANDLERS = {
1266
1351
  behavior: cmdBehavior,
1267
1352
  posture: cmdPosture,
1268
1353
  cost: cmdCost,
1354
+ halt: cmdHalt,
1269
1355
  next: cmdNext,
1270
1356
  env: cmdEnv,
1271
1357
  help: cmdHelp,
@@ -52,6 +52,11 @@ const HOOKS_BUNDLE_PATH = '/downloads/dashclaw-claude-code-hooks.zip';
52
52
 
53
53
  export const DEFAULT_AGENT_ID = 'claude-code';
54
54
 
55
+ // The public hosted trial instance. `--trial` falls back to it when no
56
+ // --endpoint / DASHCLAW_HOSTED_URL is given — a cold outsider following
57
+ // QUICK-START has no way to answer a "which URL?" prompt (v5.4 outsider run).
58
+ export const DEFAULT_HOSTED_TRIAL_URL = 'https://hosted.dashclaw.io';
59
+
55
60
  // ---------------------------------------------------------------------------
56
61
  // Python resolution
57
62
  // ---------------------------------------------------------------------------
@@ -182,8 +187,8 @@ async function downloadHooksBundle(endpoint, hooksDst, { fetchImpl = fetch } = {
182
187
  // ---------------------------------------------------------------------------
183
188
 
184
189
  const HOOK_EVENTS = {
185
- PreToolUse: { matcher: 'Agent|Task|Bash|Edit|Write|MultiEdit|Skill|mcp__.*', script: 'dashclaw_pretool.py', timeout: 3600000 },
186
- PostToolUse: { matcher: 'Agent|Task|Bash|Edit|Write|MultiEdit|mcp__.*', script: 'dashclaw_posttool.py' },
190
+ PreToolUse: { matcher: 'Agent|Task|Workflow|Bash|Edit|Write|MultiEdit|Skill|mcp__.*', script: 'dashclaw_pretool.py', timeout: 3600000 },
191
+ PostToolUse: { matcher: 'Agent|Task|Workflow|Bash|Edit|Write|MultiEdit|mcp__.*', script: 'dashclaw_posttool.py' },
187
192
  Stop: { script: 'dashclaw_stop.py' },
188
193
  };
189
194
 
@@ -192,12 +197,15 @@ export function isManagedHookEntry(entry) {
192
197
  }
193
198
 
194
199
  /** Build the managed hook entries pointing at <hooksDir> via <python>. */
195
- export function buildHookEntries(hooksDir, python) {
200
+ export function buildHookEntries(hooksDir, python, agentId = DEFAULT_AGENT_ID) {
196
201
  const entries = {};
197
202
  for (const [event, spec] of Object.entries(HOOK_EVENTS)) {
198
203
  const hook = {
199
204
  type: 'command',
200
- command: `${python} "${join(hooksDir, spec.script)}"`,
205
+ // --agent-id is the per-harness identity declaration (roadmap v2.2):
206
+ // argv beats the machine-ambient DASHCLAW_AGENT_ID env var, so this
207
+ // install keeps its identity even when another harness exports one.
208
+ command: `${python} "${join(hooksDir, spec.script)}" --agent-id "${agentId}"`,
201
209
  ...(spec.timeout ? { timeout: spec.timeout } : {}),
202
210
  };
203
211
  entries[event] = [{ ...(spec.matcher ? { matcher: spec.matcher } : {}), hooks: [hook] }];
@@ -209,7 +217,7 @@ export function buildHookEntries(hooksDir, python) {
209
217
  * Merge the managed hook entries into a Claude Code settings.json file:
210
218
  * previous dashclaw-managed entries are replaced, everything else preserved.
211
219
  */
212
- export function mergeClaudeSettings(settingsPath, hooksDir, python) {
220
+ export function mergeClaudeSettings(settingsPath, hooksDir, python, agentId = DEFAULT_AGENT_ID) {
213
221
  let settings = {};
214
222
  if (existsSync(settingsPath)) {
215
223
  try {
@@ -221,7 +229,7 @@ export function mergeClaudeSettings(settingsPath, hooksDir, python) {
221
229
  if (!existsSync(bak)) copyFileSync(settingsPath, bak);
222
230
  }
223
231
  settings.hooks = settings.hooks || {};
224
- const managed = buildHookEntries(hooksDir, python);
232
+ const managed = buildHookEntries(hooksDir, python, agentId);
225
233
  for (const [event, entries] of Object.entries(managed)) {
226
234
  const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
227
235
  settings.hooks[event] = [...existing.filter((e) => !isManagedHookEntry(e)), ...entries];
@@ -284,8 +292,13 @@ export async function installClaude({
284
292
  apiKey = apiKey || env.DASHCLAW_API_KEY || '';
285
293
 
286
294
  if (trial && !apiKey) {
287
- const hostedBase = (endpoint || env.DASHCLAW_HOSTED_URL || '').replace(/\/+$/, '')
288
- || (await mustPrompt(prompt, 'Hosted DashClaw URL (where you signed up / will sign up): '));
295
+ let hostedBase = (endpoint || env.DASHCLAW_HOSTED_URL || '').replace(/\/+$/, '');
296
+ if (!hostedBase) {
297
+ hostedBase = DEFAULT_HOSTED_TRIAL_URL;
298
+ logger.log('');
299
+ logger.log(` Using the public hosted trial: ${DEFAULT_HOSTED_TRIAL_URL}`);
300
+ logger.log(' (Trying your own instance instead? Re-run with --endpoint <url>.)');
301
+ }
289
302
  const signupUrl = `${hostedBase.replace(/\/+$/, '')}/connect`;
290
303
  logger.log('');
291
304
  logger.log(` Opening the trial signup page: ${signupUrl}`);
@@ -321,8 +334,8 @@ export async function installClaude({
321
334
  throw new Error('No python3 or python found on PATH. Install Python 3.10+ and re-run.');
322
335
  }
323
336
  const settingsPath = join(homeDir, '.claude', 'settings.json');
324
- logger.log(` Wiring hooks into ${settingsPath} (python: ${python})`);
325
- mergeClaudeSettings(settingsPath, hooksDir, python);
337
+ logger.log(` Wiring hooks into ${settingsPath} (python: ${python}, agent: ${agentId})`);
338
+ mergeClaudeSettings(settingsPath, hooksDir, python, agentId);
326
339
 
327
340
  // 5. Credentials -------------------------------------------------------------
328
341
  const hookEnvPath = join(hooksDir, '.env');