@bankr/cli 0.2.2 → 0.2.4
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 +4 -1
- package/dist/commands/x402.d.ts +3 -7
- package/dist/commands/x402.js +185 -55
- package/dist/lib/updateCheck.d.ts +8 -0
- package/dist/lib/updateCheck.js +78 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ import { tokensSearchCommand, tokensInfoCommand } from "./commands/tokens.js";
|
|
|
25
25
|
import { x402InitCommand, x402AddCommand, x402ConfigureCommand, x402DeployCommand, x402ListCommand, x402PauseResumeCommand, x402DeleteCommand, x402RevenueCommand, x402EnvSetCommand, x402EnvListCommand, x402EnvUnsetCommand, x402SearchCommand, } from "./commands/x402.js";
|
|
26
26
|
import { profileViewCommand, profileCreateCommand, profileUpdateCommand, profileDeleteCommand, profileAddUpdateCommand, } from "./commands/profile.js";
|
|
27
27
|
import * as output from "./lib/output.js";
|
|
28
|
+
import { checkForUpdate } from "./lib/updateCheck.js";
|
|
28
29
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
29
30
|
/**
|
|
30
31
|
* Read prompt text from stdin (piped) or interactively.
|
|
@@ -771,7 +772,8 @@ x402EnvCmd
|
|
|
771
772
|
x402Cmd
|
|
772
773
|
.command("search [query...]")
|
|
773
774
|
.description("Search the x402 service marketplace (no auth required)")
|
|
774
|
-
.
|
|
775
|
+
.option("--raw", "Output raw JSON (unformatted)")
|
|
776
|
+
.action(async (query, opts) => x402SearchCommand(query, { raw: opts.raw }));
|
|
775
777
|
// Default: treat unrecognized arguments as a prompt
|
|
776
778
|
program
|
|
777
779
|
.arguments("[text...]")
|
|
@@ -797,6 +799,7 @@ async function main() {
|
|
|
797
799
|
output.error(err.message);
|
|
798
800
|
process.exit(1);
|
|
799
801
|
}
|
|
802
|
+
await checkForUpdate(pkg.version);
|
|
800
803
|
}
|
|
801
804
|
main();
|
|
802
805
|
//# sourceMappingURL=cli.js.map
|
package/dist/commands/x402.d.ts
CHANGED
|
@@ -59,11 +59,7 @@ export declare function x402EnvListCommand(): Promise<void>;
|
|
|
59
59
|
* bankr x402 env unset KEY
|
|
60
60
|
*/
|
|
61
61
|
export declare function x402EnvUnsetCommand(key: string): Promise<void>;
|
|
62
|
-
|
|
63
|
-
|
|
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>;
|
|
62
|
+
export declare function x402SearchCommand(queryParts: string[], opts?: {
|
|
63
|
+
raw?: boolean;
|
|
64
|
+
}): Promise<void>;
|
|
69
65
|
//# sourceMappingURL=x402.d.ts.map
|
package/dist/commands/x402.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
|
+
import chalk from "chalk";
|
|
20
21
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
21
22
|
import * as output from "../lib/output.js";
|
|
22
23
|
import { getApiUrl, requireApiKey, readConfig, CLI_USER_AGENT, } from "../lib/config.js";
|
|
@@ -202,15 +203,27 @@ export default async function handler(req: Request): Promise<Response> {
|
|
|
202
203
|
}
|
|
203
204
|
`;
|
|
204
205
|
writeFileSync(join(serviceDir, "index.ts"), handlerContent);
|
|
205
|
-
// Build
|
|
206
|
-
const schema = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
214
|
+
? "Example body field"
|
|
215
|
+
: "Example query parameter",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
required: [isPost ? "field" : "param"],
|
|
219
|
+
},
|
|
220
|
+
output: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
result: { type: "string", description: "Result value" },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
214
227
|
// Add to config
|
|
215
228
|
let config = loadConfigFile(root);
|
|
216
229
|
if (!config) {
|
|
@@ -562,13 +575,158 @@ export async function x402EnvUnsetCommand(key) {
|
|
|
562
575
|
process.exit(1);
|
|
563
576
|
}
|
|
564
577
|
}
|
|
578
|
+
// ── Schema display helpers ──────────────────────────────────────────────
|
|
579
|
+
const P = " │";
|
|
565
580
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
|
|
569
|
-
|
|
581
|
+
* Detects whether a schema object is JSON Schema format (has `type` and `properties`)
|
|
582
|
+
* vs the legacy flat Record<string,string> format.
|
|
583
|
+
*/
|
|
584
|
+
function isJsonSchema(obj) {
|
|
585
|
+
return (typeof obj === "object" &&
|
|
586
|
+
obj !== null &&
|
|
587
|
+
"type" in obj &&
|
|
588
|
+
obj.type === "object" &&
|
|
589
|
+
"properties" in obj);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Renders a JSON Schema properties table.
|
|
593
|
+
* Output: lines like " │ symbol* string Token symbol"
|
|
594
|
+
*/
|
|
595
|
+
function renderJsonSchemaProps(schema) {
|
|
596
|
+
if (!schema.properties)
|
|
597
|
+
return [];
|
|
598
|
+
const required = new Set(schema.required ?? []);
|
|
599
|
+
const entries = Object.entries(schema.properties);
|
|
600
|
+
// Calculate column widths
|
|
601
|
+
let maxName = 0;
|
|
602
|
+
let maxType = 0;
|
|
603
|
+
for (const [name, prop] of entries) {
|
|
604
|
+
const nameLen = name.length + (required.has(name) ? 1 : 0);
|
|
605
|
+
if (nameLen > maxName)
|
|
606
|
+
maxName = nameLen;
|
|
607
|
+
const typeStr = prop.type + (prop.enum ? ` (${prop.enum.join("|")})` : "");
|
|
608
|
+
if (typeStr.length > maxType)
|
|
609
|
+
maxType = typeStr.length;
|
|
610
|
+
}
|
|
611
|
+
const lines = [];
|
|
612
|
+
for (const [name, prop] of entries) {
|
|
613
|
+
const req = required.has(name) ? chalk.red("*") : " ";
|
|
614
|
+
const nameStr = `${chalk.cyan(name)}${req}`;
|
|
615
|
+
const namePad = " ".repeat(Math.max(0, maxName + 1 - name.length - (required.has(name) ? 1 : 0)));
|
|
616
|
+
const typeStr = prop.type + (prop.enum ? ` (${prop.enum.join("|")})` : "");
|
|
617
|
+
const typePad = " ".repeat(Math.max(0, maxType + 2 - typeStr.length));
|
|
618
|
+
const desc = prop.description ? chalk.dim(prop.description) : "";
|
|
619
|
+
lines.push(`${P} ${nameStr}${namePad}${chalk.yellow(typeStr)}${typePad}${desc}`);
|
|
620
|
+
}
|
|
621
|
+
return lines;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Renders a legacy flat Record<string,string> schema as a simple key: value list.
|
|
625
|
+
* Pretty-prints nested objects/arrays with proper indentation.
|
|
570
626
|
*/
|
|
571
|
-
|
|
627
|
+
function renderLegacySchema(obj) {
|
|
628
|
+
const lines = [];
|
|
629
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
630
|
+
if (typeof v === "object" && v !== null) {
|
|
631
|
+
const pretty = JSON.stringify(v, null, 2);
|
|
632
|
+
const indented = pretty
|
|
633
|
+
.split("\n")
|
|
634
|
+
.map((line, i) => i === 0 ? line : `${P} ${" ".repeat(k.length)}${line}`)
|
|
635
|
+
.join("\n");
|
|
636
|
+
lines.push(`${P} ${chalk.cyan(k)} ${chalk.dim(indented)}`);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
lines.push(`${P} ${chalk.cyan(k)} ${chalk.dim(String(v))}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return lines;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Resolves input/output schemas from a route, handling both JSON Schema
|
|
646
|
+
* and legacy flat formats for backward compatibility.
|
|
647
|
+
*/
|
|
648
|
+
function resolveSchemas(schema) {
|
|
649
|
+
if (!schema)
|
|
650
|
+
return { input: undefined, output: undefined };
|
|
651
|
+
// Prefer new JSON Schema `input`/`output` fields
|
|
652
|
+
const inputSchema = schema.input ?? schema.queryParams ?? schema.body;
|
|
653
|
+
const outputSchema = schema.output;
|
|
654
|
+
return { input: inputSchema, output: outputSchema };
|
|
655
|
+
}
|
|
656
|
+
function printServiceFormatted(svc) {
|
|
657
|
+
const route = svc.routes[0];
|
|
658
|
+
const methods = route?.methods?.filter((m) => m !== "*").join(", ") || "ANY";
|
|
659
|
+
const url = svc.url ?? `https://x402.bankr.bot/${svc.slug}`;
|
|
660
|
+
const isGet = methods === "GET";
|
|
661
|
+
// ── Card header ──
|
|
662
|
+
console.log(` ${chalk.dim("┌")} ${output.fmt.brandBold(svc.name)}`);
|
|
663
|
+
if (svc.description) {
|
|
664
|
+
console.log(`${P} ${chalk.dim(svc.description)}`);
|
|
665
|
+
}
|
|
666
|
+
console.log(`${P}`);
|
|
667
|
+
// ── Metadata ──
|
|
668
|
+
console.log(`${P} ${chalk.dim("URL")} ${url}`);
|
|
669
|
+
console.log(`${P} ${chalk.dim("Method")} ${chalk.bold(methods)}`);
|
|
670
|
+
console.log(`${P} ${chalk.dim("Price")} ${chalk.green(`$${route?.price ?? "?"} ${route?.currency ?? "USDC"}`)}`);
|
|
671
|
+
console.log(`${P} ${chalk.dim("Network")} ${route?.network ?? "base"}`);
|
|
672
|
+
// ── Schema ──
|
|
673
|
+
const { input: inputSchema, output: outputSchema } = resolveSchemas(route?.schema);
|
|
674
|
+
if (inputSchema || outputSchema) {
|
|
675
|
+
console.log(`${P}`);
|
|
676
|
+
if (inputSchema) {
|
|
677
|
+
const label = isGet ? "Input (query params)" : "Input (JSON body)";
|
|
678
|
+
console.log(`${P} ${chalk.bold(label)}`);
|
|
679
|
+
if (isJsonSchema(inputSchema)) {
|
|
680
|
+
for (const line of renderJsonSchemaProps(inputSchema)) {
|
|
681
|
+
console.log(line);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else if (typeof inputSchema === "object" && inputSchema !== null) {
|
|
685
|
+
for (const line of renderLegacySchema(inputSchema)) {
|
|
686
|
+
console.log(line);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (outputSchema) {
|
|
691
|
+
console.log(`${P}`);
|
|
692
|
+
console.log(`${P} ${chalk.bold("Output")}`);
|
|
693
|
+
if (isJsonSchema(outputSchema)) {
|
|
694
|
+
for (const line of renderJsonSchemaProps(outputSchema)) {
|
|
695
|
+
console.log(line);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else if (typeof outputSchema === "object" && outputSchema !== null) {
|
|
699
|
+
for (const line of renderLegacySchema(outputSchema)) {
|
|
700
|
+
console.log(line);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// ── Example ──
|
|
706
|
+
if (inputSchema && isJsonSchema(inputSchema) && inputSchema.properties) {
|
|
707
|
+
console.log(`${P}`);
|
|
708
|
+
const props = Object.entries(inputSchema.properties);
|
|
709
|
+
if (isGet) {
|
|
710
|
+
const qs = props.map(([k, p]) => `${k}=<${p.type}>`).join("&");
|
|
711
|
+
console.log(`${P} ${chalk.dim("Example")} ${chalk.dim("GET")} ${url}?${qs}`);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
const bodyObj = {};
|
|
715
|
+
for (const [k, p] of props)
|
|
716
|
+
bodyObj[k] = `<${p.type}>`;
|
|
717
|
+
console.log(`${P} ${chalk.dim("Example")} ${chalk.dim("POST")} ${url}`);
|
|
718
|
+
console.log(`${P} ${chalk.dim(JSON.stringify(bodyObj))}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// ── Tags ──
|
|
722
|
+
if (svc.tags?.length) {
|
|
723
|
+
console.log(`${P}`);
|
|
724
|
+
console.log(`${P} ${svc.tags.map((t) => chalk.dim(`#${t}`)).join(" ")}`);
|
|
725
|
+
}
|
|
726
|
+
console.log(` ${chalk.dim("└" + "─".repeat(60))}`);
|
|
727
|
+
console.log();
|
|
728
|
+
}
|
|
729
|
+
export async function x402SearchCommand(queryParts, opts = {}) {
|
|
572
730
|
const query = queryParts.join(" ").trim();
|
|
573
731
|
if (!query) {
|
|
574
732
|
output.error("Usage: bankr x402 search <query>");
|
|
@@ -580,51 +738,23 @@ export async function x402SearchCommand(queryParts) {
|
|
|
580
738
|
const res = await fetch(`${getApiUrl()}/x402/endpoints/discover?${params}`);
|
|
581
739
|
const result = await handleResponse(res);
|
|
582
740
|
spin.stop();
|
|
583
|
-
|
|
741
|
+
// Filter low-relevance results client-side
|
|
742
|
+
const MIN_DISPLAY_SCORE = 0.15;
|
|
743
|
+
const services = result.services.filter((s) => !s.score || s.score >= MIN_DISPLAY_SCORE);
|
|
744
|
+
if (services.length === 0) {
|
|
584
745
|
output.info(`No services found for "${query}"`);
|
|
585
746
|
return;
|
|
586
747
|
}
|
|
748
|
+
// Raw mode: dump JSON and exit
|
|
749
|
+
if (opts.raw) {
|
|
750
|
+
console.log(JSON.stringify(services, null, 2));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
587
753
|
console.log();
|
|
588
|
-
|
|
589
|
-
console.log();
|
|
590
|
-
for (const svc of
|
|
591
|
-
|
|
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();
|
|
754
|
+
console.log(` ${chalk.dim(`Found ${services.length} service(s) for`)} "${query}"`);
|
|
755
|
+
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
756
|
+
for (const svc of services) {
|
|
757
|
+
printServiceFormatted(svc);
|
|
628
758
|
}
|
|
629
759
|
}
|
|
630
760
|
catch (err) {
|
|
@@ -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
|