@bankr/cli 0.3.1 → 0.3.11

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.
@@ -20,7 +20,7 @@ import { join } from "node:path";
20
20
  import chalk from "chalk";
21
21
  import { input, select, confirm } from "@inquirer/prompts";
22
22
  import * as output from "../lib/output.js";
23
- import { getApiUrl, requireApiKey, readConfig, CLI_USER_AGENT, } from "../lib/config.js";
23
+ import { getApiUrl, requireApiKey, CLI_USER_AGENT } from "../lib/config.js";
24
24
  // ── Config file helpers ─────────────────────────────────────────────────
25
25
  const CONFIG_FILENAME = "bankr.x402.json";
26
26
  function findProjectRoot() {
@@ -50,16 +50,11 @@ function saveConfigFile(projectRoot, config) {
50
50
  }
51
51
  // ── API helpers ─────────────────────────────────────────────────────────
52
52
  function authHeaders() {
53
- const headers = {
53
+ return {
54
54
  "X-API-Key": requireApiKey(),
55
55
  "Content-Type": "application/json",
56
56
  "User-Agent": CLI_USER_AGENT,
57
57
  };
58
- const config = readConfig();
59
- if (config.partnerKey) {
60
- headers["X-Partner-Key"] = config.partnerKey;
61
- }
62
- return headers;
63
58
  }
64
59
  async function handleResponse(res) {
65
60
  if (!res.ok) {
@@ -279,10 +274,7 @@ export async function x402ConfigureCommand(name) {
279
274
  const currency = "USDC";
280
275
  const network = await select({
281
276
  message: "Network:",
282
- choices: [
283
- { name: "Base", value: "base" },
284
- { name: "Base Sepolia (testnet)", value: "base-sepolia" },
285
- ],
277
+ choices: [{ name: "Base", value: "base" }],
286
278
  default: existing.network ?? "base",
287
279
  theme: output.bankrTheme,
288
280
  });
@@ -697,62 +689,78 @@ function resolveSchemas(schema) {
697
689
  const outputSchema = schema.output;
698
690
  return { input: inputSchema, output: outputSchema };
699
691
  }
700
- function printServiceFormatted(svc) {
692
+ /**
693
+ * Builds the verbose multi-line card for a service. Returns the rendered
694
+ * string. Pass `rank` and `total` to render a `#N of M` badge in the header
695
+ * (used by the search command so reading top-to-bottom shows ascending rank
696
+ * with the top match at the bottom).
697
+ */
698
+ function formatServiceCard(svc, rank, total) {
701
699
  const route = svc.routes[0];
702
700
  const methods = route?.methods?.filter((m) => m !== "*").join(", ") || "ANY";
703
701
  const url = svc.url ?? `https://x402.bankr.bot/${svc.slug}`;
704
702
  const isGet = methods === "GET";
705
- // ── Header ──
706
- console.log(` ${output.fmt.brandBold(svc.name)}`);
707
- if (svc.description) {
708
- console.log(` ${chalk.dim(svc.description)}`);
709
- }
710
- console.log("");
703
+ const lines = [];
704
+ // ── Header (with rank badge) ──
705
+ const rankBadge = rank !== undefined
706
+ ? `${chalk.bold(`#${rank}${total ? ` of ${total}` : ""}`)} `
707
+ : "";
708
+ const scoreBadge = svc.score !== undefined
709
+ ? ` ${chalk.dim(`(score ${svc.score.toFixed(2)})`)}`
710
+ : "";
711
+ lines.push(` ${rankBadge}${output.fmt.brandBold(svc.name)}${scoreBadge}`);
712
+ if (svc.description)
713
+ lines.push(` ${chalk.dim(svc.description)}`);
714
+ lines.push("");
711
715
  // ── Metadata ──
712
- console.log(` ${chalk.dim("URL")} ${url}`);
713
- console.log(` ${chalk.dim("Method")} ${chalk.bold(methods)}`);
714
- console.log(` ${chalk.dim("Price")} ${chalk.green(`$${route?.price ?? "?"} ${route?.currency ?? "USDC"}`)}`);
715
- console.log(` ${chalk.dim("Network")} ${route?.network ?? "base"}`);
716
+ lines.push(` ${chalk.dim("URL")} ${url}`);
717
+ lines.push(` ${chalk.dim("Method")} ${chalk.bold(methods)}`);
718
+ lines.push(` ${chalk.dim("Price")} ${chalk.green(`$${route?.price ?? "?"} ${route?.currency ?? "USDC"}`)}`);
719
+ lines.push(` ${chalk.dim("Network")} ${route?.network ?? "base"}`);
716
720
  // ── Schema ──
717
721
  const { input: inputSchema, output: outputSchema } = resolveSchemas(route?.schema);
718
722
  if (inputSchema || outputSchema) {
719
- console.log("");
723
+ lines.push("");
720
724
  if (inputSchema) {
721
725
  const label = isGet ? "Input (query params)" : "Input (JSON body)";
722
- console.log(` ${chalk.bold(label)}`);
726
+ lines.push(` ${chalk.bold(label)}`);
723
727
  if (isJsonSchema(inputSchema)) {
724
728
  for (const line of renderJsonSchemaProps(inputSchema)) {
725
- console.log(line);
729
+ lines.push(line);
726
730
  }
727
731
  }
728
732
  else if (typeof inputSchema === "object" && inputSchema !== null) {
729
733
  for (const line of renderLegacySchema(inputSchema)) {
730
- console.log(line);
734
+ lines.push(line);
731
735
  }
732
736
  }
733
737
  }
734
738
  if (outputSchema) {
735
- console.log("");
736
- console.log(` ${chalk.bold("Output")}`);
739
+ lines.push("");
740
+ lines.push(` ${chalk.bold("Output")}`);
737
741
  if (isJsonSchema(outputSchema)) {
738
742
  for (const line of renderJsonSchemaProps(outputSchema)) {
739
- console.log(line);
743
+ lines.push(line);
740
744
  }
741
745
  }
742
746
  else if (typeof outputSchema === "object" && outputSchema !== null) {
743
747
  for (const line of renderLegacySchema(outputSchema)) {
744
- console.log(line);
748
+ lines.push(line);
745
749
  }
746
750
  }
747
751
  }
748
752
  }
749
753
  // ── Tags ──
750
754
  if (svc.tags?.length) {
751
- console.log("");
752
- console.log(` ${svc.tags.map((t) => chalk.dim(`#${t}`)).join(" ")}`);
755
+ lines.push("");
756
+ lines.push(` ${svc.tags.map((t) => chalk.dim(`#${t}`)).join(" ")}`);
753
757
  }
754
- console.log(` ${chalk.dim("─".repeat(60))}`);
755
- console.log("");
758
+ lines.push(` ${chalk.dim("─".repeat(60))}`);
759
+ lines.push("");
760
+ return lines.join("\n");
761
+ }
762
+ function printServiceFormatted(svc, rank, total) {
763
+ console.log(formatServiceCard(svc, rank, total));
756
764
  }
757
765
  // ── Schema ────────────────────────────────────────────────────────────
758
766
  export async function x402SchemaCommand(url, opts = {}) {
@@ -1144,8 +1152,7 @@ export async function x402CallCommand(url, opts = {}) {
1144
1152
  resolvedPrice = parseFloat(schema.price);
1145
1153
  }
1146
1154
  }
1147
- // Confirm payment unless --yes
1148
- if (!opts.yes && !opts.interactive) {
1155
+ if (!opts.yes && !opts.interactive && !output.isNonInteractive()) {
1149
1156
  const priceDisplay = resolvedPrice !== null
1150
1157
  ? chalk.green(`$${resolvedPrice} USDC`)
1151
1158
  : chalk.green(`up to $${maxPaymentUsd} USDC`);
@@ -1235,7 +1242,7 @@ export async function x402SearchCommand(queryParts, opts = {}) {
1235
1242
  }
1236
1243
  const spin = output.spinner("Searching services...");
1237
1244
  try {
1238
- const params = new URLSearchParams({ q: query, limit: "10" });
1245
+ const params = new URLSearchParams({ q: query, limit: "50" });
1239
1246
  const res = await fetch(`${getApiUrl()}/x402/endpoints/discover?${params}`);
1240
1247
  const result = await handleResponse(res);
1241
1248
  spin.stop();
@@ -1246,16 +1253,25 @@ export async function x402SearchCommand(queryParts, opts = {}) {
1246
1253
  output.info(`No services found for "${query}"`);
1247
1254
  return;
1248
1255
  }
1249
- // Raw mode: dump JSON and exit
1256
+ // Raw mode: dump JSON and exit (preserves natural rank order for AI
1257
+ // agents and pipelines — most relevant first).
1250
1258
  if (opts.raw) {
1251
1259
  console.log(JSON.stringify(services, null, 2));
1252
1260
  return;
1253
1261
  }
1262
+ // Print results in REVERSE rank order so the cursor parks on the most
1263
+ // relevant result (rank #1) at the bottom of the buffer. Each card has
1264
+ // a `#N of M` badge so the rank is always visible regardless of which
1265
+ // direction you read.
1254
1266
  console.log();
1255
- console.log(` ${chalk.dim(`Found ${services.length} service(s) for`)} "${query}"`);
1267
+ console.log(` ${chalk.dim(`Found ${services.length} service(s) for`)} "${query}" ${chalk.dim("(scrolling order: lowest rank first, top match at the bottom)")}`);
1256
1268
  console.log(` ${chalk.dim("─".repeat(60))}`);
1257
- for (const svc of services) {
1258
- printServiceFormatted(svc);
1269
+ console.log();
1270
+ const total = services.length;
1271
+ // Iterate in reverse: start with the lowest-ranked result, end on rank #1.
1272
+ for (let i = total - 1; i >= 0; i--) {
1273
+ const svc = services[i];
1274
+ printServiceFormatted(svc, i + 1, total);
1259
1275
  }
1260
1276
  }
1261
1277
  catch (err) {
package/dist/lib/api.d.ts CHANGED
@@ -384,16 +384,20 @@ export interface AgentProfileResponse {
384
384
  content: string;
385
385
  createdAt: string;
386
386
  }>;
387
+ tags?: string[];
387
388
  approved: boolean;
389
+ isPublished?: boolean;
388
390
  createdAt: string;
389
391
  }
392
+ export declare function getOwnProfiles(): Promise<AgentProfileResponse[]>;
393
+ /** @deprecated Use getOwnProfiles() — returns first profile for backward compat */
390
394
  export declare function getOwnProfile(): Promise<AgentProfileResponse>;
391
395
  export declare function createProfile(data: Record<string, unknown>): Promise<AgentProfileResponse>;
392
- export declare function updateProfile(data: Record<string, unknown>): Promise<AgentProfileResponse>;
393
- export declare function deleteProfile(): Promise<{
396
+ export declare function updateProfile(slug: string, data: Record<string, unknown>): Promise<AgentProfileResponse>;
397
+ export declare function deleteProfile(slug: string): Promise<{
394
398
  success: boolean;
395
399
  }>;
396
- export declare function addProjectUpdate(data: {
400
+ export declare function addProjectUpdate(slug: string, data: {
397
401
  title: string;
398
402
  content: string;
399
403
  }): Promise<AgentProfileResponse>;
@@ -422,6 +426,49 @@ export interface TransferResponse {
422
426
  error?: string;
423
427
  }
424
428
  export declare function transfer(request: TransferRequest): Promise<TransferResponse>;
429
+ export interface SwapQuoteRequest {
430
+ fromChain: string;
431
+ fromToken: string;
432
+ toChain: string;
433
+ toToken: string;
434
+ amount: string;
435
+ }
436
+ export interface SwapQuoteSide {
437
+ chain: string;
438
+ token: string;
439
+ amount: string;
440
+ formattedAmount: string;
441
+ symbol: string;
442
+ decimals: number;
443
+ usdValue: string;
444
+ }
445
+ export interface SwapQuoteResponse {
446
+ from: SwapQuoteSide;
447
+ to: SwapQuoteSide;
448
+ minBuyAmount: string;
449
+ }
450
+ export interface SwapExecuteRequest extends SwapQuoteRequest {
451
+ minBuyAmount: string;
452
+ }
453
+ export interface SwapExecuteResponse {
454
+ success: boolean;
455
+ hash?: string;
456
+ error?: string;
457
+ amountReceived?: number;
458
+ amountReceivedRaw?: string;
459
+ amountSold?: number;
460
+ amountSoldRaw?: string;
461
+ }
462
+ export declare function getSwapQuote(request: SwapQuoteRequest): Promise<SwapQuoteResponse>;
463
+ export declare function executeSwap(request: SwapExecuteRequest): Promise<SwapExecuteResponse>;
464
+ export type RecipientType = "address" | "ens" | "twitter" | "farcaster";
465
+ export interface ResolveRecipientResponse {
466
+ resolved: boolean;
467
+ address?: string;
468
+ displayName?: string;
469
+ error?: string;
470
+ }
471
+ export declare function resolveRecipient(value: string, type: RecipientType): Promise<ResolveRecipientResponse>;
425
472
  export interface X402PayRequest {
426
473
  url: string;
427
474
  method?: string;
package/dist/lib/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CLI_USER_AGENT, getApiUrl, readConfig, requireApiKey, } from "./config.js";
1
+ import { CLI_USER_AGENT, getApiUrl, requireApiKey } from "./config.js";
2
2
  const TERMINAL_STATUSES = [
3
3
  "completed",
4
4
  "failed",
@@ -8,16 +8,11 @@ function isTerminalStatus(status) {
8
8
  return TERMINAL_STATUSES.includes(status);
9
9
  }
10
10
  function authHeaders() {
11
- const headers = {
11
+ return {
12
12
  "X-API-Key": requireApiKey(),
13
13
  "Content-Type": "application/json",
14
14
  "User-Agent": CLI_USER_AGENT,
15
15
  };
16
- const config = readConfig();
17
- if (config.partnerKey) {
18
- headers["X-Partner-Key"] = config.partnerKey;
19
- }
20
- return headers;
21
16
  }
22
17
  async function handleResponse(res) {
23
18
  if (!res.ok) {
@@ -265,37 +260,42 @@ export async function claimTokenLaunchFees(tokenAddress) {
265
260
  });
266
261
  return handleResponse(res);
267
262
  }
268
- export async function getOwnProfile() {
269
- const res = await fetch(`${getApiUrl()}/agent/profile`, {
263
+ export async function getOwnProfiles() {
264
+ const res = await fetch(`${getApiUrl()}/agent/profile?multi=true`, {
270
265
  headers: authHeaders(),
271
266
  });
272
267
  return handleResponse(res);
273
268
  }
269
+ /** @deprecated Use getOwnProfiles() — returns first profile for backward compat */
270
+ export async function getOwnProfile() {
271
+ const profiles = await getOwnProfiles();
272
+ return profiles[0];
273
+ }
274
274
  export async function createProfile(data) {
275
- const res = await fetch(`${getApiUrl()}/agent/profile`, {
275
+ const res = await fetch(`${getApiUrl()}/agent/profile?multi=true`, {
276
276
  method: "POST",
277
277
  headers: authHeaders(),
278
278
  body: JSON.stringify(data),
279
279
  });
280
280
  return handleResponse(res);
281
281
  }
282
- export async function updateProfile(data) {
283
- const res = await fetch(`${getApiUrl()}/agent/profile`, {
282
+ export async function updateProfile(slug, data) {
283
+ const res = await fetch(`${getApiUrl()}/agent/profile/${encodeURIComponent(slug)}`, {
284
284
  method: "PUT",
285
285
  headers: authHeaders(),
286
286
  body: JSON.stringify(data),
287
287
  });
288
288
  return handleResponse(res);
289
289
  }
290
- export async function deleteProfile() {
291
- const res = await fetch(`${getApiUrl()}/agent/profile`, {
290
+ export async function deleteProfile(slug) {
291
+ const res = await fetch(`${getApiUrl()}/agent/profile/${encodeURIComponent(slug)}`, {
292
292
  method: "DELETE",
293
293
  headers: authHeaders(),
294
294
  });
295
295
  return handleResponse(res);
296
296
  }
297
- export async function addProjectUpdate(data) {
298
- const res = await fetch(`${getApiUrl()}/agent/profile/update`, {
297
+ export async function addProjectUpdate(slug, data) {
298
+ const res = await fetch(`${getApiUrl()}/agent/profile/${encodeURIComponent(slug)}/update`, {
299
299
  method: "POST",
300
300
  headers: authHeaders(),
301
301
  body: JSON.stringify(data),
@@ -321,6 +321,31 @@ export async function transfer(request) {
321
321
  });
322
322
  return handleResponse(res);
323
323
  }
324
+ export async function getSwapQuote(request) {
325
+ const res = await fetch(`${getApiUrl()}/wallet/swap-quote`, {
326
+ method: "POST",
327
+ headers: authHeaders(),
328
+ body: JSON.stringify(request),
329
+ });
330
+ return handleResponse(res);
331
+ }
332
+ export async function executeSwap(request) {
333
+ const res = await fetch(`${getApiUrl()}/wallet/swap`, {
334
+ method: "POST",
335
+ headers: authHeaders(),
336
+ body: JSON.stringify(request),
337
+ });
338
+ return handleResponse(res);
339
+ }
340
+ export async function resolveRecipient(value, type) {
341
+ const params = new URLSearchParams({ value, type });
342
+ const res = await fetch(`${getApiUrl()}/addresses/resolve?${params}`, {
343
+ headers: {
344
+ "User-Agent": CLI_USER_AGENT,
345
+ },
346
+ });
347
+ return handleResponse(res);
348
+ }
324
349
  export async function x402Pay(request) {
325
350
  const res = await fetch(`${getApiUrl()}/wallet/x402-pay`, {
326
351
  method: "POST",
@@ -5,7 +5,6 @@ export interface BankrConfig {
5
5
  llmKey?: string;
6
6
  llmUrl?: string;
7
7
  lastThreadId?: string;
8
- partnerKey?: string;
9
8
  sound?: SoundConfig;
10
9
  }
11
10
  export declare function setConfigPath(path: string): void;
@@ -44,5 +44,6 @@ export interface RemediationStep {
44
44
  * Used when the API returns a `remediation` array in error responses.
45
45
  */
46
46
  export declare function remediation(steps: RemediationStep[]): void;
47
+ export declare function isNonInteractive(): boolean;
47
48
  export declare function formatStatus(status: string): string;
48
49
  //# sourceMappingURL=output.d.ts.map
@@ -105,6 +105,9 @@ export function remediation(steps) {
105
105
  }
106
106
  console.log();
107
107
  }
108
+ export function isNonInteractive() {
109
+ return process.env.BANKR_NOT_INTERACTIVE === "1";
110
+ }
108
111
  export function formatStatus(status) {
109
112
  switch (status) {
110
113
  case "completed":
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Native-token sentinel (zero address). The API detects + sanitizes native
3
+ * tokens from this, so callers send it instead of a real contract address.
4
+ */
5
+ export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
6
+ /** Shorten an address for display, e.g. `0x8335…2913`. */
7
+ export declare function shortenAddress(address: string): string;
8
+ /**
9
+ * Resolve a token symbol or 0x address to `{ address, symbol }` on a chain:
10
+ * - a valid 0x address is used as-is (symbol shown shortened for display),
11
+ * - otherwise an exact, case-insensitive symbol match is looked up via
12
+ * `/tokens/search`, throwing a "Did you mean" suggestion list on no match.
13
+ *
14
+ * Native-token handling is caller-specific (a `--native` flag for transfer, a
15
+ * native-ticker/zero-address short-circuit for swap), so it lives in the command.
16
+ */
17
+ export declare function resolveTokenSymbol(tokenInput: string, chain: string): Promise<{
18
+ address: string;
19
+ symbol: string;
20
+ }>;
21
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { isAddress } from "viem";
2
+ import { searchTokens } from "./api.js";
3
+ import { CHAIN_IDS } from "./chains.js";
4
+ /**
5
+ * Native-token sentinel (zero address). The API detects + sanitizes native
6
+ * tokens from this, so callers send it instead of a real contract address.
7
+ */
8
+ export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
9
+ /** Shorten an address for display, e.g. `0x8335…2913`. */
10
+ export function shortenAddress(address) {
11
+ return `${address.slice(0, 6)}…${address.slice(-4)}`;
12
+ }
13
+ /**
14
+ * Resolve a token symbol or 0x address to `{ address, symbol }` on a chain:
15
+ * - a valid 0x address is used as-is (symbol shown shortened for display),
16
+ * - otherwise an exact, case-insensitive symbol match is looked up via
17
+ * `/tokens/search`, throwing a "Did you mean" suggestion list on no match.
18
+ *
19
+ * Native-token handling is caller-specific (a `--native` flag for transfer, a
20
+ * native-ticker/zero-address short-circuit for swap), so it lives in the command.
21
+ */
22
+ export async function resolveTokenSymbol(tokenInput, chain) {
23
+ const input = tokenInput.trim();
24
+ if (isAddress(input)) {
25
+ return { address: input, symbol: shortenAddress(input) };
26
+ }
27
+ const chainId = CHAIN_IDS[chain];
28
+ const res = await searchTokens(input, chainId);
29
+ const inputUpper = input.toUpperCase();
30
+ const exactMatch = res.tokens.find((t) => t.symbol.toUpperCase() === inputUpper);
31
+ if (!exactMatch) {
32
+ const available = res.tokens
33
+ .slice(0, 5)
34
+ .map((t) => `${t.symbol} (${t.address})`)
35
+ .join(", ");
36
+ throw new Error(`No exact match for "${input}" on ${chain}. Did you mean: ${available || "no results"}`);
37
+ }
38
+ return { address: exactMatch.address, symbol: exactMatch.symbol };
39
+ }
40
+ //# sourceMappingURL=tokens.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankr/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.11",
4
4
  "description": "Official CLI for the Bankr AI agent platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,11 +40,11 @@
40
40
  "url": "https://discord.gg/bankr"
41
41
  },
42
42
  "dependencies": {
43
- "@inquirer/prompts": "^8.2.0",
44
- "chalk": "^5.3.0",
45
- "commander": "^12.1.0",
46
- "open": "^10.1.0",
47
- "ora": "^8.1.0",
43
+ "@inquirer/prompts": "^8.4.2",
44
+ "chalk": "^5.6.2",
45
+ "commander": "^14.0.3",
46
+ "open": "^11.0.0",
47
+ "ora": "^9.4.0",
48
48
  "viem": "^2.37.2"
49
49
  }
50
50
  }