@dashclaw/cli 0.4.0 → 0.5.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 default it 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 a hosted instance, 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 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.
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)
@@ -447,6 +456,54 @@ async function cmdInstallCodex() {
447
456
  }
448
457
  }
449
458
 
459
+ async function cmdHalt() {
460
+ const sub = args[1];
461
+ if (!['on', 'off', 'status'].includes(sub || '')) {
462
+ console.error('Usage: dashclaw halt on|off|status [--reason "<why>"]');
463
+ process.exitCode = 1;
464
+ return;
465
+ }
466
+ try {
467
+ if (sub === 'status') {
468
+ const { halt } = await apiRequest({ baseUrl, apiKey }, 'GET', '/api/halt');
469
+ if (halt?.halted) {
470
+ console.log(`
471
+ ${red('HALTED')} — every governed action for this org is blocked.`);
472
+ console.log(` By: ${halt.actor || 'admin'}`);
473
+ console.log(` Reason: ${halt.reason || '(none given)'}`);
474
+ console.log(` Since: ${halt.at || 'unknown'}
475
+ `);
476
+ } else {
477
+ console.log(`
478
+ ${green('Running')} — org is not halted.
479
+ `);
480
+ }
481
+ return;
482
+ }
483
+ const halted = sub === 'on';
484
+ const reason = getFlag('--reason') || null;
485
+ const { halt } = await apiRequest({ baseUrl, apiKey }, 'POST', '/api/halt', { body: { halted, reason } });
486
+ if (halted) {
487
+ console.log(`
488
+ ${red('HALTED')} — every governed action for this org now blocks immediately.`);
489
+ if (halt?.reason) console.log(` Reason: ${halt.reason}`);
490
+ console.log(` Resume with: dashclaw halt off
491
+ `);
492
+ } else {
493
+ console.log(`
494
+ ${green('Resumed')} — guard evaluation is back to normal.
495
+ `);
496
+ }
497
+ } catch (err) {
498
+ if (err.status === 401 || err.status === 403) {
499
+ console.error('Rejected (admin access required). The kill switch needs an admin API key.');
500
+ } else {
501
+ console.error(`Halt request failed: ${err.message}`);
502
+ }
503
+ process.exitCode = 1;
504
+ }
505
+ }
506
+
450
507
  async function cmdCost() {
451
508
  const lens = getFlag('--lens') || 'claude-code';
452
509
  const period = getFlag('--period') || '7d';
@@ -507,6 +564,31 @@ async function cmdInstall() {
507
564
  }
508
565
  }
509
566
 
567
+ // -- up / down (one-command local install) -----------------------------------
568
+
569
+ async function cmdUp() {
570
+ try {
571
+ await upCommand(args.slice(1));
572
+ } catch (err) {
573
+ console.error(red(`Error: ${err.message}`));
574
+ process.exitCode = 1;
575
+ }
576
+ }
577
+
578
+ async function cmdDown() {
579
+ try {
580
+ // Parse --dir so `dashclaw down --dir /path` targets the right state directory.
581
+ const downArgv = args.slice(1);
582
+ const dirIdx = downArgv.indexOf('--dir');
583
+ const dir = dirIdx !== -1 ? downArgv[dirIdx + 1] : null;
584
+ const baseDir = resolveBaseDir({ dir });
585
+ await runDown({ baseDir });
586
+ } catch (err) {
587
+ console.error(red(`Error: ${err.message}`));
588
+ process.exitCode = 1;
589
+ }
590
+ }
591
+
510
592
  // -- codex subcommand group --------------------------------------------------
511
593
  //
512
594
  // `dashclaw codex notify '<json>'` is invoked by Codex's legacy notify config.
@@ -1215,7 +1297,7 @@ async function cmdEnv() {
1215
1297
 
1216
1298
  // -- Router -------------------------------------------------------------------
1217
1299
 
1218
- const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'code', 'prompts', 'inbox', 'behavior', 'posture', 'next', 'env']);
1300
+ const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'code', 'prompts', 'inbox', 'behavior', 'posture', 'next', 'env', 'halt']);
1219
1301
  // `install` deliberately omitted: provisioning hooks and AGENTS.md shouldn't
1220
1302
  // require the user to have already configured API keys. If config happens to
1221
1303
  // be present, install will pick up baseUrl for the AGENTS.md instance link.
@@ -1259,6 +1341,8 @@ const COMMAND_HANDLERS = {
1259
1341
  doctor: cmdDoctor,
1260
1342
  logout: cmdLogout,
1261
1343
  code: cmdCode,
1344
+ up: cmdUp,
1345
+ down: cmdDown,
1262
1346
  install: cmdInstall,
1263
1347
  codex: cmdCodex,
1264
1348
  prompts: cmdPrompts,
@@ -1266,6 +1350,7 @@ const COMMAND_HANDLERS = {
1266
1350
  behavior: cmdBehavior,
1267
1351
  posture: cmdPosture,
1268
1352
  cost: cmdCost,
1353
+ halt: cmdHalt,
1269
1354
  next: cmdNext,
1270
1355
  env: cmdEnv,
1271
1356
  help: cmdHelp,
package/lib/config.js CHANGED
@@ -87,6 +87,31 @@ export function askSecret(question) {
87
87
  });
88
88
  }
89
89
 
90
+ // The CLI sends x-api-key over whatever scheme baseUrl has. Plaintext http to
91
+ // a non-local host exposes the key to the network path — warn once per
92
+ // process (don't refuse: LAN self-hosting over http is a supported setup).
93
+ const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]', '::1', '0.0.0.0']);
94
+ let warnedInsecureUrl = false;
95
+
96
+ export function __resetInsecureUrlWarning() {
97
+ warnedInsecureUrl = false;
98
+ }
99
+
100
+ function warnIfInsecureBaseUrl(baseUrl) {
101
+ if (warnedInsecureUrl) return;
102
+ try {
103
+ const url = new URL(baseUrl);
104
+ if (url.protocol === 'http:' && !LOCAL_HOSTNAMES.has(url.hostname)) {
105
+ warnedInsecureUrl = true;
106
+ console.error(yellow(
107
+ `Warning: DashClaw base URL ${url.origin} uses plaintext http — your API key is sent unencrypted. Use https for non-local instances.`
108
+ ));
109
+ }
110
+ } catch {
111
+ // Unparseable URL — the request itself will surface the real error.
112
+ }
113
+ }
114
+
90
115
  /**
91
116
  * Resolve config from env, then file, then interactive prompt.
92
117
  * @returns {Promise<{ baseUrl, apiKey, agentId, source } | null>}
@@ -112,6 +137,7 @@ export async function resolveConfig({ env = process.env, interactive = true } =
112
137
  }
113
138
 
114
139
  if (baseUrl && apiKey) {
140
+ warnIfInsecureBaseUrl(baseUrl);
115
141
  return { baseUrl, apiKey, agentId: agentId || 'cli-operator', source };
116
142
  }
117
143
 
@@ -156,5 +182,6 @@ export async function resolveConfig({ env = process.env, interactive = true } =
156
182
  }
157
183
  console.log();
158
184
 
185
+ warnIfInsecureBaseUrl(baseUrl);
159
186
  return { baseUrl, apiKey, agentId, source: 'prompted' };
160
187
  }
package/lib/up/args.js ADDED
@@ -0,0 +1,25 @@
1
+ const DB_MODES = ['docker', 'embedded', 'url'];
2
+
3
+ export function parseUpArgs(argv) {
4
+ const out = { update: false, yes: false, noBrowser: false, db: null, dir: null, port: null, sourceDir: null };
5
+ for (let i = 0; i < argv.length; i++) {
6
+ const a = argv[i];
7
+ if (a === '--update') out.update = true;
8
+ else if (a === '--yes') out.yes = true;
9
+ else if (a === '--no-browser') out.noBrowser = true;
10
+ else if (a === '--db') {
11
+ const v = argv[++i];
12
+ if (!DB_MODES.includes(v)) throw new Error(`--db must be one of: ${DB_MODES.join(', ')}`);
13
+ out.db = v;
14
+ } else if (a === '--dir') out.dir = argv[++i] ?? null;
15
+ else if (a === '--source-dir') out.sourceDir = argv[++i] ?? null;
16
+ else if (a === '--port') {
17
+ const v = Number(argv[++i]);
18
+ if (!Number.isInteger(v) || v < 1 || v > 65535) throw new Error('--port must be an integer 1-65535');
19
+ out.port = v;
20
+ } else if (a.startsWith('--')) {
21
+ throw new Error(`Unknown flag: ${a}. Run 'dashclaw help' for usage.`);
22
+ }
23
+ }
24
+ return out;
25
+ }