@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 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,25 @@ 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 ? "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
- * 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.
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
- export async function x402SearchCommand(queryParts) {
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
- output.dim(` Found ${result.services.length} service(s) for "${query}"`);
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
- 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();
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {