@bankr/cli 0.3.1 → 0.3.11
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 +7 -7
- package/dist/cli.js +146 -83
- package/dist/commands/club.d.ts +1 -0
- package/dist/commands/club.js +47 -5
- package/dist/commands/fees.js +10 -2
- package/dist/commands/launch.js +16 -23
- package/dist/commands/llm.d.ts +3 -1
- package/dist/commands/llm.js +240 -63
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +28 -23
- package/dist/commands/profile.d.ts +5 -1
- package/dist/commands/profile.js +54 -10
- package/dist/commands/prompt.js +6 -0
- package/dist/commands/swap.d.ts +8 -0
- package/dist/commands/swap.js +107 -0
- package/dist/commands/transfer.js +38 -25
- package/dist/commands/webhooks.js +2 -7
- package/dist/commands/x402.js +57 -41
- package/dist/lib/api.d.ts +50 -3
- package/dist/lib/api.js +41 -16
- package/dist/lib/config.d.ts +0 -1
- package/dist/lib/output.d.ts +1 -0
- package/dist/lib/output.js +3 -0
- package/dist/lib/tokens.d.ts +21 -0
- package/dist/lib/tokens.js +40 -0
- package/package.json +6 -6
package/dist/commands/launch.js
CHANGED
|
@@ -25,7 +25,16 @@ function feeTypeLabel(type) {
|
|
|
25
25
|
return "Wallet";
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
async function promptOrSkip(message) {
|
|
29
|
+
if (output.isNonInteractive())
|
|
30
|
+
return "";
|
|
31
|
+
return input({ message, theme: output.bankrTheme });
|
|
32
|
+
}
|
|
28
33
|
async function collectInteractive(options) {
|
|
34
|
+
if (output.isNonInteractive() && !options.name) {
|
|
35
|
+
output.error("--name is required in non-interactive mode");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
29
38
|
const tokenName = options.name ??
|
|
30
39
|
(await input({
|
|
31
40
|
message: "What do you want to call your token?",
|
|
@@ -33,32 +42,17 @@ async function collectInteractive(options) {
|
|
|
33
42
|
required: true,
|
|
34
43
|
}));
|
|
35
44
|
const tokenSymbol = options.symbol ??
|
|
36
|
-
(await
|
|
37
|
-
message: "Token symbol (press Enter to auto-generate from name):",
|
|
38
|
-
theme: output.bankrTheme,
|
|
39
|
-
}));
|
|
45
|
+
(await promptOrSkip("Token symbol (press Enter to auto-generate from name):"));
|
|
40
46
|
const imageUrl = options.image ??
|
|
41
|
-
(await
|
|
42
|
-
message: "Image URL for your token (press Enter to skip):",
|
|
43
|
-
theme: output.bankrTheme,
|
|
44
|
-
}));
|
|
47
|
+
(await promptOrSkip("Image URL for your token (press Enter to skip):"));
|
|
45
48
|
const tweetUrl = options.tweet ??
|
|
46
|
-
(await
|
|
47
|
-
message: "Associate with a tweet? Paste URL (press Enter to skip):",
|
|
48
|
-
theme: output.bankrTheme,
|
|
49
|
-
}));
|
|
49
|
+
(await promptOrSkip("Associate with a tweet? Paste URL (press Enter to skip):"));
|
|
50
50
|
const websiteUrl = options.website ??
|
|
51
|
-
(await
|
|
52
|
-
message: "Project website URL (press Enter to skip):",
|
|
53
|
-
theme: output.bankrTheme,
|
|
54
|
-
}));
|
|
51
|
+
(await promptOrSkip("Project website URL (press Enter to skip):"));
|
|
55
52
|
let feeRecipient = options.fee ??
|
|
56
|
-
(await
|
|
57
|
-
message: "Direct fees to someone? Enter username, ENS, or address (press Enter to skip):",
|
|
58
|
-
theme: output.bankrTheme,
|
|
59
|
-
}));
|
|
53
|
+
(await promptOrSkip("Direct fees to someone? Enter username, ENS, or address (press Enter to skip):"));
|
|
60
54
|
let feeRecipientType = parseFeeType(options.feeType);
|
|
61
|
-
if (feeRecipient && !options.feeType) {
|
|
55
|
+
if (feeRecipient && !options.feeType && !output.isNonInteractive()) {
|
|
62
56
|
feeRecipientType = await select({
|
|
63
57
|
message: "What type of identifier is this?",
|
|
64
58
|
choices: [
|
|
@@ -108,8 +102,7 @@ export async function launchCommand(options) {
|
|
|
108
102
|
output.keyValue("Mode", "Simulation (dry run)");
|
|
109
103
|
}
|
|
110
104
|
console.log();
|
|
111
|
-
|
|
112
|
-
if (!options.yes) {
|
|
105
|
+
if (!options.yes && !output.isNonInteractive()) {
|
|
113
106
|
const proceed = await confirm({
|
|
114
107
|
message: options.simulate
|
|
115
108
|
? "Simulate this token launch?"
|
package/dist/commands/llm.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export declare function modelsCommand(
|
|
1
|
+
export declare function modelsCommand(opts?: {
|
|
2
|
+
private?: boolean;
|
|
3
|
+
}): Promise<void>;
|
|
2
4
|
export declare function creditsCommand(): Promise<void>;
|
|
3
5
|
export declare function creditsAddCommand(amount: string, opts: {
|
|
4
6
|
token?: string;
|
package/dist/commands/llm.js
CHANGED
|
@@ -5,8 +5,7 @@ import { spawn } from "child_process";
|
|
|
5
5
|
import { CLI_USER_AGENT, getApiUrl, getLlmKey, getLlmUrl, requireApiKey, } from "../lib/config.js";
|
|
6
6
|
import { getBalances } from "../lib/api.js";
|
|
7
7
|
import * as output from "../lib/output.js";
|
|
8
|
-
|
|
9
|
-
const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api";
|
|
8
|
+
const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api-keys";
|
|
10
9
|
/**
|
|
11
10
|
* Fallback model catalog — used when the gateway is unreachable or unauthenticated.
|
|
12
11
|
* resolveModels() fetches live data from GET /v1/models; this list is the offline safety net.
|
|
@@ -15,6 +14,24 @@ const IMAGE_INPUT = ["text", "image"];
|
|
|
15
14
|
const TEXT_INPUT = ["text"];
|
|
16
15
|
const GATEWAY_MODELS = [
|
|
17
16
|
// Claude
|
|
17
|
+
{
|
|
18
|
+
id: "claude-opus-4.8",
|
|
19
|
+
name: "Claude Opus 4.8",
|
|
20
|
+
owned_by: "anthropic",
|
|
21
|
+
contextWindow: 1000000,
|
|
22
|
+
maxTokens: 128000,
|
|
23
|
+
input: IMAGE_INPUT,
|
|
24
|
+
cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "claude-opus-4.7",
|
|
28
|
+
name: "Claude Opus 4.7",
|
|
29
|
+
owned_by: "anthropic",
|
|
30
|
+
contextWindow: 1000000,
|
|
31
|
+
maxTokens: 128000,
|
|
32
|
+
input: IMAGE_INPUT,
|
|
33
|
+
cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
34
|
+
},
|
|
18
35
|
{
|
|
19
36
|
id: "claude-opus-4.6",
|
|
20
37
|
name: "Claude Opus 4.6",
|
|
@@ -171,6 +188,15 @@ const GATEWAY_MODELS = [
|
|
|
171
188
|
cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
|
|
172
189
|
},
|
|
173
190
|
// Other providers
|
|
191
|
+
{
|
|
192
|
+
id: "grok-4.3",
|
|
193
|
+
name: "Grok 4.3",
|
|
194
|
+
owned_by: "x-ai",
|
|
195
|
+
contextWindow: 1000000,
|
|
196
|
+
maxTokens: 1000000,
|
|
197
|
+
input: IMAGE_INPUT,
|
|
198
|
+
cost: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
|
|
199
|
+
},
|
|
174
200
|
{
|
|
175
201
|
id: "grok-4.1-fast",
|
|
176
202
|
name: "Grok 4.1 Fast",
|
|
@@ -198,6 +224,24 @@ const GATEWAY_MODELS = [
|
|
|
198
224
|
input: TEXT_INPUT,
|
|
199
225
|
cost: { input: 0.12, output: 0.75, cacheRead: 0.06, cacheWrite: 0 },
|
|
200
226
|
},
|
|
227
|
+
{
|
|
228
|
+
id: "qwen3.7-plus",
|
|
229
|
+
name: "Qwen3.7 Plus",
|
|
230
|
+
owned_by: "qwen",
|
|
231
|
+
contextWindow: 1000000,
|
|
232
|
+
maxTokens: 65536,
|
|
233
|
+
input: TEXT_INPUT,
|
|
234
|
+
cost: { input: 0.4, output: 1.6, cacheRead: 0.04, cacheWrite: 0 },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "qwen3.6-flash",
|
|
238
|
+
name: "Qwen3.6 Flash",
|
|
239
|
+
owned_by: "qwen",
|
|
240
|
+
contextWindow: 1000000,
|
|
241
|
+
maxTokens: 65536,
|
|
242
|
+
input: TEXT_INPUT,
|
|
243
|
+
cost: { input: 0.25, output: 1.5, cacheRead: 0, cacheWrite: 0 },
|
|
244
|
+
},
|
|
201
245
|
{
|
|
202
246
|
id: "qwen3.5-plus",
|
|
203
247
|
name: "Qwen3.5 Plus",
|
|
@@ -216,6 +260,15 @@ const GATEWAY_MODELS = [
|
|
|
216
260
|
input: TEXT_INPUT,
|
|
217
261
|
cost: { input: 0.1, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
|
218
262
|
},
|
|
263
|
+
{
|
|
264
|
+
id: "kimi-k2.7-code",
|
|
265
|
+
name: "Kimi K2.7 Code",
|
|
266
|
+
owned_by: "moonshotai",
|
|
267
|
+
contextWindow: 262144,
|
|
268
|
+
maxTokens: 262144,
|
|
269
|
+
input: TEXT_INPUT,
|
|
270
|
+
cost: { input: 0.95, output: 4.0, cacheRead: 0.19, cacheWrite: 0 },
|
|
271
|
+
},
|
|
219
272
|
{
|
|
220
273
|
id: "kimi-k2.6",
|
|
221
274
|
name: "Kimi K2.6",
|
|
@@ -316,6 +369,7 @@ async function resolveModels() {
|
|
|
316
369
|
cacheWrite: 0,
|
|
317
370
|
}),
|
|
318
371
|
...(m.deprecation && { deprecation: m.deprecation }),
|
|
372
|
+
...(m.private && { private: true }),
|
|
319
373
|
};
|
|
320
374
|
});
|
|
321
375
|
return { models, live: true };
|
|
@@ -354,28 +408,46 @@ function installConfigFile(configFile, toolName, merge) {
|
|
|
354
408
|
output.success(`Bankr provider installed to ${configFile}`);
|
|
355
409
|
}
|
|
356
410
|
/* ───────────────────────────── bankr llm models ───────────────────────────── */
|
|
357
|
-
export async function modelsCommand() {
|
|
411
|
+
export async function modelsCommand(opts = {}) {
|
|
358
412
|
const spin = output.spinner("Fetching models…");
|
|
359
413
|
const { models, live } = await resolveModels();
|
|
360
414
|
spin.stop();
|
|
361
|
-
|
|
415
|
+
// `private` is the gateway's own "supports :private" flag from
|
|
416
|
+
// /v1/models (driven by each model's NEAR slot + NEARAI_API_KEY), so this
|
|
417
|
+
// tracks the live registry rather than a hardcoded CLI list.
|
|
418
|
+
const shown = opts.private ? models.filter((m) => m.private) : models;
|
|
419
|
+
output.brandBold(opts.private
|
|
420
|
+
? "Bankr LLM Gateway — Private Models"
|
|
421
|
+
: "Bankr LLM Gateway — Available Models");
|
|
362
422
|
console.log();
|
|
423
|
+
if (opts.private && shown.length === 0) {
|
|
424
|
+
output.warn(live
|
|
425
|
+
? "No models currently offer a private (TEE) compute environment."
|
|
426
|
+
: "Private status needs a live gateway — set BANKR_LLM_KEY (and BANKR_LLM_URL).");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
363
429
|
const COL = { id: 24, name: 24, provider: 12 };
|
|
364
|
-
const
|
|
365
|
-
console.log(` ${output.fmt.brandBold("Model ID".padEnd(COL.id))} ${output.fmt.brandBold("Name".padEnd(COL.name))} ${output.fmt.brandBold("Provider".padEnd(COL.provider))}${
|
|
366
|
-
console.log(output.fmt.dim(` ${"─".repeat(COL.id + COL.name + COL.provider + (
|
|
367
|
-
for (const m of
|
|
430
|
+
const hasNotes = shown.some((m) => m.deprecation || m.private);
|
|
431
|
+
console.log(` ${output.fmt.brandBold("Model ID".padEnd(COL.id))} ${output.fmt.brandBold("Name".padEnd(COL.name))} ${output.fmt.brandBold("Provider".padEnd(COL.provider))}${hasNotes ? ` ${output.fmt.brandBold("Notes")}` : ""}`);
|
|
432
|
+
console.log(output.fmt.dim(` ${"─".repeat(COL.id + COL.name + COL.provider + (hasNotes ? 24 : 2))}`));
|
|
433
|
+
for (const m of shown) {
|
|
368
434
|
let notes = "";
|
|
369
435
|
if (m.deprecation) {
|
|
370
436
|
notes = m.deprecation.replaced_by
|
|
371
437
|
? `deprecated → ${m.deprecation.replaced_by}`
|
|
372
438
|
: "deprecated";
|
|
373
439
|
}
|
|
440
|
+
else if (m.private) {
|
|
441
|
+
notes = "Private (TEE)";
|
|
442
|
+
}
|
|
374
443
|
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) : ""}`);
|
|
375
444
|
}
|
|
376
445
|
console.log();
|
|
377
446
|
output.dim(` Gateway: ${getLlmUrl()}`);
|
|
378
|
-
output.dim(` ${
|
|
447
|
+
output.dim(` ${shown.length} model${shown.length === 1 ? "" : "s"}${opts.private ? " support :private" : " available"}${live ? "" : " (cached)"}`);
|
|
448
|
+
if (!opts.private && shown.some((m) => m.private)) {
|
|
449
|
+
output.dim(" Private (TEE) — append :private to the model id (e.g. glm-5.2:private). List: bankr llm models --private");
|
|
450
|
+
}
|
|
379
451
|
}
|
|
380
452
|
/* ─────────────────────── bankr llm credits ─────────────────────────────────── */
|
|
381
453
|
export async function creditsCommand() {
|
|
@@ -422,6 +494,89 @@ export async function creditsCommand() {
|
|
|
422
494
|
}
|
|
423
495
|
}
|
|
424
496
|
/* ─────────────────────── bankr llm credits add ──────────────────────────── */
|
|
497
|
+
// Keep in sync with packages/shared/src/chain-config-base/chainConfigBase.ts.
|
|
498
|
+
// CLI is standalone-published and cannot depend on @bankr/shared.
|
|
499
|
+
const TOPUP_CHAINS = ["base", "polygon", "mainnet", "arbitrum", "bnb"];
|
|
500
|
+
const CHAIN_DISPLAY_NAME = {
|
|
501
|
+
base: "Base",
|
|
502
|
+
polygon: "Polygon",
|
|
503
|
+
mainnet: "Ethereum",
|
|
504
|
+
arbitrum: "Arbitrum",
|
|
505
|
+
bnb: "BNB Chain",
|
|
506
|
+
};
|
|
507
|
+
// Polygon's native POL is at a precompile, not the 0xeee placeholder.
|
|
508
|
+
const NATIVE_TOKEN_ADDRESS_BY_CHAIN = {
|
|
509
|
+
base: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
510
|
+
polygon: "0x0000000000000000000000000000000000001010",
|
|
511
|
+
mainnet: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
512
|
+
arbitrum: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
513
|
+
bnb: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
514
|
+
};
|
|
515
|
+
const BLOCK_EXPLORER_URL = {
|
|
516
|
+
base: "https://basescan.org",
|
|
517
|
+
polygon: "https://polygonscan.com",
|
|
518
|
+
mainnet: "https://etherscan.io",
|
|
519
|
+
arbitrum: "https://arbiscan.io",
|
|
520
|
+
bnb: "https://bscscan.com",
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
* Resolve user input (symbol or 0x address) to the (chain, token) it should
|
|
524
|
+
* pay with. Scans every supported chain and picks the highest USD balance,
|
|
525
|
+
* so the user never has to think about chains. Returns null if nothing matches.
|
|
526
|
+
*/
|
|
527
|
+
function resolveTokenAcrossChains(input, balances) {
|
|
528
|
+
const isAddress = input.startsWith("0x");
|
|
529
|
+
const sym = isAddress ? null : input.toUpperCase();
|
|
530
|
+
const candidates = [];
|
|
531
|
+
for (const chain of TOPUP_CHAINS) {
|
|
532
|
+
const chainBal = balances[chain];
|
|
533
|
+
if (!chainBal)
|
|
534
|
+
continue;
|
|
535
|
+
if (!isAddress) {
|
|
536
|
+
const nativeUsd = parseFloat(chainBal.nativeUsd || "0");
|
|
537
|
+
const nativeSym = nativeSymbolFor(chain);
|
|
538
|
+
if (sym === nativeSym && nativeUsd > 0) {
|
|
539
|
+
candidates.push({
|
|
540
|
+
chain,
|
|
541
|
+
address: NATIVE_TOKEN_ADDRESS_BY_CHAIN[chain],
|
|
542
|
+
symbol: nativeSym,
|
|
543
|
+
name: nativeSym,
|
|
544
|
+
decimals: 18,
|
|
545
|
+
balance: parseFloat(chainBal.nativeBalance || "0"),
|
|
546
|
+
balanceUsd: nativeUsd,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
for (const tb of chainBal.tokenBalances) {
|
|
551
|
+
const matchesSym = sym != null && tb.token.baseToken.symbol.toUpperCase() === sym;
|
|
552
|
+
const matchesAddr = isAddress &&
|
|
553
|
+
tb.token.baseToken.address.toLowerCase() === input.toLowerCase();
|
|
554
|
+
if (!matchesSym && !matchesAddr)
|
|
555
|
+
continue;
|
|
556
|
+
candidates.push({
|
|
557
|
+
chain,
|
|
558
|
+
address: tb.token.baseToken.address,
|
|
559
|
+
symbol: tb.token.baseToken.symbol,
|
|
560
|
+
name: tb.token.baseToken.name,
|
|
561
|
+
decimals: tb.token.baseToken.decimals,
|
|
562
|
+
balance: tb.token.balance,
|
|
563
|
+
balanceUsd: tb.token.balanceUSD,
|
|
564
|
+
imageUrl: tb.token.baseToken.imgUrl,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (candidates.length === 0)
|
|
569
|
+
return null;
|
|
570
|
+
candidates.sort((a, b) => b.balanceUsd - a.balanceUsd);
|
|
571
|
+
return candidates[0];
|
|
572
|
+
}
|
|
573
|
+
function nativeSymbolFor(chain) {
|
|
574
|
+
if (chain === "polygon")
|
|
575
|
+
return "POL";
|
|
576
|
+
if (chain === "bnb")
|
|
577
|
+
return "BNB";
|
|
578
|
+
return "ETH";
|
|
579
|
+
}
|
|
425
580
|
export async function creditsAddCommand(amount, opts) {
|
|
426
581
|
const apiKey = requireApiKey();
|
|
427
582
|
const apiUrl = getApiUrl();
|
|
@@ -433,9 +588,9 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
433
588
|
output.error("Amount must be between $1 and $1,000");
|
|
434
589
|
process.exit(1);
|
|
435
590
|
}
|
|
436
|
-
// Fetch current balance for display (skip when --yes since user won't see it)
|
|
591
|
+
// Fetch current balance for display (skip when --yes/--not-interactive since user won't see it)
|
|
437
592
|
let currentBalance = "unknown";
|
|
438
|
-
if (llmKey && !opts.yes) {
|
|
593
|
+
if (llmKey && !opts.yes && !output.isNonInteractive()) {
|
|
439
594
|
const balSpin = output.spinner("Fetching balance…");
|
|
440
595
|
try {
|
|
441
596
|
const balRes = await fetch(`${llmUrl}/v1/credits`, {
|
|
@@ -457,18 +612,41 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
457
612
|
output.dim(` (Could not fetch balance: ${err.message})`);
|
|
458
613
|
}
|
|
459
614
|
}
|
|
615
|
+
// When --token is passed, the chain holding the highest USD balance of
|
|
616
|
+
// that symbol/address wins. Default (no --token) is Base USDC.
|
|
617
|
+
let resolved = null;
|
|
618
|
+
if (opts.token) {
|
|
619
|
+
const resolveSpin = output.spinner("Resolving token…");
|
|
620
|
+
try {
|
|
621
|
+
const balData = await getBalances([...TOPUP_CHAINS]);
|
|
622
|
+
resolved = resolveTokenAcrossChains(opts.token, balData.balances);
|
|
623
|
+
resolveSpin.stop();
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
resolveSpin.stop();
|
|
627
|
+
output.warn(`Could not fetch balances: ${err.message}`);
|
|
628
|
+
}
|
|
629
|
+
if (!resolved) {
|
|
630
|
+
output.error(`Token "${opts.token}" not found in your wallet on any supported chain.`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const targetChain = resolved?.chain ?? "base";
|
|
635
|
+
const sourceTokenLabel = resolved
|
|
636
|
+
? `${resolved.symbol} on ${CHAIN_DISPLAY_NAME[targetChain]}`
|
|
637
|
+
: `USDC on ${CHAIN_DISPLAY_NAME.base} (default)`;
|
|
460
638
|
// Show confirmation
|
|
461
639
|
console.log();
|
|
462
640
|
output.label("Add Credits", `$${amountUsd.toFixed(2)}`);
|
|
463
|
-
output.keyValue("
|
|
641
|
+
output.keyValue("Pay with", sourceTokenLabel);
|
|
464
642
|
output.keyValue("Current balance", currentBalance);
|
|
465
643
|
console.log();
|
|
466
|
-
if (!opts.yes) {
|
|
644
|
+
if (!opts.yes && !output.isNonInteractive()) {
|
|
467
645
|
const { confirm } = await import("@inquirer/prompts");
|
|
468
646
|
const proceed = await confirm({
|
|
469
647
|
message: "Proceed?",
|
|
470
648
|
default: true,
|
|
471
|
-
theme: bankrTheme,
|
|
649
|
+
theme: output.bankrTheme,
|
|
472
650
|
});
|
|
473
651
|
if (!proceed) {
|
|
474
652
|
output.dim("Cancelled.");
|
|
@@ -477,9 +655,12 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
477
655
|
}
|
|
478
656
|
const spin = output.spinner("Adding credits…");
|
|
479
657
|
try {
|
|
480
|
-
const body = {
|
|
481
|
-
|
|
482
|
-
|
|
658
|
+
const body = {
|
|
659
|
+
amountUsd,
|
|
660
|
+
chain: targetChain,
|
|
661
|
+
};
|
|
662
|
+
if (resolved) {
|
|
663
|
+
body.sourceToken = resolved.address;
|
|
483
664
|
}
|
|
484
665
|
const res = await fetch(`${apiUrl}/llm/credits/topup`, {
|
|
485
666
|
method: "POST",
|
|
@@ -516,7 +697,7 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
516
697
|
output.success(`Added $${result.creditsGranted.toFixed(2)} credits`);
|
|
517
698
|
output.label("New Balance", `$${result.newBalance.toFixed(2)}`);
|
|
518
699
|
if (result.txHash) {
|
|
519
|
-
output.keyValue("Transaction",
|
|
700
|
+
output.keyValue("Transaction", `${BLOCK_EXPLORER_URL[targetChain]}/tx/${result.txHash}`);
|
|
520
701
|
}
|
|
521
702
|
}
|
|
522
703
|
catch (err) {
|
|
@@ -632,45 +813,27 @@ export async function creditsAutoCommand(opts) {
|
|
|
632
813
|
process.exit(1);
|
|
633
814
|
}
|
|
634
815
|
const resolved = [];
|
|
635
|
-
//
|
|
816
|
+
// Highest USD balance wins when the same symbol exists on multiple chains.
|
|
636
817
|
const spin = output.spinner("Resolving tokens…");
|
|
637
|
-
let
|
|
638
|
-
let hasNativeEth = false;
|
|
818
|
+
let balances = {};
|
|
639
819
|
try {
|
|
640
|
-
const balData = await getBalances([
|
|
641
|
-
|
|
642
|
-
baseTokens = baseChain?.tokenBalances ?? [];
|
|
643
|
-
hasNativeEth = !!baseChain && parseFloat(baseChain.nativeBalance) > 0;
|
|
820
|
+
const balData = await getBalances([...TOPUP_CHAINS]);
|
|
821
|
+
balances = balData.balances;
|
|
644
822
|
}
|
|
645
823
|
catch {
|
|
646
824
|
output.warn("Could not fetch balances for token resolution");
|
|
647
825
|
}
|
|
648
826
|
spin.stop();
|
|
649
827
|
for (const input of inputs) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
// Native ETH
|
|
653
|
-
if (sym === "ETH" && hasNativeEth) {
|
|
828
|
+
const hit = resolveTokenAcrossChains(input, balances);
|
|
829
|
+
if (hit) {
|
|
654
830
|
resolved.push({
|
|
655
|
-
address:
|
|
656
|
-
chain:
|
|
657
|
-
symbol:
|
|
658
|
-
name:
|
|
659
|
-
decimals:
|
|
660
|
-
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
const tb = baseTokens.find((t) => isAddress
|
|
664
|
-
? t.token.baseToken.address.toLowerCase() === input.toLowerCase()
|
|
665
|
-
: t.token.baseToken.symbol.toUpperCase() === sym);
|
|
666
|
-
if (tb) {
|
|
667
|
-
resolved.push({
|
|
668
|
-
address: tb.token.baseToken.address,
|
|
669
|
-
chain: "base",
|
|
670
|
-
symbol: tb.token.baseToken.symbol,
|
|
671
|
-
name: tb.token.baseToken.name,
|
|
672
|
-
decimals: tb.token.baseToken.decimals,
|
|
673
|
-
imageUrl: tb.token.baseToken.imgUrl,
|
|
831
|
+
address: hit.address,
|
|
832
|
+
chain: hit.chain,
|
|
833
|
+
symbol: hit.symbol,
|
|
834
|
+
name: hit.name,
|
|
835
|
+
decimals: hit.decimals,
|
|
836
|
+
imageUrl: hit.imageUrl,
|
|
674
837
|
});
|
|
675
838
|
}
|
|
676
839
|
else {
|
|
@@ -740,15 +903,25 @@ export async function setupOpenclawCommand(opts) {
|
|
|
740
903
|
baseUrl: llmUrl,
|
|
741
904
|
apiKey: llmKey ?? "${BANKR_API_KEY}",
|
|
742
905
|
api: "openai-completions",
|
|
743
|
-
models:
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
906
|
+
// Private models get both entries: the plain id and a `:private`
|
|
907
|
+
// twin that routes through the TEE compute environment — so users can pick either.
|
|
908
|
+
models: activeModels.flatMap((m) => {
|
|
909
|
+
const base = {
|
|
910
|
+
id: m.id,
|
|
911
|
+
name: m.name,
|
|
912
|
+
...(m.owned_by === "anthropic" ? { api: "anthropic-messages" } : {}),
|
|
913
|
+
input: m.input,
|
|
914
|
+
contextWindow: m.contextWindow,
|
|
915
|
+
maxTokens: m.maxTokens,
|
|
916
|
+
cost: m.cost,
|
|
917
|
+
};
|
|
918
|
+
return m.private
|
|
919
|
+
? [
|
|
920
|
+
base,
|
|
921
|
+
{ ...base, id: `${m.id}:private`, name: `${m.name} (Private)` },
|
|
922
|
+
]
|
|
923
|
+
: [base];
|
|
924
|
+
}),
|
|
752
925
|
};
|
|
753
926
|
if (opts.install) {
|
|
754
927
|
const configFile = join(homedir(), ".openclaw", "openclaw.json");
|
|
@@ -778,6 +951,10 @@ export async function setupOpenCodeCommand(opts) {
|
|
|
778
951
|
const modelsObj = {};
|
|
779
952
|
for (const m of activeModels) {
|
|
780
953
|
modelsObj[m.id] = { name: m.name };
|
|
954
|
+
// Private models also expose a `:private` variant (TEE compute environment).
|
|
955
|
+
if (m.private) {
|
|
956
|
+
modelsObj[`${m.id}:private`] = { name: `${m.name} (Private)` };
|
|
957
|
+
}
|
|
781
958
|
}
|
|
782
959
|
const bankrProvider = {
|
|
783
960
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -810,7 +987,7 @@ export async function setupOpenCodeCommand(opts) {
|
|
|
810
987
|
output.warn("No API key set. Set BANKR_API_KEY env var or run: bankr login");
|
|
811
988
|
}
|
|
812
989
|
output.dim("Add to ~/.config/opencode/opencode.json or run: bankr llm setup opencode --install");
|
|
813
|
-
output.dim("Use bankr/ prefix for models: opencode -m bankr/claude-opus-4.
|
|
990
|
+
output.dim("Use bankr/ prefix for models: opencode -m bankr/claude-opus-4.8");
|
|
814
991
|
}
|
|
815
992
|
/* ──────────────────────── bankr llm setup cursor ──────────────────────────── */
|
|
816
993
|
export async function setupCursorCommand() {
|
|
@@ -820,7 +997,7 @@ export async function setupCursorCommand() {
|
|
|
820
997
|
const { models } = await resolveModels();
|
|
821
998
|
const activeModels = models.filter((m) => !m.deprecation);
|
|
822
999
|
// Pick one model per provider as recommended examples
|
|
823
|
-
const recommendedIds = ["claude-opus-4.
|
|
1000
|
+
const recommendedIds = ["claude-opus-4.8", "gemini-3.1-pro", "gpt-5.2"];
|
|
824
1001
|
const recommended = recommendedIds.filter((id) => activeModels.some((m) => m.id === id));
|
|
825
1002
|
output.brandBold("Cursor — Bankr LLM Gateway");
|
|
826
1003
|
console.log();
|
|
@@ -859,8 +1036,8 @@ export async function setupClaudeCodeCommand() {
|
|
|
859
1036
|
output.dim("Or launch directly: bankr llm claude");
|
|
860
1037
|
console.log();
|
|
861
1038
|
output.info("Model format:");
|
|
862
|
-
output.dim(" Claude Code accepts Anthropic-style IDs (dashed): claude-opus-4-
|
|
863
|
-
output.dim(" `bankr llm claude` auto-translates the dotted form (claude-opus-4.
|
|
1039
|
+
output.dim(" Claude Code accepts Anthropic-style IDs (dashed): claude-opus-4-8, claude-sonnet-4-6.");
|
|
1040
|
+
output.dim(" `bankr llm claude` auto-translates the dotted form (claude-opus-4.8) for you.");
|
|
864
1041
|
}
|
|
865
1042
|
/* ─────────────────────── Shared launcher helpers ────────────────────────── */
|
|
866
1043
|
function requireAuth() {
|
|
@@ -901,8 +1078,8 @@ function fileContains(filePath, search) {
|
|
|
901
1078
|
}
|
|
902
1079
|
/* ─────────────────────────── bankr llm claude ─────────────────────────────── */
|
|
903
1080
|
/**
|
|
904
|
-
* Rewrite gateway-canonical Claude IDs (dotted, e.g. `claude-opus-4.
|
|
905
|
-
* the wire format Claude Code expects (dashed, e.g. `claude-opus-4-
|
|
1081
|
+
* Rewrite gateway-canonical Claude IDs (dotted, e.g. `claude-opus-4.8`) to
|
|
1082
|
+
* the wire format Claude Code expects (dashed, e.g. `claude-opus-4-8`).
|
|
906
1083
|
* Otherwise Claude Code silently falls back to its default model — the
|
|
907
1084
|
* `--model` flag appears honored but the real request is a different
|
|
908
1085
|
* model. Any suffix (e.g. `[1m]` context tier) is preserved.
|