@bankr/cli 0.2.0 → 0.2.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/dist/cli.js CHANGED
@@ -22,8 +22,10 @@ import { submitCommand, submitJsonCommand } from "./commands/submit.js";
22
22
  import { updateCommand } from "./commands/update.js";
23
23
  import { whoamiCommand } from "./commands/whoami.js";
24
24
  import { tokensSearchCommand, tokensInfoCommand } from "./commands/tokens.js";
25
+ import { x402InitCommand, x402AddCommand, x402ConfigureCommand, x402DeployCommand, x402ListCommand, x402PauseResumeCommand, x402DeleteCommand, x402RevenueCommand, x402EnvSetCommand, x402EnvListCommand, x402EnvUnsetCommand, x402SearchCommand, } from "./commands/x402.js";
25
26
  import { profileViewCommand, profileCreateCommand, profileUpdateCommand, profileDeleteCommand, profileAddUpdateCommand, } from "./commands/profile.js";
26
27
  import * as output from "./lib/output.js";
28
+ import { checkForUpdate } from "./lib/updateCheck.js";
27
29
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
28
30
  /**
29
31
  * Read prompt text from stdin (piped) or interactively.
@@ -712,6 +714,66 @@ program
712
714
  .action(async (opts) => {
713
715
  await updateCommand({ check: opts.check });
714
716
  });
717
+ // ── x402 Endpoint Hosting ─────────────────────────────────────────────
718
+ const x402Cmd = program
719
+ .command("x402")
720
+ .description("Deploy and manage x402 paid API endpoints");
721
+ x402Cmd
722
+ .command("init")
723
+ .description("Scaffold x402/ folder and bankr.x402.json config")
724
+ .action(x402InitCommand);
725
+ x402Cmd
726
+ .command("add <name>")
727
+ .description("Add a new x402 service handler")
728
+ .action(x402AddCommand);
729
+ x402Cmd
730
+ .command("configure <name>")
731
+ .description("Interactively configure pricing and description")
732
+ .action(x402ConfigureCommand);
733
+ x402Cmd
734
+ .command("deploy [name]")
735
+ .description("Bundle and deploy services to Bankr")
736
+ .action(x402DeployCommand);
737
+ x402Cmd
738
+ .command("list")
739
+ .description("List your deployed x402 endpoints")
740
+ .action(x402ListCommand);
741
+ x402Cmd
742
+ .command("pause <name>")
743
+ .description("Pause a deployed endpoint")
744
+ .action(async (name) => x402PauseResumeCommand(name, "pause"));
745
+ x402Cmd
746
+ .command("resume <name>")
747
+ .description("Resume a paused endpoint")
748
+ .action(async (name) => x402PauseResumeCommand(name, "resume"));
749
+ x402Cmd
750
+ .command("delete <name>")
751
+ .description("Delete a deployed endpoint")
752
+ .action(x402DeleteCommand);
753
+ x402Cmd
754
+ .command("revenue [name]")
755
+ .description("View endpoint earnings breakdown")
756
+ .action(x402RevenueCommand);
757
+ const x402EnvCmd = x402Cmd
758
+ .command("env")
759
+ .description("Manage encrypted environment variables");
760
+ x402EnvCmd
761
+ .command("set <keyValue>")
762
+ .description("Set an env var (KEY=VALUE)")
763
+ .action(x402EnvSetCommand);
764
+ x402EnvCmd
765
+ .command("list")
766
+ .description("List env var names")
767
+ .action(x402EnvListCommand);
768
+ x402EnvCmd
769
+ .command("unset <key>")
770
+ .description("Remove an env var")
771
+ .action(x402EnvUnsetCommand);
772
+ x402Cmd
773
+ .command("search [query...]")
774
+ .description("Search the x402 service marketplace (no auth required)")
775
+ .option("--raw", "Output raw JSON (unformatted)")
776
+ .action(async (query, opts) => x402SearchCommand(query, { raw: opts.raw }));
715
777
  // Default: treat unrecognized arguments as a prompt
716
778
  program
717
779
  .arguments("[text...]")
@@ -737,6 +799,7 @@ async function main() {
737
799
  output.error(err.message);
738
800
  process.exit(1);
739
801
  }
802
+ await checkForUpdate(pkg.version);
740
803
  }
741
804
  main();
742
805
  //# sourceMappingURL=cli.js.map
@@ -79,15 +79,6 @@ const GATEWAY_MODELS = [
79
79
  input: IMAGE_INPUT,
80
80
  cost: { input: 0.25, output: 1.5, cacheRead: 0.025, cacheWrite: 0.08333 },
81
81
  },
82
- {
83
- id: "gemini-3-pro",
84
- name: "Gemini 3 Pro",
85
- owned_by: "google",
86
- contextWindow: 1048576,
87
- maxTokens: 65536,
88
- input: IMAGE_INPUT,
89
- cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0.375 },
90
- },
91
82
  {
92
83
  id: "gemini-3-flash",
93
84
  name: "Gemini 3 Flash",
@@ -238,20 +229,29 @@ const GATEWAY_MODELS = [
238
229
  id: "minimax-m2.5",
239
230
  name: "MiniMax M2.5",
240
231
  owned_by: "minimax",
241
- contextWindow: 196608,
232
+ contextWindow: 204800,
242
233
  maxTokens: 196608,
243
234
  input: TEXT_INPUT,
244
- cost: { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
235
+ cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
245
236
  },
246
237
  {
247
238
  id: "minimax-m2.7",
248
239
  name: "MiniMax M2.7",
249
240
  owned_by: "minimax",
250
241
  contextWindow: 204800,
251
- maxTokens: 131072,
242
+ maxTokens: 196608,
252
243
  input: TEXT_INPUT,
253
244
  cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
254
245
  },
246
+ {
247
+ id: "minimax-m2.7-highspeed",
248
+ name: "MiniMax M2.7 Highspeed",
249
+ owned_by: "minimax",
250
+ contextWindow: 204800,
251
+ maxTokens: 196608,
252
+ input: TEXT_INPUT,
253
+ cost: { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0 },
254
+ },
255
255
  {
256
256
  id: "glm-5",
257
257
  name: "GLM-5",
@@ -261,6 +261,15 @@ const GATEWAY_MODELS = [
261
261
  input: TEXT_INPUT,
262
262
  cost: { input: 0.72, output: 2.3, cacheRead: 0, cacheWrite: 0 },
263
263
  },
264
+ {
265
+ id: "glm-5-turbo",
266
+ name: "GLM-5 Turbo",
267
+ owned_by: "z-ai",
268
+ contextWindow: 202752,
269
+ maxTokens: 131072,
270
+ input: TEXT_INPUT,
271
+ cost: { input: 1.2, output: 4.0, cacheRead: 0.24, cacheWrite: 0 },
272
+ },
264
273
  ];
265
274
  /** Fetch live model list from the gateway; falls back to hardcoded catalog. */
266
275
  async function resolveModels() {
@@ -715,11 +724,12 @@ export async function setupOpenclawCommand(opts) {
715
724
  const llmKey = getLlmKey();
716
725
  const llmUrl = getLlmUrl();
717
726
  const { models } = await resolveModels();
727
+ const activeModels = models.filter((m) => !m.deprecation);
718
728
  const providerConfig = {
719
729
  baseUrl: llmUrl,
720
730
  apiKey: llmKey ?? "${BANKR_API_KEY}",
721
731
  api: "openai-completions",
722
- models: models.map((m) => ({
732
+ models: activeModels.map((m) => ({
723
733
  id: m.id,
724
734
  name: m.name,
725
735
  ...(m.owned_by === "anthropic" ? { api: "anthropic-messages" } : {}),
@@ -753,8 +763,9 @@ export async function setupOpenCodeCommand(opts) {
753
763
  const llmKey = getLlmKey();
754
764
  const llmUrl = getLlmUrl();
755
765
  const { models } = await resolveModels();
766
+ const activeModels = models.filter((m) => !m.deprecation);
756
767
  const modelsObj = {};
757
- for (const m of models) {
768
+ for (const m of activeModels) {
758
769
  modelsObj[m.id] = { name: m.name };
759
770
  }
760
771
  const bankrProvider = {
@@ -796,9 +807,10 @@ export async function setupCursorCommand() {
796
807
  const llmUrl = getLlmUrl();
797
808
  const token = llmKey ?? "<your-bankr-api-key>";
798
809
  const { models } = await resolveModels();
810
+ const activeModels = models.filter((m) => !m.deprecation);
799
811
  // Pick one model per provider as recommended examples
800
- const recommendedIds = ["claude-opus-4.6", "gemini-3-pro", "gpt-5.2"];
801
- const recommended = recommendedIds.filter((id) => models.some((m) => m.id === id));
812
+ const recommendedIds = ["claude-opus-4.6", "gemini-3.1-pro", "gpt-5.2"];
813
+ const recommended = recommendedIds.filter((id) => activeModels.some((m) => m.id === id));
802
814
  output.brandBold("Cursor — Bankr LLM Gateway");
803
815
  console.log();
804
816
  output.info("In Cursor, go to Settings > Models and configure:");
@@ -0,0 +1,65 @@
1
+ /**
2
+ * CLI commands for x402 endpoint hosting.
3
+ *
4
+ * bankr x402 init — Scaffold x402/ folder + bankr.x402.json
5
+ * bankr x402 add <name> — Add a new service
6
+ * bankr x402 configure <name> — Interactive pricing/description setup
7
+ * bankr x402 deploy [name] — Deploy all or a single service
8
+ * bankr x402 list — List deployed services
9
+ * bankr x402 logs <name> — View request logs (future)
10
+ * bankr x402 pause <name> — Pause a service
11
+ * bankr x402 resume <name> — Resume a service
12
+ * bankr x402 delete <name> — Delete a service
13
+ * bankr x402 revenue [name] — View earnings
14
+ * bankr x402 env set KEY=VALUE — Set encrypted env var
15
+ * bankr x402 env list — List env var names
16
+ * bankr x402 env unset KEY — Remove env var
17
+ */
18
+ /**
19
+ * bankr x402 init — Scaffold x402/ folder + bankr.x402.json
20
+ */
21
+ export declare function x402InitCommand(): Promise<void>;
22
+ export declare function x402AddCommand(name: string): Promise<void>;
23
+ /**
24
+ * bankr x402 configure <name> — Interactive pricing/description setup
25
+ */
26
+ export declare function x402ConfigureCommand(name: string): Promise<void>;
27
+ /**
28
+ * bankr x402 deploy [name] — Deploy services
29
+ *
30
+ * This bundles the handler(s) with Bun and uploads via the Bankr API.
31
+ * If no name is provided, deploys all services in x402/.
32
+ */
33
+ export declare function x402DeployCommand(name?: string): Promise<void>;
34
+ /**
35
+ * bankr x402 list — List deployed services
36
+ */
37
+ export declare function x402ListCommand(): Promise<void>;
38
+ /**
39
+ * bankr x402 pause/resume <name>
40
+ */
41
+ export declare function x402PauseResumeCommand(name: string, action: "pause" | "resume"): Promise<void>;
42
+ /**
43
+ * bankr x402 delete <name>
44
+ */
45
+ export declare function x402DeleteCommand(name: string): Promise<void>;
46
+ /**
47
+ * bankr x402 revenue [name]
48
+ */
49
+ export declare function x402RevenueCommand(name?: string): Promise<void>;
50
+ /**
51
+ * bankr x402 env set KEY=VALUE
52
+ */
53
+ export declare function x402EnvSetCommand(keyValue: string): Promise<void>;
54
+ /**
55
+ * bankr x402 env list
56
+ */
57
+ export declare function x402EnvListCommand(): Promise<void>;
58
+ /**
59
+ * bankr x402 env unset KEY
60
+ */
61
+ export declare function x402EnvUnsetCommand(key: string): Promise<void>;
62
+ export declare function x402SearchCommand(queryParts: string[], opts?: {
63
+ raw?: boolean;
64
+ }): Promise<void>;
65
+ //# sourceMappingURL=x402.d.ts.map
@@ -0,0 +1,761 @@
1
+ /**
2
+ * CLI commands for x402 endpoint hosting.
3
+ *
4
+ * bankr x402 init — Scaffold x402/ folder + bankr.x402.json
5
+ * bankr x402 add <name> — Add a new service
6
+ * bankr x402 configure <name> — Interactive pricing/description setup
7
+ * bankr x402 deploy [name] — Deploy all or a single service
8
+ * bankr x402 list — List deployed services
9
+ * bankr x402 logs <name> — View request logs (future)
10
+ * bankr x402 pause <name> — Pause a service
11
+ * bankr x402 resume <name> — Resume a service
12
+ * bankr x402 delete <name> — Delete a service
13
+ * bankr x402 revenue [name] — View earnings
14
+ * bankr x402 env set KEY=VALUE — Set encrypted env var
15
+ * bankr x402 env list — List env var names
16
+ * bankr x402 env unset KEY — Remove env var
17
+ */
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
19
+ import { join } from "node:path";
20
+ import chalk from "chalk";
21
+ import { input, select, confirm } from "@inquirer/prompts";
22
+ import * as output from "../lib/output.js";
23
+ import { getApiUrl, requireApiKey, readConfig, CLI_USER_AGENT, } from "../lib/config.js";
24
+ // ── Config file helpers ─────────────────────────────────────────────────
25
+ const CONFIG_FILENAME = "bankr.x402.json";
26
+ function findProjectRoot() {
27
+ return process.cwd();
28
+ }
29
+ function loadConfigFile(projectRoot) {
30
+ const candidates = [
31
+ join(projectRoot, CONFIG_FILENAME),
32
+ join(projectRoot, "x402", CONFIG_FILENAME),
33
+ ];
34
+ for (const p of candidates) {
35
+ if (existsSync(p)) {
36
+ try {
37
+ return JSON.parse(readFileSync(p, "utf-8"));
38
+ }
39
+ catch {
40
+ output.error(`Failed to parse ${p}. Check that it is valid JSON.`);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ function saveConfigFile(projectRoot, config) {
48
+ const configPath = join(projectRoot, CONFIG_FILENAME);
49
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
50
+ }
51
+ // ── API helpers ─────────────────────────────────────────────────────────
52
+ function authHeaders() {
53
+ const headers = {
54
+ "X-API-Key": requireApiKey(),
55
+ "Content-Type": "application/json",
56
+ "User-Agent": CLI_USER_AGENT,
57
+ };
58
+ const config = readConfig();
59
+ if (config.partnerKey) {
60
+ headers["X-Partner-Key"] = config.partnerKey;
61
+ }
62
+ return headers;
63
+ }
64
+ async function handleResponse(res) {
65
+ if (!res.ok) {
66
+ const body = (await res
67
+ .json()
68
+ .catch(() => ({ message: res.statusText })));
69
+ const msg = body.message || body.error || res.statusText;
70
+ throw new Error(`API error (${res.status}): ${msg}`);
71
+ }
72
+ return res.json();
73
+ }
74
+ // ── Default handler template ────────────────────────────────────────────
75
+ const HANDLER_TEMPLATE = `/**
76
+ * x402 service handler.
77
+ *
78
+ * Receives a standard Web Request, returns a standard Web Response.
79
+ * Bankr wraps the x402 payment layer around this — you just write the logic.
80
+ *
81
+ * Environment variables are available via process.env.
82
+ */
83
+ export default async function handler(req: Request): Promise<Response> {
84
+ const url = new URL(req.url);
85
+
86
+ return Response.json({
87
+ message: "Hello from SERVICE_NAME!",
88
+ timestamp: new Date().toISOString(),
89
+ });
90
+ }
91
+ `;
92
+ // ── Commands ────────────────────────────────────────────────────────────
93
+ /**
94
+ * bankr x402 init — Scaffold x402/ folder + bankr.x402.json
95
+ */
96
+ export async function x402InitCommand() {
97
+ const root = findProjectRoot();
98
+ const x402Dir = join(root, "x402");
99
+ if (existsSync(x402Dir)) {
100
+ output.warn("x402/ directory already exists");
101
+ const overwrite = await confirm({
102
+ message: "Re-initialize config?",
103
+ default: false,
104
+ theme: output.bankrTheme,
105
+ });
106
+ if (!overwrite)
107
+ return;
108
+ }
109
+ else {
110
+ mkdirSync(x402Dir, { recursive: true });
111
+ output.success(`Created x402/ directory`);
112
+ }
113
+ const existing = loadConfigFile(root);
114
+ if (!existing) {
115
+ const config = {
116
+ network: "base",
117
+ currency: "USDC",
118
+ services: {},
119
+ };
120
+ saveConfigFile(root, config);
121
+ output.success(`Created ${CONFIG_FILENAME}`);
122
+ }
123
+ else {
124
+ output.info(`${CONFIG_FILENAME} already exists, keeping it`);
125
+ }
126
+ output.info("Next: run 'bankr x402 add <name>' to create your first service");
127
+ }
128
+ /**
129
+ * bankr x402 add <name> — Add a new service with handler scaffold
130
+ */
131
+ const MAX_SERVICE_NAME_LENGTH = 47; // bx4- (4) + hash (12) + - (1) = 17 overhead; 64 - 17 = 47
132
+ const SERVICE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
133
+ function validateServiceName(name) {
134
+ if (!SERVICE_NAME_PATTERN.test(name)) {
135
+ return `Invalid service name "${name}". Use only letters, numbers, hyphens, and underscores.`;
136
+ }
137
+ if (name.length > MAX_SERVICE_NAME_LENGTH) {
138
+ return `Service name "${name}" is too long (${name.length} chars). Maximum is ${MAX_SERVICE_NAME_LENGTH} characters.`;
139
+ }
140
+ return null;
141
+ }
142
+ export async function x402AddCommand(name) {
143
+ const nameError = validateServiceName(name);
144
+ if (nameError) {
145
+ output.error(nameError);
146
+ process.exit(1);
147
+ }
148
+ const root = findProjectRoot();
149
+ const x402Dir = join(root, "x402");
150
+ if (!existsSync(x402Dir)) {
151
+ output.error("No x402/ directory found. Run 'bankr x402 init' first.");
152
+ process.exit(1);
153
+ }
154
+ const serviceDir = join(x402Dir, name);
155
+ if (existsSync(serviceDir)) {
156
+ output.error(`Service "${name}" already exists at x402/${name}/`);
157
+ process.exit(1);
158
+ }
159
+ // Ask for method
160
+ const method = await select({
161
+ message: "HTTP method:",
162
+ choices: [
163
+ { name: "GET — query parameters in URL", value: "GET" },
164
+ { name: "POST — JSON body", value: "POST" },
165
+ { name: "Any — accept all methods", value: "*" },
166
+ ],
167
+ default: "GET",
168
+ theme: output.bankrTheme,
169
+ });
170
+ // Create service directory and handler
171
+ mkdirSync(serviceDir, { recursive: true });
172
+ const isPost = method === "POST";
173
+ const handlerContent = isPost
174
+ ? `/**
175
+ * ${name} — x402 service handler (POST with JSON body).
176
+ */
177
+ export default async function handler(req: Request): Promise<Response> {
178
+ if (req.method !== "POST") {
179
+ return Response.json({ error: "POST required" }, { status: 405 });
180
+ }
181
+
182
+ const body = await req.json();
183
+ // TODO: validate body fields
184
+
185
+ return Response.json({
186
+ message: "Hello from ${name}!",
187
+ received: body,
188
+ timestamp: new Date().toISOString(),
189
+ });
190
+ }
191
+ `
192
+ : `/**
193
+ * ${name} — x402 service handler.
194
+ */
195
+ export default async function handler(req: Request): Promise<Response> {
196
+ const url = new URL(req.url);
197
+ // const myParam = url.searchParams.get("myParam") ?? "default";
198
+
199
+ return Response.json({
200
+ message: "Hello from ${name}!",
201
+ timestamp: new Date().toISOString(),
202
+ });
203
+ }
204
+ `;
205
+ writeFileSync(join(serviceDir, "index.ts"), handlerContent);
206
+ // Build JSON Schema scaffold
207
+ const schema = {
208
+ input: {
209
+ type: "object",
210
+ properties: {
211
+ [isPost ? "field" : "param"]: {
212
+ type: "string",
213
+ description: isPost ? "Example body field" : "Example query parameter",
214
+ },
215
+ },
216
+ required: [isPost ? "field" : "param"],
217
+ },
218
+ output: {
219
+ type: "object",
220
+ properties: {
221
+ result: { type: "string", description: "Result value" },
222
+ },
223
+ },
224
+ };
225
+ // Add to config
226
+ let config = loadConfigFile(root);
227
+ if (!config) {
228
+ config = { services: {} };
229
+ }
230
+ config.services[name] = {
231
+ price: "0.001",
232
+ description: `${name} service`,
233
+ methods: method === "*" ? undefined : [method],
234
+ schema,
235
+ };
236
+ saveConfigFile(root, config);
237
+ output.success(`Created x402/${name}/index.ts`);
238
+ output.info(`Default price: $0.001 USDC. Run 'bankr x402 configure ${name}' to customize.`);
239
+ output.info(`Edit the schema in bankr.x402.json to describe your ${isPost ? "body fields" : "query parameters"} for agent discovery.`);
240
+ }
241
+ /**
242
+ * bankr x402 configure <name> — Interactive pricing/description setup
243
+ */
244
+ export async function x402ConfigureCommand(name) {
245
+ const root = findProjectRoot();
246
+ let config = loadConfigFile(root);
247
+ if (!config) {
248
+ config = { services: {} };
249
+ }
250
+ const existing = config.services[name] ?? { price: "0.001" };
251
+ const description = await input({
252
+ message: "Service description:",
253
+ default: existing.description ?? `${name} service`,
254
+ theme: output.bankrTheme,
255
+ });
256
+ const price = await input({
257
+ message: "Price per request (USD):",
258
+ default: existing.price ?? "0.001",
259
+ theme: output.bankrTheme,
260
+ });
261
+ const currency = "USDC";
262
+ const network = await select({
263
+ message: "Network:",
264
+ choices: [
265
+ { name: "Base", value: "base" },
266
+ { name: "Base Sepolia (testnet)", value: "base-sepolia" },
267
+ ],
268
+ default: existing.network ?? "base",
269
+ theme: output.bankrTheme,
270
+ });
271
+ const method = await select({
272
+ message: "HTTP method:",
273
+ choices: [
274
+ { name: "GET — query parameters in URL", value: "GET" },
275
+ { name: "POST — JSON body", value: "POST" },
276
+ { name: "Any — accept all methods", value: "*" },
277
+ ],
278
+ default: existing.methods?.[0] ?? "GET",
279
+ theme: output.bankrTheme,
280
+ });
281
+ config.services[name] = {
282
+ ...existing,
283
+ description,
284
+ price,
285
+ currency,
286
+ network,
287
+ methods: method === "*" ? undefined : [method],
288
+ };
289
+ saveConfigFile(root, config);
290
+ output.success(`Updated config for "${name}"`);
291
+ }
292
+ /**
293
+ * bankr x402 deploy [name] — Deploy services
294
+ *
295
+ * This bundles the handler(s) with Bun and uploads via the Bankr API.
296
+ * If no name is provided, deploys all services in x402/.
297
+ */
298
+ export async function x402DeployCommand(name) {
299
+ const root = findProjectRoot();
300
+ const x402Dir = join(root, "x402");
301
+ if (!existsSync(x402Dir)) {
302
+ output.error("No x402/ directory found. Run 'bankr x402 init' first.");
303
+ process.exit(1);
304
+ }
305
+ // Load or bootstrap config
306
+ let config = loadConfigFile(root);
307
+ if (!config) {
308
+ config = { services: {} };
309
+ }
310
+ // Discover services
311
+ const { readdirSync, statSync } = await import("node:fs");
312
+ const entries = readdirSync(x402Dir);
313
+ const services = [];
314
+ for (const entry of entries) {
315
+ const entryPath = join(x402Dir, entry);
316
+ if (statSync(entryPath).isDirectory() &&
317
+ existsSync(join(entryPath, "index.ts"))) {
318
+ services.push(entry);
319
+ }
320
+ }
321
+ if (services.length === 0) {
322
+ output.error("No services found. Run 'bankr x402 add <name>' first.");
323
+ process.exit(1);
324
+ }
325
+ // Filter to single service if name provided
326
+ const toDeploy = name ? services.filter((s) => s === name) : services;
327
+ if (name && toDeploy.length === 0) {
328
+ output.error(`Service "${name}" not found in x402/`);
329
+ process.exit(1);
330
+ }
331
+ // Validate service names and ensure config entries
332
+ for (const svc of toDeploy) {
333
+ const nameError = validateServiceName(svc);
334
+ if (nameError) {
335
+ output.error(nameError);
336
+ process.exit(1);
337
+ }
338
+ if (!config.services[svc]) {
339
+ config.services[svc] = {
340
+ price: "0.001",
341
+ description: `${svc} service`,
342
+ };
343
+ }
344
+ }
345
+ const spin = output.spinner(`Deploying ${toDeploy.length} service(s)...`);
346
+ try {
347
+ // Read raw TypeScript source for each service.
348
+ // Bundling + security wrapping happens server-side in the builder Lambda.
349
+ const bundles = [];
350
+ for (const svc of toDeploy) {
351
+ const entrypoint = join(x402Dir, svc, "index.ts");
352
+ if (!existsSync(entrypoint)) {
353
+ spin.fail(`Missing handler: x402/${svc}/index.ts`);
354
+ process.exit(1);
355
+ }
356
+ const source = readFileSync(entrypoint, "utf-8");
357
+ bundles.push({ name: svc, source });
358
+ }
359
+ // Deploy via API (server-side build + deploy)
360
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/deploy`, {
361
+ method: "POST",
362
+ headers: authHeaders(),
363
+ body: JSON.stringify({
364
+ config,
365
+ bundles: bundles.map((b) => ({
366
+ name: b.name,
367
+ source: b.source,
368
+ })),
369
+ }),
370
+ });
371
+ const result = await handleResponse(res);
372
+ spin.succeed(`Deployed ${result.deployments.length} service(s)`);
373
+ // Print results
374
+ console.log();
375
+ for (const dep of result.deployments) {
376
+ const svcConfig = config.services[dep.name];
377
+ output.label(" Service", dep.name);
378
+ output.label(" URL", dep.url);
379
+ output.label(" Price", `$${svcConfig?.price ?? "0.001"} ${svcConfig?.currency ?? "USDC"}/req`);
380
+ output.label(" Version", String(dep.version));
381
+ console.log();
382
+ }
383
+ }
384
+ catch (err) {
385
+ spin.fail("Deploy failed");
386
+ output.error(err instanceof Error ? err.message : String(err));
387
+ process.exit(1);
388
+ }
389
+ }
390
+ /**
391
+ * bankr x402 list — List deployed services
392
+ */
393
+ export async function x402ListCommand() {
394
+ const spin = output.spinner("Fetching endpoints...");
395
+ try {
396
+ const res = await fetch(`${getApiUrl()}/x402/endpoints`, {
397
+ headers: authHeaders(),
398
+ });
399
+ const result = await handleResponse(res);
400
+ spin.stop();
401
+ if (result.endpoints.length === 0) {
402
+ output.info("No deployed endpoints. Run 'bankr x402 deploy' to get started.");
403
+ return;
404
+ }
405
+ for (const ep of result.endpoints) {
406
+ const route = ep.routes[0];
407
+ const status = ep.status === "active"
408
+ ? output.fmt.success("active")
409
+ : output.fmt.warn(ep.status);
410
+ console.log(` ${output.fmt.brand(ep.name)} ${status} v${ep.version} $${route?.price ?? "?"} ${route?.currency ?? "USDC"} ${ep.totalRequests} reqs $${ep.totalRevenueUsd} earned`);
411
+ output.dim(` ${ep.description}`);
412
+ }
413
+ }
414
+ catch (err) {
415
+ spin.fail("Failed to list endpoints");
416
+ output.error(err instanceof Error ? err.message : String(err));
417
+ process.exit(1);
418
+ }
419
+ }
420
+ /**
421
+ * bankr x402 pause/resume <name>
422
+ */
423
+ export async function x402PauseResumeCommand(name, action) {
424
+ const status = action === "pause" ? "paused" : "active";
425
+ const spin = output.spinner(`${action === "pause" ? "Pausing" : "Resuming"} ${name}...`);
426
+ try {
427
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/${name}`, {
428
+ method: "PATCH",
429
+ headers: authHeaders(),
430
+ body: JSON.stringify({ status }),
431
+ });
432
+ await handleResponse(res);
433
+ spin.succeed(`${name} ${action === "pause" ? "paused" : "resumed"}`);
434
+ }
435
+ catch (err) {
436
+ spin.fail(`Failed to ${action} ${name}`);
437
+ output.error(err instanceof Error ? err.message : String(err));
438
+ process.exit(1);
439
+ }
440
+ }
441
+ /**
442
+ * bankr x402 delete <name>
443
+ */
444
+ export async function x402DeleteCommand(name) {
445
+ const confirmed = await confirm({
446
+ message: `Delete endpoint "${name}"? This cannot be undone.`,
447
+ default: false,
448
+ theme: output.bankrTheme,
449
+ });
450
+ if (!confirmed)
451
+ return;
452
+ const spin = output.spinner(`Deleting ${name}...`);
453
+ try {
454
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/${name}`, {
455
+ method: "DELETE",
456
+ headers: authHeaders(),
457
+ });
458
+ await handleResponse(res);
459
+ spin.succeed(`${name} deleted`);
460
+ }
461
+ catch (err) {
462
+ spin.fail(`Failed to delete ${name}`);
463
+ output.error(err instanceof Error ? err.message : String(err));
464
+ process.exit(1);
465
+ }
466
+ }
467
+ /**
468
+ * bankr x402 revenue [name]
469
+ */
470
+ export async function x402RevenueCommand(name) {
471
+ if (!name) {
472
+ // Show all endpoints revenue summary
473
+ await x402ListCommand();
474
+ return;
475
+ }
476
+ const spin = output.spinner(`Fetching revenue for ${name}...`);
477
+ try {
478
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/revenue/${name}`, {
479
+ headers: authHeaders(),
480
+ });
481
+ const result = await handleResponse(res);
482
+ spin.stop();
483
+ console.log(`\n Revenue for ${output.fmt.brand(name)}\n`);
484
+ for (const [period, data] of Object.entries(result.revenue)) {
485
+ const label = period === "last7d"
486
+ ? "Last 7 days"
487
+ : period === "last30d"
488
+ ? "Last 30 days"
489
+ : "All time";
490
+ const earned = (data.totalUsd - data.bankrFeesUsd).toFixed(6);
491
+ console.log(` ${label.padEnd(14)} ${data.requests} reqs $${earned} earned $${data.bankrFeesUsd.toFixed(6)} fees`);
492
+ }
493
+ console.log();
494
+ }
495
+ catch (err) {
496
+ spin.fail("Failed to fetch revenue");
497
+ output.error(err instanceof Error ? err.message : String(err));
498
+ process.exit(1);
499
+ }
500
+ }
501
+ /**
502
+ * bankr x402 env set KEY=VALUE
503
+ */
504
+ export async function x402EnvSetCommand(keyValue) {
505
+ const eqIdx = keyValue.indexOf("=");
506
+ if (eqIdx === -1) {
507
+ output.error("Usage: bankr x402 env set KEY=VALUE");
508
+ process.exit(1);
509
+ }
510
+ const key = keyValue.slice(0, eqIdx);
511
+ const value = keyValue.slice(eqIdx + 1);
512
+ if (!key) {
513
+ output.error("Key cannot be empty");
514
+ process.exit(1);
515
+ }
516
+ const spin = output.spinner(`Setting ${key}...`);
517
+ try {
518
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env`, {
519
+ method: "POST",
520
+ headers: authHeaders(),
521
+ body: JSON.stringify({ vars: { [key]: value } }),
522
+ });
523
+ await handleResponse(res);
524
+ spin.succeed(`Set ${key}`);
525
+ }
526
+ catch (err) {
527
+ spin.fail(`Failed to set ${key}`);
528
+ output.error(err instanceof Error ? err.message : String(err));
529
+ process.exit(1);
530
+ }
531
+ }
532
+ /**
533
+ * bankr x402 env list
534
+ */
535
+ export async function x402EnvListCommand() {
536
+ const spin = output.spinner("Fetching env vars...");
537
+ try {
538
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env`, {
539
+ headers: authHeaders(),
540
+ });
541
+ const result = await handleResponse(res);
542
+ spin.stop();
543
+ if (result.vars.length === 0) {
544
+ output.info("No environment variables set.");
545
+ return;
546
+ }
547
+ for (const name of result.vars) {
548
+ console.log(` ${name}`);
549
+ }
550
+ }
551
+ catch (err) {
552
+ spin.fail("Failed to list env vars");
553
+ output.error(err instanceof Error ? err.message : String(err));
554
+ process.exit(1);
555
+ }
556
+ }
557
+ /**
558
+ * bankr x402 env unset KEY
559
+ */
560
+ export async function x402EnvUnsetCommand(key) {
561
+ const spin = output.spinner(`Removing ${key}...`);
562
+ try {
563
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env/${key}`, {
564
+ method: "DELETE",
565
+ headers: authHeaders(),
566
+ });
567
+ await handleResponse(res);
568
+ spin.succeed(`Removed ${key}`);
569
+ }
570
+ catch (err) {
571
+ spin.fail(`Failed to remove ${key}`);
572
+ output.error(err instanceof Error ? err.message : String(err));
573
+ process.exit(1);
574
+ }
575
+ }
576
+ // ── Schema display helpers ──────────────────────────────────────────────
577
+ const P = " │";
578
+ /**
579
+ * Detects whether a schema object is JSON Schema format (has `type` and `properties`)
580
+ * vs the legacy flat Record<string,string> format.
581
+ */
582
+ function isJsonSchema(obj) {
583
+ return (typeof obj === "object" &&
584
+ obj !== null &&
585
+ "type" in obj &&
586
+ obj.type === "object" &&
587
+ "properties" in obj);
588
+ }
589
+ /**
590
+ * Renders a JSON Schema properties table.
591
+ * Output: lines like " │ symbol* string Token symbol"
592
+ */
593
+ function renderJsonSchemaProps(schema) {
594
+ if (!schema.properties)
595
+ return [];
596
+ const required = new Set(schema.required ?? []);
597
+ const entries = Object.entries(schema.properties);
598
+ // Calculate column widths
599
+ let maxName = 0;
600
+ let maxType = 0;
601
+ for (const [name, prop] of entries) {
602
+ const nameLen = name.length + (required.has(name) ? 1 : 0);
603
+ if (nameLen > maxName)
604
+ maxName = nameLen;
605
+ const typeStr = prop.type + (prop.enum ? ` (${prop.enum.join("|")})` : "");
606
+ if (typeStr.length > maxType)
607
+ maxType = typeStr.length;
608
+ }
609
+ const lines = [];
610
+ for (const [name, prop] of entries) {
611
+ const req = required.has(name) ? chalk.red("*") : " ";
612
+ const nameStr = `${chalk.cyan(name)}${req}`;
613
+ const namePad = " ".repeat(Math.max(0, maxName + 1 - name.length - (required.has(name) ? 1 : 0)));
614
+ const typeStr = prop.type + (prop.enum ? ` (${prop.enum.join("|")})` : "");
615
+ const typePad = " ".repeat(Math.max(0, maxType + 2 - typeStr.length));
616
+ const desc = prop.description ? chalk.dim(prop.description) : "";
617
+ lines.push(`${P} ${nameStr}${namePad}${chalk.yellow(typeStr)}${typePad}${desc}`);
618
+ }
619
+ return lines;
620
+ }
621
+ /**
622
+ * Renders a legacy flat Record<string,string> schema as a simple key: value list.
623
+ * Pretty-prints nested objects/arrays with proper indentation.
624
+ */
625
+ function renderLegacySchema(obj) {
626
+ const lines = [];
627
+ for (const [k, v] of Object.entries(obj)) {
628
+ if (typeof v === "object" && v !== null) {
629
+ const pretty = JSON.stringify(v, null, 2);
630
+ const indented = pretty
631
+ .split("\n")
632
+ .map((line, i) => (i === 0 ? line : `${P} ${" ".repeat(k.length)}${line}`))
633
+ .join("\n");
634
+ lines.push(`${P} ${chalk.cyan(k)} ${chalk.dim(indented)}`);
635
+ }
636
+ else {
637
+ lines.push(`${P} ${chalk.cyan(k)} ${chalk.dim(String(v))}`);
638
+ }
639
+ }
640
+ return lines;
641
+ }
642
+ /**
643
+ * Resolves input/output schemas from a route, handling both JSON Schema
644
+ * and legacy flat formats for backward compatibility.
645
+ */
646
+ function resolveSchemas(schema) {
647
+ if (!schema)
648
+ return { input: undefined, output: undefined };
649
+ // Prefer new JSON Schema `input`/`output` fields
650
+ const inputSchema = schema.input ?? schema.queryParams ?? schema.body;
651
+ const outputSchema = schema.output;
652
+ return { input: inputSchema, output: outputSchema };
653
+ }
654
+ function printServiceFormatted(svc) {
655
+ const route = svc.routes[0];
656
+ const methods = route?.methods?.filter((m) => m !== "*").join(", ") || "ANY";
657
+ const url = svc.url ?? `https://x402.bankr.bot/${svc.slug}`;
658
+ const isGet = methods === "GET";
659
+ // ── Card header ──
660
+ console.log(` ${chalk.dim("┌")} ${output.fmt.brandBold(svc.name)}`);
661
+ if (svc.description) {
662
+ console.log(`${P} ${chalk.dim(svc.description)}`);
663
+ }
664
+ console.log(`${P}`);
665
+ // ── Metadata ──
666
+ console.log(`${P} ${chalk.dim("URL")} ${url}`);
667
+ console.log(`${P} ${chalk.dim("Method")} ${chalk.bold(methods)}`);
668
+ console.log(`${P} ${chalk.dim("Price")} ${chalk.green(`$${route?.price ?? "?"} ${route?.currency ?? "USDC"}`)}`);
669
+ console.log(`${P} ${chalk.dim("Network")} ${route?.network ?? "base"}`);
670
+ // ── Schema ──
671
+ const { input: inputSchema, output: outputSchema } = resolveSchemas(route?.schema);
672
+ if (inputSchema || outputSchema) {
673
+ console.log(`${P}`);
674
+ if (inputSchema) {
675
+ const label = isGet ? "Input (query params)" : "Input (JSON body)";
676
+ console.log(`${P} ${chalk.bold(label)}`);
677
+ if (isJsonSchema(inputSchema)) {
678
+ for (const line of renderJsonSchemaProps(inputSchema)) {
679
+ console.log(line);
680
+ }
681
+ }
682
+ else if (typeof inputSchema === "object" && inputSchema !== null) {
683
+ for (const line of renderLegacySchema(inputSchema)) {
684
+ console.log(line);
685
+ }
686
+ }
687
+ }
688
+ if (outputSchema) {
689
+ console.log(`${P}`);
690
+ console.log(`${P} ${chalk.bold("Output")}`);
691
+ if (isJsonSchema(outputSchema)) {
692
+ for (const line of renderJsonSchemaProps(outputSchema)) {
693
+ console.log(line);
694
+ }
695
+ }
696
+ else if (typeof outputSchema === "object" && outputSchema !== null) {
697
+ for (const line of renderLegacySchema(outputSchema)) {
698
+ console.log(line);
699
+ }
700
+ }
701
+ }
702
+ }
703
+ // ── Example ──
704
+ if (inputSchema && isJsonSchema(inputSchema) && inputSchema.properties) {
705
+ console.log(`${P}`);
706
+ const props = Object.entries(inputSchema.properties);
707
+ if (isGet) {
708
+ const qs = props.map(([k, p]) => `${k}=<${p.type}>`).join("&");
709
+ console.log(`${P} ${chalk.dim("Example")} ${chalk.dim("GET")} ${url}?${qs}`);
710
+ }
711
+ else {
712
+ const bodyObj = {};
713
+ for (const [k, p] of props)
714
+ bodyObj[k] = `<${p.type}>`;
715
+ console.log(`${P} ${chalk.dim("Example")} ${chalk.dim("POST")} ${url}`);
716
+ console.log(`${P} ${chalk.dim(JSON.stringify(bodyObj))}`);
717
+ }
718
+ }
719
+ // ── Tags ──
720
+ if (svc.tags?.length) {
721
+ console.log(`${P}`);
722
+ console.log(`${P} ${svc.tags.map((t) => chalk.dim(`#${t}`)).join(" ")}`);
723
+ }
724
+ console.log(` ${chalk.dim("└" + "─".repeat(60))}`);
725
+ console.log();
726
+ }
727
+ export async function x402SearchCommand(queryParts, opts = {}) {
728
+ const query = queryParts.join(" ").trim();
729
+ if (!query) {
730
+ output.error("Usage: bankr x402 search <query>");
731
+ process.exit(1);
732
+ }
733
+ const spin = output.spinner("Searching services...");
734
+ try {
735
+ const params = new URLSearchParams({ q: query, limit: "10" });
736
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/discover?${params}`);
737
+ const result = await handleResponse(res);
738
+ spin.stop();
739
+ if (result.services.length === 0) {
740
+ output.info(`No services found for "${query}"`);
741
+ return;
742
+ }
743
+ // Raw mode: dump JSON and exit
744
+ if (opts.raw) {
745
+ console.log(JSON.stringify(result.services, null, 2));
746
+ return;
747
+ }
748
+ console.log();
749
+ console.log(` ${chalk.dim(`Found ${result.services.length} service(s) for`)} "${query}"`);
750
+ console.log(` ${chalk.dim("─".repeat(60))}`);
751
+ for (const svc of result.services) {
752
+ printServiceFormatted(svc);
753
+ }
754
+ }
755
+ catch (err) {
756
+ spin.fail("Search failed");
757
+ output.error(err instanceof Error ? err.message : String(err));
758
+ process.exit(1);
759
+ }
760
+ }
761
+ //# sourceMappingURL=x402.js.map
@@ -5,6 +5,7 @@ export const CHAIN_LABELS = {
5
5
  unichain: "Unichain",
6
6
  worldchain: "World Chain",
7
7
  arbitrum: "Arbitrum",
8
+ bnb: "BNB Chain",
8
9
  solana: "Solana",
9
10
  };
10
11
  export const VALID_CHAINS = new Set([
@@ -14,6 +15,7 @@ export const VALID_CHAINS = new Set([
14
15
  "unichain",
15
16
  "worldchain",
16
17
  "arbitrum",
18
+ "bnb",
17
19
  "solana",
18
20
  ]);
19
21
  export const CHAIN_IDS = {
@@ -23,6 +25,7 @@ export const CHAIN_IDS = {
23
25
  unichain: 130,
24
26
  worldchain: 480,
25
27
  arbitrum: 42161,
28
+ bnb: 56,
26
29
  };
27
30
  export const NATIVE_SYMBOLS = {
28
31
  base: "ETH",
@@ -31,5 +34,6 @@ export const NATIVE_SYMBOLS = {
31
34
  worldchain: "ETH",
32
35
  arbitrum: "ETH",
33
36
  polygon: "POL",
37
+ bnb: "BNB",
34
38
  };
35
39
  //# sourceMappingURL=chains.js.map
@@ -4,6 +4,8 @@ export declare const fmt: {
4
4
  readonly brand: import("chalk").ChalkInstance;
5
5
  readonly brandBold: import("chalk").ChalkInstance;
6
6
  readonly dim: import("chalk").ChalkInstance;
7
+ readonly success: import("chalk").ChalkInstance;
8
+ readonly warn: import("chalk").ChalkInstance;
7
9
  };
8
10
  export declare const bankrTheme: {
9
11
  prefix: string;
@@ -8,6 +8,8 @@ export const fmt = {
8
8
  brand: brandColor,
9
9
  brandBold: brandColorBold,
10
10
  dim: chalk.dim,
11
+ success: chalk.green,
12
+ warn: chalk.yellow,
11
13
  };
12
14
  export const bankrTheme = {
13
15
  prefix: brandColor("▸"),
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Non-blocking update check. Call at the end of main().
3
+ * - Checks npm registry at most once every 4 hours (cached)
4
+ * - Prints a one-line notice if a newer version exists
5
+ * - Never throws, never delays CLI exit
6
+ */
7
+ export declare function checkForUpdate(currentVersion: string): Promise<void>;
8
+ //# sourceMappingURL=updateCheck.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import chalk from "chalk";
5
+ const PACKAGE_NAME = "@bankr/cli";
6
+ const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
7
+ const CACHE_DIR = join(homedir(), ".bankr");
8
+ const CACHE_FILE = join(CACHE_DIR, "update-check.json");
9
+ function readCache() {
10
+ try {
11
+ if (!existsSync(CACHE_FILE))
12
+ return null;
13
+ return JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function writeCache(cache) {
20
+ try {
21
+ if (!existsSync(CACHE_DIR))
22
+ mkdirSync(CACHE_DIR, { recursive: true });
23
+ writeFileSync(CACHE_FILE, JSON.stringify(cache));
24
+ }
25
+ catch {
26
+ // Silent — cache is best-effort
27
+ }
28
+ }
29
+ function compareSemver(a, b) {
30
+ const partsA = a.split(".").map(Number);
31
+ const partsB = b.split(".").map(Number);
32
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
33
+ const numA = partsA[i] ?? 0;
34
+ const numB = partsB[i] ?? 0;
35
+ if (numA < numB)
36
+ return -1;
37
+ if (numA > numB)
38
+ return 1;
39
+ }
40
+ return 0;
41
+ }
42
+ /**
43
+ * Non-blocking update check. Call at the end of main().
44
+ * - Checks npm registry at most once every 4 hours (cached)
45
+ * - Prints a one-line notice if a newer version exists
46
+ * - Never throws, never delays CLI exit
47
+ */
48
+ export async function checkForUpdate(currentVersion) {
49
+ try {
50
+ // Check cache first
51
+ const cache = readCache();
52
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
53
+ if (compareSemver(currentVersion, cache.latestVersion) < 0) {
54
+ printNotice(currentVersion, cache.latestVersion);
55
+ }
56
+ return;
57
+ }
58
+ // Fetch latest with a short timeout
59
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { signal: AbortSignal.timeout(3000) });
60
+ if (!res.ok)
61
+ return;
62
+ const data = (await res.json());
63
+ const latest = data.version;
64
+ writeCache({ lastCheck: Date.now(), latestVersion: latest });
65
+ if (compareSemver(currentVersion, latest) < 0) {
66
+ printNotice(currentVersion, latest);
67
+ }
68
+ }
69
+ catch {
70
+ // Silent — never block the CLI for update checks
71
+ }
72
+ }
73
+ function printNotice(current, latest) {
74
+ console.log();
75
+ console.log(chalk.yellow(` Update available: ${current} → ${latest}`));
76
+ console.log(chalk.dim(` Run ${chalk.white("bankr update")} to update`));
77
+ }
78
+ //# sourceMappingURL=updateCheck.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {