@bankofai/x402-gateway 1.0.0-beta.1 → 1.0.0-beta.2

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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/dist/cli.js +71 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,6 +45,11 @@ x402-gateway check --providers providers
45
45
  x402-gateway check --provider examples/provider.yml --json
46
46
  ```
47
47
 
48
+ `check` validates provider YAML syntax, required fields, environment expansion,
49
+ network aliases, endpoint shape, and duplicate provider names. It does not call
50
+ upstream APIs, facilitator services, RPC endpoints, recipient addresses, or the
51
+ full payment path.
52
+
48
53
  Run from source during development:
49
54
 
50
55
  ```bash
@@ -81,10 +86,11 @@ Options:
81
86
 
82
87
  - `--provider <file>`: load one provider YAML file.
83
88
  - `--providers <dir>`: recursively load `provider.yml` / `provider.yaml`.
89
+ `--provider` and `--providers` are mutually exclusive.
84
90
  - `--host <host>`: bind host. The CLI default is `127.0.0.1`; Docker passes `0.0.0.0`.
85
91
  - `--port <port>`: bind port, default `8080`.
86
92
  - `--json`: machine-readable startup/check/error output.
87
- - `--quiet`: suppress startup and shutdown messages.
93
+ - `--quiet`: suppress startup, shutdown, and successful `check` output.
88
94
  - `--debug`: include stack traces for startup errors.
89
95
  - `--help`, `--version`: inspect usage and installed version.
90
96
 
@@ -97,6 +103,10 @@ health: http://127.0.0.1:4020/__402/health
97
103
  ready: http://127.0.0.1:4020/__402/ready
98
104
  ```
99
105
 
106
+ With `--json`, startup output includes `source`, `host`, `port`, `count`,
107
+ `providers`, `health`, and `ready` for deployment scripts. CLI errors also
108
+ honor `--json`, including argument parsing errors such as unknown options.
109
+
100
110
  Admin endpoints such as `/__402/providers`, `/__402/endpoints`, and `/metrics`
101
111
  are protected by default. Set `X402_GATEWAY_ADMIN_TOKEN` in deployed
102
112
  environments and call them with `Authorization: Bearer <token>`. For a
package/dist/cli.js CHANGED
@@ -65,17 +65,21 @@ function opt(options, key, fallback) {
65
65
  function flag(options, key) {
66
66
  return options[key] === true;
67
67
  }
68
- function version() {
68
+ function rawHasFlag(argv, name, short) {
69
+ return argv.some(item => item === `--${name}` || item.startsWith(`--${name}=`) || (short ? item === short : false));
70
+ }
71
+ function readVersion() {
69
72
  try {
70
73
  const url = new URL("../package.json", import.meta.url);
71
- return String(JSON.parse(fs.readFileSync(url, "utf8")).version ?? "0.0.0");
74
+ const version = JSON.parse(fs.readFileSync(url, "utf8")).version;
75
+ return typeof version === "string" && version ? version : undefined;
72
76
  }
73
77
  catch {
74
- return "0.0.0";
78
+ return undefined;
75
79
  }
76
80
  }
77
81
  function help() {
78
- return `x402-gateway ${version()}
82
+ return `x402-gateway ${readVersion() ?? "unknown"}
79
83
 
80
84
  Usage:
81
85
  x402-gateway start --providers <dir> [options]
@@ -105,6 +109,9 @@ Examples:
105
109
  `;
106
110
  }
107
111
  function providerPath(options) {
112
+ if (opt(options, "provider") && opt(options, "providers")) {
113
+ throw new CliError("Options --provider and --providers are mutually exclusive.");
114
+ }
108
115
  const source = opt(options, "providers", opt(options, "provider", process.env.X402_GATEWAY_PROVIDERS_DIR));
109
116
  if (!source)
110
117
  throw new CliError("No provider source configured. Use --provider <file> or --providers <dir>.");
@@ -120,25 +127,56 @@ function parsePort(value) {
120
127
  }
121
128
  return port;
122
129
  }
130
+ function parseHost(value) {
131
+ const host = value ?? process.env.X402_GATEWAY_HOST ?? "127.0.0.1";
132
+ if (!host.trim())
133
+ throw new CliError("Invalid --host; expected a non-empty host.");
134
+ return host;
135
+ }
136
+ function listenErrorMessage(error, host, port) {
137
+ const err = error;
138
+ if (err?.code === "EADDRINUSE")
139
+ return `Port ${port} is already in use on ${host}. Choose another --port or stop the existing process.`;
140
+ if (err?.code === "EADDRNOTAVAIL")
141
+ return `Host ${host} is not available on this machine. Use --host 127.0.0.1 for local runs or --host 0.0.0.0 in containers.`;
142
+ return error instanceof Error ? error.message : String(error);
143
+ }
123
144
  function publicHost(host) {
124
145
  return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
125
146
  }
126
- function printStartup(options, host, port, providers) {
147
+ function localUrls(host, port) {
148
+ const base = `http://${publicHost(host)}:${port}`;
149
+ return {
150
+ base,
151
+ health: `${base}/__402/health`,
152
+ ready: `${base}/__402/ready`,
153
+ };
154
+ }
155
+ function printStartup(options, source, host, port, providers) {
127
156
  if (flag(options, "quiet"))
128
157
  return;
158
+ const urls = localUrls(host, port);
129
159
  if (flag(options, "json")) {
130
- process.stdout.write(JSON.stringify({ ok: true, host, port, providers }, null, 2) + "\n");
160
+ process.stdout.write(JSON.stringify({ ok: true, source, host, port, count: providers.length, providers, health: urls.health, ready: urls.ready }, null, 2) + "\n");
131
161
  return;
132
162
  }
133
- const base = `http://${publicHost(host)}:${port}`;
134
- process.stdout.write(`x402-gateway listening on ${base}\n`);
163
+ process.stdout.write(`x402-gateway listening on ${urls.base}\n`);
135
164
  process.stdout.write(`providers: ${providers.length} loaded\n`);
136
- process.stdout.write(`health: ${base}/__402/health\n`);
137
- process.stdout.write(`ready: ${base}/__402/ready\n`);
165
+ process.stdout.write(`health: ${urls.health}\n`);
166
+ process.stdout.write(`ready: ${urls.ready}\n`);
167
+ }
168
+ function loadProvidersForCli(action, source) {
169
+ try {
170
+ return loadProviders(source);
171
+ }
172
+ catch (error) {
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ throw new Error(`failed to load providers from ${source} for ${action}: ${message}`);
175
+ }
138
176
  }
139
177
  function check(options) {
140
178
  const source = providerPath(options);
141
- const providers = loadProviders(source);
179
+ const providers = loadProvidersForCli("check", source);
142
180
  const summary = [...providers.values()].map(entry => ({
143
181
  name: entry.config.name,
144
182
  network: entry.config.operator.network,
@@ -148,6 +186,8 @@ function check(options) {
148
186
  process.stdout.write(JSON.stringify({ ok: true, source, count: summary.length, providers: summary }, null, 2) + "\n");
149
187
  return;
150
188
  }
189
+ if (flag(options, "quiet"))
190
+ return;
151
191
  process.stdout.write(`ok: ${summary.length} provider${summary.length === 1 ? "" : "s"} loaded from ${source}\n`);
152
192
  for (const item of summary) {
153
193
  process.stdout.write(` ${item.name} (${item.network}) endpoints=${item.endpoints}\n`);
@@ -155,22 +195,29 @@ function check(options) {
155
195
  }
156
196
  async function start(options) {
157
197
  const source = providerPath(options);
158
- const host = opt(options, "host", process.env.X402_GATEWAY_HOST || "127.0.0.1");
198
+ const host = parseHost(opt(options, "host"));
159
199
  const port = parsePort(opt(options, "port"));
160
- const providers = loadProviders(source);
200
+ const providers = loadProvidersForCli("start", source);
161
201
  const server = createGatewayServer(providers);
162
202
  await new Promise((resolve, reject) => {
163
- server.once("error", reject);
203
+ server.once("error", error => reject(new Error(listenErrorMessage(error, host, port))));
164
204
  server.listen(port, host, () => {
165
205
  server.off("error", reject);
166
- printStartup(options, host, port, [...providers.keys()]);
206
+ printStartup(options, source, host, port, [...providers.keys()]);
167
207
  resolve();
168
208
  });
169
209
  });
170
210
  const shutdown = (signal) => {
171
211
  if (!flag(options, "quiet") && !flag(options, "json"))
172
212
  process.stderr.write(`received ${signal}, shutting down...\n`);
213
+ const timeout = setTimeout(() => {
214
+ if (!flag(options, "quiet") && !flag(options, "json"))
215
+ process.stderr.write("server close timed out; exiting\n");
216
+ process.exit(1);
217
+ }, 10_000);
218
+ timeout.unref();
173
219
  server.close(() => {
220
+ clearTimeout(timeout);
174
221
  if (!flag(options, "quiet") && !flag(options, "json"))
175
222
  process.stderr.write("server closed\n");
176
223
  process.exit(0);
@@ -186,7 +233,10 @@ async function main() {
186
233
  return;
187
234
  }
188
235
  if (flag(parsed.options, "version")) {
189
- process.stdout.write(`${version()}\n`);
236
+ const version = readVersion();
237
+ if (!version)
238
+ throw new Error("Unable to read package version.");
239
+ process.stdout.write(`${version}\n`);
190
240
  return;
191
241
  }
192
242
  if (parsed.command === "check")
@@ -195,22 +245,17 @@ async function main() {
195
245
  await start(parsed.options);
196
246
  }
197
247
  main().catch(error => {
198
- const parsed = (() => {
199
- try {
200
- return parseArgs(process.argv.slice(2));
201
- }
202
- catch {
203
- return { options: {} };
204
- }
205
- })();
248
+ const argv = process.argv.slice(2);
249
+ const json = rawHasFlag(argv, "json");
250
+ const debug = rawHasFlag(argv, "debug");
206
251
  const message = error instanceof Error ? error.message : String(error);
207
- if (flag(parsed.options, "json")) {
252
+ if (json) {
208
253
  process.stdout.write(JSON.stringify({ ok: false, error: { message } }, null, 2) + "\n");
209
254
  }
210
255
  else {
211
256
  process.stderr.write(`x402-gateway: ${message}\n`);
212
257
  process.stderr.write("Run `x402-gateway --help` for usage.\n");
213
- if (flag(parsed.options, "debug") && error instanceof Error && error.stack) {
258
+ if (debug && error instanceof Error && error.stack) {
214
259
  process.stderr.write(`${error.stack}\n`);
215
260
  }
216
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-gateway",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [