@bankr/cli 0.2.7 → 0.2.9
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 +473 -0
- package/dist/lib/api.d.ts +19 -0
- package/dist/lib/api.js +8 -0
- package/dist/lib/output.js +7 -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
|
@@ -741,6 +741,479 @@ function printServiceFormatted(svc) {
|
|
|
741
741
|
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
742
742
|
console.log("");
|
|
743
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
|
+
// Checks header first (Bankr endpoints), then falls back to response
|
|
930
|
+
// body (Coinbase x402 standard used by external endpoints).
|
|
931
|
+
try {
|
|
932
|
+
const probeRes = await fetch(url, { method: "GET" });
|
|
933
|
+
if (probeRes.status === 402) {
|
|
934
|
+
const paymentHeader = probeRes.headers.get("payment-required") ??
|
|
935
|
+
probeRes.headers.get("x-payment-required");
|
|
936
|
+
let accept = null;
|
|
937
|
+
if (paymentHeader) {
|
|
938
|
+
try {
|
|
939
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
940
|
+
const requirements = JSON.parse(decoded);
|
|
941
|
+
accept = requirements.accepts?.[0] ?? requirements;
|
|
942
|
+
}
|
|
943
|
+
catch {
|
|
944
|
+
// Header parse failed, try body
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (!accept) {
|
|
948
|
+
try {
|
|
949
|
+
const bodyText = await probeRes.text();
|
|
950
|
+
const bodyJson = JSON.parse(bodyText);
|
|
951
|
+
accept = bodyJson.accepts?.[0] ?? null;
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
// Body parse failed too
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const acceptAmount = accept?.maxAmountRequired ?? accept?.amount;
|
|
958
|
+
if (acceptAmount) {
|
|
959
|
+
const amountAtomic = BigInt(acceptAmount);
|
|
960
|
+
const amountUsd = Number(amountAtomic) / 1000000;
|
|
961
|
+
// Extract service name from URL path
|
|
962
|
+
const serviceName = parts[parts.length - 1] ?? "endpoint";
|
|
963
|
+
return {
|
|
964
|
+
name: serviceName,
|
|
965
|
+
description: accept?.description ?? "",
|
|
966
|
+
methods: ["GET"],
|
|
967
|
+
price: amountUsd.toFixed(6),
|
|
968
|
+
currency: "USDC",
|
|
969
|
+
inputSchema: null,
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch {
|
|
975
|
+
// Fall through
|
|
976
|
+
}
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
// ── Call ──────────────────────────────────────────────────────────────
|
|
980
|
+
export async function x402CallCommand(url, opts = {}) {
|
|
981
|
+
if (!url) {
|
|
982
|
+
output.error("Usage: bankr x402 call <url> [--method POST] [--body '{}']");
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
985
|
+
let method = opts.method?.toUpperCase() ?? "GET";
|
|
986
|
+
const maxPaymentUsd = opts.maxPayment ? parseFloat(opts.maxPayment) : 1;
|
|
987
|
+
let requestBody = opts.body;
|
|
988
|
+
if (isNaN(maxPaymentUsd) || maxPaymentUsd <= 0 || maxPaymentUsd > 10) {
|
|
989
|
+
output.error("--max-payment must be between 0 and 10 (USD)");
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
// Interactive mode: fetch schema and prompt for input values
|
|
993
|
+
let resolvedPrice = null;
|
|
994
|
+
if (opts.interactive) {
|
|
995
|
+
const schema = await fetchSchemaForInteractive(url);
|
|
996
|
+
if (schema) {
|
|
997
|
+
// Full schema available — show endpoint info and prompt for fields
|
|
998
|
+
console.log();
|
|
999
|
+
console.log(` ${chalk.bold(schema.name)}`);
|
|
1000
|
+
if (schema.description) {
|
|
1001
|
+
console.log(` ${chalk.dim(schema.description)}`);
|
|
1002
|
+
}
|
|
1003
|
+
console.log(` ${chalk.dim("Price")} ${chalk.green(`$${schema.price} ${schema.currency}`)}`);
|
|
1004
|
+
console.log();
|
|
1005
|
+
// Use the endpoint's actual price and preferred method
|
|
1006
|
+
resolvedPrice = parseFloat(schema.price);
|
|
1007
|
+
if (schema.methods.length > 0) {
|
|
1008
|
+
method = schema.methods.includes("POST") ? "POST" : schema.methods[0];
|
|
1009
|
+
}
|
|
1010
|
+
// Prompt for each input field
|
|
1011
|
+
if (schema.inputSchema && schema.inputSchema.properties) {
|
|
1012
|
+
const required = new Set(schema.inputSchema.required ?? []);
|
|
1013
|
+
const values = {};
|
|
1014
|
+
for (const [key, prop] of Object.entries(schema.inputSchema.properties)) {
|
|
1015
|
+
const typedProp = prop;
|
|
1016
|
+
const isRequired = required.has(key);
|
|
1017
|
+
const label = isRequired
|
|
1018
|
+
? `${chalk.bold(key)} ${chalk.dim(`(${typedProp.type})`)}`
|
|
1019
|
+
: `${key} ${chalk.dim(`(${typedProp.type}, optional)`)}`;
|
|
1020
|
+
const hint = typedProp.description ?? "";
|
|
1021
|
+
// Enum fields → select prompt
|
|
1022
|
+
if (typedProp.enum && typedProp.enum.length > 0) {
|
|
1023
|
+
const selected = await select({
|
|
1024
|
+
message: `${label}${hint ? ` — ${chalk.dim(hint)}` : ""}`,
|
|
1025
|
+
choices: typedProp.enum.map((v) => ({
|
|
1026
|
+
name: String(v),
|
|
1027
|
+
value: String(v),
|
|
1028
|
+
})),
|
|
1029
|
+
});
|
|
1030
|
+
values[key] =
|
|
1031
|
+
typedProp.type === "number" || typedProp.type === "integer"
|
|
1032
|
+
? Number(selected)
|
|
1033
|
+
: selected;
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
// Text input
|
|
1037
|
+
const answer = await input({
|
|
1038
|
+
message: `${label}${hint ? ` — ${chalk.dim(hint)}` : ""}`,
|
|
1039
|
+
required: isRequired,
|
|
1040
|
+
});
|
|
1041
|
+
if (answer === "" && !isRequired)
|
|
1042
|
+
continue;
|
|
1043
|
+
// Coerce types
|
|
1044
|
+
if (typedProp.type === "number" || typedProp.type === "integer") {
|
|
1045
|
+
const num = Number(answer);
|
|
1046
|
+
if (isNaN(num)) {
|
|
1047
|
+
output.error(`Invalid number for ${key}: "${answer}"`);
|
|
1048
|
+
process.exit(1);
|
|
1049
|
+
}
|
|
1050
|
+
values[key] = num;
|
|
1051
|
+
}
|
|
1052
|
+
else if (typedProp.type === "boolean") {
|
|
1053
|
+
values[key] = answer.toLowerCase() === "true" || answer === "1";
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
values[key] = answer;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (Object.keys(values).length > 0) {
|
|
1061
|
+
if (method === "GET") {
|
|
1062
|
+
// Append as query params
|
|
1063
|
+
const params = new URLSearchParams();
|
|
1064
|
+
for (const [k, v] of Object.entries(values)) {
|
|
1065
|
+
params.set(k, String(v));
|
|
1066
|
+
}
|
|
1067
|
+
url = `${url}${url.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
requestBody = JSON.stringify(values);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
// No schema available — fallback: prompt for method and body
|
|
1077
|
+
output.warn("No schema found for this endpoint. Falling back to manual input.");
|
|
1078
|
+
console.log();
|
|
1079
|
+
method = await select({
|
|
1080
|
+
message: "HTTP method",
|
|
1081
|
+
choices: [
|
|
1082
|
+
{ name: "GET", value: "GET" },
|
|
1083
|
+
{ name: "POST", value: "POST" },
|
|
1084
|
+
{ name: "PUT", value: "PUT" },
|
|
1085
|
+
{ name: "DELETE", value: "DELETE" },
|
|
1086
|
+
],
|
|
1087
|
+
default: method,
|
|
1088
|
+
theme: output.bankrTheme,
|
|
1089
|
+
});
|
|
1090
|
+
if (method === "POST" || method === "PUT") {
|
|
1091
|
+
const bodyInput = await input({
|
|
1092
|
+
message: "JSON body (or leave empty)",
|
|
1093
|
+
theme: output.bankrTheme,
|
|
1094
|
+
});
|
|
1095
|
+
if (bodyInput.trim()) {
|
|
1096
|
+
try {
|
|
1097
|
+
JSON.parse(bodyInput);
|
|
1098
|
+
requestBody = bodyInput;
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
output.error("Invalid JSON body");
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
const qsInput = await input({
|
|
1108
|
+
message: "Query params (key=val&key2=val2, or leave empty)",
|
|
1109
|
+
theme: output.bankrTheme,
|
|
1110
|
+
});
|
|
1111
|
+
if (qsInput.trim()) {
|
|
1112
|
+
url = `${url}${url.includes("?") ? "&" : "?"}${qsInput}`;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
// Show what we're about to send
|
|
1117
|
+
console.log(` ${chalk.dim("─".repeat(60))}`);
|
|
1118
|
+
console.log(` ${chalk.dim("Method")} ${method}`);
|
|
1119
|
+
console.log(` ${chalk.dim("URL")} ${url}`);
|
|
1120
|
+
if (requestBody) {
|
|
1121
|
+
console.log(` ${chalk.dim("Body")} ${requestBody}`);
|
|
1122
|
+
}
|
|
1123
|
+
console.log();
|
|
1124
|
+
}
|
|
1125
|
+
// Non-interactive: probe the endpoint to show the actual price before confirming
|
|
1126
|
+
if (!opts.interactive && !opts.yes && resolvedPrice === null) {
|
|
1127
|
+
const spin = output.spinner("Checking endpoint price...");
|
|
1128
|
+
const schema = await fetchSchemaForInteractive(url);
|
|
1129
|
+
spin.stop();
|
|
1130
|
+
if (schema) {
|
|
1131
|
+
resolvedPrice = parseFloat(schema.price);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// Confirm payment unless --yes
|
|
1135
|
+
if (!opts.yes && !opts.interactive) {
|
|
1136
|
+
const priceDisplay = resolvedPrice !== null
|
|
1137
|
+
? chalk.green(`$${resolvedPrice} USDC`)
|
|
1138
|
+
: chalk.green(`up to $${maxPaymentUsd} USDC`);
|
|
1139
|
+
const confirmed = await confirm({
|
|
1140
|
+
message: `Call ${chalk.bold(url)} with ${priceDisplay} payment?`,
|
|
1141
|
+
default: true,
|
|
1142
|
+
});
|
|
1143
|
+
if (!confirmed) {
|
|
1144
|
+
output.info("Cancelled.");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
else if (opts.interactive) {
|
|
1149
|
+
const confirmed = await confirm({
|
|
1150
|
+
message: `Submit with ${chalk.green(`$${resolvedPrice ?? maxPaymentUsd} USDC`)} payment?`,
|
|
1151
|
+
default: true,
|
|
1152
|
+
});
|
|
1153
|
+
if (!confirmed) {
|
|
1154
|
+
output.info("Cancelled.");
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const spin = output.spinner(`Calling ${method} ${url}...`);
|
|
1159
|
+
try {
|
|
1160
|
+
const res = await fetch(`${getApiUrl()}/wallet/x402-pay`, {
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
headers: authHeaders(),
|
|
1163
|
+
body: JSON.stringify({
|
|
1164
|
+
url,
|
|
1165
|
+
method,
|
|
1166
|
+
body: requestBody,
|
|
1167
|
+
maxPaymentUsd: resolvedPrice ?? maxPaymentUsd,
|
|
1168
|
+
}),
|
|
1169
|
+
});
|
|
1170
|
+
const result = await handleResponse(res);
|
|
1171
|
+
spin.stop();
|
|
1172
|
+
if (opts.raw) {
|
|
1173
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (!result.success) {
|
|
1177
|
+
output.error(result.error ?? "Call failed");
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
// Show endpoint schema info (Bankr-hosted endpoints only)
|
|
1181
|
+
if (result.endpointSchema) {
|
|
1182
|
+
const ep = result.endpointSchema;
|
|
1183
|
+
console.log();
|
|
1184
|
+
console.log(` ${chalk.dim("Endpoint")} ${chalk.bold(ep.name)} — ${ep.description}`);
|
|
1185
|
+
if (ep.schema?.input) {
|
|
1186
|
+
console.log(` ${chalk.dim("Input")} ${chalk.dim(JSON.stringify(ep.schema.input))}`);
|
|
1187
|
+
}
|
|
1188
|
+
if (ep.schema?.output) {
|
|
1189
|
+
console.log(` ${chalk.dim("Output")} ${chalk.dim(JSON.stringify(ep.schema.output))}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
// Show payment info
|
|
1193
|
+
if (result.paymentMade) {
|
|
1194
|
+
console.log();
|
|
1195
|
+
console.log(` ${chalk.green("Paid")} ${chalk.bold(`$${result.paymentMade.amountUsd.toFixed(4)}`)} USDC on ${result.paymentMade.network}`);
|
|
1196
|
+
}
|
|
1197
|
+
// Show response
|
|
1198
|
+
console.log();
|
|
1199
|
+
console.log(` ${chalk.dim("Status")} ${result.status}`);
|
|
1200
|
+
console.log(` ${chalk.dim("Response")}`);
|
|
1201
|
+
console.log();
|
|
1202
|
+
if (typeof result.response === "string") {
|
|
1203
|
+
console.log(` ${result.response}`);
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
console.log(JSON.stringify(result.response, null, 2));
|
|
1207
|
+
}
|
|
1208
|
+
console.log();
|
|
1209
|
+
}
|
|
1210
|
+
catch (err) {
|
|
1211
|
+
spin.fail("Call failed");
|
|
1212
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
1213
|
+
process.exit(1);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
// ── Search ────────────────────────────────────────────────────────────
|
|
744
1217
|
export async function x402SearchCommand(queryParts, opts = {}) {
|
|
745
1218
|
const query = queryParts.join(" ").trim();
|
|
746
1219
|
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",
|
package/dist/lib/output.js
CHANGED
|
@@ -61,6 +61,13 @@ export function spinner(text) {
|
|
|
61
61
|
export function maskApiKey(key) {
|
|
62
62
|
if (key.length <= 8)
|
|
63
63
|
return "****";
|
|
64
|
+
// New prefixed keys: bk_usr_{keyId}_{secret} → bk_usr_{keyId}_*****
|
|
65
|
+
const parts = key.split("_");
|
|
66
|
+
if (parts.length === 4 &&
|
|
67
|
+
parts[0] === "bk" &&
|
|
68
|
+
(parts[1] === "usr" || parts[1] === "ptr")) {
|
|
69
|
+
return `${parts[0]}_${parts[1]}_${parts[2]}_*****`;
|
|
70
|
+
}
|
|
64
71
|
return key.slice(0, 6) + "..." + key.slice(-4);
|
|
65
72
|
}
|
|
66
73
|
export function formatDuration(ms) {
|