@bankr/cli 0.2.2 → 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 +4 -1
- package/dist/commands/x402.d.ts +3 -7
- package/dist/commands/x402.js +178 -53
- 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,25 @@ 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 ? "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
|
+
};
|
|
214
225
|
// Add to config
|
|
215
226
|
let config = loadConfigFile(root);
|
|
216
227
|
if (!config) {
|
|
@@ -562,13 +573,158 @@ export async function x402EnvUnsetCommand(key) {
|
|
|
562
573
|
process.exit(1);
|
|
563
574
|
}
|
|
564
575
|
}
|
|
576
|
+
// ── Schema display helpers ──────────────────────────────────────────────
|
|
577
|
+
const P = " │";
|
|
565
578
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
|
|
569
|
-
|
|
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.
|
|
570
624
|
*/
|
|
571
|
-
|
|
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 = {}) {
|
|
572
728
|
const query = queryParts.join(" ").trim();
|
|
573
729
|
if (!query) {
|
|
574
730
|
output.error("Usage: bankr x402 search <query>");
|
|
@@ -584,47 +740,16 @@ export async function x402SearchCommand(queryParts) {
|
|
|
584
740
|
output.info(`No services found for "${query}"`);
|
|
585
741
|
return;
|
|
586
742
|
}
|
|
743
|
+
// Raw mode: dump JSON and exit
|
|
744
|
+
if (opts.raw) {
|
|
745
|
+
console.log(JSON.stringify(result.services, null, 2));
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
587
748
|
console.log();
|
|
588
|
-
|
|
589
|
-
console.log();
|
|
749
|
+
console.log(` ${chalk.dim(`Found ${result.services.length} service(s) for`)} "${query}"`);
|
|
750
|
+
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
590
751
|
for (const svc of result.services) {
|
|
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();
|
|
752
|
+
printServiceFormatted(svc);
|
|
628
753
|
}
|
|
629
754
|
}
|
|
630
755
|
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
|