@bankr/cli 0.3.32 → 0.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,6 +94,12 @@ bankr launch --name "TESTCOIN" --image "https://example.com/img.png" \
94
94
  # Quick launch (name only, skip confirmation)
95
95
  bankr launch --name "TESTCOIN" -y
96
96
 
97
+ # Pair the pool with a tokenized stock or another listed quote token instead of WETH (Robinhood Chain or Base)
98
+ bankr launch quotes --chain robinhood # list the quote tokens you can pair with
99
+ bankr launch --name "TESTCOIN" --chain robinhood --quote TSLA -y
100
+ bankr launch --name "TESTCOIN" --chain robinhood --quote musebook -y
101
+ bankr launch --name "TESTCOIN" --chain robinhood --quote BNKR -y # the Robinhood Chain BNKR contract
102
+
97
103
  # Fee recipient types: x, farcaster, ens, wallet
98
104
  bankr launch --name "TESTCOIN" --fee "vitalik.eth" --fee-type ens -y
99
105
  bankr launch --name "TESTCOIN" --fee "0x1234..." --fee-type wallet -y
@@ -174,12 +180,12 @@ Credentials are stored in `~/.bankr/config.json`.
174
180
 
175
181
  Environment variables override stored values:
176
182
 
177
- | Variable | Description |
178
- |----------|-------------|
179
- | `BANKR_API_KEY` | API key (overrides stored key) |
180
- | `BANKR_API_URL` | API URL (default: `https://api.bankr.bot`) |
183
+ | Variable | Description |
184
+ | --------------- | ---------------------------------------------------------- |
185
+ | `BANKR_API_KEY` | API key (overrides stored key) |
186
+ | `BANKR_API_URL` | API URL (default: `https://api.bankr.bot`) |
181
187
  | `BANKR_LLM_KEY` | LLM gateway key (falls back to `BANKR_API_KEY` if not set) |
182
- | `BANKR_LLM_URL` | LLM gateway URL (default: `https://llm.bankr.bot`) |
188
+ | `BANKR_LLM_URL` | LLM gateway URL (default: `https://llm.bankr.bot`) |
183
189
 
184
190
  ## API Key
185
191
 
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ import { passthroughArgs } from "./lib/passthrough.js";
15
15
  import { claudeCommand, creditsCommand, creditsAddCommand, creditsAutoCommand, modelsCommand, opencodeCommand, setupClaudeCodeCommand, setupCursorCommand, setupOpenclawCommand, setupOpenCodeCommand, } from "./commands/llm.js";
16
16
  import { feesCommand, feesClaimCommand, feesClaimWalletCommand, } from "./commands/fees.js";
17
17
  import { launchCommand } from "./commands/launch.js";
18
+ import { launchQuotesCommand } from "./commands/launchQuotes.js";
18
19
  import { loginCommand } from "./commands/login.js";
19
20
  import { logoutCommand } from "./commands/logout.js";
20
21
  import { promptCommand } from "./commands/prompt.js";
@@ -125,8 +126,10 @@ loginCmd
125
126
  .option("--accept-terms", "Accept Terms of Service (skips prompt)")
126
127
  .option("--key-name <name>", "API key name (default: CLI)")
127
128
  .optionsGroup("API Permissions")
128
- .option("--read-write", "Disable read-only mode (allow transactions)")
129
+ .option("--read-only", "Restrict the API key to read operations")
130
+ .option("--read-write", "Allow transactions (the default for new keys)")
129
131
  .option("--no-wallet-api", "Disable Wallet API (enabled by default)")
132
+ .option("--no-agent-api", "Disable Agent API (enabled by default)")
130
133
  .option("--no-token-launch", "Disable Token Launch API (enabled by default)")
131
134
  .option("--llm", "Enable LLM gateway on the API key")
132
135
  .optionsGroup("Security")
@@ -141,8 +144,10 @@ loginCmd
141
144
  code: opts.code,
142
145
  acceptTerms: opts.acceptTerms,
143
146
  keyName: opts.keyName,
147
+ readOnly: opts.readOnly,
144
148
  readWrite: opts.readWrite,
145
149
  walletApi: opts.walletApi,
150
+ agentApi: opts.agentApi,
146
151
  tokenLaunch: opts.tokenLaunch,
147
152
  llm: opts.llm,
148
153
  allowedIps: opts.allowedIps,
@@ -157,6 +162,7 @@ loginCmd
157
162
  .optionsGroup("API Permissions")
158
163
  .option("--read-write", "Disable read-only mode (allow transactions)")
159
164
  .option("--no-wallet-api", "Disable Wallet API (enabled by default)")
165
+ .option("--no-agent-api", "Disable Agent API (enabled by default)")
160
166
  .option("--no-token-launch", "Disable Token Launch API (enabled by default)")
161
167
  .optionsGroup("Security")
162
168
  .option("--allowed-ips <ips>", "Comma-separated IP/CIDR allowlist, e.g. 1.2.3.4,10.0.0.0/24")
@@ -170,6 +176,7 @@ loginCmd
170
176
  keyName: opts.keyName,
171
177
  readWrite: opts.readWrite,
172
178
  walletApi: opts.walletApi,
179
+ agentApi: opts.agentApi,
173
180
  tokenLaunch: opts.tokenLaunch,
174
181
  allowedIps: opts.allowedIps,
175
182
  allowedRecipients: opts.allowedRecipients,
@@ -337,7 +344,7 @@ agentCmd
337
344
  .description("Show all Bankr AI agent skills with examples")
338
345
  .action(skillsCommand);
339
346
  program.commandsGroup("Tokens");
340
- program
347
+ const launchCmd = program
341
348
  .command("launch")
342
349
  .description("Launch a token on Base, Robinhood Chain or Arbitrum (interactive wizard)")
343
350
  .optionsGroup("Token")
@@ -345,6 +352,7 @@ program
345
352
  .option("--symbol <symbol>", "Token symbol (default: auto-generated from name)")
346
353
  .option("--image <url>", "Token image URL")
347
354
  .option("--chain <chain>", "Chain to launch on: base, robinhood, arbitrum (default: base)")
355
+ .option("--quote <token>", 'Quote token to pair the pool with, e.g. a tokenized stock: a symbol or address from "bankr launch quotes" (default: WETH)')
348
356
  .option("--no-vesting", "Turn off the default 15% creator vesting (sell 100% of supply into the pool)")
349
357
  .optionsGroup("Links")
350
358
  .option("--tweet <url>", "Associated tweet URL")
@@ -362,6 +370,7 @@ program
362
370
  symbol: opts.symbol,
363
371
  image: opts.image,
364
372
  chain: opts.chain,
373
+ quote: opts.quote,
365
374
  vesting: opts.vesting,
366
375
  tweet: opts.tweet,
367
376
  website: opts.website,
@@ -372,6 +381,14 @@ program
372
381
  simulate: opts.simulate,
373
382
  });
374
383
  });
384
+ launchCmd
385
+ .command("quotes")
386
+ .description("List the quote tokens a launch can pair with on a chain (WETH, tokenized stocks, more)")
387
+ .option("--chain <chain>", "Chain to list for: base, robinhood, arbitrum (default: base)")
388
+ .option("--json", "Output raw JSON")
389
+ // The parent `launch` also owns --chain and, like every command here, parses
390
+ // its options from anywhere on the line — so read the merged view.
391
+ .action((_opts, cmd) => launchQuotesCommand(cmd.optsWithGlobals()));
375
392
  program.commandsGroup("Tokens");
376
393
  const feesCmd = program
377
394
  .command("fees [address]")
@@ -502,7 +519,7 @@ llmSetupCmd
502
519
  .command("openclaw")
503
520
  .description("OpenClaw config")
504
521
  .option("--install", "Write config to ~/.openclaw/openclaw.json")
505
- .option("--images", "Also route image generation (gpt-image-2) through the gateway — takes over OpenClaw's 'openai' provider slot")
522
+ .option("--images", "Also route image generation (gpt-image-2.5-flare) through the gateway — takes over OpenClaw's 'openai' provider slot")
506
523
  .action(setupOpenclawCommand);
507
524
  llmSetupCmd
508
525
  .command("opencode")
@@ -1,8 +1,11 @@
1
+ import { type LaunchChain } from "../lib/api.js";
1
2
  export interface LaunchOptions {
2
3
  name?: string;
3
4
  symbol?: string;
4
5
  image?: string;
5
6
  chain?: string;
7
+ /** --quote: symbol or address of the quote token to pair the pool with. */
8
+ quote?: string;
6
9
  /** Commander --no-vesting flag: false = vesting explicitly turned off. */
7
10
  vesting?: boolean;
8
11
  tweet?: string;
@@ -14,5 +17,7 @@ export interface LaunchOptions {
14
17
  yes?: boolean;
15
18
  simulate?: boolean;
16
19
  }
20
+ export declare function launchChainLabel(chain: LaunchChain): string;
21
+ export declare function parseLaunchChain(value?: string): LaunchChain | undefined;
17
22
  export declare function launchCommand(options: LaunchOptions): Promise<void>;
18
23
  //# sourceMappingURL=launch.d.ts.map
@@ -1,9 +1,10 @@
1
- import { confirm, input, select } from "@inquirer/prompts";
2
- import { deployToken, } from "../lib/api.js";
1
+ import { confirm, input, search, select } from "@inquirer/prompts";
2
+ import { deployToken, getLaunchQuoteTokens, } from "../lib/api.js";
3
3
  import { emitCESP } from "../lib/cesp/engine.js";
4
4
  import { requireApiKey } from "../lib/config.js";
5
5
  import * as output from "../lib/output.js";
6
6
  import { getErrorMessage } from "../lib/errors.js";
7
+ import { quoteTokenAvailabilityNote, quoteTokenDeployFields, resolveQuoteToken, } from "../lib/quoteTokens.js";
7
8
  const VALID_FEE_TYPES = ["x", "farcaster", "ens", "wallet"];
8
9
  const VALID_CHAINS = ["base", "robinhood", "arbitrum"];
9
10
  const CHAIN_LABELS = {
@@ -11,10 +12,10 @@ const CHAIN_LABELS = {
11
12
  robinhood: "Robinhood Chain",
12
13
  arbitrum: "Arbitrum",
13
14
  };
14
- function chainLabel(chain) {
15
+ export function launchChainLabel(chain) {
15
16
  return CHAIN_LABELS[chain];
16
17
  }
17
- function parseChain(value) {
18
+ export function parseLaunchChain(value) {
18
19
  if (!value)
19
20
  return undefined;
20
21
  const lower = value.toLowerCase();
@@ -33,6 +34,60 @@ function parseFeeType(value) {
33
34
  output.warn(`Unknown fee type "${value}". Valid types: ${VALID_FEE_TYPES.join(", ")}. Defaulting to "x".`);
34
35
  return "x";
35
36
  }
37
+ function quoteTokenChoiceName(option) {
38
+ const note = quoteTokenAvailabilityNote(option);
39
+ const base = option.isDefault
40
+ ? `${option.symbol} (default)`
41
+ : `${option.symbol} — ${option.name}`;
42
+ return note ? `${base} [${note}]` : base;
43
+ }
44
+ /**
45
+ * Resolve the quote token for the pool: --quote wins; otherwise the wizard
46
+ * offers the chain's list. Non-interactive runs without --quote keep the
47
+ * chain default and never hit the network.
48
+ */
49
+ async function collectQuoteToken(chain, requested) {
50
+ if (!requested && output.isNonInteractive())
51
+ return undefined;
52
+ let listing;
53
+ try {
54
+ listing = await getLaunchQuoteTokens(chain);
55
+ }
56
+ catch (err) {
57
+ if (requested) {
58
+ output.fatal(`Could not load quote tokens for ${launchChainLabel(chain)}: ${getErrorMessage(err)}`);
59
+ }
60
+ output.warn(`Could not load quote tokens for ${launchChainLabel(chain)}; pairing with the chain default.`);
61
+ return undefined;
62
+ }
63
+ const { provider, quoteTokens } = listing;
64
+ if (requested) {
65
+ const option = resolveQuoteToken(quoteTokens, requested);
66
+ if (!option) {
67
+ output.fatal(`"${requested}" is not a quote token you can pair with on ${launchChainLabel(chain)}. Run "bankr launch quotes --chain ${chain}" to see the list.`);
68
+ }
69
+ return option.isDefault ? undefined : { option, provider };
70
+ }
71
+ // Nothing to choose from — the chain only offers its default.
72
+ if (quoteTokens.length <= 1)
73
+ return undefined;
74
+ const option = await search({
75
+ message: "Quote token to pair the pool with? (type to filter)",
76
+ source: (term) => {
77
+ const key = (term ?? "").trim().toLowerCase().replace(/^\$/, "");
78
+ return quoteTokens
79
+ .filter((candidate) => !key ||
80
+ candidate.symbol.toLowerCase().includes(key) ||
81
+ candidate.name.toLowerCase().includes(key))
82
+ .map((candidate) => ({
83
+ name: quoteTokenChoiceName(candidate),
84
+ value: candidate,
85
+ }));
86
+ },
87
+ theme: output.bankrTheme,
88
+ });
89
+ return option.isDefault ? undefined : { option, provider };
90
+ }
36
91
  function feeTypeLabel(type) {
37
92
  switch (type) {
38
93
  case "x":
@@ -66,12 +121,12 @@ async function collectInteractive(options) {
66
121
  (await promptOrSkip("Image URL for your token (press Enter to skip):"));
67
122
  // Chain: --chain flag wins; otherwise interactive select (default Base).
68
123
  // Non-interactive runs without --chain launch on Base.
69
- let chain = parseChain(options.chain);
124
+ let chain = parseLaunchChain(options.chain);
70
125
  if (!chain && !output.isNonInteractive()) {
71
126
  chain = await select({
72
127
  message: "Which chain do you want to launch on?",
73
128
  choices: VALID_CHAINS.map((value) => ({
74
- name: chainLabel(value),
129
+ name: launchChainLabel(value),
75
130
  value,
76
131
  })),
77
132
  default: "base",
@@ -79,6 +134,8 @@ async function collectInteractive(options) {
79
134
  });
80
135
  }
81
136
  chain ?? (chain = "base");
137
+ const quoteToken = await collectQuoteToken(chain, options.quote);
138
+ const quoteSymbol = quoteToken?.option.symbol ?? "WETH";
82
139
  // Vesting: --no-vesting wins; otherwise interactive select (default: keep
83
140
  // the 15% creator vesting). Non-interactive runs keep vesting on.
84
141
  let disableVesting = options.vesting === false;
@@ -98,7 +155,7 @@ async function collectInteractive(options) {
98
155
  let quoteOnlyFees = options.quoteOnlyFees === true;
99
156
  if (!quoteOnlyFees && !output.isNonInteractive()) {
100
157
  quoteOnlyFees = await select({
101
- message: "Fee collection? By default creator fees accrue as a mix of your token and the pool's quote token (e.g. WETH).",
158
+ message: `Fee collection? By default creator fees accrue as a mix of your token and the pool's quote token (${quoteSymbol}).`,
102
159
  choices: [
103
160
  { name: "Both tokens (default)", value: false },
104
161
  { name: "Quote token only", value: true },
@@ -132,6 +189,7 @@ async function collectInteractive(options) {
132
189
  tokenSymbol: tokenSymbol.trim(),
133
190
  imageUrl: imageUrl.trim(),
134
191
  chain,
192
+ quoteToken,
135
193
  tweetUrl: tweetUrl.trim(),
136
194
  websiteUrl: websiteUrl.trim(),
137
195
  feeRecipient: feeRecipient.trim(),
@@ -159,17 +217,32 @@ export async function launchCommand(options) {
159
217
  output.keyValue("Fee recipient", state.feeRecipient
160
218
  ? `${state.feeRecipient} (${feeTypeLabel(state.feeRecipientType)})`
161
219
  : "(self)");
162
- output.keyValue("Chain", chainLabel(state.chain));
220
+ output.keyValue("Chain", launchChainLabel(state.chain));
221
+ const quoteSymbol = state.quoteToken?.option.symbol ?? "WETH";
222
+ output.keyValue("Quote token", state.quoteToken
223
+ ? `${quoteSymbol} — ${state.quoteToken.option.name} (${state.quoteToken.option.address})`
224
+ : "WETH (default)");
163
225
  output.keyValue("Vesting", state.disableVesting
164
226
  ? "Off (100% of supply sold into the pool)"
165
227
  : "15% to fee recipient over 1 year (default)");
166
228
  output.keyValue("Fee collection", state.quoteOnlyFees
167
- ? "Quote token only (e.g. WETH)"
229
+ ? `Quote token only (${quoteSymbol})`
168
230
  : "Both tokens (default)");
169
231
  if (options.simulate) {
170
232
  output.keyValue("Mode", "Simulation (dry run)");
171
233
  }
172
234
  console.log();
235
+ const quoteNote = state.quoteToken
236
+ ? quoteTokenAvailabilityNote(state.quoteToken.option)
237
+ : undefined;
238
+ if (quoteNote === "thin liquidity") {
239
+ output.warn(`${quoteSymbol} has thin liquidity right now — the pool may be hard to trade until more arrives.`);
240
+ console.log();
241
+ }
242
+ else if (quoteNote) {
243
+ output.warn(`${quoteSymbol} is ${quoteNote}; the API may refuse this pairing until it is live.`);
244
+ console.log();
245
+ }
173
246
  if (!options.yes && !output.isNonInteractive()) {
174
247
  const proceed = await confirm({
175
248
  message: options.simulate
@@ -200,6 +273,8 @@ export async function launchCommand(options) {
200
273
  simulateOnly: options.simulate,
201
274
  disableVesting: state.disableVesting || undefined,
202
275
  quoteOnlyFees: state.quoteOnlyFees || undefined,
276
+ ...(state.quoteToken &&
277
+ quoteTokenDeployFields(state.quoteToken.option, state.quoteToken.provider)),
203
278
  });
204
279
  const elapsed = output.formatDuration(Date.now() - startTime);
205
280
  spin.succeed(result.simulated
@@ -0,0 +1,7 @@
1
+ export interface LaunchQuotesOptions {
2
+ chain?: string;
3
+ json?: boolean;
4
+ }
5
+ /** `bankr launch quotes` — the quote tokens a launch on a chain can pair with. */
6
+ export declare function launchQuotesCommand(opts: LaunchQuotesOptions): Promise<void>;
7
+ //# sourceMappingURL=launchQuotes.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { getLaunchQuoteTokens } from "../lib/api.js";
2
+ import * as output from "../lib/output.js";
3
+ import { getErrorMessage } from "../lib/errors.js";
4
+ import { quoteTokenAvailabilityNote, quoteTokenGroupLabel, } from "../lib/quoteTokens.js";
5
+ import { launchChainLabel, parseLaunchChain } from "./launch.js";
6
+ const GROUP_ORDER = [
7
+ "Default",
8
+ "Majors",
9
+ "More tokens",
10
+ "Stocks",
11
+ ];
12
+ /** `bankr launch quotes` — the quote tokens a launch on a chain can pair with. */
13
+ export async function launchQuotesCommand(opts) {
14
+ const chain = parseLaunchChain(opts.chain) ?? "base";
15
+ if (opts.json) {
16
+ try {
17
+ console.log(JSON.stringify(await getLaunchQuoteTokens(chain), null, 2));
18
+ }
19
+ catch (err) {
20
+ output.fatal(getErrorMessage(err));
21
+ }
22
+ return;
23
+ }
24
+ const spin = output.spinner(`Loading quote tokens for ${launchChainLabel(chain)}...`);
25
+ let listing;
26
+ try {
27
+ listing = await getLaunchQuoteTokens(chain);
28
+ }
29
+ catch (err) {
30
+ output.failWith(spin, "Could not load quote tokens", err);
31
+ }
32
+ spin.succeed(`${listing.quoteTokens.length} quote token(s) on ${launchChainLabel(chain)}`);
33
+ const symbolWidth = Math.max(...listing.quoteTokens.map((option) => option.symbol.length), 4);
34
+ for (const group of GROUP_ORDER) {
35
+ const members = listing.quoteTokens.filter((option) => quoteTokenGroupLabel(option) === group);
36
+ if (members.length === 0)
37
+ continue;
38
+ console.log();
39
+ output.brandBold(` ${group} (${members.length})`);
40
+ for (const option of members) {
41
+ const note = quoteTokenAvailabilityNote(option);
42
+ const label = `${option.symbol.padEnd(symbolWidth)} ${option.name}`;
43
+ console.log(` ${label} ${output.fmt.dim(option.address)}${note ? ` ${output.fmt.dim(`[${note}]`)}` : ""}`);
44
+ }
45
+ }
46
+ console.log();
47
+ output.dim(`Pair a launch with one: bankr launch --chain ${chain} --quote <symbol or address>`);
48
+ }
49
+ //# sourceMappingURL=launchQuotes.js.map
@@ -212,6 +212,28 @@ const GATEWAY_MODELS = [
212
212
  cost: { input: 0.15, output: 0.6, cacheRead: 0, cacheWrite: 0 },
213
213
  },
214
214
  // OpenAI
215
+ {
216
+ id: "gpt-image-2.5-flare",
217
+ name: "GPT Image 2.5 Flare",
218
+ owned_by: "openai",
219
+ contextWindow: 400000,
220
+ maxTokens: 16384,
221
+ input: IMAGE_INPUT,
222
+ output: ["image"],
223
+ // Image model — served via /v1/images/generations; output rate is per
224
+ // million image tokens.
225
+ cost: { input: 5.0, output: 30.0, cacheRead: 1.25, cacheWrite: 0 },
226
+ },
227
+ {
228
+ id: "gpt-image-2.5-sunburst",
229
+ name: "GPT Image 2.5 Sunburst",
230
+ owned_by: "openai",
231
+ contextWindow: 400000,
232
+ maxTokens: 16384,
233
+ input: IMAGE_INPUT,
234
+ output: ["image"],
235
+ cost: { input: 5.0, output: 30.0, cacheRead: 1.25, cacheWrite: 0 },
236
+ },
215
237
  {
216
238
  id: "gpt-6-astra",
217
239
  name: "GPT 6 Astra",
@@ -258,7 +280,7 @@ const GATEWAY_MODELS = [
258
280
  output: ["image"],
259
281
  // Image model — served via /v1/images/generations; output rate is per
260
282
  // million image tokens.
261
- cost: { input: 5.0, output: 30.0, cacheRead: 0, cacheWrite: 0 },
283
+ cost: { input: 5.0, output: 30.0, cacheRead: 1.25, cacheWrite: 0 },
262
284
  },
263
285
  {
264
286
  id: "gpt-5.5",
@@ -384,6 +406,15 @@ const GATEWAY_MODELS = [
384
406
  removed_at: "2026-06-17T00:00:00.000Z",
385
407
  },
386
408
  },
409
+ {
410
+ id: "deepseek-v4.1-flash",
411
+ name: "DeepSeek V4.1 Flash",
412
+ owned_by: "deepseek",
413
+ contextWindow: 1048576,
414
+ maxTokens: 384000,
415
+ input: IMAGE_INPUT,
416
+ cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0 },
417
+ },
387
418
  {
388
419
  id: "deepseek-v4-pro-0813",
389
420
  name: "DeepSeek V4 Pro 0813",
@@ -838,10 +869,10 @@ export async function modelsCommand(opts = {}) {
838
869
  output.dim(` Gateway: ${getLlmUrl()}`);
839
870
  output.dim(` ${shown.length} model${shown.length === 1 ? "" : "s"}${view?.countSuffix ?? " available"}${live ? "" : " (cached)"}`);
840
871
  if (!view && shown.some((m) => m.private)) {
841
- output.dim(" Private (TEE) — append :private to the model id (e.g. glm-5.2:private). List: bankr llm models --private");
872
+ output.dim(" Private (TEE) — append :private to the model id (e.g. glm-5.3-flash:private). List: bankr llm models --private");
842
873
  }
843
874
  if (!view && shown.some((m) => m.zdr)) {
844
- output.dim(" Zero data retention — append :zdr to the model id (e.g. glm-5.2:zdr), or turn it on account-wide under Settings in the web terminal. List: bankr llm models --zdr");
875
+ output.dim(" Zero data retention — append :zdr to the model id (e.g. glm-5.3-flash:zdr), or turn it on account-wide under Settings in the web terminal. List: bankr llm models --zdr");
845
876
  }
846
877
  }
847
878
  /* ─────────────────────── bankr llm credits ─────────────────────────────────── */
@@ -1283,7 +1314,7 @@ export async function setupOpenclawCommand(opts) {
1283
1314
  ? { baseUrl: imageBaseUrl, apiKey: llmKey ?? "${BANKR_API_KEY}" }
1284
1315
  : null;
1285
1316
  const imageGenerationModel = opts.images
1286
- ? { primary: "openai/gpt-image-2", timeoutMs: 180000 }
1317
+ ? { primary: "openai/gpt-image-2.5-flare", timeoutMs: 180000 }
1287
1318
  : null;
1288
1319
  const warnImagesSlot = () => {
1289
1320
  if (opts.images)
@@ -11,6 +11,19 @@ interface CliAuthTokens {
11
11
  /** The auth headers every authenticated CLI call sends — one place, so a new
12
12
  * endpoint can't silently omit the access token and go unrevocable again. */
13
13
  export declare function authHeaders(tokens: CliAuthTokens): Record<string, string>;
14
+ /**
15
+ * Maps the permission opt-out flags to the create-key body. Omitted fields
16
+ * are dropped by JSON.stringify so the server's API_KEY_CREATE_DEFAULTS
17
+ * decide them; only an explicit flag sends a value.
18
+ */
19
+ export declare function resolveKeyPermissionOverrides(opts: {
20
+ readOnly?: boolean;
21
+ readWrite?: boolean;
22
+ agentApi?: boolean;
23
+ }): {
24
+ readOnly?: boolean;
25
+ agentApiEnabled?: boolean;
26
+ };
14
27
  export declare function loginCommand(opts: {
15
28
  apiUrl?: string;
16
29
  apiKey?: string;
@@ -18,8 +31,10 @@ export declare function loginCommand(opts: {
18
31
  code?: string;
19
32
  acceptTerms?: boolean;
20
33
  keyName?: string;
34
+ readOnly?: boolean;
21
35
  readWrite?: boolean;
22
36
  walletApi?: boolean;
37
+ agentApi?: boolean;
23
38
  tokenLaunch?: boolean;
24
39
  llm?: boolean;
25
40
  llmKey?: string;
@@ -52,6 +52,26 @@ export function authHeaders(tokens) {
52
52
  "x-access-token": tokens.accessToken,
53
53
  };
54
54
  }
55
+ /**
56
+ * Maps the permission opt-out flags to the create-key body. Omitted fields
57
+ * are dropped by JSON.stringify so the server's API_KEY_CREATE_DEFAULTS
58
+ * decide them; only an explicit flag sends a value.
59
+ */
60
+ export function resolveKeyPermissionOverrides(opts) {
61
+ if (opts.readOnly && opts.readWrite) {
62
+ throw new Error("--read-only and --read-write are mutually exclusive");
63
+ }
64
+ // Build conditionally: an own `undefined` key would clobber a value the
65
+ // caller set before spreading this in.
66
+ const overrides = {};
67
+ if (opts.readOnly)
68
+ overrides.readOnly = true;
69
+ else if (opts.readWrite)
70
+ overrides.readOnly = false;
71
+ if (opts.agentApi === false)
72
+ overrides.agentApiEnabled = false;
73
+ return overrides;
74
+ }
55
75
  async function verifyPrivyOtp(privyConfig, email, code) {
56
76
  const res = await fetch("https://auth.privy.io/api/v1/passwordless/authenticate", {
57
77
  method: "POST",
@@ -266,10 +286,12 @@ async function emailLoginFlow(apiUrl, opts) {
266
286
  validate: (v) => v.trim().length >= 3 ? true : "Name must be at least 3 characters.",
267
287
  }));
268
288
  // Fine-grained API flags. walletApi and tokenLaunch default to enabled.
269
- // --read-write only controls readOnly, not which APIs are enabled.
289
+ // --read-only/--read-write only control readOnly, not which APIs are enabled.
270
290
  const enableWallet = opts.walletApi ?? true;
271
- // Agent API must be enabled from the website (bankr.bot/api-keys), not the CLI.
272
- const enableAgent = false;
291
+ // readOnly and agentApiEnabled are only sent when a flag asks for it, so the
292
+ // server's API_KEY_CREATE_DEFAULTS (the bankr.bot/api-keys form) decide them.
293
+ // The --read-only/--read-write conflict was already rejected in loginCommand.
294
+ const overrides = resolveKeyPermissionOverrides(opts);
273
295
  const enableTokenLaunch = opts.tokenLaunch ?? true;
274
296
  const enableLlm = opts.llm ??
275
297
  (headless
@@ -279,17 +301,13 @@ async function emailLoginFlow(apiUrl, opts) {
279
301
  default: false,
280
302
  theme: output.bankrTheme,
281
303
  }));
282
- if (opts.readWrite && !enableWallet) {
283
- output.warn("--read-write has no effect when Wallet API is disabled");
284
- }
285
304
  const keySpin = output.spinner("Generating API key...");
286
305
  let apiKeyResult;
287
306
  try {
288
307
  apiKeyResult = await callGenerateApiKey(apiUrl, tokens, {
289
308
  name: keyName.trim(),
290
309
  walletApiEnabled: enableWallet,
291
- agentApiEnabled: enableAgent,
292
- readOnly: opts.readWrite ? false : undefined,
310
+ ...overrides,
293
311
  tokenLaunchApiEnabled: enableTokenLaunch,
294
312
  llmGatewayEnabled: enableLlm,
295
313
  allowedIps: opts.allowedIps,
@@ -304,7 +322,7 @@ async function emailLoginFlow(apiUrl, opts) {
304
322
  output.blank();
305
323
  const features = [
306
324
  enableWallet && "Wallet",
307
- enableAgent && "Agent",
325
+ apiKeyResult.agentApiEnabled && "Agent",
308
326
  enableTokenLaunch && "Token Launch",
309
327
  enableLlm && "LLM",
310
328
  ].filter(Boolean);
@@ -321,7 +339,6 @@ async function emailLoginFlow(apiUrl, opts) {
321
339
  ];
322
340
  output.dim(` Recipients: ${parts.join(", ")}`);
323
341
  }
324
- output.dim(" Agent API: enable at bankr.bot/api-keys");
325
342
  output.dim(" Manage keys at bankr.bot/api-keys");
326
343
  return apiKeyResult.apiKey;
327
344
  }
@@ -372,7 +389,7 @@ async function siweLoginFlow(apiUrl, opts) {
372
389
  keyName: opts.keyName ?? `SIWE-${new Date().toISOString().slice(0, 10)}`,
373
390
  readOnly: opts.readOnly ?? false,
374
391
  walletApiEnabled: opts.walletApi ?? true,
375
- agentApiEnabled: false,
392
+ ...resolveKeyPermissionOverrides({ agentApi: opts.agentApi }),
376
393
  tokenLaunchApiEnabled: opts.tokenLaunch ?? true,
377
394
  allowedIps: opts.allowedIps,
378
395
  allowedRecipients: opts.allowedRecipients,
@@ -492,6 +509,11 @@ export async function loginCommand(opts) {
492
509
  if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
493
510
  output.fatal("--api-key requires a non-empty value");
494
511
  }
512
+ // Reject conflicting permission flags before any network call, so a
513
+ // headless step-2 login doesn't burn its one-time code on a usage error.
514
+ if (opts.readOnly && opts.readWrite) {
515
+ output.fatal("--read-only and --read-write are mutually exclusive");
516
+ }
495
517
  // Non-interactive mode requires a headless flag combo. Without one, the
496
518
  // command would hang on a prompt — fail fast with the available paths.
497
519
  // Email step 1 (`login email <addr>` without --code) is also headless:
@@ -569,6 +591,7 @@ export async function loginCommand(opts) {
569
591
  keyName: opts.keyName,
570
592
  readOnly: !opts.readWrite,
571
593
  walletApi: opts.walletApi,
594
+ agentApi: opts.agentApi,
572
595
  tokenLaunch: opts.tokenLaunch,
573
596
  ...getParsedRestrictions(),
574
597
  });
@@ -36,9 +36,11 @@ const VALID_MAX_MODE_MODELS = new Set([
36
36
  // Grok (OpenRouter)
37
37
  "grok-4.6",
38
38
  "grok-4.5",
39
- // DeepSeek (OpenRouter)
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.
40
41
  "deepseek-v4-pro-0813",
41
42
  "deepseek-v4-pro",
43
+ "deepseek-v4.1-flash",
42
44
  // Qwen (OpenRouter)
43
45
  "qwen3.8-max",
44
46
  "qwen3.7-max",
package/dist/lib/api.d.ts CHANGED
@@ -199,6 +199,8 @@ export declare function submit(request: SubmitRequest): Promise<SubmitResponse>;
199
199
  export type FeeRecipientType = "wallet" | "x" | "farcaster" | "ens";
200
200
  /** Chains the deploy endpoint accepts (mirrors the API's zod enum). */
201
201
  export type LaunchChain = "base" | "robinhood" | "arbitrum";
202
+ /** Launch providers the deploy endpoint knows (mirrors the API's zod enum). */
203
+ export type LaunchProvider = "doppler" | "bankr_v3";
202
204
  export interface DeployTokenRequest {
203
205
  tokenName: string;
204
206
  tokenSymbol?: string;
@@ -217,7 +219,44 @@ export interface DeployTokenRequest {
217
219
  /** Collect all creator fees in the pool's quote token (e.g. WETH) instead of
218
220
  * a mix of the launched token and the quote token. */
219
221
  quoteOnlyFees?: boolean;
222
+ /** Pin the launch provider; sent alongside a quote-token pick so the API
223
+ * never resolves the pairing against a different provider. */
224
+ provider?: LaunchProvider;
225
+ /** Quote the pool in a tokenized stock (Robinhood Chain / Base, Doppler). */
226
+ pairedStockAddress?: string;
227
+ /** Quote the pool in one of the chain's fixed Doppler allowlist tokens
228
+ * (Base or Robinhood Chain). */
229
+ pairedTokenAddress?: string;
230
+ /** Bankr Launch v3 options; `quoteAddress` picks the pool's quote token. */
231
+ launchV3?: {
232
+ quoteAddress?: string;
233
+ };
220
234
  }
235
+ /** Deploy-body field that selects a quote token, as reported by the API. */
236
+ export type LaunchQuoteTokenDeployField = "pairedStockAddress" | "pairedTokenAddress" | "launchV3.quoteAddress";
237
+ export interface LaunchQuoteTokenOption {
238
+ address: string;
239
+ symbol: string;
240
+ name: string;
241
+ kind: "major" | "stable" | "project" | "wrapper" | "stock";
242
+ isDefault: boolean;
243
+ /** null = the chain default (omit every pairing field). */
244
+ deployField: LaunchQuoteTokenDeployField | null;
245
+ /** Stocks only: advisory thin-pool verdict, null when unmeasured. */
246
+ illiquid?: boolean | null;
247
+ /** Doppler allowlist tokens only (pairedTokenAddress): reviewed-liquidity gate. */
248
+ readiness?: "live" | "pending" | "unknown";
249
+ }
250
+ export interface LaunchQuoteTokensResponse {
251
+ chain: LaunchChain;
252
+ provider: LaunchProvider;
253
+ quoteTokens: LaunchQuoteTokenOption[];
254
+ }
255
+ /**
256
+ * Every quote token a new launch on `chain` can pair with, the default first.
257
+ * Unauthenticated — calls the public endpoint directly.
258
+ */
259
+ export declare function getLaunchQuoteTokens(chain: LaunchChain): Promise<LaunchQuoteTokensResponse>;
221
260
  export interface DeployTokenResponse {
222
261
  success: boolean;
223
262
  tokenAddress: string;
package/dist/lib/api.js CHANGED
@@ -129,6 +129,14 @@ export async function submit(request) {
129
129
  });
130
130
  return handleResponse(res);
131
131
  }
132
+ /**
133
+ * Every quote token a new launch on `chain` can pair with, the default first.
134
+ * Unauthenticated — calls the public endpoint directly.
135
+ */
136
+ export async function getLaunchQuoteTokens(chain) {
137
+ const res = await fetch(`${getApiUrl()}/token-launches/quote-tokens?chain=${chain}`, { headers: { "User-Agent": CLI_USER_AGENT } });
138
+ return handleResponse(res);
139
+ }
132
140
  export async function deployToken(request) {
133
141
  const res = await authedFetch(`${getApiUrl()}/token-launches/deploy`, {
134
142
  method: "POST",
@@ -7,6 +7,7 @@ export const CHAIN_LABELS = {
7
7
  arbitrum: "Arbitrum",
8
8
  bnb: "BNB Chain",
9
9
  robinhood: "Robinhood Chain",
10
+ arc: "Arc",
10
11
  solana: "Solana",
11
12
  };
12
13
  export const VALID_CHAINS = new Set([
@@ -18,6 +19,7 @@ export const VALID_CHAINS = new Set([
18
19
  "arbitrum",
19
20
  "bnb",
20
21
  "robinhood",
22
+ "arc",
21
23
  "solana",
22
24
  ]);
23
25
  export const CHAIN_IDS = {
@@ -29,6 +31,7 @@ export const CHAIN_IDS = {
29
31
  arbitrum: 42161,
30
32
  bnb: 56,
31
33
  robinhood: 4663,
34
+ arc: 5042,
32
35
  };
33
36
  export const NATIVE_SYMBOLS = {
34
37
  base: "ETH",
@@ -39,5 +42,6 @@ export const NATIVE_SYMBOLS = {
39
42
  polygon: "POL",
40
43
  bnb: "BNB",
41
44
  robinhood: "ETH",
45
+ arc: "USDC",
42
46
  };
43
47
  //# sourceMappingURL=chains.js.map
@@ -0,0 +1,15 @@
1
+ import type { DeployTokenRequest, LaunchProvider, LaunchQuoteTokenOption } from "./api.js";
2
+ /** Match a `--quote` value (symbol or address) against the API's list. */
3
+ export declare function resolveQuoteToken(options: readonly LaunchQuoteTokenOption[], query: string): LaunchQuoteTokenOption | undefined;
4
+ export type QuoteTokenDeployFields = Pick<DeployTokenRequest, "pairedStockAddress" | "pairedTokenAddress" | "launchV3" | "provider">;
5
+ /**
6
+ * The deploy-body fields that select `option`. The provider is pinned with
7
+ * every non-default pick so a rollout can't move the launch to a provider
8
+ * that ignores the field.
9
+ */
10
+ export declare function quoteTokenDeployFields(option: LaunchQuoteTokenOption, provider: LaunchProvider): QuoteTokenDeployFields;
11
+ export type QuoteTokenGroupLabel = "Default" | "Majors" | "More tokens" | "Stocks";
12
+ export declare function quoteTokenGroupLabel(option: LaunchQuoteTokenOption): QuoteTokenGroupLabel;
13
+ /** Short caveat to print next to an option, or undefined when it's clean. */
14
+ export declare function quoteTokenAvailabilityNote(option: LaunchQuoteTokenOption): string | undefined;
15
+ //# sourceMappingURL=quoteTokens.d.ts.map
@@ -0,0 +1,53 @@
1
+ function normalize(query) {
2
+ return query.trim().toLowerCase().replace(/^\$/, "");
3
+ }
4
+ /** Match a `--quote` value (symbol or address) against the API's list. */
5
+ export function resolveQuoteToken(options, query) {
6
+ const key = normalize(query);
7
+ if (!key)
8
+ return undefined;
9
+ return options.find((option) => option.symbol.toLowerCase() === key ||
10
+ option.address.toLowerCase() === key);
11
+ }
12
+ /**
13
+ * The deploy-body fields that select `option`. The provider is pinned with
14
+ * every non-default pick so a rollout can't move the launch to a provider
15
+ * that ignores the field.
16
+ */
17
+ export function quoteTokenDeployFields(option, provider) {
18
+ switch (option.deployField) {
19
+ case null:
20
+ return {};
21
+ case "pairedStockAddress":
22
+ return { pairedStockAddress: option.address, provider };
23
+ case "pairedTokenAddress":
24
+ return { pairedTokenAddress: option.address, provider };
25
+ case "launchV3.quoteAddress":
26
+ return { launchV3: { quoteAddress: option.address }, provider };
27
+ }
28
+ }
29
+ export function quoteTokenGroupLabel(option) {
30
+ if (option.isDefault)
31
+ return "Default";
32
+ switch (option.kind) {
33
+ case "major":
34
+ case "stable":
35
+ return "Majors";
36
+ case "project":
37
+ case "wrapper":
38
+ return "More tokens";
39
+ case "stock":
40
+ return "Stocks";
41
+ }
42
+ }
43
+ /** Short caveat to print next to an option, or undefined when it's clean. */
44
+ export function quoteTokenAvailabilityNote(option) {
45
+ if (option.illiquid === true)
46
+ return "thin liquidity";
47
+ if (option.readiness === "pending")
48
+ return "not ready yet";
49
+ if (option.readiness === "unknown")
50
+ return "readiness unknown";
51
+ return undefined;
52
+ }
53
+ //# sourceMappingURL=quoteTokens.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.3.32",
3
+ "version": "0.3.37",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {