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