@bankr/cli 0.3.23 → 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.
@@ -16,9 +16,9 @@
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { confirm } from "@inquirer/prompts";
19
+ import { authHeaders, handleResponse } from "../lib/api.js";
19
20
  import * as output from "../lib/output.js";
20
- import { getErrorMessage } from "../lib/errors.js";
21
- import { getApiUrl, requireApiKey, CLI_USER_AGENT } from "../lib/config.js";
21
+ import { getApiUrl } from "../lib/config.js";
22
22
  // ── Config file ─────────────────────────────────────────────────────────
23
23
  const CONFIG_FILENAME = "bankr.webhooks.json";
24
24
  const WEBHOOKS_DIR = "webhooks";
@@ -36,8 +36,7 @@ function loadConfigFile(projectRoot) {
36
36
  return JSON.parse(readFileSync(p, "utf-8"));
37
37
  }
38
38
  catch {
39
- output.error(`Failed to parse ${p}. Check that it is valid JSON.`);
40
- process.exit(1);
39
+ output.fatal(`Failed to parse ${p}. Check that it is valid JSON.`);
41
40
  }
42
41
  }
43
42
  }
@@ -46,23 +45,6 @@ function loadConfigFile(projectRoot) {
46
45
  function saveConfigFile(projectRoot, config) {
47
46
  writeFileSync(join(projectRoot, CONFIG_FILENAME), JSON.stringify(config, null, 2) + "\n");
48
47
  }
49
- function authHeaders() {
50
- return {
51
- "X-API-Key": requireApiKey(),
52
- "Content-Type": "application/json",
53
- "User-Agent": CLI_USER_AGENT,
54
- };
55
- }
56
- async function handleResponse(res) {
57
- if (!res.ok) {
58
- const body = (await res
59
- .json()
60
- .catch(() => ({ message: res.statusText })));
61
- const msg = body.message || body.error || res.statusText;
62
- throw new Error(`API error (${res.status}): ${msg}`);
63
- }
64
- return res.json();
65
- }
66
48
  const MAX_WEBHOOK_NAME_LENGTH = 47;
67
49
  const WEBHOOK_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
68
50
  function validateWebhookName(name) {
@@ -326,8 +308,7 @@ export default async function handler(req: Request): Promise<Response> {
326
308
  export async function webhooksAddCommand(name, options = {}) {
327
309
  const err = validateWebhookName(name);
328
310
  if (err) {
329
- output.error(err);
330
- process.exit(1);
311
+ output.fatal(err);
331
312
  }
332
313
  const providerInput = (options.provider ?? "generic").toLowerCase();
333
314
  const validProviders = [
@@ -337,20 +318,17 @@ export async function webhooksAddCommand(name, options = {}) {
337
318
  "generic",
338
319
  ];
339
320
  if (!validProviders.includes(providerInput)) {
340
- output.error(`Invalid provider "${providerInput}". Valid: ${validProviders.join(", ")}.`);
341
- process.exit(1);
321
+ output.fatal(`Invalid provider "${providerInput}". Valid: ${validProviders.join(", ")}.`);
342
322
  }
343
323
  const provider = providerInput;
344
324
  const root = findProjectRoot();
345
325
  const dir = join(root, WEBHOOKS_DIR);
346
326
  if (!existsSync(dir)) {
347
- output.error(`No ${WEBHOOKS_DIR}/ directory found. Run 'bankr webhooks init' first.`);
348
- process.exit(1);
327
+ output.fatal(`No ${WEBHOOKS_DIR}/ directory found. Run 'bankr webhooks init' first.`);
349
328
  }
350
329
  const serviceDir = join(dir, name);
351
330
  if (existsSync(serviceDir)) {
352
- output.error(`Webhook "${name}" already exists at ${WEBHOOKS_DIR}/${name}/`);
353
- process.exit(1);
331
+ output.fatal(`Webhook "${name}" already exists at ${WEBHOOKS_DIR}/${name}/`);
354
332
  }
355
333
  mkdirSync(serviceDir, { recursive: true });
356
334
  const { source, envHint } = handlerTemplate(name, provider);
@@ -397,8 +375,7 @@ export async function webhooksDeployCommand(name) {
397
375
  const root = findProjectRoot();
398
376
  const dir = join(root, WEBHOOKS_DIR);
399
377
  if (!existsSync(dir)) {
400
- output.error(`No ${WEBHOOKS_DIR}/ directory found. Run 'bankr webhooks init' first.`);
401
- process.exit(1);
378
+ output.fatal(`No ${WEBHOOKS_DIR}/ directory found. Run 'bankr webhooks init' first.`);
402
379
  }
403
380
  let config = loadConfigFile(root);
404
381
  if (!config)
@@ -413,19 +390,16 @@ export async function webhooksDeployCommand(name) {
413
390
  }
414
391
  }
415
392
  if (discovered.length === 0) {
416
- output.error("No webhooks found. Run 'bankr webhooks add <name>' first.");
417
- process.exit(1);
393
+ output.fatal("No webhooks found. Run 'bankr webhooks add <name>' first.");
418
394
  }
419
395
  const toDeploy = name ? discovered.filter((s) => s === name) : discovered;
420
396
  if (name && toDeploy.length === 0) {
421
- output.error(`Webhook "${name}" not found in ${WEBHOOKS_DIR}/`);
422
- process.exit(1);
397
+ output.fatal(`Webhook "${name}" not found in ${WEBHOOKS_DIR}/`);
423
398
  }
424
399
  for (const svc of toDeploy) {
425
400
  const nameError = validateWebhookName(svc);
426
401
  if (nameError) {
427
- output.error(nameError);
428
- process.exit(1);
402
+ output.fatal(nameError);
429
403
  }
430
404
  if (!config.webhooks[svc]) {
431
405
  config.webhooks[svc] = {
@@ -487,9 +461,7 @@ export async function webhooksDeployCommand(name) {
487
461
  output.info("Your handler code is responsible for verifying incoming signatures (Slack, GitHub, etc.). Provider-specific scaffolds include the verifier inline — `bankr webhooks add <name> --provider slack|github|stripe` to scaffold one.");
488
462
  }
489
463
  catch (err) {
490
- spin.fail("Deploy failed");
491
- output.error(getErrorMessage(err));
492
- process.exit(1);
464
+ output.failWith(spin, "Deploy failed", err);
493
465
  }
494
466
  }
495
467
  export async function webhooksListCommand() {
@@ -517,9 +489,7 @@ export async function webhooksListCommand() {
517
489
  }
518
490
  }
519
491
  catch (err) {
520
- spin.fail("Failed to list webhooks");
521
- output.error(getErrorMessage(err));
522
- process.exit(1);
492
+ output.failWith(spin, "Failed to list webhooks", err);
523
493
  }
524
494
  }
525
495
  export async function webhooksPauseResumeCommand(name, action) {
@@ -535,9 +505,7 @@ export async function webhooksPauseResumeCommand(name, action) {
535
505
  spin.succeed(`${name} ${action === "pause" ? "paused" : "resumed"}`);
536
506
  }
537
507
  catch (err) {
538
- spin.fail(`Failed to ${action} ${name}`);
539
- output.error(getErrorMessage(err));
540
- process.exit(1);
508
+ output.failWith(spin, `Failed to ${action} ${name}`, err);
541
509
  }
542
510
  }
543
511
  export async function webhooksDeleteCommand(name) {
@@ -558,9 +526,7 @@ export async function webhooksDeleteCommand(name) {
558
526
  spin.succeed(`${name} deleted`);
559
527
  }
560
528
  catch (err) {
561
- spin.fail(`Failed to delete ${name}`);
562
- output.error(getErrorMessage(err));
563
- process.exit(1);
529
+ output.failWith(spin, `Failed to delete ${name}`, err);
564
530
  }
565
531
  }
566
532
  export async function webhooksLogsCommand(name) {
@@ -586,23 +552,19 @@ export async function webhooksLogsCommand(name) {
586
552
  }
587
553
  }
588
554
  catch (err) {
589
- spin.fail("Failed to fetch logs");
590
- output.error(getErrorMessage(err));
591
- process.exit(1);
555
+ output.failWith(spin, "Failed to fetch logs", err);
592
556
  }
593
557
  }
594
558
  // ── Env vars ────────────────────────────────────────────────────────────
595
559
  export async function webhooksEnvSetCommand(keyValue) {
596
560
  const eqIdx = keyValue.indexOf("=");
597
561
  if (eqIdx === -1) {
598
- output.error("Usage: bankr webhooks env set KEY=VALUE");
599
- process.exit(1);
562
+ output.fatal("Usage: bankr webhooks env set KEY=VALUE");
600
563
  }
601
564
  const key = keyValue.slice(0, eqIdx);
602
565
  const value = keyValue.slice(eqIdx + 1);
603
566
  if (!key) {
604
- output.error("Key cannot be empty");
605
- process.exit(1);
567
+ output.fatal("Key cannot be empty");
606
568
  }
607
569
  const spin = output.spinner(`Setting ${key}...`);
608
570
  try {
@@ -615,9 +577,7 @@ export async function webhooksEnvSetCommand(keyValue) {
615
577
  spin.succeed(`Set ${key}`);
616
578
  }
617
579
  catch (err) {
618
- spin.fail(`Failed to set ${key}`);
619
- output.error(getErrorMessage(err));
620
- process.exit(1);
580
+ output.failWith(spin, `Failed to set ${key}`, err);
621
581
  }
622
582
  }
623
583
  export async function webhooksEnvListCommand() {
@@ -636,9 +596,7 @@ export async function webhooksEnvListCommand() {
636
596
  console.log(` ${n}`);
637
597
  }
638
598
  catch (err) {
639
- spin.fail("Failed to list env vars");
640
- output.error(getErrorMessage(err));
641
- process.exit(1);
599
+ output.failWith(spin, "Failed to list env vars", err);
642
600
  }
643
601
  }
644
602
  export async function webhooksEnvUnsetCommand(key) {
@@ -649,9 +607,7 @@ export async function webhooksEnvUnsetCommand(key) {
649
607
  spin.succeed(`Removed ${key}`);
650
608
  }
651
609
  catch (err) {
652
- spin.fail(`Failed to remove ${key}`);
653
- output.error(getErrorMessage(err));
654
- process.exit(1);
610
+ output.failWith(spin, `Failed to remove ${key}`, err);
655
611
  }
656
612
  }
657
613
  //# sourceMappingURL=webhooks.js.map
@@ -8,8 +8,7 @@ export async function whoamiCommand() {
8
8
  const apiUrl = getApiUrl();
9
9
  const configPath = getConfigPath();
10
10
  if (!apiKey) {
11
- output.error("Not authenticated. Run `bankr login` to set up your Bankr API key.");
12
- process.exit(1);
11
+ output.fatal("Not authenticated. Run `bankr login` to set up your Bankr API key.");
13
12
  }
14
13
  const source = process.env.BANKR_API_KEY ? "env (BANKR_API_KEY)" : configPath;
15
14
  output.label("Bankr API Key", output.maskApiKey(apiKey));
@@ -19,9 +19,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import chalk from "chalk";
21
21
  import { input, select, confirm } from "@inquirer/prompts";
22
+ import { authHeaders, handleResponse } from "../lib/api.js";
22
23
  import * as output from "../lib/output.js";
23
- import { getErrorMessage } from "../lib/errors.js";
24
- import { getApiUrl, requireApiKey, CLI_USER_AGENT } from "../lib/config.js";
24
+ import { getApiUrl } from "../lib/config.js";
25
25
  // ── Config file helpers ─────────────────────────────────────────────────
26
26
  const CONFIG_FILENAME = "bankr.x402.json";
27
27
  function findProjectRoot() {
@@ -44,8 +44,7 @@ function loadConfigFile(projectRoot) {
44
44
  return JSON.parse(readFileSync(p, "utf-8"));
45
45
  }
46
46
  catch {
47
- output.error(`Failed to parse ${p}. Check that it is valid JSON.`);
48
- process.exit(1);
47
+ output.fatal(`Failed to parse ${p}. Check that it is valid JSON.`);
49
48
  }
50
49
  }
51
50
  }
@@ -55,24 +54,6 @@ function saveConfigFile(projectRoot, config) {
55
54
  const configPath = join(projectRoot, CONFIG_FILENAME);
56
55
  writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
57
56
  }
58
- // ── API helpers ─────────────────────────────────────────────────────────
59
- function authHeaders() {
60
- return {
61
- "X-API-Key": requireApiKey(),
62
- "Content-Type": "application/json",
63
- "User-Agent": CLI_USER_AGENT,
64
- };
65
- }
66
- async function handleResponse(res) {
67
- if (!res.ok) {
68
- const body = (await res
69
- .json()
70
- .catch(() => ({ message: res.statusText })));
71
- const msg = body.message || body.error || res.statusText;
72
- throw new Error(`API error (${res.status}): ${msg}`);
73
- }
74
- return res.json();
75
- }
76
57
  // ── Default handler template ────────────────────────────────────────────
77
58
  const HANDLER_TEMPLATE = `/**
78
59
  * x402 service handler.
@@ -144,19 +125,16 @@ function validateServiceName(name) {
144
125
  export async function x402AddCommand(name) {
145
126
  const nameError = validateServiceName(name);
146
127
  if (nameError) {
147
- output.error(nameError);
148
- process.exit(1);
128
+ output.fatal(nameError);
149
129
  }
150
130
  const root = findProjectRoot();
151
131
  const x402Dir = join(root, "x402");
152
132
  if (!existsSync(x402Dir)) {
153
- output.error("No x402/ directory found. Run 'bankr x402 init' first.");
154
- process.exit(1);
133
+ output.fatal("No x402/ directory found. Run 'bankr x402 init' first.");
155
134
  }
156
135
  const serviceDir = join(x402Dir, name);
157
136
  if (existsSync(serviceDir)) {
158
- output.error(`Service "${name}" already exists at x402/${name}/`);
159
- process.exit(1);
137
+ output.fatal(`Service "${name}" already exists at x402/${name}/`);
160
138
  }
161
139
  // Ask for method
162
140
  const method = await select({
@@ -327,8 +305,7 @@ export async function x402DeployCommand(name) {
327
305
  const root = findProjectRoot();
328
306
  const x402Dir = join(root, "x402");
329
307
  if (!existsSync(x402Dir)) {
330
- output.error("No x402/ directory found. Run 'bankr x402 init' first.");
331
- process.exit(1);
308
+ output.fatal("No x402/ directory found. Run 'bankr x402 init' first.");
332
309
  }
333
310
  // Load or bootstrap config
334
311
  let config = loadConfigFile(root);
@@ -345,21 +322,18 @@ export async function x402DeployCommand(name) {
345
322
  }
346
323
  }
347
324
  if (services.length === 0) {
348
- output.error("No services found. Run 'bankr x402 add <name>' first.");
349
- process.exit(1);
325
+ output.fatal("No services found. Run 'bankr x402 add <name>' first.");
350
326
  }
351
327
  // Filter to single service if name provided
352
328
  const toDeploy = name ? services.filter((s) => s === name) : services;
353
329
  if (name && toDeploy.length === 0) {
354
- output.error(`Service "${name}" not found in x402/`);
355
- process.exit(1);
330
+ output.fatal(`Service "${name}" not found in x402/`);
356
331
  }
357
332
  // Validate service names and ensure config entries
358
333
  for (const svc of toDeploy) {
359
334
  const nameError = validateServiceName(svc);
360
335
  if (nameError) {
361
- output.error(nameError);
362
- process.exit(1);
336
+ output.fatal(nameError);
363
337
  }
364
338
  if (!config.services[svc]) {
365
339
  config.services[svc] = {
@@ -424,9 +398,7 @@ export async function x402DeployCommand(name) {
424
398
  }
425
399
  }
426
400
  catch (err) {
427
- spin.fail("Deploy failed");
428
- output.error(getErrorMessage(err));
429
- process.exit(1);
401
+ output.failWith(spin, "Deploy failed", err);
430
402
  }
431
403
  }
432
404
  /**
@@ -454,9 +426,7 @@ export async function x402ListCommand() {
454
426
  }
455
427
  }
456
428
  catch (err) {
457
- spin.fail("Failed to list endpoints");
458
- output.error(getErrorMessage(err));
459
- process.exit(1);
429
+ output.failWith(spin, "Failed to list endpoints", err);
460
430
  }
461
431
  }
462
432
  /**
@@ -475,9 +445,7 @@ export async function x402PauseResumeCommand(name, action) {
475
445
  spin.succeed(`${name} ${action === "pause" ? "paused" : "resumed"}`);
476
446
  }
477
447
  catch (err) {
478
- spin.fail(`Failed to ${action} ${name}`);
479
- output.error(getErrorMessage(err));
480
- process.exit(1);
448
+ output.failWith(spin, `Failed to ${action} ${name}`, err);
481
449
  }
482
450
  }
483
451
  /**
@@ -501,9 +469,7 @@ export async function x402DeleteCommand(name) {
501
469
  spin.succeed(`${name} deleted`);
502
470
  }
503
471
  catch (err) {
504
- spin.fail(`Failed to delete ${name}`);
505
- output.error(getErrorMessage(err));
506
- process.exit(1);
472
+ output.failWith(spin, `Failed to delete ${name}`, err);
507
473
  }
508
474
  }
509
475
  /**
@@ -535,9 +501,7 @@ export async function x402RevenueCommand(name) {
535
501
  console.log();
536
502
  }
537
503
  catch (err) {
538
- spin.fail("Failed to fetch revenue");
539
- output.error(getErrorMessage(err));
540
- process.exit(1);
504
+ output.failWith(spin, "Failed to fetch revenue", err);
541
505
  }
542
506
  }
543
507
  /**
@@ -546,14 +510,12 @@ export async function x402RevenueCommand(name) {
546
510
  export async function x402EnvSetCommand(keyValue) {
547
511
  const eqIdx = keyValue.indexOf("=");
548
512
  if (eqIdx === -1) {
549
- output.error("Usage: bankr x402 env set KEY=VALUE");
550
- process.exit(1);
513
+ output.fatal("Usage: bankr x402 env set KEY=VALUE");
551
514
  }
552
515
  const key = keyValue.slice(0, eqIdx);
553
516
  const value = keyValue.slice(eqIdx + 1);
554
517
  if (!key) {
555
- output.error("Key cannot be empty");
556
- process.exit(1);
518
+ output.fatal("Key cannot be empty");
557
519
  }
558
520
  const spin = output.spinner(`Setting ${key}...`);
559
521
  try {
@@ -566,9 +528,7 @@ export async function x402EnvSetCommand(keyValue) {
566
528
  spin.succeed(`Set ${key}`);
567
529
  }
568
530
  catch (err) {
569
- spin.fail(`Failed to set ${key}`);
570
- output.error(getErrorMessage(err));
571
- process.exit(1);
531
+ output.failWith(spin, `Failed to set ${key}`, err);
572
532
  }
573
533
  }
574
534
  /**
@@ -591,9 +551,7 @@ export async function x402EnvListCommand() {
591
551
  }
592
552
  }
593
553
  catch (err) {
594
- spin.fail("Failed to list env vars");
595
- output.error(getErrorMessage(err));
596
- process.exit(1);
554
+ output.failWith(spin, "Failed to list env vars", err);
597
555
  }
598
556
  }
599
557
  /**
@@ -610,9 +568,7 @@ export async function x402EnvUnsetCommand(key) {
610
568
  spin.succeed(`Removed ${key}`);
611
569
  }
612
570
  catch (err) {
613
- spin.fail(`Failed to remove ${key}`);
614
- output.error(getErrorMessage(err));
615
- process.exit(1);
571
+ output.failWith(spin, `Failed to remove ${key}`, err);
616
572
  }
617
573
  }
618
574
  // ── Schema display helpers ──────────────────────────────────────────────
@@ -768,34 +724,28 @@ function printServiceFormatted(svc, rank, total) {
768
724
  // ── Schema ────────────────────────────────────────────────────────────
769
725
  export async function x402SchemaCommand(url, opts = {}) {
770
726
  if (!url) {
771
- output.error("Usage: bankr x402 schema <url>\n Example: bankr x402 schema https://x402.bankr.bot/0xabc.../my-service");
772
- process.exit(1);
727
+ output.fatal("Usage: bankr x402 schema <url>\n Example: bankr x402 schema https://x402.bankr.bot/0xabc.../my-service");
773
728
  }
774
729
  let parsed;
775
730
  try {
776
731
  parsed = new URL(url);
777
732
  }
778
733
  catch {
779
- output.error("Invalid URL");
780
- process.exit(1);
781
- return; // unreachable but makes TS happy
734
+ output.fatal("Invalid URL");
782
735
  }
783
736
  if (!parsed.hostname.endsWith("x402.bankr.bot")) {
784
- output.error("Schema lookup is only available for Bankr-hosted endpoints (x402.bankr.bot).");
785
- process.exit(1);
737
+ output.fatal("Schema lookup is only available for Bankr-hosted endpoints (x402.bankr.bot).");
786
738
  }
787
739
  const parts = parsed.pathname.split("/").filter(Boolean);
788
740
  if (parts.length < 2) {
789
- output.error("Invalid Bankr x402 URL. Expected: https://x402.bankr.bot/<address>/<service>");
790
- process.exit(1);
741
+ output.fatal("Invalid Bankr x402 URL. Expected: https://x402.bankr.bot/<address>/<service>");
791
742
  }
792
743
  const spin = output.spinner("Fetching endpoint schema...");
793
744
  try {
794
745
  const res = await fetch(`${getApiUrl()}/x402/endpoints/schema/${parts[0]}/${parts[1]}`);
795
746
  if (res.status === 404) {
796
747
  spin.fail("Endpoint not found");
797
- output.error(`No active endpoint found for ${parts[0]}/${parts[1]}`);
798
- process.exit(1);
748
+ output.fatal(`No active endpoint found for ${parts[0]}/${parts[1]}`);
799
749
  }
800
750
  const result = await handleResponse(res);
801
751
  spin.stop();
@@ -877,9 +827,7 @@ export async function x402SchemaCommand(url, opts = {}) {
877
827
  console.log();
878
828
  }
879
829
  catch (err) {
880
- spin.fail("Schema lookup failed");
881
- output.error(getErrorMessage(err));
882
- process.exit(1);
830
+ output.failWith(spin, "Schema lookup failed", err);
883
831
  }
884
832
  }
885
833
  async function fetchSchemaForInteractive(url) {
@@ -1029,15 +977,13 @@ export function outboundMaxPayment(callerCapUsd, advertisedUsd) {
1029
977
  }
1030
978
  export async function x402CallCommand(url, opts = {}) {
1031
979
  if (!url) {
1032
- output.error("Usage: bankr x402 call <url> [--method POST] [--body '{}']");
1033
- process.exit(1);
980
+ output.fatal("Usage: bankr x402 call <url> [--method POST] [--body '{}']");
1034
981
  }
1035
982
  let method = opts.method?.toUpperCase() ?? "GET";
1036
983
  const maxPaymentUsd = opts.maxPayment ? parseFloat(opts.maxPayment) : 1;
1037
984
  let requestBody = opts.body;
1038
985
  if (isNaN(maxPaymentUsd) || maxPaymentUsd <= 0 || maxPaymentUsd > 10) {
1039
- output.error("--max-payment must be between 0 and 10 (USD)");
1040
- process.exit(1);
986
+ output.fatal("--max-payment must be between 0 and 10 (USD)");
1041
987
  }
1042
988
  // Endpoint-advertised price. Shown at confirm time; the outbound cap is
1043
989
  // clamped to min(advertised, caller's maxPaymentUsd) — see outboundMaxPayment.
@@ -1057,8 +1003,7 @@ export async function x402CallCommand(url, opts = {}) {
1057
1003
  resolvedPrice = parseAdvertisedPrice(schema.price, schema.currency);
1058
1004
  const capError = checkAdvertisedPrice(resolvedPrice, maxPaymentUsd);
1059
1005
  if (capError) {
1060
- output.error(capError);
1061
- process.exit(1);
1006
+ output.fatal(capError);
1062
1007
  }
1063
1008
  if (schema.methods.length > 0) {
1064
1009
  method = schema.methods.includes("POST") ? "POST" : schema.methods[0];
@@ -1100,8 +1045,7 @@ export async function x402CallCommand(url, opts = {}) {
1100
1045
  if (typedProp.type === "number" || typedProp.type === "integer") {
1101
1046
  const num = Number(answer);
1102
1047
  if (isNaN(num)) {
1103
- output.error(`Invalid number for ${key}: "${answer}"`);
1104
- process.exit(1);
1048
+ output.fatal(`Invalid number for ${key}: "${answer}"`);
1105
1049
  }
1106
1050
  values[key] = num;
1107
1051
  }
@@ -1155,8 +1099,7 @@ export async function x402CallCommand(url, opts = {}) {
1155
1099
  requestBody = bodyInput;
1156
1100
  }
1157
1101
  catch {
1158
- output.error("Invalid JSON body");
1159
- process.exit(1);
1102
+ output.fatal("Invalid JSON body");
1160
1103
  }
1161
1104
  }
1162
1105
  }
@@ -1192,8 +1135,7 @@ export async function x402CallCommand(url, opts = {}) {
1192
1135
  resolvedPrice = parseAdvertisedPrice(schema.price, schema.currency);
1193
1136
  const capError = checkAdvertisedPrice(resolvedPrice, maxPaymentUsd);
1194
1137
  if (capError) {
1195
- output.error(capError);
1196
- process.exit(1);
1138
+ output.fatal(capError);
1197
1139
  }
1198
1140
  }
1199
1141
  }
@@ -1239,8 +1181,7 @@ export async function x402CallCommand(url, opts = {}) {
1239
1181
  return;
1240
1182
  }
1241
1183
  if (!result.success) {
1242
- output.error(result.error ?? "Call failed");
1243
- process.exit(1);
1184
+ output.fatal(result.error ?? "Call failed");
1244
1185
  }
1245
1186
  // Show endpoint schema info (Bankr-hosted endpoints only)
1246
1187
  if (result.endpointSchema) {
@@ -1273,17 +1214,14 @@ export async function x402CallCommand(url, opts = {}) {
1273
1214
  console.log();
1274
1215
  }
1275
1216
  catch (err) {
1276
- spin.fail("Call failed");
1277
- output.error(getErrorMessage(err));
1278
- process.exit(1);
1217
+ output.failWith(spin, "Call failed", err);
1279
1218
  }
1280
1219
  }
1281
1220
  // ── Search ────────────────────────────────────────────────────────────
1282
1221
  export async function x402SearchCommand(queryParts, opts = {}) {
1283
1222
  const query = queryParts.join(" ").trim();
1284
1223
  if (!query) {
1285
- output.error("Usage: bankr x402 search <query>");
1286
- process.exit(1);
1224
+ output.fatal("Usage: bankr x402 search <query>");
1287
1225
  }
1288
1226
  const spin = output.spinner("Searching services...");
1289
1227
  try {
@@ -1320,9 +1258,7 @@ export async function x402SearchCommand(queryParts, opts = {}) {
1320
1258
  }
1321
1259
  }
1322
1260
  catch (err) {
1323
- spin.fail("Search failed");
1324
- output.error(getErrorMessage(err));
1325
- process.exit(1);
1261
+ output.failWith(spin, "Search failed", err);
1326
1262
  }
1327
1263
  }
1328
1264
  //# sourceMappingURL=x402.js.map
package/dist/lib/api.d.ts CHANGED
@@ -43,6 +43,8 @@ export interface JobStatusResponse {
43
43
  richData?: unknown[];
44
44
  cancellable?: boolean;
45
45
  }
46
+ export declare function authHeaders(): Record<string, string>;
47
+ export declare function handleResponse<T>(res: Response): Promise<T>;
46
48
  export interface ApiErrorBody {
47
49
  error: string;
48
50
  message: string;
@@ -196,7 +198,7 @@ export interface SubmitResponse {
196
198
  export declare function submit(request: SubmitRequest): Promise<SubmitResponse>;
197
199
  export type FeeRecipientType = "wallet" | "x" | "farcaster" | "ens";
198
200
  /** Chains the deploy endpoint accepts (mirrors the API's zod enum). */
199
- export type LaunchChain = "base" | "robinhood";
201
+ export type LaunchChain = "base" | "robinhood" | "arbitrum";
200
202
  export interface DeployTokenRequest {
201
203
  tokenName: string;
202
204
  tokenSymbol?: string;
@@ -234,7 +236,7 @@ export interface CreatorFeesTokenResult {
234
236
  tokenAddress: string;
235
237
  name: string;
236
238
  symbol: string;
237
- /** Launch chain ("base" | "robinhood"); absent on older API versions → base. */
239
+ /** Launch chain ("base" | "robinhood" | "arbitrum"); absent on older API versions → base. */
238
240
  chain?: string;
239
241
  poolId: string;
240
242
  initializer?: string;
package/dist/lib/api.js CHANGED
@@ -7,14 +7,14 @@ const TERMINAL_STATUSES = [
7
7
  function isTerminalStatus(status) {
8
8
  return TERMINAL_STATUSES.includes(status);
9
9
  }
10
- function authHeaders() {
10
+ export function authHeaders() {
11
11
  return {
12
12
  "X-API-Key": requireApiKey(),
13
13
  "Content-Type": "application/json",
14
14
  "User-Agent": CLI_USER_AGENT,
15
15
  };
16
16
  }
17
- async function handleResponse(res) {
17
+ export async function handleResponse(res) {
18
18
  if (!res.ok) {
19
19
  const body = (await res
20
20
  .json()
@@ -5,6 +5,7 @@ const EXPLORERS = {
5
5
  name: "Blockscout",
6
6
  baseUrl: "https://robinhoodchain.blockscout.com",
7
7
  },
8
+ 42161: { name: "Arbiscan", baseUrl: "https://arbiscan.io" },
8
9
  };
9
10
  /** Unknown chain ids fall back to Basescan (pre-multichain behavior). */
10
11
  export function txExplorer(chainId, txHash) {
@@ -22,6 +22,10 @@ export declare function brand(msg: string): void;
22
22
  export declare function brandBold(msg: string): void;
23
23
  export declare function success(msg: string): void;
24
24
  export declare function error(msg: string): void;
25
+ /** Collapses the error-then-exit epilogue every command repeats. */
26
+ export declare function fatal(message: string): never;
27
+ /** Same epilogue, for the spinner-driven variant that reports a caught error. */
28
+ export declare function failWith(spinner: Ora, label: string, err: unknown): never;
25
29
  export declare function warn(msg: string): void;
26
30
  export declare function info(msg: string): void;
27
31
  export declare function blank(): void;
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
+ import { getErrorMessage } from "./errors.js";
3
4
  const BRAND_HEX = "#FF613D";
4
5
  const brandColor = chalk.hex(BRAND_HEX);
5
6
  const brandColorBold = brandColor.bold;
@@ -39,6 +40,17 @@ export function success(msg) {
39
40
  export function error(msg) {
40
41
  console.error(chalk.red(`✗ ${msg}`));
41
42
  }
43
+ /** Collapses the error-then-exit epilogue every command repeats. */
44
+ export function fatal(message) {
45
+ error(message);
46
+ process.exit(1);
47
+ }
48
+ /** Same epilogue, for the spinner-driven variant that reports a caught error. */
49
+ export function failWith(spinner, label, err) {
50
+ spinner.fail(label);
51
+ error(getErrorMessage(err));
52
+ process.exit(1);
53
+ }
42
54
  export function warn(msg) {
43
55
  console.log(chalk.yellow(`⚠ ${msg}`));
44
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.3.23",
3
+ "version": "0.3.32",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {