@bankr/cli 0.2.6 → 0.2.8
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 +16 -1
- package/dist/commands/x402.d.ts +11 -0
- package/dist/commands/x402.js +474 -2
- package/dist/lib/api.d.ts +19 -0
- package/dist/lib/api.js +8 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import { submitCommand, submitJsonCommand } from "./commands/submit.js";
|
|
|
22
22
|
import { updateCommand } from "./commands/update.js";
|
|
23
23
|
import { whoamiCommand } from "./commands/whoami.js";
|
|
24
24
|
import { tokensSearchCommand, tokensInfoCommand } from "./commands/tokens.js";
|
|
25
|
-
import { x402InitCommand, x402AddCommand, x402ConfigureCommand, x402DeployCommand, x402ListCommand, x402PauseResumeCommand, x402DeleteCommand, x402RevenueCommand, x402EnvSetCommand, x402EnvListCommand, x402EnvUnsetCommand, x402SearchCommand, } from "./commands/x402.js";
|
|
25
|
+
import { x402InitCommand, x402AddCommand, x402ConfigureCommand, x402DeployCommand, x402ListCommand, x402PauseResumeCommand, x402DeleteCommand, x402RevenueCommand, x402EnvSetCommand, x402EnvListCommand, x402EnvUnsetCommand, x402SearchCommand, x402SchemaCommand, x402CallCommand, } 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
28
|
import { checkForUpdate } from "./lib/updateCheck.js";
|
|
@@ -774,6 +774,21 @@ x402Cmd
|
|
|
774
774
|
.description("Search the x402 service marketplace (no auth required)")
|
|
775
775
|
.option("--raw", "Output raw JSON (unformatted)")
|
|
776
776
|
.action(async (query, opts) => x402SearchCommand(query, { raw: opts.raw }));
|
|
777
|
+
x402Cmd
|
|
778
|
+
.command("schema <url>")
|
|
779
|
+
.description("View input/output schema for a Bankr x402 endpoint (no auth required)")
|
|
780
|
+
.option("--raw", "Output raw JSON (unformatted)")
|
|
781
|
+
.action(async (url, opts) => x402SchemaCommand(url, opts));
|
|
782
|
+
x402Cmd
|
|
783
|
+
.command("call <url>")
|
|
784
|
+
.description("Call an x402-protected endpoint with automatic USDC payment")
|
|
785
|
+
.option("-X, --method <method>", "HTTP method (GET, POST, PUT, DELETE)", "GET")
|
|
786
|
+
.option("-d, --body <json>", "JSON request body")
|
|
787
|
+
.option("--max-payment <usd>", "Maximum payment in USD (default: 1, max: 10)", "1")
|
|
788
|
+
.option("-i, --interactive", "Fetch endpoint schema and prompt for input values")
|
|
789
|
+
.option("-y, --yes", "Skip payment confirmation")
|
|
790
|
+
.option("--raw", "Output raw JSON (unformatted)")
|
|
791
|
+
.action(async (url, opts) => x402CallCommand(url, opts));
|
|
777
792
|
// Default: treat unrecognized arguments as a prompt
|
|
778
793
|
program
|
|
779
794
|
.arguments("[text...]")
|
package/dist/commands/x402.d.ts
CHANGED
|
@@ -59,6 +59,17 @@ 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
|
+
export declare function x402SchemaCommand(url: string, opts?: {
|
|
63
|
+
raw?: boolean;
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
export declare function x402CallCommand(url: string, opts?: {
|
|
66
|
+
method?: string;
|
|
67
|
+
body?: string;
|
|
68
|
+
maxPayment?: string;
|
|
69
|
+
yes?: boolean;
|
|
70
|
+
raw?: boolean;
|
|
71
|
+
interactive?: boolean;
|
|
72
|
+
}): Promise<void>;
|
|
62
73
|
export declare function x402SearchCommand(queryParts: string[], opts?: {
|
|
63
74
|
raw?: boolean;
|
|
64
75
|
}): Promise<void>;
|
package/dist/commands/x402.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* bankr x402 env list — List env var names
|
|
16
16
|
* bankr x402 env unset KEY — Remove env var
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import chalk from "chalk";
|
|
21
21
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
@@ -203,6 +203,22 @@ export default async function handler(req: Request): Promise<Response> {
|
|
|
203
203
|
}
|
|
204
204
|
`;
|
|
205
205
|
writeFileSync(join(serviceDir, "index.ts"), handlerContent);
|
|
206
|
+
// Ask about npm dependencies
|
|
207
|
+
const useDeps = await confirm({
|
|
208
|
+
message: "Will this service use npm packages?",
|
|
209
|
+
default: false,
|
|
210
|
+
theme: output.bankrTheme,
|
|
211
|
+
});
|
|
212
|
+
if (useDeps) {
|
|
213
|
+
const pkg = {
|
|
214
|
+
name: `x402-${name}`,
|
|
215
|
+
private: true,
|
|
216
|
+
dependencies: {},
|
|
217
|
+
};
|
|
218
|
+
writeFileSync(join(serviceDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
219
|
+
output.success(`Created x402/${name}/package.json`);
|
|
220
|
+
output.info(`Add packages with: cd x402/${name} && bun add <package>`);
|
|
221
|
+
}
|
|
206
222
|
// Build JSON Schema scaffold
|
|
207
223
|
const schema = {
|
|
208
224
|
input: {
|
|
@@ -356,7 +372,22 @@ export async function x402DeployCommand(name) {
|
|
|
356
372
|
process.exit(1);
|
|
357
373
|
}
|
|
358
374
|
const source = readFileSync(entrypoint, "utf-8");
|
|
359
|
-
|
|
375
|
+
// Read package.json dependencies if present
|
|
376
|
+
let dependencies;
|
|
377
|
+
const pkgPath = join(x402Dir, svc, "package.json");
|
|
378
|
+
if (existsSync(pkgPath)) {
|
|
379
|
+
try {
|
|
380
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
381
|
+
if (pkg.dependencies && Object.keys(pkg.dependencies).length > 0) {
|
|
382
|
+
dependencies = pkg.dependencies;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
spin.fail(`Invalid package.json in x402/${svc}/`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
bundles.push({ name: svc, source, dependencies });
|
|
360
391
|
}
|
|
361
392
|
// Deploy via API (server-side build + deploy)
|
|
362
393
|
const res = await fetch(`${getApiUrl()}/x402/endpoints/deploy`, {
|
|
@@ -367,6 +398,7 @@ export async function x402DeployCommand(name) {
|
|
|
367
398
|
bundles: bundles.map((b) => ({
|
|
368
399
|
name: b.name,
|
|
369
400
|
source: b.source,
|
|
401
|
+
...(b.dependencies && { dependencies: b.dependencies }),
|
|
370
402
|
})),
|
|
371
403
|
}),
|
|
372
404
|
});
|
|
@@ -709,6 +741,446 @@ function printServiceFormatted(svc) {
|
|
|
709
741
|
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
710
742
|
console.log("");
|
|
711
743
|
}
|
|
744
|
+
// ── Schema ────────────────────────────────────────────────────────────
|
|
745
|
+
export async function x402SchemaCommand(url, opts = {}) {
|
|
746
|
+
if (!url) {
|
|
747
|
+
output.error("Usage: bankr x402 schema <url>\n Example: bankr x402 schema https://x402.bankr.bot/0xabc.../my-service");
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
let parsed;
|
|
751
|
+
try {
|
|
752
|
+
parsed = new URL(url);
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
output.error("Invalid URL");
|
|
756
|
+
process.exit(1);
|
|
757
|
+
return; // unreachable but makes TS happy
|
|
758
|
+
}
|
|
759
|
+
if (!parsed.hostname.endsWith("x402.bankr.bot")) {
|
|
760
|
+
output.error("Schema lookup is only available for Bankr-hosted endpoints (x402.bankr.bot).");
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
764
|
+
if (parts.length < 2) {
|
|
765
|
+
output.error("Invalid Bankr x402 URL. Expected: https://x402.bankr.bot/<address>/<service>");
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
const spin = output.spinner("Fetching endpoint schema...");
|
|
769
|
+
try {
|
|
770
|
+
const res = await fetch(`${getApiUrl()}/x402/endpoints/schema/${parts[0]}/${parts[1]}`);
|
|
771
|
+
if (res.status === 404) {
|
|
772
|
+
spin.fail("Endpoint not found");
|
|
773
|
+
output.error(`No active endpoint found for ${parts[0]}/${parts[1]}`);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
const result = await handleResponse(res);
|
|
777
|
+
spin.stop();
|
|
778
|
+
if (opts.raw) {
|
|
779
|
+
console.log(JSON.stringify(result, null, 2));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
// Header
|
|
783
|
+
console.log();
|
|
784
|
+
console.log(` ${chalk.bold(result.name)}`);
|
|
785
|
+
if (result.description) {
|
|
786
|
+
console.log(` ${chalk.dim(result.description)}`);
|
|
787
|
+
}
|
|
788
|
+
console.log();
|
|
789
|
+
// Metadata
|
|
790
|
+
console.log(` ${chalk.dim("URL")} ${url}`);
|
|
791
|
+
console.log(` ${chalk.dim("Method")} ${chalk.bold(result.methods.join(", ") || "GET")}`);
|
|
792
|
+
console.log(` ${chalk.dim("Price")} ${chalk.green(`$${result.price} ${result.currency}`)}`);
|
|
793
|
+
console.log(` ${chalk.dim("Network")} ${result.network}`);
|
|
794
|
+
// Input schema
|
|
795
|
+
if (result.schema?.input) {
|
|
796
|
+
console.log();
|
|
797
|
+
const isGet = result.methods.includes("GET") && !result.methods.includes("POST");
|
|
798
|
+
const label = isGet ? "Input (query params)" : "Input (JSON body)";
|
|
799
|
+
console.log(` ${chalk.bold(label)}`);
|
|
800
|
+
if (isJsonSchema(result.schema.input)) {
|
|
801
|
+
for (const line of renderJsonSchemaProps(result.schema.input)) {
|
|
802
|
+
console.log(line);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
for (const line of renderLegacySchema(result.schema.input)) {
|
|
807
|
+
console.log(line);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// Example
|
|
811
|
+
if (isJsonSchema(result.schema.input) &&
|
|
812
|
+
result.schema.input.properties) {
|
|
813
|
+
const props = Object.entries(result.schema.input.properties);
|
|
814
|
+
console.log();
|
|
815
|
+
if (isGet) {
|
|
816
|
+
const qs = props.map(([k, p]) => `${k}=<${p.type}>`).join("&");
|
|
817
|
+
console.log(` ${chalk.dim("Example")} ${chalk.dim("GET")} ${url}?${qs}`);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
const bodyObj = {};
|
|
821
|
+
for (const [k, p] of props)
|
|
822
|
+
bodyObj[k] = `<${p.type}>`;
|
|
823
|
+
console.log(` ${chalk.dim("Example")} ${chalk.dim(result.methods[0] || "POST")} ${url}`);
|
|
824
|
+
console.log(` ${chalk.dim(JSON.stringify(bodyObj))}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Output schema
|
|
829
|
+
if (result.schema?.output) {
|
|
830
|
+
console.log();
|
|
831
|
+
console.log(` ${chalk.bold("Output")}`);
|
|
832
|
+
if (isJsonSchema(result.schema.output)) {
|
|
833
|
+
for (const line of renderJsonSchemaProps(result.schema.output)) {
|
|
834
|
+
console.log(line);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
for (const line of renderLegacySchema(result.schema.output)) {
|
|
839
|
+
console.log(line);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (!result.schema?.input && !result.schema?.output) {
|
|
844
|
+
console.log();
|
|
845
|
+
console.log(` ${chalk.dim("No input/output schema defined for this endpoint.")}`);
|
|
846
|
+
}
|
|
847
|
+
// Tags
|
|
848
|
+
if (result.tags?.length) {
|
|
849
|
+
console.log();
|
|
850
|
+
console.log(` ${result.tags.map((t) => chalk.dim(`#${t}`)).join(" ")}`);
|
|
851
|
+
}
|
|
852
|
+
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
853
|
+
console.log();
|
|
854
|
+
}
|
|
855
|
+
catch (err) {
|
|
856
|
+
spin.fail("Schema lookup failed");
|
|
857
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function fetchSchemaForInteractive(url) {
|
|
862
|
+
let parsed;
|
|
863
|
+
try {
|
|
864
|
+
parsed = new URL(url);
|
|
865
|
+
}
|
|
866
|
+
catch {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
const isBankr = parsed.hostname.endsWith("x402.bankr.bot");
|
|
870
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
871
|
+
// Strategy 1: Direct schema API (works once deployed)
|
|
872
|
+
if (isBankr && parts.length >= 2) {
|
|
873
|
+
try {
|
|
874
|
+
const res = await fetch(`${getApiUrl()}/x402/endpoints/schema/${parts[0]}/${parts[1]}`);
|
|
875
|
+
if (res.ok) {
|
|
876
|
+
const data = (await res.json());
|
|
877
|
+
if (data.success) {
|
|
878
|
+
const rawInput = data.schema?.input;
|
|
879
|
+
const inputSchema = rawInput && isJsonSchema(rawInput)
|
|
880
|
+
? rawInput
|
|
881
|
+
: null;
|
|
882
|
+
return {
|
|
883
|
+
name: data.name,
|
|
884
|
+
description: data.description,
|
|
885
|
+
methods: data.methods,
|
|
886
|
+
price: data.price,
|
|
887
|
+
currency: data.currency,
|
|
888
|
+
inputSchema,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
catch {
|
|
894
|
+
// Fall through to next strategy
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
// Strategy 2: Discover API (already deployed, searches by service name)
|
|
898
|
+
if (isBankr && parts.length >= 2) {
|
|
899
|
+
try {
|
|
900
|
+
const serviceName = parts[parts.length - 1];
|
|
901
|
+
const discoverRes = await fetch(`${getApiUrl()}/x402/endpoints/discover?${new URLSearchParams({ q: serviceName, limit: "5" })}`);
|
|
902
|
+
if (discoverRes.ok) {
|
|
903
|
+
const discoverData = (await discoverRes.json());
|
|
904
|
+
// Match by slug or URL path
|
|
905
|
+
const slug = `${parts[0]}/${parts[1]}`;
|
|
906
|
+
const match = discoverData.services?.find((s) => s.slug === slug || s.slug.endsWith(`/${serviceName}`));
|
|
907
|
+
if (match) {
|
|
908
|
+
const route = match.routes?.[0];
|
|
909
|
+
const { input: rawInput } = resolveSchemas(route?.schema);
|
|
910
|
+
const inputSchema = rawInput && isJsonSchema(rawInput)
|
|
911
|
+
? rawInput
|
|
912
|
+
: null;
|
|
913
|
+
return {
|
|
914
|
+
name: match.name,
|
|
915
|
+
description: match.description,
|
|
916
|
+
methods: route?.methods?.filter((m) => m !== "*") ?? ["GET"],
|
|
917
|
+
price: route?.price ?? "?",
|
|
918
|
+
currency: route?.currency ?? "USDC",
|
|
919
|
+
inputSchema,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
// Fall through to next strategy
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
// Strategy 3: Probe endpoint for 402 response to get price info
|
|
929
|
+
try {
|
|
930
|
+
const probeRes = await fetch(url, { method: "GET" });
|
|
931
|
+
if (probeRes.status === 402) {
|
|
932
|
+
const paymentHeader = probeRes.headers.get("payment-required") ??
|
|
933
|
+
probeRes.headers.get("x-payment-required");
|
|
934
|
+
if (paymentHeader) {
|
|
935
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
936
|
+
const requirements = JSON.parse(decoded);
|
|
937
|
+
const accept = requirements.accepts?.[0] ?? requirements;
|
|
938
|
+
const amountAtomic = BigInt(accept.maxAmountRequired ?? "0");
|
|
939
|
+
const amountUsd = Number(amountAtomic) / 1000000;
|
|
940
|
+
// Extract service name from URL path
|
|
941
|
+
const serviceName = parts[parts.length - 1] ?? "endpoint";
|
|
942
|
+
return {
|
|
943
|
+
name: serviceName,
|
|
944
|
+
description: "",
|
|
945
|
+
methods: ["GET"],
|
|
946
|
+
price: amountUsd.toFixed(6),
|
|
947
|
+
currency: "USDC",
|
|
948
|
+
inputSchema: null,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
// Fall through
|
|
955
|
+
}
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
// ── Call ──────────────────────────────────────────────────────────────
|
|
959
|
+
export async function x402CallCommand(url, opts = {}) {
|
|
960
|
+
if (!url) {
|
|
961
|
+
output.error("Usage: bankr x402 call <url> [--method POST] [--body '{}']");
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
let method = opts.method?.toUpperCase() ?? "GET";
|
|
965
|
+
const maxPaymentUsd = opts.maxPayment ? parseFloat(opts.maxPayment) : 1;
|
|
966
|
+
let requestBody = opts.body;
|
|
967
|
+
if (isNaN(maxPaymentUsd) || maxPaymentUsd <= 0 || maxPaymentUsd > 10) {
|
|
968
|
+
output.error("--max-payment must be between 0 and 10 (USD)");
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
// Interactive mode: fetch schema and prompt for input values
|
|
972
|
+
let resolvedPrice = null;
|
|
973
|
+
if (opts.interactive) {
|
|
974
|
+
const schema = await fetchSchemaForInteractive(url);
|
|
975
|
+
if (schema) {
|
|
976
|
+
// Full schema available — show endpoint info and prompt for fields
|
|
977
|
+
console.log();
|
|
978
|
+
console.log(` ${chalk.bold(schema.name)}`);
|
|
979
|
+
if (schema.description) {
|
|
980
|
+
console.log(` ${chalk.dim(schema.description)}`);
|
|
981
|
+
}
|
|
982
|
+
console.log(` ${chalk.dim("Price")} ${chalk.green(`$${schema.price} ${schema.currency}`)}`);
|
|
983
|
+
console.log();
|
|
984
|
+
// Use the endpoint's actual price and preferred method
|
|
985
|
+
resolvedPrice = parseFloat(schema.price);
|
|
986
|
+
if (schema.methods.length > 0) {
|
|
987
|
+
method = schema.methods.includes("POST") ? "POST" : schema.methods[0];
|
|
988
|
+
}
|
|
989
|
+
// Prompt for each input field
|
|
990
|
+
if (schema.inputSchema && schema.inputSchema.properties) {
|
|
991
|
+
const required = new Set(schema.inputSchema.required ?? []);
|
|
992
|
+
const values = {};
|
|
993
|
+
for (const [key, prop] of Object.entries(schema.inputSchema.properties)) {
|
|
994
|
+
const typedProp = prop;
|
|
995
|
+
const isRequired = required.has(key);
|
|
996
|
+
const label = isRequired
|
|
997
|
+
? `${chalk.bold(key)} ${chalk.dim(`(${typedProp.type})`)}`
|
|
998
|
+
: `${key} ${chalk.dim(`(${typedProp.type}, optional)`)}`;
|
|
999
|
+
const hint = typedProp.description ?? "";
|
|
1000
|
+
// Enum fields → select prompt
|
|
1001
|
+
if (typedProp.enum && typedProp.enum.length > 0) {
|
|
1002
|
+
const selected = await select({
|
|
1003
|
+
message: `${label}${hint ? ` — ${chalk.dim(hint)}` : ""}`,
|
|
1004
|
+
choices: typedProp.enum.map((v) => ({
|
|
1005
|
+
name: String(v),
|
|
1006
|
+
value: String(v),
|
|
1007
|
+
})),
|
|
1008
|
+
});
|
|
1009
|
+
values[key] =
|
|
1010
|
+
typedProp.type === "number" || typedProp.type === "integer"
|
|
1011
|
+
? Number(selected)
|
|
1012
|
+
: selected;
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
// Text input
|
|
1016
|
+
const answer = await input({
|
|
1017
|
+
message: `${label}${hint ? ` — ${chalk.dim(hint)}` : ""}`,
|
|
1018
|
+
required: isRequired,
|
|
1019
|
+
});
|
|
1020
|
+
if (answer === "" && !isRequired)
|
|
1021
|
+
continue;
|
|
1022
|
+
// Coerce types
|
|
1023
|
+
if (typedProp.type === "number" || typedProp.type === "integer") {
|
|
1024
|
+
const num = Number(answer);
|
|
1025
|
+
if (isNaN(num)) {
|
|
1026
|
+
output.error(`Invalid number for ${key}: "${answer}"`);
|
|
1027
|
+
process.exit(1);
|
|
1028
|
+
}
|
|
1029
|
+
values[key] = num;
|
|
1030
|
+
}
|
|
1031
|
+
else if (typedProp.type === "boolean") {
|
|
1032
|
+
values[key] = answer.toLowerCase() === "true" || answer === "1";
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
values[key] = answer;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (Object.keys(values).length > 0) {
|
|
1040
|
+
if (method === "GET") {
|
|
1041
|
+
// Append as query params
|
|
1042
|
+
const params = new URLSearchParams();
|
|
1043
|
+
for (const [k, v] of Object.entries(values)) {
|
|
1044
|
+
params.set(k, String(v));
|
|
1045
|
+
}
|
|
1046
|
+
url = `${url}${url.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
requestBody = JSON.stringify(values);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
// No schema available — fallback: prompt for method and body
|
|
1056
|
+
output.warn("No schema found for this endpoint. Falling back to manual input.");
|
|
1057
|
+
console.log();
|
|
1058
|
+
method = await select({
|
|
1059
|
+
message: "HTTP method",
|
|
1060
|
+
choices: [
|
|
1061
|
+
{ name: "GET", value: "GET" },
|
|
1062
|
+
{ name: "POST", value: "POST" },
|
|
1063
|
+
{ name: "PUT", value: "PUT" },
|
|
1064
|
+
{ name: "DELETE", value: "DELETE" },
|
|
1065
|
+
],
|
|
1066
|
+
default: method,
|
|
1067
|
+
theme: output.bankrTheme,
|
|
1068
|
+
});
|
|
1069
|
+
if (method === "POST" || method === "PUT") {
|
|
1070
|
+
const bodyInput = await input({
|
|
1071
|
+
message: "JSON body (or leave empty)",
|
|
1072
|
+
theme: output.bankrTheme,
|
|
1073
|
+
});
|
|
1074
|
+
if (bodyInput.trim()) {
|
|
1075
|
+
try {
|
|
1076
|
+
JSON.parse(bodyInput);
|
|
1077
|
+
requestBody = bodyInput;
|
|
1078
|
+
}
|
|
1079
|
+
catch {
|
|
1080
|
+
output.error("Invalid JSON body");
|
|
1081
|
+
process.exit(1);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
const qsInput = await input({
|
|
1087
|
+
message: "Query params (key=val&key2=val2, or leave empty)",
|
|
1088
|
+
theme: output.bankrTheme,
|
|
1089
|
+
});
|
|
1090
|
+
if (qsInput.trim()) {
|
|
1091
|
+
url = `${url}${url.includes("?") ? "&" : "?"}${qsInput}`;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
// Show what we're about to send
|
|
1096
|
+
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
1097
|
+
console.log(` ${chalk.dim("Method")} ${method}`);
|
|
1098
|
+
console.log(` ${chalk.dim("URL")} ${url}`);
|
|
1099
|
+
if (requestBody) {
|
|
1100
|
+
console.log(` ${chalk.dim("Body")} ${requestBody}`);
|
|
1101
|
+
}
|
|
1102
|
+
console.log();
|
|
1103
|
+
}
|
|
1104
|
+
// Confirm payment unless --yes
|
|
1105
|
+
if (!opts.yes && !opts.interactive) {
|
|
1106
|
+
const confirmed = await confirm({
|
|
1107
|
+
message: `Call ${chalk.bold(url)} with up to ${chalk.green(`$${maxPaymentUsd} USDC`)} payment?`,
|
|
1108
|
+
default: true,
|
|
1109
|
+
});
|
|
1110
|
+
if (!confirmed) {
|
|
1111
|
+
output.info("Cancelled.");
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
else if (opts.interactive) {
|
|
1116
|
+
const confirmed = await confirm({
|
|
1117
|
+
message: `Submit with ${chalk.green(`$${resolvedPrice ?? maxPaymentUsd} USDC`)} payment?`,
|
|
1118
|
+
default: true,
|
|
1119
|
+
});
|
|
1120
|
+
if (!confirmed) {
|
|
1121
|
+
output.info("Cancelled.");
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
const spin = output.spinner(`Calling ${method} ${url}...`);
|
|
1126
|
+
try {
|
|
1127
|
+
const res = await fetch(`${getApiUrl()}/wallet/x402-pay`, {
|
|
1128
|
+
method: "POST",
|
|
1129
|
+
headers: authHeaders(),
|
|
1130
|
+
body: JSON.stringify({
|
|
1131
|
+
url,
|
|
1132
|
+
method,
|
|
1133
|
+
body: requestBody,
|
|
1134
|
+
maxPaymentUsd: resolvedPrice ?? maxPaymentUsd,
|
|
1135
|
+
}),
|
|
1136
|
+
});
|
|
1137
|
+
const result = await handleResponse(res);
|
|
1138
|
+
spin.stop();
|
|
1139
|
+
if (opts.raw) {
|
|
1140
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (!result.success) {
|
|
1144
|
+
output.error(result.error ?? "Call failed");
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
// Show endpoint schema info (Bankr-hosted endpoints only)
|
|
1148
|
+
if (result.endpointSchema) {
|
|
1149
|
+
const ep = result.endpointSchema;
|
|
1150
|
+
console.log();
|
|
1151
|
+
console.log(` ${chalk.dim("Endpoint")} ${chalk.bold(ep.name)} — ${ep.description}`);
|
|
1152
|
+
if (ep.schema?.input) {
|
|
1153
|
+
console.log(` ${chalk.dim("Input")} ${chalk.dim(JSON.stringify(ep.schema.input))}`);
|
|
1154
|
+
}
|
|
1155
|
+
if (ep.schema?.output) {
|
|
1156
|
+
console.log(` ${chalk.dim("Output")} ${chalk.dim(JSON.stringify(ep.schema.output))}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
// Show payment info
|
|
1160
|
+
if (result.paymentMade) {
|
|
1161
|
+
console.log();
|
|
1162
|
+
console.log(` ${chalk.green("Paid")} ${chalk.bold(`$${result.paymentMade.amountUsd.toFixed(4)}`)} USDC on ${result.paymentMade.network}`);
|
|
1163
|
+
}
|
|
1164
|
+
// Show response
|
|
1165
|
+
console.log();
|
|
1166
|
+
console.log(` ${chalk.dim("Status")} ${result.status}`);
|
|
1167
|
+
console.log(` ${chalk.dim("Response")}`);
|
|
1168
|
+
console.log();
|
|
1169
|
+
if (typeof result.response === "string") {
|
|
1170
|
+
console.log(` ${result.response}`);
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
console.log(JSON.stringify(result.response, null, 2));
|
|
1174
|
+
}
|
|
1175
|
+
console.log();
|
|
1176
|
+
}
|
|
1177
|
+
catch (err) {
|
|
1178
|
+
spin.fail("Call failed");
|
|
1179
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
1180
|
+
process.exit(1);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
// ── Search ────────────────────────────────────────────────────────────
|
|
712
1184
|
export async function x402SearchCommand(queryParts, opts = {}) {
|
|
713
1185
|
const query = queryParts.join(" ").trim();
|
|
714
1186
|
if (!query) {
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -381,5 +381,24 @@ export interface TransferResponse {
|
|
|
381
381
|
error?: string;
|
|
382
382
|
}
|
|
383
383
|
export declare function transfer(request: TransferRequest): Promise<TransferResponse>;
|
|
384
|
+
export interface X402PayRequest {
|
|
385
|
+
url: string;
|
|
386
|
+
method?: string;
|
|
387
|
+
body?: string;
|
|
388
|
+
headers?: Record<string, string>;
|
|
389
|
+
maxPaymentUsd?: number;
|
|
390
|
+
}
|
|
391
|
+
export interface X402PayResponse {
|
|
392
|
+
success: boolean;
|
|
393
|
+
status?: number;
|
|
394
|
+
response?: unknown;
|
|
395
|
+
paymentMade?: {
|
|
396
|
+
amountUsd: number;
|
|
397
|
+
network: string;
|
|
398
|
+
payTo: string;
|
|
399
|
+
};
|
|
400
|
+
error?: string;
|
|
401
|
+
}
|
|
402
|
+
export declare function x402Pay(request: X402PayRequest): Promise<X402PayResponse>;
|
|
384
403
|
export declare function buildPublicClaimTxs(beneficiaryAddress: string, tokenAddresses: string[]): Promise<BuildClaimResponse>;
|
|
385
404
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/lib/api.js
CHANGED
|
@@ -280,6 +280,14 @@ export async function transfer(request) {
|
|
|
280
280
|
});
|
|
281
281
|
return handleResponse(res);
|
|
282
282
|
}
|
|
283
|
+
export async function x402Pay(request) {
|
|
284
|
+
const res = await fetch(`${getApiUrl()}/wallet/x402-pay`, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers: authHeaders(),
|
|
287
|
+
body: JSON.stringify(request),
|
|
288
|
+
});
|
|
289
|
+
return handleResponse(res);
|
|
290
|
+
}
|
|
283
291
|
export async function buildPublicClaimTxs(beneficiaryAddress, tokenAddresses) {
|
|
284
292
|
const res = await fetch(`${getApiUrl()}/public/doppler/build-claim`, {
|
|
285
293
|
method: "POST",
|