@bankr/cli 0.1.0 → 0.2.0

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.
@@ -11,100 +11,255 @@ const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at
11
11
  * Fallback model catalog — used when the gateway is unreachable or unauthenticated.
12
12
  * resolveModels() fetches live data from GET /v1/models; this list is the offline safety net.
13
13
  */
14
+ const IMAGE_INPUT = ["text", "image"];
15
+ const TEXT_INPUT = ["text"];
14
16
  const GATEWAY_MODELS = [
15
- // Claude — 90% cache read discount, 25% cache write premium
17
+ // Claude
16
18
  {
17
19
  id: "claude-opus-4.6",
18
20
  name: "Claude Opus 4.6",
19
21
  owned_by: "anthropic",
20
- cost: { input: 15.0, output: 75.0, cacheRead: 1.5, cacheWrite: 18.75 },
22
+ contextWindow: 1000000,
23
+ maxTokens: 128000,
24
+ input: IMAGE_INPUT,
25
+ cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
21
26
  },
22
27
  {
23
28
  id: "claude-opus-4.5",
24
29
  name: "Claude Opus 4.5",
25
30
  owned_by: "anthropic",
26
- cost: { input: 15.0, output: 75.0, cacheRead: 1.5, cacheWrite: 18.75 },
31
+ contextWindow: 200000,
32
+ maxTokens: 64000,
33
+ input: IMAGE_INPUT,
34
+ cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
27
35
  },
28
36
  {
29
37
  id: "claude-sonnet-4.6",
30
38
  name: "Claude Sonnet 4.6",
31
39
  owned_by: "anthropic",
40
+ contextWindow: 1000000,
41
+ maxTokens: 128000,
42
+ input: IMAGE_INPUT,
32
43
  cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
33
44
  },
34
45
  {
35
46
  id: "claude-sonnet-4.5",
36
47
  name: "Claude Sonnet 4.5",
37
48
  owned_by: "anthropic",
49
+ contextWindow: 1000000,
50
+ maxTokens: 64000,
51
+ input: IMAGE_INPUT,
38
52
  cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
39
53
  },
40
54
  {
41
55
  id: "claude-haiku-4.5",
42
56
  name: "Claude Haiku 4.5",
43
57
  owned_by: "anthropic",
44
- cost: { input: 0.8, output: 4.0, cacheRead: 0.08, cacheWrite: 1.0 },
58
+ contextWindow: 200000,
59
+ maxTokens: 64000,
60
+ input: IMAGE_INPUT,
61
+ cost: { input: 1.0, output: 5.0, cacheRead: 0.1, cacheWrite: 1.25 },
62
+ },
63
+ // Gemini
64
+ {
65
+ id: "gemini-3.1-pro",
66
+ name: "Gemini 3.1 Pro",
67
+ owned_by: "google",
68
+ contextWindow: 1048576,
69
+ maxTokens: 65536,
70
+ input: IMAGE_INPUT,
71
+ cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0.375 },
72
+ },
73
+ {
74
+ id: "gemini-3.1-flash-lite",
75
+ name: "Gemini 3.1 Flash Lite",
76
+ owned_by: "google",
77
+ contextWindow: 1048576,
78
+ maxTokens: 65536,
79
+ input: IMAGE_INPUT,
80
+ cost: { input: 0.25, output: 1.5, cacheRead: 0.025, cacheWrite: 0.08333 },
45
81
  },
46
- // Gemini — 75% cache read discount
47
82
  {
48
83
  id: "gemini-3-pro",
49
84
  name: "Gemini 3 Pro",
50
85
  owned_by: "google",
51
- cost: { input: 1.25, output: 10.0, cacheRead: 0.3125, cacheWrite: 1.25 },
86
+ contextWindow: 1048576,
87
+ maxTokens: 65536,
88
+ input: IMAGE_INPUT,
89
+ cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0.375 },
52
90
  },
53
91
  {
54
92
  id: "gemini-3-flash",
55
93
  name: "Gemini 3 Flash",
56
94
  owned_by: "google",
57
- cost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0.15 },
95
+ contextWindow: 1048576,
96
+ maxTokens: 65535,
97
+ input: IMAGE_INPUT,
98
+ cost: { input: 0.5, output: 3.0, cacheRead: 0.05, cacheWrite: 0.0833 },
58
99
  },
59
100
  {
60
101
  id: "gemini-2.5-pro",
61
102
  name: "Gemini 2.5 Pro",
62
103
  owned_by: "google",
63
- cost: { input: 1.25, output: 10.0, cacheRead: 0.3125, cacheWrite: 1.25 },
104
+ contextWindow: 1048576,
105
+ maxTokens: 65536,
106
+ input: IMAGE_INPUT,
107
+ cost: { input: 1.25, output: 10.0, cacheRead: 0.125, cacheWrite: 0.375 },
64
108
  },
65
109
  {
66
110
  id: "gemini-2.5-flash",
67
111
  name: "Gemini 2.5 Flash",
68
112
  owned_by: "google",
69
- cost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0.15 },
113
+ contextWindow: 1048576,
114
+ maxTokens: 65535,
115
+ input: IMAGE_INPUT,
116
+ cost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0.0833 },
117
+ },
118
+ // OpenAI
119
+ {
120
+ id: "gpt-5.4",
121
+ name: "GPT 5.4",
122
+ owned_by: "openai",
123
+ contextWindow: 1050000,
124
+ maxTokens: 128000,
125
+ input: IMAGE_INPUT,
126
+ cost: { input: 2.5, output: 15.0, cacheRead: 0.25, cacheWrite: 0 },
127
+ },
128
+ {
129
+ id: "gpt-5.4-mini",
130
+ name: "GPT 5.4 Mini",
131
+ owned_by: "openai",
132
+ contextWindow: 400000,
133
+ maxTokens: 128000,
134
+ input: IMAGE_INPUT,
135
+ cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
136
+ },
137
+ {
138
+ id: "gpt-5.4-nano",
139
+ name: "GPT 5.4 Nano",
140
+ owned_by: "openai",
141
+ contextWindow: 400000,
142
+ maxTokens: 128000,
143
+ input: IMAGE_INPUT,
144
+ cost: { input: 0.2, output: 1.25, cacheRead: 0.02, cacheWrite: 0 },
70
145
  },
71
- // OpenAI — 50% cache read discount
72
146
  {
73
147
  id: "gpt-5.2",
74
148
  name: "GPT 5.2",
75
149
  owned_by: "openai",
76
- cost: { input: 2.5, output: 10.0, cacheRead: 1.25, cacheWrite: 2.5 },
150
+ contextWindow: 400000,
151
+ maxTokens: 128000,
152
+ input: TEXT_INPUT,
153
+ cost: { input: 1.75, output: 14.0, cacheRead: 0.175, cacheWrite: 0 },
77
154
  },
78
155
  {
79
156
  id: "gpt-5.2-codex",
80
157
  name: "GPT 5.2 Codex",
81
158
  owned_by: "openai",
82
- cost: { input: 2.5, output: 10.0, cacheRead: 1.25, cacheWrite: 2.5 },
159
+ contextWindow: 400000,
160
+ maxTokens: 128000,
161
+ input: TEXT_INPUT,
162
+ cost: { input: 1.75, output: 14.0, cacheRead: 0.175, cacheWrite: 0 },
83
163
  },
84
164
  {
85
165
  id: "gpt-5-mini",
86
166
  name: "GPT 5 Mini",
87
167
  owned_by: "openai",
88
- cost: { input: 0.4, output: 1.6, cacheRead: 0.2, cacheWrite: 0.4 },
168
+ contextWindow: 400000,
169
+ maxTokens: 128000,
170
+ input: TEXT_INPUT,
171
+ cost: { input: 0.25, output: 2.0, cacheRead: 0.025, cacheWrite: 0 },
89
172
  },
90
173
  {
91
174
  id: "gpt-5-nano",
92
175
  name: "GPT 5 Nano",
93
176
  owned_by: "openai",
94
- cost: { input: 0.1, output: 0.4, cacheRead: 0.05, cacheWrite: 0.1 },
177
+ contextWindow: 400000,
178
+ maxTokens: 128000,
179
+ input: TEXT_INPUT,
180
+ cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
95
181
  },
96
182
  // Other providers
97
183
  {
98
- id: "kimi-k2.5",
99
- name: "Kimi K2.5",
100
- owned_by: "moonshotai",
101
- cost: { input: 0.6, output: 2.4, cacheRead: 0.09, cacheWrite: 0.6 },
184
+ id: "grok-4.1-fast",
185
+ name: "Grok 4.1 Fast",
186
+ owned_by: "x-ai",
187
+ contextWindow: 2000000,
188
+ maxTokens: 30000,
189
+ input: TEXT_INPUT,
190
+ cost: { input: 0.2, output: 0.5, cacheRead: 0.05, cacheWrite: 0 },
191
+ },
192
+ {
193
+ id: "deepseek-v3.2",
194
+ name: "DeepSeek V3.2",
195
+ owned_by: "deepseek",
196
+ contextWindow: 163840,
197
+ maxTokens: 65536,
198
+ input: TEXT_INPUT,
199
+ cost: { input: 0.26, output: 0.38, cacheRead: 0.13, cacheWrite: 0 },
102
200
  },
103
201
  {
104
202
  id: "qwen3-coder",
105
203
  name: "Qwen3 Coder",
106
204
  owned_by: "qwen",
107
- cost: { input: 0.3, output: 1.2, cacheRead: 0.15, cacheWrite: 0.3 },
205
+ contextWindow: 262144,
206
+ maxTokens: 65536,
207
+ input: TEXT_INPUT,
208
+ cost: { input: 0.12, output: 0.75, cacheRead: 0.06, cacheWrite: 0 },
209
+ },
210
+ {
211
+ id: "qwen3.5-plus",
212
+ name: "Qwen3.5 Plus",
213
+ owned_by: "qwen",
214
+ contextWindow: 1000000,
215
+ maxTokens: 65536,
216
+ input: TEXT_INPUT,
217
+ cost: { input: 0.26, output: 1.56, cacheRead: 0, cacheWrite: 0 },
218
+ },
219
+ {
220
+ id: "qwen3.5-flash",
221
+ name: "Qwen3.5 Flash",
222
+ owned_by: "qwen",
223
+ contextWindow: 1000000,
224
+ maxTokens: 65536,
225
+ input: TEXT_INPUT,
226
+ cost: { input: 0.1, output: 0.4, cacheRead: 0, cacheWrite: 0 },
227
+ },
228
+ {
229
+ id: "kimi-k2.5",
230
+ name: "Kimi K2.5",
231
+ owned_by: "moonshotai",
232
+ contextWindow: 262144,
233
+ maxTokens: 65535,
234
+ input: TEXT_INPUT,
235
+ cost: { input: 0.45, output: 2.2, cacheRead: 0.225, cacheWrite: 0 },
236
+ },
237
+ {
238
+ id: "minimax-m2.5",
239
+ name: "MiniMax M2.5",
240
+ owned_by: "minimax",
241
+ contextWindow: 196608,
242
+ maxTokens: 196608,
243
+ input: TEXT_INPUT,
244
+ cost: { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
245
+ },
246
+ {
247
+ id: "minimax-m2.7",
248
+ name: "MiniMax M2.7",
249
+ owned_by: "minimax",
250
+ contextWindow: 204800,
251
+ maxTokens: 131072,
252
+ input: TEXT_INPUT,
253
+ cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
254
+ },
255
+ {
256
+ id: "glm-5",
257
+ name: "GLM-5",
258
+ owned_by: "z-ai",
259
+ contextWindow: 202752,
260
+ maxTokens: 131072,
261
+ input: TEXT_INPUT,
262
+ cost: { input: 0.72, output: 2.3, cacheRead: 0, cacheWrite: 0 },
108
263
  },
109
264
  ];
110
265
  /** Fetch live model list from the gateway; falls back to hardcoded catalog. */
@@ -126,6 +281,9 @@ async function resolveModels() {
126
281
  id: m.id,
127
282
  name: m.name ?? fallback?.name ?? m.id,
128
283
  owned_by: m.owned_by,
284
+ contextWindow: m.context_window ?? fallback?.contextWindow ?? 128000,
285
+ maxTokens: m.max_output_tokens ?? fallback?.maxTokens ?? 32768,
286
+ input: m.input_modalities ?? fallback?.input ?? TEXT_INPUT,
129
287
  cost: m.pricing
130
288
  ? {
131
289
  input: m.pricing.input,
@@ -139,6 +297,7 @@ async function resolveModels() {
139
297
  cacheRead: 0,
140
298
  cacheWrite: 0,
141
299
  }),
300
+ ...(m.deprecation && { deprecation: m.deprecation }),
142
301
  };
143
302
  });
144
303
  return { models, live: true };
@@ -184,10 +343,17 @@ export async function modelsCommand() {
184
343
  output.brandBold("Bankr LLM Gateway — Available Models");
185
344
  console.log();
186
345
  const COL = { id: 24, name: 24, provider: 12 };
187
- console.log(` ${output.fmt.brandBold("Model ID".padEnd(COL.id))} ${output.fmt.brandBold("Name".padEnd(COL.name))} ${output.fmt.brandBold("Provider")}`);
188
- console.log(output.fmt.dim(` ${"".repeat(COL.id + COL.name + COL.provider + 2)}`));
346
+ const hasDeprecated = models.some((m) => m.deprecation);
347
+ console.log(` ${output.fmt.brandBold("Model ID".padEnd(COL.id))} ${output.fmt.brandBold("Name".padEnd(COL.name))} ${output.fmt.brandBold("Provider".padEnd(COL.provider))}${hasDeprecated ? ` ${output.fmt.brandBold("Notes")}` : ""}`);
348
+ console.log(output.fmt.dim(` ${"─".repeat(COL.id + COL.name + COL.provider + (hasDeprecated ? 24 : 2))}`));
189
349
  for (const m of models) {
190
- console.log(` ${output.fmt.brand(m.id.padEnd(COL.id))} ${m.name.padEnd(COL.name)} ${output.fmt.dim(m.owned_by)}`);
350
+ let notes = "";
351
+ if (m.deprecation) {
352
+ notes = m.deprecation.replaced_by
353
+ ? `deprecated → ${m.deprecation.replaced_by}`
354
+ : "deprecated";
355
+ }
356
+ console.log(` ${output.fmt.brand(m.id.padEnd(COL.id))} ${m.name.padEnd(COL.name)} ${output.fmt.dim(m.owned_by.padEnd(COL.provider))} ${notes ? output.fmt.dim(notes) : ""}`);
191
357
  }
192
358
  console.log();
193
359
  output.dim(` Gateway: ${getLlmUrl()}`);
@@ -557,6 +723,9 @@ export async function setupOpenclawCommand(opts) {
557
723
  id: m.id,
558
724
  name: m.name,
559
725
  ...(m.owned_by === "anthropic" ? { api: "anthropic-messages" } : {}),
726
+ input: m.input,
727
+ contextWindow: m.contextWindow,
728
+ maxTokens: m.maxTokens,
560
729
  cost: m.cost,
561
730
  })),
562
731
  };
@@ -6,11 +6,16 @@ export declare function loginCommand(opts: {
6
6
  acceptTerms?: boolean;
7
7
  keyName?: string;
8
8
  readWrite?: boolean;
9
+ walletApi?: boolean;
10
+ agentApi?: boolean;
11
+ tokenLaunch?: boolean;
9
12
  llm?: boolean;
10
13
  llmKey?: string;
11
14
  url?: boolean;
12
15
  siwe?: boolean;
13
16
  privateKey?: string;
14
17
  partnerKey?: string;
18
+ allowedIps?: string;
19
+ allowedRecipients?: string;
15
20
  }): Promise<void>;
16
21
  //# sourceMappingURL=login.d.ts.map
@@ -1,5 +1,7 @@
1
1
  import { confirm, input, select } from "@inquirer/prompts";
2
+ import { isIP } from "node:net";
2
3
  import open from "open";
4
+ import { isAddress } from "viem";
3
5
  import { CLI_USER_AGENT, DEFAULT_API_URL, getApiUrl, getConfigPath, readConfig, writeConfig, } from "../lib/config.js";
4
6
  import * as output from "../lib/output.js";
5
7
  const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api";
@@ -218,17 +220,21 @@ async function emailLoginFlow(apiUrl, opts) {
218
220
  if (wallet.solAddress) {
219
221
  output.dim(` SOL: ${wallet.solAddress}`);
220
222
  }
223
+ // Headless mode: when --code is provided, skip all interactive prompts
224
+ const headless = !!opts.code;
221
225
  // Step 3: Accept terms if needed
222
226
  if (!wallet.hasAcceptedTerms) {
223
227
  output.blank();
224
228
  output.dim(" Please review our Terms of Service: https://bankr.bot/terms");
225
229
  output.blank();
226
230
  const accepted = opts.acceptTerms ??
227
- (await confirm({
228
- message: "Do you accept the Terms of Service?",
229
- default: false,
230
- theme: output.bankrTheme,
231
- }));
231
+ (headless
232
+ ? false
233
+ : await confirm({
234
+ message: "Do you accept the Terms of Service?",
235
+ default: false,
236
+ theme: output.bankrTheme,
237
+ }));
232
238
  if (!accepted) {
233
239
  fatal("Terms must be accepted to continue.");
234
240
  }
@@ -246,37 +252,49 @@ async function emailLoginFlow(apiUrl, opts) {
246
252
  output.blank();
247
253
  const defaultKeyName = `CLI-${new Date().toISOString().slice(0, 10)}`;
248
254
  const keyName = opts.keyName ??
249
- (await input({
250
- message: "Key name:",
251
- default: defaultKeyName,
252
- theme: output.bankrTheme,
253
- validate: (v) => v.trim().length >= 3 ? true : "Name must be at least 3 characters.",
254
- }));
255
- const enableWrite = opts.readWrite ??
256
- (await confirm({
257
- message: "Enable write operations?",
258
- default: false,
259
- theme: output.bankrTheme,
260
- }));
261
- if (enableWrite) {
262
- output.dim(" Includes: transactions, transfers, automations, order management");
263
- }
264
- else {
265
- output.dim(" Read-only: portfolio, balances, prices, research");
266
- }
255
+ (headless
256
+ ? defaultKeyName
257
+ : await input({
258
+ message: "Key name:",
259
+ default: defaultKeyName,
260
+ theme: output.bankrTheme,
261
+ validate: (v) => v.trim().length >= 3 ? true : "Name must be at least 3 characters.",
262
+ }));
263
+ // Fine-grained API flags. walletApi and tokenLaunch default to enabled.
264
+ // --read-write only controls readOnly, not which APIs are enabled.
265
+ const enableWallet = opts.walletApi ?? true;
266
+ const enableAgent = opts.agentApi ??
267
+ (headless
268
+ ? false
269
+ : await confirm({
270
+ message: "Enable Agent API? (AI prompts)",
271
+ default: false,
272
+ theme: output.bankrTheme,
273
+ }));
274
+ const enableTokenLaunch = opts.tokenLaunch ?? true;
267
275
  const enableLlm = opts.llm ??
268
- (await confirm({
269
- message: "Enable LLM gateway?",
270
- default: false,
271
- theme: output.bankrTheme,
272
- }));
276
+ (headless
277
+ ? false
278
+ : await confirm({
279
+ message: "Enable LLM gateway?",
280
+ default: false,
281
+ theme: output.bankrTheme,
282
+ }));
283
+ if (opts.readWrite && !enableWallet && !enableAgent) {
284
+ output.warn("--read-write has no effect when both Wallet API and Agent API are disabled");
285
+ }
273
286
  const keySpin = output.spinner("Generating API key...");
274
287
  let apiKeyResult;
275
288
  try {
276
289
  apiKeyResult = await callGenerateApiKey(apiUrl, identityToken, {
277
290
  name: keyName.trim(),
278
- agentApiEnabled: { readOnly: !enableWrite },
291
+ walletApiEnabled: enableWallet,
292
+ agentApiEnabled: enableAgent,
293
+ readOnly: opts.readWrite ? false : undefined,
294
+ tokenLaunchApiEnabled: enableTokenLaunch,
279
295
  llmGatewayEnabled: enableLlm,
296
+ allowedIps: opts.allowedIps,
297
+ allowedRecipients: opts.allowedRecipients,
280
298
  });
281
299
  keySpin.stop();
282
300
  }
@@ -286,10 +304,25 @@ async function emailLoginFlow(apiUrl, opts) {
286
304
  }
287
305
  // Show result
288
306
  output.blank();
289
- const mode = enableWrite ? "read-write" : "read-only";
307
+ const features = [
308
+ enableWallet && "Wallet",
309
+ enableAgent && "Agent",
310
+ enableTokenLaunch && "Token Launch",
311
+ enableLlm && "LLM",
312
+ ].filter(Boolean);
290
313
  output.success(`API key "${apiKeyResult.name}" saved`);
291
- output.dim(` Mode: ${mode}`);
314
+ output.dim(` Features: ${features.length ? features.join(", ") : "none"}`);
292
315
  output.dim(` LLM: ${apiKeyResult.llmGatewayEnabled ? "enabled" : "disabled"}`);
316
+ if (opts.allowedIps) {
317
+ output.dim(` IPs: ${opts.allowedIps.join(", ")}`);
318
+ }
319
+ if (opts.allowedRecipients) {
320
+ const parts = [
321
+ ...(opts.allowedRecipients.evm ?? []),
322
+ ...(opts.allowedRecipients.solana ?? []),
323
+ ];
324
+ output.dim(` Recipients: ${parts.join(", ")}`);
325
+ }
293
326
  output.dim(" Manage keys at bankr.bot/api");
294
327
  return apiKeyResult.apiKey;
295
328
  }
@@ -340,6 +373,11 @@ async function siweLoginFlow(apiUrl, opts) {
340
373
  partnerApiKey: opts.partnerKey,
341
374
  keyName: opts.keyName ?? `SIWE-${new Date().toISOString().slice(0, 10)}`,
342
375
  readOnly: opts.readOnly ?? false,
376
+ walletApiEnabled: opts.walletApi ?? true,
377
+ agentApiEnabled: opts.agentApi ?? false,
378
+ tokenLaunchApiEnabled: opts.tokenLaunch ?? true,
379
+ allowedIps: opts.allowedIps,
380
+ allowedRecipients: opts.allowedRecipients,
343
381
  }),
344
382
  });
345
383
  if (!verifyRes.ok) {
@@ -397,6 +435,49 @@ async function validateApiKey(apiUrl, apiKey) {
397
435
  }
398
436
  }
399
437
  // ── Main login command ──────────────────────────────────────────────
438
+ function parseAndValidateIps(raw) {
439
+ const ips = raw
440
+ .split(",")
441
+ .map((s) => s.trim())
442
+ .filter(Boolean);
443
+ const invalid = ips.filter((ip) => isIP(ip) === 0);
444
+ if (invalid.length > 0) {
445
+ fatal(`Invalid IP address(es): ${invalid.join(", ")}`);
446
+ }
447
+ return ips;
448
+ }
449
+ function parseAndValidateRecipients(raw) {
450
+ const addrs = raw
451
+ .split(",")
452
+ .map((s) => s.trim())
453
+ .filter(Boolean);
454
+ const evm = [];
455
+ const solana = [];
456
+ const invalid = [];
457
+ for (const addr of addrs) {
458
+ if (addr.startsWith("0x")) {
459
+ if (isAddress(addr)) {
460
+ evm.push(addr);
461
+ }
462
+ else {
463
+ invalid.push(addr);
464
+ }
465
+ }
466
+ else {
467
+ // Solana: base58, 32-44 chars
468
+ if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addr)) {
469
+ solana.push(addr);
470
+ }
471
+ else {
472
+ invalid.push(addr);
473
+ }
474
+ }
475
+ }
476
+ if (invalid.length > 0) {
477
+ fatal(`Invalid address(es): ${invalid.join(", ")}`);
478
+ }
479
+ return { evm, solana };
480
+ }
400
481
  export async function loginCommand(opts) {
401
482
  // Fail fast if flags were passed with empty values
402
483
  if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
@@ -444,6 +525,17 @@ export async function loginCommand(opts) {
444
525
  output.success(`Credentials saved to ${getConfigPath()}`);
445
526
  output.dim(`Bankr API URL: ${apiUrl}`);
446
527
  }
528
+ // Lazily parse allowedIps / allowedRecipients only in flows that use them
529
+ function getParsedRestrictions() {
530
+ return {
531
+ allowedIps: opts.allowedIps
532
+ ? parseAndValidateIps(opts.allowedIps)
533
+ : undefined,
534
+ allowedRecipients: opts.allowedRecipients
535
+ ? parseAndValidateRecipients(opts.allowedRecipients)
536
+ : undefined,
537
+ };
538
+ }
447
539
  // --siwe: SIWE login flow (headless agent onboarding)
448
540
  if (opts.siwe) {
449
541
  if (!opts.privateKey) {
@@ -454,6 +546,10 @@ export async function loginCommand(opts) {
454
546
  partnerKey: opts.partnerKey,
455
547
  keyName: opts.keyName,
456
548
  readOnly: !opts.readWrite,
549
+ walletApi: opts.walletApi,
550
+ agentApi: opts.agentApi,
551
+ tokenLaunch: opts.tokenLaunch,
552
+ ...getParsedRestrictions(),
457
553
  });
458
554
  config.apiKey = apiKey;
459
555
  if (opts.partnerKey) {
@@ -492,10 +588,20 @@ export async function loginCommand(opts) {
492
588
  const privyConfig = await fetchPrivyConfigOrFail(apiUrl);
493
589
  await sendOtpWithSpinner(privyConfig, opts.email);
494
590
  output.blank();
495
- output.info(`Complete login with: bankr login email ${opts.email} --code <code>`);
591
+ // Build the hint command, including any flags the user passed so they
592
+ // remember to re-supply them in step 2
593
+ let hint = `bankr login email ${opts.email} --code <code>`;
594
+ if (opts.allowedIps)
595
+ hint += ` --allowed-ips ${opts.allowedIps}`;
596
+ if (opts.allowedRecipients)
597
+ hint += ` --allowed-recipients ${opts.allowedRecipients}`;
598
+ output.info(`Complete login with: ${hint}`);
496
599
  return;
497
600
  }
498
- const apiKey = await emailLoginFlow(apiUrl, opts);
601
+ const apiKey = await emailLoginFlow(apiUrl, {
602
+ ...opts,
603
+ ...getParsedRestrictions(),
604
+ });
499
605
  output.blank();
500
606
  saveCredentials(apiKey, opts.llmKey);
501
607
  return;
@@ -0,0 +1,9 @@
1
+ export declare function portfolioCommand(opts: {
2
+ chain?: string;
3
+ json?: boolean;
4
+ lowValue?: boolean;
5
+ pnl?: boolean;
6
+ nfts?: boolean;
7
+ all?: boolean;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=portfolio.d.ts.map