@bankr/cli 0.1.0-beta.9 → 0.1.3

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 (64) hide show
  1. package/README.md +18 -0
  2. package/dist/cli.js +200 -3
  3. package/dist/commands/balances.d.ts +5 -0
  4. package/dist/commands/balances.js +113 -0
  5. package/dist/commands/fees.d.ts +18 -0
  6. package/dist/commands/fees.js +793 -0
  7. package/dist/commands/launch.d.ts +13 -0
  8. package/dist/commands/launch.js +174 -0
  9. package/dist/commands/llm.d.ts +11 -0
  10. package/dist/commands/llm.js +463 -22
  11. package/dist/commands/login.d.ts +3 -0
  12. package/dist/commands/login.js +94 -4
  13. package/dist/commands/profile.d.ts +26 -0
  14. package/dist/commands/profile.js +183 -0
  15. package/dist/commands/prompt.js +5 -0
  16. package/dist/commands/sign.js +3 -0
  17. package/dist/commands/sounds.d.ts +12 -0
  18. package/dist/commands/sounds.js +262 -0
  19. package/dist/commands/submit.js +14 -4
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +1 -1
  22. package/dist/lib/api.d.ts +213 -0
  23. package/dist/lib/api.js +177 -3
  24. package/dist/lib/cesp/engine.d.ts +13 -0
  25. package/dist/lib/cesp/engine.js +132 -0
  26. package/dist/lib/cesp/player.d.ts +6 -0
  27. package/dist/lib/cesp/player.js +50 -0
  28. package/dist/lib/cesp/types.d.ts +38 -0
  29. package/dist/lib/cesp/types.js +2 -0
  30. package/dist/lib/config.d.ts +4 -0
  31. package/dist/lib/config.js +1 -0
  32. package/package.json +4 -2
  33. package/dist/cli.d.ts.map +0 -1
  34. package/dist/cli.js.map +0 -1
  35. package/dist/commands/cancel.d.ts.map +0 -1
  36. package/dist/commands/cancel.js.map +0 -1
  37. package/dist/commands/capabilities.d.ts.map +0 -1
  38. package/dist/commands/capabilities.js.map +0 -1
  39. package/dist/commands/config.d.ts.map +0 -1
  40. package/dist/commands/config.js.map +0 -1
  41. package/dist/commands/llm.d.ts.map +0 -1
  42. package/dist/commands/llm.js.map +0 -1
  43. package/dist/commands/login.d.ts.map +0 -1
  44. package/dist/commands/login.js.map +0 -1
  45. package/dist/commands/logout.d.ts.map +0 -1
  46. package/dist/commands/logout.js.map +0 -1
  47. package/dist/commands/prompt.d.ts.map +0 -1
  48. package/dist/commands/prompt.js.map +0 -1
  49. package/dist/commands/sign.d.ts.map +0 -1
  50. package/dist/commands/sign.js.map +0 -1
  51. package/dist/commands/status.d.ts.map +0 -1
  52. package/dist/commands/status.js.map +0 -1
  53. package/dist/commands/submit.d.ts.map +0 -1
  54. package/dist/commands/submit.js.map +0 -1
  55. package/dist/commands/whoami.d.ts.map +0 -1
  56. package/dist/commands/whoami.js.map +0 -1
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/lib/api.d.ts.map +0 -1
  60. package/dist/lib/api.js.map +0 -1
  61. package/dist/lib/config.d.ts.map +0 -1
  62. package/dist/lib/config.js.map +0 -1
  63. package/dist/lib/output.d.ts.map +0 -1
  64. package/dist/lib/output.js.map +0 -1
@@ -2,100 +2,228 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { dirname, join } from "path";
4
4
  import { spawn } from "child_process";
5
- import { getLlmKey, getLlmUrl } from "../lib/config.js";
5
+ import { CLI_USER_AGENT, getApiUrl, getLlmKey, getLlmUrl, requireApiKey, } from "../lib/config.js";
6
+ import { getBalances } from "../lib/api.js";
6
7
  import * as output from "../lib/output.js";
8
+ import { bankrTheme } from "../lib/output.js";
9
+ const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api";
7
10
  /**
8
11
  * Fallback model catalog — used when the gateway is unreachable or unauthenticated.
9
12
  * resolveModels() fetches live data from GET /v1/models; this list is the offline safety net.
10
13
  */
14
+ const IMAGE_INPUT = ["text", "image"];
15
+ const TEXT_INPUT = ["text"];
11
16
  const GATEWAY_MODELS = [
12
- // Claude — 90% cache read discount, 25% cache write premium
17
+ // Claude
13
18
  {
14
19
  id: "claude-opus-4.6",
15
20
  name: "Claude Opus 4.6",
16
21
  owned_by: "anthropic",
17
- 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 },
18
26
  },
19
27
  {
20
28
  id: "claude-opus-4.5",
21
29
  name: "Claude Opus 4.5",
22
30
  owned_by: "anthropic",
23
- 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 },
35
+ },
36
+ {
37
+ id: "claude-sonnet-4.6",
38
+ name: "Claude Sonnet 4.6",
39
+ owned_by: "anthropic",
40
+ contextWindow: 1000000,
41
+ maxTokens: 128000,
42
+ input: IMAGE_INPUT,
43
+ cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
24
44
  },
25
45
  {
26
46
  id: "claude-sonnet-4.5",
27
47
  name: "Claude Sonnet 4.5",
28
48
  owned_by: "anthropic",
49
+ contextWindow: 1000000,
50
+ maxTokens: 64000,
51
+ input: IMAGE_INPUT,
29
52
  cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
30
53
  },
31
54
  {
32
55
  id: "claude-haiku-4.5",
33
56
  name: "Claude Haiku 4.5",
34
57
  owned_by: "anthropic",
35
- 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 },
36
81
  },
37
- // Gemini — 75% cache read discount
38
82
  {
39
83
  id: "gemini-3-pro",
40
84
  name: "Gemini 3 Pro",
41
85
  owned_by: "google",
42
- 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 },
43
90
  },
44
91
  {
45
92
  id: "gemini-3-flash",
46
93
  name: "Gemini 3 Flash",
47
94
  owned_by: "google",
48
- 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 },
49
99
  },
50
100
  {
51
101
  id: "gemini-2.5-pro",
52
102
  name: "Gemini 2.5 Pro",
53
103
  owned_by: "google",
54
- 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 },
55
108
  },
56
109
  {
57
110
  id: "gemini-2.5-flash",
58
111
  name: "Gemini 2.5 Flash",
59
112
  owned_by: "google",
60
- 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 },
61
127
  },
62
- // OpenAI — 50% cache read discount
63
128
  {
64
129
  id: "gpt-5.2",
65
130
  name: "GPT 5.2",
66
131
  owned_by: "openai",
67
- cost: { input: 2.5, output: 10.0, cacheRead: 1.25, cacheWrite: 2.5 },
132
+ contextWindow: 400000,
133
+ maxTokens: 128000,
134
+ input: TEXT_INPUT,
135
+ cost: { input: 1.75, output: 14.0, cacheRead: 0.175, cacheWrite: 0 },
68
136
  },
69
137
  {
70
138
  id: "gpt-5.2-codex",
71
139
  name: "GPT 5.2 Codex",
72
140
  owned_by: "openai",
73
- cost: { input: 2.5, output: 10.0, cacheRead: 1.25, cacheWrite: 2.5 },
141
+ contextWindow: 400000,
142
+ maxTokens: 128000,
143
+ input: TEXT_INPUT,
144
+ cost: { input: 1.75, output: 14.0, cacheRead: 0.175, cacheWrite: 0 },
74
145
  },
75
146
  {
76
147
  id: "gpt-5-mini",
77
148
  name: "GPT 5 Mini",
78
149
  owned_by: "openai",
79
- cost: { input: 0.4, output: 1.6, cacheRead: 0.2, cacheWrite: 0.4 },
150
+ contextWindow: 400000,
151
+ maxTokens: 128000,
152
+ input: TEXT_INPUT,
153
+ cost: { input: 0.25, output: 2.0, cacheRead: 0.025, cacheWrite: 0 },
80
154
  },
81
155
  {
82
156
  id: "gpt-5-nano",
83
157
  name: "GPT 5 Nano",
84
158
  owned_by: "openai",
85
- cost: { input: 0.1, output: 0.4, cacheRead: 0.05, cacheWrite: 0.1 },
159
+ contextWindow: 400000,
160
+ maxTokens: 128000,
161
+ input: TEXT_INPUT,
162
+ cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
86
163
  },
87
164
  // Other providers
88
165
  {
89
- id: "kimi-k2.5",
90
- name: "Kimi K2.5",
91
- owned_by: "moonshotai",
92
- cost: { input: 0.6, output: 2.4, cacheRead: 0.09, cacheWrite: 0.6 },
166
+ id: "grok-4.1-fast",
167
+ name: "Grok 4.1 Fast",
168
+ owned_by: "x-ai",
169
+ contextWindow: 2000000,
170
+ maxTokens: 30000,
171
+ input: TEXT_INPUT,
172
+ cost: { input: 0.2, output: 0.5, cacheRead: 0.05, cacheWrite: 0 },
173
+ },
174
+ {
175
+ id: "deepseek-v3.2",
176
+ name: "DeepSeek V3.2",
177
+ owned_by: "deepseek",
178
+ contextWindow: 163840,
179
+ maxTokens: 65536,
180
+ input: TEXT_INPUT,
181
+ cost: { input: 0.26, output: 0.38, cacheRead: 0.13, cacheWrite: 0 },
93
182
  },
94
183
  {
95
184
  id: "qwen3-coder",
96
185
  name: "Qwen3 Coder",
97
186
  owned_by: "qwen",
98
- cost: { input: 0.3, output: 1.2, cacheRead: 0.15, cacheWrite: 0.3 },
187
+ contextWindow: 262144,
188
+ maxTokens: 65536,
189
+ input: TEXT_INPUT,
190
+ cost: { input: 0.12, output: 0.75, cacheRead: 0.06, cacheWrite: 0 },
191
+ },
192
+ {
193
+ id: "qwen3.5-plus",
194
+ name: "Qwen3.5 Plus",
195
+ owned_by: "qwen",
196
+ contextWindow: 1000000,
197
+ maxTokens: 65536,
198
+ input: TEXT_INPUT,
199
+ cost: { input: 0.26, output: 1.56, cacheRead: 0, cacheWrite: 0 },
200
+ },
201
+ {
202
+ id: "qwen3.5-flash",
203
+ name: "Qwen3.5 Flash",
204
+ owned_by: "qwen",
205
+ contextWindow: 1000000,
206
+ maxTokens: 65536,
207
+ input: TEXT_INPUT,
208
+ cost: { input: 0.1, output: 0.4, cacheRead: 0, cacheWrite: 0 },
209
+ },
210
+ {
211
+ id: "kimi-k2.5",
212
+ name: "Kimi K2.5",
213
+ owned_by: "moonshotai",
214
+ contextWindow: 262144,
215
+ maxTokens: 65535,
216
+ input: TEXT_INPUT,
217
+ cost: { input: 0.45, output: 2.2, cacheRead: 0.225, cacheWrite: 0 },
218
+ },
219
+ {
220
+ id: "minimax-m2.5",
221
+ name: "MiniMax M2.5",
222
+ owned_by: "minimax",
223
+ contextWindow: 196608,
224
+ maxTokens: 196608,
225
+ input: TEXT_INPUT,
226
+ cost: { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
99
227
  },
100
228
  ];
101
229
  /** Fetch live model list from the gateway; falls back to hardcoded catalog. */
@@ -105,7 +233,7 @@ async function resolveModels() {
105
233
  return { models: GATEWAY_MODELS, live: false };
106
234
  try {
107
235
  const res = await fetch(`${getLlmUrl()}/v1/models`, {
108
- headers: { "X-API-Key": llmKey },
236
+ headers: { "X-API-Key": llmKey, "User-Agent": CLI_USER_AGENT },
109
237
  signal: AbortSignal.timeout(5000),
110
238
  });
111
239
  if (!res.ok)
@@ -117,6 +245,9 @@ async function resolveModels() {
117
245
  id: m.id,
118
246
  name: m.name ?? fallback?.name ?? m.id,
119
247
  owned_by: m.owned_by,
248
+ contextWindow: m.context_window ?? fallback?.contextWindow ?? 128000,
249
+ maxTokens: m.max_output_tokens ?? fallback?.maxTokens ?? 32768,
250
+ input: m.input_modalities ?? fallback?.input ?? TEXT_INPUT,
120
251
  cost: m.pricing
121
252
  ? {
122
253
  input: m.pricing.input,
@@ -195,7 +326,7 @@ export async function creditsCommand() {
195
326
  const spin = output.spinner("Fetching credit balance…");
196
327
  try {
197
328
  const res = await fetch(`${llmUrl}/v1/credits`, {
198
- headers: { "X-API-Key": llmKey },
329
+ headers: { "X-API-Key": llmKey, "User-Agent": CLI_USER_AGENT },
199
330
  signal: AbortSignal.timeout(10000),
200
331
  });
201
332
  spin.stop();
@@ -228,6 +359,313 @@ export async function creditsCommand() {
228
359
  process.exit(1);
229
360
  }
230
361
  }
362
+ /* ─────────────────────── bankr llm credits add ──────────────────────────── */
363
+ export async function creditsAddCommand(amount, opts) {
364
+ const apiKey = requireApiKey();
365
+ const apiUrl = getApiUrl();
366
+ const llmKey = getLlmKey();
367
+ const llmUrl = getLlmUrl();
368
+ // Validate amount
369
+ const amountUsd = Number(amount);
370
+ if (Number.isNaN(amountUsd) || amountUsd < 1 || amountUsd > 1000) {
371
+ output.error("Amount must be between $1 and $1,000");
372
+ process.exit(1);
373
+ }
374
+ // Fetch current balance for display (skip when --yes since user won't see it)
375
+ let currentBalance = "unknown";
376
+ if (llmKey && !opts.yes) {
377
+ const balSpin = output.spinner("Fetching balance…");
378
+ try {
379
+ const balRes = await fetch(`${llmUrl}/v1/credits`, {
380
+ headers: { "X-API-Key": llmKey, "User-Agent": CLI_USER_AGENT },
381
+ signal: AbortSignal.timeout(10000),
382
+ });
383
+ balSpin.stop();
384
+ if (balRes.ok) {
385
+ const bal = (await balRes.json());
386
+ currentBalance = `$${bal.balanceUsd.toFixed(2)}`;
387
+ }
388
+ else if (balRes.status === 402) {
389
+ currentBalance = "$0.00";
390
+ }
391
+ }
392
+ catch (err) {
393
+ balSpin.stop();
394
+ // Non-fatal — proceed without balance display
395
+ output.dim(` (Could not fetch balance: ${err.message})`);
396
+ }
397
+ }
398
+ // Show confirmation
399
+ console.log();
400
+ output.label("Add Credits", `$${amountUsd.toFixed(2)}`);
401
+ output.keyValue("Source token", opts.token ?? "USDC (default)");
402
+ output.keyValue("Current balance", currentBalance);
403
+ console.log();
404
+ if (!opts.yes) {
405
+ const { confirm } = await import("@inquirer/prompts");
406
+ const proceed = await confirm({
407
+ message: "Proceed?",
408
+ default: true,
409
+ theme: bankrTheme,
410
+ });
411
+ if (!proceed) {
412
+ output.dim("Cancelled.");
413
+ return;
414
+ }
415
+ }
416
+ const spin = output.spinner("Adding credits…");
417
+ try {
418
+ const body = { amountUsd };
419
+ if (opts.token) {
420
+ body.sourceToken = opts.token;
421
+ }
422
+ const res = await fetch(`${apiUrl}/llm/credits/topup`, {
423
+ method: "POST",
424
+ headers: {
425
+ "X-API-Key": apiKey,
426
+ "Content-Type": "application/json",
427
+ "User-Agent": CLI_USER_AGENT,
428
+ },
429
+ body: JSON.stringify(body),
430
+ signal: AbortSignal.timeout(60000),
431
+ });
432
+ spin.stop();
433
+ if (res.status === 402) {
434
+ output.error("Insufficient wallet balance. Add funds to your wallet first.");
435
+ process.exit(1);
436
+ }
437
+ if (res.status === 403) {
438
+ output.error(ERR_LLM_NOT_ENABLED);
439
+ process.exit(1);
440
+ }
441
+ if (res.status === 502) {
442
+ output.error("Token swap failed. Try USDC or a different token.");
443
+ process.exit(1);
444
+ }
445
+ if (!res.ok) {
446
+ const errBody = (await res.json().catch(() => ({})));
447
+ output.error(errBody.error ?? `Failed to add credits (HTTP ${res.status})`);
448
+ process.exit(1);
449
+ }
450
+ const result = (await res.json());
451
+ console.log();
452
+ output.success(`Added $${result.creditsGranted.toFixed(2)} credits`);
453
+ output.label("New Balance", `$${result.newBalance.toFixed(2)}`);
454
+ if (result.txHash) {
455
+ output.keyValue("Transaction", `https://basescan.org/tx/${result.txHash}`);
456
+ }
457
+ }
458
+ catch (err) {
459
+ spin.stop();
460
+ output.error(`Failed to add credits: ${err.message}`);
461
+ process.exit(1);
462
+ }
463
+ }
464
+ function formatTimeAgo(dateStr) {
465
+ if (!dateStr)
466
+ return "never";
467
+ const diff = Date.now() - new Date(dateStr).getTime();
468
+ const mins = Math.floor(diff / 60000);
469
+ if (mins < 60)
470
+ return `${mins}m ago`;
471
+ const hours = Math.floor(mins / 60);
472
+ if (hours < 24)
473
+ return `${hours}h ago`;
474
+ const days = Math.floor(hours / 24);
475
+ return `${days}d ago`;
476
+ }
477
+ function displayAutoTopUpConfig(config) {
478
+ output.keyValue("Status", config.enabled ? "Enabled" : "Disabled");
479
+ output.keyValue("Amount", `$${config.amountUsd.toFixed(2)}`);
480
+ output.keyValue("Threshold", `$${config.thresholdUsd.toFixed(2)}`);
481
+ output.keyValue("Tokens", config.tokens.length > 0
482
+ ? config.tokens.map((t) => t.symbol).join(" → ")
483
+ : "none configured");
484
+ }
485
+ export async function creditsAutoCommand(opts) {
486
+ const apiKey = requireApiKey();
487
+ const apiUrl = getApiUrl();
488
+ const isUpdate = opts.enable || opts.disable || opts.amount || opts.threshold || opts.tokens;
489
+ if (!isUpdate) {
490
+ // GET current config
491
+ const spin = output.spinner("Fetching auto top-up config…");
492
+ try {
493
+ const res = await fetch(`${apiUrl}/llm/credits/auto-topup`, {
494
+ headers: {
495
+ "X-API-Key": apiKey,
496
+ "User-Agent": CLI_USER_AGENT,
497
+ },
498
+ signal: AbortSignal.timeout(10000),
499
+ });
500
+ spin.stop();
501
+ if (res.status === 403) {
502
+ output.error(ERR_LLM_NOT_ENABLED);
503
+ process.exit(1);
504
+ }
505
+ if (!res.ok) {
506
+ output.error(`Failed to fetch config (HTTP ${res.status})`);
507
+ process.exit(1);
508
+ }
509
+ const { config } = (await res.json());
510
+ output.brandBold("Bankr LLM Gateway — Auto Top-Up");
511
+ console.log();
512
+ displayAutoTopUpConfig(config);
513
+ output.keyValue("Last top-up", formatTimeAgo(config.lastTopUpAt));
514
+ if (config.lastError) {
515
+ output.keyValue("Last error", config.lastError.message);
516
+ }
517
+ console.log();
518
+ output.dim(" Update: bankr llm credits auto --amount 25 --tokens USDC");
519
+ }
520
+ catch (err) {
521
+ spin.stop();
522
+ output.error(`Failed to fetch config: ${err.message}`);
523
+ process.exit(1);
524
+ }
525
+ return;
526
+ }
527
+ // POST update config
528
+ if (opts.enable && opts.disable) {
529
+ output.error("Cannot use --enable and --disable together");
530
+ process.exit(1);
531
+ }
532
+ const body = {};
533
+ if (opts.disable) {
534
+ body.enabled = false;
535
+ }
536
+ else if (opts.enable) {
537
+ body.enabled = true;
538
+ }
539
+ else if (opts.amount || opts.threshold || opts.tokens) {
540
+ // Setting config values implicitly enables
541
+ body.enabled = true;
542
+ }
543
+ if (opts.amount) {
544
+ const amt = Number(opts.amount);
545
+ if (Number.isNaN(amt) || amt < 1 || amt > 1000) {
546
+ output.error("Amount must be between $1 and $1,000");
547
+ process.exit(1);
548
+ }
549
+ body.amountUsd = amt;
550
+ }
551
+ if (opts.threshold) {
552
+ const thr = Number(opts.threshold);
553
+ if (Number.isNaN(thr) || thr < 1 || thr > 500) {
554
+ output.error("Threshold must be between $1 and $500");
555
+ process.exit(1);
556
+ }
557
+ body.thresholdUsd = thr;
558
+ }
559
+ const missing = [];
560
+ if (opts.tokens) {
561
+ // Accepts symbols (USDC) or addresses (0x...), comma-separated
562
+ const inputs = opts.tokens
563
+ .split(",")
564
+ .map((s) => s.trim())
565
+ .filter(Boolean);
566
+ if (inputs.length === 0 || inputs.length > 3) {
567
+ output.error("Provide 1-3 comma-separated token symbols or addresses");
568
+ process.exit(1);
569
+ }
570
+ const resolved = [];
571
+ // Fetch balances to resolve both symbols and addresses
572
+ const spin = output.spinner("Resolving tokens…");
573
+ let baseTokens = [];
574
+ let hasNativeEth = false;
575
+ try {
576
+ const balData = await getBalances(["base"]);
577
+ const baseChain = balData.balances.base;
578
+ baseTokens = baseChain?.tokenBalances ?? [];
579
+ hasNativeEth = !!baseChain && parseFloat(baseChain.nativeBalance) > 0;
580
+ }
581
+ catch {
582
+ output.warn("Could not fetch balances for token resolution");
583
+ }
584
+ spin.stop();
585
+ for (const input of inputs) {
586
+ const isAddress = input.startsWith("0x");
587
+ const sym = isAddress ? null : input.toUpperCase();
588
+ // Native ETH
589
+ if (sym === "ETH" && hasNativeEth) {
590
+ resolved.push({
591
+ address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
592
+ chain: "base",
593
+ symbol: "ETH",
594
+ name: "Ethereum",
595
+ decimals: 18,
596
+ });
597
+ continue;
598
+ }
599
+ const tb = baseTokens.find((t) => isAddress
600
+ ? t.token.baseToken.address.toLowerCase() === input.toLowerCase()
601
+ : t.token.baseToken.symbol.toUpperCase() === sym);
602
+ if (tb) {
603
+ resolved.push({
604
+ address: tb.token.baseToken.address,
605
+ chain: "base",
606
+ symbol: tb.token.baseToken.symbol,
607
+ name: tb.token.baseToken.name,
608
+ decimals: tb.token.baseToken.decimals,
609
+ imageUrl: tb.token.baseToken.imgUrl,
610
+ });
611
+ }
612
+ else {
613
+ missing.push(input);
614
+ }
615
+ }
616
+ if (resolved.length === 0) {
617
+ output.error("No valid tokens resolved. Check your token symbols.");
618
+ process.exit(1);
619
+ }
620
+ body.tokens = resolved;
621
+ }
622
+ // Collect warnings to show after success output
623
+ const warnings = [];
624
+ if (missing.length > 0) {
625
+ warnings.push(`Token(s) not found in wallet: ${missing.join(", ")} — skipped`);
626
+ }
627
+ const spin = output.spinner("Updating auto top-up…");
628
+ try {
629
+ const res = await fetch(`${apiUrl}/llm/credits/auto-topup`, {
630
+ method: "POST",
631
+ headers: {
632
+ "X-API-Key": apiKey,
633
+ "Content-Type": "application/json",
634
+ "User-Agent": CLI_USER_AGENT,
635
+ },
636
+ body: JSON.stringify(body),
637
+ signal: AbortSignal.timeout(10000),
638
+ });
639
+ spin.stop();
640
+ if (res.status === 403) {
641
+ output.error(ERR_LLM_NOT_ENABLED);
642
+ process.exit(1);
643
+ }
644
+ if (!res.ok) {
645
+ const errBody = (await res.json().catch(() => ({})));
646
+ output.error(errBody.error ?? `Failed to update config (HTTP ${res.status})`);
647
+ process.exit(1);
648
+ }
649
+ output.success(opts.disable ? "Auto top-up disabled" : "Auto top-up settings updated");
650
+ // Show config from POST response (avoids extra GET round-trip)
651
+ const resBody = (await res.json());
652
+ if (resBody.config) {
653
+ console.log();
654
+ displayAutoTopUpConfig(resBody.config);
655
+ }
656
+ if (warnings.length > 0) {
657
+ console.log();
658
+ for (const w of warnings) {
659
+ output.warn(w);
660
+ }
661
+ }
662
+ }
663
+ catch (err) {
664
+ spin.stop();
665
+ output.error(`Failed to update config: ${err.message}`);
666
+ process.exit(1);
667
+ }
668
+ }
231
669
  /* ──────────────────────── bankr llm setup openclaw ────────────────────────── */
232
670
  export async function setupOpenclawCommand(opts) {
233
671
  const llmKey = getLlmKey();
@@ -241,6 +679,9 @@ export async function setupOpenclawCommand(opts) {
241
679
  id: m.id,
242
680
  name: m.name,
243
681
  ...(m.owned_by === "anthropic" ? { api: "anthropic-messages" } : {}),
682
+ input: m.input,
683
+ contextWindow: m.contextWindow,
684
+ maxTokens: m.maxTokens,
244
685
  cost: m.cost,
245
686
  })),
246
687
  };
@@ -9,5 +9,8 @@ export declare function loginCommand(opts: {
9
9
  llm?: boolean;
10
10
  llmKey?: string;
11
11
  url?: boolean;
12
+ siwe?: boolean;
13
+ privateKey?: string;
14
+ partnerKey?: string;
12
15
  }): Promise<void>;
13
16
  //# sourceMappingURL=login.d.ts.map