@bankr/cli 0.2.9 → 0.2.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/dist/cli.js +14 -9
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +26 -15
- package/dist/commands/prompt.d.ts +1 -0
- package/dist/commands/prompt.js +31 -3
- package/dist/lib/api.d.ts +19 -1
- package/dist/lib/api.js +22 -3
- package/dist/lib/output.d.ts +11 -0
- package/dist/lib/output.js +13 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -119,10 +119,9 @@ loginCmd
|
|
|
119
119
|
.option("--key-name <name>", "API key name (default: CLI)")
|
|
120
120
|
.option("--read-write", "Disable read-only mode (allow transactions)")
|
|
121
121
|
.option("--no-wallet-api", "Disable Wallet API (enabled by default)")
|
|
122
|
-
.option("--agent-api", "Enable Agent API (AI prompts)")
|
|
123
122
|
.option("--no-token-launch", "Disable Token Launch API (enabled by default)")
|
|
124
123
|
.option("--llm", "Enable LLM gateway on the API key")
|
|
125
|
-
.option("--allowed-ips <ips>", "Comma-separated IP allowlist
|
|
124
|
+
.option("--allowed-ips <ips>", "Comma-separated IP/CIDR allowlist, e.g. 1.2.3.4,10.0.0.0/24")
|
|
126
125
|
.option("--allowed-recipients <addresses>", "Comma-separated EVM/Solana addresses (agent will only send funds to these)")
|
|
127
126
|
.action(async (address, opts, cmd) => {
|
|
128
127
|
const parentOpts = cmd.parent.opts();
|
|
@@ -135,7 +134,6 @@ loginCmd
|
|
|
135
134
|
keyName: opts.keyName,
|
|
136
135
|
readWrite: opts.readWrite,
|
|
137
136
|
walletApi: opts.walletApi,
|
|
138
|
-
agentApi: opts.agentApi,
|
|
139
137
|
tokenLaunch: opts.tokenLaunch,
|
|
140
138
|
llm: opts.llm,
|
|
141
139
|
allowedIps: opts.allowedIps,
|
|
@@ -150,9 +148,8 @@ loginCmd
|
|
|
150
148
|
.option("--key-name <name>", "API key name (default: SIWE-<date>)")
|
|
151
149
|
.option("--read-write", "Disable read-only mode (allow transactions)")
|
|
152
150
|
.option("--no-wallet-api", "Disable Wallet API (enabled by default)")
|
|
153
|
-
.option("--agent-api", "Enable Agent API (AI prompts)")
|
|
154
151
|
.option("--no-token-launch", "Disable Token Launch API (enabled by default)")
|
|
155
|
-
.option("--allowed-ips <ips>", "Comma-separated IP allowlist
|
|
152
|
+
.option("--allowed-ips <ips>", "Comma-separated IP/CIDR allowlist, e.g. 1.2.3.4,10.0.0.0/24")
|
|
156
153
|
.option("--allowed-recipients <addresses>", "Comma-separated EVM/Solana addresses (agent will only send funds to these)")
|
|
157
154
|
.action(async (opts, cmd) => {
|
|
158
155
|
const parentOpts = cmd.parent.opts();
|
|
@@ -164,7 +161,6 @@ loginCmd
|
|
|
164
161
|
keyName: opts.keyName,
|
|
165
162
|
readWrite: opts.readWrite,
|
|
166
163
|
walletApi: opts.walletApi,
|
|
167
|
-
agentApi: opts.agentApi,
|
|
168
164
|
tokenLaunch: opts.tokenLaunch,
|
|
169
165
|
allowedIps: opts.allowedIps,
|
|
170
166
|
allowedRecipients: opts.allowedRecipients,
|
|
@@ -274,7 +270,9 @@ const agentCmd = program
|
|
|
274
270
|
.description("AI agent commands (prompt, status, cancel, profile, skills)")
|
|
275
271
|
.option("--thread <id>", "Continue a specific conversation thread")
|
|
276
272
|
.option("-c, --continue", "Continue the most recent thread")
|
|
277
|
-
.
|
|
273
|
+
.option("-m, --model <model>", "Use Max Mode with a specific model (e.g. claude-opus-4.6)")
|
|
274
|
+
.action(async (text, opts, cmd) => {
|
|
275
|
+
opts.model ?? (opts.model = cmd.parent?.opts()?.model);
|
|
278
276
|
const joined = text.join(" ").trim();
|
|
279
277
|
if (!joined) {
|
|
280
278
|
agentCmd.help();
|
|
@@ -287,7 +285,11 @@ agentCmd
|
|
|
287
285
|
.description("Send a prompt to the Bankr AI agent")
|
|
288
286
|
.option("--thread <id>", "Continue a specific conversation thread")
|
|
289
287
|
.option("-c, --continue", "Continue the most recent thread")
|
|
290
|
-
.
|
|
288
|
+
.option("-m, --model <model>", "Use Max Mode with a specific model (e.g. claude-opus-4.6)")
|
|
289
|
+
.action(async (text, opts, cmd) => {
|
|
290
|
+
// Commander hoists --model to the parent when both define it;
|
|
291
|
+
// fall back to parent/grandparent opts so `bankr agent prompt --model` works.
|
|
292
|
+
opts.model ?? (opts.model = cmd.parent?.opts()?.model ?? cmd.parent?.parent?.opts()?.model);
|
|
291
293
|
const joined = text.join(" ").trim();
|
|
292
294
|
await promptCommand(joined || (await readPromptInput()), opts);
|
|
293
295
|
});
|
|
@@ -581,7 +583,9 @@ program
|
|
|
581
583
|
.description("Deprecated: use `bankr agent <prompt>` instead")
|
|
582
584
|
.option("--thread <id>")
|
|
583
585
|
.option("-c, --continue")
|
|
584
|
-
.
|
|
586
|
+
.option("-m, --model <model>")
|
|
587
|
+
.action(async (text, opts, cmd) => {
|
|
588
|
+
opts.model ?? (opts.model = cmd.parent?.opts()?.model);
|
|
585
589
|
const joined = text.join(" ").trim();
|
|
586
590
|
await promptCommand(joined || (await readPromptInput()), opts);
|
|
587
591
|
});
|
|
@@ -794,6 +798,7 @@ program
|
|
|
794
798
|
.arguments("[text...]")
|
|
795
799
|
.option("--thread <id>", "Continue a specific conversation thread")
|
|
796
800
|
.option("-c, --continue", "Continue the most recent thread")
|
|
801
|
+
.option("-m, --model <model>", "Use Max Mode with a specific model (e.g. claude-opus-4.6)")
|
|
797
802
|
.action(async (text, opts) => {
|
|
798
803
|
if (text.length === 0) {
|
|
799
804
|
program.help();
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -95,7 +95,7 @@ async function callAcceptTerms(apiUrl, identityToken) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
async function callGenerateApiKey(apiUrl, identityToken, opts) {
|
|
98
|
-
const res = await fetch(`${apiUrl}/
|
|
98
|
+
const res = await fetch(`${apiUrl}/api-keys`, {
|
|
99
99
|
method: "POST",
|
|
100
100
|
headers: {
|
|
101
101
|
"Content-Type": "application/json",
|
|
@@ -263,14 +263,8 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
263
263
|
// Fine-grained API flags. walletApi and tokenLaunch default to enabled.
|
|
264
264
|
// --read-write only controls readOnly, not which APIs are enabled.
|
|
265
265
|
const enableWallet = opts.walletApi ?? true;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
? false
|
|
269
|
-
: await confirm({
|
|
270
|
-
message: "Enable Agent API? (AI prompts)",
|
|
271
|
-
default: false,
|
|
272
|
-
theme: output.bankrTheme,
|
|
273
|
-
}));
|
|
266
|
+
// Agent API must be enabled from the website (bankr.bot/api), not the CLI.
|
|
267
|
+
const enableAgent = false;
|
|
274
268
|
const enableTokenLaunch = opts.tokenLaunch ?? true;
|
|
275
269
|
const enableLlm = opts.llm ??
|
|
276
270
|
(headless
|
|
@@ -280,8 +274,8 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
280
274
|
default: false,
|
|
281
275
|
theme: output.bankrTheme,
|
|
282
276
|
}));
|
|
283
|
-
if (opts.readWrite && !enableWallet
|
|
284
|
-
output.warn("--read-write has no effect when
|
|
277
|
+
if (opts.readWrite && !enableWallet) {
|
|
278
|
+
output.warn("--read-write has no effect when Wallet API is disabled");
|
|
285
279
|
}
|
|
286
280
|
const keySpin = output.spinner("Generating API key...");
|
|
287
281
|
let apiKeyResult;
|
|
@@ -323,6 +317,7 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
323
317
|
];
|
|
324
318
|
output.dim(` Recipients: ${parts.join(", ")}`);
|
|
325
319
|
}
|
|
320
|
+
output.dim(" Agent API: enable at bankr.bot/api");
|
|
326
321
|
output.dim(" Manage keys at bankr.bot/api");
|
|
327
322
|
return apiKeyResult.apiKey;
|
|
328
323
|
}
|
|
@@ -374,7 +369,7 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
374
369
|
keyName: opts.keyName ?? `SIWE-${new Date().toISOString().slice(0, 10)}`,
|
|
375
370
|
readOnly: opts.readOnly ?? false,
|
|
376
371
|
walletApiEnabled: opts.walletApi ?? true,
|
|
377
|
-
agentApiEnabled:
|
|
372
|
+
agentApiEnabled: false,
|
|
378
373
|
tokenLaunchApiEnabled: opts.tokenLaunch ?? true,
|
|
379
374
|
allowedIps: opts.allowedIps,
|
|
380
375
|
allowedRecipients: opts.allowedRecipients,
|
|
@@ -435,14 +430,31 @@ async function validateApiKey(apiUrl, apiKey) {
|
|
|
435
430
|
}
|
|
436
431
|
}
|
|
437
432
|
// ── Main login command ──────────────────────────────────────────────
|
|
433
|
+
function isValidIpOrCidr(value) {
|
|
434
|
+
const slashIdx = value.indexOf("/");
|
|
435
|
+
if (slashIdx === -1)
|
|
436
|
+
return isIP(value) !== 0;
|
|
437
|
+
const ip = value.slice(0, slashIdx);
|
|
438
|
+
const prefix = Number(value.slice(slashIdx + 1));
|
|
439
|
+
if (!Number.isInteger(prefix) || prefix < 0)
|
|
440
|
+
return false;
|
|
441
|
+
const version = isIP(ip);
|
|
442
|
+
if (version === 0)
|
|
443
|
+
return false;
|
|
444
|
+
// ::ffff: mapped IPv4 addresses normalize to IPv4 at runtime,
|
|
445
|
+
// so validate prefix against IPv4 max (32) not IPv6 max (128)
|
|
446
|
+
const isV4Mapped = version === 6 && ip.toLowerCase().startsWith("::ffff:");
|
|
447
|
+
const maxPrefix = isV4Mapped ? 32 : version === 4 ? 32 : 128;
|
|
448
|
+
return prefix <= maxPrefix;
|
|
449
|
+
}
|
|
438
450
|
function parseAndValidateIps(raw) {
|
|
439
451
|
const ips = raw
|
|
440
452
|
.split(",")
|
|
441
453
|
.map((s) => s.trim())
|
|
442
454
|
.filter(Boolean);
|
|
443
|
-
const invalid = ips.filter((ip) =>
|
|
455
|
+
const invalid = ips.filter((ip) => !isValidIpOrCidr(ip));
|
|
444
456
|
if (invalid.length > 0) {
|
|
445
|
-
fatal(`Invalid IP address(es): ${invalid.join(", ")}`);
|
|
457
|
+
fatal(`Invalid IP address(es) or CIDR range(s): ${invalid.join(", ")}`);
|
|
446
458
|
}
|
|
447
459
|
return ips;
|
|
448
460
|
}
|
|
@@ -547,7 +559,6 @@ export async function loginCommand(opts) {
|
|
|
547
559
|
keyName: opts.keyName,
|
|
548
560
|
readOnly: !opts.readWrite,
|
|
549
561
|
walletApi: opts.walletApi,
|
|
550
|
-
agentApi: opts.agentApi,
|
|
551
562
|
tokenLaunch: opts.tokenLaunch,
|
|
552
563
|
...getParsedRestrictions(),
|
|
553
564
|
});
|
package/dist/commands/prompt.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { pollJob, submitPrompt } from "../lib/api.js";
|
|
1
|
+
import { ApiError, pollJob, submitPrompt } from "../lib/api.js";
|
|
2
2
|
import { emitCESP } from "../lib/cesp/engine.js";
|
|
3
3
|
import { getLastThreadId, requireApiKey, setLastThreadId, } from "../lib/config.js";
|
|
4
4
|
import * as output from "../lib/output.js";
|
|
5
|
+
/** Valid Max Mode model IDs — hardcoded because CLI is standalone (no monorepo imports).
|
|
6
|
+
* Must be manually kept in sync with models in the LLM gateway DB. */
|
|
7
|
+
const VALID_MAX_MODE_MODELS = new Set([
|
|
8
|
+
"claude-opus-4.6",
|
|
9
|
+
"claude-sonnet-4.6",
|
|
10
|
+
"gemini-3.1-pro",
|
|
11
|
+
]);
|
|
5
12
|
export async function promptCommand(text, options = {}) {
|
|
6
13
|
requireApiKey();
|
|
7
14
|
// Resolve thread ID from flags
|
|
@@ -16,11 +23,20 @@ export async function promptCommand(text, options = {}) {
|
|
|
16
23
|
else if (options.thread) {
|
|
17
24
|
threadId = options.thread;
|
|
18
25
|
}
|
|
26
|
+
// Resolve Max Mode from --model flag
|
|
27
|
+
let maxMode;
|
|
28
|
+
if (options.model) {
|
|
29
|
+
if (!VALID_MAX_MODE_MODELS.has(options.model)) {
|
|
30
|
+
output.error(`Invalid model "${options.model}". Valid models: ${[...VALID_MAX_MODE_MODELS].join(", ")}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
maxMode = { enabled: true, model: options.model };
|
|
34
|
+
}
|
|
19
35
|
let spin = output.spinner("Submitting prompt...");
|
|
20
36
|
let jobId;
|
|
21
37
|
let resolvedThreadId;
|
|
22
38
|
try {
|
|
23
|
-
const res = await submitPrompt(text, threadId);
|
|
39
|
+
const res = await submitPrompt(text, threadId, maxMode);
|
|
24
40
|
jobId = res.jobId;
|
|
25
41
|
resolvedThreadId = res.threadId;
|
|
26
42
|
spin.succeed("Prompt submitted");
|
|
@@ -34,7 +50,19 @@ export async function promptCommand(text, options = {}) {
|
|
|
34
50
|
}
|
|
35
51
|
catch (err) {
|
|
36
52
|
spin.fail("Failed to submit prompt");
|
|
37
|
-
|
|
53
|
+
if (err instanceof ApiError && err.body.remediation?.length) {
|
|
54
|
+
output.error(err.body.message);
|
|
55
|
+
if (process.stdout.isTTY) {
|
|
56
|
+
output.remediation(err.body.remediation);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Machine-readable JSON for AI agents / piped usage
|
|
60
|
+
console.log(JSON.stringify(err.body));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
output.error(err.message);
|
|
65
|
+
}
|
|
38
66
|
process.exit(1);
|
|
39
67
|
}
|
|
40
68
|
try {
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -43,7 +43,25 @@ export interface JobStatusResponse {
|
|
|
43
43
|
richData?: unknown[];
|
|
44
44
|
cancellable?: boolean;
|
|
45
45
|
}
|
|
46
|
-
export
|
|
46
|
+
export interface ApiErrorBody {
|
|
47
|
+
error: string;
|
|
48
|
+
message: string;
|
|
49
|
+
remediation?: Array<{
|
|
50
|
+
action: string;
|
|
51
|
+
label: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
command?: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
export declare class ApiError extends Error {
|
|
57
|
+
status: number;
|
|
58
|
+
body: ApiErrorBody;
|
|
59
|
+
constructor(status: number, body: ApiErrorBody);
|
|
60
|
+
}
|
|
61
|
+
export declare function submitPrompt(prompt: string, threadId?: string, maxMode?: {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
model: string;
|
|
64
|
+
}): Promise<PromptResponse>;
|
|
47
65
|
export declare function getJobStatus(jobId: string): Promise<JobStatusResponse>;
|
|
48
66
|
export declare function cancelJob(jobId: string): Promise<JobStatusResponse>;
|
|
49
67
|
export declare function validateApiKey(): Promise<boolean>;
|
package/dist/lib/api.js
CHANGED
|
@@ -29,13 +29,32 @@ async function handleResponse(res) {
|
|
|
29
29
|
}
|
|
30
30
|
return res.json();
|
|
31
31
|
}
|
|
32
|
-
export
|
|
32
|
+
export class ApiError extends Error {
|
|
33
|
+
constructor(status, body) {
|
|
34
|
+
super(body.message);
|
|
35
|
+
this.name = "ApiError";
|
|
36
|
+
this.status = status;
|
|
37
|
+
this.body = body;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function submitPrompt(prompt, threadId, maxMode) {
|
|
33
41
|
const res = await fetch(`${getApiUrl()}/agent/prompt`, {
|
|
34
42
|
method: "POST",
|
|
35
43
|
headers: authHeaders(),
|
|
36
|
-
body: JSON.stringify({
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
prompt,
|
|
46
|
+
...(threadId && { threadId }),
|
|
47
|
+
...(maxMode && { maxMode }),
|
|
48
|
+
}),
|
|
37
49
|
});
|
|
38
|
-
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const body = (await res.json().catch(() => ({
|
|
52
|
+
error: res.statusText,
|
|
53
|
+
message: res.statusText,
|
|
54
|
+
})));
|
|
55
|
+
throw new ApiError(res.status, body);
|
|
56
|
+
}
|
|
57
|
+
return res.json();
|
|
39
58
|
}
|
|
40
59
|
export async function getJobStatus(jobId) {
|
|
41
60
|
const res = await fetch(`${getApiUrl()}/agent/job/${jobId}`, {
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -33,5 +33,16 @@ export declare function maskApiKey(key: string): string;
|
|
|
33
33
|
export declare function formatDuration(ms: number): string;
|
|
34
34
|
export declare function formatUsd(value: string | number): string;
|
|
35
35
|
export declare function formatBalance(value: string | number, decimals?: number): string;
|
|
36
|
+
export interface RemediationStep {
|
|
37
|
+
action: string;
|
|
38
|
+
label: string;
|
|
39
|
+
url?: string;
|
|
40
|
+
command?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Display a structured remediation block with actionable fix steps.
|
|
44
|
+
* Used when the API returns a `remediation` array in error responses.
|
|
45
|
+
*/
|
|
46
|
+
export declare function remediation(steps: RemediationStep[]): void;
|
|
36
47
|
export declare function formatStatus(status: string): string;
|
|
37
48
|
//# sourceMappingURL=output.d.ts.map
|
package/dist/lib/output.js
CHANGED
|
@@ -92,6 +92,19 @@ export function formatBalance(value, decimals = 6) {
|
|
|
92
92
|
maximumFractionDigits: decimals,
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Display a structured remediation block with actionable fix steps.
|
|
97
|
+
* Used when the API returns a `remediation` array in error responses.
|
|
98
|
+
*/
|
|
99
|
+
export function remediation(steps) {
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(` ${brandColorBold("Options:")}`);
|
|
102
|
+
for (const step of steps) {
|
|
103
|
+
const target = step.command ?? step.url ?? "";
|
|
104
|
+
console.log(` ${brandColor("→")} ${step.label.padEnd(40)} ${chalk.dim(target)}`);
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
}
|
|
95
108
|
export function formatStatus(status) {
|
|
96
109
|
switch (status) {
|
|
97
110
|
case "completed":
|