@bankr/cli 0.3.13 → 0.3.32
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 +34 -10
- package/dist/cli.js +34 -18
- package/dist/commands/balances.js +3 -3
- package/dist/commands/cancel.js +1 -3
- package/dist/commands/club.js +6 -16
- package/dist/commands/config.js +1 -2
- package/dist/commands/fees.js +79 -64
- package/dist/commands/files.js +17 -39
- package/dist/commands/launch.d.ts +4 -0
- package/dist/commands/launch.js +56 -18
- package/dist/commands/llm.d.ts +10 -0
- package/dist/commands/llm.js +510 -99
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.js +57 -57
- package/dist/commands/portfolio.js +4 -4
- package/dist/commands/profile.js +6 -5
- package/dist/commands/prompt.js +36 -26
- package/dist/commands/sign.js +9 -16
- package/dist/commands/sounds.js +6 -15
- package/dist/commands/status.js +2 -4
- package/dist/commands/submit.js +13 -24
- package/dist/commands/swap.js +11 -11
- package/dist/commands/tokens.js +4 -4
- package/dist/commands/transfer.js +10 -10
- package/dist/commands/update.js +2 -2
- package/dist/commands/webhooks.js +27 -67
- package/dist/commands/whoami.js +4 -4
- package/dist/commands/x402.d.ts +3 -0
- package/dist/commands/x402.js +95 -115
- package/dist/lib/api.d.ts +17 -3
- package/dist/lib/api.js +34 -43
- package/dist/lib/explorer.js +1 -0
- package/dist/lib/output.d.ts +5 -0
- package/dist/lib/output.js +18 -0
- package/dist/lib/passthrough.d.ts +21 -0
- package/dist/lib/passthrough.js +26 -0
- package/dist/lib/tokens.d.ts +0 -5
- package/dist/lib/tokens.js +0 -5
- package/package.json +1 -1
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Both Privy credentials minted by the OTP exchange. The identity token alone
|
|
3
|
+
* authenticates, but only the ACCESS token carries the session `sid` claim —
|
|
4
|
+
* so sending it too is what makes a CLI login show up in Active Sessions and
|
|
5
|
+
* be killable by "log out this device". Keep them paired; never send just one.
|
|
6
|
+
*/
|
|
7
|
+
interface CliAuthTokens {
|
|
8
|
+
identityToken: string;
|
|
9
|
+
accessToken: string;
|
|
10
|
+
}
|
|
11
|
+
/** The auth headers every authenticated CLI call sends — one place, so a new
|
|
12
|
+
* endpoint can't silently omit the access token and go unrevocable again. */
|
|
13
|
+
export declare function authHeaders(tokens: CliAuthTokens): Record<string, string>;
|
|
1
14
|
export declare function loginCommand(opts: {
|
|
2
15
|
apiUrl?: string;
|
|
3
16
|
apiKey?: string;
|
|
@@ -16,4 +29,5 @@ export declare function loginCommand(opts: {
|
|
|
16
29
|
allowedIps?: string;
|
|
17
30
|
allowedRecipients?: string;
|
|
18
31
|
}): Promise<void>;
|
|
32
|
+
export {};
|
|
19
33
|
//# sourceMappingURL=login.d.ts.map
|
package/dist/commands/login.js
CHANGED
|
@@ -4,13 +4,10 @@ import open from "open";
|
|
|
4
4
|
import { isAddress } from "viem";
|
|
5
5
|
import { CLI_USER_AGENT, DEFAULT_API_URL, getApiUrl, getConfigPath, readConfig, writeConfig, } from "../lib/config.js";
|
|
6
6
|
import * as output from "../lib/output.js";
|
|
7
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
7
8
|
const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api-keys";
|
|
8
9
|
const MAX_OTP_ATTEMPTS = 3;
|
|
9
10
|
const INVALID_CODE_STATUSES = [400, 401, 422];
|
|
10
|
-
function fatal(message) {
|
|
11
|
-
output.error(message);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
11
|
function deriveDashboardUrl(apiUrl) {
|
|
15
12
|
if (apiUrl === DEFAULT_API_URL) {
|
|
16
13
|
return DEFAULT_DASHBOARD_URL;
|
|
@@ -45,6 +42,16 @@ async function sendPrivyOtp(privyConfig, email) {
|
|
|
45
42
|
throw new Error(`Failed to send verification code: ${body || res.status}`);
|
|
46
43
|
}
|
|
47
44
|
}
|
|
45
|
+
/** The auth headers every authenticated CLI call sends — one place, so a new
|
|
46
|
+
* endpoint can't silently omit the access token and go unrevocable again. */
|
|
47
|
+
export function authHeaders(tokens) {
|
|
48
|
+
return {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"User-Agent": CLI_USER_AGENT,
|
|
51
|
+
"privy-id-token": tokens.identityToken,
|
|
52
|
+
"x-access-token": tokens.accessToken,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
48
55
|
async function verifyPrivyOtp(privyConfig, email, code) {
|
|
49
56
|
const res = await fetch("https://auth.privy.io/api/v1/passwordless/authenticate", {
|
|
50
57
|
method: "POST",
|
|
@@ -66,14 +73,10 @@ async function verifyPrivyOtp(privyConfig, email, code) {
|
|
|
66
73
|
}
|
|
67
74
|
return (await res.json());
|
|
68
75
|
}
|
|
69
|
-
async function callGenerateWallet(apiUrl,
|
|
76
|
+
async function callGenerateWallet(apiUrl, tokens) {
|
|
70
77
|
const res = await fetch(`${apiUrl}/cli/generate-wallet`, {
|
|
71
78
|
method: "POST",
|
|
72
|
-
headers:
|
|
73
|
-
"Content-Type": "application/json",
|
|
74
|
-
"User-Agent": CLI_USER_AGENT,
|
|
75
|
-
"privy-id-token": identityToken,
|
|
76
|
-
},
|
|
79
|
+
headers: authHeaders(tokens),
|
|
77
80
|
});
|
|
78
81
|
const body = (await res.json());
|
|
79
82
|
if (!res.ok) {
|
|
@@ -81,28 +84,20 @@ async function callGenerateWallet(apiUrl, identityToken) {
|
|
|
81
84
|
}
|
|
82
85
|
return body;
|
|
83
86
|
}
|
|
84
|
-
async function callAcceptTerms(apiUrl,
|
|
87
|
+
async function callAcceptTerms(apiUrl, tokens) {
|
|
85
88
|
const res = await fetch(`${apiUrl}/user/accept-terms`, {
|
|
86
89
|
method: "POST",
|
|
87
|
-
headers:
|
|
88
|
-
"Content-Type": "application/json",
|
|
89
|
-
"User-Agent": CLI_USER_AGENT,
|
|
90
|
-
"privy-id-token": identityToken,
|
|
91
|
-
},
|
|
90
|
+
headers: authHeaders(tokens),
|
|
92
91
|
});
|
|
93
92
|
if (!res.ok) {
|
|
94
93
|
const body = (await res.json().catch(() => ({})));
|
|
95
94
|
throw new Error(body.message || body.error || `Accept terms failed (${res.status})`);
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
|
-
async function callGenerateApiKey(apiUrl,
|
|
97
|
+
async function callGenerateApiKey(apiUrl, tokens, opts) {
|
|
99
98
|
const res = await fetch(`${apiUrl}/api-keys`, {
|
|
100
99
|
method: "POST",
|
|
101
|
-
headers:
|
|
102
|
-
"Content-Type": "application/json",
|
|
103
|
-
"User-Agent": CLI_USER_AGENT,
|
|
104
|
-
"privy-id-token": identityToken,
|
|
105
|
-
},
|
|
100
|
+
headers: authHeaders(tokens),
|
|
106
101
|
body: JSON.stringify(opts),
|
|
107
102
|
});
|
|
108
103
|
const body = (await res.json());
|
|
@@ -115,7 +110,7 @@ async function callGenerateApiKey(apiUrl, identityToken, opts) {
|
|
|
115
110
|
async function fetchPrivyConfigOrFail(apiUrl) {
|
|
116
111
|
const config = await fetchPrivyConfig(apiUrl).catch(() => null);
|
|
117
112
|
if (!config) {
|
|
118
|
-
fatal("Could not connect to Bankr API. Check your connection and try again.");
|
|
113
|
+
output.fatal("Could not connect to Bankr API. Check your connection and try again.");
|
|
119
114
|
}
|
|
120
115
|
return config;
|
|
121
116
|
}
|
|
@@ -126,8 +121,7 @@ async function sendOtpWithSpinner(privyConfig, email) {
|
|
|
126
121
|
spin.succeed(`Verification code sent to ${email}`);
|
|
127
122
|
}
|
|
128
123
|
catch (err) {
|
|
129
|
-
|
|
130
|
-
fatal(err.message);
|
|
124
|
+
output.failWith(spin, "Failed to send verification code", err);
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
127
|
async function verifyOtpWithSpinner(privyConfig, email, code) {
|
|
@@ -138,9 +132,9 @@ async function verifyOtpWithSpinner(privyConfig, email, code) {
|
|
|
138
132
|
return result;
|
|
139
133
|
}
|
|
140
134
|
catch (err) {
|
|
141
|
-
const errMsg = err
|
|
135
|
+
const errMsg = getErrorMessage(err);
|
|
142
136
|
spin.fail("Verification failed");
|
|
143
|
-
fatal(errMsg === "INVALID_CODE" ? "Invalid verification code." : errMsg);
|
|
137
|
+
output.fatal(errMsg === "INVALID_CODE" ? "Invalid verification code." : errMsg);
|
|
144
138
|
}
|
|
145
139
|
}
|
|
146
140
|
// ── Email authentication flow ───────────────────────────────────────
|
|
@@ -162,7 +156,13 @@ async function authenticateWithEmail(apiUrl, providedEmail, providedCode) {
|
|
|
162
156
|
// Headless: code already provided, verify directly
|
|
163
157
|
if (providedCode) {
|
|
164
158
|
const result = await verifyOtpWithSpinner(privyConfig, email, providedCode.trim());
|
|
165
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
tokens: {
|
|
161
|
+
identityToken: result.identity_token,
|
|
162
|
+
accessToken: result.token,
|
|
163
|
+
},
|
|
164
|
+
email,
|
|
165
|
+
};
|
|
166
166
|
}
|
|
167
167
|
// Interactive: send OTP, then prompt for code with retries
|
|
168
168
|
await sendOtpWithSpinner(privyConfig, email);
|
|
@@ -176,38 +176,43 @@ async function authenticateWithEmail(apiUrl, providedEmail, providedCode) {
|
|
|
176
176
|
try {
|
|
177
177
|
const result = await verifyPrivyOtp(privyConfig, email, code.trim());
|
|
178
178
|
verifySpin.succeed("Email verified");
|
|
179
|
-
return {
|
|
179
|
+
return {
|
|
180
|
+
tokens: {
|
|
181
|
+
identityToken: result.identity_token,
|
|
182
|
+
accessToken: result.token,
|
|
183
|
+
},
|
|
184
|
+
email,
|
|
185
|
+
};
|
|
180
186
|
}
|
|
181
187
|
catch (err) {
|
|
182
|
-
const errMsg = err
|
|
188
|
+
const errMsg = getErrorMessage(err);
|
|
183
189
|
if (errMsg === "INVALID_CODE") {
|
|
184
190
|
if (attempt < MAX_OTP_ATTEMPTS - 1) {
|
|
185
191
|
verifySpin.fail("Invalid code, please try again");
|
|
186
192
|
continue;
|
|
187
193
|
}
|
|
188
194
|
verifySpin.fail("Invalid code");
|
|
189
|
-
fatal("Too many invalid attempts. Please restart login.");
|
|
195
|
+
output.fatal("Too many invalid attempts. Please restart login.");
|
|
190
196
|
}
|
|
191
197
|
verifySpin.fail("Verification failed");
|
|
192
|
-
fatal(errMsg);
|
|
198
|
+
output.fatal(errMsg);
|
|
193
199
|
}
|
|
194
200
|
}
|
|
195
|
-
fatal("Verification failed. Please try again.");
|
|
201
|
+
output.fatal("Verification failed. Please try again.");
|
|
196
202
|
}
|
|
197
203
|
// ── Email login flow ────────────────────────────────────────────────
|
|
198
204
|
async function emailLoginFlow(apiUrl, opts) {
|
|
199
205
|
// Step 1: Authenticate via email OTP
|
|
200
|
-
const {
|
|
206
|
+
const { tokens } = await authenticateWithEmail(apiUrl, opts.email, opts.code);
|
|
201
207
|
// Step 2: Generate/resolve wallet
|
|
202
208
|
const walletSpin = output.spinner("Setting up wallet...");
|
|
203
209
|
let wallet;
|
|
204
210
|
try {
|
|
205
|
-
wallet = await callGenerateWallet(apiUrl,
|
|
211
|
+
wallet = await callGenerateWallet(apiUrl, tokens);
|
|
206
212
|
walletSpin.stop();
|
|
207
213
|
}
|
|
208
214
|
catch (err) {
|
|
209
|
-
|
|
210
|
-
fatal(err.message);
|
|
215
|
+
output.failWith(walletSpin, "Wallet setup failed", err);
|
|
211
216
|
}
|
|
212
217
|
// Show wallet info
|
|
213
218
|
output.blank();
|
|
@@ -237,16 +242,15 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
237
242
|
theme: output.bankrTheme,
|
|
238
243
|
}));
|
|
239
244
|
if (!accepted) {
|
|
240
|
-
fatal("Terms must be accepted to continue.");
|
|
245
|
+
output.fatal("Terms must be accepted to continue.");
|
|
241
246
|
}
|
|
242
247
|
const termsSpin = output.spinner("Accepting terms...");
|
|
243
248
|
try {
|
|
244
|
-
await callAcceptTerms(apiUrl,
|
|
249
|
+
await callAcceptTerms(apiUrl, tokens);
|
|
245
250
|
termsSpin.succeed("Terms accepted");
|
|
246
251
|
}
|
|
247
252
|
catch (err) {
|
|
248
|
-
|
|
249
|
-
fatal(err.message);
|
|
253
|
+
output.failWith(termsSpin, "Failed to accept terms", err);
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
// Step 4: Generate API key
|
|
@@ -281,7 +285,7 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
281
285
|
const keySpin = output.spinner("Generating API key...");
|
|
282
286
|
let apiKeyResult;
|
|
283
287
|
try {
|
|
284
|
-
apiKeyResult = await callGenerateApiKey(apiUrl,
|
|
288
|
+
apiKeyResult = await callGenerateApiKey(apiUrl, tokens, {
|
|
285
289
|
name: keyName.trim(),
|
|
286
290
|
walletApiEnabled: enableWallet,
|
|
287
291
|
agentApiEnabled: enableAgent,
|
|
@@ -294,8 +298,7 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
294
298
|
keySpin.stop();
|
|
295
299
|
}
|
|
296
300
|
catch (err) {
|
|
297
|
-
|
|
298
|
-
fatal(err.message);
|
|
301
|
+
output.failWith(keySpin, "Failed to generate API key", err);
|
|
299
302
|
}
|
|
300
303
|
// Show result
|
|
301
304
|
output.blank();
|
|
@@ -334,7 +337,7 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
334
337
|
});
|
|
335
338
|
if (!nonceRes.ok) {
|
|
336
339
|
nonceSpinner.fail("Failed to fetch nonce");
|
|
337
|
-
fatal(`Nonce request failed (${nonceRes.status})`);
|
|
340
|
+
output.fatal(`Nonce request failed (${nonceRes.status})`);
|
|
338
341
|
}
|
|
339
342
|
const { nonce } = (await nonceRes.json());
|
|
340
343
|
nonceSpinner.succeed("Nonce received");
|
|
@@ -378,7 +381,7 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
378
381
|
if (!verifyRes.ok) {
|
|
379
382
|
verifySpinner.fail("SIWE verification failed");
|
|
380
383
|
const body = (await verifyRes.json().catch(() => ({})));
|
|
381
|
-
fatal(body.message || `Verification failed (${verifyRes.status})`);
|
|
384
|
+
output.fatal(body.message || `Verification failed (${verifyRes.status})`);
|
|
382
385
|
}
|
|
383
386
|
const result = (await verifyRes.json());
|
|
384
387
|
verifySpinner.succeed("Wallet created");
|
|
@@ -406,13 +409,11 @@ async function validateApiKey(apiUrl, apiKey) {
|
|
|
406
409
|
const errorType = body.error ?? "";
|
|
407
410
|
if (errorType === "Authentication required") {
|
|
408
411
|
spin.fail("API key is not linked to a wallet");
|
|
409
|
-
output.
|
|
410
|
-
process.exit(1);
|
|
412
|
+
output.fatal("This API key is not associated with a wallet. Generate a new key at https://bankr.bot/api-keys");
|
|
411
413
|
}
|
|
412
414
|
else {
|
|
413
415
|
spin.fail("Invalid Bankr API key");
|
|
414
|
-
output.
|
|
415
|
-
process.exit(1);
|
|
416
|
+
output.fatal("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api-keys");
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
else {
|
|
@@ -450,7 +451,7 @@ function parseAndValidateIps(raw) {
|
|
|
450
451
|
.filter(Boolean);
|
|
451
452
|
const invalid = ips.filter((ip) => !isValidIpOrCidr(ip));
|
|
452
453
|
if (invalid.length > 0) {
|
|
453
|
-
fatal(`Invalid IP address(es) or CIDR range(s): ${invalid.join(", ")}`);
|
|
454
|
+
output.fatal(`Invalid IP address(es) or CIDR range(s): ${invalid.join(", ")}`);
|
|
454
455
|
}
|
|
455
456
|
return ips;
|
|
456
457
|
}
|
|
@@ -482,14 +483,14 @@ function parseAndValidateRecipients(raw) {
|
|
|
482
483
|
}
|
|
483
484
|
}
|
|
484
485
|
if (invalid.length > 0) {
|
|
485
|
-
fatal(`Invalid address(es): ${invalid.join(", ")}`);
|
|
486
|
+
output.fatal(`Invalid address(es): ${invalid.join(", ")}`);
|
|
486
487
|
}
|
|
487
488
|
return { evm, solana };
|
|
488
489
|
}
|
|
489
490
|
export async function loginCommand(opts) {
|
|
490
491
|
// Fail fast if flags were passed with empty values
|
|
491
492
|
if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
|
|
492
|
-
fatal("--api-key requires a non-empty value");
|
|
493
|
+
output.fatal("--api-key requires a non-empty value");
|
|
493
494
|
}
|
|
494
495
|
// Non-interactive mode requires a headless flag combo. Without one, the
|
|
495
496
|
// command would hang on a prompt — fail fast with the available paths.
|
|
@@ -501,8 +502,7 @@ export async function loginCommand(opts) {
|
|
|
501
502
|
(opts.siwe && opts.privateKey) ||
|
|
502
503
|
(opts.email && opts.email.trim());
|
|
503
504
|
if (!hasHeadlessFlag) {
|
|
504
|
-
output.
|
|
505
|
-
process.exit(1);
|
|
505
|
+
output.fatal("Non-interactive login requires one of: --api-key <key>, login siwe --private-key <key>, or login email <address> [--code <otp>]");
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
508
|
const config = readConfig();
|
|
@@ -562,7 +562,7 @@ export async function loginCommand(opts) {
|
|
|
562
562
|
// --siwe: SIWE login flow (headless agent onboarding)
|
|
563
563
|
if (opts.siwe) {
|
|
564
564
|
if (!opts.privateKey) {
|
|
565
|
-
fatal("--private-key is required with --siwe");
|
|
565
|
+
output.fatal("--private-key is required with --siwe");
|
|
566
566
|
}
|
|
567
567
|
const { apiKey } = await siweLoginFlow(apiUrl, {
|
|
568
568
|
privateKey: opts.privateKey,
|
|
@@ -593,7 +593,7 @@ export async function loginCommand(opts) {
|
|
|
593
593
|
if (opts.apiKey) {
|
|
594
594
|
const apiKey = opts.apiKey.trim();
|
|
595
595
|
if (!apiKey.startsWith("bk_")) {
|
|
596
|
-
fatal('Invalid format. Bankr API keys start with "bk_".');
|
|
596
|
+
output.fatal('Invalid format. Bankr API keys start with "bk_".');
|
|
597
597
|
}
|
|
598
598
|
await validateApiKey(apiUrl, apiKey);
|
|
599
599
|
saveCredentials(apiKey, opts.llmKey);
|
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { getPortfolio, } from "../lib/api.js";
|
|
3
3
|
import * as output from "../lib/output.js";
|
|
4
4
|
import { formatUsd, formatBalance } from "../lib/output.js";
|
|
5
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
5
6
|
import { CHAIN_LABELS, VALID_CHAINS } from "../lib/chains.js";
|
|
6
7
|
const BRAND = chalk.hex("#FF613D");
|
|
7
8
|
const DIM = chalk.dim;
|
|
@@ -53,8 +54,7 @@ export async function portfolioCommand(opts) {
|
|
|
53
54
|
if (chains) {
|
|
54
55
|
const invalid = chains.filter((c) => !VALID_CHAINS.has(c));
|
|
55
56
|
if (invalid.length > 0) {
|
|
56
|
-
output.
|
|
57
|
-
process.exit(1);
|
|
57
|
+
output.fatal(`Invalid chain${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid chains: ${[...VALID_CHAINS].join(", ")}`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
const includePnl = opts.pnl || opts.all;
|
|
@@ -78,7 +78,7 @@ export async function portfolioCommand(opts) {
|
|
|
78
78
|
spin.succeed("Portfolio loaded");
|
|
79
79
|
}
|
|
80
80
|
catch (err) {
|
|
81
|
-
spin.fail(`Failed to fetch portfolio: ${err
|
|
81
|
+
spin.fail(`Failed to fetch portfolio: ${getErrorMessage(err)}`);
|
|
82
82
|
process.exit(1);
|
|
83
83
|
}
|
|
84
84
|
// JSON output mode
|
|
@@ -109,7 +109,7 @@ export async function portfolioCommand(opts) {
|
|
|
109
109
|
printChainBalances(chain, chainData, !!includePnl);
|
|
110
110
|
}
|
|
111
111
|
// Print NFTs
|
|
112
|
-
if (includeNfts && data.nfts
|
|
112
|
+
if (includeNfts && data.nfts?.length) {
|
|
113
113
|
console.log();
|
|
114
114
|
console.log();
|
|
115
115
|
console.log(` ${BRAND.bold("NFTs")} ${DIM(`${data.nfts.length} item(s)`)}`);
|
package/dist/commands/profile.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
3
3
|
import { getOwnProfiles, createProfile, updateProfile, deleteProfile, addProjectUpdate, } from "../lib/api.js";
|
|
4
4
|
import * as output from "../lib/output.js";
|
|
5
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
5
6
|
const BRAND = chalk.hex("#FF613D");
|
|
6
7
|
const DIM = chalk.dim;
|
|
7
8
|
function printProfile(profile, json) {
|
|
@@ -74,7 +75,7 @@ export async function profileViewCommand(opts) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
catch (err) {
|
|
77
|
-
const msg = err
|
|
78
|
+
const msg = getErrorMessage(err);
|
|
78
79
|
if (msg.includes("404")) {
|
|
79
80
|
output.info("No profiles found. Create one with: bankr profile create");
|
|
80
81
|
}
|
|
@@ -132,7 +133,7 @@ export async function profileCreateCommand(opts) {
|
|
|
132
133
|
printProfile(profile, !!opts.json);
|
|
133
134
|
}
|
|
134
135
|
catch (err) {
|
|
135
|
-
output.error(err
|
|
136
|
+
output.error(getErrorMessage(err));
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
export async function profileUpdateCommand(opts) {
|
|
@@ -163,7 +164,7 @@ export async function profileUpdateCommand(opts) {
|
|
|
163
164
|
printProfile(profile, !!opts.json);
|
|
164
165
|
}
|
|
165
166
|
catch (err) {
|
|
166
|
-
output.error(err
|
|
167
|
+
output.error(getErrorMessage(err));
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
export async function profileDeleteCommand(opts) {
|
|
@@ -187,7 +188,7 @@ export async function profileDeleteCommand(opts) {
|
|
|
187
188
|
output.success("Profile deleted.");
|
|
188
189
|
}
|
|
189
190
|
catch (err) {
|
|
190
|
-
output.error(err
|
|
191
|
+
output.error(getErrorMessage(err));
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
export async function profileAddUpdateCommand(opts) {
|
|
@@ -221,7 +222,7 @@ export async function profileAddUpdateCommand(opts) {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
catch (err) {
|
|
224
|
-
output.error(err
|
|
225
|
+
output.error(getErrorMessage(err));
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
//# sourceMappingURL=profile.js.map
|
package/dist/commands/prompt.js
CHANGED
|
@@ -2,46 +2,59 @@ import { ApiError, pollJob, submitPrompt } from "../lib/api.js";
|
|
|
2
2
|
import { emitCESP } from "../lib/cesp/engine.js";
|
|
3
3
|
import { getLastThreadId, requireApiKey, setLastThreadId, } from "../lib/config.js";
|
|
4
4
|
import * as output from "../lib/output.js";
|
|
5
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
5
6
|
/** Valid Max Mode model IDs — hardcoded because CLI is standalone (no monorepo imports).
|
|
6
|
-
* Must be manually kept in sync with models in the LLM gateway DB.
|
|
7
|
+
* Must be manually kept in sync with models in the LLM gateway DB.
|
|
8
|
+
*
|
|
9
|
+
* Only the two newest releases of each vendor tier are listed, and only the
|
|
10
|
+
* frontier / flagship / balanced ranks — matching what the web picker shows.
|
|
11
|
+
* Older generations and `light` lines still run on the gateway, but the CLI
|
|
12
|
+
* stops steering people onto them. Superseding a model here means DROPPING
|
|
13
|
+
* the one it pushes out of the tier, not just adding the new id. */
|
|
7
14
|
const VALID_MAX_MODE_MODELS = new Set([
|
|
8
15
|
// Claude (Vertex AI)
|
|
16
|
+
"claude-fable-5.1",
|
|
9
17
|
"claude-fable-5",
|
|
18
|
+
"claude-opus-5",
|
|
10
19
|
"claude-opus-4.8",
|
|
11
|
-
"claude-opus-4.7",
|
|
12
|
-
"claude-opus-4.6",
|
|
13
|
-
"claude-opus-4.5",
|
|
14
20
|
"claude-sonnet-5",
|
|
15
21
|
"claude-sonnet-4.6",
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
22
|
+
// Gemini (Vertex AI) — gemini-3-pro deliberately absent: removed 2026-03-09,
|
|
23
|
+
// both provider slugs retired. The Flash Lite tier likewise: 3.5 and 3.1
|
|
24
|
+
// deprecated 2026-09-09 (replaced by gemini-3.8-flash). Don't re-add on a
|
|
25
|
+
// sync pass.
|
|
26
|
+
"gemini-3.8-flash",
|
|
27
|
+
"gemini-3.7-flash",
|
|
19
28
|
"gemini-3.1-pro",
|
|
20
|
-
"gemini-3-pro",
|
|
21
29
|
"gemini-2.5-pro",
|
|
22
30
|
// OpenAI (OpenRouter)
|
|
23
|
-
"gpt-
|
|
24
|
-
"gpt-5.
|
|
25
|
-
"gpt-5.
|
|
26
|
-
"gpt-5-
|
|
31
|
+
"gpt-6-astra",
|
|
32
|
+
"gpt-5.6-sol",
|
|
33
|
+
"gpt-5.5",
|
|
34
|
+
"gpt-5.6-terra",
|
|
35
|
+
"gpt-5.2-codex",
|
|
27
36
|
// Grok (OpenRouter)
|
|
28
|
-
"grok-4.
|
|
37
|
+
"grok-4.6",
|
|
38
|
+
"grok-4.5",
|
|
29
39
|
// DeepSeek (OpenRouter)
|
|
30
|
-
"deepseek-
|
|
40
|
+
"deepseek-v4-pro-0813",
|
|
41
|
+
"deepseek-v4-pro",
|
|
31
42
|
// Qwen (OpenRouter)
|
|
32
|
-
"qwen3-
|
|
43
|
+
"qwen3.8-max",
|
|
33
44
|
"qwen3.7-max",
|
|
34
45
|
"qwen3.7-plus",
|
|
35
46
|
"qwen3.5-plus",
|
|
36
47
|
// MiniMax (direct)
|
|
37
|
-
"minimax-
|
|
48
|
+
"minimax-m3",
|
|
38
49
|
"minimax-m2.7",
|
|
50
|
+
"minimax-m2.7-highspeed",
|
|
39
51
|
// Other (OpenRouter)
|
|
52
|
+
"kimi-k3",
|
|
40
53
|
"kimi-k2.7-code",
|
|
41
54
|
"kimi-k2.6",
|
|
42
|
-
"kimi-k2.5",
|
|
43
|
-
"glm-5",
|
|
44
55
|
"glm-5.2",
|
|
56
|
+
"glm-5.3",
|
|
57
|
+
"glm-5-turbo",
|
|
45
58
|
]);
|
|
46
59
|
export async function promptCommand(text, options = {}) {
|
|
47
60
|
requireApiKey();
|
|
@@ -50,8 +63,7 @@ export async function promptCommand(text, options = {}) {
|
|
|
50
63
|
if (options.continue) {
|
|
51
64
|
threadId = getLastThreadId();
|
|
52
65
|
if (!threadId) {
|
|
53
|
-
output.
|
|
54
|
-
process.exit(1);
|
|
66
|
+
output.fatal("No previous thread found. Run a prompt first.");
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
else if (options.thread) {
|
|
@@ -61,8 +73,7 @@ export async function promptCommand(text, options = {}) {
|
|
|
61
73
|
let maxMode;
|
|
62
74
|
if (options.model) {
|
|
63
75
|
if (!VALID_MAX_MODE_MODELS.has(options.model)) {
|
|
64
|
-
output.
|
|
65
|
-
process.exit(1);
|
|
76
|
+
output.fatal(`Invalid model "${options.model}". Valid models: ${[...VALID_MAX_MODE_MODELS].join(", ")}`);
|
|
66
77
|
}
|
|
67
78
|
maxMode = { enabled: true, model: options.model };
|
|
68
79
|
}
|
|
@@ -95,7 +106,7 @@ export async function promptCommand(text, options = {}) {
|
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
else {
|
|
98
|
-
output.error(err
|
|
109
|
+
output.error(getErrorMessage(err));
|
|
99
110
|
}
|
|
100
111
|
process.exit(1);
|
|
101
112
|
}
|
|
@@ -121,7 +132,7 @@ export async function promptCommand(text, options = {}) {
|
|
|
121
132
|
if (result.response) {
|
|
122
133
|
console.log(result.response);
|
|
123
134
|
}
|
|
124
|
-
if (result.transactions
|
|
135
|
+
if (result.transactions?.length) {
|
|
125
136
|
console.log();
|
|
126
137
|
output.dim(`${result.transactions.length} transaction(s) returned. Use the web app to review and execute.`);
|
|
127
138
|
}
|
|
@@ -141,8 +152,7 @@ export async function promptCommand(text, options = {}) {
|
|
|
141
152
|
catch (err) {
|
|
142
153
|
spin.fail("Error while polling");
|
|
143
154
|
emitCESP("resource.limit");
|
|
144
|
-
output.
|
|
145
|
-
process.exit(1);
|
|
155
|
+
output.fatal(getErrorMessage(err));
|
|
146
156
|
}
|
|
147
157
|
}
|
|
148
158
|
//# sourceMappingURL=prompt.js.map
|
package/dist/commands/sign.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sign } from "../lib/api.js";
|
|
2
2
|
import { emitCESP } from "../lib/cesp/engine.js";
|
|
3
3
|
import * as output from "../lib/output.js";
|
|
4
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
4
5
|
export async function signCommand(opts) {
|
|
5
6
|
const request = {
|
|
6
7
|
signatureType: opts.type,
|
|
@@ -9,40 +10,34 @@ export async function signCommand(opts) {
|
|
|
9
10
|
switch (opts.type) {
|
|
10
11
|
case "personal_sign":
|
|
11
12
|
if (!opts.message) {
|
|
12
|
-
output.
|
|
13
|
-
process.exit(1);
|
|
13
|
+
output.fatal("--message is required for personal_sign");
|
|
14
14
|
}
|
|
15
15
|
request.message = opts.message;
|
|
16
16
|
break;
|
|
17
17
|
case "eth_signTypedData_v4":
|
|
18
18
|
if (!opts.typedData) {
|
|
19
|
-
output.
|
|
20
|
-
process.exit(1);
|
|
19
|
+
output.fatal("--typed-data is required for eth_signTypedData_v4");
|
|
21
20
|
}
|
|
22
21
|
try {
|
|
23
22
|
request.typedData = JSON.parse(opts.typedData);
|
|
24
23
|
}
|
|
25
24
|
catch {
|
|
26
|
-
output.
|
|
27
|
-
process.exit(1);
|
|
25
|
+
output.fatal("--typed-data must be valid JSON");
|
|
28
26
|
}
|
|
29
27
|
break;
|
|
30
28
|
case "eth_signTransaction":
|
|
31
29
|
if (!opts.transaction) {
|
|
32
|
-
output.
|
|
33
|
-
process.exit(1);
|
|
30
|
+
output.fatal("--transaction is required for eth_signTransaction");
|
|
34
31
|
}
|
|
35
32
|
try {
|
|
36
33
|
request.transaction = JSON.parse(opts.transaction);
|
|
37
34
|
}
|
|
38
35
|
catch {
|
|
39
|
-
output.
|
|
40
|
-
process.exit(1);
|
|
36
|
+
output.fatal("--transaction must be valid JSON");
|
|
41
37
|
}
|
|
42
38
|
break;
|
|
43
39
|
default:
|
|
44
|
-
output.
|
|
45
|
-
process.exit(1);
|
|
40
|
+
output.fatal(`Invalid signature type: ${opts.type}. Must be one of: personal_sign, eth_signTypedData_v4, eth_signTransaction`);
|
|
46
41
|
}
|
|
47
42
|
// Execute signing with spinner
|
|
48
43
|
const spinner = output.spinner("Signing...");
|
|
@@ -50,8 +45,7 @@ export async function signCommand(opts) {
|
|
|
50
45
|
const result = await sign(request);
|
|
51
46
|
spinner.stop();
|
|
52
47
|
if (!result.success) {
|
|
53
|
-
output.
|
|
54
|
-
process.exit(1);
|
|
48
|
+
output.fatal(result.error || "Signing failed");
|
|
55
49
|
}
|
|
56
50
|
output.success("Signed successfully");
|
|
57
51
|
emitCESP("task.complete");
|
|
@@ -63,8 +57,7 @@ export async function signCommand(opts) {
|
|
|
63
57
|
catch (err) {
|
|
64
58
|
spinner.stop();
|
|
65
59
|
emitCESP("task.error");
|
|
66
|
-
output.
|
|
67
|
-
process.exit(1);
|
|
60
|
+
output.fatal(getErrorMessage(err));
|
|
68
61
|
}
|
|
69
62
|
}
|
|
70
63
|
//# sourceMappingURL=sign.js.map
|
package/dist/commands/sounds.js
CHANGED
|
@@ -70,8 +70,7 @@ export async function soundsUseCommand(packName) {
|
|
|
70
70
|
const packs = listInstalledPacks();
|
|
71
71
|
const match = packs.find((p) => p.name === packName);
|
|
72
72
|
if (!match) {
|
|
73
|
-
output.
|
|
74
|
-
process.exit(1);
|
|
73
|
+
output.fatal(`Pack "${packName}" not found. Run: bankr sounds list`);
|
|
75
74
|
}
|
|
76
75
|
const sound = getSoundConfig();
|
|
77
76
|
sound.activePack = packName;
|
|
@@ -86,8 +85,7 @@ export async function soundsVolumeCommand(level) {
|
|
|
86
85
|
}
|
|
87
86
|
const vol = parseFloat(level);
|
|
88
87
|
if (isNaN(vol) || vol < 0 || vol > 1) {
|
|
89
|
-
output.
|
|
90
|
-
process.exit(1);
|
|
88
|
+
output.fatal("Volume must be a number between 0.0 and 1.0");
|
|
91
89
|
}
|
|
92
90
|
sound.volume = vol;
|
|
93
91
|
saveSoundConfig(sound);
|
|
@@ -108,8 +106,7 @@ export async function soundsUnmuteCommand() {
|
|
|
108
106
|
export async function soundsTestCommand(category) {
|
|
109
107
|
const sound = getSoundConfig();
|
|
110
108
|
if (!sound.activePack) {
|
|
111
|
-
output.
|
|
112
|
-
process.exit(1);
|
|
109
|
+
output.fatal("No active sound pack. Run: bankr sounds use <pack>");
|
|
113
110
|
}
|
|
114
111
|
const cat = category ?? "task.complete";
|
|
115
112
|
output.dim(`Playing "${cat}" from ${sound.activePack}...`);
|
|
@@ -163,9 +160,7 @@ export async function soundsInstallCommand(packName) {
|
|
|
163
160
|
spin.succeed(`Found ${entry.display_name} (${entry.sound_count} sounds, ${formatSize(entry.total_size_bytes)})`);
|
|
164
161
|
}
|
|
165
162
|
catch (err) {
|
|
166
|
-
|
|
167
|
-
output.error(err.message);
|
|
168
|
-
process.exit(1);
|
|
163
|
+
output.failWith(spin, "Failed to fetch registry", err);
|
|
169
164
|
}
|
|
170
165
|
// Download and extract
|
|
171
166
|
const tarballUrl = `https://github.com/${entry.source_repo}/archive/refs/tags/${entry.source_ref}.tar.gz`;
|
|
@@ -217,9 +212,7 @@ export async function soundsInstallCommand(packName) {
|
|
|
217
212
|
}
|
|
218
213
|
}
|
|
219
214
|
catch (err) {
|
|
220
|
-
|
|
221
|
-
output.error(err.message);
|
|
222
|
-
process.exit(1);
|
|
215
|
+
output.failWith(spin2, "Install failed", err);
|
|
223
216
|
}
|
|
224
217
|
}
|
|
225
218
|
// -- Search command --
|
|
@@ -231,9 +224,7 @@ export async function soundsSearchCommand(query) {
|
|
|
231
224
|
spin.stop();
|
|
232
225
|
}
|
|
233
226
|
catch (err) {
|
|
234
|
-
|
|
235
|
-
output.error(err.message);
|
|
236
|
-
process.exit(1);
|
|
227
|
+
output.failWith(spin, "Failed to fetch registry", err);
|
|
237
228
|
}
|
|
238
229
|
// Filter by query if provided
|
|
239
230
|
let results = registry;
|