@bankofai/x402-cli 1.0.0-beta.0 → 1.0.0-beta.1
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/README.md +14 -2
- package/dist/cli.js +907 -82
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,6 +29,18 @@ Run the compiled CLI:
|
|
|
29
29
|
node dist/cli.js <command> [options]
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Common CLI options:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
x402-cli --help
|
|
36
|
+
x402-cli --version
|
|
37
|
+
x402-cli pay --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Output is human-readable by default. Add `--json` to commands such as `pay`,
|
|
41
|
+
`serve`, `gateway check`, and `catalog search` for a stable machine-readable
|
|
42
|
+
envelope with `ok`, `command`, `result`, or structured `error` fields.
|
|
43
|
+
|
|
32
44
|
## Commands
|
|
33
45
|
|
|
34
46
|
### Serve
|
|
@@ -96,10 +108,10 @@ Aliases accepted:
|
|
|
96
108
|
|
|
97
109
|
## Facilitator
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
Pass a facilitator URL when needed:
|
|
100
112
|
|
|
101
113
|
```bash
|
|
102
|
-
|
|
114
|
+
x402-cli serve --facilitator-url https://facilitator.bankofai.io ...
|
|
103
115
|
```
|
|
104
116
|
|
|
105
117
|
CLI payment challenges and payload selection always emit `scheme: "exact"` for
|
package/dist/cli.js
CHANGED
|
@@ -2,24 +2,58 @@
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
7
9
|
import YAML from "yaml";
|
|
8
10
|
import { createPaymentPayload, decodeRequired, decodeResponse, encodeRequired, encodeResponse, encodeSignature, headers } from "./x402.js";
|
|
9
|
-
import { getToken, normalizeNetwork, toSmallestUnit } from "./tokens.js";
|
|
11
|
+
import { findTokenByAddress, getToken, normalizeNetwork, toSmallestUnit } from "./tokens.js";
|
|
12
|
+
const BOOLEAN_FLAGS = new Set(["daemon", "dry-run", "help", "human", "include-blocked", "json", "version"]);
|
|
10
13
|
function parseArgs(argv) {
|
|
11
14
|
const [command = "help", ...rest] = argv;
|
|
12
15
|
const positional = [];
|
|
13
16
|
const options = {};
|
|
14
17
|
for (let i = 0; i < rest.length; i += 1) {
|
|
15
18
|
const item = rest[i];
|
|
19
|
+
if (item === "-h") {
|
|
20
|
+
options.help = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (item === "-V") {
|
|
24
|
+
options.version = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (item === "-d") {
|
|
28
|
+
options.daemon = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (item === "-n") {
|
|
32
|
+
const next = rest[i + 1];
|
|
33
|
+
if (!next || next.startsWith("-")) {
|
|
34
|
+
options.limit = true;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
options.limit = next;
|
|
38
|
+
i += 1;
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
16
42
|
if (!item.startsWith("--")) {
|
|
17
43
|
positional.push(item);
|
|
18
44
|
continue;
|
|
19
45
|
}
|
|
20
|
-
const
|
|
46
|
+
const eq = item.indexOf("=");
|
|
47
|
+
const key = eq > 2 ? item.slice(2, eq) : item.slice(2);
|
|
48
|
+
const inline = eq > 2 ? item.slice(eq + 1) : undefined;
|
|
21
49
|
const next = rest[i + 1];
|
|
22
|
-
if (
|
|
50
|
+
if (inline !== undefined) {
|
|
51
|
+
options[key] = inline;
|
|
52
|
+
}
|
|
53
|
+
else if (BOOLEAN_FLAGS.has(key)) {
|
|
54
|
+
options[key] = true;
|
|
55
|
+
}
|
|
56
|
+
else if (!next || next.startsWith("-")) {
|
|
23
57
|
options[key] = true;
|
|
24
58
|
}
|
|
25
59
|
else {
|
|
@@ -35,10 +69,25 @@ function parseArgs(argv) {
|
|
|
35
69
|
}
|
|
36
70
|
return { command, positional, options };
|
|
37
71
|
}
|
|
72
|
+
function getVersion() {
|
|
73
|
+
try {
|
|
74
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
75
|
+
return String(pkg.version ?? "0.0.0");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return "0.0.0";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
38
81
|
function opt(options, key, fallback) {
|
|
39
82
|
const value = options[key];
|
|
40
83
|
return typeof value === "string" ? value : fallback;
|
|
41
84
|
}
|
|
85
|
+
function hasFlag(options, key) {
|
|
86
|
+
return options[key] === true;
|
|
87
|
+
}
|
|
88
|
+
function outputMode(options) {
|
|
89
|
+
return hasFlag(options, "json") ? "json" : "human";
|
|
90
|
+
}
|
|
42
91
|
function optAll(options, key) {
|
|
43
92
|
const value = options[key];
|
|
44
93
|
if (Array.isArray(value))
|
|
@@ -48,6 +97,247 @@ function optAll(options, key) {
|
|
|
48
97
|
function printJson(value) {
|
|
49
98
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
50
99
|
}
|
|
100
|
+
function emit(args) {
|
|
101
|
+
const mode = args.mode ?? "human";
|
|
102
|
+
if (mode === "json") {
|
|
103
|
+
const envelope = {
|
|
104
|
+
ok: !args.error,
|
|
105
|
+
command: args.command,
|
|
106
|
+
};
|
|
107
|
+
if (args.network)
|
|
108
|
+
envelope.network = args.network;
|
|
109
|
+
if (args.scheme)
|
|
110
|
+
envelope.scheme = args.scheme;
|
|
111
|
+
if (args.error)
|
|
112
|
+
envelope.error = args.error;
|
|
113
|
+
else
|
|
114
|
+
envelope.result = args.result ?? null;
|
|
115
|
+
printJson(envelope);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (args.error) {
|
|
119
|
+
process.stderr.write(`ERROR ${args.command}: ${args.error.code}\n`);
|
|
120
|
+
process.stderr.write(` ${args.error.message}\n`);
|
|
121
|
+
if (args.error.hint)
|
|
122
|
+
process.stderr.write(` hint: ${args.error.hint}\n`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const suffix = [args.network, args.scheme].filter(Boolean).join(" ");
|
|
126
|
+
process.stdout.write(`OK ${args.command}${suffix ? ` (${suffix})` : ""}\n`);
|
|
127
|
+
if (args.result && typeof args.result === "object" && !Array.isArray(args.result)) {
|
|
128
|
+
for (const [key, value] of Object.entries(args.result)) {
|
|
129
|
+
if (value === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
if (value && typeof value === "object") {
|
|
132
|
+
process.stdout.write(` ${key}: ${JSON.stringify(value)}\n`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
process.stdout.write(` ${key}: ${value}\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (args.result !== undefined) {
|
|
140
|
+
process.stdout.write(` ${args.result}\n`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function classify(error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
+
const lower = message.toLowerCase();
|
|
146
|
+
if (lower.includes("missing private key") || lower.includes("could not find a wallet")) {
|
|
147
|
+
return {
|
|
148
|
+
code: "WALLET_NOT_CONFIGURED",
|
|
149
|
+
message,
|
|
150
|
+
hint: "Set PRIVATE_KEY, TRON_PRIVATE_KEY, EVM_PRIVATE_KEY, or configure agent-wallet with a payer wallet.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (lower.includes("wallets_config") || lower.includes("wallet config")) {
|
|
154
|
+
return {
|
|
155
|
+
code: "WALLET_CONFIG_CORRUPT",
|
|
156
|
+
message,
|
|
157
|
+
hint: "Check ~/.agent-wallet/wallets_config.json or recreate the local agent-wallet configuration.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (lower.includes("does not exist") && lower.includes("account [t")) {
|
|
161
|
+
return {
|
|
162
|
+
code: "TRON_ACCOUNT_NOT_ACTIVATED",
|
|
163
|
+
message,
|
|
164
|
+
hint: "Activate the TRON address by sending it a small amount of TRX before signing contract calls.",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (lower.includes("permit2_insufficient_balance") || lower.includes("insufficient") && lower.includes("balance")) {
|
|
168
|
+
return {
|
|
169
|
+
code: "INSUFFICIENT_TOKEN_BALANCE",
|
|
170
|
+
message,
|
|
171
|
+
hint: "Fund the payer address with the exact token and network advertised by the provider, then retry.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (lower.includes("transfer_from_failed") || lower.includes("transferfrom failed")) {
|
|
175
|
+
return {
|
|
176
|
+
code: "TOKEN_TRANSFER_FAILED",
|
|
177
|
+
message,
|
|
178
|
+
hint: "Check token balance, token contract, payer address, and that the selected x402 route matches the provider requirement.",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (lower.includes("insufficient funds for gas") || lower.includes("insufficient gas") || lower.includes("energy")) {
|
|
182
|
+
return {
|
|
183
|
+
code: "INSUFFICIENT_GAS",
|
|
184
|
+
message,
|
|
185
|
+
hint: "Fund the payer address with the native gas token for this network.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (lower.includes("deadline") || lower.includes("expired")) {
|
|
189
|
+
return {
|
|
190
|
+
code: "DEADLINE_OR_CLOCK_SKEW",
|
|
191
|
+
message,
|
|
192
|
+
hint: "Check local clock sync and retry with a fresh payment requirement.",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (lower.includes("permittransferfrom") || lower.includes("invalid signature") || lower.includes("permit reverted")) {
|
|
196
|
+
return {
|
|
197
|
+
code: "PERMIT_REVERTED",
|
|
198
|
+
message,
|
|
199
|
+
hint: "The token or Permit2 contract rejected the signature; retry with a fresh requirement and verify token/network support.",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (lower.includes("tokenregistry") && lower.includes("import")) {
|
|
203
|
+
return {
|
|
204
|
+
code: "SDK_API_DRIFT",
|
|
205
|
+
message,
|
|
206
|
+
hint: "Installed x402 SDK packages do not match this CLI; reinstall @bankofai/x402-cli and SDK dependencies.",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (lower.includes("429") || lower.includes("too many requests") || lower.includes("rate limit")) {
|
|
210
|
+
return {
|
|
211
|
+
code: "RATE_LIMITED",
|
|
212
|
+
message,
|
|
213
|
+
hint: "Wait briefly and retry; the upstream service or RPC is rate limiting requests.",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (lower.includes("402 response missing")) {
|
|
217
|
+
return {
|
|
218
|
+
code: "INVALID_X402_RESPONSE",
|
|
219
|
+
message,
|
|
220
|
+
hint: "The endpoint returned HTTP 402 without a PAYMENT-REQUIRED header.",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (lower.includes("no matching payment requirement")) {
|
|
224
|
+
return {
|
|
225
|
+
code: "NO_MATCHING_PAYMENT_REQUIREMENT",
|
|
226
|
+
message,
|
|
227
|
+
hint: "Relax --network, --token, or --scheme, or use values offered by the provider.",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (lower.includes("exceeds --max")) {
|
|
231
|
+
return {
|
|
232
|
+
code: "PAYMENT_AMOUNT_TOO_HIGH",
|
|
233
|
+
message,
|
|
234
|
+
hint: "Increase the max amount flag only if this provider price is expected.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (lower.includes("failed to fetch") || lower.includes("fetch failed") || lower.includes("econnrefused")) {
|
|
238
|
+
return {
|
|
239
|
+
code: "NETWORK_ERROR",
|
|
240
|
+
message,
|
|
241
|
+
hint: "Check the URL, local server, proxy, and network connectivity.",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
code: "IO_ERROR",
|
|
246
|
+
message,
|
|
247
|
+
hint: "Run with --json for structured output, and check the provider/gateway logs for details.",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function helpText(topic = "root") {
|
|
251
|
+
const sections = {
|
|
252
|
+
root: `x402-cli ${getVersion()}
|
|
253
|
+
|
|
254
|
+
Usage:
|
|
255
|
+
x402-cli <command> [options]
|
|
256
|
+
|
|
257
|
+
Commands:
|
|
258
|
+
pay <url> Pay an x402-protected URL
|
|
259
|
+
serve Run a local x402 paywall endpoint
|
|
260
|
+
roundtrip Start serve, pay it, then exit
|
|
261
|
+
gateway <command> Manage local gateway provider files
|
|
262
|
+
catalog <command> Search, cache, and export provider catalog assets
|
|
263
|
+
|
|
264
|
+
Global options:
|
|
265
|
+
-h, --help Show help
|
|
266
|
+
-V, --version Show version
|
|
267
|
+
--json Print machine-readable JSON envelope
|
|
268
|
+
--human Print human-readable output (default)
|
|
269
|
+
`,
|
|
270
|
+
pay: `Usage:
|
|
271
|
+
x402-cli pay <url> [options]
|
|
272
|
+
|
|
273
|
+
Options:
|
|
274
|
+
--method <method> HTTP method (default: GET)
|
|
275
|
+
--header "Name: Value" Request header, repeatable
|
|
276
|
+
--body <body> Request body for non-GET methods
|
|
277
|
+
--network <caip2> Require a specific network
|
|
278
|
+
--token <symbol> Require a specific token
|
|
279
|
+
--scheme <scheme> Require a specific x402 scheme
|
|
280
|
+
--max-amount <amount> Maximum human-readable payment amount
|
|
281
|
+
--max-rawAmount <amount> Maximum smallest-unit payment amount
|
|
282
|
+
--dry-run Read requirements but do not sign or pay
|
|
283
|
+
--private-key <hex> Explicit payer private key
|
|
284
|
+
--rpc-url <url> Explicit network RPC URL
|
|
285
|
+
--json Print JSON envelope
|
|
286
|
+
`,
|
|
287
|
+
serve: `Usage:
|
|
288
|
+
x402-cli serve --pay-to <address> [options]
|
|
289
|
+
|
|
290
|
+
Options:
|
|
291
|
+
--pay-to <address> Recipient wallet address
|
|
292
|
+
--amount <amount> Human-readable token amount
|
|
293
|
+
--rawAmount <amount> Smallest-unit amount
|
|
294
|
+
--network <caip2> Payment network (default: tron:nile)
|
|
295
|
+
--token <symbol> Token symbol (default: USDT)
|
|
296
|
+
--asset <address> Explicit token address
|
|
297
|
+
--decimals <count> Token decimals for unregistered --asset
|
|
298
|
+
--host <host> Bind host (default: 127.0.0.1)
|
|
299
|
+
--port <port> Bind port (default: 4020)
|
|
300
|
+
--resource-url <url> URL advertised in payment requirements
|
|
301
|
+
--facilitator-url <url> Facilitator base URL
|
|
302
|
+
--daemon Run in background and print the child pid
|
|
303
|
+
--json Print JSON envelope
|
|
304
|
+
`,
|
|
305
|
+
roundtrip: `Usage:
|
|
306
|
+
x402-cli roundtrip --pay-to <address> [serve/pay options]
|
|
307
|
+
`,
|
|
308
|
+
gateway: `Usage:
|
|
309
|
+
x402-cli gateway <search|start|check|scaffold|catalog> [options]
|
|
310
|
+
|
|
311
|
+
Commands:
|
|
312
|
+
search <query> Search a gateway/catalog artifact
|
|
313
|
+
start Start a local x402 gateway process
|
|
314
|
+
check <providers> Validate provider.yml files
|
|
315
|
+
scaffold <name> Write a starter provider.yml
|
|
316
|
+
catalog <command> Build/check/search gateway catalog assets
|
|
317
|
+
`,
|
|
318
|
+
catalog: `Usage:
|
|
319
|
+
x402-cli catalog <update|search|show|endpoints|pay-json|export-gateway|build> [options]
|
|
320
|
+
|
|
321
|
+
Commands:
|
|
322
|
+
update Cache hosted/local catalog assets under ~/.cache
|
|
323
|
+
search <query> Search providers
|
|
324
|
+
show <provider> Show provider detail JSON
|
|
325
|
+
endpoints <provider> List provider endpoints
|
|
326
|
+
pay-json <provider> Print provider pay JSON
|
|
327
|
+
export-gateway <url> Export catalog.json and pay.md from a gateway
|
|
328
|
+
build <providers> Build catalog from provider.yml files
|
|
329
|
+
|
|
330
|
+
Options:
|
|
331
|
+
--catalog <source> catalog.json path or URL
|
|
332
|
+
--provider <fqn> Provider FQN for export-gateway
|
|
333
|
+
--output-dir <dir> Output directory for generated files
|
|
334
|
+
-n, --limit <count> Search result limit
|
|
335
|
+
--include-blocked Include blocked providers in search
|
|
336
|
+
--json Print JSON envelope where supported
|
|
337
|
+
`,
|
|
338
|
+
};
|
|
339
|
+
return sections[topic] ?? sections.root;
|
|
340
|
+
}
|
|
51
341
|
function readYaml(file) {
|
|
52
342
|
return YAML.parse(fs.readFileSync(file, "utf8"));
|
|
53
343
|
}
|
|
@@ -145,6 +435,67 @@ function providerCatalog(provider) {
|
|
|
145
435
|
})),
|
|
146
436
|
};
|
|
147
437
|
}
|
|
438
|
+
const FIELD_WEIGHTS = {
|
|
439
|
+
fqn: 12,
|
|
440
|
+
title: 10,
|
|
441
|
+
tags: 8,
|
|
442
|
+
chain_kinds: 8,
|
|
443
|
+
chains: 8,
|
|
444
|
+
category: 6,
|
|
445
|
+
category_meta: 6,
|
|
446
|
+
endpoints: 6,
|
|
447
|
+
i18n: 5,
|
|
448
|
+
description: 4,
|
|
449
|
+
use_case: 4,
|
|
450
|
+
service_url: 2,
|
|
451
|
+
};
|
|
452
|
+
function stringList(value) {
|
|
453
|
+
return Array.isArray(value) ? value.filter(item => item != null).map(String) : [];
|
|
454
|
+
}
|
|
455
|
+
function dictValues(value) {
|
|
456
|
+
if (!value || typeof value !== "object")
|
|
457
|
+
return [];
|
|
458
|
+
const out = [];
|
|
459
|
+
for (const child of Object.values(value)) {
|
|
460
|
+
if (child && typeof child === "object" && !Array.isArray(child))
|
|
461
|
+
out.push(...dictValues(child));
|
|
462
|
+
else if (Array.isArray(child))
|
|
463
|
+
out.push(...child.filter(item => item != null).map(String));
|
|
464
|
+
else if (child != null)
|
|
465
|
+
out.push(String(child));
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
468
|
+
}
|
|
469
|
+
function chainMetaValues(chainsMeta) {
|
|
470
|
+
return chainsMeta.flatMap(dictValues);
|
|
471
|
+
}
|
|
472
|
+
function endpointFields(endpoints) {
|
|
473
|
+
const values = [];
|
|
474
|
+
for (const endpoint of endpoints) {
|
|
475
|
+
values.push(String(endpoint.method ?? ""), String(endpoint.path ?? ""), String(endpoint.probe_status ?? ""));
|
|
476
|
+
const paid = endpoint.paid;
|
|
477
|
+
if (paid && typeof paid === "object") {
|
|
478
|
+
values.push(String(paid.network ?? ""), String(paid.currency ?? ""), String(paid.amount_raw ?? ""));
|
|
479
|
+
}
|
|
480
|
+
values.push(String(endpoint.title ?? ""), String(endpoint.description ?? ""), String(endpoint.use_case ?? endpoint.useCase ?? ""));
|
|
481
|
+
}
|
|
482
|
+
return values;
|
|
483
|
+
}
|
|
484
|
+
function scoreFields(terms, fields) {
|
|
485
|
+
let score = 0;
|
|
486
|
+
const matchedFields = [];
|
|
487
|
+
for (const [field, values] of Object.entries(fields)) {
|
|
488
|
+
const haystack = values.filter(Boolean).join(" ").toLowerCase();
|
|
489
|
+
if (!haystack)
|
|
490
|
+
continue;
|
|
491
|
+
const count = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
492
|
+
if (count) {
|
|
493
|
+
score += (FIELD_WEIGHTS[field] ?? 1) * count;
|
|
494
|
+
matchedFields.push(field);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return { score, matchedFields };
|
|
498
|
+
}
|
|
148
499
|
async function readCatalog(source) {
|
|
149
500
|
const text = await readText(source);
|
|
150
501
|
const parsed = JSON.parse(text);
|
|
@@ -156,6 +507,13 @@ async function readCatalog(source) {
|
|
|
156
507
|
return parsed.items;
|
|
157
508
|
return [];
|
|
158
509
|
}
|
|
510
|
+
async function readCatalogObject(source) {
|
|
511
|
+
const parsed = JSON.parse(await readText(source));
|
|
512
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
513
|
+
throw new Error(`expected JSON object from ${source}`);
|
|
514
|
+
}
|
|
515
|
+
return parsed;
|
|
516
|
+
}
|
|
159
517
|
async function readText(source) {
|
|
160
518
|
if (!source.startsWith("http://") && !source.startsWith("https://")) {
|
|
161
519
|
return fs.readFileSync(source, "utf8");
|
|
@@ -168,6 +526,55 @@ async function readText(source) {
|
|
|
168
526
|
async function readJson(source) {
|
|
169
527
|
return JSON.parse(await readText(source));
|
|
170
528
|
}
|
|
529
|
+
function writeJson(file, value) {
|
|
530
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
531
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
532
|
+
}
|
|
533
|
+
async function responsePayload(response) {
|
|
534
|
+
const text = await response.text();
|
|
535
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
536
|
+
if (contentType.toLowerCase().includes("json")) {
|
|
537
|
+
try {
|
|
538
|
+
return JSON.parse(text);
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
return text;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return text;
|
|
545
|
+
}
|
|
546
|
+
function cacheDir() {
|
|
547
|
+
return path.join(os.homedir(), ".cache", "x402-cli", "catalog");
|
|
548
|
+
}
|
|
549
|
+
function cachedCatalogPath() {
|
|
550
|
+
return path.join(cacheDir(), "catalog.json");
|
|
551
|
+
}
|
|
552
|
+
function providerFilename(fqn) {
|
|
553
|
+
return `${fqn.replace(/\//g, "__")}.json`;
|
|
554
|
+
}
|
|
555
|
+
function defaultCatalogSource() {
|
|
556
|
+
return fs.existsSync(cachedCatalogPath())
|
|
557
|
+
? cachedCatalogPath()
|
|
558
|
+
: "https://x402-catelog.bankofai.io/api/catalog.json";
|
|
559
|
+
}
|
|
560
|
+
function remoteBaseFromCatalogPayload(payload) {
|
|
561
|
+
const base = payload.base_url ?? payload.baseUrl;
|
|
562
|
+
return typeof base === "string" && /^https?:\/\//.test(base) ? `${base.replace(/\/+$/, "")}/` : undefined;
|
|
563
|
+
}
|
|
564
|
+
async function remoteBaseFromSource(source, payload) {
|
|
565
|
+
const fromPayload = payload ? remoteBaseFromCatalogPayload(payload) : undefined;
|
|
566
|
+
if (fromPayload)
|
|
567
|
+
return fromPayload;
|
|
568
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
569
|
+
return `${source.slice(0, source.lastIndexOf("/") + 1)}`;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
return remoteBaseFromCatalogPayload(await readCatalogObject(source));
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
171
578
|
function catalogDetailSource(source, section, name) {
|
|
172
579
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
173
580
|
const base = new URL(source);
|
|
@@ -183,7 +590,10 @@ function catalogDetailSource(source, section, name) {
|
|
|
183
590
|
}
|
|
184
591
|
const stat = fs.existsSync(source) ? fs.statSync(source) : undefined;
|
|
185
592
|
const root = stat?.isDirectory() ? source : path.dirname(source);
|
|
186
|
-
|
|
593
|
+
const direct = path.join(root, section, `${name}.json`);
|
|
594
|
+
if (fs.existsSync(direct))
|
|
595
|
+
return direct;
|
|
596
|
+
return path.join(root, section, providerFilename(name));
|
|
187
597
|
}
|
|
188
598
|
async function readCatalogProvider(source, name) {
|
|
189
599
|
const providers = await readCatalog(source);
|
|
@@ -213,19 +623,168 @@ async function readCatalogPayProvider(source, name) {
|
|
|
213
623
|
throw new Error(`provider not found: ${name}`);
|
|
214
624
|
}
|
|
215
625
|
}
|
|
626
|
+
async function cacheProviderAssets(source, catalogPayload) {
|
|
627
|
+
const base = await remoteBaseFromSource(source, catalogPayload);
|
|
628
|
+
if (!base)
|
|
629
|
+
return { detailCount: 0, payCount: 0 };
|
|
630
|
+
let detailCount = 0;
|
|
631
|
+
let payCount = 0;
|
|
632
|
+
for (const provider of catalogPayload.providers ?? []) {
|
|
633
|
+
const fqn = provider?.fqn ?? provider?.name;
|
|
634
|
+
if (typeof fqn !== "string" || !fqn)
|
|
635
|
+
continue;
|
|
636
|
+
const filename = providerFilename(fqn);
|
|
637
|
+
try {
|
|
638
|
+
const detail = await readJson(new URL(`providers/${filename}`, base).toString());
|
|
639
|
+
writeJson(path.join(cacheDir(), "providers", filename), detail);
|
|
640
|
+
detailCount += 1;
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
// A partial cache is still useful.
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
const pay = await readJson(new URL(`pay/${filename}`, base).toString());
|
|
647
|
+
writeJson(path.join(cacheDir(), "pay", filename), pay);
|
|
648
|
+
payCount += 1;
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// A partial cache is still useful.
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return { detailCount, payCount };
|
|
655
|
+
}
|
|
656
|
+
async function catalogUpdate(source, options) {
|
|
657
|
+
const payload = await readCatalogObject(source);
|
|
658
|
+
writeJson(cachedCatalogPath(), payload);
|
|
659
|
+
const cached = await cacheProviderAssets(source, payload);
|
|
660
|
+
const result = {
|
|
661
|
+
source,
|
|
662
|
+
path: cachedCatalogPath(),
|
|
663
|
+
providerCount: payload.provider_count ?? payload.providerCount ?? (payload.providers ?? []).length,
|
|
664
|
+
detailCount: cached.detailCount,
|
|
665
|
+
payCount: cached.payCount,
|
|
666
|
+
};
|
|
667
|
+
emit({ command: "catalog update", mode: outputMode(options), result });
|
|
668
|
+
}
|
|
669
|
+
function zhCopy(title, subtitle, description, useCase) {
|
|
670
|
+
return { title, subtitle, description, useCase };
|
|
671
|
+
}
|
|
672
|
+
function submissionCatalog(detail) {
|
|
673
|
+
const title = String(detail.title ?? detail.fqn);
|
|
674
|
+
const subtitle = String(detail.subtitle ?? detail.use_case ?? title);
|
|
675
|
+
const description = String(detail.description ?? subtitle);
|
|
676
|
+
const useCase = String(detail.use_case ?? detail.useCase ?? description);
|
|
677
|
+
return {
|
|
678
|
+
version: 1,
|
|
679
|
+
fqn: detail.fqn,
|
|
680
|
+
title,
|
|
681
|
+
subtitle,
|
|
682
|
+
description,
|
|
683
|
+
useCase,
|
|
684
|
+
i18n: detail.i18n ?? { "zh-CN": zhCopy(title, subtitle, description, useCase) },
|
|
685
|
+
logo: detail.logo ?? "https://x402-catelog.bankofai.io/assets/providers/default.png",
|
|
686
|
+
category: detail.category ?? "other",
|
|
687
|
+
chains: detail.chains ?? [],
|
|
688
|
+
isFirstParty: Boolean(detail.is_first_party ?? detail.isFirstParty),
|
|
689
|
+
isFeatured: Boolean(detail.is_featured ?? detail.isFeatured),
|
|
690
|
+
featuredTags: detail.featured_tags ?? detail.featuredTags ?? [],
|
|
691
|
+
serviceUrl: detail.service_url ?? detail.serviceUrl,
|
|
692
|
+
endpoints: (detail.endpoints ?? []).map((endpoint) => {
|
|
693
|
+
const endpointTitle = endpoint.title ?? endpoint.path;
|
|
694
|
+
const endpointSubtitle = endpoint.subtitle ?? endpoint.path;
|
|
695
|
+
const endpointDescription = endpoint.description ?? description;
|
|
696
|
+
const endpointUseCase = endpoint.use_case ?? endpoint.useCase ?? useCase;
|
|
697
|
+
return {
|
|
698
|
+
method: endpoint.method,
|
|
699
|
+
path: endpoint.path,
|
|
700
|
+
url: endpoint.url,
|
|
701
|
+
title: endpointTitle,
|
|
702
|
+
subtitle: endpointSubtitle,
|
|
703
|
+
description: endpointDescription,
|
|
704
|
+
useCase: endpointUseCase,
|
|
705
|
+
i18n: endpoint.i18n ?? { "zh-CN": zhCopy(endpointTitle, endpointSubtitle, endpointDescription, endpointUseCase) },
|
|
706
|
+
metered: Boolean(endpoint.metered),
|
|
707
|
+
minPriceUsd: endpoint.min_price_usd ?? endpoint.minPriceUsd ?? 0,
|
|
708
|
+
maxPriceUsd: endpoint.max_price_usd ?? endpoint.maxPriceUsd ?? 0,
|
|
709
|
+
};
|
|
710
|
+
}),
|
|
711
|
+
status: detail.status ?? {
|
|
712
|
+
catalog: "draft",
|
|
713
|
+
gateway: "unknown",
|
|
714
|
+
payment: "unknown",
|
|
715
|
+
upstream: "unknown",
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function payMarkdownFromDetail(detail) {
|
|
720
|
+
const lines = [
|
|
721
|
+
`# ${detail.title ?? detail.fqn}`,
|
|
722
|
+
"",
|
|
723
|
+
"## Service",
|
|
724
|
+
"",
|
|
725
|
+
`- FQN: \`${detail.fqn}\``,
|
|
726
|
+
`- Service URL: \`${detail.service_url ?? detail.serviceUrl ?? ""}\``,
|
|
727
|
+
`- Category: \`${detail.category ?? ""}\``,
|
|
728
|
+
`- Chains: \`${(detail.chains ?? []).join(", ")}\``,
|
|
729
|
+
"",
|
|
730
|
+
"## Endpoints",
|
|
731
|
+
"",
|
|
732
|
+
];
|
|
733
|
+
for (const endpoint of detail.endpoints ?? []) {
|
|
734
|
+
const metered = Boolean(endpoint.metered);
|
|
735
|
+
const price = endpoint.min_price_usd ?? endpoint.minPriceUsd ?? 0;
|
|
736
|
+
lines.push(`### ${endpoint.method} ${endpoint.path}`, "", endpoint.description ?? "", "", `- URL: \`${endpoint.url ?? ""}\``, `- Metered: \`${String(metered)}\``, `- Price: \`$${price}\``, "");
|
|
737
|
+
if (metered) {
|
|
738
|
+
lines.push("```bash", `x402-cli pay '${endpoint.url ?? ""}'`, "```", "");
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
lines.push("No payment required.", "");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
lines.push("## Notes", "", "This file is public. Do not include upstream API keys, bearer tokens, provider.yml, `.env`, passwords, or private infrastructure URLs.", "");
|
|
745
|
+
return lines.join("\n");
|
|
746
|
+
}
|
|
747
|
+
async function catalogExportGateway(gatewayUrl, options) {
|
|
748
|
+
const providerFqn = opt(options, "provider");
|
|
749
|
+
if (!providerFqn)
|
|
750
|
+
throw new Error("--provider is required");
|
|
751
|
+
const base = gatewayUrl.replace(/\/+$/, "");
|
|
752
|
+
const detail = await readJson(`${base}/__402/catalog/providers/${providerFqn}.json`);
|
|
753
|
+
const target = opt(options, "output-dir", path.join("providers", providerFqn));
|
|
754
|
+
fs.mkdirSync(target, { recursive: true });
|
|
755
|
+
const catalogPath = path.join(target, "catalog.json");
|
|
756
|
+
const payMdPath = path.join(target, "pay.md");
|
|
757
|
+
writeJson(catalogPath, submissionCatalog(detail));
|
|
758
|
+
fs.writeFileSync(payMdPath, payMarkdownFromDetail(detail));
|
|
759
|
+
emit({ command: "catalog export-gateway", mode: outputMode(options), result: { provider: providerFqn, catalog: catalogPath, payMd: payMdPath } });
|
|
760
|
+
}
|
|
216
761
|
function buildRequirement(options) {
|
|
217
762
|
const network = normalizeNetwork(opt(options, "network", "tron:nile"));
|
|
218
763
|
const tokenSymbol = opt(options, "token", "USDT");
|
|
219
|
-
const
|
|
220
|
-
const
|
|
764
|
+
const explicitAsset = opt(options, "asset");
|
|
765
|
+
const registryToken = explicitAsset
|
|
766
|
+
? findTokenByAddress(network, explicitAsset)
|
|
767
|
+
: getToken(network, tokenSymbol);
|
|
768
|
+
const decimalsOption = opt(options, "decimals");
|
|
769
|
+
if (explicitAsset && !registryToken && decimalsOption === undefined) {
|
|
770
|
+
throw new Error("When --asset is set without a registry match, --decimals must be provided");
|
|
771
|
+
}
|
|
772
|
+
if (!explicitAsset && !registryToken)
|
|
773
|
+
throw new Error(`unknown token ${tokenSymbol} on ${network}`);
|
|
774
|
+
const decimals = decimalsOption !== undefined ? Number(decimalsOption) : registryToken.decimals;
|
|
775
|
+
if (!Number.isInteger(decimals) || decimals < 0)
|
|
776
|
+
throw new Error("--decimals must be a non-negative integer");
|
|
777
|
+
const amount = opt(options, "rawAmount") ?? toSmallestUnit(opt(options, "amount", "0.0001"), decimals);
|
|
778
|
+
const assetAddress = explicitAsset ?? registryToken.address;
|
|
779
|
+
const assetTransferMethod = registryToken?.assetTransferMethod ?? "permit2";
|
|
221
780
|
return {
|
|
222
781
|
scheme: "exact",
|
|
223
782
|
network,
|
|
224
783
|
amount,
|
|
225
|
-
asset:
|
|
784
|
+
asset: assetAddress,
|
|
226
785
|
payTo: opt(options, "pay-to") ?? opt(options, "payTo") ?? "",
|
|
227
786
|
maxTimeoutSeconds: Number(opt(options, "valid-for-seconds", "300")),
|
|
228
|
-
extra:
|
|
787
|
+
extra: assetTransferMethod ? { assetTransferMethod } : {},
|
|
229
788
|
};
|
|
230
789
|
}
|
|
231
790
|
async function facilitatorPost(baseUrl, path, body) {
|
|
@@ -250,6 +809,34 @@ function requestHeaders(options) {
|
|
|
250
809
|
}
|
|
251
810
|
return headersOut;
|
|
252
811
|
}
|
|
812
|
+
function stripFlag(argv, flag) {
|
|
813
|
+
return argv.filter(item => item !== flag);
|
|
814
|
+
}
|
|
815
|
+
function serveDaemon(argv, options) {
|
|
816
|
+
const daemonArgs = stripFlag(stripFlag(argv, "--daemon"), "-d");
|
|
817
|
+
const script = fileURLToPath(import.meta.url);
|
|
818
|
+
const child = spawn(process.execPath, [script, ...daemonArgs], {
|
|
819
|
+
detached: true,
|
|
820
|
+
stdio: "ignore",
|
|
821
|
+
env: process.env,
|
|
822
|
+
});
|
|
823
|
+
child.unref();
|
|
824
|
+
const host = opt(options, "host", "127.0.0.1");
|
|
825
|
+
const port = Number(opt(options, "port", "4020"));
|
|
826
|
+
const resourceUrl = opt(options, "resource-url", `http://${host}:${port}/pay`);
|
|
827
|
+
emit({
|
|
828
|
+
command: "server",
|
|
829
|
+
mode: outputMode(options),
|
|
830
|
+
network: normalizeNetwork(opt(options, "network", "tron:nile")),
|
|
831
|
+
scheme: "exact",
|
|
832
|
+
result: {
|
|
833
|
+
pid: child.pid,
|
|
834
|
+
pay_url: resourceUrl,
|
|
835
|
+
resource_url: resourceUrl,
|
|
836
|
+
daemon: true,
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
}
|
|
253
840
|
function validateAmountLimits(selected, options) {
|
|
254
841
|
const maxRaw = opt(options, "max-rawAmount") ?? opt(options, "max-raw-amount");
|
|
255
842
|
const maxAmount = opt(options, "max-amount");
|
|
@@ -257,8 +844,15 @@ function validateAmountLimits(selected, options) {
|
|
|
257
844
|
throw new Error(`payment raw amount ${selected.amount} exceeds --max-rawAmount ${maxRaw}`);
|
|
258
845
|
}
|
|
259
846
|
if (maxAmount) {
|
|
260
|
-
const token =
|
|
261
|
-
|
|
847
|
+
const token = findTokenByAddress(selected.network, selected.asset);
|
|
848
|
+
const decimalsOption = opt(options, "decimals");
|
|
849
|
+
if (!token && decimalsOption === undefined) {
|
|
850
|
+
throw new Error("cannot evaluate --max-amount for an unknown asset; pass --max-rawAmount or --decimals");
|
|
851
|
+
}
|
|
852
|
+
const decimals = decimalsOption !== undefined ? Number(decimalsOption) : token.decimals;
|
|
853
|
+
if (!Number.isInteger(decimals) || decimals < 0)
|
|
854
|
+
throw new Error("--decimals must be a non-negative integer");
|
|
855
|
+
if (BigInt(selected.amount) > BigInt(toSmallestUnit(maxAmount, decimals))) {
|
|
262
856
|
throw new Error(`payment amount exceeds --max-amount ${maxAmount}`);
|
|
263
857
|
}
|
|
264
858
|
}
|
|
@@ -266,7 +860,7 @@ function validateAmountLimits(selected, options) {
|
|
|
266
860
|
async function serve(options) {
|
|
267
861
|
const host = opt(options, "host", "127.0.0.1");
|
|
268
862
|
const port = Number(opt(options, "port", "4020"));
|
|
269
|
-
const facilitatorUrl = opt(options, "facilitator-url",
|
|
863
|
+
const facilitatorUrl = opt(options, "facilitator-url", "https://facilitator.bankofai.io");
|
|
270
864
|
const requirement = buildRequirement(options);
|
|
271
865
|
if (!requirement.payTo)
|
|
272
866
|
throw new Error("--pay-to is required");
|
|
@@ -330,7 +924,13 @@ async function serve(options) {
|
|
|
330
924
|
}
|
|
331
925
|
});
|
|
332
926
|
server.listen(port, host, () => {
|
|
333
|
-
|
|
927
|
+
emit({
|
|
928
|
+
command: "server",
|
|
929
|
+
network: requirement.network,
|
|
930
|
+
scheme: "exact",
|
|
931
|
+
mode: outputMode(options),
|
|
932
|
+
result: { pay_url: resourceUrl, token: opt(options, "token", "USDT"), rawAmount: requirement.amount, pay_to: requirement.payTo },
|
|
933
|
+
});
|
|
334
934
|
});
|
|
335
935
|
}
|
|
336
936
|
function selectRequirement(accepts, options) {
|
|
@@ -354,6 +954,8 @@ function selectRequirement(accepts, options) {
|
|
|
354
954
|
return selected;
|
|
355
955
|
}
|
|
356
956
|
async function pay(url, options) {
|
|
957
|
+
if (!url)
|
|
958
|
+
throw new Error("URL is required");
|
|
357
959
|
const method = opt(options, "method", "GET");
|
|
358
960
|
const baseHeaders = requestHeaders(options);
|
|
359
961
|
const probe = await fetch(url, {
|
|
@@ -362,7 +964,16 @@ async function pay(url, options) {
|
|
|
362
964
|
body: ["GET", "HEAD"].includes(method.toUpperCase()) ? undefined : opt(options, "body"),
|
|
363
965
|
});
|
|
364
966
|
if (probe.status !== 402) {
|
|
365
|
-
|
|
967
|
+
emit({
|
|
968
|
+
command: "client",
|
|
969
|
+
mode: outputMode(options),
|
|
970
|
+
result: {
|
|
971
|
+
url,
|
|
972
|
+
status: probe.status,
|
|
973
|
+
message: "Not a payment-required endpoint",
|
|
974
|
+
response: await responsePayload(probe),
|
|
975
|
+
},
|
|
976
|
+
});
|
|
366
977
|
return;
|
|
367
978
|
}
|
|
368
979
|
const header = probe.headers.get(headers.required);
|
|
@@ -372,11 +983,11 @@ async function pay(url, options) {
|
|
|
372
983
|
const selected = selectRequirement(required.accepts ?? [], options);
|
|
373
984
|
validateAmountLimits(selected, options);
|
|
374
985
|
if (options["dry-run"]) {
|
|
375
|
-
|
|
376
|
-
ok: true,
|
|
986
|
+
emit({
|
|
377
987
|
command: "client",
|
|
378
988
|
network: selected.network,
|
|
379
989
|
scheme: "exact",
|
|
990
|
+
mode: outputMode(options),
|
|
380
991
|
result: {
|
|
381
992
|
url,
|
|
382
993
|
resource: required.resource?.url ?? url,
|
|
@@ -400,18 +1011,24 @@ async function pay(url, options) {
|
|
|
400
1011
|
headers: retryHeaders,
|
|
401
1012
|
body: opt(options, "body"),
|
|
402
1013
|
});
|
|
403
|
-
const
|
|
1014
|
+
const body = await responsePayload(paid);
|
|
404
1015
|
const paymentResponse = paid.headers.get(headers.response);
|
|
405
|
-
|
|
406
|
-
|
|
1016
|
+
const result = {
|
|
1017
|
+
url,
|
|
1018
|
+
status: paid.status,
|
|
1019
|
+
paid: paid.ok,
|
|
1020
|
+
response: body,
|
|
1021
|
+
...(paymentResponse ? { paymentResponse: decodeResponse(paymentResponse) } : {}),
|
|
1022
|
+
};
|
|
1023
|
+
if (!paid.ok) {
|
|
1024
|
+
throw new Error(`HTTP ${paid.status} from ${url}: ${typeof body === "string" ? body.slice(0, 500) : JSON.stringify(body).slice(0, 500)}`);
|
|
1025
|
+
}
|
|
1026
|
+
emit({
|
|
407
1027
|
command: "client",
|
|
408
1028
|
network: selected.network,
|
|
409
1029
|
scheme: "exact",
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
body: text,
|
|
413
|
-
...(paymentResponse ? { paymentResponse: decodeResponse(paymentResponse) } : {}),
|
|
414
|
-
},
|
|
1030
|
+
mode: outputMode(options),
|
|
1031
|
+
result,
|
|
415
1032
|
});
|
|
416
1033
|
}
|
|
417
1034
|
async function roundtrip(options) {
|
|
@@ -421,12 +1038,46 @@ async function roundtrip(options) {
|
|
|
421
1038
|
await pay(`http://127.0.0.1:${port}/pay`, options);
|
|
422
1039
|
process.exit(0);
|
|
423
1040
|
}
|
|
424
|
-
function
|
|
425
|
-
|
|
426
|
-
|
|
1041
|
+
function executableInPath(name) {
|
|
1042
|
+
for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
1043
|
+
if (!dir)
|
|
1044
|
+
continue;
|
|
1045
|
+
const candidate = path.join(dir, name);
|
|
1046
|
+
try {
|
|
1047
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
1048
|
+
return candidate;
|
|
1049
|
+
}
|
|
1050
|
+
catch {
|
|
1051
|
+
// Try the next PATH entry.
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return undefined;
|
|
1055
|
+
}
|
|
1056
|
+
function gatewayCommand(options) {
|
|
1057
|
+
const explicit = opt(options, "gateway-bin");
|
|
1058
|
+
const candidates = [
|
|
1059
|
+
explicit ? { file: explicit, source: "--gateway-bin" } : undefined,
|
|
1060
|
+
{ file: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "gateway", "cli.js"), source: "bundled gateway runtime" },
|
|
1061
|
+
executableInPath("x402-gateway") ? { file: executableInPath("x402-gateway"), source: "PATH x402-gateway" } : undefined,
|
|
1062
|
+
{ file: path.resolve(process.cwd(), "../x402-gateway/dist/cli.js"), source: "sibling ../x402-gateway" },
|
|
1063
|
+
].filter(Boolean);
|
|
1064
|
+
for (const candidate of candidates) {
|
|
1065
|
+
try {
|
|
1066
|
+
fs.accessSync(candidate.file, fs.constants.R_OK);
|
|
1067
|
+
if (candidate.file.endsWith(".js")) {
|
|
1068
|
+
return { command: process.execPath, argsPrefix: [candidate.file], source: candidate.source };
|
|
1069
|
+
}
|
|
1070
|
+
return { command: candidate.file, argsPrefix: [], source: candidate.source };
|
|
1071
|
+
}
|
|
1072
|
+
catch {
|
|
1073
|
+
// Try the next candidate.
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
throw new Error("x402-gateway runtime not found. Install/publish a x402-gateway npm binary, run from a checkout with ../x402-gateway/dist/cli.js, or pass --gateway-bin <path>.");
|
|
427
1077
|
}
|
|
428
|
-
async function gatewayStart(args) {
|
|
429
|
-
const
|
|
1078
|
+
async function gatewayStart(args, options) {
|
|
1079
|
+
const gateway = gatewayCommand(options);
|
|
1080
|
+
const child = spawn(gateway.command, [...gateway.argsPrefix, ...args], {
|
|
430
1081
|
stdio: "inherit",
|
|
431
1082
|
env: process.env,
|
|
432
1083
|
});
|
|
@@ -435,7 +1086,7 @@ async function gatewayStart(args) {
|
|
|
435
1086
|
child.on("error", reject);
|
|
436
1087
|
});
|
|
437
1088
|
}
|
|
438
|
-
function gatewayCheck(target) {
|
|
1089
|
+
function gatewayCheck(target, options) {
|
|
439
1090
|
const files = providerFiles(target);
|
|
440
1091
|
const providers = files.map(loadProviderFile);
|
|
441
1092
|
const names = new Set();
|
|
@@ -444,7 +1095,11 @@ function gatewayCheck(target) {
|
|
|
444
1095
|
throw new Error(`duplicate provider name: ${provider.name}`);
|
|
445
1096
|
names.add(provider.name);
|
|
446
1097
|
}
|
|
447
|
-
|
|
1098
|
+
emit({
|
|
1099
|
+
command: "gateway check",
|
|
1100
|
+
mode: outputMode(options),
|
|
1101
|
+
result: { providers: providers.map(p => p.name), count: providers.length },
|
|
1102
|
+
});
|
|
448
1103
|
}
|
|
449
1104
|
function gatewayScaffold(name, options) {
|
|
450
1105
|
const outputDir = opt(options, "output-dir", path.join("providers", name));
|
|
@@ -465,12 +1120,12 @@ operator:
|
|
|
465
1120
|
network: tron-nile
|
|
466
1121
|
currencies:
|
|
467
1122
|
usd: ["USDT"]
|
|
468
|
-
recipient:
|
|
1123
|
+
recipient: <provider-recipient-address>
|
|
469
1124
|
scheme: exact
|
|
470
1125
|
protocol: exact
|
|
471
1126
|
asset_transfer_method: permit2
|
|
472
|
-
facilitator_url:
|
|
473
|
-
facilitator_api_key:
|
|
1127
|
+
facilitator_url: https://facilitator.bankofai.io
|
|
1128
|
+
facilitator_api_key: <facilitator-api-key>
|
|
474
1129
|
valid_for_seconds: 300
|
|
475
1130
|
|
|
476
1131
|
endpoints:
|
|
@@ -480,9 +1135,13 @@ endpoints:
|
|
|
480
1135
|
dimensions:
|
|
481
1136
|
- tiers:
|
|
482
1137
|
- price_usd: 0.0001
|
|
483
|
-
`;
|
|
1138
|
+
`;
|
|
484
1139
|
fs.writeFileSync(path.join(outputDir, "provider.yml"), body);
|
|
485
|
-
|
|
1140
|
+
emit({
|
|
1141
|
+
command: "gateway scaffold",
|
|
1142
|
+
mode: outputMode(options),
|
|
1143
|
+
result: { file: path.join(outputDir, "provider.yml") },
|
|
1144
|
+
});
|
|
486
1145
|
}
|
|
487
1146
|
function catalogBuild(target, options) {
|
|
488
1147
|
const providers = providerFiles(target).map(loadProviderFile).map(providerCatalog);
|
|
@@ -491,13 +1150,17 @@ function catalogBuild(target, options) {
|
|
|
491
1150
|
if (output) {
|
|
492
1151
|
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
493
1152
|
fs.writeFileSync(output, JSON.stringify(catalog, null, 2));
|
|
494
|
-
|
|
1153
|
+
emit({
|
|
1154
|
+
command: "catalog build",
|
|
1155
|
+
mode: outputMode(options),
|
|
1156
|
+
result: { output, count: providers.length },
|
|
1157
|
+
});
|
|
495
1158
|
}
|
|
496
1159
|
else {
|
|
497
1160
|
printJson(catalog);
|
|
498
1161
|
}
|
|
499
1162
|
}
|
|
500
|
-
function catalogPayAssets(target) {
|
|
1163
|
+
function catalogPayAssets(target, options) {
|
|
501
1164
|
const rows = providerFiles(target).map(loadProviderFile).flatMap(provider => (provider.endpoints ?? []).map((endpoint) => ({
|
|
502
1165
|
provider: provider.name,
|
|
503
1166
|
method: endpoint.method,
|
|
@@ -508,41 +1171,172 @@ function catalogPayAssets(target) {
|
|
|
508
1171
|
scheme: "exact",
|
|
509
1172
|
assetTransferMethod: providerAssetTransferMethod(provider),
|
|
510
1173
|
})));
|
|
511
|
-
|
|
1174
|
+
if (outputMode(options) === "json") {
|
|
1175
|
+
printJson({ assets: rows, count: rows.length });
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
emit({
|
|
1179
|
+
command: "gateway catalog pay-assets",
|
|
1180
|
+
mode: "human",
|
|
1181
|
+
result: { count: rows.length, assets: rows },
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
512
1184
|
}
|
|
513
|
-
async function
|
|
1185
|
+
async function readProviderDetailForSearch(source, fqn) {
|
|
1186
|
+
try {
|
|
1187
|
+
return await readJson(catalogDetailSource(source, "providers", fqn));
|
|
1188
|
+
}
|
|
1189
|
+
catch {
|
|
1190
|
+
return {};
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
async function searchCatalog(source, query, options) {
|
|
514
1194
|
const providers = await readCatalog(source);
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
.
|
|
536
|
-
.
|
|
1195
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1196
|
+
if (!terms.length)
|
|
1197
|
+
return [];
|
|
1198
|
+
const includeBlocked = hasFlag(options, "include-blocked");
|
|
1199
|
+
const hits = [];
|
|
1200
|
+
for (const provider of providers) {
|
|
1201
|
+
if (provider.block && !includeBlocked)
|
|
1202
|
+
continue;
|
|
1203
|
+
const fqn = String(provider.fqn ?? provider.name ?? "");
|
|
1204
|
+
if (!fqn)
|
|
1205
|
+
continue;
|
|
1206
|
+
const detail = await readProviderDetailForSearch(source, fqn);
|
|
1207
|
+
const tags = stringList(detail.featured_tags ?? provider.featured_tags ?? detail.tags ?? provider.tags);
|
|
1208
|
+
const endpoints = Array.isArray(detail.endpoints) ? detail.endpoints : Array.isArray(provider.endpoints) ? provider.endpoints : [];
|
|
1209
|
+
const categoryMeta = detail.category_meta ?? provider.category_meta;
|
|
1210
|
+
const chains = stringList(detail.chains ?? provider.chains);
|
|
1211
|
+
const chainKinds = stringList(detail.chain_kinds ?? provider.chain_kinds);
|
|
1212
|
+
const chainsMetaRaw = detail.chains_meta ?? provider.chains_meta ?? [];
|
|
1213
|
+
const chainsMeta = Array.isArray(chainsMetaRaw) ? chainsMetaRaw.filter(item => item && typeof item === "object") : [];
|
|
1214
|
+
const titleZh = String(detail.title_zh ?? provider.title_zh ?? "");
|
|
1215
|
+
const mainTitle = String(detail.main_title ?? provider.main_title ?? detail.mainTitle ?? provider.mainTitle ?? "");
|
|
1216
|
+
const subTitle = String(detail.sub_title ?? provider.sub_title ?? detail.subTitle ?? provider.subTitle ?? "");
|
|
1217
|
+
const fields = {
|
|
1218
|
+
fqn: [fqn],
|
|
1219
|
+
title: [String(detail.title ?? provider.title ?? ""), mainTitle],
|
|
1220
|
+
i18n: [titleZh, subTitle, ...dictValues(detail.i18n ?? provider.i18n)],
|
|
1221
|
+
category: [String(detail.category ?? provider.category ?? "")],
|
|
1222
|
+
category_meta: dictValues(categoryMeta),
|
|
1223
|
+
chains: [...chains, ...chainMetaValues(chainsMeta)],
|
|
1224
|
+
chain_kinds: chainKinds,
|
|
1225
|
+
service_url: [String(detail.service_url ?? provider.service_url ?? detail.serviceUrl ?? provider.serviceUrl ?? "")],
|
|
1226
|
+
description: [String(detail.description ?? provider.description ?? "")],
|
|
1227
|
+
use_case: [String(detail.use_case ?? provider.use_case ?? detail.useCase ?? provider.useCase ?? "")],
|
|
1228
|
+
tags,
|
|
1229
|
+
endpoints: endpointFields(endpoints),
|
|
1230
|
+
};
|
|
1231
|
+
const scored = scoreFields(terms, fields);
|
|
1232
|
+
if (scored.score === 0)
|
|
1233
|
+
continue;
|
|
1234
|
+
hits.push({
|
|
1235
|
+
provider,
|
|
1236
|
+
detail,
|
|
1237
|
+
fqn,
|
|
1238
|
+
title: fields.title[0],
|
|
1239
|
+
category: fields.category[0],
|
|
1240
|
+
serviceUrl: fields.service_url[0],
|
|
1241
|
+
description: fields.description[0] || undefined,
|
|
1242
|
+
useCase: fields.use_case[0] || undefined,
|
|
1243
|
+
titleZh: titleZh || undefined,
|
|
1244
|
+
mainTitle: mainTitle || undefined,
|
|
1245
|
+
subTitle: subTitle || undefined,
|
|
1246
|
+
categoryMeta: categoryMeta && typeof categoryMeta === "object" ? categoryMeta : undefined,
|
|
1247
|
+
chains,
|
|
1248
|
+
chainKinds,
|
|
1249
|
+
chainsMeta,
|
|
1250
|
+
tags,
|
|
1251
|
+
endpoints,
|
|
1252
|
+
score: scored.score,
|
|
1253
|
+
matchedFields: scored.matchedFields,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
return hits
|
|
1257
|
+
.sort((a, b) => b.score - a.score || a.fqn.localeCompare(b.fqn))
|
|
537
1258
|
.slice(0, Number(opt(options, "limit", "10")));
|
|
538
|
-
printJson({ query, count: hits.length, results: hits.map(item => item.provider) });
|
|
539
1259
|
}
|
|
540
|
-
|
|
541
|
-
|
|
1260
|
+
function searchHitToJson(hit) {
|
|
1261
|
+
return {
|
|
1262
|
+
fqn: hit.fqn,
|
|
1263
|
+
title: hit.title,
|
|
1264
|
+
category: hit.category,
|
|
1265
|
+
serviceUrl: hit.serviceUrl,
|
|
1266
|
+
description: hit.description,
|
|
1267
|
+
useCase: hit.useCase,
|
|
1268
|
+
title_zh: hit.titleZh,
|
|
1269
|
+
main_title: hit.mainTitle,
|
|
1270
|
+
sub_title: hit.subTitle,
|
|
1271
|
+
category_meta: hit.categoryMeta,
|
|
1272
|
+
chains: hit.chains,
|
|
1273
|
+
chain_kinds: hit.chainKinds,
|
|
1274
|
+
chains_meta: hit.chainsMeta,
|
|
1275
|
+
tags: hit.tags,
|
|
1276
|
+
score: hit.score,
|
|
1277
|
+
matchedFields: hit.matchedFields,
|
|
1278
|
+
endpoints: hit.endpoints,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
async function catalogSearch(source, query, options) {
|
|
1282
|
+
const hits = await searchCatalog(source, query, options);
|
|
1283
|
+
const results = hits.map(searchHitToJson);
|
|
1284
|
+
if (outputMode(options) === "json") {
|
|
1285
|
+
printJson({ ok: true, command: "catalog search", query, catalog: source, count: hits.length, results });
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (!hits.length) {
|
|
1289
|
+
process.stdout.write("no matches\n");
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
for (const hit of hits) {
|
|
1293
|
+
const tags = hit.tags.length ? hit.tags.join(",") : "-";
|
|
1294
|
+
process.stdout.write(`${hit.fqn.padEnd(32)} score=${String(hit.score).padEnd(3)} category=${hit.category.padEnd(12)} tags=${tags}\n`);
|
|
1295
|
+
if (hit.title)
|
|
1296
|
+
process.stdout.write(` ${hit.title}\n`);
|
|
1297
|
+
if (hit.description)
|
|
1298
|
+
process.stdout.write(` ${hit.description.split("\n")[0]}\n`);
|
|
1299
|
+
if (hit.serviceUrl)
|
|
1300
|
+
process.stdout.write(` service: ${hit.serviceUrl}\n`);
|
|
1301
|
+
for (const endpoint of hit.endpoints.slice(0, 3)) {
|
|
1302
|
+
const method = String(endpoint.method ?? "");
|
|
1303
|
+
const pathText = String(endpoint.path ?? endpoint.url ?? "");
|
|
1304
|
+
const paid = endpoint.paid;
|
|
1305
|
+
let suffix = "";
|
|
1306
|
+
if (paid && typeof paid === "object") {
|
|
1307
|
+
suffix = ` ${paid.network ?? ""} ${paid.currency ?? ""} ${paid.amount_raw ?? ""}`.trimEnd();
|
|
1308
|
+
}
|
|
1309
|
+
process.stdout.write(` ${method.padEnd(6)} ${pathText}${suffix ? ` ${suffix}` : ""}\n`);
|
|
1310
|
+
}
|
|
1311
|
+
process.stdout.write("\n");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async function catalogShow(source, name, options) {
|
|
1315
|
+
const provider = await readCatalogProvider(source, name);
|
|
1316
|
+
if (outputMode(options) === "json") {
|
|
1317
|
+
printJson({ ok: true, command: "catalog show", result: provider });
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
process.stdout.write(`${provider.fqn ?? provider.name} - ${provider.title ?? provider.main_title ?? provider.name}\n`);
|
|
1321
|
+
if (provider.description)
|
|
1322
|
+
process.stdout.write(`${String(provider.description).split("\n")[0]}\n`);
|
|
1323
|
+
if (provider.category)
|
|
1324
|
+
process.stdout.write(`category: ${provider.category}\n`);
|
|
1325
|
+
if (provider.chains)
|
|
1326
|
+
process.stdout.write(`chains: ${provider.chains.join(", ")}\n`);
|
|
542
1327
|
}
|
|
543
|
-
async function catalogEndpoints(source, name) {
|
|
1328
|
+
async function catalogEndpoints(source, name, options) {
|
|
544
1329
|
const provider = await readCatalogProvider(source, name);
|
|
545
|
-
|
|
1330
|
+
const endpoints = provider.endpoints ?? [];
|
|
1331
|
+
if (outputMode(options) === "json") {
|
|
1332
|
+
printJson({ ok: true, command: "catalog endpoints", provider: provider.fqn ?? provider.name, endpoints });
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
for (const endpoint of endpoints) {
|
|
1336
|
+
process.stdout.write(`${String(endpoint.method ?? "").padEnd(6)} ${endpoint.path ?? endpoint.url ?? ""}\n`);
|
|
1337
|
+
if (endpoint.description)
|
|
1338
|
+
process.stdout.write(` ${String(endpoint.description).split("\n")[0]}\n`);
|
|
1339
|
+
}
|
|
546
1340
|
}
|
|
547
1341
|
async function catalogPayJson(source, name) {
|
|
548
1342
|
const provider = await readCatalogPayProvider(source, name);
|
|
@@ -560,16 +1354,22 @@ async function catalogPayJson(source, name) {
|
|
|
560
1354
|
}
|
|
561
1355
|
async function handleGateway(args) {
|
|
562
1356
|
const { command, positional, options } = parseArgs(args);
|
|
563
|
-
if (command === "
|
|
564
|
-
|
|
1357
|
+
if (hasFlag(options, "help") || command === "help") {
|
|
1358
|
+
process.stdout.write(helpText("gateway"));
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (command === "search")
|
|
1362
|
+
await catalogSearch(opt(options, "catalog", defaultCatalogSource()), positional.join(" "), options);
|
|
1363
|
+
else if (command === "start")
|
|
1364
|
+
await gatewayStart(["--providers", opt(options, "providers", opt(options, "providers-dir", positional[0] ?? "providers")), "--host", opt(options, "host", "127.0.0.1"), "--port", opt(options, "port", "4020")], options);
|
|
565
1365
|
else if (command === "check")
|
|
566
|
-
gatewayCheck(positional[0] ?? opt(options, "providers", "providers"));
|
|
1366
|
+
gatewayCheck(positional[0] ?? opt(options, "providers", "providers"), options);
|
|
567
1367
|
else if (command === "scaffold")
|
|
568
1368
|
gatewayScaffold(positional[0] ?? "example-provider", options);
|
|
569
1369
|
else if (command === "catalog")
|
|
570
1370
|
await handleGatewayCatalog(positional, options);
|
|
571
1371
|
else
|
|
572
|
-
throw new Error("Usage: x402-cli gateway <start|check|scaffold|catalog>");
|
|
1372
|
+
throw new Error("Usage: x402-cli gateway <search|start|check|scaffold|catalog>");
|
|
573
1373
|
}
|
|
574
1374
|
async function handleGatewayCatalog(positional, options) {
|
|
575
1375
|
const sub = positional[0] ?? "build";
|
|
@@ -577,35 +1377,56 @@ async function handleGatewayCatalog(positional, options) {
|
|
|
577
1377
|
if (sub === "build")
|
|
578
1378
|
catalogBuild(target, options);
|
|
579
1379
|
else if (sub === "check")
|
|
580
|
-
gatewayCheck(target);
|
|
1380
|
+
gatewayCheck(target, options);
|
|
581
1381
|
else if (sub === "pay-assets")
|
|
582
|
-
catalogPayAssets(target);
|
|
1382
|
+
catalogPayAssets(target, options);
|
|
583
1383
|
else if (sub === "search")
|
|
584
|
-
await catalogSearch(opt(options, "catalog",
|
|
1384
|
+
await catalogSearch(opt(options, "catalog", defaultCatalogSource()), positional.slice(2).join(" ") || opt(options, "query", ""), options);
|
|
585
1385
|
else
|
|
586
1386
|
throw new Error("Usage: x402-cli gateway catalog <build|check|pay-assets|search>");
|
|
587
1387
|
}
|
|
588
1388
|
async function handleCatalog(args) {
|
|
589
1389
|
const { command, positional, options } = parseArgs(args);
|
|
590
|
-
|
|
591
|
-
|
|
1390
|
+
if (hasFlag(options, "help") || command === "help") {
|
|
1391
|
+
process.stdout.write(helpText("catalog"));
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const source = opt(options, "catalog", defaultCatalogSource());
|
|
1395
|
+
if (command === "update")
|
|
1396
|
+
await catalogUpdate(source, options);
|
|
1397
|
+
else if (command === "search")
|
|
592
1398
|
await catalogSearch(source, positional.join(" "), options);
|
|
593
1399
|
else if (command === "show")
|
|
594
|
-
await catalogShow(source, positional[0]);
|
|
1400
|
+
await catalogShow(source, positional[0], options);
|
|
595
1401
|
else if (command === "endpoints")
|
|
596
|
-
await catalogEndpoints(source, positional[0]);
|
|
1402
|
+
await catalogEndpoints(source, positional[0], options);
|
|
597
1403
|
else if (command === "pay-json")
|
|
598
1404
|
await catalogPayJson(source, positional[0]);
|
|
1405
|
+
else if (command === "export-gateway")
|
|
1406
|
+
await catalogExportGateway(positional[0], options);
|
|
599
1407
|
else if (command === "build")
|
|
600
1408
|
catalogBuild(positional[0] ?? "providers", options);
|
|
601
1409
|
else
|
|
602
|
-
throw new Error("Usage: x402-cli catalog <search|show|endpoints|pay-json|build>");
|
|
1410
|
+
throw new Error("Usage: x402-cli catalog <update|search|show|endpoints|pay-json|export-gateway|build>");
|
|
603
1411
|
}
|
|
604
1412
|
async function main() {
|
|
605
1413
|
const argv = process.argv.slice(2);
|
|
606
1414
|
const { command, positional, options } = parseArgs(argv);
|
|
607
|
-
if (command === "
|
|
608
|
-
|
|
1415
|
+
if (command === "--help" || command === "-h" || command === "help" || hasFlag(options, "help")) {
|
|
1416
|
+
const topic = command === "help" ? positional[0] : command.startsWith("-") ? undefined : command;
|
|
1417
|
+
process.stdout.write(helpText(topic));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (command === "--version" || command === "-V" || hasFlag(options, "version")) {
|
|
1421
|
+
process.stdout.write(`${getVersion()}\n`);
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (command === "serve") {
|
|
1425
|
+
if (hasFlag(options, "daemon"))
|
|
1426
|
+
serveDaemon(argv, options);
|
|
1427
|
+
else
|
|
1428
|
+
await serve(options);
|
|
1429
|
+
}
|
|
609
1430
|
else if (command === "pay")
|
|
610
1431
|
await pay(positional[0], options);
|
|
611
1432
|
else if (command === "roundtrip")
|
|
@@ -615,10 +1436,14 @@ async function main() {
|
|
|
615
1436
|
else if (command === "catalog")
|
|
616
1437
|
await handleCatalog(argv.slice(1));
|
|
617
1438
|
else {
|
|
618
|
-
process.stdout.write(
|
|
1439
|
+
process.stdout.write(helpText());
|
|
619
1440
|
}
|
|
620
1441
|
}
|
|
621
1442
|
main().catch(error => {
|
|
622
|
-
|
|
1443
|
+
emit({
|
|
1444
|
+
command: process.argv[2] ?? "x402-cli",
|
|
1445
|
+
mode: process.argv.includes("--json") ? "json" : "human",
|
|
1446
|
+
error: classify(error),
|
|
1447
|
+
});
|
|
623
1448
|
process.exit(1);
|
|
624
1449
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bankofai/x402-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
13
14
|
"start": "node dist/cli.js",
|
|
14
15
|
"dev": "tsx src/cli.ts"
|
|
15
16
|
},
|