@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 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 (requests from other IPs will be blocked)")
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 (requests from other IPs will be blocked)")
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
- .action(async (text, opts) => {
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
- .action(async (text, opts) => {
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
- .action(async (text, opts) => {
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();
@@ -7,7 +7,6 @@ export declare function loginCommand(opts: {
7
7
  keyName?: string;
8
8
  readWrite?: boolean;
9
9
  walletApi?: boolean;
10
- agentApi?: boolean;
11
10
  tokenLaunch?: boolean;
12
11
  llm?: boolean;
13
12
  llmKey?: string;
@@ -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}/generate-api-key`, {
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
- 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
- }));
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 && !enableAgent) {
284
- output.warn("--read-write has no effect when both Wallet API and Agent API are disabled");
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: opts.agentApi ?? false,
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) => isIP(ip) === 0);
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
  });
@@ -1,6 +1,7 @@
1
1
  export interface PromptOptions {
2
2
  thread?: string;
3
3
  continue?: boolean;
4
+ model?: string;
4
5
  }
5
6
  export declare function promptCommand(text: string, options?: PromptOptions): Promise<void>;
6
7
  //# sourceMappingURL=prompt.d.ts.map
@@ -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
- output.error(err.message);
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 declare function submitPrompt(prompt: string, threadId?: string): Promise<PromptResponse>;
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 async function submitPrompt(prompt, threadId) {
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({ prompt, ...(threadId && { threadId }) }),
44
+ body: JSON.stringify({
45
+ prompt,
46
+ ...(threadId && { threadId }),
47
+ ...(maxMode && { maxMode }),
48
+ }),
37
49
  });
38
- return handleResponse(res);
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}`, {
@@ -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
@@ -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":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {