@haven_ai/cli 0.1.34-alpha.0 → 0.1.35-alpha.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,40 +1,111 @@
1
1
  # @haven_ai/cli
2
2
 
3
- A terminal-native, scriptable companion to the Haven dashboard. Sign in as
4
- yourself and read or manage your account from the shell — used **alongside** the
5
- web app, not instead of it.
3
+ The terminal surface for setting up and running a Haven agent — **an agent can
4
+ drive it end to end**, up to the steps that need a human signature. It is also a
5
+ scriptable companion for a human power user; both readers are served below, agent
6
+ first, because the agent is the one that cannot fall back to the dashboard.
7
+
8
+ What it never does is sign. On-chain, owner-signed actions — approving a budget,
9
+ rotating a key, moving funds — happen in the dashboard with the owner's passkey.
10
+ This CLI reads, arranges and hands off.
11
+
12
+ ## Are you an AI agent whose user has no Haven account yet?
13
+
14
+ Read **`/for-agents.md`** on the Haven host your user gave you — or
15
+ [the copy in this repository](https://github.com/d-hinders/Haven-AI/blob/dev/packages/frontend/public/for-agents.md)
16
+ if you do not have that host yet.
17
+
18
+ Your user creates the account and the passkey: those are theirs, they need a
19
+ human, and you should never ask for their password. You can do everything else
20
+ — including running the connector command from the setup prompt they paste you,
21
+ and managing the account from the shell with `@haven_ai/cli`.
6
22
 
7
23
  ## Install
8
24
 
9
25
  ```bash
10
- npm i -g @haven_ai/cli # or run ad hoc: npx @haven_ai/cli <command>
26
+ npm i -g @haven_ai/cli@alpha # or run ad hoc: npx @haven_ai/cli@alpha <command>
11
27
  haven --help
12
28
  ```
13
29
 
30
+ A bare `npx @haven_ai/cli` resolves to the **same version** as `@alpha`: the
31
+ `latest` dist-tag now tracks the newest published release (owner decision
32
+ 2026-09-04, mechanism in `publish.yml` since #2536). Verified on 2026-09-06 from
33
+ a clean directory — `@alpha` and the bare form both `0.1.34-alpha.0`, `@dev` the
34
+ snapshot `0.0.0-dev.202609061037.7cf43bb`. The pinned `@alpha` stays in the
35
+ one-liner above because every generated artifact quotes that string verbatim.
36
+
14
37
  The CLI talks to the hosted Haven backend by default. Point it elsewhere with
15
38
  `--api <url>` or `HAVEN_API_URL` (e.g. a local backend at
16
39
  `http://localhost:3001`).
17
40
 
18
- > **This version: login, read, and backend-only management.** On-chain,
19
- > owner-signed actions (deploy, budgets, approvers, send) are signed in the
20
- > dashboard — this CLI never holds your keys. See
41
+ > **This version: login, read, backend-only management, and budget
42
+ > construct-and-hand-off.** On-chain, owner-signed actions (budget signature,
43
+ > send) are signed in the dashboard — this CLI never holds your keys. See
21
44
  > [`docs/research/haven-cli.md`](../../docs/research/haven-cli.md) for the full
22
45
  > design and roadmap.
23
46
 
24
- ## Usage
47
+ ## Setting Haven up as an agent
48
+
49
+ The path an agent walks, and where it stops. Four of the six steps in
50
+ [`/for-agents.md`](https://github.com/d-hinders/Haven-AI/blob/dev/packages/frontend/public/for-agents.md)
51
+ are your user's; these are the two that are yours.
52
+
53
+ ```bash
54
+ # 1. Get a scoped session. Prints a code and a link for your user to approve in
55
+ # a browser — you never see or ask for their password.
56
+ npx -y @haven_ai/cli@alpha login --api <api-url>
57
+
58
+ # 2. Create the agent and its budget. Prints the connector command the backend
59
+ # built, and the approval link to hand your user.
60
+ haven agents connect --name <name> --budget 25 --token USDC --period 1440
61
+
62
+ # --run executes that command here instead of printing it for a human.
63
+ haven agents connect --name <name> --budget 25 --token USDC --period 1440 --run
64
+ ```
65
+
66
+ `haven guide` prints the whole runbook — the same text served at
67
+ `/for-agents.md`, so you can read it without a network round trip.
68
+
69
+ **What the session can and cannot do.** It is an allow-list, not your user's
70
+ authority: it creates and manages agents and reads the account, and it **cannot**
71
+ approve a budget, rotate a key, change a signer or move money. Those need your
72
+ user, every time.
73
+
74
+ **Funding is theirs too, and has a command anyway.** `haven wallets funding`
75
+ prints the address, the amount **and which chain** — read the chain from there
76
+ rather than assuming one. You can compose the message; you cannot send the money.
77
+
78
+ **Every command takes `--json`**, and every refusal is a JSON object with a
79
+ machine-readable `code`. The contract and the six exit codes are in
80
+ [*For agents and scripts*](#for-agents-and-scripts) below — read that before
81
+ branching on anything.
82
+
83
+ **Pass `--api <url>` or set `HAVEN_API_URL` on the first command.** The session
84
+ remembers the backend afterwards. There is a default and it is Haven's hosted
85
+ **production** backend, so on any other deployment an omitted flag does not fail
86
+ — it connects somewhere real and wrong.
87
+
88
+ ## Usage — the full command surface
89
+
90
+ Everything the CLI does, for a human power user and as the reference an agent
91
+ checks a command against. `haven --help` prints the same list.
25
92
 
26
93
  ```bash
27
94
  # auth
28
- haven login --email you@example.com # password via prompt or HAVEN_PASSWORD
29
- haven whoami
95
+ haven login # browser device-code approval (the default)
96
+ haven login --email you@example.com # password path instead (prompt or HAVEN_PASSWORD)
97
+ haven whoami # user, session expiry, API URL
98
+ haven guide # the agent onboarding runbook
30
99
  haven logout
31
100
 
32
101
  # read
33
102
  haven wallets list
34
103
  haven wallets balances --safe <id|address>
104
+ haven wallets funding [--safe <id|address>] [--wait] # the paste-ready funding instruction (#2534)
35
105
  haven agents list
36
106
  haven agents show <id>
37
107
  haven budget show <agentId>
108
+ haven budget show <agentId> --hashes
38
109
  haven activity list [--safe <id|address>] [--agent <id>] [--direction in|out] [--limit <n>] [--offset <n>]
39
110
  haven activity export [same filters] > activity.csv
40
111
  haven activity export --format sie [--from <ISO>] [--to <ISO>] [--company <name>] > books.si
@@ -48,14 +119,202 @@ haven agents rotate-key <id> # new API key, shown once
48
119
  haven agents rename <id> <name>
49
120
  haven wallets rename <id> <name>
50
121
  haven contacts add <name> <address> | contacts remove <id>
122
+
123
+ # set an agent up without the dashboard modal (#2527)
124
+ haven agents connect --name <name> --budget <amount> --token USDC --period <minutes>
125
+ haven agents connect --name <name> --budget 25 --token USDC --period 1440 --run
126
+ haven agents connect --status <setupId> [--wait]
127
+
128
+ # budgets: construct-and-hand-off (#2539) — the CLI never signs
129
+ haven budget grant <agentId> --amount 25 --token USDC --period 1440
130
+ haven budget grant <agentId> --amount 25 --token USDC --period 1440 --recipient <address> --wait
131
+ haven budget revoke <agentId> <delegationHash> [--wait]
51
132
  ```
52
133
 
134
+ ### `haven budget grant` / `haven budget revoke` (#2539)
135
+
136
+ Later budget changes — a second token, a raise, a recipient pin, a stop — no
137
+ longer require describing where to click. Both commands CONSTRUCT the
138
+ signature request and print a dashboard link; **the human signs in the
139
+ browser**, with their passkey or wallet, every time. The CLI never signs and
140
+ never calls `activate` — that is the whole design.
141
+
142
+ - `budget grant <agentId> --amount <n> --token USDC --period <minutes>`
143
+ builds the pending delegation (period is whole **minutes**, at least `1` —
144
+ this rail has no one-time budget, and the CLI refuses `--period 0` rather
145
+ than letting `/delegations/build` answer it with a 400. `agents connect`
146
+ does take `--period 0`, but that sets `reset_period_min` on a different
147
+ route). `--amount` is in
148
+ whole tokens, read from your wallet's balances exactly like
149
+ `agents connect`; `--recipient` pins the budget to one address (omit for an
150
+ open budget); `--expires` takes unix seconds (default: 90 days).
151
+ - `budget revoke <agentId> <delegationHash>` prepares the sponsored
152
+ revocation (no gas, one signature). The hash comes from
153
+ `haven budget show <agentId> --hashes` or the dashboard. (`agents show` does
154
+ NOT print hashes — it renders the allowances projection, which has no hash
155
+ field. #2612.)
156
+ - `--wait` on either command polls until the human's signature lands (grant:
157
+ the hash turns `active`; revoke: the row turns `revoked`), 5 s interval, 15
158
+ minute ceiling. The build is idempotent for the same parameters while it is
159
+ still pending and unexpired — the dashboard form re-running the same grant
160
+ returns the same hash, so `--wait` converges instead of chasing a version
161
+ that never activates.
162
+ - Under `--json`, grant returns the backend's build object —
163
+ `{ build_id, typed_data_hash, signing_url, delegation_hash, version }`
164
+ (`build_id` and `typed_data_hash` are the delegation hash, named for API
165
+ clarity) — plus `agent_id` and `status`. The link first, the settled status
166
+ after: the same two-emission shape as device login.
167
+
168
+ ### `haven agents connect`
169
+
170
+ Does what the dashboard's connect modal does, from a terminal: creates the
171
+ setup and prints the connector command, the approval link, and when the setup
172
+ expires. `--budget` is in **whole tokens** as you would say it (`25` is 25
173
+ USDC); the CLI reads the token's decimals from your wallet's own balances and
174
+ converts, and it **refuses** an amount with more precision than the token has
175
+ rather than rounding it away.
176
+
177
+ The connector command is **printed, never composed** — it is the same string
178
+ the dashboard shows for the same setup, because both render what the backend
179
+ built. `--run` executes it for you as a child process with exactly `--json`
180
+ appended and nothing else changed, streams the connector's output, and puts the
181
+ thing you have to act on first.
182
+
183
+ If the connector refuses — it cannot tell which runtime to wire, or the machine
184
+ is already wired to a different agent — you get **exit 4** with the refusal
185
+ object intact, including any ids or suggested name it carried. That is a
186
+ message to relay to your user, not a problem to solve: `haven agents connect`
187
+ deliberately has no `--replace` and no `--name` for the connector, because
188
+ choosing between replacing an existing wiring and installing alongside it is
189
+ the human's decision.
190
+
191
+ Two flags the issue sketched and this does not have, so you are not left
192
+ looking for them: **`--recipient`** (a recipient pin lives in the delegation's
193
+ caveat enforcers; `budget grant` above can set one, but a connect setup has no
194
+ API field to carry it — the human adds it when approving) and
195
+ **`haven agents create`** (`POST /agents` requires a
196
+ delegate address, and a CLI an agent drives must never hold a signing key —
197
+ `connect` is the path that generates one locally, on your machine).
198
+
199
+ Approving the budget stays with the human, in the browser, every time.
200
+
201
+ ### `haven wallets funding`
202
+
203
+ Prints the funding instruction a human acts on: what to send (each token's
204
+ documented minimum-useful amount), to which address, on which chain, plus the
205
+ explorer link and a faucet link on testnets. It reads
206
+ `GET /user/safes/:safeId/funding` — the same facts the dashboard's funding
207
+ card shows — and composes nothing locally, so the printed sentence and the
208
+ dashboard can never disagree about the amount.
209
+
210
+ `--wait` polls the same read until the account counts as funded, printing the
211
+ elapsed time on stderr while it waits, and exits 0 the moment `funded` flips.
212
+ On timeout it exits 1 with the elapsed time in the message. It is read-only in
213
+ every mode: it never sends anything and never touches a faucet — the transfer
214
+ itself stays with the human.
215
+
53
216
  Add `--json` to any read command for machine-readable output:
54
217
 
55
218
  ```bash
56
219
  haven agents list --json | jq '.[] | select(.status == "active") | .name'
57
220
  ```
58
221
 
222
+ ## For agents and scripts
223
+
224
+ `--json` is a contract, not a formatting flag (#2525). Under it, **stdout
225
+ carries exactly one JSON value and nothing else** — every sentence meant for a
226
+ human goes to stderr. That holds for refusals too, which is the half a caller
227
+ cannot work around: parse stdout, branch on the exit code, and read stderr only
228
+ when a person is watching.
229
+
230
+ ### Signing in without a password (#2526)
231
+
232
+ `haven login` starts a **browser-approved** flow by default. It prints a link
233
+ and a code; a human opens the link, sees who is asking — the `client_label` the
234
+ CLI sent — and what the session may do, then approves.
235
+ There is no password anywhere in that path, which is the point: an agent
236
+ driving this CLI must never hold its user's password.
237
+
238
+ ```bash
239
+ haven login --json
240
+ # {"ok":true,"verification_url":"https://app.haven…/device?code=ABCD-2345",
241
+ # "user_code":"ABCD-2345","expires_at":"…"}
242
+ ```
243
+
244
+ Under `--json` that object is printed **before** polling begins, so an agent
245
+ can hand its user the link immediately rather than after the flow completes.
246
+ Add `--no-wait` to stop there and poll later; without it the CLI waits at the
247
+ interval the server names, widening it when the server says `slow_down`.
248
+
249
+ Exit codes carry the outcome an agent acts on: **3** when the code expired
250
+ (ask for a new one), **4** when the human denied it (stop asking).
251
+
252
+ `haven login --email <address>` keeps the password path for a human who wants
253
+ it. It is not removed — it is simply no longer what an agent gets by asking to
254
+ log in.
255
+
256
+ **What the approved session can do.** Create and manage agents, set up a
257
+ connection, and read your account. **What it cannot:** sign anything, approve a
258
+ budget, change signers, move funds, change your credentials, or rotate an
259
+ agent's keys — neither the delegate key (`/agents/:id/rekey/*`) nor the API key
260
+ (`/agents/:id/rotate-key`). `haven agents rotate-key` therefore needs an
261
+ ordinary session (`haven login --email`), not a device-code one: issuing a
262
+ fresh credential is a change of authority, and the human keeps those. The allow-list lives in
263
+ `packages/backend/src/middleware/owner-cli.ts`; a route that is not on it
264
+ refuses, because #1640 already refuses every purpose-carrying token everywhere
265
+ and this is a single opt-in exception. A census test measures what the
266
+ enforcement actually answers for every registered route, refuses an entry whose
267
+ route does not exist or is not behind `authMiddleware`, and holds the list
268
+ against an independent opinion about which path shapes are authority.
269
+
270
+ ```bash
271
+ haven agents list --json # success: the payload, unchanged
272
+ haven agents show missing --json # failure: one object, still parseable
273
+ ```
274
+
275
+ A failure is always:
276
+
277
+ ```json
278
+ { "ok": false, "error": { "code": "not_authenticated", "message": "Not authenticated.", "hint": "Run `haven login` ..." } }
279
+ ```
280
+
281
+ Success keeps whatever shape the command already returned — including the bare
282
+ arrays the list commands emit — so a script that parses a success today keeps
283
+ working. `login`, `logout` and the manage commands, which used to print only a
284
+ sentence, now emit an object as well.
285
+
286
+ ### Exit codes
287
+
288
+ | Code | Meaning | What a caller should do |
289
+ |---|---|---|
290
+ | `0` | Success | Continue. |
291
+ | `1` | Failed | Something broke that none of the below describes (a 5xx, an unexpected error). Retrying may help. |
292
+ | `2` | Usage | The command line was wrong — unknown command, missing argument, bad flag, or a `--safe` that matches nothing. Fix the argv; retrying it unchanged will not help. |
293
+ | `3` | Not authenticated | No stored session, or the backend rejected the one we have. Run `haven login`. |
294
+ | `4` | Refused | The session is fine and the backend said no anyway (403, 410, other 4xx). The message is the backend's, echoed verbatim. |
295
+ | `5` | Network | The backend could not be reached at all. Check connectivity and `--api`. |
296
+
297
+ **Why a 401 is `3` and not `4`.** The two overlap by definition — a 401 *is* the
298
+ backend refusing — and the split is made on what the caller does next: `3` means
299
+ re-authenticate, `4` means do not bother, the session was never the problem.
300
+ Collapsing them would leave an agent guessing which one it had.
301
+
302
+ ### `haven guide`
303
+
304
+ ```bash
305
+ haven guide # the agent onboarding runbook, as Markdown
306
+ haven guide --json # { ok, format, content }
307
+ ```
308
+
309
+ Prints the same text served at `/for-agents.md` — what Haven is, which steps
310
+ need a human, and what to say at each hand-off. It is compiled into the CLI, so
311
+ it works with no session and no network, which is exactly the situation it
312
+ describes how to get out of. The string is generated from
313
+ `packages/sdk/src/agent-guidance.ts` by
314
+ `node packages/cli/scripts/sync-agent-guidance.mjs` and byte-pinned to it by a
315
+ test; the copy exists so this package keeps **zero runtime dependencies** and
316
+ `npx @haven_ai/cli` stays a small install for an agent.
317
+
59
318
  ## Config
60
319
 
61
320
  - `--api <url>` or `HAVEN_API_URL` — backend URL (defaults to the hosted Haven