@bankr/cli 0.3.23 → 0.3.35

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.
@@ -8,10 +8,6 @@ import { getErrorMessage } from "../lib/errors.js";
8
8
  const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api-keys";
9
9
  const MAX_OTP_ATTEMPTS = 3;
10
10
  const INVALID_CODE_STATUSES = [400, 401, 422];
11
- function fatal(message) {
12
- output.error(message);
13
- process.exit(1);
14
- }
15
11
  function deriveDashboardUrl(apiUrl) {
16
12
  if (apiUrl === DEFAULT_API_URL) {
17
13
  return DEFAULT_DASHBOARD_URL;
@@ -114,7 +110,7 @@ async function callGenerateApiKey(apiUrl, tokens, opts) {
114
110
  async function fetchPrivyConfigOrFail(apiUrl) {
115
111
  const config = await fetchPrivyConfig(apiUrl).catch(() => null);
116
112
  if (!config) {
117
- 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.");
118
114
  }
119
115
  return config;
120
116
  }
@@ -125,8 +121,7 @@ async function sendOtpWithSpinner(privyConfig, email) {
125
121
  spin.succeed(`Verification code sent to ${email}`);
126
122
  }
127
123
  catch (err) {
128
- spin.fail("Failed to send verification code");
129
- fatal(getErrorMessage(err));
124
+ output.failWith(spin, "Failed to send verification code", err);
130
125
  }
131
126
  }
132
127
  async function verifyOtpWithSpinner(privyConfig, email, code) {
@@ -139,7 +134,7 @@ async function verifyOtpWithSpinner(privyConfig, email, code) {
139
134
  catch (err) {
140
135
  const errMsg = getErrorMessage(err);
141
136
  spin.fail("Verification failed");
142
- fatal(errMsg === "INVALID_CODE" ? "Invalid verification code." : errMsg);
137
+ output.fatal(errMsg === "INVALID_CODE" ? "Invalid verification code." : errMsg);
143
138
  }
144
139
  }
145
140
  // ── Email authentication flow ───────────────────────────────────────
@@ -197,13 +192,13 @@ async function authenticateWithEmail(apiUrl, providedEmail, providedCode) {
197
192
  continue;
198
193
  }
199
194
  verifySpin.fail("Invalid code");
200
- fatal("Too many invalid attempts. Please restart login.");
195
+ output.fatal("Too many invalid attempts. Please restart login.");
201
196
  }
202
197
  verifySpin.fail("Verification failed");
203
- fatal(errMsg);
198
+ output.fatal(errMsg);
204
199
  }
205
200
  }
206
- fatal("Verification failed. Please try again.");
201
+ output.fatal("Verification failed. Please try again.");
207
202
  }
208
203
  // ── Email login flow ────────────────────────────────────────────────
209
204
  async function emailLoginFlow(apiUrl, opts) {
@@ -217,8 +212,7 @@ async function emailLoginFlow(apiUrl, opts) {
217
212
  walletSpin.stop();
218
213
  }
219
214
  catch (err) {
220
- walletSpin.fail("Wallet setup failed");
221
- fatal(getErrorMessage(err));
215
+ output.failWith(walletSpin, "Wallet setup failed", err);
222
216
  }
223
217
  // Show wallet info
224
218
  output.blank();
@@ -248,7 +242,7 @@ async function emailLoginFlow(apiUrl, opts) {
248
242
  theme: output.bankrTheme,
249
243
  }));
250
244
  if (!accepted) {
251
- fatal("Terms must be accepted to continue.");
245
+ output.fatal("Terms must be accepted to continue.");
252
246
  }
253
247
  const termsSpin = output.spinner("Accepting terms...");
254
248
  try {
@@ -256,8 +250,7 @@ async function emailLoginFlow(apiUrl, opts) {
256
250
  termsSpin.succeed("Terms accepted");
257
251
  }
258
252
  catch (err) {
259
- termsSpin.fail("Failed to accept terms");
260
- fatal(getErrorMessage(err));
253
+ output.failWith(termsSpin, "Failed to accept terms", err);
261
254
  }
262
255
  }
263
256
  // Step 4: Generate API key
@@ -305,8 +298,7 @@ async function emailLoginFlow(apiUrl, opts) {
305
298
  keySpin.stop();
306
299
  }
307
300
  catch (err) {
308
- keySpin.fail("Failed to generate API key");
309
- fatal(getErrorMessage(err));
301
+ output.failWith(keySpin, "Failed to generate API key", err);
310
302
  }
311
303
  // Show result
312
304
  output.blank();
@@ -345,7 +337,7 @@ async function siweLoginFlow(apiUrl, opts) {
345
337
  });
346
338
  if (!nonceRes.ok) {
347
339
  nonceSpinner.fail("Failed to fetch nonce");
348
- fatal(`Nonce request failed (${nonceRes.status})`);
340
+ output.fatal(`Nonce request failed (${nonceRes.status})`);
349
341
  }
350
342
  const { nonce } = (await nonceRes.json());
351
343
  nonceSpinner.succeed("Nonce received");
@@ -389,7 +381,7 @@ async function siweLoginFlow(apiUrl, opts) {
389
381
  if (!verifyRes.ok) {
390
382
  verifySpinner.fail("SIWE verification failed");
391
383
  const body = (await verifyRes.json().catch(() => ({})));
392
- fatal(body.message || `Verification failed (${verifyRes.status})`);
384
+ output.fatal(body.message || `Verification failed (${verifyRes.status})`);
393
385
  }
394
386
  const result = (await verifyRes.json());
395
387
  verifySpinner.succeed("Wallet created");
@@ -417,13 +409,11 @@ async function validateApiKey(apiUrl, apiKey) {
417
409
  const errorType = body.error ?? "";
418
410
  if (errorType === "Authentication required") {
419
411
  spin.fail("API key is not linked to a wallet");
420
- output.error("This API key is not associated with a wallet. Generate a new key at https://bankr.bot/api-keys");
421
- 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");
422
413
  }
423
414
  else {
424
415
  spin.fail("Invalid Bankr API key");
425
- output.error("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api-keys");
426
- process.exit(1);
416
+ output.fatal("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api-keys");
427
417
  }
428
418
  }
429
419
  else {
@@ -461,7 +451,7 @@ function parseAndValidateIps(raw) {
461
451
  .filter(Boolean);
462
452
  const invalid = ips.filter((ip) => !isValidIpOrCidr(ip));
463
453
  if (invalid.length > 0) {
464
- fatal(`Invalid IP address(es) or CIDR range(s): ${invalid.join(", ")}`);
454
+ output.fatal(`Invalid IP address(es) or CIDR range(s): ${invalid.join(", ")}`);
465
455
  }
466
456
  return ips;
467
457
  }
@@ -493,14 +483,14 @@ function parseAndValidateRecipients(raw) {
493
483
  }
494
484
  }
495
485
  if (invalid.length > 0) {
496
- fatal(`Invalid address(es): ${invalid.join(", ")}`);
486
+ output.fatal(`Invalid address(es): ${invalid.join(", ")}`);
497
487
  }
498
488
  return { evm, solana };
499
489
  }
500
490
  export async function loginCommand(opts) {
501
491
  // Fail fast if flags were passed with empty values
502
492
  if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
503
- fatal("--api-key requires a non-empty value");
493
+ output.fatal("--api-key requires a non-empty value");
504
494
  }
505
495
  // Non-interactive mode requires a headless flag combo. Without one, the
506
496
  // command would hang on a prompt — fail fast with the available paths.
@@ -512,8 +502,7 @@ export async function loginCommand(opts) {
512
502
  (opts.siwe && opts.privateKey) ||
513
503
  (opts.email && opts.email.trim());
514
504
  if (!hasHeadlessFlag) {
515
- output.error("Non-interactive login requires one of: --api-key <key>, login siwe --private-key <key>, or login email <address> [--code <otp>]");
516
- 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>]");
517
506
  }
518
507
  }
519
508
  const config = readConfig();
@@ -573,7 +562,7 @@ export async function loginCommand(opts) {
573
562
  // --siwe: SIWE login flow (headless agent onboarding)
574
563
  if (opts.siwe) {
575
564
  if (!opts.privateKey) {
576
- fatal("--private-key is required with --siwe");
565
+ output.fatal("--private-key is required with --siwe");
577
566
  }
578
567
  const { apiKey } = await siweLoginFlow(apiUrl, {
579
568
  privateKey: opts.privateKey,
@@ -604,7 +593,7 @@ export async function loginCommand(opts) {
604
593
  if (opts.apiKey) {
605
594
  const apiKey = opts.apiKey.trim();
606
595
  if (!apiKey.startsWith("bk_")) {
607
- fatal('Invalid format. Bankr API keys start with "bk_".');
596
+ output.fatal('Invalid format. Bankr API keys start with "bk_".');
608
597
  }
609
598
  await validateApiKey(apiUrl, apiKey);
610
599
  saveCredentials(apiKey, opts.llmKey);
@@ -54,8 +54,7 @@ export async function portfolioCommand(opts) {
54
54
  if (chains) {
55
55
  const invalid = chains.filter((c) => !VALID_CHAINS.has(c));
56
56
  if (invalid.length > 0) {
57
- output.error(`Invalid chain${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid chains: ${[...VALID_CHAINS].join(", ")}`);
58
- process.exit(1);
57
+ output.fatal(`Invalid chain${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid chains: ${[...VALID_CHAINS].join(", ")}`);
59
58
  }
60
59
  }
61
60
  const includePnl = opts.pnl || opts.all;
@@ -4,57 +4,59 @@ import { getLastThreadId, requireApiKey, setLastThreadId, } from "../lib/config.
4
4
  import * as output from "../lib/output.js";
5
5
  import { getErrorMessage } from "../lib/errors.js";
6
6
  /** Valid Max Mode model IDs — hardcoded because CLI is standalone (no monorepo imports).
7
- * 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. */
8
14
  const VALID_MAX_MODE_MODELS = new Set([
9
15
  // Claude (Vertex AI)
16
+ "claude-fable-5.1",
10
17
  "claude-fable-5",
11
18
  "claude-opus-5",
12
19
  "claude-opus-4.8",
13
- "claude-opus-4.7",
14
- "claude-opus-4.6",
15
- "claude-opus-4.5",
16
20
  "claude-sonnet-5",
17
21
  "claude-sonnet-4.6",
18
- "claude-sonnet-4.5",
19
- "claude-haiku-4.5",
20
22
  // Gemini (Vertex AI) — gemini-3-pro deliberately absent: removed 2026-03-09,
21
- // both provider slugs retired. Don't re-add on a sync pass.
22
- "gemini-3.6-flash",
23
- "gemini-3.5-flash-lite",
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",
24
28
  "gemini-3.1-pro",
25
29
  "gemini-2.5-pro",
26
30
  // OpenAI (OpenRouter)
31
+ "gpt-6-astra",
27
32
  "gpt-5.6-sol",
28
- "gpt-5.6-terra",
29
- "gpt-5.6-luna",
30
33
  "gpt-5.5",
31
- "gpt-5.4",
32
- "gpt-5.4-mini",
33
- "gpt-5.2",
34
- "gpt-5-mini",
34
+ "gpt-5.6-terra",
35
+ "gpt-5.2-codex",
35
36
  // Grok (OpenRouter)
36
37
  "grok-4.6",
37
38
  "grok-4.5",
38
- "grok-4.3",
39
- // DeepSeek (OpenRouter)
40
- "deepseek-v3.2",
39
+ // DeepSeek (OpenRouter) — v4.1-flash is its own tier at `balanced`
40
+ // ($0.30/$1.20, 3.3x under the V4 Pro card), not the `light` flash line.
41
+ "deepseek-v4-pro-0813",
42
+ "deepseek-v4-pro",
43
+ "deepseek-v4.1-flash",
41
44
  // Qwen (OpenRouter)
42
- "qwen3-coder",
43
45
  "qwen3.8-max",
44
46
  "qwen3.7-max",
45
47
  "qwen3.7-plus",
46
- "qwen3.7-flash",
47
48
  "qwen3.5-plus",
48
49
  // MiniMax (direct)
49
- "minimax-m2.5",
50
+ "minimax-m3",
50
51
  "minimax-m2.7",
52
+ "minimax-m2.7-highspeed",
51
53
  // Other (OpenRouter)
52
54
  "kimi-k3",
53
55
  "kimi-k2.7-code",
54
56
  "kimi-k2.6",
55
- "kimi-k2.5",
56
- "glm-5",
57
57
  "glm-5.2",
58
+ "glm-5.3",
59
+ "glm-5-turbo",
58
60
  ]);
59
61
  export async function promptCommand(text, options = {}) {
60
62
  requireApiKey();
@@ -63,8 +65,7 @@ export async function promptCommand(text, options = {}) {
63
65
  if (options.continue) {
64
66
  threadId = getLastThreadId();
65
67
  if (!threadId) {
66
- output.error("No previous thread found. Run a prompt first.");
67
- process.exit(1);
68
+ output.fatal("No previous thread found. Run a prompt first.");
68
69
  }
69
70
  }
70
71
  else if (options.thread) {
@@ -74,8 +75,7 @@ export async function promptCommand(text, options = {}) {
74
75
  let maxMode;
75
76
  if (options.model) {
76
77
  if (!VALID_MAX_MODE_MODELS.has(options.model)) {
77
- output.error(`Invalid model "${options.model}". Valid models: ${[...VALID_MAX_MODE_MODELS].join(", ")}`);
78
- process.exit(1);
78
+ output.fatal(`Invalid model "${options.model}". Valid models: ${[...VALID_MAX_MODE_MODELS].join(", ")}`);
79
79
  }
80
80
  maxMode = { enabled: true, model: options.model };
81
81
  }
@@ -154,8 +154,7 @@ export async function promptCommand(text, options = {}) {
154
154
  catch (err) {
155
155
  spin.fail("Error while polling");
156
156
  emitCESP("resource.limit");
157
- output.error(getErrorMessage(err));
158
- process.exit(1);
157
+ output.fatal(getErrorMessage(err));
159
158
  }
160
159
  }
161
160
  //# sourceMappingURL=prompt.js.map
@@ -10,40 +10,34 @@ export async function signCommand(opts) {
10
10
  switch (opts.type) {
11
11
  case "personal_sign":
12
12
  if (!opts.message) {
13
- output.error("--message is required for personal_sign");
14
- process.exit(1);
13
+ output.fatal("--message is required for personal_sign");
15
14
  }
16
15
  request.message = opts.message;
17
16
  break;
18
17
  case "eth_signTypedData_v4":
19
18
  if (!opts.typedData) {
20
- output.error("--typed-data is required for eth_signTypedData_v4");
21
- process.exit(1);
19
+ output.fatal("--typed-data is required for eth_signTypedData_v4");
22
20
  }
23
21
  try {
24
22
  request.typedData = JSON.parse(opts.typedData);
25
23
  }
26
24
  catch {
27
- output.error("--typed-data must be valid JSON");
28
- process.exit(1);
25
+ output.fatal("--typed-data must be valid JSON");
29
26
  }
30
27
  break;
31
28
  case "eth_signTransaction":
32
29
  if (!opts.transaction) {
33
- output.error("--transaction is required for eth_signTransaction");
34
- process.exit(1);
30
+ output.fatal("--transaction is required for eth_signTransaction");
35
31
  }
36
32
  try {
37
33
  request.transaction = JSON.parse(opts.transaction);
38
34
  }
39
35
  catch {
40
- output.error("--transaction must be valid JSON");
41
- process.exit(1);
36
+ output.fatal("--transaction must be valid JSON");
42
37
  }
43
38
  break;
44
39
  default:
45
- output.error(`Invalid signature type: ${opts.type}. Must be one of: personal_sign, eth_signTypedData_v4, eth_signTransaction`);
46
- process.exit(1);
40
+ output.fatal(`Invalid signature type: ${opts.type}. Must be one of: personal_sign, eth_signTypedData_v4, eth_signTransaction`);
47
41
  }
48
42
  // Execute signing with spinner
49
43
  const spinner = output.spinner("Signing...");
@@ -51,8 +45,7 @@ export async function signCommand(opts) {
51
45
  const result = await sign(request);
52
46
  spinner.stop();
53
47
  if (!result.success) {
54
- output.error(result.error || "Signing failed");
55
- process.exit(1);
48
+ output.fatal(result.error || "Signing failed");
56
49
  }
57
50
  output.success("Signed successfully");
58
51
  emitCESP("task.complete");
@@ -64,8 +57,7 @@ export async function signCommand(opts) {
64
57
  catch (err) {
65
58
  spinner.stop();
66
59
  emitCESP("task.error");
67
- output.error(getErrorMessage(err));
68
- process.exit(1);
60
+ output.fatal(getErrorMessage(err));
69
61
  }
70
62
  }
71
63
  //# sourceMappingURL=sign.js.map
@@ -6,7 +6,6 @@ import { tmpdir } from "node:os";
6
6
  import { readConfig, writeConfig } from "../lib/config.js";
7
7
  import { DEFAULT_SOUND_CONFIG, emitCESP, getPacksDir, listInstalledPacks, } from "../lib/cesp/engine.js";
8
8
  import * as output from "../lib/output.js";
9
- import { getErrorMessage } from "../lib/errors.js";
10
9
  const execFileAsync = promisify(execFile);
11
10
  const REGISTRY_URL = "https://peonping.github.io/registry/index.json";
12
11
  function getSoundConfig() {
@@ -71,8 +70,7 @@ export async function soundsUseCommand(packName) {
71
70
  const packs = listInstalledPacks();
72
71
  const match = packs.find((p) => p.name === packName);
73
72
  if (!match) {
74
- output.error(`Pack "${packName}" not found. Run: bankr sounds list`);
75
- process.exit(1);
73
+ output.fatal(`Pack "${packName}" not found. Run: bankr sounds list`);
76
74
  }
77
75
  const sound = getSoundConfig();
78
76
  sound.activePack = packName;
@@ -87,8 +85,7 @@ export async function soundsVolumeCommand(level) {
87
85
  }
88
86
  const vol = parseFloat(level);
89
87
  if (isNaN(vol) || vol < 0 || vol > 1) {
90
- output.error("Volume must be a number between 0.0 and 1.0");
91
- process.exit(1);
88
+ output.fatal("Volume must be a number between 0.0 and 1.0");
92
89
  }
93
90
  sound.volume = vol;
94
91
  saveSoundConfig(sound);
@@ -109,8 +106,7 @@ export async function soundsUnmuteCommand() {
109
106
  export async function soundsTestCommand(category) {
110
107
  const sound = getSoundConfig();
111
108
  if (!sound.activePack) {
112
- output.error("No active sound pack. Run: bankr sounds use <pack>");
113
- process.exit(1);
109
+ output.fatal("No active sound pack. Run: bankr sounds use <pack>");
114
110
  }
115
111
  const cat = category ?? "task.complete";
116
112
  output.dim(`Playing "${cat}" from ${sound.activePack}...`);
@@ -164,9 +160,7 @@ export async function soundsInstallCommand(packName) {
164
160
  spin.succeed(`Found ${entry.display_name} (${entry.sound_count} sounds, ${formatSize(entry.total_size_bytes)})`);
165
161
  }
166
162
  catch (err) {
167
- spin.fail("Failed to fetch registry");
168
- output.error(getErrorMessage(err));
169
- process.exit(1);
163
+ output.failWith(spin, "Failed to fetch registry", err);
170
164
  }
171
165
  // Download and extract
172
166
  const tarballUrl = `https://github.com/${entry.source_repo}/archive/refs/tags/${entry.source_ref}.tar.gz`;
@@ -218,9 +212,7 @@ export async function soundsInstallCommand(packName) {
218
212
  }
219
213
  }
220
214
  catch (err) {
221
- spin2.fail("Install failed");
222
- output.error(getErrorMessage(err));
223
- process.exit(1);
215
+ output.failWith(spin2, "Install failed", err);
224
216
  }
225
217
  }
226
218
  // -- Search command --
@@ -232,9 +224,7 @@ export async function soundsSearchCommand(query) {
232
224
  spin.stop();
233
225
  }
234
226
  catch (err) {
235
- spin.fail("Failed to fetch registry");
236
- output.error(getErrorMessage(err));
237
- process.exit(1);
227
+ output.failWith(spin, "Failed to fetch registry", err);
238
228
  }
239
229
  // Filter by query if provided
240
230
  let results = registry;
@@ -1,7 +1,6 @@
1
1
  import { getJobStatus } from "../lib/api.js";
2
2
  import { requireApiKey } from "../lib/config.js";
3
3
  import * as output from "../lib/output.js";
4
- import { getErrorMessage } from "../lib/errors.js";
5
4
  export async function statusCommand(jobId) {
6
5
  requireApiKey();
7
6
  const spin = output.spinner("Fetching job status...");
@@ -38,9 +37,7 @@ export async function statusCommand(jobId) {
38
37
  }
39
38
  }
40
39
  catch (err) {
41
- spin.fail("Failed to fetch job status");
42
- output.error(getErrorMessage(err));
43
- process.exit(1);
40
+ output.failWith(spin, "Failed to fetch job status", err);
44
41
  }
45
42
  }
46
43
  //# sourceMappingURL=status.js.map
@@ -28,24 +28,20 @@ function displayResult(result) {
28
28
  export async function submitCommand(opts) {
29
29
  // Validate required fields
30
30
  if (!opts.to) {
31
- output.error("--to is required");
32
- process.exit(1);
31
+ output.fatal("--to is required");
33
32
  }
34
33
  if (!opts.chainId) {
35
- output.error("--chain-id is required");
36
- process.exit(1);
34
+ output.fatal("--chain-id is required");
37
35
  }
38
36
  const chainId = parseInt(opts.chainId, 10);
39
37
  if (isNaN(chainId)) {
40
- output.error("--chain-id must be a number");
41
- process.exit(1);
38
+ output.fatal("--chain-id must be a number");
42
39
  }
43
40
  let nonce;
44
41
  if (opts.nonce) {
45
42
  nonce = parseInt(opts.nonce, 10);
46
43
  if (isNaN(nonce)) {
47
- output.error("--nonce must be a number");
48
- process.exit(1);
44
+ output.fatal("--nonce must be a number");
49
45
  }
50
46
  }
51
47
  const request = {
@@ -74,8 +70,7 @@ export async function submitCommand(opts) {
74
70
  }
75
71
  catch (err) {
76
72
  spinner.stop();
77
- output.error(getErrorMessage(err));
78
- process.exit(1);
73
+ output.fatal(getErrorMessage(err));
79
74
  }
80
75
  }
81
76
  // Alternative: submit from JSON
@@ -85,32 +80,26 @@ export async function submitJsonCommand(json, opts) {
85
80
  transaction = JSON.parse(json);
86
81
  }
87
82
  catch {
88
- output.error("Invalid JSON. Expected: {to, chainId, value?, data?}");
89
- process.exit(1);
83
+ output.fatal("Invalid JSON. Expected: {to, chainId, value?, data?}");
90
84
  }
91
85
  if (!transaction ||
92
86
  typeof transaction !== "object" ||
93
87
  Array.isArray(transaction)) {
94
- output.error("Invalid JSON. Expected object: {to, chainId, value?, data?}");
95
- process.exit(1);
88
+ output.fatal("Invalid JSON. Expected object: {to, chainId, value?, data?}");
96
89
  }
97
90
  // Validate required fields
98
91
  if (!transaction.to) {
99
- output.error("Transaction must have 'to' field");
100
- process.exit(1);
92
+ output.fatal("Transaction must have 'to' field");
101
93
  }
102
94
  if (transaction.chainId === undefined) {
103
- output.error("Transaction must have 'chainId' field");
104
- process.exit(1);
95
+ output.fatal("Transaction must have 'chainId' field");
105
96
  }
106
97
  if (typeof transaction.chainId !== "number" || isNaN(transaction.chainId)) {
107
- output.error("Transaction 'chainId' must be a number");
108
- process.exit(1);
98
+ output.fatal("Transaction 'chainId' must be a number");
109
99
  }
110
100
  if (transaction.nonce !== undefined &&
111
101
  (typeof transaction.nonce !== "number" || isNaN(transaction.nonce))) {
112
- output.error("Transaction 'nonce' must be a number");
113
- process.exit(1);
102
+ output.fatal("Transaction 'nonce' must be a number");
114
103
  }
115
104
  const request = {
116
105
  transaction,
@@ -128,8 +117,7 @@ export async function submitJsonCommand(json, opts) {
128
117
  }
129
118
  catch (err) {
130
119
  spinner.stop();
131
- output.error(getErrorMessage(err));
132
- process.exit(1);
120
+ output.fatal(getErrorMessage(err));
133
121
  }
134
122
  }
135
123
  //# sourceMappingURL=submit.js.map
@@ -24,16 +24,14 @@ async function resolveSwapToken(tokenInput, chain) {
24
24
  export async function swapCommand(opts) {
25
25
  const chain = opts.chain ?? "base";
26
26
  if (!VALID_CHAINS.has(chain) || chain === "solana") {
27
- output.error(`Unsupported chain "${chain}". Swaps support EVM chains: base, polygon, mainnet, unichain, worldchain, arbitrum, bnb.`);
28
- process.exit(1);
27
+ output.fatal(`Unsupported chain "${chain}". Swaps support EVM chains: base, polygon, mainnet, unichain, worldchain, arbitrum, bnb.`);
29
28
  }
30
29
  // Validate amount client-side so a bad value gives a clear error instead of a
31
30
  // generic 500 from the API's parseUnits(amount, decimals). Number() alone
32
31
  // accepts "1e-3" / "0x10" / "Infinity" that parseUnits rejects, so require a
33
32
  // plain decimal string.
34
33
  if (!/^\d*\.?\d+$/.test(opts.amount) || Number(opts.amount) <= 0) {
35
- output.error(`Invalid amount "${opts.amount}". Provide a positive decimal number, e.g. --amount 0.5.`);
36
- process.exit(1);
34
+ output.fatal(`Invalid amount "${opts.amount}". Provide a positive decimal number, e.g. --amount 0.5.`);
37
35
  }
38
36
  // Resolve from/to tokens (symbol → address, native → zero address)
39
37
  let fromToken;
@@ -38,8 +38,7 @@ export async function tokensInfoCommand(address, opts) {
38
38
  const token = res.tokens.find((t) => t.address.toLowerCase() === addressLower) ??
39
39
  res.tokens[0];
40
40
  if (!token) {
41
- output.error("Token not found. Check the address and try again.");
42
- process.exit(1);
41
+ output.fatal("Token not found. Check the address and try again.");
43
42
  }
44
43
  console.log();
45
44
  output.label("Name", token.name);
@@ -14,8 +14,7 @@ async function resolveTransferRecipient(raw) {
14
14
  return { address: value, label: value };
15
15
  }
16
16
  if (!looksLikeEnsName(value)) {
17
- output.error(`Invalid recipient "${value}". Expected a 0x address or an ENS name (e.g. vitalik.eth, name.base.eth, name.cb.id).`);
18
- process.exit(1);
17
+ output.fatal(`Invalid recipient "${value}". Expected a 0x address or an ENS name (e.g. vitalik.eth, name.base.eth, name.cb.id).`);
19
18
  }
20
19
  const spin = output.spinner(`Resolving ENS name "${value}"...`);
21
20
  try {
@@ -39,8 +38,7 @@ export async function transferCommand(opts) {
39
38
  const isNative = opts.native ?? false;
40
39
  const chain = opts.chain ?? "base";
41
40
  if (!isNative && !opts.token) {
42
- output.error("Token required for ERC20 transfers. Use --token <symbol or address> or --native.");
43
- process.exit(1);
41
+ output.fatal("Token required for ERC20 transfers. Use --token <symbol or address> or --native.");
44
42
  }
45
43
  const recipient = await resolveTransferRecipient(opts.to);
46
44
  const recipientAddress = recipient.address;
@@ -80,8 +80,7 @@ export async function updateCommand(opts) {
80
80
  }
81
81
  catch (err) {
82
82
  spin.fail();
83
- output.error(getErrorMessage(err));
84
- process.exit(1);
83
+ output.fatal(getErrorMessage(err));
85
84
  }
86
85
  const cmp = compareSemver(current, latest);
87
86
  if (cmp === 0) {