@bankr/cli 0.2.0 → 0.2.2

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,6 +22,7 @@ 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";
27
28
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
@@ -712,6 +713,65 @@ program
712
713
  .action(async (opts) => {
713
714
  await updateCommand({ check: opts.check });
714
715
  });
716
+ // ── x402 Endpoint Hosting ─────────────────────────────────────────────
717
+ const x402Cmd = program
718
+ .command("x402")
719
+ .description("Deploy and manage x402 paid API endpoints");
720
+ x402Cmd
721
+ .command("init")
722
+ .description("Scaffold x402/ folder and bankr.x402.json config")
723
+ .action(x402InitCommand);
724
+ x402Cmd
725
+ .command("add <name>")
726
+ .description("Add a new x402 service handler")
727
+ .action(x402AddCommand);
728
+ x402Cmd
729
+ .command("configure <name>")
730
+ .description("Interactively configure pricing and description")
731
+ .action(x402ConfigureCommand);
732
+ x402Cmd
733
+ .command("deploy [name]")
734
+ .description("Bundle and deploy services to Bankr")
735
+ .action(x402DeployCommand);
736
+ x402Cmd
737
+ .command("list")
738
+ .description("List your deployed x402 endpoints")
739
+ .action(x402ListCommand);
740
+ x402Cmd
741
+ .command("pause <name>")
742
+ .description("Pause a deployed endpoint")
743
+ .action(async (name) => x402PauseResumeCommand(name, "pause"));
744
+ x402Cmd
745
+ .command("resume <name>")
746
+ .description("Resume a paused endpoint")
747
+ .action(async (name) => x402PauseResumeCommand(name, "resume"));
748
+ x402Cmd
749
+ .command("delete <name>")
750
+ .description("Delete a deployed endpoint")
751
+ .action(x402DeleteCommand);
752
+ x402Cmd
753
+ .command("revenue [name]")
754
+ .description("View endpoint earnings breakdown")
755
+ .action(x402RevenueCommand);
756
+ const x402EnvCmd = x402Cmd
757
+ .command("env")
758
+ .description("Manage encrypted environment variables");
759
+ x402EnvCmd
760
+ .command("set <keyValue>")
761
+ .description("Set an env var (KEY=VALUE)")
762
+ .action(x402EnvSetCommand);
763
+ x402EnvCmd
764
+ .command("list")
765
+ .description("List env var names")
766
+ .action(x402EnvListCommand);
767
+ x402EnvCmd
768
+ .command("unset <key>")
769
+ .description("Remove an env var")
770
+ .action(x402EnvUnsetCommand);
771
+ x402Cmd
772
+ .command("search [query...]")
773
+ .description("Search the x402 service marketplace (no auth required)")
774
+ .action(async (query) => x402SearchCommand(query));
715
775
  // Default: treat unrecognized arguments as a prompt
716
776
  program
717
777
  .arguments("[text...]")
@@ -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,69 @@
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
+ /**
63
+ * bankr x402 search <query> — Search the x402 service marketplace
64
+ *
65
+ * Public — no auth required. Uses vector search + keyword matching
66
+ * to find relevant paid API services.
67
+ */
68
+ export declare function x402SearchCommand(queryParts: string[]): Promise<void>;
69
+ //# sourceMappingURL=x402.d.ts.map
@@ -0,0 +1,636 @@
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 { input, select, confirm } from "@inquirer/prompts";
21
+ import * as output from "../lib/output.js";
22
+ import { getApiUrl, requireApiKey, readConfig, CLI_USER_AGENT, } from "../lib/config.js";
23
+ // ── Config file helpers ─────────────────────────────────────────────────
24
+ const CONFIG_FILENAME = "bankr.x402.json";
25
+ function findProjectRoot() {
26
+ return process.cwd();
27
+ }
28
+ function loadConfigFile(projectRoot) {
29
+ const candidates = [
30
+ join(projectRoot, CONFIG_FILENAME),
31
+ join(projectRoot, "x402", CONFIG_FILENAME),
32
+ ];
33
+ for (const p of candidates) {
34
+ if (existsSync(p)) {
35
+ try {
36
+ return JSON.parse(readFileSync(p, "utf-8"));
37
+ }
38
+ catch {
39
+ output.error(`Failed to parse ${p}. Check that it is valid JSON.`);
40
+ process.exit(1);
41
+ }
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ function saveConfigFile(projectRoot, config) {
47
+ const configPath = join(projectRoot, CONFIG_FILENAME);
48
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
49
+ }
50
+ // ── API helpers ─────────────────────────────────────────────────────────
51
+ function authHeaders() {
52
+ const headers = {
53
+ "X-API-Key": requireApiKey(),
54
+ "Content-Type": "application/json",
55
+ "User-Agent": CLI_USER_AGENT,
56
+ };
57
+ const config = readConfig();
58
+ if (config.partnerKey) {
59
+ headers["X-Partner-Key"] = config.partnerKey;
60
+ }
61
+ return headers;
62
+ }
63
+ async function handleResponse(res) {
64
+ if (!res.ok) {
65
+ const body = (await res
66
+ .json()
67
+ .catch(() => ({ message: res.statusText })));
68
+ const msg = body.message || body.error || res.statusText;
69
+ throw new Error(`API error (${res.status}): ${msg}`);
70
+ }
71
+ return res.json();
72
+ }
73
+ // ── Default handler template ────────────────────────────────────────────
74
+ const HANDLER_TEMPLATE = `/**
75
+ * x402 service handler.
76
+ *
77
+ * Receives a standard Web Request, returns a standard Web Response.
78
+ * Bankr wraps the x402 payment layer around this — you just write the logic.
79
+ *
80
+ * Environment variables are available via process.env.
81
+ */
82
+ export default async function handler(req: Request): Promise<Response> {
83
+ const url = new URL(req.url);
84
+
85
+ return Response.json({
86
+ message: "Hello from SERVICE_NAME!",
87
+ timestamp: new Date().toISOString(),
88
+ });
89
+ }
90
+ `;
91
+ // ── Commands ────────────────────────────────────────────────────────────
92
+ /**
93
+ * bankr x402 init — Scaffold x402/ folder + bankr.x402.json
94
+ */
95
+ export async function x402InitCommand() {
96
+ const root = findProjectRoot();
97
+ const x402Dir = join(root, "x402");
98
+ if (existsSync(x402Dir)) {
99
+ output.warn("x402/ directory already exists");
100
+ const overwrite = await confirm({
101
+ message: "Re-initialize config?",
102
+ default: false,
103
+ theme: output.bankrTheme,
104
+ });
105
+ if (!overwrite)
106
+ return;
107
+ }
108
+ else {
109
+ mkdirSync(x402Dir, { recursive: true });
110
+ output.success(`Created x402/ directory`);
111
+ }
112
+ const existing = loadConfigFile(root);
113
+ if (!existing) {
114
+ const config = {
115
+ network: "base",
116
+ currency: "USDC",
117
+ services: {},
118
+ };
119
+ saveConfigFile(root, config);
120
+ output.success(`Created ${CONFIG_FILENAME}`);
121
+ }
122
+ else {
123
+ output.info(`${CONFIG_FILENAME} already exists, keeping it`);
124
+ }
125
+ output.info("Next: run 'bankr x402 add <name>' to create your first service");
126
+ }
127
+ /**
128
+ * bankr x402 add <name> — Add a new service with handler scaffold
129
+ */
130
+ const MAX_SERVICE_NAME_LENGTH = 47; // bx4- (4) + hash (12) + - (1) = 17 overhead; 64 - 17 = 47
131
+ const SERVICE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
132
+ function validateServiceName(name) {
133
+ if (!SERVICE_NAME_PATTERN.test(name)) {
134
+ return `Invalid service name "${name}". Use only letters, numbers, hyphens, and underscores.`;
135
+ }
136
+ if (name.length > MAX_SERVICE_NAME_LENGTH) {
137
+ return `Service name "${name}" is too long (${name.length} chars). Maximum is ${MAX_SERVICE_NAME_LENGTH} characters.`;
138
+ }
139
+ return null;
140
+ }
141
+ export async function x402AddCommand(name) {
142
+ const nameError = validateServiceName(name);
143
+ if (nameError) {
144
+ output.error(nameError);
145
+ process.exit(1);
146
+ }
147
+ const root = findProjectRoot();
148
+ const x402Dir = join(root, "x402");
149
+ if (!existsSync(x402Dir)) {
150
+ output.error("No x402/ directory found. Run 'bankr x402 init' first.");
151
+ process.exit(1);
152
+ }
153
+ const serviceDir = join(x402Dir, name);
154
+ if (existsSync(serviceDir)) {
155
+ output.error(`Service "${name}" already exists at x402/${name}/`);
156
+ process.exit(1);
157
+ }
158
+ // Ask for method
159
+ const method = await select({
160
+ message: "HTTP method:",
161
+ choices: [
162
+ { name: "GET — query parameters in URL", value: "GET" },
163
+ { name: "POST — JSON body", value: "POST" },
164
+ { name: "Any — accept all methods", value: "*" },
165
+ ],
166
+ default: "GET",
167
+ theme: output.bankrTheme,
168
+ });
169
+ // Create service directory and handler
170
+ mkdirSync(serviceDir, { recursive: true });
171
+ const isPost = method === "POST";
172
+ const handlerContent = isPost
173
+ ? `/**
174
+ * ${name} — x402 service handler (POST with JSON body).
175
+ */
176
+ export default async function handler(req: Request): Promise<Response> {
177
+ if (req.method !== "POST") {
178
+ return Response.json({ error: "POST required" }, { status: 405 });
179
+ }
180
+
181
+ const body = await req.json();
182
+ // TODO: validate body fields
183
+
184
+ return Response.json({
185
+ message: "Hello from ${name}!",
186
+ received: body,
187
+ timestamp: new Date().toISOString(),
188
+ });
189
+ }
190
+ `
191
+ : `/**
192
+ * ${name} — x402 service handler.
193
+ */
194
+ export default async function handler(req: Request): Promise<Response> {
195
+ const url = new URL(req.url);
196
+ // const myParam = url.searchParams.get("myParam") ?? "default";
197
+
198
+ return Response.json({
199
+ message: "Hello from ${name}!",
200
+ timestamp: new Date().toISOString(),
201
+ });
202
+ }
203
+ `;
204
+ writeFileSync(join(serviceDir, "index.ts"), handlerContent);
205
+ // Build schema scaffold based on method
206
+ const schema = {};
207
+ if (isPost) {
208
+ schema.body = { field: "string" };
209
+ }
210
+ else if (method === "GET") {
211
+ schema.queryParams = { param: "string" };
212
+ }
213
+ schema.output = { result: "string" };
214
+ // Add to config
215
+ let config = loadConfigFile(root);
216
+ if (!config) {
217
+ config = { services: {} };
218
+ }
219
+ config.services[name] = {
220
+ price: "0.001",
221
+ description: `${name} service`,
222
+ methods: method === "*" ? undefined : [method],
223
+ schema,
224
+ };
225
+ saveConfigFile(root, config);
226
+ output.success(`Created x402/${name}/index.ts`);
227
+ output.info(`Default price: $0.001 USDC. Run 'bankr x402 configure ${name}' to customize.`);
228
+ output.info(`Edit the schema in bankr.x402.json to describe your ${isPost ? "body fields" : "query parameters"} for agent discovery.`);
229
+ }
230
+ /**
231
+ * bankr x402 configure <name> — Interactive pricing/description setup
232
+ */
233
+ export async function x402ConfigureCommand(name) {
234
+ const root = findProjectRoot();
235
+ let config = loadConfigFile(root);
236
+ if (!config) {
237
+ config = { services: {} };
238
+ }
239
+ const existing = config.services[name] ?? { price: "0.001" };
240
+ const description = await input({
241
+ message: "Service description:",
242
+ default: existing.description ?? `${name} service`,
243
+ theme: output.bankrTheme,
244
+ });
245
+ const price = await input({
246
+ message: "Price per request (USD):",
247
+ default: existing.price ?? "0.001",
248
+ theme: output.bankrTheme,
249
+ });
250
+ const currency = "USDC";
251
+ const network = await select({
252
+ message: "Network:",
253
+ choices: [
254
+ { name: "Base", value: "base" },
255
+ { name: "Base Sepolia (testnet)", value: "base-sepolia" },
256
+ ],
257
+ default: existing.network ?? "base",
258
+ theme: output.bankrTheme,
259
+ });
260
+ const method = await select({
261
+ message: "HTTP method:",
262
+ choices: [
263
+ { name: "GET — query parameters in URL", value: "GET" },
264
+ { name: "POST — JSON body", value: "POST" },
265
+ { name: "Any — accept all methods", value: "*" },
266
+ ],
267
+ default: existing.methods?.[0] ?? "GET",
268
+ theme: output.bankrTheme,
269
+ });
270
+ config.services[name] = {
271
+ ...existing,
272
+ description,
273
+ price,
274
+ currency,
275
+ network,
276
+ methods: method === "*" ? undefined : [method],
277
+ };
278
+ saveConfigFile(root, config);
279
+ output.success(`Updated config for "${name}"`);
280
+ }
281
+ /**
282
+ * bankr x402 deploy [name] — Deploy services
283
+ *
284
+ * This bundles the handler(s) with Bun and uploads via the Bankr API.
285
+ * If no name is provided, deploys all services in x402/.
286
+ */
287
+ export async function x402DeployCommand(name) {
288
+ const root = findProjectRoot();
289
+ const x402Dir = join(root, "x402");
290
+ if (!existsSync(x402Dir)) {
291
+ output.error("No x402/ directory found. Run 'bankr x402 init' first.");
292
+ process.exit(1);
293
+ }
294
+ // Load or bootstrap config
295
+ let config = loadConfigFile(root);
296
+ if (!config) {
297
+ config = { services: {} };
298
+ }
299
+ // Discover services
300
+ const { readdirSync, statSync } = await import("node:fs");
301
+ const entries = readdirSync(x402Dir);
302
+ const services = [];
303
+ for (const entry of entries) {
304
+ const entryPath = join(x402Dir, entry);
305
+ if (statSync(entryPath).isDirectory() &&
306
+ existsSync(join(entryPath, "index.ts"))) {
307
+ services.push(entry);
308
+ }
309
+ }
310
+ if (services.length === 0) {
311
+ output.error("No services found. Run 'bankr x402 add <name>' first.");
312
+ process.exit(1);
313
+ }
314
+ // Filter to single service if name provided
315
+ const toDeploy = name ? services.filter((s) => s === name) : services;
316
+ if (name && toDeploy.length === 0) {
317
+ output.error(`Service "${name}" not found in x402/`);
318
+ process.exit(1);
319
+ }
320
+ // Validate service names and ensure config entries
321
+ for (const svc of toDeploy) {
322
+ const nameError = validateServiceName(svc);
323
+ if (nameError) {
324
+ output.error(nameError);
325
+ process.exit(1);
326
+ }
327
+ if (!config.services[svc]) {
328
+ config.services[svc] = {
329
+ price: "0.001",
330
+ description: `${svc} service`,
331
+ };
332
+ }
333
+ }
334
+ const spin = output.spinner(`Deploying ${toDeploy.length} service(s)...`);
335
+ try {
336
+ // Read raw TypeScript source for each service.
337
+ // Bundling + security wrapping happens server-side in the builder Lambda.
338
+ const bundles = [];
339
+ for (const svc of toDeploy) {
340
+ const entrypoint = join(x402Dir, svc, "index.ts");
341
+ if (!existsSync(entrypoint)) {
342
+ spin.fail(`Missing handler: x402/${svc}/index.ts`);
343
+ process.exit(1);
344
+ }
345
+ const source = readFileSync(entrypoint, "utf-8");
346
+ bundles.push({ name: svc, source });
347
+ }
348
+ // Deploy via API (server-side build + deploy)
349
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/deploy`, {
350
+ method: "POST",
351
+ headers: authHeaders(),
352
+ body: JSON.stringify({
353
+ config,
354
+ bundles: bundles.map((b) => ({
355
+ name: b.name,
356
+ source: b.source,
357
+ })),
358
+ }),
359
+ });
360
+ const result = await handleResponse(res);
361
+ spin.succeed(`Deployed ${result.deployments.length} service(s)`);
362
+ // Print results
363
+ console.log();
364
+ for (const dep of result.deployments) {
365
+ const svcConfig = config.services[dep.name];
366
+ output.label(" Service", dep.name);
367
+ output.label(" URL", dep.url);
368
+ output.label(" Price", `$${svcConfig?.price ?? "0.001"} ${svcConfig?.currency ?? "USDC"}/req`);
369
+ output.label(" Version", String(dep.version));
370
+ console.log();
371
+ }
372
+ }
373
+ catch (err) {
374
+ spin.fail("Deploy failed");
375
+ output.error(err instanceof Error ? err.message : String(err));
376
+ process.exit(1);
377
+ }
378
+ }
379
+ /**
380
+ * bankr x402 list — List deployed services
381
+ */
382
+ export async function x402ListCommand() {
383
+ const spin = output.spinner("Fetching endpoints...");
384
+ try {
385
+ const res = await fetch(`${getApiUrl()}/x402/endpoints`, {
386
+ headers: authHeaders(),
387
+ });
388
+ const result = await handleResponse(res);
389
+ spin.stop();
390
+ if (result.endpoints.length === 0) {
391
+ output.info("No deployed endpoints. Run 'bankr x402 deploy' to get started.");
392
+ return;
393
+ }
394
+ for (const ep of result.endpoints) {
395
+ const route = ep.routes[0];
396
+ const status = ep.status === "active"
397
+ ? output.fmt.success("active")
398
+ : output.fmt.warn(ep.status);
399
+ console.log(` ${output.fmt.brand(ep.name)} ${status} v${ep.version} $${route?.price ?? "?"} ${route?.currency ?? "USDC"} ${ep.totalRequests} reqs $${ep.totalRevenueUsd} earned`);
400
+ output.dim(` ${ep.description}`);
401
+ }
402
+ }
403
+ catch (err) {
404
+ spin.fail("Failed to list endpoints");
405
+ output.error(err instanceof Error ? err.message : String(err));
406
+ process.exit(1);
407
+ }
408
+ }
409
+ /**
410
+ * bankr x402 pause/resume <name>
411
+ */
412
+ export async function x402PauseResumeCommand(name, action) {
413
+ const status = action === "pause" ? "paused" : "active";
414
+ const spin = output.spinner(`${action === "pause" ? "Pausing" : "Resuming"} ${name}...`);
415
+ try {
416
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/${name}`, {
417
+ method: "PATCH",
418
+ headers: authHeaders(),
419
+ body: JSON.stringify({ status }),
420
+ });
421
+ await handleResponse(res);
422
+ spin.succeed(`${name} ${action === "pause" ? "paused" : "resumed"}`);
423
+ }
424
+ catch (err) {
425
+ spin.fail(`Failed to ${action} ${name}`);
426
+ output.error(err instanceof Error ? err.message : String(err));
427
+ process.exit(1);
428
+ }
429
+ }
430
+ /**
431
+ * bankr x402 delete <name>
432
+ */
433
+ export async function x402DeleteCommand(name) {
434
+ const confirmed = await confirm({
435
+ message: `Delete endpoint "${name}"? This cannot be undone.`,
436
+ default: false,
437
+ theme: output.bankrTheme,
438
+ });
439
+ if (!confirmed)
440
+ return;
441
+ const spin = output.spinner(`Deleting ${name}...`);
442
+ try {
443
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/${name}`, {
444
+ method: "DELETE",
445
+ headers: authHeaders(),
446
+ });
447
+ await handleResponse(res);
448
+ spin.succeed(`${name} deleted`);
449
+ }
450
+ catch (err) {
451
+ spin.fail(`Failed to delete ${name}`);
452
+ output.error(err instanceof Error ? err.message : String(err));
453
+ process.exit(1);
454
+ }
455
+ }
456
+ /**
457
+ * bankr x402 revenue [name]
458
+ */
459
+ export async function x402RevenueCommand(name) {
460
+ if (!name) {
461
+ // Show all endpoints revenue summary
462
+ await x402ListCommand();
463
+ return;
464
+ }
465
+ const spin = output.spinner(`Fetching revenue for ${name}...`);
466
+ try {
467
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/revenue/${name}`, {
468
+ headers: authHeaders(),
469
+ });
470
+ const result = await handleResponse(res);
471
+ spin.stop();
472
+ console.log(`\n Revenue for ${output.fmt.brand(name)}\n`);
473
+ for (const [period, data] of Object.entries(result.revenue)) {
474
+ const label = period === "last7d"
475
+ ? "Last 7 days"
476
+ : period === "last30d"
477
+ ? "Last 30 days"
478
+ : "All time";
479
+ const earned = (data.totalUsd - data.bankrFeesUsd).toFixed(6);
480
+ console.log(` ${label.padEnd(14)} ${data.requests} reqs $${earned} earned $${data.bankrFeesUsd.toFixed(6)} fees`);
481
+ }
482
+ console.log();
483
+ }
484
+ catch (err) {
485
+ spin.fail("Failed to fetch revenue");
486
+ output.error(err instanceof Error ? err.message : String(err));
487
+ process.exit(1);
488
+ }
489
+ }
490
+ /**
491
+ * bankr x402 env set KEY=VALUE
492
+ */
493
+ export async function x402EnvSetCommand(keyValue) {
494
+ const eqIdx = keyValue.indexOf("=");
495
+ if (eqIdx === -1) {
496
+ output.error("Usage: bankr x402 env set KEY=VALUE");
497
+ process.exit(1);
498
+ }
499
+ const key = keyValue.slice(0, eqIdx);
500
+ const value = keyValue.slice(eqIdx + 1);
501
+ if (!key) {
502
+ output.error("Key cannot be empty");
503
+ process.exit(1);
504
+ }
505
+ const spin = output.spinner(`Setting ${key}...`);
506
+ try {
507
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env`, {
508
+ method: "POST",
509
+ headers: authHeaders(),
510
+ body: JSON.stringify({ vars: { [key]: value } }),
511
+ });
512
+ await handleResponse(res);
513
+ spin.succeed(`Set ${key}`);
514
+ }
515
+ catch (err) {
516
+ spin.fail(`Failed to set ${key}`);
517
+ output.error(err instanceof Error ? err.message : String(err));
518
+ process.exit(1);
519
+ }
520
+ }
521
+ /**
522
+ * bankr x402 env list
523
+ */
524
+ export async function x402EnvListCommand() {
525
+ const spin = output.spinner("Fetching env vars...");
526
+ try {
527
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env`, {
528
+ headers: authHeaders(),
529
+ });
530
+ const result = await handleResponse(res);
531
+ spin.stop();
532
+ if (result.vars.length === 0) {
533
+ output.info("No environment variables set.");
534
+ return;
535
+ }
536
+ for (const name of result.vars) {
537
+ console.log(` ${name}`);
538
+ }
539
+ }
540
+ catch (err) {
541
+ spin.fail("Failed to list env vars");
542
+ output.error(err instanceof Error ? err.message : String(err));
543
+ process.exit(1);
544
+ }
545
+ }
546
+ /**
547
+ * bankr x402 env unset KEY
548
+ */
549
+ export async function x402EnvUnsetCommand(key) {
550
+ const spin = output.spinner(`Removing ${key}...`);
551
+ try {
552
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/env/${key}`, {
553
+ method: "DELETE",
554
+ headers: authHeaders(),
555
+ });
556
+ await handleResponse(res);
557
+ spin.succeed(`Removed ${key}`);
558
+ }
559
+ catch (err) {
560
+ spin.fail(`Failed to remove ${key}`);
561
+ output.error(err instanceof Error ? err.message : String(err));
562
+ process.exit(1);
563
+ }
564
+ }
565
+ /**
566
+ * bankr x402 search <query> — Search the x402 service marketplace
567
+ *
568
+ * Public — no auth required. Uses vector search + keyword matching
569
+ * to find relevant paid API services.
570
+ */
571
+ export async function x402SearchCommand(queryParts) {
572
+ const query = queryParts.join(" ").trim();
573
+ if (!query) {
574
+ output.error("Usage: bankr x402 search <query>");
575
+ process.exit(1);
576
+ }
577
+ const spin = output.spinner("Searching services...");
578
+ try {
579
+ const params = new URLSearchParams({ q: query, limit: "10" });
580
+ const res = await fetch(`${getApiUrl()}/x402/endpoints/discover?${params}`);
581
+ const result = await handleResponse(res);
582
+ spin.stop();
583
+ if (result.services.length === 0) {
584
+ output.info(`No services found for "${query}"`);
585
+ return;
586
+ }
587
+ console.log();
588
+ output.dim(` Found ${result.services.length} service(s) for "${query}"`);
589
+ console.log();
590
+ for (const svc of result.services) {
591
+ const route = svc.routes[0];
592
+ const price = route ? `$${route.price} ${route.currency}` : "—";
593
+ const methods = route?.methods?.filter((m) => m !== "*").join(", ") || "ANY";
594
+ const score = svc.score
595
+ ? ` ${output.fmt.dim(`${(svc.score * 100).toFixed(0)}% match`)}`
596
+ : "";
597
+ console.log(` ${output.fmt.brand(svc.name)}${score}`);
598
+ if (svc.description) {
599
+ output.dim(` ${svc.description}`);
600
+ }
601
+ const url = svc.url ?? `https://x402.bankr.bot/${svc.slug}`;
602
+ console.log(` ${output.fmt.dim("Method:")} ${methods} ${output.fmt.dim("Price:")} ${price} ${output.fmt.dim("Network:")} ${route?.network ?? "base"}`);
603
+ output.dim(` ${url}`);
604
+ // Show schema if available
605
+ const qp = route?.schema?.queryParams ??
606
+ (methods === "GET" ? route?.schema?.input : undefined);
607
+ const body = route?.schema?.body ??
608
+ (methods !== "GET" ? route?.schema?.input : undefined);
609
+ if (qp) {
610
+ const fields = Object.entries(qp)
611
+ .map(([k, v]) => `${k}=${v}`)
612
+ .join("&");
613
+ output.dim(` ${output.fmt.dim("Params:")} ?${fields}`);
614
+ }
615
+ if (body) {
616
+ const fields = Object.entries(body)
617
+ .map(([k, v]) => `${k}: ${v}`)
618
+ .join(", ");
619
+ output.dim(` ${output.fmt.dim("Body:")} { ${fields} }`);
620
+ }
621
+ if (route?.schema?.output) {
622
+ const fields = Object.entries(route.schema.output)
623
+ .map(([k, v]) => `${k}: ${v}`)
624
+ .join(", ");
625
+ output.dim(` ${output.fmt.dim("Returns:")} { ${fields} }`);
626
+ }
627
+ console.log();
628
+ }
629
+ }
630
+ catch (err) {
631
+ spin.fail("Search failed");
632
+ output.error(err instanceof Error ? err.message : String(err));
633
+ process.exit(1);
634
+ }
635
+ }
636
+ //# 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("▸"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {