@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 +293 -10
- package/dist/cli.cjs +943 -128
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +943 -128
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +925 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +925 -119
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,40 +1,118 @@
|
|
|
1
1
|
# @haven_ai/cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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,
|
|
19
|
-
> owner-signed actions (
|
|
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
|
-
##
|
|
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
|
|
29
|
-
haven
|
|
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
|