@rozoai/checkout 0.1.18 → 0.1.20

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/llms.txt CHANGED
@@ -148,6 +148,137 @@ private key (EVM and Solana only); every other path is keyless.
148
148
  only when the router reports paid / coinbase.settled — an intents status of
149
149
  payment_completed alone is the bridge lifecycle, not final settlement.
150
150
 
151
+ ## x402: pay any x402 API from a prepaid balance
152
+
153
+ For endpoints that answer HTTP 402 with x402 payment requirements. Fund a Rozo
154
+ balance once with the coin you hold, then pay many small x402 calls from it.
155
+
156
+ npx @rozoai/checkout x402 topup 20 --with usdt-solana # one-time deposit address
157
+ npx @rozoai/checkout x402 pay <url> [--method POST] [--body '...'] [--header 'K: V'] [--max-usd 1.00]
158
+ npx @rozoai/checkout x402 balance
159
+
160
+ Payment leg (what the seller receives): USDC on Base (eip155:8453) only,
161
+ scheme "exact". Solana payment leg is coming later. Topup leg: USDT on Solana,
162
+ BNB Chain, Ethereum, Polygon, Arbitrum; USDC on Solana, BNB Chain, Ethereum,
163
+ Polygon, Arbitrum, Base, Stellar. x402 balance top ups accept USDC and USDT only; any other coin
164
+ is answered with 400 X402_TOPUP_SOURCE_UNSUPPORTED. USDT only funds the balance;
165
+ sellers are paid in USDC. Holding a native coin or sats? Use them to top up
166
+ OpenRouter with ROZO Checkout (https://checkout.rozo.ai).
167
+
168
+ Raw HTTP (Authorization: Bearer <agent key ak_...>):
169
+ POST https://apiserver.mpprouter.dev/v1/x402/topup {"amount":"20","token":"USDT","chain":"900"}
170
+ -> one-time deposit address/order; balance is credited after it confirms
171
+ POST https://apiserver.mpprouter.dev/v1/x402/sign {"accepts":[<one entry from the 402>],"budget":"1.00","idempotencyKey":"<uuid>"}
172
+ -> the value for the PAYMENT-SIGNATURE header; replay your request with it
173
+ POST /v1/x402/keys creates the agent key (returned once). GET /v1/x402/balance.
174
+
175
+ x402 safety rules:
176
+ - Send the paid request yourself. Never send Rozo your request body, headers or
177
+ the API keys of the service you are paying; /sign needs only the accepts entry.
178
+ - Generate one idempotencyKey per payment and reuse it on every retry of /sign.
179
+ A new key on retry can charge twice.
180
+ - Refuse a payment above what the user approved; refuse a payTo on a
181
+ compromised-wallet list.
182
+ - 503 X402_RETRY, X402_PAYER_MODE_CHANGED or X402_LEDGER_UNAVAILABLE: retry
183
+ with the same idempotencyKey. Any other 503 from /v1/x402 means x402 is not
184
+ available for this key right now. Nothing new was charged; stop. Every server
185
+ code is listed below.
186
+ - The agent key owns the balance. Store it 0600, never print or log it.
187
+
188
+ x402 server error codes (returned by https://apiserver.mpprouter.dev/v1/x402/*):
189
+ Every error body looks like
190
+ {"ok":false,"code":"X402_...","error":{"code":"X402_...","message":"..."}},
191
+ sometimes with extra top level fields (limitUsd, balanceUsd, ...).
192
+ These are separate from the codes the CLI raises on its own (X402_UNSUPPORTED,
193
+ X402_OVER_BUDGET, X402_BAD_CHALLENGE, X402_PAYMENT_REJECTED and others). The balance is
194
+ debited only by the ledger commit inside /sign, after signing; every error
195
+ raised before that commit charges nothing. If /sign answers
196
+ X402_LEDGER_UNAVAILABLE or X402_INTERNAL the commit may have landed: retry
197
+ with the same idempotencyKey, and a payment that did land comes back as the
198
+ stored signature (replay:true), never as a second charge.
199
+ The CLI reports every 503 except X402_RETRY, X402_PAYER_MODE_CHANGED and
200
+ X402_LEDGER_UNAVAILABLE as X402_PAYER_DISABLED, with the server code in
201
+ error.details.serverCode. On /sign it retries 429, 500, 502, 504 and those
202
+ three 503 codes itself, with the same idempotencyKey.
203
+
204
+ balance, topup and sign (keys does not read a key):
205
+ - 401 X402_KEY_INVALID: agent key missing, malformed or unknown. Send
206
+ "Authorization: Bearer ak_...". Do not mint a new key to get around it: the
207
+ balance belongs to the old key.
208
+ - 403 X402_KEY_SUSPENDED: the key is not active. Stop; email hi@rozo.ai.
209
+
210
+ Any route:
211
+ - 503 X402_PAYER_DISABLED: the payer is switched off. Stop, tell the user.
212
+ - 503 X402_PAYER_NOT_CONFIGURED: payer not set up on this deployment. Stop.
213
+ - 503 X402_LEDGER_UNAVAILABLE: ledger unreachable. Retry shortly with the same
214
+ idempotencyKey.
215
+ - 500 X402_INTERNAL: unexpected error. Retry with the same idempotencyKey a few
216
+ times, then stop and report it.
217
+ - 405 METHOD_NOT_ALLOWED: wrong HTTP method (keys/topup/sign are POST, balance is GET).
218
+
219
+ POST /v1/x402/keys:
220
+ - 429 X402_KEY_RATE_LIMITED: too many keys created from this IP this hour.
221
+ Reuse the key you have, or retry later.
222
+ - 503 X402_RETRY: key collision. Retry.
223
+
224
+ POST /v1/x402/topup (no deposit address is shown on any error, so nothing can
225
+ be sent to it; only pay an address from a 200 response):
226
+ - 400 X402_INVALID_REQUEST: body is not JSON, or amount is not a USD amount
227
+ with at most 2 decimals.
228
+ - 400 X402_TOPUP_AMOUNT_OUT_OF_RANGE: top up must be between $5 and $500.
229
+ - 400 X402_TOPUP_SOURCE_REQUIRED: say which coin you pay with: chain and
230
+ token. The response lists accepted chains.
231
+ - 400 X402_UNSUPPORTED_CHAIN: unknown chain. Use a CAIP-2 id or a name such as
232
+ solana, base, ethereum, bsc, polygon, arbitrum, stellar.
233
+ - 400 X402_TOPUP_SOURCE_UNSUPPORTED: that coin is not accepted for top ups.
234
+ Use USDC or USDT. May carry a "supported" list.
235
+ - 502 INTENTS_API_FAILED: the top up order could not be created. Retry later.
236
+ - 503 X402_TOPUP_MISCONFIGURED or X402_TOPUP_NOT_REGISTERED: the order was not
237
+ usable, the address was withheld. Retry later; email hi@rozo.ai if it repeats.
238
+ - 503 X402_PAYER_SHADOW, X402_TOPUP_NOT_CONFIGURED or X402_SIGNER_NOT_CONFIGURED:
239
+ top ups are closed right now. Stop.
240
+
241
+ POST /v1/x402/sign:
242
+ - 400 X402_INVALID_REQUEST: body is not JSON, or budget is not a positive USD amount.
243
+ - 400 X402_IDEMPOTENCY_KEY_REQUIRED: send idempotencyKey, 8 to 128 chars of
244
+ [A-Za-z0-9_.:-], one per 402 challenge.
245
+ - 400 X402_UNSUPPORTED_VERSION: only x402Version 2 is accepted.
246
+ - 400 X402_UNSUPPORTED_NETWORK, X402_UNSUPPORTED_SCHEME, X402_UNSUPPORTED_ASSET,
247
+ X402_INVALID_REQUIREMENT or X402_SIGN_UNSUPPORTED: the accepts entry is
248
+ malformed or asks for something not covered (only exact USDC is signed).
249
+ Do not retry the same entry; tell the user.
250
+ - 402 X402_BUDGET_EXCEEDED: the challenge asks more than your budget. Stop and
251
+ ask the user.
252
+ - 402 X402_PER_TX_LIMIT_EXCEEDED: above this key's single-payment limit
253
+ (limitUsd). Stop and ask the user.
254
+ - 402 X402_INSUFFICIENT_BALANCE: balance too low (balanceUsd). Top up, wait
255
+ for the credit, then pay again.
256
+ - 403 X402_PAYTO_NOT_ALLOWED: payTo is not on this key's allowlist. Stop.
257
+ - 403 X402_PAYTO_BLOCKED: this payTo is blocked. Stop, do not retry.
258
+ - 409 X402_IDEMPOTENCY_CONFLICT: this idempotencyKey was already used for a
259
+ different requirement. Nothing new was charged. Use a new key only for a new
260
+ 402 challenge.
261
+ - 410 X402_CREDENTIAL_REFUNDED: the stored signature expired unused and its
262
+ amount went back to the balance. Fetch a new 402 and sign it with a new key.
263
+ - 429 X402_DAILY_LIMIT_EXCEEDED: this key's daily limit would be exceeded
264
+ (limitUsd, spentTodayUsd). Wait for the daily
265
+ window to reset, or ask hi@rozo.ai for a higher limit.
266
+ - 429 X402_GLOBAL_DAILY_CAP_REACHED or X402_SIGNER_DAILY_CAP_REACHED: a
267
+ platform-wide daily cap was reached. Retry after 00:00 UTC.
268
+ - 429 X402_SIGNER_CAP_REACHED: the signing service refused this amount. Try a
269
+ smaller payment or later.
270
+ - 503 X402_RETRY or X402_PAYER_MODE_CHANGED: transient. Retry with the same
271
+ idempotencyKey.
272
+ - 503 X402_PAYER_SHADOW: the payer is not live for this key. A fresh request
273
+ was checked and recorded but not signed, and nothing was charged. With
274
+ "replay":true it refers to an earlier payment under this idempotencyKey,
275
+ which may have been charged: check /balance, never pay again with a new key.
276
+ Stop.
277
+ - 503 X402_SIGNER_NOT_CONFIGURED or X402_SIGNER_DISABLED: signing for this
278
+ network is off right now. Stop.
279
+ A 200 with "replay":true is the stored signature for an idempotencyKey that
280
+ was already paid; it is not a second charge.
281
+
151
282
  ## Safety rules (encode these, they prevent fund loss)
152
283
 
153
284
  - Before paying, require: status == "payment_unpaid" AND source.txHash == null AND
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozoai/checkout",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Pay OpenRouter, or any Coinbase invoice, with the crypto you already hold: USDT/USDC on Solana, BNB Chain, Ethereum or Polygon, USDC on Base or Stellar, or BTC over Lightning. No Coinbase account.",
6
6
  "license": "MIT",
@@ -20473,8 +20473,8 @@ var require_dist = __commonJS({
20473
20473
  constructor(failure, failures) {
20474
20474
  let cached2;
20475
20475
  const { message, explanation, ...rest } = failure;
20476
- const { path: path7 } = failure;
20477
- const msg = path7.length === 0 ? message : `At path: ${path7.join(".")} -- ${message}`;
20476
+ const { path: path8 } = failure;
20477
+ const msg = path8.length === 0 ? message : `At path: ${path8.join(".")} -- ${message}`;
20478
20478
  super(explanation ?? msg);
20479
20479
  if (explanation != null)
20480
20480
  this.cause = msg;
@@ -20519,15 +20519,15 @@ var require_dist = __commonJS({
20519
20519
  } else if (typeof result === "string") {
20520
20520
  result = { message: result };
20521
20521
  }
20522
- const { path: path7, branch } = context;
20522
+ const { path: path8, branch } = context;
20523
20523
  const { type: type2 } = struct2;
20524
20524
  const { refinement, message = `Expected a value of type \`${type2}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result;
20525
20525
  return {
20526
20526
  value,
20527
20527
  type: type2,
20528
20528
  refinement,
20529
- key: path7[path7.length - 1],
20530
- path: path7,
20529
+ key: path8[path8.length - 1],
20530
+ path: path8,
20531
20531
  branch,
20532
20532
  ...result,
20533
20533
  message
@@ -20545,8 +20545,8 @@ var require_dist = __commonJS({
20545
20545
  }
20546
20546
  }
20547
20547
  function* run9(value, struct2, options = {}) {
20548
- const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20549
- const ctx = { path: path7, branch, mask: mask2 };
20548
+ const { path: path8 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20549
+ const ctx = { path: path8, branch, mask: mask2 };
20550
20550
  if (coerce2) {
20551
20551
  value = struct2.coercer(value, ctx);
20552
20552
  }
@@ -20558,7 +20558,7 @@ var require_dist = __commonJS({
20558
20558
  }
20559
20559
  for (let [k, v, s] of struct2.entries(value, ctx)) {
20560
20560
  const ts = run9(v, s, {
20561
- path: k === void 0 ? path7 : [...path7, k],
20561
+ path: k === void 0 ? path8 : [...path8, k],
20562
20562
  branch: k === void 0 ? branch : [...branch, v],
20563
20563
  coerce: coerce2,
20564
20564
  mask: mask2,
@@ -20793,7 +20793,7 @@ var require_dist = __commonJS({
20793
20793
  }
20794
20794
  return object(schema);
20795
20795
  }
20796
- function pick(struct2, keys) {
20796
+ function pick2(struct2, keys) {
20797
20797
  const { schema } = struct2;
20798
20798
  const subschema = {};
20799
20799
  for (const key of keys) {
@@ -21254,7 +21254,7 @@ var require_dist = __commonJS({
21254
21254
  exports2.optional = optional;
21255
21255
  exports2.partial = partial;
21256
21256
  exports2.pattern = pattern;
21257
- exports2.pick = pick;
21257
+ exports2.pick = pick2;
21258
21258
  exports2.record = record;
21259
21259
  exports2.refine = refine;
21260
21260
  exports2.regexp = regexp;
@@ -22744,14 +22744,14 @@ var require_url_state_machine = __commonJS({
22744
22744
  return url.replace(/\u0009|\u000A|\u000D/g, "");
22745
22745
  }
22746
22746
  function shortenPath(url) {
22747
- const path7 = url.path;
22748
- if (path7.length === 0) {
22747
+ const path8 = url.path;
22748
+ if (path8.length === 0) {
22749
22749
  return;
22750
22750
  }
22751
- if (url.scheme === "file" && path7.length === 1 && isNormalizedWindowsDriveLetter(path7[0])) {
22751
+ if (url.scheme === "file" && path8.length === 1 && isNormalizedWindowsDriveLetter(path8[0])) {
22752
22752
  return;
22753
22753
  }
22754
- path7.pop();
22754
+ path8.pop();
22755
22755
  }
22756
22756
  function includesCredentials(url) {
22757
22757
  return url.username !== "" || url.password !== "";
@@ -25046,16 +25046,16 @@ var require_constants = __commonJS({
25046
25046
  // node_modules/node-gyp-build/node-gyp-build.js
25047
25047
  var require_node_gyp_build = __commonJS({
25048
25048
  "node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
25049
- var fs7 = __require("fs");
25050
- var path7 = __require("path");
25051
- var os5 = __require("os");
25049
+ var fs8 = __require("fs");
25050
+ var path8 = __require("path");
25051
+ var os6 = __require("os");
25052
25052
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
25053
25053
  var vars = process.config && process.config.variables || {};
25054
25054
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
25055
25055
  var abi2 = process.versions.modules;
25056
25056
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
25057
- var arch = process.env.npm_config_arch || os5.arch();
25058
- var platform = process.env.npm_config_platform || os5.platform();
25057
+ var arch = process.env.npm_config_arch || os6.arch();
25058
+ var platform = process.env.npm_config_platform || os6.platform();
25059
25059
  var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
25060
25060
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
25061
25061
  var uv = (process.versions.uv || "").split(".")[0];
@@ -25064,21 +25064,21 @@ var require_node_gyp_build = __commonJS({
25064
25064
  return runtimeRequire(load.resolve(dir));
25065
25065
  }
25066
25066
  load.resolve = load.path = function(dir) {
25067
- dir = path7.resolve(dir || ".");
25067
+ dir = path8.resolve(dir || ".");
25068
25068
  try {
25069
- var name = runtimeRequire(path7.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
25069
+ var name = runtimeRequire(path8.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
25070
25070
  if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
25071
25071
  } catch (err) {
25072
25072
  }
25073
25073
  if (!prebuildsOnly) {
25074
- var release = getFirst(path7.join(dir, "build/Release"), matchBuild);
25074
+ var release = getFirst(path8.join(dir, "build/Release"), matchBuild);
25075
25075
  if (release) return release;
25076
- var debug = getFirst(path7.join(dir, "build/Debug"), matchBuild);
25076
+ var debug = getFirst(path8.join(dir, "build/Debug"), matchBuild);
25077
25077
  if (debug) return debug;
25078
25078
  }
25079
25079
  var prebuild = resolve(dir);
25080
25080
  if (prebuild) return prebuild;
25081
- var nearby = resolve(path7.dirname(process.execPath));
25081
+ var nearby = resolve(path8.dirname(process.execPath));
25082
25082
  if (nearby) return nearby;
25083
25083
  var target = [
25084
25084
  "platform=" + platform,
@@ -25095,26 +25095,26 @@ var require_node_gyp_build = __commonJS({
25095
25095
  ].filter(Boolean).join(" ");
25096
25096
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
25097
25097
  function resolve(dir2) {
25098
- var tuples = readdirSync(path7.join(dir2, "prebuilds")).map(parseTuple);
25098
+ var tuples = readdirSync(path8.join(dir2, "prebuilds")).map(parseTuple);
25099
25099
  var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
25100
25100
  if (!tuple) return;
25101
- var prebuilds = path7.join(dir2, "prebuilds", tuple.name);
25101
+ var prebuilds = path8.join(dir2, "prebuilds", tuple.name);
25102
25102
  var parsed = readdirSync(prebuilds).map(parseTags);
25103
25103
  var candidates = parsed.filter(matchTags(runtime, abi2));
25104
25104
  var winner = candidates.sort(compareTags(runtime))[0];
25105
- if (winner) return path7.join(prebuilds, winner.file);
25105
+ if (winner) return path8.join(prebuilds, winner.file);
25106
25106
  }
25107
25107
  };
25108
25108
  function readdirSync(dir) {
25109
25109
  try {
25110
- return fs7.readdirSync(dir);
25110
+ return fs8.readdirSync(dir);
25111
25111
  } catch (err) {
25112
25112
  return [];
25113
25113
  }
25114
25114
  }
25115
25115
  function getFirst(dir, filter) {
25116
25116
  var files = readdirSync(dir).filter(filter);
25117
- return files[0] && path7.join(dir, files[0]);
25117
+ return files[0] && path8.join(dir, files[0]);
25118
25118
  }
25119
25119
  function matchBuild(name) {
25120
25120
  return /\.node$/.test(name);
@@ -25201,7 +25201,7 @@ var require_node_gyp_build = __commonJS({
25201
25201
  return typeof window !== "undefined" && window.process && window.process.type === "renderer";
25202
25202
  }
25203
25203
  function isAlpine(platform2) {
25204
- return platform2 === "linux" && fs7.existsSync("/etc/alpine-release");
25204
+ return platform2 === "linux" && fs8.existsSync("/etc/alpine-release");
25205
25205
  }
25206
25206
  load.parseTags = parseTags;
25207
25207
  load.matchTags = matchTags;
@@ -41510,6 +41510,7 @@ function redact(text) {
41510
41510
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
41511
41511
  });
41512
41512
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
41513
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
41513
41514
  s = s.replace(
41514
41515
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
41515
41516
  "$1$2<redacted>"
@@ -42203,6 +42204,7 @@ USAGE
42203
42204
  npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
42204
42205
  npx @rozoai/checkout receipt <rozoPaymentId | coinbase-link>
42205
42206
  npx @rozoai/checkout resume <rozoPaymentId>
42207
+ npx @rozoai/checkout x402 <topup|pay|balance> (pay any x402 API; see x402 --help)
42206
42208
 
42207
42209
  COINS (--with)
42208
42210
  usdt-solana usdc-solana usdt-bnb usdc-bnb
@@ -54677,12 +54679,12 @@ function bodyRetryAfterMs(json) {
54677
54679
  return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
54678
54680
  }
54679
54681
  function rateLimitInfo(res) {
54680
- const pick = (name) => res.headers?.get?.(name) ?? null;
54682
+ const pick2 = (name) => res.headers?.get?.(name) ?? null;
54681
54683
  const info = {
54682
- limit: pick("x-ratelimit-limit"),
54683
- remaining: pick("x-ratelimit-remaining"),
54684
- tier: pick("x-ratelimit-tier"),
54685
- scope: pick("x-ratelimit-scope")
54684
+ limit: pick2("x-ratelimit-limit"),
54685
+ remaining: pick2("x-ratelimit-remaining"),
54686
+ tier: pick2("x-ratelimit-tier"),
54687
+ scope: pick2("x-ratelimit-scope")
54686
54688
  };
54687
54689
  const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
54688
54690
  return Object.keys(present).length ? present : null;
@@ -58208,6 +58210,781 @@ async function run8(argv = process.argv.slice(2)) {
58208
58210
  return main8(argv);
58209
58211
  }
58210
58212
 
58213
+ // scripts/src/lib/x402-args.mjs
58214
+ var X402_SUBCOMMANDS = ["topup", "pay", "balance"];
58215
+ var BOOLEAN = /* @__PURE__ */ new Set(["json", "dry-run", "help"]);
58216
+ var VALUE = /* @__PURE__ */ new Set(["with", "method", "body", "header", "max-usd", "prefer", "idempotency-key"]);
58217
+ var REPEATABLE = /* @__PURE__ */ new Set(["header"]);
58218
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
58219
+ var X402_HELP = `rozo-checkout x402: pay any x402 API from a prepaid Rozo balance.
58220
+
58221
+ USAGE
58222
+ npx @rozoai/checkout x402 topup <usd> --with <coin>
58223
+ npx @rozoai/checkout x402 pay <url> [--method POST] [--body '{...}'] [--header 'Name: value']
58224
+ npx @rozoai/checkout x402 balance
58225
+
58226
+ TOPUP COINS (--with)
58227
+ usdt-solana usdc-solana usdt-bnb usdc-bnb usdt-ethereum usdc-ethereum
58228
+ usdt-polygon usdc-polygon usdt-arbitrum usdc-arbitrum usdc-base usdc-stellar
58229
+ x402 top ups accept USDC and USDT only. Holding a native coin or sats?
58230
+ Use them to top up OpenRouter with ROZO Checkout (rozo-checkout pay).
58231
+
58232
+ PAY OPTIONS
58233
+ --method <m> HTTP method (default GET)
58234
+ --body <s> request body, sent as is
58235
+ --header 'Name: value' extra request header; repeat for more
58236
+ --max-usd <n> most this one call may cost (default 1.00)
58237
+ --idempotency-key <id> reuse after an interrupted run so you are not charged twice
58238
+ --dry-run read the 402 and show what would be paid; sign nothing
58239
+
58240
+ Payment leg: USDC on Base only (eip155:8453, x402 scheme "exact").
58241
+ Solana payment leg is coming later.
58242
+ Your request goes straight from this machine to the endpoint; Rozo only sees
58243
+ the 402 payment requirements it is asked to sign.`;
58244
+ function parseX402Args(argv) {
58245
+ const positional = [];
58246
+ const flags = { header: [] };
58247
+ for (let i = 0; i < argv.length; i++) {
58248
+ const a = argv[i];
58249
+ if (a === "--") {
58250
+ positional.push(...argv.slice(i + 1));
58251
+ break;
58252
+ }
58253
+ if (a.startsWith("--")) {
58254
+ let key = a.slice(2);
58255
+ let value;
58256
+ const eq = key.indexOf("=");
58257
+ if (eq !== -1) {
58258
+ value = key.slice(eq + 1);
58259
+ key = key.slice(0, eq);
58260
+ }
58261
+ if (BOOLEAN.has(key)) {
58262
+ flags[key] = value === void 0 ? true : value !== "false";
58263
+ continue;
58264
+ }
58265
+ if (VALUE.has(key)) {
58266
+ if (value === void 0) {
58267
+ value = argv[i + 1];
58268
+ if (value === void 0 || value.startsWith("--") && key !== "body") {
58269
+ throw new CliError("MISSING_VALUE", `--${key} needs a value.`);
58270
+ }
58271
+ i++;
58272
+ }
58273
+ if (REPEATABLE.has(key)) flags[key].push(value);
58274
+ else flags[key] = value;
58275
+ continue;
58276
+ }
58277
+ throw new CliError("UNKNOWN_FLAG", `Unknown option --${key} for x402.`);
58278
+ }
58279
+ if (a === "-j") {
58280
+ flags.json = true;
58281
+ continue;
58282
+ }
58283
+ if (a === "-h") {
58284
+ flags.help = true;
58285
+ continue;
58286
+ }
58287
+ positional.push(a);
58288
+ }
58289
+ const sub = positional[1];
58290
+ const json = Boolean(flags.json);
58291
+ if (flags.help || !sub || sub === "help") return { command: "x402", sub: "help", json };
58292
+ if (!X402_SUBCOMMANDS.includes(sub)) {
58293
+ throw new CliError("UNKNOWN_COMMAND", `Unknown x402 command "${sub}". Expected one of: ${X402_SUBCOMMANDS.join(", ")}.`);
58294
+ }
58295
+ if (sub === "balance") return { command: "x402", sub, json };
58296
+ if (sub === "topup") {
58297
+ const amount = positional[2];
58298
+ if (!amount) throw new CliError("MISSING_TARGET", "Usage: rozo-checkout x402 topup <usd> --with <coin>");
58299
+ if (!flags.with) throw new CliError("MISSING_PRESET", "A coin is required, e.g. --with usdt-solana");
58300
+ return { command: "x402", sub, json, amount, coin: flags.with };
58301
+ }
58302
+ const url = positional[2];
58303
+ if (!url) throw new CliError("MISSING_TARGET", "Usage: rozo-checkout x402 pay <url>");
58304
+ if (flags.prefer !== void 0 && !["base", "solana"].includes(String(flags.prefer).toLowerCase())) {
58305
+ throw new CliError("BAD_VALUE", "--prefer must be base or solana.");
58306
+ }
58307
+ if (flags["idempotency-key"] !== void 0 && !UUID_RE2.test(flags["idempotency-key"])) {
58308
+ throw new CliError("BAD_VALUE", "--idempotency-key must be a UUID (the one a previous run printed).");
58309
+ }
58310
+ if (flags.method !== void 0 && !/^[A-Za-z]+$/.test(flags.method)) {
58311
+ throw new CliError("BAD_VALUE", "--method must be an HTTP method like GET or POST.");
58312
+ }
58313
+ return {
58314
+ command: "x402",
58315
+ sub,
58316
+ json,
58317
+ url,
58318
+ method: (flags.method ?? "GET").toUpperCase(),
58319
+ body: flags.body,
58320
+ headers: flags.header,
58321
+ maxUsd: flags["max-usd"],
58322
+ prefer: flags.prefer ? String(flags.prefer).toLowerCase() : void 0,
58323
+ idempotencyKey: flags["idempotency-key"],
58324
+ dryRun: flags["dry-run"] === true
58325
+ };
58326
+ }
58327
+ function isX402Argv(argv) {
58328
+ const first = argv.find((a) => !a.startsWith("-"));
58329
+ return first === "x402";
58330
+ }
58331
+
58332
+ // scripts/src/x402.mjs
58333
+ import crypto8 from "node:crypto";
58334
+
58335
+ // scripts/src/lib/x402-key.mjs
58336
+ import fs7 from "node:fs";
58337
+ import path7 from "node:path";
58338
+ import os5 from "node:os";
58339
+ var KEY_ENV = "ROZO_CHECKOUT_X402_KEY";
58340
+ function keyPath() {
58341
+ const root = process.env.ROZO_CHECKOUT_STATE_DIR ? path7.dirname(stateRoot()) : path7.join(os5.homedir(), ".rozo-checkout");
58342
+ return path7.join(root, "x402-key");
58343
+ }
58344
+ function readAgentKey() {
58345
+ const fromEnv = process.env[KEY_ENV];
58346
+ if (fromEnv && fromEnv.trim()) return { key: fromEnv.trim(), source: "env" };
58347
+ try {
58348
+ const text = fs7.readFileSync(keyPath(), "utf8").trim();
58349
+ if (text) return { key: text, source: "file" };
58350
+ } catch {
58351
+ }
58352
+ return null;
58353
+ }
58354
+ function saveAgentKey(key) {
58355
+ const file = keyPath();
58356
+ fs7.mkdirSync(path7.dirname(file), { recursive: true, mode: 448 });
58357
+ const fd = fs7.openSync(file, "wx", 384);
58358
+ try {
58359
+ fs7.writeSync(fd, `${key}
58360
+ `);
58361
+ fs7.fsyncSync(fd);
58362
+ } finally {
58363
+ fs7.closeSync(fd);
58364
+ }
58365
+ return file;
58366
+ }
58367
+
58368
+ // scripts/src/lib/x402.mjs
58369
+ var NETWORK_BASE = "eip155:8453";
58370
+ var NETWORK_SOLANA = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
58371
+ var USDC_ASSET = {
58372
+ [NETWORK_BASE]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
58373
+ [NETWORK_SOLANA]: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
58374
+ };
58375
+ var USDC_DECIMALS = 6;
58376
+ var PAYABLE_NETWORKS = /* @__PURE__ */ new Set([NETWORK_BASE]);
58377
+ var SOLANA_COMING_LATER = "Solana payment leg is coming later";
58378
+ var V1_NETWORK_ALIASES = {
58379
+ base: NETWORK_BASE,
58380
+ solana: NETWORK_SOLANA
58381
+ };
58382
+ function networkFamily(network) {
58383
+ if (network === NETWORK_BASE) return "evm";
58384
+ if (network === NETWORK_SOLANA) return "solana";
58385
+ return null;
58386
+ }
58387
+ function networkLabel(network) {
58388
+ if (network === NETWORK_BASE) return "Base";
58389
+ if (network === NETWORK_SOLANA) return "Solana";
58390
+ return String(network);
58391
+ }
58392
+ function decodeBase64Json(value) {
58393
+ if (typeof value !== "string" || !value.trim()) return null;
58394
+ try {
58395
+ const normalized = value.trim().replace(/-/g, "+").replace(/_/g, "/");
58396
+ const text = Buffer.from(normalized, "base64").toString("utf8");
58397
+ const parsed = JSON.parse(text);
58398
+ return parsed && typeof parsed === "object" ? parsed : null;
58399
+ } catch {
58400
+ return null;
58401
+ }
58402
+ }
58403
+ function encodeBase64Json(value) {
58404
+ return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
58405
+ }
58406
+ function parseJsonText(text) {
58407
+ if (typeof text !== "string" || !text.trim()) return null;
58408
+ try {
58409
+ const parsed = JSON.parse(text);
58410
+ return parsed && typeof parsed === "object" ? parsed : null;
58411
+ } catch {
58412
+ return null;
58413
+ }
58414
+ }
58415
+ function parseChallenge({ status, getHeader, bodyText }) {
58416
+ if (status !== 402) {
58417
+ throw new SkillError("X402_NOT_402", `Expected HTTP 402, got ${status}.`);
58418
+ }
58419
+ const headerValue = getHeader("payment-required");
58420
+ let doc = headerValue ? decodeBase64Json(headerValue) : null;
58421
+ let source = doc ? "header" : null;
58422
+ if (!doc) {
58423
+ doc = parseJsonText(bodyText);
58424
+ source = doc ? "body" : null;
58425
+ }
58426
+ if (!doc || !Array.isArray(doc.accepts) || doc.accepts.length === 0) {
58427
+ throw new SkillError(
58428
+ "X402_BAD_CHALLENGE",
58429
+ "The endpoint returned 402 but no x402 payment requirements (no PAYMENT-REQUIRED header and no accepts list in the body)."
58430
+ );
58431
+ }
58432
+ const x402Version = Number(doc.x402Version) || (source === "header" ? 2 : 1);
58433
+ return {
58434
+ x402Version,
58435
+ source,
58436
+ resource: doc.resource ?? null,
58437
+ error: typeof doc.error === "string" ? doc.error : null,
58438
+ extensions: doc.extensions ?? null,
58439
+ accepts: doc.accepts
58440
+ };
58441
+ }
58442
+ function atomicAmount(req) {
58443
+ const raw = req?.amount ?? req?.maxAmountRequired;
58444
+ if (raw === null || raw === void 0) return null;
58445
+ const s = String(raw).trim();
58446
+ if (!/^\d+$/.test(s)) return null;
58447
+ return BigInt(s);
58448
+ }
58449
+ function formatUsdc(atomic) {
58450
+ const a = BigInt(atomic);
58451
+ const whole = a / 1000000n;
58452
+ const frac = (a % 1000000n).toString().padStart(USDC_DECIMALS, "0").replace(/0+$/, "");
58453
+ const fracOut = frac.length < 2 ? frac.padEnd(2, "0") : frac;
58454
+ return `${whole}.${fracOut}`;
58455
+ }
58456
+ function usdToAtomic(value) {
58457
+ const s = String(value ?? "").trim();
58458
+ if (!/^\d+(\.\d{1,6})?$/.test(s)) {
58459
+ throw new SkillError("BAD_VALUE", `"${value}" is not a USD amount like 0.50.`);
58460
+ }
58461
+ const [w, f = ""] = s.split(".");
58462
+ return BigInt(w) * 1000000n + BigInt(f.padEnd(USDC_DECIMALS, "0"));
58463
+ }
58464
+ function classifyRequirement(req) {
58465
+ if (!req || typeof req !== "object") return { supported: false, reason: "not an object" };
58466
+ const network = V1_NETWORK_ALIASES[req.network] ?? req.network;
58467
+ if (req.scheme !== "exact") {
58468
+ return { supported: false, reason: `scheme ${req.scheme} (only exact)`, network };
58469
+ }
58470
+ const usdc = USDC_ASSET[network];
58471
+ if (!usdc) {
58472
+ return { supported: false, reason: `network ${req.network} (only Base USDC)`, network };
58473
+ }
58474
+ if (!PAYABLE_NETWORKS.has(network)) {
58475
+ const reason = network === NETWORK_SOLANA ? `USDC on Solana (${SOLANA_COMING_LATER})` : `network ${req.network} (only Base USDC)`;
58476
+ return { supported: false, reason, network };
58477
+ }
58478
+ const assetOk = network === NETWORK_BASE ? String(req.asset ?? "").toLowerCase() === usdc.toLowerCase() : String(req.asset ?? "") === usdc;
58479
+ if (!assetOk) {
58480
+ return { supported: false, reason: `asset ${req.asset} on ${networkLabel(network)} is not USDC`, network };
58481
+ }
58482
+ const amount = atomicAmount(req);
58483
+ if (amount === null || amount <= 0n) {
58484
+ return { supported: false, reason: "missing or invalid amount", network };
58485
+ }
58486
+ if (typeof req.payTo !== "string" || !req.payTo.trim()) {
58487
+ return { supported: false, reason: "missing payTo", network };
58488
+ }
58489
+ return {
58490
+ supported: true,
58491
+ option: {
58492
+ network,
58493
+ asset: req.asset,
58494
+ payTo: req.payTo,
58495
+ amountAtomic: amount,
58496
+ amountUsd: formatUsdc(amount),
58497
+ requirement: req
58498
+ }
58499
+ };
58500
+ }
58501
+ function selectRequirement(challenge2, { budgetAtomic, prefer } = {}) {
58502
+ const classified = challenge2.accepts.map(classifyRequirement);
58503
+ const supported = classified.filter((c2) => c2.supported).map((c2) => c2.option);
58504
+ if (supported.length === 0) {
58505
+ const solanaOffered = classified.some((c2) => c2.network === NETWORK_SOLANA);
58506
+ throw new SkillError(
58507
+ "X402_UNSUPPORTED",
58508
+ "This endpoint does not accept a payment Rozo can sign. Rozo pays with USDC on Base (eip155:8453, scheme exact) only." + (solanaOffered ? ` It offers USDC on Solana; the ${SOLANA_COMING_LATER}.` : ""),
58509
+ { offered: classified.map((c2) => c2.reason ?? "supported") }
58510
+ );
58511
+ }
58512
+ const within = supported.filter((o) => budgetAtomic === void 0 || o.amountAtomic <= budgetAtomic);
58513
+ if (within.length === 0) {
58514
+ const cheapest = supported.reduce((a, b) => b.amountAtomic < a.amountAtomic ? b : a);
58515
+ throw new SkillError(
58516
+ "X402_OVER_BUDGET",
58517
+ `The endpoint asks for ${cheapest.amountUsd} USDC, above your budget of ${formatUsdc(budgetAtomic)} USD. Raise --max-usd if you mean to pay that much.`,
58518
+ { askedUsd: cheapest.amountUsd, budgetUsd: formatUsdc(budgetAtomic) }
58519
+ );
58520
+ }
58521
+ const preferNet = prefer === "base" ? NETWORK_BASE : prefer === "solana" ? NETWORK_SOLANA : null;
58522
+ within.sort((a, b) => {
58523
+ if (preferNet) {
58524
+ const pa = a.network === preferNet ? 0 : 1;
58525
+ const pb = b.network === preferNet ? 0 : 1;
58526
+ if (pa !== pb) return pa - pb;
58527
+ }
58528
+ return a.amountAtomic < b.amountAtomic ? -1 : a.amountAtomic > b.amountAtomic ? 1 : 0;
58529
+ });
58530
+ return within[0];
58531
+ }
58532
+ function paymentHeaderName(x402Version) {
58533
+ return Number(x402Version) >= 2 ? "PAYMENT-SIGNATURE" : "X-PAYMENT";
58534
+ }
58535
+ function paymentResponseHeaderNames() {
58536
+ return ["payment-response", "x-payment-response"];
58537
+ }
58538
+ function extractSignature(resp) {
58539
+ if (!resp || typeof resp !== "object") return null;
58540
+ const direct = resp.paymentSignature ?? resp.PAYMENT_SIGNATURE ?? resp["PAYMENT-SIGNATURE"] ?? resp.signature ?? resp.headerValue ?? resp.header?.value ?? null;
58541
+ if (typeof direct === "string" && direct.trim()) return direct.trim();
58542
+ if (resp.paymentPayload && typeof resp.paymentPayload === "object") {
58543
+ return encodeBase64Json(resp.paymentPayload);
58544
+ }
58545
+ return null;
58546
+ }
58547
+ function decodeSettlement(headerValue) {
58548
+ const doc = decodeBase64Json(headerValue);
58549
+ if (!doc) return null;
58550
+ return {
58551
+ success: doc.success ?? null,
58552
+ transaction: doc.transaction ?? doc.txHash ?? null,
58553
+ network: doc.network ?? null,
58554
+ payer: doc.payer ?? null,
58555
+ errorReason: doc.errorReason ?? null
58556
+ };
58557
+ }
58558
+ var TOPUP_PRESETS = {
58559
+ "usdt-solana": { chain: "900", token: "USDT" },
58560
+ "usdc-solana": { chain: "900", token: "USDC" },
58561
+ "usdt-bnb": { chain: "56", token: "USDT" },
58562
+ "usdc-bnb": { chain: "56", token: "USDC" },
58563
+ "usdt-ethereum": { chain: "1", token: "USDT" },
58564
+ "usdc-ethereum": { chain: "1", token: "USDC" },
58565
+ "usdt-polygon": { chain: "137", token: "USDT" },
58566
+ "usdc-polygon": { chain: "137", token: "USDC" },
58567
+ "usdt-arbitrum": { chain: "42161", token: "USDT" },
58568
+ "usdc-arbitrum": { chain: "42161", token: "USDC" },
58569
+ "usdc-base": { chain: "8453", token: "USDC" },
58570
+ "usdc-stellar": { chain: "1500", token: "USDC" }
58571
+ };
58572
+ var NON_TOPUP_PRESETS = /* @__PURE__ */ new Set([
58573
+ "btc-lightning",
58574
+ "eth-ethereum",
58575
+ "eth-base",
58576
+ "eth-arbitrum",
58577
+ "bnb-bnb",
58578
+ "sol-solana"
58579
+ ]);
58580
+ var TOPUP_STABLE_ONLY = "x402 top ups accept USDC and USDT only. Holding a native coin or sats? Use them to top up OpenRouter with: rozo-checkout pay <openrouter-payment-link> --with btc-lightning (sats), or https://checkout.rozo.ai (native coins).";
58581
+ var TOPUP_MIN_USD = 5;
58582
+ function resolveTopupPreset(value) {
58583
+ const key = String(value ?? "").trim().toLowerCase();
58584
+ if (NON_TOPUP_PRESETS.has(key) || /^(eth|bnb|sol|pol|matic|xlm|btc|sats)-/.test(key)) {
58585
+ throw new SkillError("X402_TOPUP_SOURCE_UNSUPPORTED", TOPUP_STABLE_ONLY, { preset: key });
58586
+ }
58587
+ const hit = TOPUP_PRESETS[key];
58588
+ if (!hit) {
58589
+ throw new SkillError(
58590
+ "BAD_PRESET",
58591
+ `"${value}" is not a topup coin. Known: ${Object.keys(TOPUP_PRESETS).join(", ")}`
58592
+ );
58593
+ }
58594
+ return { preset: key, ...hit };
58595
+ }
58596
+ function maskKey(key) {
58597
+ const s = String(key ?? "");
58598
+ if (s.length <= 10) return "<redacted>";
58599
+ return `${s.slice(0, 3)}\u2026${s.slice(-4)}`;
58600
+ }
58601
+
58602
+ // scripts/src/x402.mjs
58603
+ var X402_BASE = process.env.ROZO_CHECKOUT_X402_BASE || "https://apiserver.mpprouter.dev/v1/x402";
58604
+ var DISABLED_MESSAGE = "x402 payer not enabled for this key right now; nothing was charged. Try again later, or email hi@rozo.ai with the masked key.";
58605
+ var SIGN_RETRIES = 2;
58606
+ var SIGN_BACKOFF_MS = [1e3, 3e3];
58607
+ var API_TIMEOUT_MS = 2e4;
58608
+ var TARGET_TIMEOUT_MS = 6e4;
58609
+ var MAX_BODY_CHARS = 1e6;
58610
+ var DEFAULT_MAX_USD = "1.00";
58611
+ var defaultSleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
58612
+ var RETRYABLE_503 = /* @__PURE__ */ new Set(["X402_RETRY", "X402_PAYER_MODE_CHANGED", "X402_LEDGER_UNAVAILABLE"]);
58613
+ function isTransient(err) {
58614
+ if (!(err instanceof SkillError)) return false;
58615
+ if (err.code === "HTTP_TIMEOUT" || err.code === "HTTP_UNREACHABLE") return true;
58616
+ if (RETRYABLE_503.has(err.code)) return true;
58617
+ const status = err.details?.httpStatus;
58618
+ return status === 500 || status === 502 || status === 504 || status === 429;
58619
+ }
58620
+ async function x402Api(method, path8, { body, key, fetchImpl = globalThis.fetch, base = X402_BASE } = {}) {
58621
+ const url = `${base.replace(/\/+$/, "")}${path8}`;
58622
+ const controller = new AbortController();
58623
+ const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
58624
+ let res;
58625
+ let text;
58626
+ try {
58627
+ res = await fetchImpl(url, {
58628
+ method,
58629
+ headers: {
58630
+ accept: "application/json",
58631
+ "user-agent": USER_AGENT,
58632
+ "x-rozo-client": ATTRIBUTION_CLIENT,
58633
+ ...key ? { authorization: `Bearer ${key}` } : {},
58634
+ ...body !== void 0 ? { "content-type": "application/json" } : {}
58635
+ },
58636
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
58637
+ signal: controller.signal
58638
+ });
58639
+ text = await res.text();
58640
+ } catch (err) {
58641
+ throw new SkillError(
58642
+ err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
58643
+ `${method} ${path8} failed: ${redact(err?.message || String(err))}`,
58644
+ { url: redactUrl(url) }
58645
+ );
58646
+ } finally {
58647
+ clearTimeout(timer);
58648
+ }
58649
+ let json = null;
58650
+ try {
58651
+ json = text ? JSON.parse(text) : null;
58652
+ } catch {
58653
+ json = null;
58654
+ }
58655
+ if (res.status === 503) {
58656
+ const serverCode = json?.code || json?.error?.code || null;
58657
+ if (RETRYABLE_503.has(serverCode)) {
58658
+ throw new SkillError(serverCode, redact(String(json?.error?.message || json?.message || "Retry.")), { httpStatus: 503 });
58659
+ }
58660
+ throw new SkillError("X402_PAYER_DISABLED", DISABLED_MESSAGE, { httpStatus: 503, ...serverCode ? { serverCode } : {} });
58661
+ }
58662
+ if (!res.ok) {
58663
+ const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
58664
+ const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
58665
+ throw new SkillError(code, redact(String(message)), {
58666
+ httpStatus: res.status,
58667
+ url: redactUrl(url),
58668
+ body: redactDeep(json ?? redact(String(text ?? "")).slice(0, 800))
58669
+ });
58670
+ }
58671
+ if (json === null || typeof json !== "object") {
58672
+ throw new SkillError("HTTP_BAD_JSON", `${method} ${path8} returned a non-JSON body.`, { url: redactUrl(url) });
58673
+ }
58674
+ return json;
58675
+ }
58676
+ function requireKey() {
58677
+ const found = readAgentKey();
58678
+ if (!found) {
58679
+ throw new SkillError(
58680
+ "X402_NO_KEY",
58681
+ `No x402 agent key yet. Run "rozo-checkout x402 topup <usd> --with <coin>" first; it creates one and stores it in ${keyPath()}. Or set ${KEY_ENV}.`
58682
+ );
58683
+ }
58684
+ return found;
58685
+ }
58686
+ function pick(obj, ...names) {
58687
+ for (const n of names) {
58688
+ const v = n.split(".").reduce((o, k) => o == null ? void 0 : o[k], obj);
58689
+ if (v !== void 0 && v !== null && v !== "") return v;
58690
+ }
58691
+ return null;
58692
+ }
58693
+ function normalizeTopup(resp) {
58694
+ return {
58695
+ orderId: pick(resp, "orderId", "rozoPaymentId", "paymentId", "id", "order.id"),
58696
+ address: pick(resp, "depositAddress", "receiverAddress", "address", "deposit.address", "order.receiverAddress"),
58697
+ memo: pick(resp, "memo", "receiverMemo", "deposit.memo"),
58698
+ memoType: pick(resp, "memoType", "receiverMemoType", "deposit.memoType"),
58699
+ lnInvoice: pick(resp, "lnInvoice", "invoice", "deposit.lnInvoice"),
58700
+ amount: pick(resp, "payAmount", "amountToSend", "deposit.amount", "amount"),
58701
+ token: pick(resp, "deposit.tokenSymbol", "deposit.token", "tokenSymbol", "token"),
58702
+ chain: pick(resp, "deposit.chainId", "deposit.chain", "chainId", "chain"),
58703
+ creditUsd: pick(resp, "creditUsd", "credit", "amountUsd"),
58704
+ expiresAt: pick(resp, "expiresAt", "deposit.expiresAt"),
58705
+ paymentLink: pick(resp, "paymentLink", "payUrl")
58706
+ };
58707
+ }
58708
+ async function runTopup({ amountUsd, coin }, { fetchImpl, blacklist } = {}) {
58709
+ const usd = Number(amountUsd);
58710
+ if (!/^\d+(\.\d{1,2})?$/.test(String(amountUsd ?? "").trim()) || !Number.isFinite(usd)) {
58711
+ throw new SkillError("BAD_VALUE", `"${amountUsd}" is not a USD amount like 20 or 20.50.`);
58712
+ }
58713
+ if (usd < TOPUP_MIN_USD) {
58714
+ throw new SkillError("BAD_VALUE", `Minimum topup is $${TOPUP_MIN_USD}.`);
58715
+ }
58716
+ const preset = resolveTopupPreset(coin);
58717
+ let found = readAgentKey();
58718
+ let created = null;
58719
+ if (!found) {
58720
+ const resp2 = await x402Api("POST", "/keys", { body: {}, fetchImpl });
58721
+ const key = pick(resp2, "key", "agentKey", "apiKey");
58722
+ if (typeof key !== "string" || !key.startsWith("ak_")) {
58723
+ throw new SkillError("X402_BAD_KEY", "Rozo did not return an agent key (expected ak_\u2026).");
58724
+ }
58725
+ const file = saveAgentKey(key);
58726
+ found = { key, source: "file" };
58727
+ created = { keyMasked: maskKey(key), storedAt: file };
58728
+ }
58729
+ const resp = await x402Api("POST", "/topup", {
58730
+ // {amount, token, chain} is the documented contract; `source` is the same
58731
+ // choice in create-invoice's shape, sent too because a router that reads
58732
+ // only `source` would otherwise fall back to its default coin.
58733
+ body: {
58734
+ amount: String(amountUsd).trim(),
58735
+ token: preset.token,
58736
+ chain: preset.chain,
58737
+ source: { chainId: preset.chain, tokenSymbol: preset.token }
58738
+ },
58739
+ key: found.key,
58740
+ fetchImpl
58741
+ });
58742
+ const deposit = normalizeTopup(resp);
58743
+ if (deposit.chain !== null && String(deposit.chain) !== preset.chain || deposit.token !== null && String(deposit.token).toUpperCase() !== preset.token) {
58744
+ throw new SkillError(
58745
+ "X402_TOPUP_MISMATCH",
58746
+ `Rozo returned a deposit for ${deposit.token ?? "?"} on chain ${deposit.chain ?? "?"}, not ${preset.token} on chain ${preset.chain}. Do not send anything; the address is withheld.`,
58747
+ { orderId: deposit.orderId }
58748
+ );
58749
+ }
58750
+ if (!deposit.address && !deposit.lnInvoice) {
58751
+ throw new SkillError("X402_BAD_TOPUP", "Rozo returned a topup order without a deposit address.", {
58752
+ orderId: deposit.orderId
58753
+ });
58754
+ }
58755
+ if (deposit.address) {
58756
+ const family = /^0x[0-9a-fA-F]{40}$/.test(deposit.address) ? "evm" : preset.chain === "900" ? "solana" : "other";
58757
+ assertNotBlacklisted([{ address: deposit.address, family, role: "topup deposit address" }], blacklist ?? loadBlacklist());
58758
+ }
58759
+ return {
58760
+ success: true,
58761
+ command: "x402 topup",
58762
+ ...created ? { keyCreated: created } : {},
58763
+ key: { masked: maskKey(found.key), source: found.source },
58764
+ coin: preset.preset,
58765
+ requestedUsd: String(amountUsd).trim(),
58766
+ deposit,
58767
+ note: "Send exactly once, from your own wallet. Your x402 balance is credited after the deposit confirms."
58768
+ };
58769
+ }
58770
+ async function runBalance({ fetchImpl } = {}) {
58771
+ const found = requireKey();
58772
+ const resp = await x402Api("GET", "/balance", { key: found.key, fetchImpl });
58773
+ return {
58774
+ success: true,
58775
+ command: "x402 balance",
58776
+ key: { masked: maskKey(found.key), source: found.source },
58777
+ balanceUsd: pick(resp, "balanceUsd", "balance", "available"),
58778
+ pendingUsd: pick(resp, "pendingUsd", "pending"),
58779
+ status: pick(resp, "status"),
58780
+ mode: pick(resp, "mode"),
58781
+ limits: pick(resp, "limits") ?? {
58782
+ perPaymentUsd: pick(resp, "perTxLimitUsd"),
58783
+ dailyUsd: pick(resp, "dailyLimitUsd"),
58784
+ spentTodayUsd: pick(resp, "spentTodayUsd"),
58785
+ payToAllowlist: pick(resp, "payToAllowlist")
58786
+ },
58787
+ recent: pick(resp, "recent", "payments", "history")
58788
+ };
58789
+ }
58790
+ function parseHeaderFlags(list) {
58791
+ const out2 = {};
58792
+ for (const raw of list ?? []) {
58793
+ const idx = String(raw).indexOf(":");
58794
+ if (idx <= 0) throw new SkillError("BAD_VALUE", `--header must look like "Name: value", got "${raw}".`);
58795
+ const name = raw.slice(0, idx).trim();
58796
+ const value = raw.slice(idx + 1).trim();
58797
+ if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) {
58798
+ throw new SkillError("BAD_VALUE", `--header name "${name}" is not a valid HTTP header name.`);
58799
+ }
58800
+ if (/^(payment-signature|x-payment)$/i.test(name)) {
58801
+ throw new SkillError("BAD_VALUE", `--header ${name} is set by the payer; do not pass it.`);
58802
+ }
58803
+ out2[name] = value;
58804
+ }
58805
+ return out2;
58806
+ }
58807
+ async function callTarget(fetchImpl, { url, method, headers, body }, extraHeaders = {}) {
58808
+ const controller = new AbortController();
58809
+ const timer = setTimeout(() => controller.abort(), TARGET_TIMEOUT_MS);
58810
+ try {
58811
+ const res = await fetchImpl(url, {
58812
+ method,
58813
+ headers: { "user-agent": USER_AGENT, ...headers, ...extraHeaders },
58814
+ body: body ?? void 0,
58815
+ signal: controller.signal,
58816
+ redirect: "manual"
58817
+ });
58818
+ const text = await res.text();
58819
+ return { res, text };
58820
+ } catch (err) {
58821
+ throw new SkillError(
58822
+ err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
58823
+ `${method} ${redactUrl(url)} failed: ${redact(err?.message || String(err))}`
58824
+ );
58825
+ } finally {
58826
+ clearTimeout(timer);
58827
+ }
58828
+ }
58829
+ function headerGetter(res) {
58830
+ return (name) => res.headers?.get?.(name) ?? null;
58831
+ }
58832
+ function bodyOut(text, contentType) {
58833
+ const truncated = text.length > MAX_BODY_CHARS;
58834
+ const s = truncated ? text.slice(0, MAX_BODY_CHARS) : text;
58835
+ if (/json/i.test(contentType ?? "") && !truncated) {
58836
+ try {
58837
+ return { body: JSON.parse(s), truncated };
58838
+ } catch {
58839
+ }
58840
+ }
58841
+ return { body: s, truncated };
58842
+ }
58843
+ async function signWithRetry(signBody, { key, fetchImpl, sleep: sleep2 = defaultSleep2, retries = SIGN_RETRIES }) {
58844
+ let attempt = 0;
58845
+ for (; ; ) {
58846
+ try {
58847
+ const resp = await x402Api("POST", "/sign", { body: signBody, key, fetchImpl });
58848
+ return { resp, attempts: attempt + 1 };
58849
+ } catch (err) {
58850
+ if (attempt < retries && isTransient(err)) {
58851
+ await sleep2(SIGN_BACKOFF_MS[attempt] ?? 3e3);
58852
+ attempt += 1;
58853
+ continue;
58854
+ }
58855
+ if (err instanceof SkillError) {
58856
+ err.details = { ...err.details ?? {}, idempotencyKey: signBody.idempotencyKey, attempts: attempt + 1 };
58857
+ }
58858
+ throw err;
58859
+ }
58860
+ }
58861
+ }
58862
+ async function runPay({ url, method = "GET", headers = {}, body, maxUsd = DEFAULT_MAX_USD, prefer, idempotencyKey, dryRun = false }, { fetchImpl = globalThis.fetch, sleep: sleep2 = defaultSleep2, blacklist, uuid = () => crypto8.randomUUID() } = {}) {
58863
+ let target;
58864
+ try {
58865
+ target = new URL(url);
58866
+ } catch {
58867
+ throw new SkillError("BAD_VALUE", `"${url}" is not a URL.`);
58868
+ }
58869
+ if (target.protocol !== "https:" && target.protocol !== "http:") {
58870
+ throw new SkillError("BAD_VALUE", "x402 pay only calls http(s) URLs.");
58871
+ }
58872
+ const budgetAtomic = usdToAtomic(maxUsd);
58873
+ const request2 = { url: target.toString(), method: method.toUpperCase(), headers, body };
58874
+ const first = await callTarget(fetchImpl, request2);
58875
+ if (first.res.status !== 402) {
58876
+ const { body: outBody2, truncated: truncated2 } = bodyOut(first.text, first.res.headers.get("content-type"));
58877
+ return {
58878
+ success: first.res.ok,
58879
+ command: "x402 pay",
58880
+ paid: false,
58881
+ status: first.res.status,
58882
+ contentType: first.res.headers.get("content-type"),
58883
+ body: outBody2,
58884
+ ...truncated2 ? { truncated: truncated2 } : {},
58885
+ note: "The endpoint did not ask for payment (no 402). Nothing was charged."
58886
+ };
58887
+ }
58888
+ const challenge2 = parseChallenge({ status: 402, getHeader: headerGetter(first.res), bodyText: first.text });
58889
+ const option = selectRequirement(challenge2, { budgetAtomic, prefer });
58890
+ assertNotBlacklisted(
58891
+ [{ address: option.payTo, family: networkFamily(option.network), role: "x402 payTo" }],
58892
+ blacklist ?? loadBlacklist()
58893
+ );
58894
+ const key = idempotencyKey || uuid();
58895
+ const plan = {
58896
+ network: option.network,
58897
+ networkLabel: networkLabel(option.network),
58898
+ asset: option.asset,
58899
+ payTo: option.payTo,
58900
+ amountUsd: option.amountUsd,
58901
+ budgetUsd: formatUsdc(budgetAtomic),
58902
+ idempotencyKey: key
58903
+ };
58904
+ if (dryRun) {
58905
+ return {
58906
+ success: true,
58907
+ command: "x402 pay",
58908
+ paid: false,
58909
+ dryRun: true,
58910
+ x402Version: challenge2.x402Version,
58911
+ plan,
58912
+ note: "Dry run: the challenge was read and an option chosen; Rozo was not asked to sign and nothing was charged."
58913
+ };
58914
+ }
58915
+ const found = requireKey();
58916
+ const signBody = {
58917
+ x402Version: challenge2.x402Version,
58918
+ // Only the seller's own declared resource, never our request URL: a query
58919
+ // string can carry the caller's credential for that seller.
58920
+ ...challenge2.resource ? { resource: challenge2.resource } : {},
58921
+ accepts: [option.requirement],
58922
+ budget: plan.budgetUsd,
58923
+ // Same cap under the name the router reads.
58924
+ maxAmountUsd: plan.budgetUsd,
58925
+ idempotencyKey: key
58926
+ };
58927
+ const { resp: signed, attempts } = await signWithRetry(signBody, { key: found.key, fetchImpl, sleep: sleep2 });
58928
+ const signature = extractSignature(signed);
58929
+ if (!signature) {
58930
+ throw new SkillError("X402_BAD_SIGNATURE", "Rozo answered /sign without a PAYMENT-SIGNATURE value.", {
58931
+ idempotencyKey: key
58932
+ });
58933
+ }
58934
+ const named = [signed.headerName, signed.header].find((h) => typeof h === "string" && /^[A-Za-z-]+$/.test(h));
58935
+ const headerName = named || paymentHeaderName(challenge2.x402Version);
58936
+ let replay;
58937
+ try {
58938
+ try {
58939
+ replay = await callTarget(fetchImpl, request2, { [headerName]: signature });
58940
+ } catch (err) {
58941
+ if (err.code !== "HTTP_TIMEOUT" && err.code !== "HTTP_UNREACHABLE") throw err;
58942
+ await sleep2(1e3);
58943
+ replay = await callTarget(fetchImpl, request2, { [headerName]: signature });
58944
+ }
58945
+ } catch (err) {
58946
+ throw new SkillError(
58947
+ "X402_REPLAY_FAILED",
58948
+ `Rozo signed the payment but the paid request failed: ${redact(err?.message || String(err))}. The seller may or may not have settled it. Check "x402 balance", then rerun with the same --idempotency-key.`,
58949
+ { idempotencyKey: key, paymentId: pick(signed, "paymentId", "id"), signed: true, cause: err?.code ?? null }
58950
+ );
58951
+ }
58952
+ const settlementHeader = paymentResponseHeaderNames().map((n) => replay.res.headers.get(n)).find((v) => v);
58953
+ const settlement = settlementHeader ? decodeSettlement(settlementHeader) : null;
58954
+ const { body: outBody, truncated } = bodyOut(replay.text, replay.res.headers.get("content-type"));
58955
+ const payment = {
58956
+ ...plan,
58957
+ paymentId: pick(signed, "paymentId", "id"),
58958
+ signAttempts: attempts,
58959
+ settlement
58960
+ };
58961
+ if (replay.res.status === 402) {
58962
+ let reason = null;
58963
+ try {
58964
+ reason = parseChallenge({ status: 402, getHeader: headerGetter(replay.res), bodyText: replay.text }).error;
58965
+ } catch {
58966
+ }
58967
+ throw new SkillError(
58968
+ "X402_PAYMENT_REJECTED",
58969
+ `The endpoint rejected the payment${reason ? `: ${redact(reason)}` : ""}. Do not retry with a new idempotency key; check "x402 balance" first.`,
58970
+ { payment: redactDeep(payment) }
58971
+ );
58972
+ }
58973
+ return {
58974
+ success: replay.res.ok,
58975
+ command: "x402 pay",
58976
+ paid: true,
58977
+ status: replay.res.status,
58978
+ contentType: replay.res.headers.get("content-type"),
58979
+ payment,
58980
+ body: outBody,
58981
+ ...truncated ? { truncated } : {},
58982
+ ...replay.res.ok ? {} : {
58983
+ note: 'The payment was signed but the endpoint did not return success. The seller may still have settled it; check "x402 balance" before trying again, and reuse --idempotency-key to avoid a second charge.'
58984
+ }
58985
+ };
58986
+ }
58987
+
58211
58988
  // scripts/src/cli.mjs
58212
58989
  var SkillErrorLike = class extends Error {
58213
58990
  constructor(code, message) {
@@ -58813,7 +59590,129 @@ function printDeposit(deposit, opts) {
58813
59590
  out(dim(" Send it exactly once."));
58814
59591
  out();
58815
59592
  }
59593
+ function x402Printable(payload) {
59594
+ const { body, ...rest } = payload;
59595
+ if (rest.payment?.settlement?.transaction) {
59596
+ rest.payment = {
59597
+ ...rest.payment,
59598
+ settlement: { ...rest.payment.settlement, transaction: publicHash(rest.payment.settlement.transaction) }
59599
+ };
59600
+ }
59601
+ const safe = redactDeep(rest);
59602
+ return body === void 0 ? safe : { ...safe, body };
59603
+ }
59604
+ async function cmdX402(opts) {
59605
+ if (opts.sub === "help") {
59606
+ if (opts.json) printJson({ success: true, help: X402_HELP, version: VERSION });
59607
+ else out(X402_HELP);
59608
+ return EXIT_OK;
59609
+ }
59610
+ let payload;
59611
+ try {
59612
+ if (opts.sub === "balance") payload = await runBalance();
59613
+ else if (opts.sub === "topup") payload = await runTopup({ amountUsd: opts.amount, coin: opts.coin });
59614
+ else {
59615
+ payload = await runPay({
59616
+ url: opts.url,
59617
+ method: opts.method,
59618
+ headers: parseHeaderFlags(opts.headers),
59619
+ body: opts.body,
59620
+ maxUsd: opts.maxUsd,
59621
+ prefer: opts.prefer,
59622
+ idempotencyKey: opts.idempotencyKey,
59623
+ dryRun: opts.dryRun
59624
+ });
59625
+ }
59626
+ } catch (err2) {
59627
+ const failure = formatFailure(err2);
59628
+ if (opts.json) printJson(failure);
59629
+ else {
59630
+ printError(failure);
59631
+ if (failure.error?.details?.idempotencyKey && failure.error.code !== "X402_PAYER_DISABLED") {
59632
+ out(` ${dim(`idempotencyKey ${failure.error.details.idempotencyKey} (reuse it with --idempotency-key to avoid a second charge)`)}`);
59633
+ }
59634
+ }
59635
+ return err2?.code === "BAD_VALUE" || err2?.code === "BAD_PRESET" ? EXIT_USAGE : EXIT_ERROR;
59636
+ }
59637
+ const printable = x402Printable(payload);
59638
+ if (opts.json) {
59639
+ printJson(printable);
59640
+ return payload.success ? EXIT_OK : EXIT_ERROR;
59641
+ }
59642
+ if (opts.sub === "balance") {
59643
+ out();
59644
+ out(` Balance ${bold(`$${printable.balanceUsd ?? "?"}`)}`);
59645
+ if (printable.pendingUsd) out(` Pending $${printable.pendingUsd}`);
59646
+ if (printable.limits) out(` Limits ${dim(JSON.stringify(printable.limits))}`);
59647
+ out(` Key ${printable.key.masked} ${dim(`(${printable.key.source})`)}`);
59648
+ out();
59649
+ return EXIT_OK;
59650
+ }
59651
+ if (opts.sub === "topup") {
59652
+ const d = printable.deposit;
59653
+ out();
59654
+ if (printable.keyCreated) {
59655
+ out(` ${green("New x402 agent key")} ${printable.keyCreated.keyMasked}, stored in ${printable.keyCreated.storedAt}`);
59656
+ out(` ${dim("Keep that file private: it owns your x402 balance.")}`);
59657
+ out();
59658
+ }
59659
+ out(` ${bold("Send exactly:")}`);
59660
+ out();
59661
+ if (d.lnInvoice) {
59662
+ if (d.amount) out(` Amount ${bold(String(d.amount))}`);
59663
+ out(` BOLT11 ${payload.deposit.lnInvoice}`);
59664
+ } else {
59665
+ out(` Amount ${bold(`${d.amount ?? "?"} ${d.token ?? ""}`.trim())}`);
59666
+ if (d.chain) out(` Chain ${bold(String(d.chain))}`);
59667
+ out(` Address ${bold(payload.deposit.address)}`);
59668
+ if (d.memo) out(` Memo ${bold(String(d.memo))} ${red(`(required${d.memoType ? `, ${d.memoType}` : ""})`)}`);
59669
+ }
59670
+ if (d.expiresAt) out(` Expires ${d.expiresAt}`);
59671
+ if (d.orderId) out(` Order ${d.orderId}`);
59672
+ out();
59673
+ out(dim(` ${printable.note}`));
59674
+ out(dim(" Then: rozo-checkout x402 balance"));
59675
+ out();
59676
+ return EXIT_OK;
59677
+ }
59678
+ const err = (s = "") => process.stderr.write(`${s}
59679
+ `);
59680
+ if (printable.dryRun) {
59681
+ const p = printable.plan;
59682
+ err(` Would pay ${bold(`${p.amountUsd} USDC`)} on ${p.networkLabel} to ${p.payTo} (budget ${p.budgetUsd}).`);
59683
+ err(dim(` idempotencyKey ${p.idempotencyKey}`));
59684
+ return EXIT_OK;
59685
+ }
59686
+ if (printable.paid) {
59687
+ const p = printable.payment;
59688
+ err(` ${green("Paid")} ${p.amountUsd} USDC on ${p.networkLabel} \u2192 HTTP ${printable.status}`);
59689
+ if (p.settlement?.transaction) err(dim(` tx ${p.settlement.transaction}`));
59690
+ err(dim(` idempotencyKey ${p.idempotencyKey}`));
59691
+ } else {
59692
+ err(dim(` HTTP ${printable.status}, no payment needed.`));
59693
+ }
59694
+ if (printable.note) err(yellow(` ${printable.note}`));
59695
+ const body = payload.body;
59696
+ process.stdout.write(typeof body === "string" ? body : JSON.stringify(body, null, 2));
59697
+ process.stdout.write("\n");
59698
+ return payload.success ? EXIT_OK : EXIT_ERROR;
59699
+ }
58816
59700
  async function main9() {
59701
+ if (isX402Argv(process.argv.slice(2))) {
59702
+ let x402opts;
59703
+ try {
59704
+ x402opts = parseX402Args(process.argv.slice(2));
59705
+ } catch (err) {
59706
+ const payload = formatFailure(err);
59707
+ if (process.argv.includes("--json")) printJson(payload);
59708
+ else {
59709
+ printError(payload);
59710
+ out(dim("\n rozo-checkout x402 --help for usage."));
59711
+ }
59712
+ return EXIT_USAGE;
59713
+ }
59714
+ return cmdX402(x402opts);
59715
+ }
58817
59716
  let opts;
58818
59717
  try {
58819
59718
  opts = parseCliArgs(process.argv.slice(2));
@@ -62,6 +62,7 @@ function redact(text) {
62
62
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
63
63
  });
64
64
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
65
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
65
66
  s = s.replace(
66
67
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
67
68
  "$1$2<redacted>"
@@ -36,6 +36,7 @@ function redact(text) {
36
36
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
37
37
  });
38
38
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
39
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
39
40
  s = s.replace(
40
41
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
41
42
  "$1$2<redacted>"
@@ -36,6 +36,7 @@ function redact(text) {
36
36
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
37
37
  });
38
38
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
39
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
39
40
  s = s.replace(
40
41
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
41
42
  "$1$2<redacted>"
@@ -36,6 +36,7 @@ function redact(text) {
36
36
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
37
37
  });
38
38
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
39
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
39
40
  s = s.replace(
40
41
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
41
42
  "$1$2<redacted>"
@@ -10005,6 +10005,7 @@ function redact(text) {
10005
10005
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
10006
10006
  });
10007
10007
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
10008
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
10008
10009
  s = s.replace(
10009
10010
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
10010
10011
  "$1$2<redacted>"
@@ -32173,6 +32173,7 @@ function redact(text) {
32173
32173
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
32174
32174
  });
32175
32175
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
32176
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
32176
32177
  s = s.replace(
32177
32178
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
32178
32179
  "$1$2<redacted>"
@@ -37,6 +37,7 @@ function redact(text) {
37
37
  return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
38
38
  });
39
39
  s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
40
+ s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
40
41
  s = s.replace(
41
42
  /\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
42
43
  "$1$2<redacted>"