@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.
@@ -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
@@ -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, identityToken) {
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, identityToken) {
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, identityToken, opts) {
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
- spin.fail("Failed to send verification code");
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.message;
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 { identityToken: result.identity_token, email };
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 { identityToken: result.identity_token, email };
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.message;
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 { identityToken } = await authenticateWithEmail(apiUrl, opts.email, opts.code);
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, identityToken);
211
+ wallet = await callGenerateWallet(apiUrl, tokens);
206
212
  walletSpin.stop();
207
213
  }
208
214
  catch (err) {
209
- walletSpin.fail("Wallet setup failed");
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, identityToken);
249
+ await callAcceptTerms(apiUrl, tokens);
245
250
  termsSpin.succeed("Terms accepted");
246
251
  }
247
252
  catch (err) {
248
- termsSpin.fail("Failed to accept terms");
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, identityToken, {
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
- keySpin.fail("Failed to generate API key");
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.error("This API key is not associated with a wallet. Generate a new key at https://bankr.bot/api-keys");
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.error("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api-keys");
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.error("Non-interactive login requires one of: --api-key <key>, login siwe --private-key <key>, or login email <address> [--code <otp>]");
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.error(`Invalid chain${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid chains: ${[...VALID_CHAINS].join(", ")}`);
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.message}`);
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 && data.nfts.length > 0) {
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)`)}`);
@@ -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.message;
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.message);
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.message);
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.message);
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.message);
225
+ output.error(getErrorMessage(err));
225
226
  }
226
227
  }
227
228
  //# sourceMappingURL=profile.js.map
@@ -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
- "claude-sonnet-4.5",
17
- "claude-haiku-4.5",
18
- // Gemini (Vertex AI)
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-5.4",
24
- "gpt-5.4-mini",
25
- "gpt-5.2",
26
- "gpt-5-mini",
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.1-fast",
37
+ "grok-4.6",
38
+ "grok-4.5",
29
39
  // DeepSeek (OpenRouter)
30
- "deepseek-v3.2",
40
+ "deepseek-v4-pro-0813",
41
+ "deepseek-v4-pro",
31
42
  // Qwen (OpenRouter)
32
- "qwen3-coder",
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-m2.5",
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.error("No previous thread found. Run a prompt first.");
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.error(`Invalid model "${options.model}". Valid models: ${[...VALID_MAX_MODE_MODELS].join(", ")}`);
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.message);
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 && result.transactions.length > 0) {
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.error(err.message);
145
- process.exit(1);
155
+ output.fatal(getErrorMessage(err));
146
156
  }
147
157
  }
148
158
  //# sourceMappingURL=prompt.js.map
@@ -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.error("--message is required for personal_sign");
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.error("--typed-data is required for eth_signTypedData_v4");
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.error("--typed-data must be valid JSON");
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.error("--transaction is required for eth_signTransaction");
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.error("--transaction must be valid JSON");
40
- process.exit(1);
36
+ output.fatal("--transaction must be valid JSON");
41
37
  }
42
38
  break;
43
39
  default:
44
- output.error(`Invalid signature type: ${opts.type}. Must be one of: personal_sign, eth_signTypedData_v4, eth_signTransaction`);
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.error(result.error || "Signing failed");
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.error(err.message);
67
- process.exit(1);
60
+ output.fatal(getErrorMessage(err));
68
61
  }
69
62
  }
70
63
  //# sourceMappingURL=sign.js.map
@@ -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.error(`Pack "${packName}" not found. Run: bankr sounds list`);
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.error("Volume must be a number between 0.0 and 1.0");
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.error("No active sound pack. Run: bankr sounds use <pack>");
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
- spin.fail("Failed to fetch registry");
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
- spin2.fail("Install failed");
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
- spin.fail("Failed to fetch registry");
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;