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.
- package/README.md +140 -2
- package/dist/bin/botanary.js +5 -2
- package/dist/bin/botanary.js.map +1 -1
- package/dist/package.json +15 -8
- package/dist/src/app-url.js +19 -2
- package/dist/src/app-url.js.map +1 -1
- package/dist/src/cli.js +17 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/account.js +171 -0
- package/dist/src/commands/account.js.map +1 -0
- package/dist/src/commands/agent.js +6 -6
- package/dist/src/commands/agent.js.map +1 -1
- package/dist/src/commands/authority.js +634 -0
- package/dist/src/commands/authority.js.map +1 -0
- package/dist/src/commands/services.js +434 -0
- package/dist/src/commands/services.js.map +1 -0
- package/dist/src/commands/session-writes.js +196 -0
- package/dist/src/commands/session-writes.js.map +1 -0
- package/dist/src/commands/sign.js +306 -0
- package/dist/src/commands/sign.js.map +1 -0
- package/dist/src/commands/tokens.js +36 -0
- package/dist/src/commands/tokens.js.map +1 -0
- package/dist/src/commands/wallet.js +596 -44
- package/dist/src/commands/wallet.js.map +1 -1
- package/dist/src/commands/watch.js +259 -0
- package/dist/src/commands/watch.js.map +1 -0
- package/dist/src/help/examples.js +538 -8
- package/dist/src/help/examples.js.map +1 -1
- package/dist/src/package-version.js +53 -0
- package/dist/src/package-version.js.map +1 -0
- package/dist/src/progress.js +19 -0
- package/dist/src/progress.js.map +1 -1
- package/dist/src/render/balance.js +19 -2
- package/dist/src/render/balance.js.map +1 -1
- package/dist/src/validate.js +55 -9
- package/dist/src/validate.js.map +1 -1
- 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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"}
|