@dashclaw/cli 0.3.2 → 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,179 +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 auto-fix safe issues. Checks database, configuration, auth, deployment, SDK reachability, and governance.
87
-
88
- ```bash
89
- dashclaw doctor # rich terminal output, auto-fix what it can
90
- dashclaw doctor --json # JSON output for CI/scripts
91
- dashclaw doctor --no-fix # diagnose only
92
- dashclaw doctor --category database,config
93
- ```
94
-
95
- The CLI invokes your instance's `/api/doctor` endpoints, so fixes that need filesystem access (env writes) are handled separately by self-hosters running `npm run doctor` locally.
96
-
97
- ### `dashclaw code`
98
-
99
- Subcommand group for Code Sessions (Claude Code analytics, ported from AgentLens). Three actions:
100
-
101
- ```bash
102
- dashclaw code ingest # walk ~/.claude/projects and POST every .jsonl
103
- dashclaw code ingest --dry-run # preview the file list and payload shape without POSTing
104
- dashclaw code ingest --projects-dir <p> # override the default projects directory (also reads $CLAUDE_PROJECTS_DIR)
105
-
106
- dashclaw code memo --project <slug> # show the most recent weekly memo for a project
107
- dashclaw code memo --project <slug> --save # write the memo to ./memos/<iso-week>.md
108
-
109
- dashclaw code apply <manifest-id> --dest=<project-cwd> # apply an Optimal Files manifest locally
110
- dashclaw code apply <manifest-id> --dest=<project-cwd> --dry-run
111
- ```
112
-
113
- `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.
114
-
115
- 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).
116
-
117
- `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).
118
-
119
- ### `dashclaw install codex`
120
-
121
- Provision DashClaw governance into the Codex CLI: writes an AGENTS.md governance block into the target project and (optionally) wires Codex's notify config.
122
-
123
- ```bash
124
- dashclaw install codex --project <path> # default: current directory
125
- dashclaw install codex --approval-policy on-request
126
- dashclaw install codex --include-notify # also wire notify dashclaw codex notify
127
- ```
128
-
129
- ### `dashclaw codex notify '<json>'`
130
-
131
- 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.
132
-
133
- ### `dashclaw prompts`
134
-
135
- 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]`.
136
-
137
- ### `dashclaw inbox`
138
-
139
- Agent message inbox: `inbox list [--unread] [--limit N]`, `inbox read <id> [...]`, `inbox archive <id> [...]`.
140
-
141
- ### `dashclaw behavior`
142
-
143
- Behavior Learning readback: `behavior status` (local recorder sample counts) and `behavior suggestions [--agent-id <id>]` (evidence-backed policy suggestions).
144
-
145
- ### `dashclaw posture` / `dashclaw next`
146
-
147
- `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.
148
-
149
- ### `dashclaw env`
150
-
151
- Run a command with managed secrets injected in-memory (values never touch disk or the terminal):
152
-
153
- ```bash
154
- dashclaw env [--agent <id>] -- node my-agent.js # inject managed secrets into the child env
155
- dashclaw env # list secret NAMES + count (never values)
156
- ```
157
-
158
- Fail-closed: if the secrets fetch fails, the child is not spawned.
159
-
160
- ### `dashclaw logout`
161
-
162
- Remove the saved config at `~/.dashclaw/config.json`.
163
-
164
- ### `dashclaw version`
165
-
166
- Print the CLI version (`--version` / `-v` also work).
167
-
168
- ### `dashclaw help`
169
-
170
- Show all commands and flags.
171
-
172
- ## Exit codes
173
-
174
- - `0` healthy
175
- - `1` — warnings present, failures, or the instance was unreachable
176
-
177
- ## License
178
-
179
- 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,13 +79,23 @@ 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
84
- dashclaw doctor Diagnose and auto-fix your DashClaw instance
92
+ dashclaw halt on|off|status [--reason] Org kill switch: halt/resume every governed action (admin)
93
+ dashclaw doctor Diagnose your instance + this machine (report-only)
94
+ --fix Apply safe auto-fixes, then re-check and report
85
95
  --json Output as JSON (for CI/scripts)
86
- --no-fix Diagnose only, skip auto-fixes
87
- --category <list> Filter checks (e.g., database,config)
96
+ --no-fix Accepted no-op alias (report-only is the default)
97
+ --category <list> Filter remote checks (e.g., database,config)
98
+ --repo <path> Treat <path> as the DashClaw checkout for repo checks
88
99
  dashclaw code ingest [--dry-run] Backfill Claude Code transcripts from ~/.claude/projects
89
100
  --projects-dir <path> Override the default scan directory
90
101
  dashclaw code ingest-codex [--dry-run] Backfill Codex transcripts from ~/.codex/sessions
@@ -164,15 +175,23 @@ async function cmdLogout() {
164
175
 
165
176
  async function cmdDoctor() {
166
177
  const jsonFlag = args.includes('--json');
178
+ const fixFlag = args.includes('--fix');
167
179
  const noFixFlag = args.includes('--no-fix');
168
180
  const catIdx = args.indexOf('--category');
169
181
  const catValue = catIdx !== -1 ? args[catIdx + 1] : undefined;
182
+ const repoIdx = args.indexOf('--repo');
183
+ const repoValue = repoIdx !== -1 ? args[repoIdx + 1] : undefined;
184
+ const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
185
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
170
186
  await runDoctorCommand({
171
187
  baseUrl,
172
188
  apiKey,
173
189
  json: jsonFlag,
190
+ fix: fixFlag,
174
191
  noFix: noFixFlag,
175
192
  category: catValue,
193
+ repo: repoValue,
194
+ cliVersion: pkg.version,
176
195
  });
177
196
  }
178
197
 
@@ -437,6 +456,54 @@ async function cmdInstallCodex() {
437
456
  }
438
457
  }
439
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
+
440
507
  async function cmdCost() {
441
508
  const lens = getFlag('--lens') || 'claude-code';
442
509
  const period = getFlag('--period') || '7d';
@@ -497,6 +564,31 @@ async function cmdInstall() {
497
564
  }
498
565
  }
499
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
+
500
592
  // -- codex subcommand group --------------------------------------------------
501
593
  //
502
594
  // `dashclaw codex notify '<json>'` is invoked by Codex's legacy notify config.
@@ -1205,7 +1297,7 @@ async function cmdEnv() {
1205
1297
 
1206
1298
  // -- Router -------------------------------------------------------------------
1207
1299
 
1208
- 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']);
1209
1301
  // `install` deliberately omitted: provisioning hooks and AGENTS.md shouldn't
1210
1302
  // require the user to have already configured API keys. If config happens to
1211
1303
  // be present, install will pick up baseUrl for the AGENTS.md instance link.
@@ -1249,6 +1341,8 @@ const COMMAND_HANDLERS = {
1249
1341
  doctor: cmdDoctor,
1250
1342
  logout: cmdLogout,
1251
1343
  code: cmdCode,
1344
+ up: cmdUp,
1345
+ down: cmdDown,
1252
1346
  install: cmdInstall,
1253
1347
  codex: cmdCodex,
1254
1348
  prompts: cmdPrompts,
@@ -1256,6 +1350,7 @@ const COMMAND_HANDLERS = {
1256
1350
  behavior: cmdBehavior,
1257
1351
  posture: cmdPosture,
1258
1352
  cost: cmdCost,
1353
+ halt: cmdHalt,
1259
1354
  next: cmdNext,
1260
1355
  env: cmdEnv,
1261
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
  }