@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.
- package/README.md +18 -0
- package/dist/cli.js +200 -3
- package/dist/commands/balances.d.ts +5 -0
- package/dist/commands/balances.js +113 -0
- package/dist/commands/fees.d.ts +18 -0
- package/dist/commands/fees.js +793 -0
- package/dist/commands/launch.d.ts +13 -0
- package/dist/commands/launch.js +174 -0
- package/dist/commands/llm.d.ts +11 -0
- package/dist/commands/llm.js +463 -22
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +94 -4
- package/dist/commands/profile.d.ts +26 -0
- package/dist/commands/profile.js +183 -0
- package/dist/commands/prompt.js +5 -0
- package/dist/commands/sign.js +3 -0
- package/dist/commands/sounds.d.ts +12 -0
- package/dist/commands/sounds.js +262 -0
- package/dist/commands/submit.js +14 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lib/api.d.ts +213 -0
- package/dist/lib/api.js +177 -3
- package/dist/lib/cesp/engine.d.ts +13 -0
- package/dist/lib/cesp/engine.js +132 -0
- package/dist/lib/cesp/player.d.ts +6 -0
- package/dist/lib/cesp/player.js +50 -0
- package/dist/lib/cesp/types.d.ts +38 -0
- package/dist/lib/cesp/types.js +2 -0
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.js +1 -0
- package/package.json +4 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/cancel.d.ts.map +0 -1
- package/dist/commands/cancel.js.map +0 -1
- package/dist/commands/capabilities.d.ts.map +0 -1
- package/dist/commands/capabilities.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/llm.d.ts.map +0 -1
- package/dist/commands/llm.js.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/prompt.d.ts.map +0 -1
- package/dist/commands/prompt.js.map +0 -1
- package/dist/commands/sign.d.ts.map +0 -1
- package/dist/commands/sign.js.map +0 -1
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/submit.d.ts.map +0 -1
- package/dist/commands/submit.js.map +0 -1
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/api.d.ts.map +0 -1
- package/dist/lib/api.js.map +0 -1
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/output.d.ts.map +0 -1
- package/dist/lib/output.js.map +0 -1
package/dist/commands/llm.js
CHANGED
|
@@ -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
|
|
17
|
+
// Claude
|
|
13
18
|
{
|
|
14
19
|
id: "claude-opus-4.6",
|
|
15
20
|
name: "Claude Opus 4.6",
|
|
16
21
|
owned_by: "anthropic",
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
90
|
-
name: "
|
|
91
|
-
owned_by: "
|
|
92
|
-
|
|
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
|
-
|
|
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
|
};
|
package/dist/commands/login.d.ts
CHANGED