botanary 0.2.2 → 0.4.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.
Files changed (37) hide show
  1. package/README.md +140 -2
  2. package/dist/bin/botanary.js +5 -2
  3. package/dist/bin/botanary.js.map +1 -1
  4. package/dist/package.json +15 -8
  5. package/dist/src/app-url.js +19 -2
  6. package/dist/src/app-url.js.map +1 -1
  7. package/dist/src/cli.js +17 -0
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/commands/account.js +171 -0
  10. package/dist/src/commands/account.js.map +1 -0
  11. package/dist/src/commands/agent.js +6 -6
  12. package/dist/src/commands/agent.js.map +1 -1
  13. package/dist/src/commands/authority.js +634 -0
  14. package/dist/src/commands/authority.js.map +1 -0
  15. package/dist/src/commands/services.js +434 -0
  16. package/dist/src/commands/services.js.map +1 -0
  17. package/dist/src/commands/session-writes.js +196 -0
  18. package/dist/src/commands/session-writes.js.map +1 -0
  19. package/dist/src/commands/sign.js +306 -0
  20. package/dist/src/commands/sign.js.map +1 -0
  21. package/dist/src/commands/tokens.js +36 -0
  22. package/dist/src/commands/tokens.js.map +1 -0
  23. package/dist/src/commands/wallet.js +596 -44
  24. package/dist/src/commands/wallet.js.map +1 -1
  25. package/dist/src/commands/watch.js +259 -0
  26. package/dist/src/commands/watch.js.map +1 -0
  27. package/dist/src/help/examples.js +538 -8
  28. package/dist/src/help/examples.js.map +1 -1
  29. package/dist/src/package-version.js +53 -0
  30. package/dist/src/package-version.js.map +1 -0
  31. package/dist/src/progress.js +19 -0
  32. package/dist/src/progress.js.map +1 -1
  33. package/dist/src/render/balance.js +19 -2
  34. package/dist/src/render/balance.js.map +1 -1
  35. package/dist/src/validate.js +55 -9
  36. package/dist/src/validate.js.map +1 -1
  37. package/package.json +24 -18
@@ -0,0 +1,196 @@
1
+ import { BotanaryApiError } from 'botanary-mcp';
2
+ import { group } from '../help/groups.js';
3
+ import { printResult } from '../render/print.js';
4
+ import { step } from '../progress.js';
5
+ import { columns, dash, section, shortTime } from '../render/kv.js';
6
+ import { colors } from '../render/colors.js';
7
+ import { invalidArgs, notLoggedIn } from '../errors.js';
8
+ /** Same duplication as every other command file - see sign.ts's own comment on why. */
9
+ async function requireWalletToken(ctx) {
10
+ const session = await ctx.runtime.walletSession();
11
+ if (!session)
12
+ throw notLoggedIn();
13
+ return session.sessionToken;
14
+ }
15
+ function findTopLevel(program, name) {
16
+ const cmd = program.commands.find((c) => c.name() === name);
17
+ if (!cmd) {
18
+ throw new Error(`session-writes.ts expected a top-level "${name}" command to already be registered - check registration order in cli.ts.`);
19
+ }
20
+ return cmd;
21
+ }
22
+ /**
23
+ * `ctx.runtime.api` (botanary-mcp's `BotanaryApiClient`) exposes only `get`/`post`/`delete` - there is no
24
+ * `patch`. `PATCH /accounts/{id}` (account rename) is genuinely PATCH-only on the backend
25
+ * (`AccountsController.updateAccount`, `@Patch(':id')`), so this mirrors `BotanaryApiClient`'s own private
26
+ * `#request` implementation byte-for-byte (same headers, same 204/empty-body handling, same
27
+ * `BotanaryApiError` on a non-2xx) for the one method that package does not expose. This is the only place
28
+ * in the CLI that talks to `fetch` directly rather than through `ctx.runtime.api` - a deliberate, narrow
29
+ * exception forced by the published package's shape, not a second way to write: it still carries the same
30
+ * bearer session token, hits the same documented off-chain route, and throws the same error type
31
+ * `errors.ts#toCliError` already knows how to map.
32
+ */
33
+ async function patchJson(baseUrl, path, body, token, fetchImpl = fetch) {
34
+ const res = await fetchImpl(`${baseUrl}${path}`, {
35
+ method: 'PATCH',
36
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
37
+ body: JSON.stringify(body),
38
+ });
39
+ let json;
40
+ if (res.status !== 204 && res.headers.get('content-length') !== '0') {
41
+ try {
42
+ json = await res.json();
43
+ }
44
+ catch {
45
+ json = undefined;
46
+ }
47
+ }
48
+ if (!res.ok) {
49
+ const parsed = json;
50
+ const rawRetryAfter = res.headers.get('retry-after');
51
+ const retryAfterSeconds = rawRetryAfter != null ? Number.parseInt(rawRetryAfter, 10) : Number.NaN;
52
+ throw new BotanaryApiError(parsed?.error?.message ?? `${path} failed with ${res.status}`, res.status, parsed?.error?.details ?? null, Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0 ? retryAfterSeconds : null);
53
+ }
54
+ return (json ?? {});
55
+ }
56
+ function renderNotifications(payload) {
57
+ const list = (Array.isArray(payload) ? payload : []);
58
+ if (!list.length) {
59
+ return `${section('Notifications')}\n${colors.muted(' Nothing here.')}`;
60
+ }
61
+ const rows = list.map((n) => [n.read ? ' ' : '*', dash(n.id), dash(n.title), shortTime(n.createdAt)]);
62
+ return [section('Notifications'), columns(['', 'ID', 'TITLE', 'CREATED'], rows), colors.dim(' * unread')].join('\n');
63
+ }
64
+ /**
65
+ * `agents claim`/`agents revoke` (attach onto wallet.ts's `agents` Command), `account rename` (attach onto
66
+ * account.ts's `account` Command), `mandates propose` (attach onto wallet.ts's `mandates` Command), and the
67
+ * new `notifications` group - every one a PLAIN SESSION WRITE, no signature involved, called through
68
+ * `ctx.runtime.api` directly rather than `submitIntent`. Each path was checked against
69
+ * `openapi/botanary-v1.yaml` rather than assumed:
70
+ *
71
+ * POST /agents/{code}/claim AgentClaimInput { name, accountId? }
72
+ * DELETE /agents/{id} disconnectAgent
73
+ * PATCH /accounts/{id} AccountUpdateRequest { label?, hidden? } - see patchJson's own doc
74
+ * GET /notifications listNotifications
75
+ * POST /notifications/{id}/read markNotificationRead
76
+ * POST /notifications/read-all markAllNotificationsRead
77
+ * POST /mandates/propose ProposeDto { candidates: ParsedIntent[] } - pure compilation,
78
+ * "reads nothing, writes nothing" per that route's own description
79
+ *
80
+ * Registration order in cli.ts matters: this module must run after `registerWalletCommands` (agents,
81
+ * mandates) and after `registerAccountCommands` (account).
82
+ */
83
+ export function registerSessionWriteCommands(program) {
84
+ // ---- agents claim/revoke ---------------------------------------------------------------------------
85
+ const agents = findTopLevel(program, 'agents');
86
+ agents
87
+ .command('claim <code>')
88
+ .description('Claim a pending pairing and bind that agent to this account')
89
+ .requiredOption('--name <name>', 'A human-readable name for the agent, e.g. "Claude Code"')
90
+ .option('--account-id <accountId>', 'Specific account of yours - omit for your default')
91
+ .action(async (code, opts) => {
92
+ const ctx = program.opts().__ctx;
93
+ const token = await requireWalletToken(ctx);
94
+ const result = await step(ctx, 'Claiming pairing', () => ctx.runtime.api.post(`/v1/agents/${encodeURIComponent(code)}/claim`, { name: opts.name, ...(opts.accountId ? { accountId: opts.accountId } : {}) }, token));
95
+ printResult(ctx, result);
96
+ });
97
+ agents
98
+ .command('revoke <id>')
99
+ .description('Disconnect an agent from this account')
100
+ .action(async (id) => {
101
+ const ctx = program.opts().__ctx;
102
+ const token = await requireWalletToken(ctx);
103
+ await step(ctx, 'Revoking agent', () => ctx.runtime.api.delete(`/v1/agents/${encodeURIComponent(id)}`, token));
104
+ printResult(ctx, { revoked: id }, () => `${section('Agent revoked')}\n ${id} no longer has access to this account.`);
105
+ });
106
+ // ---- account rename ---------------------------------------------------------------------------------
107
+ const account = findTopLevel(program, 'account');
108
+ account
109
+ .command('rename [accountId]')
110
+ .description('Rename (and/or hide/unhide) an account - off-chain metadata only, never a UserOp')
111
+ .option('--label <label>', 'New label, up to 64 characters')
112
+ .option('--hide', 'Hide this account from the picker')
113
+ .option('--unhide', 'Unhide this account')
114
+ .action(async (accountIdArg, opts) => {
115
+ const ctx = program.opts().__ctx;
116
+ const session = await ctx.runtime.walletSession();
117
+ if (!session)
118
+ throw notLoggedIn();
119
+ const accountId = accountIdArg ?? session.accountId;
120
+ if (!accountId) {
121
+ throw invalidArgs('An account id is required.', 'Pass one, or run `botanary accounts list` to see your account ids.');
122
+ }
123
+ if (opts.hide && opts.unhide) {
124
+ throw invalidArgs('Pass only one of --hide or --unhide, not both.');
125
+ }
126
+ if (opts.label === undefined && !opts.hide && !opts.unhide) {
127
+ throw invalidArgs('Nothing to change.', 'Pass --label, --hide or --unhide.');
128
+ }
129
+ const body = {};
130
+ if (opts.label !== undefined)
131
+ body.label = opts.label;
132
+ if (opts.hide)
133
+ body.hidden = true;
134
+ if (opts.unhide)
135
+ body.hidden = false;
136
+ const updated = await step(ctx, 'Renaming account', () => patchJson(ctx.runtime.apiBaseUrl, `/v1/accounts/${encodeURIComponent(accountId)}`, body, session.sessionToken));
137
+ printResult(ctx, updated);
138
+ });
139
+ // ---- mandates propose ---------------------------------------------------------------------------------
140
+ const mandates = findTopLevel(program, 'mandates');
141
+ mandates
142
+ .command('propose')
143
+ .description('Compile a parsed rule/mandate intent against the capability matrix - a pure read, nothing is written or granted')
144
+ .option('--primitive <primitive>', 'e.g. per_tx_amount_cap, per_day_amount_cap, recipient_allowlist, provider_trust_pause')
145
+ .option('--value <value>', 'The intent value, e.g. "500"')
146
+ .option('--unit <unit>', 'The intent unit, e.g. "USDC"')
147
+ .option('--comparator <comparator>', 'lte, gte, lt, gt, eq or in')
148
+ .option('--subject <subject>', 'self (a rule, enforced by AgentGuard) or copilot (a mandate, enforced by MandateExecutor)')
149
+ .action(async (opts) => {
150
+ const ctx = program.opts().__ctx;
151
+ const token = await requireWalletToken(ctx);
152
+ if (opts.subject !== undefined && opts.subject !== 'self' && opts.subject !== 'copilot') {
153
+ throw invalidArgs(`--subject must be "self" or "copilot", got ${JSON.stringify(opts.subject)}.`);
154
+ }
155
+ const candidate = {
156
+ primitive: opts.primitive ?? null,
157
+ value: opts.value ?? null,
158
+ unit: opts.unit ?? null,
159
+ comparator: opts.comparator ?? null,
160
+ subject: opts.subject ?? null,
161
+ };
162
+ const result = await step(ctx, 'Compiling intent', () => ctx.runtime.api.post('/v1/mandates/propose', { candidates: [candidate] }, token));
163
+ printResult(ctx, result);
164
+ });
165
+ // ---- notifications ---------------------------------------------------------------------------------
166
+ const notifications = group(program
167
+ .command('notifications')
168
+ .description('Alerts - blocked actions, depeg warnings, and the like')
169
+ .option('--unread-only', 'Only unread notifications')
170
+ .action(async (opts) => {
171
+ const ctx = program.opts().__ctx;
172
+ const token = await requireWalletToken(ctx);
173
+ const path = opts.unreadOnly ? '/v1/notifications?unreadOnly=true' : '/v1/notifications';
174
+ const list = await step(ctx, 'Reading notifications', () => ctx.runtime.api.get(path, token));
175
+ printResult(ctx, list, () => renderNotifications(list));
176
+ }), 'Wallet');
177
+ notifications
178
+ .command('read <id>')
179
+ .description('Mark one notification as read')
180
+ .action(async (id) => {
181
+ const ctx = program.opts().__ctx;
182
+ const token = await requireWalletToken(ctx);
183
+ const updated = await step(ctx, 'Marking as read', () => ctx.runtime.api.post(`/v1/notifications/${encodeURIComponent(id)}/read`, {}, token));
184
+ printResult(ctx, updated);
185
+ });
186
+ notifications
187
+ .command('read-all')
188
+ .description('Mark every notification as read')
189
+ .action(async () => {
190
+ const ctx = program.opts().__ctx;
191
+ const token = await requireWalletToken(ctx);
192
+ await step(ctx, 'Marking all as read', () => ctx.runtime.api.post('/v1/notifications/read-all', {}, token));
193
+ printResult(ctx, { markedAllRead: true }, () => `${section('Notifications')}\n All marked read.`);
194
+ });
195
+ }
196
+ //# sourceMappingURL=session-writes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-writes.js","sourceRoot":"","sources":["../../../src/commands/session-writes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,306 @@
1
+ import { openBrowser, getSignRequestReal } from 'botanary-mcp';
2
+ import { colors, glyphs } from '../render/colors.js';
3
+ import { columns, dash, kv, section, shortTime } from '../render/kv.js';
4
+ import { group } from '../help/groups.js';
5
+ import { printResult } from '../render/print.js';
6
+ import { step, notify, cliHitlContext } from '../progress.js';
7
+ import { classifyWalletSend } from '../outcome.js';
8
+ import { invalidArgs, notLoggedIn } from '../errors.js';
9
+ import { appOrigin } from '../app-url.js';
10
+ /** Mirrors `wallet.ts`'s own `requireWalletToken` - duplicated rather than imported (see `tokens.ts`
11
+ * for the same duplication) so this file never has to import FROM `wallet.ts`, which imports THIS
12
+ * file for `submitIntent`: two files importing each other is how a build gets a circular dependency. */
13
+ async function requireWalletToken(ctx) {
14
+ const session = await ctx.runtime.walletSession();
15
+ if (!session)
16
+ throw notLoggedIn();
17
+ return session.sessionToken;
18
+ }
19
+ function sleep(ms) {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ /** How often `GET /sign-requests/{id}` is polled while waiting on the owner's browser. Matches the
23
+ * interval botanary-mcp's own browser lane already uses - the owner is watching one open tab, not a
24
+ * device-code flow across two screens. */
25
+ const SIGN_REQUEST_POLL_INTERVAL_MS = 2_000;
26
+ /** `pending -> opened -> relayed`, or `rejected`/`expired`/`failed`. `opened` is deliberately NOT
27
+ * terminal - it means the owner's browser tab has loaded and they are looking at it, the single most
28
+ * important moment not to stop waiting. */
29
+ const TERMINAL_STATUSES = new Set(['relayed', 'rejected', 'expired', 'failed']);
30
+ /**
31
+ * Poll a parked sign request to a terminal status (or its own expiry), reporting progress as the status
32
+ * changes, then classify the result through the CLI's one exit-code table (`src/outcome.ts`). Shared by
33
+ * `submitIntent`, which just parked the request, and `sign resume`, which is rejoining one parked by a
34
+ * process that has since exited - neither one re-implements the poll loop on its own.
35
+ */
36
+ async function waitOnSignRequest(ctx, walletToken, parked, handleId) {
37
+ const getSignRequest = getSignRequestReal(ctx.runtime, walletToken);
38
+ const hitl = cliHitlContext(ctx);
39
+ // Clamped so a nonsensical (already past) expiresAt can never turn into a negative-duration loop -
40
+ // that just means zero polls past the first read, which is the right answer for a request the
41
+ // backend already considers dead on arrival.
42
+ const deadline = Math.max(Date.now(), new Date(parked.expiresAt).getTime());
43
+ let last = await getSignRequest(parked.id);
44
+ let reported = last.status;
45
+ hitl.progress({ message: `Request ${last.status}.` });
46
+ while (!TERMINAL_STATUSES.has(last.status) && Date.now() < deadline) {
47
+ await sleep(SIGN_REQUEST_POLL_INTERVAL_MS);
48
+ last = await getSignRequest(parked.id);
49
+ if (last.status !== reported) {
50
+ reported = last.status;
51
+ hitl.progress({ message: `Request ${last.status}.` });
52
+ }
53
+ }
54
+ // Only close the handle once a TERMINAL status was actually observed - if the local wait ran out
55
+ // first, the request is still live and `sign resume` needs the handle to still be there to rejoin it.
56
+ if (handleId && TERMINAL_STATUSES.has(last.status)) {
57
+ await ctx.runtime.handles.close(handleId);
58
+ }
59
+ const outcome = classifyWalletSend(last);
60
+ ctx.outcome.exit = outcome.exit;
61
+ return {
62
+ ...outcome,
63
+ data: {
64
+ id: parked.id,
65
+ url: parked.url,
66
+ handle: handleId,
67
+ status: last.status,
68
+ opId: last.opId,
69
+ txHash: last.txHash,
70
+ declineReason: last.declineReason ?? null,
71
+ },
72
+ };
73
+ }
74
+ /**
75
+ * The ONE path from a CLI command to an owner-authorized write.
76
+ *
77
+ * Every mutating command in this CLI - swap, yield deposit, freeze, revoke, recover, everything -
78
+ * funnels through here. It parks a typed intent at `POST /sign-requests` and waits. It never calls a
79
+ * build route, never sees an unsigned op, and never signs. That is not a convention: it is why a
80
+ * backend compromise of this tool costs availability and not authority.
81
+ *
82
+ * Adding a new mutating command means adding a `kind` and its params. It does NOT mean adding a
83
+ * second way to write.
84
+ */
85
+ export async function submitIntent(ctx, kind, params) {
86
+ const walletToken = await requireWalletToken(ctx);
87
+ const created = await ctx.runtime.api.post('/v1/sign-requests', { kind, ...params }, walletToken);
88
+ // Recorded BEFORE the browser opens, not after - a dropped process (Ctrl-C, a crash, a closed SSH
89
+ // session) between here and a terminal status must still leave something `botanary sign resume` can
90
+ // rejoin. Same handle file botanary_wait uses on the MCP side (~/.botanary-mcp/handles.json), so a
91
+ // request parked here is visible there too. `kind: 'send'` is the closest member of the (closed)
92
+ // HandleKind union to "a parked sign request awaiting the owner's browser" - every sign-request kind
93
+ // uses it here, not just a literal `send`.
94
+ const handle = await ctx.runtime.handles.open({
95
+ kind: 'send',
96
+ label: `sign request: ${kind}`,
97
+ ref: { signRequestId: created.id },
98
+ url: created.url,
99
+ expiresAt: created.expiresAt,
100
+ });
101
+ let browserOpened = false;
102
+ if (ctx.interactive) {
103
+ // Never throws - the headless path (no browser found) is the same flow with the URL printed, not a
104
+ // second code path. See botanary-mcp's wallet/browser.ts.
105
+ browserOpened = await openBrowser(created.url);
106
+ }
107
+ notify(ctx, browserOpened
108
+ ? glyphs.info(`Opened your browser to approve: ${created.url}`)
109
+ : ctx.interactive
110
+ ? glyphs.warning(`Could not open a browser - approve here: ${created.url}`)
111
+ : glyphs.info(`Approve at: ${created.url}`));
112
+ return waitOnSignRequest(ctx, walletToken, created, handle?.id ?? null);
113
+ }
114
+ /** Plain-English line for a settled (or still-pending) sign request - shared by `send`'s browser lane
115
+ * and every future `submitIntent` caller so the wording stays one sentence, not one per command. */
116
+ export function describeSignOutcome(outcome) {
117
+ const { status, txHash, declineReason, handle } = outcome.data;
118
+ switch (status) {
119
+ case 'relayed':
120
+ return glyphs.success(txHash ? `Sent - transaction ${txHash}.` : 'Sent - relayed, awaiting a transaction hash.');
121
+ case 'rejected':
122
+ return glyphs.warning('You rejected the request in the browser - nothing was signed and nothing was sent.');
123
+ case 'expired':
124
+ return glyphs.warning('The request expired before it was signed - nothing was sent.');
125
+ case 'failed':
126
+ return glyphs.danger(`Declined on-chain${declineReason ? `: ${declineReason}` : ''} - nothing moved.`);
127
+ default:
128
+ return glyphs.info(`Still waiting on your browser (status: ${status}).` +
129
+ (handle ? ` Run \`botanary sign resume ${handle}\` to keep waiting.` : ''));
130
+ }
131
+ }
132
+ function renderSignList(handles) {
133
+ if (!handles.length) {
134
+ return `${section('Sign requests')}\n${colors.muted(' Nothing parked right now.')}`;
135
+ }
136
+ const rows = handles.map((h) => [dash(h.ref.signRequestId), h.id, h.label, shortTime(h.expiresAt)]);
137
+ return [
138
+ section('Sign requests'),
139
+ columns(['SIGN ID', 'HANDLE', 'KIND', 'EXPIRES'], rows),
140
+ colors.dim(' `botanary sign show <sign id>` for live status, `botanary sign resume <handle>` to keep waiting.'),
141
+ ].join('\n');
142
+ }
143
+ function renderSignStatus(id, status, url) {
144
+ const lines = [
145
+ section(`Sign request ${id}`),
146
+ kv([
147
+ ['status', status.status],
148
+ ['op', dash(status.opId)],
149
+ ['tx', dash(status.txHash)],
150
+ ['decline reason', dash(status.declineReason)],
151
+ ]),
152
+ ];
153
+ // A single-op request is the common case (every kind but three) - the block above already says
154
+ // everything there is to say, so no per-op table is added on top of it.
155
+ const ops = status.ops ?? [];
156
+ if (ops.length > 1) {
157
+ const currentIndex = ops.findIndex((op) => op.status !== 'relayed');
158
+ const rows = ops.map((op) => [
159
+ String(op.index),
160
+ op.index === currentIndex ? `${op.status} (current)` : op.status,
161
+ dash(op.opId),
162
+ dash(op.txHash),
163
+ dash(op.error),
164
+ ]);
165
+ lines.push('', section(`Ops (${ops.length})`), columns(['INDEX', 'STATUS', 'OP', 'TX', 'ERROR'], rows));
166
+ }
167
+ // Replaces `sign reject`, removed: only the browser that signs a request can settle it (the
168
+ // backend's own `refuseCliSession` 403s a CLI session on both `/complete` and `/reject`), so this is
169
+ // where to point someone who wants to decline instead.
170
+ lines.push('', colors.dim(` Open in your browser: ${url}`), colors.dim(' Declining happens there too - signing and declining are both the browser\'s job.'));
171
+ return lines.join('\n');
172
+ }
173
+ /**
174
+ * The durable guard against this CLI silently falling behind the backend's sign-kind registry again
175
+ * (`../../../src/modules/sign-requests/sign-kinds.ts`) - `test/sign-kind-parity.spec.ts` asserts every
176
+ * AVAILABLE kind (no `unavailableReason`) in that registry is a key here. Keyed and valued as full
177
+ * command paths with the program name stripped, matching `help/examples.ts`'s `COMMAND_HELP` convention
178
+ * exactly (`'agent spend'`, not `'botanary agent spend'`) - `botanary <value> --help` always resolves.
179
+ *
180
+ * Two kinds are DELIBERATELY ABSENT from this list, and the reason is not "not yet wired": they were
181
+ * checked against the backend and there is no route for a CLI command to reach.
182
+ *
183
+ * - No entry maps to a deny/decline action for `agent.request.approve`'s sibling request - there is no
184
+ * such sign-kind, and no such backend route at all (`AgentsController` has `requests/{id}/approve`
185
+ * and nothing else). `agents deny <id>` exists as a plain read-only explainer, not a write.
186
+ * - `paysh.allowlist`/`paysh.settings` writes are not registered sign-kinds either - `POST
187
+ * /pay-sh/accounts/{id}/allowlist`, `DELETE .../allowlist/{domain}` and `PUT .../settings` are refused
188
+ * for any CLI session by the backend's own `CliSessionForbidden` gate before a sign-kind would even
189
+ * apply. `paysh allowlist`/`paysh settings` ship read-only.
190
+ *
191
+ * Neither is a sign-kind gap, so neither belongs in an "exempt kind" list below - that list exists for a
192
+ * REGISTERED, AVAILABLE kind this CLI deliberately does not expose, and there are none today. Keep it
193
+ * that way: every exemption must carry its own reason inline, right next to the kind it excuses, the same
194
+ * way this file's own two-example comment does above it.
195
+ *
196
+ * A THIRD reason a kind is absent: the backend marks it UNAVAILABLE. `stellar.send`/`stellar.swap` have
197
+ * always been in that group; `paysh.withdraw`, `x402.budget`, `marketplace.job.create` and
198
+ * `marketplace.job.fund` joined it once each was checked against its own build route and found
199
+ * unsignable through `/sign` (no relayable build shape, or an op built for a mandate's session key
200
+ * rather than the owner). This map's contract is "every AVAILABLE kind", and `sign-kind-parity.spec.ts`
201
+ * enforces BOTH directions - a row here for an unavailable kind fails just as loudly as a missing row
202
+ * for an available one, so this list follows the registry rather than leading it. The COMMANDS for
203
+ * those kinds stay registered: `POST /sign-requests` now refuses them with a stated 422 reason, which
204
+ * is a better answer than a parked request that dead-ends after the owner opens their browser. If a
205
+ * kind becomes available again, the parity guard demands its row back.
206
+ */
207
+ export const KIND_TO_COMMAND = {
208
+ send: 'send',
209
+ swap: 'swap',
210
+ 'device.add': 'devices add',
211
+ 'device.remove': 'devices remove',
212
+ 'device.threshold': 'devices threshold',
213
+ 'account.freeze': 'account freeze',
214
+ 'account.unfreeze': 'account unfreeze',
215
+ 'account.setup': 'account setup',
216
+ 'account.deploy': 'account deploy',
217
+ 'account.fund': 'account fund',
218
+ 'account.migrate': 'account migrate',
219
+ 'farm.deposit': 'yield deposit',
220
+ 'farm.withdraw': 'yield withdraw',
221
+ 'farm.claim': 'yield claim',
222
+ 'policy.update': 'rules update',
223
+ 'delegation.grant': 'mandates grant',
224
+ 'delegation.revoke': 'mandates revoke',
225
+ 'delegation.freeze': 'mandates freeze',
226
+ 'delegation.unfreeze': 'mandates unfreeze',
227
+ 'agent.request.approve': 'agents approve',
228
+ 'panic.install': 'panic install',
229
+ 'panic.freeze': 'panic freeze',
230
+ 'recovery.install': 'recovery install',
231
+ 'recovery.guardian.add': 'recovery guardian add',
232
+ 'recovery.guardian.remove': 'recovery guardian remove',
233
+ 'recovery.recover': 'recovery recover',
234
+ 'solana.send': 'solana send',
235
+ 'solana.swap': 'solana swap',
236
+ 'mandate.confirm': 'mandates confirm',
237
+ 'mandate.pause': 'mandates pause',
238
+ 'mandate.resume': 'mandates resume',
239
+ // Named "cancel" here, not "revoke" - `mandates revoke <delegationId>` above already owns that verb for
240
+ // the older Smart Session delegation lane, a different id space (see `render/mandates.ts`'s two tables).
241
+ 'mandate.revoke': 'mandates cancel',
242
+ 'marketplace.deposit': 'marketplace deposit',
243
+ 'marketplace.hire': 'marketplace hire',
244
+ 'policies.signer.add': 'signers add',
245
+ 'policies.signer.remove': 'signers remove',
246
+ 'policies.guardian.set': 'signers guardian set',
247
+ 'policies.policy.set': 'signers policy set',
248
+ 'policies.policy.remove': 'signers policy remove',
249
+ 'policies.threshold': 'signers threshold',
250
+ 'policies.trust_anchor': 'signers trust-anchor',
251
+ 'policies.condition_set': 'signers condition-set',
252
+ 'policies.migrate': 'signers migrate',
253
+ 'solana.mandate.grant': 'solana mandate grant',
254
+ 'solana.mandate.freeze': 'solana mandate freeze',
255
+ 'solana.mandate.revoke': 'solana mandate revoke',
256
+ // NO ROWS for `x402.budget`, `marketplace.job.create`, `marketplace.job.fund` or `paysh.withdraw` -
257
+ // the backend registry now marks all four unavailable (see this map's header). `apis budget commit`,
258
+ // `marketplace jobs create`, `marketplace jobs fund` and `paysh withdraw` all still exist as
259
+ // commands; they get the registry's own 422 sentence instead of a link to a dead end.
260
+ 'paysh.topup': 'paysh topup',
261
+ 'agentwallet.deposit': 'agentwallet deposit',
262
+ 'approvals.announce': 'approvals announce',
263
+ };
264
+ export function registerSignCommands(program) {
265
+ const sign = group(program
266
+ .command('sign')
267
+ .description('Owner-authorized writes: park a typed intent and wait for your browser to sign it'), 'Wallet');
268
+ sign
269
+ .command('list')
270
+ .description('Sign requests this machine parked and is still waiting on')
271
+ .action(async () => {
272
+ const ctx = program.opts().__ctx;
273
+ await requireWalletToken(ctx);
274
+ const handles = (await ctx.runtime.handles.list()).filter((h) => h.kind === 'send' && typeof h.ref.signRequestId === 'string');
275
+ printResult(ctx, handles, () => renderSignList(handles));
276
+ });
277
+ sign
278
+ .command('show <id>')
279
+ .description('Read one sign request by id, with its op status')
280
+ .action(async (id) => {
281
+ const ctx = program.opts().__ctx;
282
+ const token = await requireWalletToken(ctx);
283
+ // Reads `GET /v1/sign-requests/{id}` directly through `ctx.runtime.api`, not through
284
+ // botanary-mcp's `getSignRequestReal` - see `SignRequestReadResult`'s doc comment for why: the
285
+ // published 0.7.0 MCP drops `ops`/`opCount`, and rendering those is this command's whole job for
286
+ // a multi-op request.
287
+ const status = await step(ctx, 'Reading sign request', () => ctx.runtime.api.get(`/v1/sign-requests/${id}`, token));
288
+ const url = `${appOrigin(ctx.runtime.apiBaseUrl)}/sign?req=${id}`;
289
+ printResult(ctx, status, () => renderSignStatus(id, status, url));
290
+ });
291
+ sign
292
+ .command('resume <handle>')
293
+ .description('Rejoin an interrupted wait on a sign request this machine parked')
294
+ .action(async (handleId) => {
295
+ const ctx = program.opts().__ctx;
296
+ const token = await requireWalletToken(ctx);
297
+ const handle = await ctx.runtime.handles.get(handleId);
298
+ const signRequestId = handle?.ref.signRequestId;
299
+ if (!handle || !signRequestId) {
300
+ throw invalidArgs(`No pending sign request for handle "${handleId}".`, 'Run `botanary sign list` to see what this machine is still waiting on.');
301
+ }
302
+ const outcome = await waitOnSignRequest(ctx, token, { id: signRequestId, url: handle.url ?? '', expiresAt: handle.expiresAt }, handle.id);
303
+ printResult(ctx, outcome.data, () => describeSignOutcome(outcome));
304
+ });
305
+ }
306
+ //# sourceMappingURL=sign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.js","sourceRoot":"","sources":["../../../src/commands/sign.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { group } from '../help/groups.js';
2
+ import { step } from '../progress.js';
3
+ import { printResult } from '../render/print.js';
4
+ import { columns, dash } from '../render/kv.js';
5
+ import { colors } from '../render/colors.js';
6
+ import { findChain, tokenChoicesFor } from '../validate.js';
7
+ import { notLoggedIn } from '../errors.js';
8
+ /**
9
+ * The discovery surface an address-only `--token` requires. Without this command, "pass the contract
10
+ * address" is an instruction to go and find one somewhere else - a usage error, not a usable CLI. This is
11
+ * the thing `send`'s symbol refusal points at, and the thing a person runs first when they only know a
12
+ * token by its ticker.
13
+ */
14
+ export function registerTokenCommands(program) {
15
+ group(program
16
+ .command('tokens')
17
+ .description('Every token this account holds on a chain, with the address you need to send it')
18
+ .option('--chain-id <chainId>', 'Chain to list - see `botanary chains`')
19
+ .argument('[query]', 'Optional filter on symbol or name')
20
+ .action(async (query, opts) => {
21
+ const ctx = program.opts().__ctx;
22
+ const session = await ctx.runtime.walletSession();
23
+ if (!session)
24
+ throw notLoggedIn();
25
+ const token = session.sessionToken;
26
+ const chains = await step(ctx, 'Reading chains', () => ctx.runtime.api.get('/v1/chains', token));
27
+ const chain = findChain(chains, opts.chainId ?? ctx.chain);
28
+ const balance = await step(ctx, 'Reading balances', () => ctx.runtime.api.get('/v1/balance', token));
29
+ const q = query?.toLowerCase();
30
+ const rows = tokenChoicesFor(balance, chain.chainId ?? Number.NaN).filter((t) => !q || t.symbol.toLowerCase().includes(q) || (t.name ?? '').toLowerCase().includes(q));
31
+ printResult(ctx, { chainId: chain.chainId, chainKey: chain.key, tokens: rows }, () => rows.length === 0
32
+ ? colors.muted(`This account holds nothing on ${chain.name}.`)
33
+ : columns(['SYMBOL', 'ADDRESS', 'DP', 'AMOUNT', 'SOURCE'], rows.map((t) => [t.symbol, t.address, String(t.decimals), dash(t.amount), t.source ?? '-'])));
34
+ }), 'Wallet');
35
+ }
36
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../../src/commands/tokens.ts"],"names":[],"mappings":"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"}