@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.
- package/dist/cli.js +379 -133
- package/dist/commands/balances.d.ts +1 -0
- package/dist/commands/balances.js +7 -32
- package/dist/commands/llm.js +191 -22
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +139 -33
- package/dist/commands/portfolio.d.ts +9 -0
- package/dist/commands/portfolio.js +134 -0
- package/dist/commands/tokens.d.ts +7 -0
- package/dist/commands/tokens.js +60 -0
- package/dist/commands/transfer.d.ts +8 -0
- package/dist/commands/transfer.js +80 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +116 -0
- package/dist/lib/api.d.ts +59 -2
- package/dist/lib/api.js +34 -8
- package/dist/lib/chains.d.ts +5 -0
- package/dist/lib/chains.js +35 -0
- package/dist/lib/output.d.ts +2 -0
- package/dist/lib/output.js +17 -0
- package/package.json +1 -1
package/dist/commands/llm.js
CHANGED
|
@@ -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
|
|
17
|
+
// Claude
|
|
16
18
|
{
|
|
17
19
|
id: "claude-opus-4.6",
|
|
18
20
|
name: "Claude Opus 4.6",
|
|
19
21
|
owned_by: "anthropic",
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
99
|
-
name: "
|
|
100
|
-
owned_by: "
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
console.log(output.fmt.
|
|
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
|
-
|
|
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
|
};
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -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
|
package/dist/commands/login.js
CHANGED
|
@@ -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
|
-
(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
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(`
|
|
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
|
-
|
|
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,
|
|
601
|
+
const apiKey = await emailLoginFlow(apiUrl, {
|
|
602
|
+
...opts,
|
|
603
|
+
...getParsedRestrictions(),
|
|
604
|
+
});
|
|
499
605
|
output.blank();
|
|
500
606
|
saveCredentials(apiKey, opts.llmKey);
|
|
501
607
|
return;
|