@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 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
- .action(async (query) => x402SearchCommand(query));
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
@@ -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
- * 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>;
62
+ export declare function x402SearchCommand(queryParts: string[], opts?: {
63
+ raw?: boolean;
64
+ }): Promise<void>;
69
65
  //# sourceMappingURL=x402.d.ts.map
@@ -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 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" };
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
- * 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.
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
- export async function x402SearchCommand(queryParts) {
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
- if (result.services.length === 0) {
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
- 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();
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {