@haven_ai/cli 0.1.17-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/dist/index.cjs ADDED
@@ -0,0 +1,681 @@
1
+ 'use strict';
2
+
3
+ var promises = require('fs/promises');
4
+ var os = require('os');
5
+ var path = require('path');
6
+
7
+ // src/args.ts
8
+ var VALUE_FLAGS = /* @__PURE__ */ new Set([
9
+ "--api",
10
+ "--email",
11
+ "--safe",
12
+ "--agent",
13
+ "--limit",
14
+ "--direction",
15
+ "--format",
16
+ "--from",
17
+ "--to",
18
+ "--company"
19
+ ]);
20
+ function parseArgs(argv) {
21
+ const positionals = [];
22
+ const flags = { json: false, help: false, version: false, yes: false };
23
+ for (let i = 0; i < argv.length; i += 1) {
24
+ const arg = argv[i];
25
+ if (arg === "--json") flags.json = true;
26
+ else if (arg === "--help" || arg === "-h") flags.help = true;
27
+ else if (arg === "--version" || arg === "-v") flags.version = true;
28
+ else if (arg === "--yes" || arg === "-y") flags.yes = true;
29
+ else if (VALUE_FLAGS.has(arg)) {
30
+ const value = argv[++i];
31
+ if (value === void 0 || value.startsWith("--")) {
32
+ throw new Error(`Missing value for ${arg}`);
33
+ }
34
+ if (arg === "--api") flags.api = value;
35
+ else if (arg === "--email") flags.email = value;
36
+ else if (arg === "--safe") flags.safe = value;
37
+ else if (arg === "--agent") flags.agent = value;
38
+ else if (arg === "--limit") {
39
+ const n = Number(value);
40
+ if (!Number.isInteger(n) || n <= 0) throw new Error("--limit must be a positive integer");
41
+ flags.limit = n;
42
+ } else if (arg === "--direction") {
43
+ if (value !== "in" && value !== "out") throw new Error('--direction must be "in" or "out"');
44
+ flags.direction = value;
45
+ } else if (arg === "--format") {
46
+ if (value !== "csv" && value !== "sie") throw new Error('--format must be "csv" or "sie"');
47
+ flags.format = value;
48
+ } else if (arg === "--from") flags.from = value;
49
+ else if (arg === "--to") flags.to = value;
50
+ else if (arg === "--company") flags.company = value;
51
+ } else if (arg.startsWith("--")) {
52
+ throw new Error(`Unknown option: ${arg}`);
53
+ } else {
54
+ positionals.push(arg);
55
+ }
56
+ }
57
+ const [command, sub, ...rest] = positionals;
58
+ return { command, sub, positionals: rest, flags };
59
+ }
60
+ function helpText() {
61
+ return [
62
+ "haven \u2014 terminal-native companion to the Haven dashboard",
63
+ "",
64
+ "Usage: haven <command> [subcommand] [options]",
65
+ "",
66
+ "Auth:",
67
+ " login [--email <e>] Sign in (password via prompt or HAVEN_PASSWORD)",
68
+ " logout Clear the saved session",
69
+ " whoami Show the signed-in user",
70
+ "",
71
+ "Read:",
72
+ " wallets list List your Haven wallets",
73
+ " wallets balances [--safe <id|address>] Token balances for a wallet",
74
+ " agents list List your agents",
75
+ " agents show <id> Show one agent + its budget",
76
+ " budget show <agentId> Show an agent's configured budget",
77
+ " activity list [--safe <id>] [--agent <id>] [--direction in|out] [--limit <n>]",
78
+ " activity export [filters] Emit CSV to stdout (--format csv, default)",
79
+ " activity export --format sie [--from <ISO>] [--to <ISO>] [--company <name>]",
80
+ " Bookkeeping-ready SIE 4I (Fortnox/Visma/Bokio)",
81
+ " catalog list List payable services",
82
+ " contacts list List your address book",
83
+ "",
84
+ "Manage (backend-only \u2014 no on-chain signing):",
85
+ " agents pause|resume <id>",
86
+ " agents revoke <id> --yes Permanently revoke an agent",
87
+ " agents rotate-key <id> Issue a new API key (shown once)",
88
+ " agents rename <id> <name>",
89
+ " wallets rename <id> <name>",
90
+ " contacts add <name> <address> | contacts remove <id>",
91
+ "",
92
+ "Options:",
93
+ " --json Machine-readable output (for scripting)",
94
+ " --yes, -y Skip the confirmation prompt for destructive actions",
95
+ " --api <url> Backend URL (default: HAVEN_API_URL or http://localhost:3001)",
96
+ " --help, --version",
97
+ "",
98
+ "On-chain actions (deploy, budgets, approvers, send) are signed in the",
99
+ "dashboard \u2014 this CLI reads and manages; it never holds your keys."
100
+ ].join("\n");
101
+ }
102
+
103
+ // src/api.ts
104
+ var CliApiError = class extends Error {
105
+ status;
106
+ constructor(message, status) {
107
+ super(message);
108
+ this.name = "CliApiError";
109
+ this.status = status;
110
+ }
111
+ };
112
+ function createCliApi({ baseUrl, token, fetchImpl = fetch }) {
113
+ const root = baseUrl.replace(/\/+$/, "");
114
+ async function request(method, path, body) {
115
+ const headers = { Accept: "application/json" };
116
+ if (token) headers.Authorization = `Bearer ${token}`;
117
+ if (body !== void 0) headers["Content-Type"] = "application/json";
118
+ let res;
119
+ try {
120
+ res = await fetchImpl(`${root}${path}`, {
121
+ method,
122
+ headers,
123
+ body: body !== void 0 ? JSON.stringify(body) : void 0
124
+ });
125
+ } catch (err) {
126
+ throw new CliApiError(
127
+ `Could not reach Haven at ${root}: ${err instanceof Error ? err.message : String(err)}`,
128
+ 0
129
+ );
130
+ }
131
+ if (res.status === 401) {
132
+ throw new CliApiError("Not authenticated. Run `haven login` first.", 401);
133
+ }
134
+ const text = await res.text();
135
+ const payload = text ? safeParse(text) : void 0;
136
+ if (!res.ok) {
137
+ const message = (payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string" ? payload.error : null) ?? `Request failed (HTTP ${res.status}).`;
138
+ throw new CliApiError(message, res.status);
139
+ }
140
+ return payload;
141
+ }
142
+ async function requestText(path) {
143
+ const headers = { Accept: "text/plain" };
144
+ if (token) headers.Authorization = `Bearer ${token}`;
145
+ let res;
146
+ try {
147
+ res = await fetchImpl(`${root}${path}`, { method: "GET", headers });
148
+ } catch (err) {
149
+ throw new CliApiError(
150
+ `Could not reach Haven at ${root}: ${err instanceof Error ? err.message : String(err)}`,
151
+ 0
152
+ );
153
+ }
154
+ if (res.status === 401) {
155
+ throw new CliApiError("Not authenticated. Run `haven login` first.", 401);
156
+ }
157
+ const text = await res.text();
158
+ if (!res.ok) {
159
+ const payload = text ? safeParse(text) : void 0;
160
+ const message = (payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string" ? payload.error : null) ?? `Request failed (HTTP ${res.status}).`;
161
+ throw new CliApiError(message, res.status);
162
+ }
163
+ return text;
164
+ }
165
+ return {
166
+ get: (path) => request("GET", path),
167
+ post: (path, body) => request("POST", path, body),
168
+ put: (path, body) => request("PUT", path, body),
169
+ del: (path) => request("DELETE", path),
170
+ getText: (path) => requestText(path)
171
+ };
172
+ }
173
+ function safeParse(text) {
174
+ try {
175
+ return JSON.parse(text);
176
+ } catch {
177
+ return void 0;
178
+ }
179
+ }
180
+ function sessionDir(homeDir) {
181
+ return path.resolve(homeDir, ".haven");
182
+ }
183
+ function sessionPath(homeDir = os.homedir()) {
184
+ return path.join(sessionDir(homeDir), "session.json");
185
+ }
186
+ function createSessionStore(homeDir = os.homedir()) {
187
+ const path = sessionPath(homeDir);
188
+ return {
189
+ path,
190
+ async load() {
191
+ try {
192
+ const raw = await promises.readFile(path, "utf8");
193
+ const parsed = JSON.parse(raw);
194
+ if (!parsed.token || !parsed.apiBaseUrl || !parsed.user) return null;
195
+ return parsed;
196
+ } catch {
197
+ return null;
198
+ }
199
+ },
200
+ async save(session) {
201
+ await promises.mkdir(sessionDir(homeDir), { recursive: true, mode: 448 });
202
+ await promises.writeFile(path, `${JSON.stringify(session, null, 2)}
203
+ `, { mode: 384 });
204
+ await promises.chmod(path, 384).catch(() => void 0);
205
+ },
206
+ async clear() {
207
+ await promises.rm(path, { force: true });
208
+ }
209
+ };
210
+ }
211
+
212
+ // src/format.ts
213
+ function table(headers, rows) {
214
+ const widths = headers.map(
215
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
216
+ );
217
+ const line = (cells) => cells.map((c, i) => (c ?? "").padEnd(widths[i])).join(" ").trimEnd();
218
+ return [line(headers), line(widths.map((w) => "-".repeat(w))), ...rows.map(line)].join("\n");
219
+ }
220
+ function truncateAddress(address) {
221
+ if (!address || address.length <= 12) return address;
222
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
223
+ }
224
+ var CHAIN_NAMES = { 100: "Gnosis", 8453: "Base" };
225
+ function chainName(chainId) {
226
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
227
+ }
228
+
229
+ // src/csv.ts
230
+ function csvField(value) {
231
+ let v = value;
232
+ if (/^[=+\-@\t\r]/.test(v)) v = `'${v}`;
233
+ return `"${v.replace(/"/g, '""')}"`;
234
+ }
235
+ function toCsv(headers, rows) {
236
+ const lines = [headers.join(","), ...rows.map((r) => r.map(csvField).join(","))];
237
+ return lines.join("\r\n");
238
+ }
239
+
240
+ // src/commands.ts
241
+ var DEFAULT_API = "https://havenbackend-production-8a00.up.railway.app";
242
+ var CLI_VERSION = "0.1.17-alpha.0";
243
+ async function run(argv, deps = {}) {
244
+ const d = {
245
+ sessionStore: deps.sessionStore ?? createSessionStore(),
246
+ makeApi: deps.makeApi ?? ((baseUrl, token) => createCliApi({ baseUrl, token })),
247
+ promptPassword: deps.promptPassword ?? (() => Promise.reject(new Error("No password input available"))),
248
+ out: deps.out ?? ((l) => process.stdout.write(`${l}
249
+ `)),
250
+ err: deps.err ?? ((l) => process.stderr.write(`${l}
251
+ `)),
252
+ env: deps.env ?? process.env
253
+ };
254
+ let args;
255
+ try {
256
+ args = parseArgs(argv);
257
+ } catch (e) {
258
+ d.err(e instanceof Error ? e.message : String(e));
259
+ return 1;
260
+ }
261
+ if (args.flags.version) {
262
+ d.out(CLI_VERSION);
263
+ return 0;
264
+ }
265
+ if (args.flags.help || !args.command) {
266
+ d.out(helpText());
267
+ return 0;
268
+ }
269
+ try {
270
+ return await dispatch(args, d);
271
+ } catch (e) {
272
+ if (e instanceof CliApiError) {
273
+ d.err(e.message);
274
+ return e.status === 401 ? 2 : 1;
275
+ }
276
+ d.err(e instanceof Error ? e.message : String(e));
277
+ return 1;
278
+ }
279
+ }
280
+ async function dispatch(args, d) {
281
+ const key = args.sub ? `${args.command} ${args.sub}` : args.command;
282
+ switch (key) {
283
+ case "login":
284
+ return cmdLogin(args, d);
285
+ case "logout":
286
+ return cmdLogout(d);
287
+ case "whoami":
288
+ return cmdWhoami(args, d);
289
+ case "wallets list":
290
+ return cmdWalletsList(args, d);
291
+ case "wallets balances":
292
+ return cmdWalletsBalances(args, d);
293
+ case "agents list":
294
+ return cmdAgentsList(args, d);
295
+ case "agents show":
296
+ return cmdAgentsShow(args, d);
297
+ case "agents pause":
298
+ return cmdAgentLifecycle(args, d, "pause");
299
+ case "agents resume":
300
+ return cmdAgentLifecycle(args, d, "resume");
301
+ case "agents revoke":
302
+ return cmdAgentRevoke(args, d);
303
+ case "agents rotate-key":
304
+ return cmdAgentRotateKey(args, d);
305
+ case "agents rename":
306
+ return cmdAgentRename(args, d);
307
+ case "budget show":
308
+ return cmdBudgetShow(args, d);
309
+ case "wallets rename":
310
+ return cmdWalletRename(args, d);
311
+ case "activity list":
312
+ return cmdActivityList(args, d);
313
+ case "activity export":
314
+ return cmdActivityExport(args, d);
315
+ case "catalog list":
316
+ return cmdCatalogList(args, d);
317
+ case "contacts list":
318
+ return cmdContactsList(args, d);
319
+ case "contacts add":
320
+ return cmdContactsAdd(args, d);
321
+ case "contacts remove":
322
+ return cmdContactsRemove(args, d);
323
+ default:
324
+ d.err(`Unknown command: ${key}. Run \`haven --help\`.`);
325
+ return 1;
326
+ }
327
+ }
328
+ function baseUrlFor(args, d, session) {
329
+ return args.flags.api ?? session?.apiBaseUrl ?? d.env.HAVEN_API_URL ?? DEFAULT_API;
330
+ }
331
+ async function authed(args, d) {
332
+ const session = await d.sessionStore.load();
333
+ if (!session) throw new CliApiError("Not authenticated. Run `haven login` first.", 401);
334
+ return { session, api: d.makeApi(baseUrlFor(args, d, session), session.token) };
335
+ }
336
+ function emit(d, json, data, human) {
337
+ d.out(json ? JSON.stringify(data, null, 2) : human());
338
+ }
339
+ async function cmdLogin(args, d) {
340
+ const email = args.flags.email ?? d.env.HAVEN_EMAIL;
341
+ if (!email) {
342
+ d.err("Provide an email with --email (or HAVEN_EMAIL).");
343
+ return 1;
344
+ }
345
+ const password = d.env.HAVEN_PASSWORD ?? await d.promptPassword();
346
+ if (!password) {
347
+ d.err("A password is required.");
348
+ return 1;
349
+ }
350
+ const baseUrl = baseUrlFor(args, d, null);
351
+ const api = d.makeApi(baseUrl);
352
+ const res = await api.post("/auth/login", { email, password });
353
+ await d.sessionStore.save({ token: res.token, apiBaseUrl: baseUrl, user: res.user });
354
+ emit(d, args.flags.json, { user: res.user, apiBaseUrl: baseUrl }, () => `Signed in as ${res.user.email}.`);
355
+ return 0;
356
+ }
357
+ async function cmdLogout(d) {
358
+ await d.sessionStore.clear();
359
+ d.out("Signed out.");
360
+ return 0;
361
+ }
362
+ async function cmdWhoami(args, d) {
363
+ const { api } = await authed(args, d);
364
+ const user = await api.get("/auth/me");
365
+ emit(d, args.flags.json, user, () => `${user.email}${user.name ? ` (${user.name})` : ""}`);
366
+ return 0;
367
+ }
368
+ async function cmdWalletsList(args, d) {
369
+ const { api } = await authed(args, d);
370
+ const { safes } = await api.get("/user/safes");
371
+ emit(
372
+ d,
373
+ args.flags.json,
374
+ safes,
375
+ () => safes.length === 0 ? "No Haven wallets yet." : table(
376
+ ["NAME", "NETWORK", "ADDRESS", "DEFAULT"],
377
+ safes.map((s) => [s.name, chainName(s.chain_id), truncateAddress(s.safe_address), s.is_default ? "\u2713" : ""])
378
+ )
379
+ );
380
+ return 0;
381
+ }
382
+ async function cmdWalletsBalances(args, d) {
383
+ const { api } = await authed(args, d);
384
+ const { safes } = await api.get("/user/safes");
385
+ const safe = pickSafe(safes, args.flags.safe);
386
+ if (!safe) {
387
+ d.err(args.flags.safe ? `No wallet matches "${args.flags.safe}".` : "No Haven wallet found.");
388
+ return 1;
389
+ }
390
+ const { balances } = await api.get(
391
+ `/balances/${safe.safe_address}?chain_id=${safe.chain_id}`
392
+ );
393
+ emit(
394
+ d,
395
+ args.flags.json,
396
+ { safe: safe.name, chainId: safe.chain_id, balances },
397
+ () => [
398
+ `${safe.name} \xB7 ${chainName(safe.chain_id)} \xB7 ${truncateAddress(safe.safe_address)}`,
399
+ balances.length === 0 ? " (no balances)" : table(["TOKEN", "BALANCE"], balances.map((b) => [b.symbol, b.formatted]))
400
+ ].join("\n")
401
+ );
402
+ return 0;
403
+ }
404
+ function pickSafe(safes, ref) {
405
+ if (!ref) return safes.find((s) => s.is_default) ?? safes[0];
406
+ const lower = ref.toLowerCase();
407
+ return safes.find((s) => s.id === ref || s.safe_address.toLowerCase() === lower);
408
+ }
409
+ async function cmdAgentsList(args, d) {
410
+ const { api } = await authed(args, d);
411
+ const { agents } = await api.get("/agents");
412
+ emit(
413
+ d,
414
+ args.flags.json,
415
+ agents,
416
+ () => agents.length === 0 ? "No agents yet." : table(
417
+ ["ID", "NAME", "STATUS", "BUDGETS"],
418
+ agents.map((a) => [a.id, a.name, a.status, budgetSummary(a.allowances)])
419
+ )
420
+ );
421
+ return 0;
422
+ }
423
+ async function cmdAgentsShow(args, d) {
424
+ const id = args.positionals[0];
425
+ if (!id) {
426
+ d.err("Usage: haven agents show <id>");
427
+ return 1;
428
+ }
429
+ const { api } = await authed(args, d);
430
+ const agent = await api.get(`/agents/${id}`);
431
+ emit(
432
+ d,
433
+ args.flags.json,
434
+ agent,
435
+ () => [
436
+ `${agent.name} [${agent.status}]`,
437
+ `id: ${agent.id}`,
438
+ `budget: ${budgetSummary(agent.allowances)}`
439
+ ].join("\n")
440
+ );
441
+ return 0;
442
+ }
443
+ async function cmdBudgetShow(args, d) {
444
+ const id = args.positionals[0];
445
+ if (!id) {
446
+ d.err("Usage: haven budget show <agentId>");
447
+ return 1;
448
+ }
449
+ const { api } = await authed(args, d);
450
+ const agent = await api.get(`/agents/${id}`);
451
+ const allowances = agent.allowances ?? [];
452
+ emit(
453
+ d,
454
+ args.flags.json,
455
+ allowances,
456
+ () => allowances.length === 0 ? `${agent.name} has no configured budget.` : table(
457
+ ["TOKEN", "AMOUNT", "RESETS"],
458
+ allowances.map((a) => [a.token_symbol, a.allowance_amount, resetLabel(a.reset_period_min)])
459
+ )
460
+ );
461
+ return 0;
462
+ }
463
+ function budgetSummary(allowances) {
464
+ if (!allowances || allowances.length === 0) return "\u2014";
465
+ return allowances.map((a) => `${a.allowance_amount} ${a.token_symbol}`).join(", ");
466
+ }
467
+ function resetLabel(mins) {
468
+ if (mins === 0) return "one-time";
469
+ if (mins === 1440) return "daily";
470
+ if (mins === 10080) return "weekly";
471
+ if (mins === 43200) return "monthly";
472
+ return `every ${mins}m`;
473
+ }
474
+ async function cmdAgentLifecycle(args, d, action) {
475
+ const id = args.positionals[0];
476
+ if (!id) {
477
+ d.err(`Usage: haven agents ${action} <id>`);
478
+ return 1;
479
+ }
480
+ const { api } = await authed(args, d);
481
+ await api.post(`/agents/${id}/${action}`);
482
+ d.out(`Agent ${id} ${action === "pause" ? "paused" : "resumed"}.`);
483
+ return 0;
484
+ }
485
+ async function cmdAgentRevoke(args, d) {
486
+ const id = args.positionals[0];
487
+ if (!id) {
488
+ d.err("Usage: haven agents revoke <id> --yes");
489
+ return 1;
490
+ }
491
+ if (!args.flags.yes) {
492
+ d.err(`This permanently revokes agent ${id}. Re-run with --yes to confirm.`);
493
+ return 1;
494
+ }
495
+ const { api } = await authed(args, d);
496
+ await api.post(`/agents/${id}/revoke`);
497
+ d.out(`Agent ${id} revoked. To also remove its on-chain allowance, use the dashboard.`);
498
+ return 0;
499
+ }
500
+ async function cmdAgentRotateKey(args, d) {
501
+ const id = args.positionals[0];
502
+ if (!id) {
503
+ d.err("Usage: haven agents rotate-key <id>");
504
+ return 1;
505
+ }
506
+ const { api } = await authed(args, d);
507
+ const res = await api.post(`/agents/${id}/rotate-key`);
508
+ if (args.flags.json) {
509
+ d.out(JSON.stringify(res, null, 2));
510
+ } else {
511
+ d.out("New API key (shown once \u2014 store it now; the old key stops working):");
512
+ d.out(res.api_key);
513
+ }
514
+ return 0;
515
+ }
516
+ async function cmdAgentRename(args, d) {
517
+ const [id, ...nameParts] = args.positionals;
518
+ const name = nameParts.join(" ").trim();
519
+ if (!id || !name) {
520
+ d.err("Usage: haven agents rename <id> <name>");
521
+ return 1;
522
+ }
523
+ const { api } = await authed(args, d);
524
+ await api.put(`/agents/${id}`, { name });
525
+ d.out(`Agent ${id} renamed to "${name}".`);
526
+ return 0;
527
+ }
528
+ async function cmdWalletRename(args, d) {
529
+ const [id, ...nameParts] = args.positionals;
530
+ const name = nameParts.join(" ").trim();
531
+ if (!id || !name) {
532
+ d.err("Usage: haven wallets rename <id> <name>");
533
+ return 1;
534
+ }
535
+ const { api } = await authed(args, d);
536
+ await api.put(`/user/safes/${id}`, { name });
537
+ d.out(`Wallet ${id} renamed to "${name}".`);
538
+ return 0;
539
+ }
540
+ async function cmdActivityList(args, d) {
541
+ const { api } = await authed(args, d);
542
+ const params = new URLSearchParams({ offset: "0", limit: String(args.flags.limit ?? 25) });
543
+ if (args.flags.safe) params.set("safeId", args.flags.safe);
544
+ if (args.flags.agent) params.set("agentId", args.flags.agent);
545
+ const { transactions } = await api.get(`/transactions?${params.toString()}`);
546
+ const visible = args.flags.direction ? transactions.filter((t) => t.direction === args.flags.direction) : transactions;
547
+ emit(
548
+ d,
549
+ args.flags.json,
550
+ visible,
551
+ () => visible.length === 0 ? "No activity." : table(
552
+ ["DATE", "DIR", "AMOUNT", "TYPE", "ACCOUNT"],
553
+ visible.map((t) => [
554
+ new Date(t.timestamp * 1e3).toISOString().slice(0, 10),
555
+ t.direction === "in" ? "in" : "out",
556
+ `${t.direction === "in" ? "+" : "-"}${t.valueFormatted} ${t.asset}`,
557
+ t.source ?? "transfer",
558
+ t.safeName ?? ""
559
+ ])
560
+ )
561
+ );
562
+ return 0;
563
+ }
564
+ async function cmdActivityExport(args, d) {
565
+ if (args.flags.format === "sie") return exportSie(args, d);
566
+ const { api } = await authed(args, d);
567
+ const params = new URLSearchParams({ offset: "0", limit: String(args.flags.limit ?? 1e3) });
568
+ if (args.flags.safe) params.set("safeId", args.flags.safe);
569
+ if (args.flags.agent) params.set("agentId", args.flags.agent);
570
+ const { transactions } = await api.get(`/transactions?${params.toString()}`);
571
+ const visible = args.flags.direction ? transactions.filter((t) => t.direction === args.flags.direction) : transactions;
572
+ const headers = [
573
+ "date",
574
+ "type",
575
+ "status",
576
+ "direction",
577
+ "amount",
578
+ "token_symbol",
579
+ "token_address",
580
+ "counterparty_address",
581
+ "safe_address",
582
+ "agent_name",
583
+ "tx_hash",
584
+ "chain_id"
585
+ ];
586
+ const rows = visible.map((t) => [
587
+ new Date(t.timestamp * 1e3).toISOString(),
588
+ exportType(t),
589
+ exportStatus(t),
590
+ t.direction,
591
+ t.valueFormatted,
592
+ t.tokenSymbol ?? t.asset ?? "",
593
+ t.tokenAddress ?? "",
594
+ (t.direction === "in" ? t.from : t.to) ?? "",
595
+ t.safeAddress ?? "",
596
+ t.agentName ?? "",
597
+ t.hash,
598
+ t.chainId != null ? String(t.chainId) : ""
599
+ ]);
600
+ d.out(toCsv(headers, rows));
601
+ return 0;
602
+ }
603
+ function exportType(t) {
604
+ if (t.activityType === "delegate_sweep") return "allowance funding";
605
+ if (t.source === "x402") return "x402";
606
+ if (t.source === "mpp_demo") return "mpp";
607
+ return t.direction === "in" ? "receive" : "send";
608
+ }
609
+ function exportStatus(t) {
610
+ if (t.isError) return "failed";
611
+ if (t.paymentFlowStatus === "confirming_merchant") return "pending";
612
+ return "executed";
613
+ }
614
+ async function exportSie(args, d) {
615
+ const { api } = await authed(args, d);
616
+ const params = new URLSearchParams({ format: "sie" });
617
+ if (args.flags.from) params.set("from", args.flags.from);
618
+ if (args.flags.to) params.set("to", args.flags.to);
619
+ if (args.flags.company) params.set("company", args.flags.company);
620
+ const content = await api.getText(`/accounting/export?${params.toString()}`);
621
+ d.out(content);
622
+ return 0;
623
+ }
624
+ async function cmdCatalogList(args, d) {
625
+ const { api } = await authed(args, d);
626
+ const { entries } = await api.get("/catalog");
627
+ emit(
628
+ d,
629
+ args.flags.json,
630
+ entries,
631
+ () => entries.length === 0 ? "Catalog is empty." : table(
632
+ ["NAME", "CATEGORY", "RAIL", "PRICE", "STATUS"],
633
+ entries.map((e) => [e.name, e.category, e.rail, e.price_display ?? "\u2014", e.status])
634
+ )
635
+ );
636
+ return 0;
637
+ }
638
+ async function cmdContactsList(args, d) {
639
+ const { api } = await authed(args, d);
640
+ const { contacts } = await api.get("/contacts");
641
+ emit(
642
+ d,
643
+ args.flags.json,
644
+ contacts,
645
+ () => contacts.length === 0 ? "No contacts yet." : table(["ID", "NAME", "ADDRESS"], contacts.map((c) => [c.id, c.name, truncateAddress(c.address)]))
646
+ );
647
+ return 0;
648
+ }
649
+ async function cmdContactsAdd(args, d) {
650
+ const [address, ...nameParts] = [...args.positionals].reverse();
651
+ const name = nameParts.reverse().join(" ").trim();
652
+ if (!name || !address) {
653
+ d.err("Usage: haven contacts add <name> <address>");
654
+ return 1;
655
+ }
656
+ const { api } = await authed(args, d);
657
+ const contact = await api.post("/contacts", { name, address });
658
+ emit(d, args.flags.json, contact, () => `Added contact "${contact.name}" (${truncateAddress(contact.address)}).`);
659
+ return 0;
660
+ }
661
+ async function cmdContactsRemove(args, d) {
662
+ const id = args.positionals[0];
663
+ if (!id) {
664
+ d.err("Usage: haven contacts remove <id>");
665
+ return 1;
666
+ }
667
+ const { api } = await authed(args, d);
668
+ await api.del(`/contacts/${id}`);
669
+ d.out(`Contact ${id} removed.`);
670
+ return 0;
671
+ }
672
+
673
+ exports.CliApiError = CliApiError;
674
+ exports.createCliApi = createCliApi;
675
+ exports.createSessionStore = createSessionStore;
676
+ exports.helpText = helpText;
677
+ exports.parseArgs = parseArgs;
678
+ exports.run = run;
679
+ exports.sessionPath = sessionPath;
680
+ //# sourceMappingURL=index.cjs.map
681
+ //# sourceMappingURL=index.cjs.map