@bankr/cli 0.1.3 → 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 +439 -133
- package/dist/commands/balances.d.ts +1 -0
- package/dist/commands/balances.js +7 -32
- package/dist/commands/llm.js +74 -18
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +139 -33
- package/dist/commands/portfolio.d.ts +9 -0
- package/dist/commands/portfolio.js +134 -0
- package/dist/commands/tokens.d.ts +7 -0
- package/dist/commands/tokens.js +60 -0
- package/dist/commands/transfer.d.ts +8 -0
- package/dist/commands/transfer.js +80 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +116 -0
- package/dist/commands/x402.d.ts +69 -0
- package/dist/commands/x402.js +636 -0
- package/dist/lib/api.d.ts +59 -2
- package/dist/lib/api.js +34 -8
- package/dist/lib/chains.d.ts +5 -0
- package/dist/lib/chains.js +39 -0
- package/dist/lib/output.d.ts +4 -0
- package/dist/lib/output.js +19 -0
- package/package.json +1 -1
|
@@ -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
|