@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 +210 -179
- package/bin/dashclaw.js +99 -4
- package/lib/config.js +27 -0
- package/lib/doctor.js +191 -90
- package/lib/local-doctor.js +532 -0
- package/lib/up/args.js +25 -0
- package/lib/up/db.js +160 -0
- package/lib/up/fetch-app.js +82 -0
- package/lib/up/index.js +325 -0
- package/lib/up/instance.js +46 -0
- package/lib/up/run.js +52 -0
- package/package.json +36 -34
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
dashclaw
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
dashclaw
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
dashclaw
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
dashclaw
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
dashclaw
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
dashclaw
|
|
105
|
-
|
|
106
|
-
dashclaw
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
dashclaw
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
dashclaw
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
### `dashclaw
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
### `dashclaw
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### `dashclaw
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
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
|
|
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
|
}
|