@bankr/cli 0.3.12 → 0.3.23

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.
Files changed (41) hide show
  1. package/README.md +28 -10
  2. package/dist/cli.js +31 -8
  3. package/dist/commands/balances.js +2 -1
  4. package/dist/commands/cancel.js +2 -1
  5. package/dist/commands/club.js +4 -3
  6. package/dist/commands/fees.js +83 -45
  7. package/dist/commands/files.js +14 -13
  8. package/dist/commands/launch.d.ts +5 -0
  9. package/dist/commands/launch.js +76 -7
  10. package/dist/commands/llm.d.ts +2 -0
  11. package/dist/commands/llm.js +264 -44
  12. package/dist/commands/login.d.ts +14 -0
  13. package/dist/commands/login.js +41 -30
  14. package/dist/commands/portfolio.js +3 -2
  15. package/dist/commands/profile.js +6 -5
  16. package/dist/commands/prompt.js +19 -6
  17. package/dist/commands/sign.js +2 -1
  18. package/dist/commands/sounds.js +4 -3
  19. package/dist/commands/status.js +3 -2
  20. package/dist/commands/submit.js +3 -2
  21. package/dist/commands/swap.js +9 -7
  22. package/dist/commands/tokens.js +3 -2
  23. package/dist/commands/transfer.js +8 -6
  24. package/dist/commands/update.js +2 -1
  25. package/dist/commands/webhooks.js +15 -10
  26. package/dist/commands/whoami.js +3 -2
  27. package/dist/commands/x402.d.ts +3 -0
  28. package/dist/commands/x402.js +75 -30
  29. package/dist/lib/api.d.ts +18 -1
  30. package/dist/lib/api.js +32 -41
  31. package/dist/lib/errors.d.ts +7 -0
  32. package/dist/lib/errors.js +9 -0
  33. package/dist/lib/explorer.d.ts +7 -0
  34. package/dist/lib/explorer.js +14 -0
  35. package/dist/lib/output.d.ts +1 -0
  36. package/dist/lib/output.js +6 -0
  37. package/dist/lib/passthrough.d.ts +21 -0
  38. package/dist/lib/passthrough.js +26 -0
  39. package/dist/lib/tokens.d.ts +0 -5
  40. package/dist/lib/tokens.js +0 -5
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -108,26 +108,44 @@ bankr agent cancel job_ABC123DEF456
108
108
  Launch Claude Code / OpenCode routed through the Bankr gateway:
109
109
 
110
110
  ```bash
111
- # Claude Code (auto-translates dotted model IDs to the Anthropic wire format)
112
- bankr llm claude --model claude-opus-4-8
111
+ # Claude Code — `bankr claude` is a top-level alias for `bankr llm claude`
112
+ # (auto-translates dotted model IDs to the Anthropic wire format)
113
+ bankr claude --model claude-opus-5
113
114
 
114
- # Or use dotted form — bankr llm claude rewrites it to claude-opus-4-8
115
- bankr llm claude --model claude-opus-4.8
115
+ # Or use dotted form — bankr claude rewrites it to claude-opus-4-8
116
+ bankr claude --model claude-opus-4.8
117
+
118
+ # The [1m] suffix is optional via the gateway, but MUST be quoted if used
119
+ # (zsh reads [1m] as a glob)
120
+ bankr claude --model "claude-opus-5[1m]"
116
121
 
117
122
  # OpenCode (use the bankr/ prefix with the gateway-canonical dotted form)
118
123
  bankr llm opencode -m bankr/claude-opus-4.8
119
124
  ```
120
125
 
121
- **Claude Code model format**: Claude Code's `--model` flag expects
122
- Anthropic-style dashed IDs (`claude-opus-4-8`, `claude-sonnet-4-6`,
123
- `claude-haiku-4-5`). If you pass the gateway-canonical dotted form
124
- (`claude-opus-4.8`), Claude Code silently falls back to its default —
125
- the flag appears honored but the real request is a different model.
126
+ Because `claude` is a command, it shadows the default prompt fallthrough:
127
+ `bankr claude ...` launches Claude Code rather than prompting the agent. Use
128
+ `bankr agent "..."` to send the agent a prompt starting with that word.
129
+
130
+ **Claude Code model format**: Claude Code's `--model` flag expects Anthropic
131
+ wire IDs, which use dashes rather than dots in the version (`claude-opus-5`,
132
+ `claude-sonnet-5`, `claude-opus-4-8`, `claude-haiku-4-5`). If you pass the
133
+ gateway-canonical dotted form (`claude-opus-4.8`), Claude Code silently falls
134
+ back to its default — the flag appears honored but the real request is a
135
+ different model.
126
136
 
127
- `bankr llm claude` translates dotted → dashed for you. If you use older
137
+ `bankr claude` translates dotted → dashed for you. If you use older
128
138
  versions of the CLI (or invoke `claude` directly via `ANTHROPIC_BASE_URL`),
129
139
  pass the dashed form explicitly.
130
140
 
141
+ **1M context tier**: the gateway strips Claude Code's `[1m]` suffix and enables
142
+ the 1M beta from the model's own context window, so it's **optional here** —
143
+ `claude-opus-5` and `claude-opus-5[1m]` send an identical upstream request. If
144
+ you do use it, **quote it**: in zsh (the macOS default) `[1m]` is a glob
145
+ character class, so an unquoted id aborts with `zsh: no matches found:
146
+ claude-opus-5[1m]` before the CLI ever runs. Bash passes it through literally,
147
+ so the same command can work on one machine and fail on another.
148
+
131
149
  ### Configuration
132
150
 
133
151
  ```bash
package/dist/cli.js CHANGED
@@ -10,6 +10,8 @@ import { skillsCommand } from "./commands/capabilities.js";
10
10
  import { cancelCommand } from "./commands/cancel.js";
11
11
  import { configGetCommand, configSetCommand } from "./commands/config.js";
12
12
  import { setConfigPath } from "./lib/config.js";
13
+ import { getErrorMessage } from "./lib/errors.js";
14
+ import { passthroughArgs } from "./lib/passthrough.js";
13
15
  import { claudeCommand, creditsCommand, creditsAddCommand, creditsAutoCommand, modelsCommand, opencodeCommand, setupClaudeCodeCommand, setupCursorCommand, setupOpenclawCommand, setupOpenCodeCommand, } from "./commands/llm.js";
14
16
  import { feesCommand, feesClaimCommand, feesClaimWalletCommand, } from "./commands/fees.js";
15
17
  import { launchCommand } from "./commands/launch.js";
@@ -340,17 +342,20 @@ agentCmd
340
342
  program.commandsGroup("Tokens");
341
343
  program
342
344
  .command("launch")
343
- .description("Launch a token on Base (interactive wizard)")
345
+ .description("Launch a token on Base or Robinhood Chain (interactive wizard)")
344
346
  .optionsGroup("Token")
345
347
  .option("--name <name>", "Token name")
346
348
  .option("--symbol <symbol>", "Token symbol (default: auto-generated from name)")
347
349
  .option("--image <url>", "Token image URL")
350
+ .option("--chain <chain>", "Chain to launch on: base, robinhood (default: base)")
351
+ .option("--no-vesting", "Turn off the default 15% creator vesting (sell 100% of supply into the pool)")
348
352
  .optionsGroup("Links")
349
353
  .option("--tweet <url>", "Associated tweet URL")
350
354
  .option("--website <url>", "Project website URL")
351
355
  .optionsGroup("Fees")
352
356
  .option("--fee <recipient>", "Fee recipient (username, ENS, or address)")
353
357
  .option("--fee-type <type>", "Fee recipient type: x, farcaster, ens, wallet (default: x)")
358
+ .option("--quote-only-fees", "Collect all creator fees in the pool's quote token (e.g. WETH) instead of a mix of your token and the quote token")
354
359
  .optionsGroup("")
355
360
  .option("-y, --yes", "Skip confirmation prompt")
356
361
  .option("--simulate", "Simulate the deploy without broadcasting a transaction")
@@ -359,10 +364,13 @@ program
359
364
  name: opts.name,
360
365
  symbol: opts.symbol,
361
366
  image: opts.image,
367
+ chain: opts.chain,
368
+ vesting: opts.vesting,
362
369
  tweet: opts.tweet,
363
370
  website: opts.website,
364
371
  fee: opts.fee,
365
372
  feeType: opts.feeType,
373
+ quoteOnlyFees: opts.quoteOnlyFees,
366
374
  yes: opts.yes,
367
375
  simulate: opts.simulate,
368
376
  });
@@ -469,6 +477,7 @@ llmCmd
469
477
  .command("models")
470
478
  .description("List available models")
471
479
  .option("--private", "Show only models that support private (:private) inference")
480
+ .option("--zdr", "Show only models that support zero data retention (:zdr)")
472
481
  .action(modelsCommand);
473
482
  const llmCreditsCmd = llmCmd
474
483
  .command("credits")
@@ -496,6 +505,7 @@ llmSetupCmd
496
505
  .command("openclaw")
497
506
  .description("OpenClaw config")
498
507
  .option("--install", "Write config to ~/.openclaw/openclaw.json")
508
+ .option("--images", "Also route image generation (gpt-image-2) through the gateway — takes over OpenClaw's 'openai' provider slot")
499
509
  .action(setupOpenclawCommand);
500
510
  llmSetupCmd
501
511
  .command("opencode")
@@ -510,25 +520,38 @@ llmSetupCmd
510
520
  .command("claude")
511
521
  .description("Claude Code config")
512
522
  .action(setupClaudeCodeCommand);
523
+ // `helpOption(false)` forwards `-h`/`--help` to the spawned tool instead of
524
+ // letting commander print ours — on a pure passthrough launcher the user
525
+ // almost always wants the tool's help. Use `bankr llm --help` for ours.
513
526
  llmCmd
514
527
  .command("claude")
515
528
  .description("Run Claude Code via gateway")
516
529
  .allowUnknownOption()
517
530
  .allowExcessArguments()
531
+ .helpOption(false)
518
532
  .action(async () => {
519
- const idx = process.argv.indexOf("claude");
520
- const args = idx >= 0 ? process.argv.slice(idx + 1) : [];
521
- await claudeCommand(args);
533
+ await claudeCommand(passthroughArgs("claude"));
522
534
  });
523
535
  llmCmd
524
536
  .command("opencode")
525
537
  .description("Run OpenCode via gateway")
526
538
  .allowUnknownOption()
527
539
  .allowExcessArguments()
540
+ .helpOption(false)
541
+ .action(async () => {
542
+ await opencodeCommand(passthroughArgs("opencode"));
543
+ });
544
+ // Top-level shorthand for the most-used launcher. This shadows the default
545
+ // prompt fallthrough, so `bankr claude ...` launches Claude Code rather than
546
+ // prompting the agent about Claude — ask the agent via `bankr agent "..."`.
547
+ program
548
+ .command("claude")
549
+ .description("Run Claude Code via gateway (alias for `bankr llm claude`)")
550
+ .allowUnknownOption()
551
+ .allowExcessArguments()
552
+ .helpOption(false)
528
553
  .action(async () => {
529
- const idx = process.argv.indexOf("opencode");
530
- const args = idx >= 0 ? process.argv.slice(idx + 1) : [];
531
- await opencodeCommand(args);
554
+ await claudeCommand(passthroughArgs("claude"));
532
555
  });
533
556
  // Token search and info
534
557
  program.commandsGroup("Tokens");
@@ -1046,7 +1069,7 @@ async function main() {
1046
1069
  console.log();
1047
1070
  process.exit(0);
1048
1071
  }
1049
- output.error(err.message);
1072
+ output.error(getErrorMessage(err));
1050
1073
  process.exit(1);
1051
1074
  }
1052
1075
  await checkForUpdate(pkg.version);
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import { getBalances, } 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,7 +54,7 @@ export async function balancesCommand(opts) {
53
54
  spin.succeed("Balances loaded");
54
55
  }
55
56
  catch (err) {
56
- spin.fail(`Failed to fetch balances: ${err.message}`);
57
+ spin.fail(`Failed to fetch balances: ${getErrorMessage(err)}`);
57
58
  process.exit(1);
58
59
  }
59
60
  // JSON output mode
@@ -1,6 +1,7 @@
1
1
  import { cancelJob } 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";
4
5
  export async function cancelCommand(jobId) {
5
6
  requireApiKey();
6
7
  const spin = output.spinner("Cancelling job...");
@@ -18,7 +19,7 @@ export async function cancelCommand(jobId) {
18
19
  }
19
20
  catch (err) {
20
21
  spin.fail("Failed to cancel job");
21
- output.error(err.message);
22
+ output.error(getErrorMessage(err));
22
23
  process.exit(1);
23
24
  }
24
25
  }
@@ -1,6 +1,7 @@
1
1
  import { confirm } from "@inquirer/prompts";
2
2
  import { CLI_USER_AGENT, getApiUrl, requireApiKey } from "../lib/config.js";
3
3
  import * as output from "../lib/output.js";
4
+ import { getErrorMessage } from "../lib/errors.js";
4
5
  function authHeaders() {
5
6
  return {
6
7
  "X-API-Key": requireApiKey(),
@@ -78,7 +79,7 @@ export async function clubStatusCommand() {
78
79
  }
79
80
  catch (err) {
80
81
  spinner.stop();
81
- output.error(err instanceof Error ? err.message : String(err));
82
+ output.error(getErrorMessage(err));
82
83
  process.exit(1);
83
84
  }
84
85
  }
@@ -142,7 +143,7 @@ export async function clubSignupCommand(opts) {
142
143
  output.dim("Run `bankr club` to see your status.");
143
144
  }
144
145
  catch (err) {
145
- output.error(err instanceof Error ? err.message : String(err));
146
+ output.error(getErrorMessage(err));
146
147
  process.exit(1);
147
148
  }
148
149
  }
@@ -186,7 +187,7 @@ export async function clubCancelCommand(opts) {
186
187
  output.dim("You'll keep Bankr Club access until the end of the current billing period.");
187
188
  }
188
189
  catch (err) {
189
- output.error(err instanceof Error ? err.message : String(err));
190
+ output.error(getErrorMessage(err));
190
191
  process.exit(1);
191
192
  }
192
193
  }
@@ -4,7 +4,9 @@ import { isAddress } from "viem";
4
4
  import { getCreatorFees, getBeneficiaryFeesStreaming, getTokenCreatorFees, getUserInfo, claimTokenLaunchFees, } from "../lib/api.js";
5
5
  import { emitCESP } from "../lib/cesp/engine.js";
6
6
  import { requireApiKey } from "../lib/config.js";
7
+ import { txExplorer } from "../lib/explorer.js";
7
8
  import * as output from "../lib/output.js";
9
+ import { getErrorMessage } from "../lib/errors.js";
8
10
  // ---------------------------------------------------------------------------
9
11
  // Constants
10
12
  // ---------------------------------------------------------------------------
@@ -212,7 +214,11 @@ function renderDashboard(data) {
212
214
  console.log(` ${l}`);
213
215
  }
214
216
  console.log(`${DIM(` Wallet: ${data.address}`)}`);
215
- console.log(`${DIM(` Chain: Base \u00b7 Tokens: ${data.tokens.length} \u00b7 Period: ${data.days} days`)}`);
217
+ // Tokens can span launch chains ("chain" absent on pre-multichain rows).
218
+ const chainNames = [
219
+ ...new Set(data.tokens.map((t) => (t.chain ?? "base") === "robinhood" ? "Robinhood" : "Base")),
220
+ ].join(" + ") || "Base";
221
+ console.log(`${DIM(` Chain: ${chainNames} \u00b7 Tokens: ${data.tokens.length} \u00b7 Period: ${data.days} days`)}`);
216
222
  if (data.tokens.length === 0) {
217
223
  console.log(`\n ${DIM("No tokens found where this wallet is a creator beneficiary.")}`);
218
224
  console.log("");
@@ -278,28 +284,33 @@ function renderDashboard(data) {
278
284
  console.log(` ${BRAND_BOLD("\u2550".repeat(62))}`);
279
285
  console.log("");
280
286
  for (const token of data.tokens) {
281
- const wethIsToken0 = token.token0Label === "WETH";
282
- const claimableWeth = wethIsToken0
283
- ? token.claimable.token0
284
- : token.claimable.token1;
285
- const claimableToken = wethIsToken0
287
+ // Numeraire leg = the side whose label differs from the launched token's
288
+ // symbol \u2014 stock-paired launches label it with the stock ticker, not WETH.
289
+ const tokenIsToken0 = token.token0Label === token.symbol;
290
+ const numeraireLabel = tokenIsToken0
291
+ ? token.token1Label
292
+ : token.token0Label;
293
+ const claimableNumeraire = tokenIsToken0
286
294
  ? token.claimable.token1
287
295
  : token.claimable.token0;
288
- const claimedWeth = wethIsToken0
289
- ? token.claimed.token0
290
- : token.claimed.token1;
291
- const claimedToken = wethIsToken0
296
+ const claimableToken = tokenIsToken0
297
+ ? token.claimable.token0
298
+ : token.claimable.token1;
299
+ const claimedNumeraire = tokenIsToken0
292
300
  ? token.claimed.token1
293
301
  : token.claimed.token0;
294
- const tokenSymbol = wethIsToken0 ? token.token1Label : token.token0Label;
302
+ const claimedToken = tokenIsToken0
303
+ ? token.claimed.token0
304
+ : token.claimed.token1;
305
+ const tokenSymbol = tokenIsToken0 ? token.token0Label : token.token1Label;
295
306
  const sourceLabel = token.source === "clanker" ? DIM(" [Clanker]") : DIM(" [Doppler]");
296
307
  console.log(` ${BOLD(WHITE(token.name))} ${DIM("($" + token.symbol + ")")}${sourceLabel}`);
297
308
  const shareInfo = token.source === "clanker"
298
309
  ? `${DIM("Creator")} ${DIM("\u00b7 Token:")} ${DIM(token.tokenAddress)}`
299
310
  : `${DIM("Share:")} ${token.share} ${DIM("\u00b7 Token:")} ${DIM(token.tokenAddress)}`;
300
311
  console.log(` ${shareInfo}`);
301
- console.log(` ${DIM("Claimable:")} ${BRIGHT_GREEN(claimableWeth.padStart(12))} WETH ${DIM("|")} ${BRIGHT_GREEN(claimableToken.padStart(18))} ${tokenSymbol}`);
302
- console.log(` ${DIM("Claimed:")} ${WHITE(claimedWeth.padStart(12))} WETH ${DIM("|")} ${WHITE(claimedToken.padStart(18))} ${tokenSymbol} ${DIM(`(${token.claimed.count} claims)`)}`);
312
+ console.log(` ${DIM("Claimable:")} ${BRIGHT_GREEN(claimableNumeraire.padStart(12))} ${numeraireLabel} ${DIM("|")} ${BRIGHT_GREEN(claimableToken.padStart(18))} ${tokenSymbol}`);
313
+ console.log(` ${DIM("Claimed:")} ${WHITE(claimedNumeraire.padStart(12))} ${numeraireLabel} ${DIM("|")} ${WHITE(claimedToken.padStart(18))} ${tokenSymbol} ${DIM(`(${token.claimed.count} claims)`)}`);
303
314
  console.log("");
304
315
  }
305
316
  console.log(` ${BRAND_BOLD("\u2550".repeat(62))}`);
@@ -338,7 +349,7 @@ export async function feesCommand(address, opts) {
338
349
  catch (err) {
339
350
  spin.fail("Failed to fetch token fee data");
340
351
  emitCESP("task.error");
341
- output.error(err.message);
352
+ output.error(getErrorMessage(err));
342
353
  process.exit(1);
343
354
  }
344
355
  return;
@@ -360,7 +371,7 @@ export async function feesCommand(address, opts) {
360
371
  }
361
372
  catch (err) {
362
373
  spin.fail("Failed to fetch wallet info");
363
- output.error(err.message);
374
+ output.error(getErrorMessage(err));
364
375
  process.exit(1);
365
376
  }
366
377
  }
@@ -379,7 +390,7 @@ export async function feesCommand(address, opts) {
379
390
  catch (err) {
380
391
  spin.fail("Failed to fetch fee data");
381
392
  emitCESP("task.error");
382
- output.error(err.message);
393
+ output.error(getErrorMessage(err));
383
394
  process.exit(1);
384
395
  }
385
396
  }
@@ -430,7 +441,8 @@ export async function feesClaimCommand(tokenAddress, opts) {
430
441
  }
431
442
  console.log();
432
443
  if (result.transactionHash) {
433
- output.dim(`View on Basescan: https://basescan.org/tx/${result.transactionHash}`);
444
+ const explorer = txExplorer(result.chainId, result.transactionHash);
445
+ output.dim(`View on ${explorer.name}: ${explorer.url}`);
434
446
  }
435
447
  if (!ok) {
436
448
  process.exit(1);
@@ -439,7 +451,7 @@ export async function feesClaimCommand(tokenAddress, opts) {
439
451
  catch (err) {
440
452
  spin.fail("Claim submission failed");
441
453
  emitCESP("task.error");
442
- output.error(err.message);
454
+ output.error(getErrorMessage(err));
443
455
  process.exit(1);
444
456
  }
445
457
  }
@@ -544,42 +556,64 @@ export async function feesClaimWalletCommand(address, opts) {
544
556
  const result = await getBeneficiaryFeesStreaming(address, (progress) => {
545
557
  fetchSpin.text = `Scanning pools... ${progress.scanned}/${progress.total} (${progress.found ?? 0} with claimable fees)`;
546
558
  });
547
- fetchSpin.succeed(`Scanned ${result.totalLaunches} launches — ${result.poolsWithShares} pools with shares, ${result.tokens.length} with claimable fees`);
559
+ // A time-budget stop leaves `scanned` below `totalLaunches`; report the
560
+ // number actually scanned so this line can't contradict the warning below.
561
+ const scanned = result.scanned ?? result.totalLaunches;
562
+ fetchSpin.succeed(`Scanned ${scanned} launches — ${result.poolsWithShares} pools with shares, ${result.tokens.length} with claimable fees`);
563
+ if (result.truncated) {
564
+ output.warn(`Scan stopped early at ${scanned} launches — this wallet has more than the server scans in one request. Claimable fees beyond that are not listed.`);
565
+ }
548
566
  allTokens = result.tokens;
549
567
  }
550
568
  catch (err) {
551
569
  fetchSpin.fail("Failed to fetch fee data");
552
- output.error(err.message);
570
+ output.error(getErrorMessage(err));
553
571
  process.exit(1);
554
572
  }
555
573
  // Step 5: Filter to tokens with claimable fees (API already filters, but
556
574
  // double-check for any rounding edge cases)
557
575
  const parseAmount = (s) => parseFloat(s.replace(/^[<>]/, "")) || 0;
576
+ // Numeraire leg = the side whose label differs from the launched token's
577
+ // symbol — stock-paired launches label it with the stock ticker, not WETH.
578
+ const numeraireLegOf = (token) => {
579
+ const tokenIsToken0 = token.token0Label === token.symbol;
580
+ return {
581
+ label: tokenIsToken0 ? token.token1Label : token.token0Label,
582
+ claimable: tokenIsToken0
583
+ ? token.claimable.token1
584
+ : token.claimable.token0,
585
+ tokenClaimable: tokenIsToken0
586
+ ? token.claimable.token0
587
+ : token.claimable.token1,
588
+ };
589
+ };
558
590
  const claimableTokens = allTokens.filter((token) => {
559
- const wethIsToken0 = token.token0Label === "WETH";
560
- const claimableWeth = wethIsToken0
561
- ? token.claimable.token0
562
- : token.claimable.token1;
563
- const claimableToken = wethIsToken0
564
- ? token.claimable.token1
565
- : token.claimable.token0;
566
- return parseAmount(claimableWeth) > 0 || parseAmount(claimableToken) > 0;
591
+ const leg = numeraireLegOf(token);
592
+ return (parseAmount(leg.claimable) > 0 || parseAmount(leg.tokenClaimable) > 0);
567
593
  });
568
594
  if (claimableTokens.length === 0) {
569
595
  console.log();
570
596
  output.dim("No claimable fees found for this wallet.");
571
597
  return;
572
598
  }
573
- // Show total claimable WETH
599
+ // Show total claimable WETH — WETH legs only; stock-paired numeraires are a
600
+ // different unit and get their own per-ticker lines.
574
601
  const totalWeth = claimableTokens.reduce((sum, token) => {
575
- const wethIsToken0 = token.token0Label === "WETH";
576
- const wethStr = wethIsToken0
577
- ? token.claimable.token0
578
- : token.claimable.token1;
579
- return sum + parseAmount(wethStr);
602
+ const leg = numeraireLegOf(token);
603
+ return leg.label === "WETH" ? sum + parseAmount(leg.claimable) : sum;
580
604
  }, 0);
605
+ const nonWethTotals = new Map();
606
+ for (const token of claimableTokens) {
607
+ const leg = numeraireLegOf(token);
608
+ if (leg.label !== "WETH" && parseAmount(leg.claimable) > 0) {
609
+ nonWethTotals.set(leg.label, (nonWethTotals.get(leg.label) ?? 0) + parseAmount(leg.claimable));
610
+ }
611
+ }
581
612
  console.log();
582
613
  output.keyValue("Total claimable", `${totalWeth.toFixed(6)} WETH across ${claimableTokens.length} token${claimableTokens.length !== 1 ? "s" : ""}`);
614
+ for (const [label, amount] of nonWethTotals) {
615
+ output.keyValue("", `${amount.toFixed(6)} ${label} (stock-paired)`);
616
+ }
583
617
  // Step 6: Select tokens
584
618
  let selectedTokens;
585
619
  if (opts.all) {
@@ -587,12 +621,9 @@ export async function feesClaimWalletCommand(address, opts) {
587
621
  }
588
622
  else {
589
623
  const choices = claimableTokens.map((token) => {
590
- const wethIsToken0 = token.token0Label === "WETH";
591
- const claimableWeth = wethIsToken0
592
- ? token.claimable.token0
593
- : token.claimable.token1;
624
+ const leg = numeraireLegOf(token);
594
625
  return {
595
- name: `${token.name} ($${token.symbol}) - ${claimableWeth} WETH`,
626
+ name: `${token.name} ($${token.symbol}) - ${leg.claimable} ${leg.label}`,
596
627
  value: token.tokenAddress,
597
628
  checked: true,
598
629
  };
@@ -631,10 +662,17 @@ export async function feesClaimWalletCommand(address, opts) {
631
662
  ],
632
663
  },
633
664
  ];
665
+ // Robinhood-chain launches can't be claimed here: this flow signs and
666
+ // broadcasts on Base only, and a Base tx to a robinhood initializer is a
667
+ // gas-burning no-op. Skip them loudly.
668
+ const baseChainTokens = selectedTokens.filter((t) => (t.chain ?? "base") === "base");
669
+ if (baseChainTokens.length < selectedTokens.length) {
670
+ output.warn(`${selectedTokens.length - baseChainTokens.length} Robinhood Chain token(s) skipped — CLI claiming supports Base only for now. Claim at https://bankr.bot/launches instead.`);
671
+ }
634
672
  // Filter to tokens with initializer (required for local tx building)
635
- const buildableTokens = selectedTokens.filter((t) => t.initializer);
636
- if (buildableTokens.length < selectedTokens.length) {
637
- output.warn(`${selectedTokens.length - buildableTokens.length} token(s) missing initializer data — skipped`);
673
+ const buildableTokens = baseChainTokens.filter((t) => t.initializer);
674
+ if (buildableTokens.length < baseChainTokens.length) {
675
+ output.warn(`${baseChainTokens.length - buildableTokens.length} token(s) missing initializer data — skipped`);
638
676
  }
639
677
  if (buildableTokens.length === 0) {
640
678
  output.error("No transactions could be built");
@@ -663,7 +701,7 @@ export async function feesClaimWalletCommand(address, opts) {
663
701
  return (gas * GAS_BUFFER) / 100n;
664
702
  }
665
703
  catch (err) {
666
- output.warn(`${token.symbol}: gas estimate failed — ${err.message}`);
704
+ output.warn(`${token.symbol}: gas estimate failed — ${getErrorMessage(err)}`);
667
705
  return null;
668
706
  }
669
707
  }));
@@ -724,7 +762,7 @@ export async function feesClaimWalletCommand(address, opts) {
724
762
  pendingTxs.push({ token: tx.token, hash });
725
763
  }
726
764
  catch (err) {
727
- sendSpin.fail(`${tx.token.symbol}: Send failed — ${err.message}`);
765
+ sendSpin.fail(`${tx.token.symbol}: Send failed — ${getErrorMessage(err)}`);
728
766
  emitCESP("task.error");
729
767
  failCount++;
730
768
  if (readyTxs.indexOf(tx) < readyTxs.length - 1) {
@@ -754,7 +792,7 @@ export async function feesClaimWalletCommand(address, opts) {
754
792
  token,
755
793
  hash,
756
794
  status: "failed",
757
- error: err.message,
795
+ error: getErrorMessage(err),
758
796
  };
759
797
  }
760
798
  }));
@@ -2,6 +2,7 @@ import { readFileSync, statSync, writeFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
3
  import { CLI_USER_AGENT, getApiUrl, requireApiKey } from "../lib/config.js";
4
4
  import * as output from "../lib/output.js";
5
+ import { getErrorMessage } from "../lib/errors.js";
5
6
  async function readStdin() {
6
7
  if (process.stdin.isTTY) {
7
8
  throw new Error("No content provided. Pipe content via stdin or pass --from <file>.");
@@ -54,7 +55,7 @@ export async function filesListCommand(opts) {
54
55
  }
55
56
  catch (err) {
56
57
  spinner.stop();
57
- output.error(err instanceof Error ? err.message : String(err));
58
+ output.error(getErrorMessage(err));
58
59
  process.exit(1);
59
60
  }
60
61
  }
@@ -102,7 +103,7 @@ export async function filesUploadCommand(filePath, opts) {
102
103
  }
103
104
  catch (err) {
104
105
  spinner.stop();
105
- output.error(err instanceof Error ? err.message : String(err));
106
+ output.error(getErrorMessage(err));
106
107
  process.exit(1);
107
108
  }
108
109
  }
@@ -124,7 +125,7 @@ export async function filesDownloadCommand(fileId) {
124
125
  }
125
126
  catch (err) {
126
127
  spinner.stop();
127
- output.error(err instanceof Error ? err.message : String(err));
128
+ output.error(getErrorMessage(err));
128
129
  process.exit(1);
129
130
  }
130
131
  }
@@ -149,7 +150,7 @@ export async function filesMkdirCommand(name, opts) {
149
150
  }
150
151
  catch (err) {
151
152
  spinner.stop();
152
- output.error(err instanceof Error ? err.message : String(err));
153
+ output.error(getErrorMessage(err));
153
154
  process.exit(1);
154
155
  }
155
156
  }
@@ -170,7 +171,7 @@ export async function filesRmCommand(fileId) {
170
171
  }
171
172
  catch (err) {
172
173
  spinner.stop();
173
- output.error(err instanceof Error ? err.message : String(err));
174
+ output.error(getErrorMessage(err));
174
175
  process.exit(1);
175
176
  }
176
177
  }
@@ -202,7 +203,7 @@ export async function filesStorageCommand() {
202
203
  }
203
204
  catch (err) {
204
205
  spinner.stop();
205
- output.error(err instanceof Error ? err.message : String(err));
206
+ output.error(getErrorMessage(err));
206
207
  process.exit(1);
207
208
  }
208
209
  }
@@ -239,7 +240,7 @@ export async function filesCatCommand(fileId, opts) {
239
240
  }
240
241
  catch (err) {
241
242
  spinner.stop();
242
- output.error(err instanceof Error ? err.message : String(err));
243
+ output.error(getErrorMessage(err));
243
244
  process.exit(1);
244
245
  }
245
246
  }
@@ -315,7 +316,7 @@ export async function filesEditCommand(fileId, opts) {
315
316
  }
316
317
  catch (err) {
317
318
  spinner.stop();
318
- output.error(err instanceof Error ? err.message : String(err));
319
+ output.error(getErrorMessage(err));
319
320
  process.exit(1);
320
321
  }
321
322
  }
@@ -341,7 +342,7 @@ export async function filesWriteCommand(fileId, opts) {
341
342
  }
342
343
  catch (err) {
343
344
  spinner.stop();
344
- output.error(err instanceof Error ? err.message : String(err));
345
+ output.error(getErrorMessage(err));
345
346
  process.exit(1);
346
347
  }
347
348
  }
@@ -380,7 +381,7 @@ export async function filesSearchCommand(query, opts) {
380
381
  }
381
382
  catch (err) {
382
383
  spinner.stop();
383
- output.error(err instanceof Error ? err.message : String(err));
384
+ output.error(getErrorMessage(err));
384
385
  process.exit(1);
385
386
  }
386
387
  }
@@ -403,7 +404,7 @@ export async function filesMvCommand(fileId, folder) {
403
404
  }
404
405
  catch (err) {
405
406
  spinner.stop();
406
- output.error(err instanceof Error ? err.message : String(err));
407
+ output.error(getErrorMessage(err));
407
408
  process.exit(1);
408
409
  }
409
410
  }
@@ -426,7 +427,7 @@ export async function filesRenameCommand(fileId, name) {
426
427
  }
427
428
  catch (err) {
428
429
  spinner.stop();
429
- output.error(err instanceof Error ? err.message : String(err));
430
+ output.error(getErrorMessage(err));
430
431
  process.exit(1);
431
432
  }
432
433
  }
@@ -455,7 +456,7 @@ export async function filesInfoCommand(fileId) {
455
456
  }
456
457
  catch (err) {
457
458
  spinner.stop();
458
- output.error(err instanceof Error ? err.message : String(err));
459
+ output.error(getErrorMessage(err));
459
460
  process.exit(1);
460
461
  }
461
462
  }
@@ -2,10 +2,15 @@ export interface LaunchOptions {
2
2
  name?: string;
3
3
  symbol?: string;
4
4
  image?: string;
5
+ chain?: string;
6
+ /** Commander --no-vesting flag: false = vesting explicitly turned off. */
7
+ vesting?: boolean;
5
8
  tweet?: string;
6
9
  website?: string;
7
10
  fee?: string;
8
11
  feeType?: string;
12
+ /** --quote-only-fees flag: collect all creator fees in the quote token. */
13
+ quoteOnlyFees?: boolean;
9
14
  yes?: boolean;
10
15
  simulate?: boolean;
11
16
  }