@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 +210 -183
- package/bin/dashclaw.js +88 -2
- package/lib/claude/install.js +23 -10
- package/lib/codex/install.js +27 -6
- package/lib/config.js +49 -2
- package/lib/up/args.js +25 -0
- package/lib/up/db.js +163 -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,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
|
|
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
|
-
dashclaw
|
|
94
|
-
dashclaw
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
dashclaw
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
dashclaw
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
dashclaw
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
### `dashclaw
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### `dashclaw
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
### `dashclaw
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- `
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 600 — no 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,
|
package/lib/claude/install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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');
|