@inflowpayai/inflow 0.5.2 → 0.6.0

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 +105 -37
  2. package/dist/cli.js +2114 -531
  3. package/package.json +6 -4
package/dist/cli.js CHANGED
@@ -8837,19 +8837,19 @@ var require_range = __commonJS({
8837
8837
  var replaceCaret = (comp, options) => {
8838
8838
  debug("caret", comp, options);
8839
8839
  const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
8840
- const z6 = options.includePrerelease ? "-0" : "";
8840
+ const z7 = options.includePrerelease ? "-0" : "";
8841
8841
  return comp.replace(r, (_, M, m, p, pr) => {
8842
8842
  debug("caret", comp, _, M, m, p, pr);
8843
8843
  let ret;
8844
8844
  if (isX(M)) {
8845
8845
  ret = "";
8846
8846
  } else if (isX(m)) {
8847
- ret = `>=${M}.0.0${z6} <${+M + 1}.0.0-0`;
8847
+ ret = `>=${M}.0.0${z7} <${+M + 1}.0.0-0`;
8848
8848
  } else if (isX(p)) {
8849
8849
  if (M === "0") {
8850
- ret = `>=${M}.${m}.0${z6} <${M}.${+m + 1}.0-0`;
8850
+ ret = `>=${M}.${m}.0${z7} <${M}.${+m + 1}.0-0`;
8851
8851
  } else {
8852
- ret = `>=${M}.${m}.0${z6} <${+M + 1}.0.0-0`;
8852
+ ret = `>=${M}.${m}.0${z7} <${+M + 1}.0.0-0`;
8853
8853
  }
8854
8854
  } else if (pr) {
8855
8855
  debug("replaceCaret pr", pr);
@@ -8866,9 +8866,9 @@ var require_range = __commonJS({
8866
8866
  debug("no pr");
8867
8867
  if (M === "0") {
8868
8868
  if (m === "0") {
8869
- ret = `>=${M}.${m}.${p}${z6} <${M}.${m}.${+p + 1}-0`;
8869
+ ret = `>=${M}.${m}.${p}${z7} <${M}.${m}.${+p + 1}-0`;
8870
8870
  } else {
8871
- ret = `>=${M}.${m}.${p}${z6} <${M}.${+m + 1}.0-0`;
8871
+ ret = `>=${M}.${m}.${p}${z7} <${M}.${+m + 1}.0-0`;
8872
8872
  }
8873
8873
  } else {
8874
8874
  ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
@@ -9687,6 +9687,10 @@ var require_semver2 = __commonJS({
9687
9687
  import process9 from "process";
9688
9688
 
9689
9689
  // ../core/dist/index.js
9690
+ import {
9691
+ MppClient
9692
+ } from "@inflowpayai/mpp";
9693
+ import { inflow as createInflowMppMethod } from "@inflowpayai/mpp-buyer";
9690
9694
  import { createInflowClient } from "@inflowpayai/x402-buyer";
9691
9695
  import { unlink } from "fs/promises";
9692
9696
  import path5 from "path";
@@ -10959,6 +10963,7 @@ import { HEADERS, readHeader } from "@inflowpayai/x402";
10959
10963
  import { fromFoundationRequirements } from "@inflowpayai/x402-buyer";
10960
10964
  import { decodePaymentRequiredHeader } from "@x402/core/http";
10961
10965
  import { sellerProbe } from "@inflowpayai/x402-buyer/probe";
10966
+ import { EXTRA_KEYS } from "@inflowpayai/x402";
10962
10967
  import { writeFile } from "fs/promises";
10963
10968
  import { resolve as resolvePath } from "path";
10964
10969
  import { HEADERS as HEADERS2, readHeader as readHeader2 } from "@inflowpayai/x402";
@@ -10976,6 +10981,24 @@ import {
10976
10981
  sellerProbe as sellerProbe2
10977
10982
  } from "@inflowpayai/x402-buyer/probe";
10978
10983
  import { decodePaymentRequiredHeader as decodePaymentRequiredHeader2 } from "@x402/core/http";
10984
+ import { HEADERS as HEADERS3, parseChallengeHeaders, readHeaderAll } from "@inflowpayai/mpp";
10985
+ import { sellerProbe as sellerProbe3 } from "@inflowpayai/x402-buyer/probe";
10986
+ import {
10987
+ decode,
10988
+ decodeCredential,
10989
+ decodeReceipt,
10990
+ parseChallengeHeader
10991
+ } from "@inflowpayai/mpp";
10992
+ import { METHOD_INFLOW } from "@inflowpayai/mpp";
10993
+ import {
10994
+ HEADERS as HEADERS4,
10995
+ parseChallengeHeaders as parseChallengeHeaders2,
10996
+ readHeader as readHeader3,
10997
+ readHeaderAll as readHeaderAll2,
10998
+ SCHEME_PAYMENT,
10999
+ decodeReceipt as decodeReceipt2
11000
+ } from "@inflowpayai/mpp";
11001
+ import { sellerProbe as sellerProbe4 } from "@inflowpayai/x402-buyer/probe";
10979
11002
  import { hostname } from "os";
10980
11003
 
10981
11004
  // ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
@@ -11005,7 +11028,7 @@ import {
11005
11028
  parseHeaderFlag,
11006
11029
  parseHeaderFlags,
11007
11030
  replayWithPayment as replayWithPayment2,
11008
- sellerProbe as sellerProbe3,
11031
+ sellerProbe as sellerProbe5,
11009
11032
  X402HeaderFlagFormatError
11010
11033
  } from "@inflowpayai/x402-buyer/probe";
11011
11034
  var REDACTED_BODY_FIELDS = /* @__PURE__ */ new Set([
@@ -11668,11 +11691,11 @@ var NO_INFLOW_MATCH_MESSAGE = "Seller does not accept InFlow-signed payments. Us
11668
11691
  var NO_FILTERED_MATCH_CODE = "NO_FILTERED_MATCH";
11669
11692
  var PAYMENT_NOT_ACCEPTED_CODE = "PAYMENT_NOT_ACCEPTED";
11670
11693
  var UNEXPECTED_PROBE_STATUS_CODE = "UNEXPECTED_PROBE_STATUS";
11671
- function extractExtraName(entry) {
11694
+ function extractAssetName(entry) {
11672
11695
  const extra = entry.extra;
11673
11696
  if (extra === void 0 || extra === null) return void 0;
11674
- const name = extra.name;
11675
- return typeof name === "string" ? name : void 0;
11697
+ const assetName = extra[EXTRA_KEYS.ASSET_NAME];
11698
+ return typeof assetName === "string" ? assetName : void 0;
11676
11699
  }
11677
11700
  function hasAnyFilter(filters) {
11678
11701
  return filters.scheme !== void 0 || filters.network !== void 0 || filters.asset !== void 0 || filters.assetName !== void 0;
@@ -11683,7 +11706,7 @@ function filterAccepts(decoded, filters) {
11683
11706
  return {
11684
11707
  ...decoded,
11685
11708
  accepts: decoded.accepts.filter(
11686
- (entry) => (scheme === void 0 || entry.scheme === scheme) && (network === void 0 || entry.network === network) && (asset === void 0 || entry.asset === asset) && (assetName === void 0 || extractExtraName(entry) === assetName)
11709
+ (entry) => (scheme === void 0 || entry.scheme === scheme) && (network === void 0 || entry.network === network) && (asset === void 0 || entry.asset === asset) && (assetName === void 0 || extractAssetName(entry) === assetName)
11687
11710
  )
11688
11711
  };
11689
11712
  }
@@ -11698,8 +11721,8 @@ function buildNoFilteredMatchMessage(decoded, filters) {
11698
11721
  const available = decoded.accepts.map((entry) => {
11699
11722
  const parts = [`${entry.scheme}/${entry.network}`];
11700
11723
  if (entry.asset !== void 0 && entry.asset !== "") parts.push(`asset=${entry.asset}`);
11701
- const name = extractExtraName(entry);
11702
- if (name !== void 0 && name !== "") parts.push(`name=${name}`);
11724
+ const assetName2 = extractAssetName(entry);
11725
+ if (assetName2 !== void 0 && assetName2 !== "") parts.push(`assetName=${assetName2}`);
11703
11726
  return parts.join(" ");
11704
11727
  }).join(", ");
11705
11728
  return `Seller has no accepts[] entry matching ${filterDescription}. Available: ${available || "(none)"}.`;
@@ -11816,6 +11839,31 @@ function approvalUrlFor(apiBaseUrl2, approvalId) {
11816
11839
  const host = dashboardHostFor(apiBaseUrl2);
11817
11840
  return `https://${host}/approvals/${approvalId}/view/`;
11818
11841
  }
11842
+ function userFacingApiError(err, fallbackCode) {
11843
+ if (isSdkApiError(err)) {
11844
+ const code = isMeaningfulCode(err.code) ? err.code : fallbackCode;
11845
+ return { code, message: stripDiagnosticPrefix(err.message) };
11846
+ }
11847
+ return { code: fallbackCode, message: err instanceof Error ? err.message : String(err) };
11848
+ }
11849
+ var UNCODED_SENTINEL = "UNEXPECTED_ERROR";
11850
+ function isMeaningfulCode(code) {
11851
+ return code.length > 0 && code !== UNCODED_SENTINEL;
11852
+ }
11853
+ function userFacingErrorMessage(err) {
11854
+ if (isSdkApiError(err)) return stripDiagnosticPrefix(err.message);
11855
+ return err instanceof Error ? err.message : String(err);
11856
+ }
11857
+ function isSdkApiError(err) {
11858
+ if (typeof err !== "object" || err === null) return false;
11859
+ const e = err;
11860
+ return typeof e.code === "string" && typeof e.endpoint === "string" && typeof e.message === "string";
11861
+ }
11862
+ function stripDiagnosticPrefix(message) {
11863
+ const separator = " \u2014 ";
11864
+ const index = message.indexOf(separator);
11865
+ return index >= 0 ? message.slice(index + separator.length) : message;
11866
+ }
11819
11867
  function decodeHeader(raw) {
11820
11868
  const parsed = decodePaymentRequiredHeader2(raw);
11821
11869
  const decoded = {
@@ -11894,10 +11942,7 @@ function mapSdkError(err) {
11894
11942
  if (err instanceof X402AdapterRoutingError) {
11895
11943
  return { code: NO_INFLOW_MATCH_CODE, message: NO_INFLOW_MATCH_MESSAGE };
11896
11944
  }
11897
- return {
11898
- code: "PAY_FAILED",
11899
- message: err instanceof Error ? err.message : String(err)
11900
- };
11945
+ return userFacingApiError(err, "PAYMENT_FAILED");
11901
11946
  }
11902
11947
  function buildSettledMeta(headers) {
11903
11948
  const responseHeader = readHeader2(Object.fromEntries(headers.entries()), HEADERS2.PAYMENT_RESPONSE);
@@ -12007,7 +12052,7 @@ async function runPayPipeline(deps, emit) {
12007
12052
  });
12008
12053
  return;
12009
12054
  }
12010
- const requirement = deps.client.selectInflowRequirement(filtered);
12055
+ const requirement = await deps.client.selectInflowRequirement(filtered);
12011
12056
  if (requirement === null) {
12012
12057
  emit({
12013
12058
  type: "errored",
@@ -12159,7 +12204,7 @@ function runX402Status(input) {
12159
12204
  return;
12160
12205
  }
12161
12206
  } catch (err) {
12162
- yield { type: "crashed", message: err instanceof Error ? err.message : String(err) };
12207
+ yield { type: "crashed", message: userFacingErrorMessage(err) };
12163
12208
  }
12164
12209
  }
12165
12210
  return { events: generate() };
@@ -12168,6 +12213,478 @@ async function runX402Supported(input) {
12168
12213
  const client = await input.x402.client();
12169
12214
  return client.getSupported();
12170
12215
  }
12216
+ async function runMppCancel(input) {
12217
+ await input.mpp.cancelApproval(input.approvalId);
12218
+ return {
12219
+ approval_id: input.approvalId,
12220
+ cancelled: true,
12221
+ note: "best-effort; server-side state not verified"
12222
+ };
12223
+ }
12224
+ function decodeChallengeRequest(challenge) {
12225
+ if (challenge.request === "") return void 0;
12226
+ try {
12227
+ return decode(challenge.request, "challenge request");
12228
+ } catch {
12229
+ return void 0;
12230
+ }
12231
+ }
12232
+ function summarizeChallenge(challenge) {
12233
+ const out = {
12234
+ id: challenge.id,
12235
+ realm: challenge.realm,
12236
+ method: challenge.method,
12237
+ intent: challenge.intent
12238
+ };
12239
+ const request = decodeChallengeRequest(challenge);
12240
+ if (request !== void 0) {
12241
+ out.amount = request.amount;
12242
+ out.currency = request.currency;
12243
+ if (request.recipient !== void 0) out.recipient = request.recipient;
12244
+ if (request.methodDetails?.rail !== void 0) out.rail = request.methodDetails.rail;
12245
+ if (request.methodDetails?.instrumentId !== void 0) out.instrumentId = request.methodDetails.instrumentId;
12246
+ }
12247
+ if (challenge.expires !== void 0) out.expires = challenge.expires;
12248
+ if (challenge.description !== void 0) out.description = challenge.description;
12249
+ if (challenge.digest !== void 0) out.digest = challenge.digest;
12250
+ return out;
12251
+ }
12252
+ function decodeMppValue(raw) {
12253
+ const trimmed = raw.trim();
12254
+ if (/^payment\s+/i.test(trimmed) || /[a-zA-Z0-9-]+="/.test(trimmed)) {
12255
+ return { kind: "challenge", challenge: summarizeChallenge(parseChallengeHeader(trimmed)) };
12256
+ }
12257
+ const probe = decode(trimmed, "value");
12258
+ if ("settlement" in probe || "challengeId" in probe) {
12259
+ return { kind: "receipt", receipt: decodeReceipt(trimmed) };
12260
+ }
12261
+ return { kind: "credential", credential: decodeCredential(trimmed) };
12262
+ }
12263
+ var INVALID_402_CODE2 = "INVALID_402";
12264
+ var NO_INFLOW_MATCH_CODE2 = "NO_INFLOW_MATCH";
12265
+ var NO_INFLOW_MATCH_MESSAGE2 = "Seller's 402 carries no `inflow`-method MPP challenge; the InFlow buyer cannot fulfil it.";
12266
+ var NO_FILTERED_MATCH_CODE2 = "NO_FILTERED_MATCH";
12267
+ var PAYMENT_NOT_ACCEPTED_CODE2 = "PAYMENT_NOT_ACCEPTED";
12268
+ var UNEXPECTED_PROBE_STATUS_CODE2 = "UNEXPECTED_PROBE_STATUS";
12269
+ function isSuccessStatus2(status) {
12270
+ return status >= 200 && status < 300;
12271
+ }
12272
+ function filterInflowChallenges(challenges) {
12273
+ return challenges.filter((challenge) => challenge.method === METHOD_INFLOW);
12274
+ }
12275
+ function hasAnyChallengeFilter(filters) {
12276
+ return filters.paymentMethod !== void 0 || filters.intent !== void 0 || filters.currency !== void 0 || filters.rail !== void 0;
12277
+ }
12278
+ function filterChallenges(challenges, filters) {
12279
+ if (!hasAnyChallengeFilter(filters)) return [...challenges];
12280
+ const { paymentMethod, intent, currency, rail } = filters;
12281
+ return challenges.filter((challenge) => {
12282
+ if (paymentMethod !== void 0 && challenge.method !== paymentMethod) return false;
12283
+ if (intent !== void 0 && challenge.intent !== intent) return false;
12284
+ if (currency !== void 0 || rail !== void 0) {
12285
+ const request = decodeChallengeRequest(challenge);
12286
+ if (currency !== void 0 && request?.currency !== currency) return false;
12287
+ if (rail !== void 0 && request?.methodDetails?.rail !== rail) return false;
12288
+ }
12289
+ return true;
12290
+ });
12291
+ }
12292
+ function buildNoFilteredMatchMessage2(challenges, filters) {
12293
+ const { paymentMethod, intent, currency, rail } = filters;
12294
+ const filterDescription = [
12295
+ paymentMethod !== void 0 ? `--payment-method=${paymentMethod}` : null,
12296
+ intent !== void 0 ? `--intent=${intent}` : null,
12297
+ currency !== void 0 ? `--currency=${currency}` : null,
12298
+ rail !== void 0 ? `--rail=${rail}` : null
12299
+ ].filter((s) => s !== null).join(" ");
12300
+ const available = challenges.map((challenge) => {
12301
+ const request = decodeChallengeRequest(challenge);
12302
+ const parts = [`${challenge.method}/${challenge.intent}`];
12303
+ if (request?.currency !== void 0 && request.currency !== "") parts.push(`currency=${request.currency}`);
12304
+ const railValue = request?.methodDetails?.rail;
12305
+ if (railValue !== void 0 && railValue !== "") parts.push(`rail=${railValue}`);
12306
+ return parts.join(" ");
12307
+ }).join(", ");
12308
+ return `Seller has no \`inflow\` challenge matching ${filterDescription}. Available: ${available || "(none)"}.`;
12309
+ }
12310
+ function reduceMppInspect(state, event) {
12311
+ switch (event.type) {
12312
+ case "challenges":
12313
+ return { kind: "challenges", result: event.result };
12314
+ case "no-payment":
12315
+ return { kind: "no-payment", result: event.result };
12316
+ case "errored":
12317
+ return { kind: "error", code: event.code, message: event.message };
12318
+ default:
12319
+ return state;
12320
+ }
12321
+ }
12322
+ async function runMppInspectPipeline(deps, emit) {
12323
+ let probe;
12324
+ try {
12325
+ probe = await sellerProbe3(deps.url, deps.probeOptions);
12326
+ } catch (err) {
12327
+ emit({ type: "errored", code: "INSPECT_FAILED", message: err instanceof Error ? err.message : String(err) });
12328
+ return;
12329
+ }
12330
+ if (probe.status !== 402) {
12331
+ if (!isSuccessStatus2(probe.status)) {
12332
+ emit({
12333
+ type: "errored",
12334
+ code: UNEXPECTED_PROBE_STATUS_CODE2,
12335
+ message: `Seller returned status ${String(probe.status)} during probe; expected 2xx (no payment) or 402 (payment required).`
12336
+ });
12337
+ return;
12338
+ }
12339
+ emit({
12340
+ type: "no-payment",
12341
+ result: {
12342
+ outcome: "no-payment-required",
12343
+ url: deps.url,
12344
+ method: deps.probeOptions.method,
12345
+ status: probe.status,
12346
+ contentType: probe.contentType,
12347
+ bodySizeBytes: probe.bytes.byteLength
12348
+ }
12349
+ });
12350
+ return;
12351
+ }
12352
+ const headerValues = readHeaderAll(probe.headers, HEADERS3.WWW_AUTHENTICATE);
12353
+ if (headerValues.length === 0) {
12354
+ emit({
12355
+ type: "errored",
12356
+ code: INVALID_402_CODE2,
12357
+ message: "Seller returned 402 but did not include a WWW-Authenticate: Payment header."
12358
+ });
12359
+ return;
12360
+ }
12361
+ let challenges;
12362
+ try {
12363
+ challenges = parseChallengeHeaders(headerValues);
12364
+ } catch (err) {
12365
+ emit({ type: "errored", code: "DECODE_FAILED", message: err instanceof Error ? err.message : String(err) });
12366
+ return;
12367
+ }
12368
+ const inflowChallenges = filterInflowChallenges(challenges);
12369
+ if (inflowChallenges.length === 0) {
12370
+ emit({ type: "errored", code: NO_INFLOW_MATCH_CODE2, message: NO_INFLOW_MATCH_MESSAGE2 });
12371
+ return;
12372
+ }
12373
+ const filters = {
12374
+ ...deps.paymentMethodFilter !== void 0 ? { paymentMethod: deps.paymentMethodFilter } : {},
12375
+ ...deps.intentFilter !== void 0 ? { intent: deps.intentFilter } : {},
12376
+ ...deps.currencyFilter !== void 0 ? { currency: deps.currencyFilter } : {},
12377
+ ...deps.railFilter !== void 0 ? { rail: deps.railFilter } : {}
12378
+ };
12379
+ const filtered = filterChallenges(inflowChallenges, filters);
12380
+ if (hasAnyChallengeFilter(filters) && filtered.length === 0) {
12381
+ emit({
12382
+ type: "errored",
12383
+ code: NO_FILTERED_MATCH_CODE2,
12384
+ message: buildNoFilteredMatchMessage2(inflowChallenges, filters)
12385
+ });
12386
+ return;
12387
+ }
12388
+ const realm = filtered[0]?.realm ?? "";
12389
+ emit({
12390
+ type: "challenges",
12391
+ result: {
12392
+ outcome: "challenges",
12393
+ url: deps.url,
12394
+ method: deps.probeOptions.method,
12395
+ realm,
12396
+ challenges: filtered.map(summarizeChallenge)
12397
+ }
12398
+ });
12399
+ }
12400
+ function reduceMppPay(state, event) {
12401
+ switch (event.type) {
12402
+ case "decoded":
12403
+ return { kind: "decoded", challenge: event.challenge };
12404
+ case "created":
12405
+ return { kind: "created", created: event.created };
12406
+ case "replayed":
12407
+ return { kind: "success", result: event.result };
12408
+ case "rejected":
12409
+ return { kind: "seller-rejected", result: event.result };
12410
+ case "short-circuited":
12411
+ return { kind: "no-payment-final", result: event.result };
12412
+ case "errored":
12413
+ return { kind: "error", code: event.code, message: event.message };
12414
+ default:
12415
+ return state;
12416
+ }
12417
+ }
12418
+ function mapMppError(err) {
12419
+ return userFacingApiError(err, "PAYMENT_FAILED");
12420
+ }
12421
+ function buildSettlement(headers) {
12422
+ const raw = readHeader3(headers, HEADERS4.PAYMENT_RECEIPT);
12423
+ if (raw === void 0) return void 0;
12424
+ let receipt;
12425
+ try {
12426
+ receipt = decodeReceipt2(raw);
12427
+ } catch {
12428
+ return void 0;
12429
+ }
12430
+ const out = {};
12431
+ if (receipt.reference !== "") out.reference = receipt.reference;
12432
+ if (receipt.settlement.amount !== "") out.amount = receipt.settlement.amount;
12433
+ if (receipt.settlement.currency !== "") out.currency = receipt.settlement.currency;
12434
+ if (receipt.status !== "") out.status = receipt.status;
12435
+ if (receipt.timestamp !== "") out.timestamp = receipt.timestamp;
12436
+ return Object.keys(out).length > 0 ? out : void 0;
12437
+ }
12438
+ async function resolveTransaction(client, transactionId, deps) {
12439
+ const generator = pollAsync({
12440
+ fn: () => client.getTransaction(transactionId),
12441
+ isTerminal: (response) => response.state !== "pending",
12442
+ isEqual: (a, b) => a.state === b.state,
12443
+ // A 0 interval reaches here only on the TTY path (the agent path gates inline polling on interval > 0); fall back
12444
+ // to a 5s cadence so the poll loop doesn't spin.
12445
+ interval: deps.interval > 0 ? deps.interval : 5,
12446
+ maxAttempts: deps.maxAttempts,
12447
+ timeout: deps.timeout,
12448
+ ...deps.signal !== void 0 ? { signal: deps.signal } : {}
12449
+ });
12450
+ for await (const outcome of generator) {
12451
+ if (!outcome.terminal) continue;
12452
+ if (outcome.reason !== void 0) return { timedOut: true, latest: outcome.value };
12453
+ return { response: outcome.value };
12454
+ }
12455
+ return { timedOut: true };
12456
+ }
12457
+ async function runMppPayPipeline(deps, emit) {
12458
+ try {
12459
+ const probe = await sellerProbe4(deps.url, deps.probeOptions);
12460
+ if (probe.status !== 402) {
12461
+ if (!isSuccessStatus2(probe.status)) {
12462
+ emit({
12463
+ type: "errored",
12464
+ code: UNEXPECTED_PROBE_STATUS_CODE2,
12465
+ message: `Seller returned status ${String(probe.status)} during probe; expected 2xx (no payment) or 402 (payment required).`
12466
+ });
12467
+ return;
12468
+ }
12469
+ const attachment2 = await buildBodyAttachment(probe.bytes, deps.showBody, deps.outputFile);
12470
+ emit({
12471
+ type: "short-circuited",
12472
+ result: {
12473
+ outcome: "no-payment-required",
12474
+ url: deps.url,
12475
+ method: deps.probeOptions.method,
12476
+ status: probe.status,
12477
+ contentType: probe.contentType,
12478
+ ...attachment2
12479
+ }
12480
+ });
12481
+ return;
12482
+ }
12483
+ const headerValues = readHeaderAll2(probe.headers, HEADERS4.WWW_AUTHENTICATE);
12484
+ if (headerValues.length === 0) {
12485
+ emit({
12486
+ type: "errored",
12487
+ code: INVALID_402_CODE2,
12488
+ message: "Seller returned 402 but did not include a WWW-Authenticate: Payment header."
12489
+ });
12490
+ return;
12491
+ }
12492
+ let challenges;
12493
+ try {
12494
+ challenges = parseChallengeHeaders2(headerValues);
12495
+ } catch (err) {
12496
+ emit({ type: "errored", code: "DECODE_FAILED", message: err instanceof Error ? err.message : String(err) });
12497
+ return;
12498
+ }
12499
+ const inflowChallenges = filterInflowChallenges(challenges);
12500
+ if (inflowChallenges.length === 0) {
12501
+ emit({ type: "errored", code: NO_INFLOW_MATCH_CODE2, message: NO_INFLOW_MATCH_MESSAGE2 });
12502
+ return;
12503
+ }
12504
+ const filters = {
12505
+ ...deps.paymentMethodFilter !== void 0 ? { paymentMethod: deps.paymentMethodFilter } : {},
12506
+ ...deps.intentFilter !== void 0 ? { intent: deps.intentFilter } : {},
12507
+ ...deps.currencyFilter !== void 0 ? { currency: deps.currencyFilter } : {},
12508
+ ...deps.railFilter !== void 0 ? { rail: deps.railFilter } : {}
12509
+ };
12510
+ const selected = filterChallenges(inflowChallenges, filters);
12511
+ if (hasAnyChallengeFilter(filters) && selected.length === 0) {
12512
+ emit({
12513
+ type: "errored",
12514
+ code: NO_FILTERED_MATCH_CODE2,
12515
+ message: buildNoFilteredMatchMessage2(inflowChallenges, filters)
12516
+ });
12517
+ return;
12518
+ }
12519
+ const challenge = selected[0];
12520
+ emit({ type: "decoded", challenge: summarizeChallenge(challenge) });
12521
+ const options = deps.instrumentId !== void 0 ? { instrumentId: deps.instrumentId } : {};
12522
+ let created;
12523
+ try {
12524
+ created = await deps.client.createTransaction({ challenge, options });
12525
+ } catch (err) {
12526
+ const mapped = mapMppError(err);
12527
+ emit({ type: "errored", code: mapped.code, message: mapped.message });
12528
+ return;
12529
+ }
12530
+ const createdFrame = {
12531
+ transactionId: created.transactionId ?? "",
12532
+ state: created.state,
12533
+ challenge: summarizeChallenge(challenge),
12534
+ ...created.approvalId !== void 0 ? { approvalId: created.approvalId } : {},
12535
+ ...created.approvalId !== void 0 ? { approvalUrl: approvalUrlFor(deps.apiBaseUrl, created.approvalId) } : {},
12536
+ ...created.retryAfterSeconds !== void 0 ? { retryAfterSeconds: created.retryAfterSeconds } : {},
12537
+ ...created.expires !== void 0 ? { expires: created.expires } : {}
12538
+ };
12539
+ emit({ type: "created", created: createdFrame });
12540
+ let resolved = created;
12541
+ if (created.state === "pending") {
12542
+ if (deps.awaitPayment === false) return;
12543
+ if (createdFrame.transactionId === "") {
12544
+ emit({
12545
+ type: "errored",
12546
+ code: "PAYMENT_FAILED",
12547
+ message: "Pending transaction carried no transactionId to poll."
12548
+ });
12549
+ return;
12550
+ }
12551
+ const outcome = await resolveTransaction(deps.client, createdFrame.transactionId, deps);
12552
+ if ("timedOut" in outcome) {
12553
+ emit({
12554
+ type: "errored",
12555
+ code: "POLLING_TIMEOUT",
12556
+ message: "Polling timed out before the transaction reached a ready state."
12557
+ });
12558
+ return;
12559
+ }
12560
+ resolved = outcome.response;
12561
+ }
12562
+ if (resolved.state === "failed") {
12563
+ emit({
12564
+ type: "errored",
12565
+ code: "PAYMENT_FAILED",
12566
+ message: resolved.problem?.detail ?? resolved.problem?.title ?? "MPP transaction failed."
12567
+ });
12568
+ return;
12569
+ }
12570
+ if (resolved.state === "expired") {
12571
+ emit({ type: "errored", code: "PAYMENT_EXPIRED", message: "MPP transaction expired before it was ready." });
12572
+ return;
12573
+ }
12574
+ if (resolved.state !== "ready" || resolved.credential === void 0) {
12575
+ emit({
12576
+ type: "errored",
12577
+ code: "PAYMENT_FAILED",
12578
+ message: "Transaction reached a ready state without a credential."
12579
+ });
12580
+ return;
12581
+ }
12582
+ const credential = resolved.credential;
12583
+ const replay = await sellerProbe4(deps.url, {
12584
+ method: deps.probeOptions.method,
12585
+ headers: { ...deps.probeOptions.headers, [HEADERS4.AUTHORIZATION]: `${SCHEME_PAYMENT} ${credential}` },
12586
+ ...deps.probeOptions.data !== void 0 ? { data: deps.probeOptions.data } : {}
12587
+ });
12588
+ const attachment = await buildBodyAttachment(replay.bytes, deps.showBody, deps.outputFile);
12589
+ if (!isSuccessStatus2(replay.status)) {
12590
+ emit({
12591
+ type: "rejected",
12592
+ result: {
12593
+ outcome: "seller-rejected",
12594
+ url: deps.url,
12595
+ method: deps.probeOptions.method,
12596
+ transactionId: createdFrame.transactionId,
12597
+ challengeId: challenge.id,
12598
+ responseStatus: replay.status,
12599
+ responseContentType: replay.contentType,
12600
+ ...attachment
12601
+ }
12602
+ });
12603
+ return;
12604
+ }
12605
+ const settled = buildSettlement(replay.headers);
12606
+ emit({
12607
+ type: "replayed",
12608
+ result: {
12609
+ outcome: "paid",
12610
+ url: deps.url,
12611
+ method: deps.probeOptions.method,
12612
+ transactionId: createdFrame.transactionId,
12613
+ challengeId: challenge.id,
12614
+ intent: challenge.intent,
12615
+ credential,
12616
+ responseStatus: replay.status,
12617
+ responseContentType: replay.contentType,
12618
+ ...settled !== void 0 ? { settled } : {},
12619
+ ...attachment
12620
+ }
12621
+ });
12622
+ } catch (err) {
12623
+ const mapped = mapMppError(err);
12624
+ emit({ type: "errored", code: mapped.code, message: mapped.message });
12625
+ }
12626
+ }
12627
+ var TERMINAL_STATES = /* @__PURE__ */ new Set(["expired", "failed", "ready"]);
12628
+ function reduceMppStatus(state, event) {
12629
+ switch (event.type) {
12630
+ case "snapshot":
12631
+ return { kind: "polling", latest: event.response };
12632
+ case "ready":
12633
+ return { kind: "ready", response: event.response };
12634
+ case "failed":
12635
+ return { kind: "failed", response: event.response };
12636
+ case "expired":
12637
+ return { kind: "expired", response: event.response };
12638
+ case "timedOut":
12639
+ return event.response !== void 0 ? { kind: "timeout", response: event.response } : { kind: "timeout" };
12640
+ case "crashed":
12641
+ return { kind: "error", message: event.message };
12642
+ default:
12643
+ return state;
12644
+ }
12645
+ }
12646
+ function runMppStatus(input) {
12647
+ async function* generate() {
12648
+ try {
12649
+ const generator = pollAsync({
12650
+ fn: input.fetchOnce,
12651
+ isTerminal: (response) => TERMINAL_STATES.has(response.state),
12652
+ isEqual: (a, b) => a.state === b.state && a.credential !== void 0 === (b.credential !== void 0),
12653
+ interval: input.interval,
12654
+ maxAttempts: input.maxAttempts,
12655
+ timeout: input.timeout
12656
+ });
12657
+ for await (const outcome of generator) {
12658
+ if (!outcome.terminal) {
12659
+ yield { type: "snapshot", response: outcome.value };
12660
+ continue;
12661
+ }
12662
+ if (outcome.reason !== void 0) {
12663
+ yield { type: "timedOut", response: outcome.value };
12664
+ return;
12665
+ }
12666
+ const state = outcome.value.state;
12667
+ if (state === "ready") {
12668
+ yield { type: "ready", response: outcome.value };
12669
+ return;
12670
+ }
12671
+ if (state === "expired") {
12672
+ yield { type: "expired", response: outcome.value };
12673
+ return;
12674
+ }
12675
+ yield { type: "failed", response: outcome.value };
12676
+ return;
12677
+ }
12678
+ } catch (err) {
12679
+ yield { type: "crashed", message: userFacingErrorMessage(err) };
12680
+ }
12681
+ }
12682
+ return { events: generate() };
12683
+ }
12684
+ async function runMppSupported(input) {
12685
+ const client = await input.mpp.client();
12686
+ return client.getSupported();
12687
+ }
12171
12688
  function wrapEmittingPipeline(run) {
12172
12689
  const buffer = [];
12173
12690
  let started = false;
@@ -12308,16 +12825,43 @@ function augmentX402(x402Resource, resolvedApiBaseUrl2) {
12308
12825
  augmented.cancel = async (input) => runX402Cancel({ x402: x402Resource, approvalId: input.approvalId });
12309
12826
  return augmented;
12310
12827
  }
12311
- var DEFAULT_RETRIES = 3;
12312
- var DEFAULT_TIMEOUT_MS = 3e4;
12313
- var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
12314
- var SDK_USER_AGENT = `@inflowpayai/inflow-core/${true ? "0.5.0" : "0.0.0"}`;
12315
- function sleep2(ms, signal) {
12316
- return new Promise((resolve, reject) => {
12317
- const onAbort = () => {
12318
- clearTimeout(handle);
12319
- reject(new InflowTransportError("Request aborted."));
12320
- };
12828
+ function augmentMpp(mppResource, resolvedApiBaseUrl2) {
12829
+ const augmented = mppResource;
12830
+ augmented.inspect = (input) => wrapEmittingPipeline((emit) => runMppInspectPipeline(input, emit));
12831
+ augmented.supported = async () => runMppSupported({ mpp: mppResource });
12832
+ augmented.pay = (input) => wrapEmittingPipeline(async (emit) => {
12833
+ const client = await mppResource.client();
12834
+ return runMppPayPipeline(
12835
+ {
12836
+ ...input,
12837
+ client,
12838
+ apiBaseUrl: input.apiBaseUrl ?? resolvedApiBaseUrl2
12839
+ },
12840
+ emit
12841
+ );
12842
+ });
12843
+ augmented.status = (input) => runMppStatus({
12844
+ fetchOnce: async () => {
12845
+ const client = await mppResource.client();
12846
+ return client.getTransaction(input.transactionId);
12847
+ },
12848
+ interval: input.interval,
12849
+ maxAttempts: input.maxAttempts,
12850
+ timeout: input.timeout
12851
+ });
12852
+ augmented.cancel = async (input) => runMppCancel({ mpp: mppResource, approvalId: input.approvalId });
12853
+ return augmented;
12854
+ }
12855
+ var DEFAULT_RETRIES = 3;
12856
+ var DEFAULT_TIMEOUT_MS = 3e4;
12857
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
12858
+ var SDK_USER_AGENT = `@inflowpayai/inflow-core/${true ? "0.6.0" : "0.0.0"}`;
12859
+ function sleep2(ms, signal) {
12860
+ return new Promise((resolve, reject) => {
12861
+ const onAbort = () => {
12862
+ clearTimeout(handle);
12863
+ reject(new InflowTransportError("Request aborted."));
12864
+ };
12321
12865
  if (signal?.aborted) {
12322
12866
  reject(new InflowTransportError("Request aborted."));
12323
12867
  return;
@@ -12827,12 +13371,33 @@ var X402Resource = class {
12827
13371
  return this.cached;
12828
13372
  }
12829
13373
  };
13374
+ var MppResource = class {
13375
+ constructor(opts) {
13376
+ this.opts = opts;
13377
+ }
13378
+ opts;
13379
+ cachedClient;
13380
+ cachedMethod;
13381
+ client() {
13382
+ if (!this.cachedClient) {
13383
+ this.cachedClient = Promise.resolve(new MppClient(this.opts));
13384
+ }
13385
+ return this.cachedClient;
13386
+ }
13387
+ cancelApproval(approvalId) {
13388
+ if (!this.cachedMethod) {
13389
+ this.cachedMethod = createInflowMppMethod(this.opts);
13390
+ }
13391
+ return this.cachedMethod.cancelApproval(approvalId);
13392
+ }
13393
+ };
12830
13394
  var Inflow = class {
12831
13395
  auth;
12832
13396
  balances;
12833
13397
  depositAddresses;
12834
13398
  user;
12835
13399
  x402;
13400
+ mpp;
12836
13401
  /**
12837
13402
  * The effective API base URL this client will hit, after resolution against `options.apiBaseUrl`, `INFLOW_BASE_URL`,
12838
13403
  * and the environment-derived default. Exposed for callers (CLI, MCP transports, etc.) that need to display "what URL
@@ -12855,6 +13420,8 @@ var Inflow = class {
12855
13420
  this.user = augmentUser(rawUser);
12856
13421
  const x402Internal = new X402Resource(this.resolveX402Options(options, dataOptions));
12857
13422
  this.x402 = augmentX402(x402Internal, this.resolvedApiBaseUrl);
13423
+ const mppInternal = new MppResource(this.resolveMppOptions(options, dataOptions));
13424
+ this.mpp = augmentMpp(mppInternal, this.resolvedApiBaseUrl);
12858
13425
  this.auth = augmentAuth(rawAuth, rawUser, options.authStorage);
12859
13426
  }
12860
13427
  /**
@@ -12891,6 +13458,20 @@ var Inflow = class {
12891
13458
  }
12892
13459
  return connection;
12893
13460
  }
13461
+ resolveMppOptions(options, dataOptions) {
13462
+ const connection = {
13463
+ ...options.environment !== void 0 ? { environment: options.environment } : {},
13464
+ ...options.apiBaseUrl !== void 0 ? { baseUrl: options.apiBaseUrl } : {}
13465
+ };
13466
+ if (options.apiKey !== void 0 && options.apiKey.length > 0) {
13467
+ return { ...connection, apiKey: options.apiKey };
13468
+ }
13469
+ if (dataOptions.getAccessToken !== void 0) {
13470
+ const provider = dataOptions.getAccessToken;
13471
+ return { ...connection, getAccessToken: () => provider() };
13472
+ }
13473
+ return connection;
13474
+ }
12894
13475
  };
12895
13476
  async function probeSession(userResource, options) {
12896
13477
  const controller = new AbortController();
@@ -12925,13 +13506,60 @@ async function runDepositAddressesList(input) {
12925
13506
  }
12926
13507
 
12927
13508
  // src/cli.tsx
12928
- import { Cli as Cli6 } from "incur";
13509
+ import { Cli as Cli7 } from "incur";
12929
13510
 
12930
13511
  // src/commands/auth/index.tsx
12931
13512
  import { Cli } from "incur";
12932
- import { Text as Text6, useApp as useApp5 } from "ink";
13513
+ import { Text as Text6 } from "ink";
12933
13514
  import { useEffect as useEffect5, useState as useState2 } from "react";
12934
13515
 
13516
+ // src/hooks/use-flow-exit.ts
13517
+ import { useApp } from "ink";
13518
+ import { useCallback, useRef } from "react";
13519
+
13520
+ // src/utils/best-effort-cancel.ts
13521
+ var CANCEL_GRACE_MS = 1500;
13522
+ function runBestEffortCancel(cancel, done, graceMs = CANCEL_GRACE_MS) {
13523
+ let finished = false;
13524
+ const finish = () => {
13525
+ if (finished) return;
13526
+ finished = true;
13527
+ done();
13528
+ };
13529
+ const timer = setTimeout(finish, graceMs);
13530
+ void Promise.resolve(cancel?.()).catch(() => void 0).finally(() => {
13531
+ clearTimeout(timer);
13532
+ finish();
13533
+ });
13534
+ }
13535
+
13536
+ // src/hooks/use-flow-exit.ts
13537
+ function useFlowExit(onComplete) {
13538
+ const { exit } = useApp();
13539
+ const onCompleteRef = useRef(onComplete);
13540
+ onCompleteRef.current = onComplete;
13541
+ const settledRef = useRef(false);
13542
+ const cancelStartedRef = useRef(false);
13543
+ const finish = useCallback(
13544
+ (...args) => {
13545
+ if (settledRef.current) return;
13546
+ settledRef.current = true;
13547
+ onCompleteRef.current(...args);
13548
+ exit();
13549
+ },
13550
+ [exit]
13551
+ );
13552
+ const cancelThenFinish = useCallback(
13553
+ (cancel, ...args) => {
13554
+ if (cancelStartedRef.current || settledRef.current) return;
13555
+ cancelStartedRef.current = true;
13556
+ runBestEffortCancel(cancel, () => finish(...args));
13557
+ },
13558
+ [finish]
13559
+ );
13560
+ return { finish, cancelThenFinish };
13561
+ }
13562
+
12935
13563
  // src/utils/render-ink-until-exit.tsx
12936
13564
  import { render } from "ink";
12937
13565
  async function renderInkUntilExit(element, resolveResult) {
@@ -12944,7 +13572,7 @@ async function renderInkUntilExit(element, resolveResult) {
12944
13572
  var NPM_INSTALL_COMMAND = "npm install -g @inflowpayai/inflow";
12945
13573
 
12946
13574
  // src/commands/auth/login.tsx
12947
- import { Box, Text, useApp, useInput } from "ink";
13575
+ import { Box, Text, useInput } from "ink";
12948
13576
  import Spinner from "ink-spinner";
12949
13577
  import { useEffect, useReducer } from "react";
12950
13578
 
@@ -12985,7 +13613,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
12985
13613
  var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) => {
12986
13614
  const initialPhase = { kind: "init" };
12987
13615
  const [phase, dispatch] = useReducer(reduceAuthLogin, initialPhase);
12988
- const { exit } = useApp();
13616
+ const { finish } = useFlowExit(onComplete);
12989
13617
  useInput(
12990
13618
  (_input, key) => {
12991
13619
  if (key.return && phase.kind === "awaiting") {
@@ -13014,10 +13642,9 @@ var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) =>
13014
13642
  }, [auth, clientName, connection, priorRefreshToken]);
13015
13643
  useEffect(() => {
13016
13644
  if (phase.kind === "success" || phase.kind === "expired" || phase.kind === "denied" || phase.kind === "failed") {
13017
- onComplete();
13018
- exit();
13645
+ finish();
13019
13646
  }
13020
- }, [phase, onComplete, exit]);
13647
+ }, [phase, finish]);
13021
13648
  if (phase.kind === "init") {
13022
13649
  return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
13023
13650
  /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
@@ -13069,14 +13696,14 @@ var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) =>
13069
13696
  };
13070
13697
 
13071
13698
  // src/commands/auth/login-api-key.tsx
13072
- import { Box as Box2, Text as Text2, useApp as useApp2 } from "ink";
13699
+ import { Box as Box2, Text as Text2 } from "ink";
13073
13700
  import Spinner2 from "ink-spinner";
13074
13701
  import { useEffect as useEffect2, useReducer as useReducer2 } from "react";
13075
13702
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
13076
13703
  var LoginApiKey = ({ apiKey: apiKey2, auth, connection, onComplete }) => {
13077
13704
  const initial = { kind: "validating" };
13078
13705
  const [phase, dispatch] = useReducer2(reduceAuthLoginApiKey, initial);
13079
- const { exit } = useApp2();
13706
+ const { finish } = useFlowExit(onComplete);
13080
13707
  useEffect2(() => {
13081
13708
  const run = auth.loginApiKey({ apiKey: apiKey2, connection });
13082
13709
  let cancelled = false;
@@ -13092,9 +13719,8 @@ var LoginApiKey = ({ apiKey: apiKey2, auth, connection, onComplete }) => {
13092
13719
  }, [apiKey2, auth, connection]);
13093
13720
  useEffect2(() => {
13094
13721
  if (phase.kind === "validating") return;
13095
- onComplete();
13096
- exit();
13097
- }, [phase, onComplete, exit]);
13722
+ finish();
13723
+ }, [phase, finish]);
13098
13724
  if (phase.kind === "validating") {
13099
13725
  return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
13100
13726
  /* @__PURE__ */ jsx2(Spinner2, { type: "dots" }),
@@ -13136,19 +13762,19 @@ var LoginPrompt = ({ userDisplay, onAccept, onReject }) => {
13136
13762
  };
13137
13763
 
13138
13764
  // src/commands/auth/logout.tsx
13139
- import { Box as Box4, Text as Text4, useApp as useApp3 } from "ink";
13765
+ import { Box as Box4, Text as Text4 } from "ink";
13140
13766
  import Spinner3 from "ink-spinner";
13141
- import { useCallback } from "react";
13767
+ import { useCallback as useCallback2 } from "react";
13142
13768
 
13143
13769
  // src/hooks/use-flow-state.ts
13144
- import { useEffect as useEffect3, useRef, useState } from "react";
13770
+ import { useEffect as useEffect3, useRef as useRef2, useState } from "react";
13145
13771
  function useFlowState(action, onComplete) {
13146
13772
  const [status, setStatus] = useState("loading");
13147
13773
  const [data, setData] = useState(null);
13148
13774
  const [error, setError] = useState("");
13149
- const onCompleteRef = useRef(onComplete);
13775
+ const onCompleteRef = useRef2(onComplete);
13150
13776
  onCompleteRef.current = onComplete;
13151
- const completedRef = useRef(false);
13777
+ const completedRef = useRef2(false);
13152
13778
  useEffect3(() => {
13153
13779
  let cancelled = false;
13154
13780
  const run = async () => {
@@ -13184,13 +13810,9 @@ function useFlowState(action, onComplete) {
13184
13810
  // src/commands/auth/logout.tsx
13185
13811
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
13186
13812
  var Logout = ({ auth, onComplete }) => {
13187
- const { exit } = useApp3();
13188
- const action = useCallback(() => auth.logout(), [auth]);
13189
- const handleComplete = useCallback(() => {
13190
- onComplete();
13191
- exit();
13192
- }, [onComplete, exit]);
13193
- const { status } = useFlowState(action, handleComplete);
13813
+ const action = useCallback2(() => auth.logout(), [auth]);
13814
+ const { finish } = useFlowExit(onComplete);
13815
+ const { status } = useFlowState(action, finish);
13194
13816
  if (status === "loading") {
13195
13817
  return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: "cyan", children: [
13196
13818
  /* @__PURE__ */ jsx4(Spinner3, { type: "dots" }),
@@ -13220,7 +13842,7 @@ var statusOptions = z.object({
13220
13842
  });
13221
13843
 
13222
13844
  // src/commands/auth/status.tsx
13223
- import { Box as Box5, Text as Text5, useApp as useApp4 } from "ink";
13845
+ import { Box as Box5, Text as Text5 } from "ink";
13224
13846
  import Spinner4 from "ink-spinner";
13225
13847
  import { useEffect as useEffect4, useReducer as useReducer3 } from "react";
13226
13848
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -13265,7 +13887,7 @@ var AuthStatus = ({
13265
13887
  }) => {
13266
13888
  const initial = { kind: "loading" };
13267
13889
  const [view, dispatch] = useReducer3(reduce, initial);
13268
- const { exit } = useApp4();
13890
+ const { finish } = useFlowExit(onComplete);
13269
13891
  useEffect4(() => {
13270
13892
  let cancelled = false;
13271
13893
  const options = composeOptions({ apiKey: apiKey2, displayConnection: displayConnection2, verbose: verbose2 });
@@ -13285,9 +13907,8 @@ var AuthStatus = ({
13285
13907
  }, [auth, probe, apiKey2, displayConnection2, verbose2]);
13286
13908
  useEffect4(() => {
13287
13909
  if (view.kind === "loading" || view.kind === "probing") return;
13288
- onComplete();
13289
- exit();
13290
- }, [view, onComplete, exit]);
13910
+ finish();
13911
+ }, [view, finish]);
13291
13912
  if (view.kind === "loading") return null;
13292
13913
  if (view.kind === "probing") {
13293
13914
  return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
@@ -13422,7 +14043,7 @@ var InteractiveLoginShell = ({
13422
14043
  }) => {
13423
14044
  const initial = { kind: "probing" };
13424
14045
  const [stage, setStage] = useState2(initial);
13425
- const { exit } = useApp5();
14046
+ const { finish } = useFlowExit(onComplete);
13426
14047
  useEffect5(() => {
13427
14048
  let cancelled = false;
13428
14049
  const auth = authStorage2.getAuth();
@@ -13451,9 +14072,8 @@ var InteractiveLoginShell = ({
13451
14072
  }, [authStorage2, userResource]);
13452
14073
  useEffect5(() => {
13453
14074
  if (stage.kind !== "declined") return;
13454
- onComplete();
13455
- exit();
13456
- }, [stage, onComplete, exit]);
14075
+ finish();
14076
+ }, [stage, finish]);
13457
14077
  if (stage.kind === "probing") return null;
13458
14078
  if (stage.kind === "prompt") {
13459
14079
  return /* @__PURE__ */ jsx6(
@@ -13741,9 +14361,9 @@ function assertSessionGuard(c, storage2, inflow2) {
13741
14361
  }
13742
14362
 
13743
14363
  // src/commands/balances/list.tsx
13744
- import { Box as Box7, Text as Text8, useApp as useApp6 } from "ink";
14364
+ import { Box as Box7, Text as Text8 } from "ink";
13745
14365
  import Spinner5 from "ink-spinner";
13746
- import { useCallback as useCallback2 } from "react";
14366
+ import { useCallback as useCallback3 } from "react";
13747
14367
 
13748
14368
  // src/utils/table.tsx
13749
14369
  import { Box as Box6, Text as Text7 } from "ink";
@@ -13814,16 +14434,9 @@ var COLUMNS = [
13814
14434
  { header: "Available", cell: (b) => b.available }
13815
14435
  ];
13816
14436
  var BalancesList = ({ balanceResource, onComplete }) => {
13817
- const { exit } = useApp6();
13818
- const action = useCallback2(() => balanceResource.list(), [balanceResource]);
13819
- const handleLinger = useCallback2(
13820
- (result) => {
13821
- onComplete(result);
13822
- exit();
13823
- },
13824
- [onComplete, exit]
13825
- );
13826
- const { status, data: balances, error } = useFlowState(action, handleLinger);
14437
+ const action = useCallback3(() => balanceResource.list(), [balanceResource]);
14438
+ const { finish } = useFlowExit(onComplete);
14439
+ const { status, data: balances, error } = useFlowState(action, finish);
13827
14440
  if (status === "loading") {
13828
14441
  return /* @__PURE__ */ jsx8(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: "cyan", children: [
13829
14442
  /* @__PURE__ */ jsx8(Spinner5, { type: "dots" }),
@@ -13887,9 +14500,9 @@ import { Cli as Cli3 } from "incur";
13887
14500
  import "react";
13888
14501
 
13889
14502
  // src/commands/deposit-addresses/list.tsx
13890
- import { Box as Box8, Text as Text9, useApp as useApp7 } from "ink";
14503
+ import { Box as Box8, Text as Text9 } from "ink";
13891
14504
  import Spinner6 from "ink-spinner";
13892
- import { useCallback as useCallback3 } from "react";
14505
+ import { useCallback as useCallback4 } from "react";
13893
14506
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
13894
14507
  var COLUMNS2 = [
13895
14508
  { header: "Blockchain", cell: (a) => a.blockchain },
@@ -13897,16 +14510,9 @@ var COLUMNS2 = [
13897
14510
  { header: "Currencies", cell: (a) => a.currencies.join(", ") }
13898
14511
  ];
13899
14512
  var DepositAddressesList = ({ depositAddressResource, onComplete }) => {
13900
- const { exit } = useApp7();
13901
- const action = useCallback3(() => runDepositAddressesList({ depositAddressResource }), [depositAddressResource]);
13902
- const handleLinger = useCallback3(
13903
- (result) => {
13904
- onComplete(result);
13905
- exit();
13906
- },
13907
- [onComplete, exit]
13908
- );
13909
- const { status, data: addresses, error } = useFlowState(action, handleLinger);
14513
+ const action = useCallback4(() => runDepositAddressesList({ depositAddressResource }), [depositAddressResource]);
14514
+ const { finish } = useFlowExit(onComplete);
14515
+ const { status, data: addresses, error } = useFlowState(action, finish);
13910
14516
  if (status === "loading") {
13911
14517
  return /* @__PURE__ */ jsx10(Box8, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
13912
14518
  /* @__PURE__ */ jsx10(Spinner6, { type: "dots" }),
@@ -13971,204 +14577,113 @@ function createDepositAddressesCli(depositAddressResource, authStorage2, inflow2
13971
14577
  return cli2;
13972
14578
  }
13973
14579
 
13974
- // src/commands/user/index.tsx
13975
- import { Cli as Cli4, Errors as Errors2 } from "incur";
13976
- import "react";
13977
-
13978
- // src/commands/user/get.tsx
13979
- import { Box as Box9, Text as Text10, useApp as useApp8 } from "ink";
13980
- import Spinner7 from "ink-spinner";
13981
- import { useCallback as useCallback4, useRef as useRef2 } from "react";
13982
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
13983
- var UserGet = ({ userResource, onComplete }) => {
13984
- const { exit } = useApp8();
13985
- const action = useCallback4(() => userResource.retrieve(), [userResource]);
13986
- const lastErrorRef = useRef2("");
13987
- const handleLinger = useCallback4(
13988
- (result) => {
13989
- if (result !== null) {
13990
- onComplete({ kind: "success", user: result });
13991
- } else {
13992
- onComplete({
13993
- kind: "error",
13994
- message: lastErrorRef.current || "No result produced."
13995
- });
13996
- }
13997
- exit();
13998
- },
13999
- [onComplete, exit]
14000
- );
14001
- const { status, data: user, error } = useFlowState(action, handleLinger);
14002
- lastErrorRef.current = error;
14003
- if (status === "loading") {
14004
- return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
14005
- /* @__PURE__ */ jsx12(Spinner7, { type: "dots" }),
14006
- " Loading user..."
14007
- ] }) });
14008
- }
14009
- if (status === "error") {
14010
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
14011
- /* @__PURE__ */ jsx12(Text10, { color: "red", children: "\u2717 Failed to retrieve user" }),
14012
- /* @__PURE__ */ jsx12(Text10, { color: "red", children: error })
14013
- ] });
14014
- }
14015
- if (user === null) return null;
14016
- const rows = buildProfileRows(user);
14017
- return /* @__PURE__ */ jsx12(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: rows.map((row) => /* @__PURE__ */ jsxs9(Text10, { children: [
14018
- `${row.label}: `,
14019
- row.value !== null ? /* @__PURE__ */ jsx12(Text10, { bold: true, children: row.value }) : /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "\u2014" })
14020
- ] }, row.label)) });
14021
- };
14022
-
14023
- // src/commands/user/schema.ts
14024
- import { z as z4 } from "incur";
14025
- var getOptions = z4.object({});
14026
-
14027
- // src/commands/user/index.tsx
14028
- import { jsx as jsx13 } from "react/jsx-runtime";
14029
- var TTY_NO_RESULT_MESSAGE = "inflow user get exited without producing a result.";
14030
- async function runUserGet2(c, deps) {
14031
- assertSessionGuard(c, deps.authStorage, deps.inflow);
14032
- if (!c.agent && !c.formatExplicit) {
14033
- let captured = null;
14034
- const outcome = await renderInkUntilExit(
14035
- /* @__PURE__ */ jsx13(
14036
- UserGet,
14037
- {
14038
- userResource: deps.user,
14039
- onComplete: (value) => {
14040
- captured = value;
14041
- }
14042
- }
14043
- ),
14044
- () => captured
14045
- );
14046
- if (outcome === null) {
14047
- throw new Errors2.IncurError({
14048
- code: "USER_GET_FAILED",
14049
- message: TTY_NO_RESULT_MESSAGE
14050
- });
14051
- }
14052
- if (outcome.kind === "error") {
14053
- throw new Errors2.IncurError({
14054
- code: "USER_GET_FAILED",
14055
- message: outcome.message
14056
- });
14057
- }
14058
- return projectUserPayload(outcome.user);
14059
- }
14060
- return deps.user.get();
14061
- }
14062
- function createUserCli(user, authStorage2, inflow2) {
14063
- const cli2 = Cli4.create("user", { description: "User profile commands" });
14064
- cli2.command("get", {
14065
- description: "Retrieve the current authenticated user",
14066
- options: getOptions,
14067
- outputPolicy: "agent-only",
14068
- async run(c) {
14069
- return runUserGet2(c, { user, authStorage: authStorage2, inflow: inflow2 });
14070
- }
14071
- });
14072
- return cli2;
14073
- }
14074
-
14075
- // src/commands/x402/index.tsx
14580
+ // src/commands/mpp/index.tsx
14076
14581
  import { chmodSync, writeFileSync as writeFileSync2 } from "fs";
14077
14582
  import { resolve as resolvePath2 } from "path";
14078
- import { Cli as Cli5 } from "incur";
14583
+ import { Cli as Cli4 } from "incur";
14079
14584
 
14080
- // src/commands/x402/cancel.tsx
14081
- import { Box as Box10, Text as Text11, useApp as useApp9 } from "ink";
14082
- import Spinner8 from "ink-spinner";
14585
+ // src/commands/mpp/cancel.tsx
14586
+ import { Box as Box9, Text as Text10 } from "ink";
14587
+ import Spinner7 from "ink-spinner";
14083
14588
  import { useCallback as useCallback5 } from "react";
14084
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
14589
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
14085
14590
  var CancelView = ({ approvalId, cancel, onComplete }) => {
14086
- const { exit } = useApp9();
14087
14591
  const action = useCallback5(() => cancel(), [cancel]);
14088
- const handleComplete = useCallback5(() => {
14089
- onComplete();
14090
- exit();
14091
- }, [onComplete, exit]);
14092
- const { status } = useFlowState(action, handleComplete);
14592
+ const { finish } = useFlowExit(onComplete);
14593
+ const { status } = useFlowState(action, finish);
14093
14594
  if (status === "loading") {
14094
- return /* @__PURE__ */ jsx14(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
14095
- /* @__PURE__ */ jsx14(Spinner8, { type: "dots" }),
14595
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
14596
+ /* @__PURE__ */ jsx12(Spinner7, { type: "dots" }),
14096
14597
  " Cancelling approval ",
14097
14598
  approvalId,
14098
14599
  "..."
14099
14600
  ] }) });
14100
14601
  }
14101
- return /* @__PURE__ */ jsx14(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: "green", children: [
14602
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
14102
14603
  "\u2713 Cancelled approval ",
14103
14604
  approvalId,
14104
14605
  " (best-effort)"
14105
14606
  ] }) });
14106
14607
  };
14107
14608
 
14108
- // src/commands/x402/decode.tsx
14109
- import { fromFoundationRequirements as fromFoundationRequirements2 } from "@inflowpayai/x402-buyer";
14110
- import { Box as Box11, Text as Text12 } from "ink";
14111
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
14112
- var DecodeView = ({ decoded }) => {
14113
- const summary = summarizeAccepts(fromFoundationRequirements2(decoded.accepts));
14114
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingY: 1, children: [
14115
- /* @__PURE__ */ jsx15(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text12, { bold: true, children: "Decoded PAYMENT-REQUIRED" }) }),
14116
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14117
- /* @__PURE__ */ jsxs11(Text12, { children: [
14118
- "x402Version: ",
14119
- /* @__PURE__ */ jsx15(Text12, { color: "cyan", children: String(decoded.x402Version) })
14120
- ] }),
14121
- /* @__PURE__ */ jsxs11(Text12, { children: [
14122
- "resource: ",
14123
- /* @__PURE__ */ jsx15(Text12, { color: "cyan", children: decoded.resource.url })
14124
- ] }),
14125
- /* @__PURE__ */ jsx15(Text12, { children: `accepts (${String(summary.length)}):` }),
14126
- summary.map((entry, idx) => /* @__PURE__ */ jsxs11(Text12, { children: [
14127
- " ",
14128
- /* @__PURE__ */ jsx15(Text12, { color: "yellow", children: entry.scheme }),
14609
+ // src/commands/mpp/decode.tsx
14610
+ import { Box as Box10, Text as Text11 } from "ink";
14611
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
14612
+ function ChallengeBody({ challenge }) {
14613
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14614
+ /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Challenge" }),
14615
+ /* @__PURE__ */ jsxs10(Text11, { children: [
14616
+ "method/intent: ",
14617
+ /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
14618
+ challenge.method,
14129
14619
  " / ",
14130
- /* @__PURE__ */ jsx15(Text12, { color: "yellow", children: entry.network }),
14131
- entry.amount !== void 0 ? ` \xB7 amount ${entry.amount}` : "",
14132
- entry.asset !== void 0 ? ` \xB7 ${entry.asset}` : ""
14133
- ] }, `${entry.scheme}-${entry.network}-${String(idx)}`)),
14134
- decoded.extensions !== void 0 ? /* @__PURE__ */ jsx15(Text12, { dimColor: true, children: `extensions: ${Object.keys(decoded.extensions).join(", ")}` }) : null
14620
+ challenge.intent
14621
+ ] })
14622
+ ] }),
14623
+ /* @__PURE__ */ jsx13(Text11, { children: `id: ${challenge.id}` }),
14624
+ /* @__PURE__ */ jsx13(Text11, { children: `realm: ${challenge.realm}` }),
14625
+ challenge.amount !== void 0 ? /* @__PURE__ */ jsx13(Text11, { children: `amount: ${challenge.amount}${challenge.currency !== void 0 ? ` ${challenge.currency}` : ""}` }) : null,
14626
+ challenge.rail !== void 0 ? /* @__PURE__ */ jsx13(Text11, { children: `rail: ${challenge.rail}` }) : null,
14627
+ challenge.expires !== void 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `expires: ${challenge.expires}` }) : null
14628
+ ] });
14629
+ }
14630
+ var DecodeView = ({ result }) => {
14631
+ if (result.kind === "challenge") {
14632
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14633
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded WWW-Authenticate: Payment" }) }),
14634
+ /* @__PURE__ */ jsx13(ChallengeBody, { challenge: result.challenge })
14635
+ ] });
14636
+ }
14637
+ if (result.kind === "credential") {
14638
+ const { credential } = result;
14639
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14640
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded Authorization: Payment credential" }) }),
14641
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14642
+ /* @__PURE__ */ jsx13(Text11, { children: `challenge id: ${credential.challenge.id}` }),
14643
+ /* @__PURE__ */ jsx13(Text11, { children: `method: ${credential.challenge.method}` }),
14644
+ /* @__PURE__ */ jsx13(Text11, { children: `source: ${credential.source}` }),
14645
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `payload keys: ${Object.keys(credential.payload).join(", ") || "(none)"}` })
14646
+ ] })
14647
+ ] });
14648
+ }
14649
+ const { receipt } = result;
14650
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14651
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded Payment-Receipt" }) }),
14652
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14653
+ /* @__PURE__ */ jsxs10(Text11, { children: [
14654
+ "status: ",
14655
+ /* @__PURE__ */ jsx13(Text11, { color: "green", children: receipt.status })
14656
+ ] }),
14657
+ /* @__PURE__ */ jsx13(Text11, { children: `reference: ${receipt.reference}` }),
14658
+ /* @__PURE__ */ jsx13(Text11, { children: `settled: ${receipt.settlement.amount} ${receipt.settlement.currency}` }),
14659
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `timestamp: ${receipt.timestamp}` })
14135
14660
  ] })
14136
14661
  ] });
14137
14662
  };
14138
14663
 
14139
- // src/commands/x402/inspect.tsx
14140
- import { Box as Box12, Text as Text13, useApp as useApp10 } from "ink";
14141
- import Spinner9 from "ink-spinner";
14664
+ // src/commands/mpp/inspect.tsx
14665
+ import { Box as Box11, Text as Text12 } from "ink";
14666
+ import Spinner8 from "ink-spinner";
14142
14667
  import { useEffect as useEffect6, useReducer as useReducer4 } from "react";
14143
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
14144
- function summarizeExtra(extra) {
14145
- if (extra === void 0) return "\u2014";
14146
- const keys = Object.keys(extra);
14147
- if (keys.length === 0) return "\u2014";
14148
- return [...keys].sort((a, b) => a.localeCompare(b)).join(", ");
14149
- }
14150
- function formatTimeout(seconds) {
14151
- return `${String(seconds)}s`;
14152
- }
14153
- function formatAsset(asset) {
14154
- return asset === "" ? "\u2014" : asset;
14668
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
14669
+ function orDash(value) {
14670
+ return value === void 0 || value === "" ? "\u2014" : value;
14155
14671
  }
14156
14672
  var COLUMNS3 = [
14157
- { header: "Scheme", cell: (r) => r.scheme },
14158
- { header: "Network", cell: (r) => r.network },
14159
- { header: "Amount", cell: (r) => r.amount },
14160
- { header: "Asset", cell: (r) => formatAsset(r.asset) },
14161
- { header: "Pay To", cell: (r) => r.payTo },
14162
- { header: "Timeout", cell: (r) => formatTimeout(r.maxTimeoutSeconds) },
14163
- { header: "Extra", cell: (r) => summarizeExtra(r.extra) }
14673
+ { header: "Method", cell: (c) => c.method },
14674
+ { header: "Intent", cell: (c) => c.intent },
14675
+ { header: "Amount", cell: (c) => orDash(c.amount) },
14676
+ { header: "Currency", cell: (c) => orDash(c.currency) },
14677
+ { header: "Rail", cell: (c) => orDash(c.rail) },
14678
+ { header: "Expires", cell: (c) => orDash(c.expires) }
14164
14679
  ];
14165
14680
  var InspectView = ({ url, method, deps, onComplete }) => {
14166
- const { exit } = useApp10();
14167
14681
  const initial = { kind: "probing" };
14168
- const [phase, dispatch] = useReducer4(reduceX402Inspect, initial);
14682
+ const [phase, dispatch] = useReducer4(reduceMppInspect, initial);
14683
+ const { finish } = useFlowExit(onComplete);
14169
14684
  useEffect6(() => {
14170
14685
  let cancelled = false;
14171
- void runInspectPipeline(deps, (event) => {
14686
+ void runMppInspectPipeline(deps, (event) => {
14172
14687
  if (!cancelled) dispatch(event);
14173
14688
  });
14174
14689
  return () => {
@@ -14176,14 +14691,13 @@ var InspectView = ({ url, method, deps, onComplete }) => {
14176
14691
  };
14177
14692
  }, [deps]);
14178
14693
  useEffect6(() => {
14179
- if (phase.kind === "accepts" || phase.kind === "no-payment" || phase.kind === "error") {
14180
- onComplete(phase);
14181
- exit();
14694
+ if (phase.kind === "challenges" || phase.kind === "no-payment" || phase.kind === "error") {
14695
+ finish(phase);
14182
14696
  }
14183
- }, [phase, onComplete, exit]);
14697
+ }, [phase, finish]);
14184
14698
  if (phase.kind === "probing") {
14185
- return /* @__PURE__ */ jsx16(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14186
- /* @__PURE__ */ jsx16(Spinner9, { type: "dots" }),
14699
+ return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
14700
+ /* @__PURE__ */ jsx14(Spinner8, { type: "dots" }),
14187
14701
  " Probing ",
14188
14702
  method,
14189
14703
  " ",
@@ -14193,63 +14707,64 @@ var InspectView = ({ url, method, deps, onComplete }) => {
14193
14707
  }
14194
14708
  if (phase.kind === "no-payment") {
14195
14709
  const { result: result2 } = phase;
14196
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14197
- /* @__PURE__ */ jsx16(Text13, { color: "green", children: "\u2713 Seller accepted without payment" }),
14198
- /* @__PURE__ */ jsx16(Text13, { children: `status: ${String(result2.status)}` }),
14199
- result2.contentType !== void 0 ? /* @__PURE__ */ jsx16(Text13, { children: `content-type: ${result2.contentType}` }) : null,
14200
- /* @__PURE__ */ jsx16(Text13, { children: `response size: ${String(result2.bodySizeBytes)} bytes` }),
14201
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "Use `x402 pay` to fetch the body." })
14710
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14711
+ /* @__PURE__ */ jsx14(Text12, { color: "green", children: "\u2713 Seller accepted without payment" }),
14712
+ /* @__PURE__ */ jsx14(Text12, { children: `status: ${String(result2.status)}` }),
14713
+ result2.contentType !== void 0 ? /* @__PURE__ */ jsx14(Text12, { children: `content-type: ${result2.contentType}` }) : null,
14714
+ /* @__PURE__ */ jsx14(Text12, { children: `response size: ${String(result2.bodySizeBytes)} bytes` }),
14715
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Use `mpp pay` to fetch the body." })
14202
14716
  ] });
14203
14717
  }
14204
14718
  if (phase.kind === "error") {
14205
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14206
- /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
14719
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14720
+ /* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
14207
14721
  "\u2717 ",
14208
14722
  phase.code
14209
14723
  ] }),
14210
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: phase.message })
14724
+ /* @__PURE__ */ jsx14(Text12, { color: "red", children: phase.message })
14211
14725
  ] });
14212
14726
  }
14213
14727
  const { result } = phase;
14214
- const acceptsCount = result.accepts.length;
14215
- const extensionsLine = result.extensions !== void 0 ? Object.keys(result.extensions).join(", ") : null;
14216
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14217
- /* @__PURE__ */ jsxs12(Text13, { children: [
14218
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: "PAYMENT-REQUIRED" }),
14728
+ const count = result.challenges.length;
14729
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14730
+ /* @__PURE__ */ jsxs11(Text12, { children: [
14731
+ /* @__PURE__ */ jsx14(Text12, { bold: true, children: "WWW-Authenticate: Payment" }),
14219
14732
  " for ",
14220
- /* @__PURE__ */ jsx16(Text13, { color: "cyan", children: result.resource }),
14733
+ /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: result.url }),
14221
14734
  " \xB7 ",
14222
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: `x402Version ${String(result.x402Version)}` }),
14735
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `realm ${result.realm}` }),
14223
14736
  " \xB7 ",
14224
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: `${String(acceptsCount)} accept${acceptsCount === 1 ? "" : "s"}` })
14737
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `${String(count)} challenge${count === 1 ? "" : "s"}` })
14225
14738
  ] }),
14226
- extensionsLine !== null ? /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: `extensions: ${extensionsLine}` }) : null,
14227
- /* @__PURE__ */ jsx16(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx16(Table, { columns: COLUMNS3, rows: result.accepts }) }),
14228
- /* @__PURE__ */ jsx16(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "Use --format json to inspect extras values." }) })
14739
+ /* @__PURE__ */ jsx14(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx14(Table, { columns: COLUMNS3, rows: [...result.challenges] }) }),
14740
+ /* @__PURE__ */ jsx14(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Use --format json to see challenge ids and digests." }) })
14229
14741
  ] });
14230
14742
  };
14231
- function buildAcceptsFrame(result) {
14232
- const frame = {
14233
- outcome: "accepts",
14743
+ function challengeToFrame(challenge) {
14744
+ const row = {
14745
+ id: challenge.id,
14746
+ realm: challenge.realm,
14747
+ method: challenge.method,
14748
+ intent: challenge.intent
14749
+ };
14750
+ if (challenge.amount !== void 0) row.amount = challenge.amount;
14751
+ if (challenge.currency !== void 0) row.currency = challenge.currency;
14752
+ if (challenge.recipient !== void 0) row.recipient = challenge.recipient;
14753
+ if (challenge.rail !== void 0) row.rail = challenge.rail;
14754
+ if (challenge.instrumentId !== void 0) row.instrument_id = challenge.instrumentId;
14755
+ if (challenge.expires !== void 0) row.expires = challenge.expires;
14756
+ if (challenge.description !== void 0) row.description = challenge.description;
14757
+ if (challenge.digest !== void 0) row.digest = challenge.digest;
14758
+ return row;
14759
+ }
14760
+ function buildChallengesFrame(result) {
14761
+ return {
14762
+ outcome: "challenges",
14234
14763
  url: result.url,
14235
14764
  method: result.method,
14236
- resource: result.resource,
14237
- x402_version: result.x402Version,
14238
- accepts: result.accepts.map((entry) => {
14239
- const row = {
14240
- scheme: entry.scheme,
14241
- network: entry.network,
14242
- amount: entry.amount,
14243
- asset: entry.asset,
14244
- pay_to: entry.payTo,
14245
- max_timeout_seconds: entry.maxTimeoutSeconds
14246
- };
14247
- if (entry.extra !== void 0) row.extra = entry.extra;
14248
- return row;
14249
- })
14765
+ realm: result.realm,
14766
+ challenges: result.challenges.map(challengeToFrame)
14250
14767
  };
14251
- if (result.extensions !== void 0) frame.extensions = result.extensions;
14252
- return frame;
14253
14768
  }
14254
14769
  function buildNoPaymentFrame(result) {
14255
14770
  const frame = {
@@ -14263,41 +14778,1136 @@ function buildNoPaymentFrame(result) {
14263
14778
  return frame;
14264
14779
  }
14265
14780
 
14266
- // src/commands/x402/pay.tsx
14267
- import { Box as Box13, Text as Text14, useApp as useApp11, useInput as useInput3 } from "ink";
14268
- import Spinner10 from "ink-spinner";
14269
- import { useEffect as useEffect7, useReducer as useReducer5 } from "react";
14270
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
14271
- var PayView = ({ url, method, deps, onComplete }) => {
14272
- const { exit } = useApp11();
14781
+ // src/commands/mpp/pay.tsx
14782
+ import { Box as Box12, Text as Text13, useInput as useInput3 } from "ink";
14783
+ import Spinner9 from "ink-spinner";
14784
+ import { useEffect as useEffect7, useReducer as useReducer5, useState as useState3 } from "react";
14785
+ import { Fragment, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
14786
+ var PayView = ({ url, method, deps, onComplete, onCancel }) => {
14273
14787
  const initial = { kind: "probing" };
14274
- const [phase, dispatch] = useReducer5(reducePay, initial);
14788
+ const [phase, dispatch] = useReducer5(reduceMppPay, initial);
14789
+ const [cancelling, setCancelling] = useState3(false);
14790
+ const { finish, cancelThenFinish } = useFlowExit(onComplete);
14791
+ const created = phase.kind === "created" ? phase.created : void 0;
14792
+ const approvalUrl = created?.approvalUrl;
14793
+ const approvalId = created?.approvalId;
14275
14794
  useInput3(
14276
14795
  (_input, key) => {
14277
- if (key.return && phase.kind === "awaiting-approval") {
14278
- openUrl(phase.approvalUrl);
14796
+ if (approvalUrl === void 0) return;
14797
+ if (key.return) {
14798
+ openUrl(approvalUrl);
14799
+ return;
14800
+ }
14801
+ if (key.escape && approvalId !== void 0) {
14802
+ setCancelling(true);
14803
+ cancelThenFinish(() => onCancel?.(approvalId), {
14804
+ kind: "error",
14805
+ code: "APPROVAL_CANCELLED",
14806
+ message: `Approval ${approvalId} cancelled.`
14807
+ });
14279
14808
  }
14280
14809
  },
14281
- { isActive: phase.kind === "awaiting-approval" }
14810
+ { isActive: approvalUrl !== void 0 && !cancelling }
14282
14811
  );
14283
14812
  useEffect7(() => {
14813
+ const controller = new AbortController();
14284
14814
  let cancelled = false;
14285
- void runPayPipeline(deps, (event) => {
14815
+ const runDeps = { ...deps, signal: controller.signal };
14816
+ void runMppPayPipeline(runDeps, (event) => {
14286
14817
  if (!cancelled) dispatch(event);
14287
14818
  });
14288
14819
  return () => {
14289
14820
  cancelled = true;
14821
+ controller.abort();
14290
14822
  };
14291
14823
  }, [deps]);
14292
14824
  useEffect7(() => {
14293
- if (phase.kind === "success" || phase.kind === "replay-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
14294
- onComplete(phase);
14295
- exit();
14825
+ if (phase.kind === "success" || phase.kind === "seller-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
14826
+ finish(phase);
14827
+ }
14828
+ }, [phase, finish]);
14829
+ if (cancelling) {
14830
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "yellow", children: [
14831
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14832
+ " Cancelling approval..."
14833
+ ] }) });
14834
+ }
14835
+ if (phase.kind === "probing") {
14836
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14837
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14838
+ " Probing ",
14839
+ method,
14840
+ " ",
14841
+ url,
14842
+ "..."
14843
+ ] }) });
14844
+ }
14845
+ if (phase.kind === "no-payment") {
14846
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14847
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14848
+ " Seller accepted without payment (status ",
14849
+ String(phase.probe.status),
14850
+ "); finalising..."
14851
+ ] }) });
14852
+ }
14853
+ if (phase.kind === "decoded") {
14854
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14855
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14856
+ " Fulfilling ",
14857
+ phase.challenge.amount ?? "",
14858
+ " ",
14859
+ phase.challenge.currency ?? "",
14860
+ " ",
14861
+ "challenge..."
14862
+ ] }) });
14863
+ }
14864
+ if (phase.kind === "created") {
14865
+ const { created: created2 } = phase;
14866
+ if (created2.state !== "pending") {
14867
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14868
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14869
+ " Transaction ",
14870
+ created2.transactionId,
14871
+ " ready; replaying..."
14872
+ ] }) });
14873
+ }
14874
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingY: 1, children: [
14875
+ /* @__PURE__ */ jsx15(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { bold: true, children: "Approval required" }) }),
14876
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14877
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${created2.transactionId}` }),
14878
+ created2.approvalUrl !== void 0 ? /* @__PURE__ */ jsxs12(Fragment, { children: [
14879
+ /* @__PURE__ */ jsxs12(Text13, { children: [
14880
+ "Open: ",
14881
+ /* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: created2.approvalUrl })
14882
+ ] }),
14883
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Press Enter to open in browser." }),
14884
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Press Escape to cancel." })
14885
+ ] }) : null
14886
+ ] }),
14887
+ /* @__PURE__ */ jsx15(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14888
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14889
+ " Waiting for approval..."
14890
+ ] }) })
14891
+ ] });
14892
+ }
14893
+ if (phase.kind === "replaying") {
14894
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14895
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14896
+ " Replaying request with Authorization: Payment..."
14897
+ ] }) });
14898
+ }
14899
+ if (phase.kind === "success") {
14900
+ const { result } = phase;
14901
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14902
+ /* @__PURE__ */ jsxs12(Text13, { color: "green", children: [
14903
+ "\u2713 Paid (intent ",
14904
+ result.intent,
14905
+ ")"
14906
+ ] }),
14907
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.responseStatus)}` }),
14908
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${result.transactionId}` }),
14909
+ result.settled !== void 0 ? /* @__PURE__ */ jsx15(Text13, { children: `settled: ${result.settled.amount ?? "?"} ${result.settled.currency ?? ""} (ref ${result.settled.reference ?? "\u2014"})` }) : null,
14910
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14911
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs12(Text13, { children: [
14912
+ "Saved to: ",
14913
+ /* @__PURE__ */ jsx15(Text13, { bold: true, children: result.outputSavedTo })
14914
+ ] }) : null,
14915
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14916
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14917
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14918
+ ] }) : null
14919
+ ] });
14920
+ }
14921
+ if (phase.kind === "seller-rejected") {
14922
+ const { result } = phase;
14923
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14924
+ /* @__PURE__ */ jsx15(Text13, { color: "red", children: "\u2717 Payment not accepted by seller" }),
14925
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.responseStatus)}` }),
14926
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${result.transactionId}` }),
14927
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14928
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14929
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14930
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14931
+ ] }) : null
14932
+ ] });
14933
+ }
14934
+ if (phase.kind === "no-payment-final") {
14935
+ const { result } = phase;
14936
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14937
+ /* @__PURE__ */ jsx15(Text13, { color: "green", children: "\u2713 Seller accepted without payment" }),
14938
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.status)}` }),
14939
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14940
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs12(Text13, { children: [
14941
+ "Saved to: ",
14942
+ /* @__PURE__ */ jsx15(Text13, { bold: true, children: result.outputSavedTo })
14943
+ ] }) : null,
14944
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14945
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14946
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14947
+ ] }) : null
14948
+ ] });
14949
+ }
14950
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14951
+ /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
14952
+ "\u2717 ",
14953
+ phase.code
14954
+ ] }),
14955
+ /* @__PURE__ */ jsx15(Text13, { color: "red", children: phase.message })
14956
+ ] });
14957
+ };
14958
+
14959
+ // src/commands/mpp/schema.ts
14960
+ import { z as z4 } from "incur";
14961
+ var payArgs = z4.object({
14962
+ url: z4.string().describe("The MPP-protected resource URL to pay for.")
14963
+ });
14964
+ var payOptions = z4.object({
14965
+ paymentMethod: z4.string().optional().describe('Only consider challenges with this payment method (e.g. "inflow").'),
14966
+ intent: z4.string().optional().describe('Only consider challenges with this intent (e.g. "charge").'),
14967
+ currency: z4.string().optional().describe('Only consider challenges in this currency (e.g. "USDC").'),
14968
+ rail: z4.string().optional().describe('Only consider challenges on this settlement rail (e.g. "balance", "instrument").'),
14969
+ method: z4.string().default("GET").describe("HTTP method for the seller request."),
14970
+ data: z4.string().optional().describe(
14971
+ "Request body. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
14972
+ ),
14973
+ header: z4.array(z4.string()).default([]).describe('Repeatable. "Name: Value" format.'),
14974
+ interval: z4.coerce.number().default(0).describe(
14975
+ "Inline poll cadence in seconds while a transaction is pending. 0 returns the transaction id and a follow-up command hint without blocking."
14976
+ ),
14977
+ maxAttempts: z4.coerce.number().default(0).describe("Hard cap on poll attempts when --interval > 0. 0 means unlimited."),
14978
+ timeout: z4.coerce.number().default(900).describe("Polling deadline in seconds. Default 900s (matches the server-side approval expiry)."),
14979
+ instrumentId: z4.string().optional().describe(
14980
+ "Funding instrument id (UUID) for an instrument-rail challenge. The buyer does not choose the rail \u2014 it is derived from the seller challenge; this is the only buyer-supplied payment option."
14981
+ ),
14982
+ showBody: z4.boolean().default(true).describe(
14983
+ "Include the seller response body in the result. Default true so AI assistants paying for content receive the deliverable. Pass --no-show-body to suppress (e.g. for binary downloads paired with --output-file)."
14984
+ ),
14985
+ outputFile: z4.string().optional().describe(
14986
+ "Write the seller response body bytes to this file path (overwrites silently). When set, the result frame includes `output_saved_to: <absolute_path>` instead of `body` / `body_base64`. Natural choice for binary content (PDFs, images, downloads)."
14987
+ ),
14988
+ credentialFile: z4.string().optional().describe(
14989
+ "Write the base64url `Authorization: Payment` credential to this file path (mode 0o600, overwrites silently). When set, the result frame includes `credential_saved_to: <absolute_path>` instead of `credential`. Use to keep one-time payment credentials out of chat transcripts and logs."
14990
+ )
14991
+ });
14992
+ var statusArgs = z4.object({
14993
+ transactionId: z4.string().describe("The transaction id returned by `mpp pay`.")
14994
+ });
14995
+ var statusOptions2 = z4.object({
14996
+ interval: z4.coerce.number().default(0).describe(
14997
+ "Poll cadence in seconds. 0 returns the current snapshot; positive values yield on every change until ready or terminal."
14998
+ ),
14999
+ maxAttempts: z4.coerce.number().default(0).describe("Hard cap on poll attempts. 0 means unlimited."),
15000
+ timeout: z4.coerce.number().default(900).describe("Polling deadline in seconds."),
15001
+ credentialFile: z4.string().optional().describe(
15002
+ "Write the base64url `Authorization: Payment` credential to this file path (mode 0o600, overwrites silently). When set, the ready frame includes `credential_saved_to: <absolute_path>` instead of `credential`. Use to keep one-time payment credentials out of chat transcripts and logs."
15003
+ )
15004
+ });
15005
+ var cancelArgs = z4.object({
15006
+ approvalId: z4.string().describe("The approval id returned by `mpp pay` (on the pending frame).")
15007
+ });
15008
+ var decodeArgs = z4.object({
15009
+ value: z4.string().describe(
15010
+ "A raw `WWW-Authenticate: Payment` header value, or a base64url `Authorization: Payment` credential / `Payment-Receipt`. The kind is auto-detected."
15011
+ )
15012
+ });
15013
+ var inspectArgs = z4.object({
15014
+ url: z4.string().describe("The MPP-protected resource URL to probe. No payment is made.")
15015
+ });
15016
+ var inspectOptions = z4.object({
15017
+ paymentMethod: z4.string().optional().describe('Only show challenges with this payment method (e.g. "inflow").'),
15018
+ intent: z4.string().optional().describe('Only show challenges with this intent (e.g. "charge").'),
15019
+ currency: z4.string().optional().describe('Only show challenges in this currency (e.g. "USDC").'),
15020
+ rail: z4.string().optional().describe('Only show challenges on this settlement rail (e.g. "balance", "instrument").'),
15021
+ method: z4.string().default("GET").describe("HTTP method for the probe request."),
15022
+ data: z4.string().optional().describe(
15023
+ "Request body for the probe. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
15024
+ ),
15025
+ header: z4.array(z4.string()).default([]).describe('Repeatable. "Name: Value" format.')
15026
+ });
15027
+
15028
+ // src/commands/mpp/status.tsx
15029
+ import { Box as Box13, Text as Text14 } from "ink";
15030
+ import Spinner10 from "ink-spinner";
15031
+ import { useEffect as useEffect8, useReducer as useReducer6 } from "react";
15032
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
15033
+ var MppStatusView = ({
15034
+ transactionId,
15035
+ fetchOnce,
15036
+ interval,
15037
+ maxAttempts,
15038
+ timeout,
15039
+ onComplete
15040
+ }) => {
15041
+ const initial = { kind: "polling" };
15042
+ const [phase, dispatch] = useReducer6(reduceMppStatus, initial);
15043
+ const { finish } = useFlowExit(onComplete);
15044
+ useEffect8(() => {
15045
+ const run = runMppStatus({ fetchOnce, interval, maxAttempts, timeout });
15046
+ let cancelled = false;
15047
+ void (async () => {
15048
+ for await (const event of run.events) {
15049
+ if (cancelled) return;
15050
+ dispatch(event);
15051
+ }
15052
+ })();
15053
+ return () => {
15054
+ cancelled = true;
15055
+ };
15056
+ }, [fetchOnce, interval, maxAttempts, timeout]);
15057
+ useEffect8(() => {
15058
+ if (phase.kind === "ready" || phase.kind === "failed" || phase.kind === "expired" || phase.kind === "timeout" || phase.kind === "error") {
15059
+ finish(phase);
15060
+ }
15061
+ }, [phase, finish]);
15062
+ if (phase.kind === "polling") {
15063
+ const stateText = phase.latest?.state ?? "pending";
15064
+ return /* @__PURE__ */ jsx16(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
15065
+ /* @__PURE__ */ jsx16(Spinner10, { type: "dots" }),
15066
+ " Polling transaction ",
15067
+ transactionId,
15068
+ " (state: ",
15069
+ stateText,
15070
+ ")..."
15071
+ ] }) });
15072
+ }
15073
+ if (phase.kind === "ready") {
15074
+ const credential = phase.response.credential ?? "";
15075
+ const preview = credential.length > 32 ? `${credential.slice(0, 32)}...` : credential;
15076
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15077
+ /* @__PURE__ */ jsx16(Text14, { color: "green", children: "\u2713 Ready" }),
15078
+ /* @__PURE__ */ jsx16(Text14, { children: `credential: ${preview}` }),
15079
+ phase.response.expires !== void 0 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: `expires: ${phase.response.expires}` }) : null
15080
+ ] });
15081
+ }
15082
+ if (phase.kind === "failed") {
15083
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15084
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: "\u2717 Transaction failed" }),
15085
+ phase.response.problem !== void 0 ? /* @__PURE__ */ jsx16(Text14, { color: "red", children: phase.response.problem.detail ?? phase.response.problem.title }) : null
15086
+ ] });
15087
+ }
15088
+ if (phase.kind === "expired") {
15089
+ return /* @__PURE__ */ jsx16(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx16(Text14, { color: "yellow", children: "Transaction expired before it was ready." }) });
15090
+ }
15091
+ if (phase.kind === "timeout") {
15092
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15093
+ /* @__PURE__ */ jsx16(Text14, { color: "yellow", children: "Polling timed out before the transaction reached a ready state." }),
15094
+ phase.response !== void 0 ? /* @__PURE__ */ jsx16(Text14, { children: `last state: ${phase.response.state}` }) : null
15095
+ ] });
15096
+ }
15097
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15098
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: "\u2717 Polling failed" }),
15099
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: phase.message })
15100
+ ] });
15101
+ };
15102
+
15103
+ // src/commands/mpp/supported.tsx
15104
+ import { Box as Box14, Text as Text15 } from "ink";
15105
+ import Spinner11 from "ink-spinner";
15106
+ import { useCallback as useCallback6 } from "react";
15107
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
15108
+ function flattenKinds(response) {
15109
+ const rows = [];
15110
+ for (const kind of response.kinds) {
15111
+ for (const intent of kind.intents) {
15112
+ for (const rail of intent.rails) {
15113
+ rows.push({
15114
+ method: kind.method,
15115
+ intent: intent.intent,
15116
+ rail: rail.rail,
15117
+ currencies: rail.currencies.join(", ") || "\u2014"
15118
+ });
15119
+ }
15120
+ }
15121
+ }
15122
+ return rows;
15123
+ }
15124
+ var COLUMNS4 = [
15125
+ { header: "Method", cell: (r) => r.method },
15126
+ { header: "Intent", cell: (r) => r.intent },
15127
+ { header: "Rail", cell: (r) => r.rail },
15128
+ { header: "Currencies", cell: (r) => r.currencies }
15129
+ ];
15130
+ var SupportedView = ({ load, onComplete }) => {
15131
+ const action = useCallback6(() => load(), [load]);
15132
+ const { finish } = useFlowExit(onComplete);
15133
+ const { status, data, error } = useFlowState(action, finish);
15134
+ if (status === "loading") {
15135
+ return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs14(Text15, { color: "cyan", children: [
15136
+ /* @__PURE__ */ jsx17(Spinner11, { type: "dots" }),
15137
+ " Loading supported MPP methods..."
15138
+ ] }) });
15139
+ }
15140
+ if (status === "error") {
15141
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
15142
+ /* @__PURE__ */ jsx17(Text15, { color: "red", children: "Failed to load supported MPP methods" }),
15143
+ /* @__PURE__ */ jsx17(Text15, { color: "red", children: error })
15144
+ ] });
15145
+ }
15146
+ const rows = data ? flattenKinds(data) : [];
15147
+ if (rows.length === 0) {
15148
+ return /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", children: /* @__PURE__ */ jsx17(Text15, { children: "No supported MPP methods returned for this account." }) });
15149
+ }
15150
+ return /* @__PURE__ */ jsx17(Table, { columns: COLUMNS4, rows });
15151
+ };
15152
+
15153
+ // src/commands/mpp/index.tsx
15154
+ import { jsx as jsx18 } from "react/jsx-runtime";
15155
+ var POST_CREATE_INSTRUCTION = "Present the approval_url to the user and ask them to approve in the InFlow mobile app or dashboard. Then call `mpp status <transaction_id> --interval 5 --max-attempts 60` to poll until ready. Once ready, replay the request manually with the credential as the `Authorization: Payment <credential>` header.";
15156
+ var POLLING_INSTRUCTION = "Approval polling is happening inline. The yield stream emits each state change; the final frame includes the result once the transaction is ready and replayed.";
15157
+ function parseHeaderFlagsOrFail(c, flags) {
15158
+ try {
15159
+ return parseHeaderFlags(flags);
15160
+ } catch (err) {
15161
+ c.error({ code: "INVALID_HEADER", message: err instanceof Error ? err.message : String(err) });
15162
+ }
15163
+ }
15164
+ function decorateCredentialField(frame, credential, credentialFile) {
15165
+ if (credentialFile !== void 0 && credentialFile.length > 0) {
15166
+ const absolute = resolvePath2(credentialFile);
15167
+ writeFileSync2(absolute, Buffer.from(credential, "utf-8"), { mode: 384 });
15168
+ chmodSync(absolute, 384);
15169
+ frame.credential_saved_to = absolute;
15170
+ return;
15171
+ }
15172
+ frame.credential = credential;
15173
+ }
15174
+ function probeOptionsFrom(c) {
15175
+ const headers = parseHeaderFlagsOrFail(c, c.options.header);
15176
+ return {
15177
+ method: c.options.method,
15178
+ headers,
15179
+ ...c.options.data !== void 0 ? { data: c.options.data } : {}
15180
+ };
15181
+ }
15182
+ function buildPayPipelineInput(c) {
15183
+ return {
15184
+ probeOptions: probeOptionsFrom(c),
15185
+ url: c.args.url,
15186
+ showBody: c.options.showBody,
15187
+ interval: c.options.interval,
15188
+ maxAttempts: c.options.maxAttempts,
15189
+ timeout: c.options.timeout,
15190
+ ...c.options.instrumentId !== void 0 ? { instrumentId: c.options.instrumentId } : {},
15191
+ ...c.options.paymentMethod !== void 0 ? { paymentMethodFilter: c.options.paymentMethod } : {},
15192
+ ...c.options.intent !== void 0 ? { intentFilter: c.options.intent } : {},
15193
+ ...c.options.currency !== void 0 ? { currencyFilter: c.options.currency } : {},
15194
+ ...c.options.rail !== void 0 ? { railFilter: c.options.rail } : {},
15195
+ ...c.options.outputFile !== void 0 ? { outputFile: c.options.outputFile } : {}
15196
+ };
15197
+ }
15198
+ function attachBodyFields(frame, result) {
15199
+ frame.body_size_bytes = result.bodySizeBytes;
15200
+ if (result.body !== void 0) frame.body = result.body;
15201
+ if (result.bodyBase64 !== void 0) frame.body_base64 = result.bodyBase64;
15202
+ if (result.outputSavedTo !== void 0) frame.output_saved_to = result.outputSavedTo;
15203
+ }
15204
+ function challengeFields(challenge) {
15205
+ const out = {
15206
+ id: challenge.id,
15207
+ method: challenge.method,
15208
+ intent: challenge.intent
15209
+ };
15210
+ if (challenge.amount !== void 0) out.amount = challenge.amount;
15211
+ if (challenge.currency !== void 0) out.currency = challenge.currency;
15212
+ if (challenge.rail !== void 0) out.rail = challenge.rail;
15213
+ return out;
15214
+ }
15215
+ function noPaymentFrameFromResult(result) {
15216
+ const frame = { outcome: "no-payment-required", status: result.status };
15217
+ if (result.contentType !== void 0) frame.content_type = result.contentType;
15218
+ attachBodyFields(frame, result);
15219
+ return frame;
15220
+ }
15221
+ function createdFrameFromEvent(created, interval, maxAttempts) {
15222
+ const pending = created.state === "pending";
15223
+ const frame = {
15224
+ transaction_id: created.transactionId,
15225
+ state: created.state,
15226
+ challenge: challengeFields(created.challenge),
15227
+ instruction: interval > 0 ? POLLING_INSTRUCTION : POST_CREATE_INSTRUCTION
15228
+ };
15229
+ if (created.approvalId !== void 0) frame.approval_id = created.approvalId;
15230
+ if (created.approvalUrl !== void 0) frame.approval_url = created.approvalUrl;
15231
+ if (created.retryAfterSeconds !== void 0) frame.retry_after_seconds = created.retryAfterSeconds;
15232
+ if (created.expires !== void 0) frame.expires = created.expires;
15233
+ if (pending && interval <= 0) {
15234
+ const max = maxAttempts > 0 ? maxAttempts : 60;
15235
+ frame._next = {
15236
+ command: `mpp status ${created.transactionId} --interval 5 --max-attempts ${String(max)}`,
15237
+ poll_interval_seconds: 5,
15238
+ until: "state is ready (credential present)"
15239
+ };
15240
+ }
15241
+ return frame;
15242
+ }
15243
+ function paidFrameFromResult(result, credentialFile) {
15244
+ const frame = {
15245
+ outcome: "paid",
15246
+ transaction_id: result.transactionId,
15247
+ challenge_id: result.challengeId,
15248
+ intent: result.intent,
15249
+ response_status: result.responseStatus
15250
+ };
15251
+ decorateCredentialField(frame, result.credential, credentialFile);
15252
+ if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
15253
+ if (result.settled !== void 0) frame.settled = result.settled;
15254
+ attachBodyFields(frame, result);
15255
+ return frame;
15256
+ }
15257
+ function rejectedFrameFromResult(result) {
15258
+ const frame = {
15259
+ outcome: "seller-rejected",
15260
+ transaction_id: result.transactionId,
15261
+ challenge_id: result.challengeId,
15262
+ response_status: result.responseStatus
15263
+ };
15264
+ if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
15265
+ attachBodyFields(frame, result);
15266
+ return frame;
15267
+ }
15268
+ function toStatusFrame(response, credentialFile) {
15269
+ const frame = {
15270
+ transaction_id: response.transactionId ?? "",
15271
+ state: response.state
15272
+ };
15273
+ if (response.state === "ready" && response.credential !== void 0) {
15274
+ decorateCredentialField(frame, response.credential, credentialFile);
15275
+ if (response.expires !== void 0) frame.expires = response.expires;
15276
+ }
15277
+ if (response.state === "pending") {
15278
+ if (response.approvalId !== void 0) frame.approval_id = response.approvalId;
15279
+ if (response.retryAfterSeconds !== void 0) frame.retry_after_seconds = response.retryAfterSeconds;
15280
+ }
15281
+ if (response.problem !== void 0) frame.problem = response.problem;
15282
+ return frame;
15283
+ }
15284
+ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
15285
+ assertSessionGuard(c, authStorage2, inflow2);
15286
+ if (!c.agent && !c.formatExplicit) {
15287
+ const client = await inflow2.mpp.client();
15288
+ let finalPhase = null;
15289
+ await renderInkUntilExit(
15290
+ /* @__PURE__ */ jsx18(
15291
+ PayView,
15292
+ {
15293
+ url: c.args.url,
15294
+ method: c.options.method,
15295
+ deps: {
15296
+ ...buildPayPipelineInput(c),
15297
+ client,
15298
+ apiBaseUrl: apiBaseUrl2,
15299
+ awaitPayment: true
15300
+ },
15301
+ onComplete: (phase) => {
15302
+ finalPhase = phase;
15303
+ },
15304
+ onCancel: (approvalId) => inflow2.mpp.cancel({ approvalId })
15305
+ }
15306
+ )
15307
+ );
15308
+ if (finalPhase !== null) {
15309
+ const phase = finalPhase;
15310
+ if (phase.kind === "seller-rejected") {
15311
+ c.error({
15312
+ code: PAYMENT_NOT_ACCEPTED_CODE2,
15313
+ message: `Seller rejected the credential with status ${String(phase.result.responseStatus)}. The transaction was ready but the seller did not honour the payment.`
15314
+ });
15315
+ }
15316
+ if (phase.kind === "error") {
15317
+ c.error({ code: phase.code, message: phase.message });
15318
+ }
15319
+ }
15320
+ return;
15321
+ }
15322
+ const run = inflow2.mpp.pay({
15323
+ ...buildPayPipelineInput(c),
15324
+ awaitPayment: c.options.interval > 0
15325
+ });
15326
+ for await (const event of run.events) {
15327
+ if (event.type === "short-circuited") {
15328
+ yield sanitizeDeep(noPaymentFrameFromResult(event.result));
15329
+ return;
15330
+ }
15331
+ if (event.type === "created") {
15332
+ yield sanitizeDeep(createdFrameFromEvent(event.created, c.options.interval, c.options.maxAttempts));
15333
+ continue;
15334
+ }
15335
+ if (event.type === "replayed") {
15336
+ yield sanitizeDeep(paidFrameFromResult(event.result, c.options.credentialFile));
15337
+ return;
15338
+ }
15339
+ if (event.type === "rejected") {
15340
+ yield sanitizeDeep(rejectedFrameFromResult(event.result));
15341
+ c.error({
15342
+ code: PAYMENT_NOT_ACCEPTED_CODE2,
15343
+ message: `Seller rejected the credential with status ${String(event.result.responseStatus)}. The transaction was ready but the seller did not honour the payment; see the previous frame for details.`
15344
+ });
15345
+ return;
15346
+ }
15347
+ if (event.type === "errored") {
15348
+ c.error({ code: event.code, message: event.message });
15349
+ }
15350
+ }
15351
+ }
15352
+ async function* runStatusCommand(c, inflow2, authStorage2) {
15353
+ assertSessionGuard(c, authStorage2, inflow2);
15354
+ if (!c.agent && !c.formatExplicit) {
15355
+ const client2 = await inflow2.mpp.client();
15356
+ await renderInkUntilExit(
15357
+ /* @__PURE__ */ jsx18(
15358
+ MppStatusView,
15359
+ {
15360
+ transactionId: c.args.transactionId,
15361
+ fetchOnce: () => client2.getTransaction(c.args.transactionId),
15362
+ interval: c.options.interval,
15363
+ maxAttempts: c.options.maxAttempts,
15364
+ timeout: c.options.timeout,
15365
+ onComplete: () => void 0
15366
+ }
15367
+ )
15368
+ );
15369
+ return;
15370
+ }
15371
+ const client = await inflow2.mpp.client();
15372
+ const fetchOnce = () => client.getTransaction(c.args.transactionId);
15373
+ if (c.options.interval <= 0) {
15374
+ const snapshot = await fetchOnce();
15375
+ yield sanitizeDeep(toStatusFrame(snapshot, c.options.credentialFile));
15376
+ return;
15377
+ }
15378
+ const run = runMppStatus({
15379
+ fetchOnce,
15380
+ interval: c.options.interval,
15381
+ maxAttempts: c.options.maxAttempts,
15382
+ timeout: c.options.timeout
15383
+ });
15384
+ for await (const event of run.events) {
15385
+ if (event.type === "snapshot") {
15386
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15387
+ continue;
15388
+ }
15389
+ if (event.type === "ready") {
15390
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15391
+ return;
15392
+ }
15393
+ if (event.type === "failed") {
15394
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15395
+ c.error({
15396
+ code: "PAYMENT_FAILED",
15397
+ message: event.response.problem?.detail ?? event.response.problem?.title ?? "MPP transaction failed."
15398
+ });
15399
+ }
15400
+ if (event.type === "expired") {
15401
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15402
+ c.error({ code: "PAYMENT_EXPIRED", message: "MPP transaction expired before it was ready." });
15403
+ }
15404
+ if (event.type === "timedOut") {
15405
+ if (event.response !== void 0) {
15406
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15407
+ }
15408
+ c.error({
15409
+ code: "POLLING_TIMEOUT",
15410
+ message: "Polling timed out before the transaction reached a ready state.",
15411
+ retryable: true
15412
+ });
15413
+ }
15414
+ if (event.type === "crashed") {
15415
+ c.error({ code: "PAYMENT_FAILED", message: event.message });
15416
+ }
15417
+ }
15418
+ }
15419
+ async function runCancelCommand(c, inflow2, authStorage2) {
15420
+ assertSessionGuard(c, authStorage2, inflow2);
15421
+ if (!c.agent && !c.formatExplicit) {
15422
+ await renderInkUntilExit(
15423
+ /* @__PURE__ */ jsx18(
15424
+ CancelView,
15425
+ {
15426
+ approvalId: c.args.approvalId,
15427
+ cancel: () => inflow2.mpp.cancel({ approvalId: c.args.approvalId }).then(() => void 0),
15428
+ onComplete: () => void 0
15429
+ }
15430
+ )
15431
+ );
15432
+ return { approval_id: c.args.approvalId, cancelled: true, note: "best-effort; server-side state not verified" };
15433
+ }
15434
+ return inflow2.mpp.cancel({ approvalId: c.args.approvalId });
15435
+ }
15436
+ async function runDecodeCommand(c) {
15437
+ let result;
15438
+ try {
15439
+ result = decodeMppValue(c.args.value);
15440
+ } catch (err) {
15441
+ return c.error({ code: "DECODE_FAILED", message: err instanceof Error ? err.message : String(err) });
15442
+ }
15443
+ if (!c.agent && !c.formatExplicit) {
15444
+ await renderInkUntilExit(/* @__PURE__ */ jsx18(DecodeView, { result }));
15445
+ return void 0;
15446
+ }
15447
+ return sanitizeDeep(result);
15448
+ }
15449
+ async function runSupportedCommand(c, inflow2, authStorage2) {
15450
+ assertSessionGuard(c, authStorage2, inflow2);
15451
+ if (!c.agent && !c.formatExplicit) {
15452
+ await renderInkUntilExit(/* @__PURE__ */ jsx18(SupportedView, { load: () => inflow2.mpp.supported(), onComplete: () => void 0 }));
15453
+ return void 0;
15454
+ }
15455
+ const response = await inflow2.mpp.supported();
15456
+ return sanitizeDeep(response);
15457
+ }
15458
+ async function runInspectCommand(c) {
15459
+ const deps = {
15460
+ probeOptions: probeOptionsFrom(c),
15461
+ url: c.args.url,
15462
+ ...c.options.paymentMethod !== void 0 ? { paymentMethodFilter: c.options.paymentMethod } : {},
15463
+ ...c.options.intent !== void 0 ? { intentFilter: c.options.intent } : {},
15464
+ ...c.options.currency !== void 0 ? { currencyFilter: c.options.currency } : {},
15465
+ ...c.options.rail !== void 0 ? { railFilter: c.options.rail } : {}
15466
+ };
15467
+ if (!c.agent && !c.formatExplicit) {
15468
+ let finalPhase = null;
15469
+ await renderInkUntilExit(
15470
+ /* @__PURE__ */ jsx18(
15471
+ InspectView,
15472
+ {
15473
+ url: c.args.url,
15474
+ method: c.options.method,
15475
+ deps,
15476
+ onComplete: (phase) => {
15477
+ finalPhase = phase;
15478
+ }
15479
+ }
15480
+ )
15481
+ );
15482
+ if (finalPhase !== null) {
15483
+ const phase = finalPhase;
15484
+ if (phase.kind === "error") {
15485
+ c.error({ code: phase.code, message: phase.message });
15486
+ }
15487
+ }
15488
+ return void 0;
15489
+ }
15490
+ let finalEvent = null;
15491
+ await runMppInspectPipeline(deps, (event) => {
15492
+ if (event.type === "errored") {
15493
+ finalEvent = { kind: "error", payload: event };
15494
+ return;
15495
+ }
15496
+ if (event.type === "challenges") {
15497
+ finalEvent = { kind: "challenges", payload: event.result };
15498
+ return;
15499
+ }
15500
+ if (event.type === "no-payment") {
15501
+ finalEvent = { kind: "no-payment", payload: event.result };
15502
+ }
15503
+ });
15504
+ if (finalEvent === null) {
15505
+ return c.error({ code: "INSPECT_FAILED", message: "Inspect pipeline produced no result." });
15506
+ }
15507
+ const { kind, payload } = finalEvent;
15508
+ if (kind === "error") {
15509
+ const err = payload;
15510
+ return c.error({ code: err.code, message: err.message });
15511
+ }
15512
+ if (kind === "challenges") {
15513
+ return sanitizeDeep(buildChallengesFrame(payload));
15514
+ }
15515
+ return sanitizeDeep(buildNoPaymentFrame(payload));
15516
+ }
15517
+ function createMppCli(inflow2, authStorage2, apiBaseUrl2) {
15518
+ const cli2 = Cli4.create("mpp", {
15519
+ description: "MPP payment commands (pay, inspect, status, cancel, decode, supported)."
15520
+ });
15521
+ cli2.command("pay", {
15522
+ description: "Pay an MPP-protected resource and return the seller response.",
15523
+ args: payArgs,
15524
+ options: payOptions,
15525
+ outputPolicy: "agent-only",
15526
+ async *run(c) {
15527
+ yield* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2);
15528
+ }
15529
+ });
15530
+ cli2.command("status", {
15531
+ description: "Poll the buyer-side state of an in-flight MPP transaction.",
15532
+ args: statusArgs,
15533
+ options: statusOptions2,
15534
+ outputPolicy: "agent-only",
15535
+ async *run(c) {
15536
+ yield* runStatusCommand(c, inflow2, authStorage2);
15537
+ }
15538
+ });
15539
+ cli2.command("cancel", {
15540
+ description: "Best-effort cancel of an MPP approval.",
15541
+ args: cancelArgs,
15542
+ outputPolicy: "agent-only",
15543
+ async run(c) {
15544
+ return runCancelCommand(c, inflow2, authStorage2);
15545
+ }
15546
+ });
15547
+ cli2.command("decode", {
15548
+ description: "Decode a raw WWW-Authenticate: Payment header, or a base64url credential / receipt.",
15549
+ args: decodeArgs,
15550
+ outputPolicy: "agent-only",
15551
+ async run(c) {
15552
+ return runDecodeCommand(c);
15553
+ }
15554
+ });
15555
+ cli2.command("supported", {
15556
+ description: "List the methods the buyer can pay with \u2014 by intent, settlement rail, and currency.",
15557
+ outputPolicy: "agent-only",
15558
+ async run(c) {
15559
+ return runSupportedCommand(c, inflow2, authStorage2);
15560
+ }
15561
+ });
15562
+ cli2.command("inspect", {
15563
+ description: "Show the seller's MPP challenge(s) for a URL. Read-only probe \u2014 no auth, no payment.",
15564
+ args: inspectArgs,
15565
+ options: inspectOptions,
15566
+ outputPolicy: "agent-only",
15567
+ async run(c) {
15568
+ return runInspectCommand(c);
15569
+ }
15570
+ });
15571
+ return cli2;
15572
+ }
15573
+
15574
+ // src/commands/user/index.tsx
15575
+ import { Cli as Cli5, Errors as Errors2 } from "incur";
15576
+ import "react";
15577
+
15578
+ // src/commands/user/get.tsx
15579
+ import { Box as Box15, Text as Text16 } from "ink";
15580
+ import Spinner12 from "ink-spinner";
15581
+ import { useCallback as useCallback7, useRef as useRef3 } from "react";
15582
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
15583
+ var UserGet = ({ userResource, onComplete }) => {
15584
+ const action = useCallback7(() => userResource.retrieve(), [userResource]);
15585
+ const { finish } = useFlowExit(onComplete);
15586
+ const lastErrorRef = useRef3("");
15587
+ const handleLinger = useCallback7(
15588
+ (result) => {
15589
+ if (result !== null) {
15590
+ finish({ kind: "success", user: result });
15591
+ } else {
15592
+ finish({
15593
+ kind: "error",
15594
+ message: lastErrorRef.current || "No result produced."
15595
+ });
15596
+ }
15597
+ },
15598
+ [finish]
15599
+ );
15600
+ const { status, data: user, error } = useFlowState(action, handleLinger);
15601
+ lastErrorRef.current = error;
15602
+ if (status === "loading") {
15603
+ return /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsxs15(Text16, { color: "cyan", children: [
15604
+ /* @__PURE__ */ jsx19(Spinner12, { type: "dots" }),
15605
+ " Loading user..."
15606
+ ] }) });
15607
+ }
15608
+ if (status === "error") {
15609
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
15610
+ /* @__PURE__ */ jsx19(Text16, { color: "red", children: "\u2717 Failed to retrieve user" }),
15611
+ /* @__PURE__ */ jsx19(Text16, { color: "red", children: error })
15612
+ ] });
15613
+ }
15614
+ if (user === null) return null;
15615
+ const rows = buildProfileRows(user);
15616
+ return /* @__PURE__ */ jsx19(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: rows.map((row) => /* @__PURE__ */ jsxs15(Text16, { children: [
15617
+ `${row.label}: `,
15618
+ row.value !== null ? /* @__PURE__ */ jsx19(Text16, { bold: true, children: row.value }) : /* @__PURE__ */ jsx19(Text16, { dimColor: true, children: "\u2014" })
15619
+ ] }, row.label)) });
15620
+ };
15621
+
15622
+ // src/commands/user/schema.ts
15623
+ import { z as z5 } from "incur";
15624
+ var getOptions = z5.object({});
15625
+
15626
+ // src/commands/user/index.tsx
15627
+ import { jsx as jsx20 } from "react/jsx-runtime";
15628
+ var TTY_NO_RESULT_MESSAGE = "inflow user get exited without producing a result.";
15629
+ async function runUserGet2(c, deps) {
15630
+ assertSessionGuard(c, deps.authStorage, deps.inflow);
15631
+ if (!c.agent && !c.formatExplicit) {
15632
+ let captured = null;
15633
+ const outcome = await renderInkUntilExit(
15634
+ /* @__PURE__ */ jsx20(
15635
+ UserGet,
15636
+ {
15637
+ userResource: deps.user,
15638
+ onComplete: (value) => {
15639
+ captured = value;
15640
+ }
15641
+ }
15642
+ ),
15643
+ () => captured
15644
+ );
15645
+ if (outcome === null) {
15646
+ throw new Errors2.IncurError({
15647
+ code: "USER_GET_FAILED",
15648
+ message: TTY_NO_RESULT_MESSAGE
15649
+ });
15650
+ }
15651
+ if (outcome.kind === "error") {
15652
+ throw new Errors2.IncurError({
15653
+ code: "USER_GET_FAILED",
15654
+ message: outcome.message
15655
+ });
15656
+ }
15657
+ return projectUserPayload(outcome.user);
15658
+ }
15659
+ return deps.user.get();
15660
+ }
15661
+ function createUserCli(user, authStorage2, inflow2) {
15662
+ const cli2 = Cli5.create("user", { description: "User profile commands" });
15663
+ cli2.command("get", {
15664
+ description: "Retrieve the current authenticated user",
15665
+ options: getOptions,
15666
+ outputPolicy: "agent-only",
15667
+ async run(c) {
15668
+ return runUserGet2(c, { user, authStorage: authStorage2, inflow: inflow2 });
15669
+ }
15670
+ });
15671
+ return cli2;
15672
+ }
15673
+
15674
+ // src/commands/x402/index.tsx
15675
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync3 } from "fs";
15676
+ import { resolve as resolvePath3 } from "path";
15677
+ import { Cli as Cli6 } from "incur";
15678
+
15679
+ // src/commands/x402/cancel.tsx
15680
+ import { Box as Box16, Text as Text17 } from "ink";
15681
+ import Spinner13 from "ink-spinner";
15682
+ import { useCallback as useCallback8 } from "react";
15683
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
15684
+ var CancelView2 = ({ approvalId, cancel, onComplete }) => {
15685
+ const action = useCallback8(() => cancel(), [cancel]);
15686
+ const { finish } = useFlowExit(onComplete);
15687
+ const { status } = useFlowState(action, finish);
15688
+ if (status === "loading") {
15689
+ return /* @__PURE__ */ jsx21(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
15690
+ /* @__PURE__ */ jsx21(Spinner13, { type: "dots" }),
15691
+ " Cancelling approval ",
15692
+ approvalId,
15693
+ "..."
15694
+ ] }) });
15695
+ }
15696
+ return /* @__PURE__ */ jsx21(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "green", children: [
15697
+ "\u2713 Cancelled approval ",
15698
+ approvalId,
15699
+ " (best-effort)"
15700
+ ] }) });
15701
+ };
15702
+
15703
+ // src/commands/x402/decode.tsx
15704
+ import { fromFoundationRequirements as fromFoundationRequirements2 } from "@inflowpayai/x402-buyer";
15705
+ import { Box as Box17, Text as Text18 } from "ink";
15706
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
15707
+ var DecodeView2 = ({ decoded }) => {
15708
+ const summary = summarizeAccepts(fromFoundationRequirements2(decoded.accepts));
15709
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", paddingY: 1, children: [
15710
+ /* @__PURE__ */ jsx22(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsx22(Text18, { bold: true, children: "Decoded PAYMENT-REQUIRED" }) }),
15711
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
15712
+ /* @__PURE__ */ jsxs17(Text18, { children: [
15713
+ "x402Version: ",
15714
+ /* @__PURE__ */ jsx22(Text18, { color: "cyan", children: String(decoded.x402Version) })
15715
+ ] }),
15716
+ /* @__PURE__ */ jsxs17(Text18, { children: [
15717
+ "resource: ",
15718
+ /* @__PURE__ */ jsx22(Text18, { color: "cyan", children: decoded.resource.url })
15719
+ ] }),
15720
+ /* @__PURE__ */ jsx22(Text18, { children: `accepts (${String(summary.length)}):` }),
15721
+ summary.map((entry, idx) => /* @__PURE__ */ jsxs17(Text18, { children: [
15722
+ " ",
15723
+ /* @__PURE__ */ jsx22(Text18, { color: "yellow", children: entry.scheme }),
15724
+ " / ",
15725
+ /* @__PURE__ */ jsx22(Text18, { color: "yellow", children: entry.network }),
15726
+ entry.amount !== void 0 ? ` \xB7 amount ${entry.amount}` : "",
15727
+ entry.asset !== void 0 ? ` \xB7 ${entry.asset}` : ""
15728
+ ] }, `${entry.scheme}-${entry.network}-${String(idx)}`)),
15729
+ decoded.extensions !== void 0 ? /* @__PURE__ */ jsx22(Text18, { dimColor: true, children: `extensions: ${Object.keys(decoded.extensions).join(", ")}` }) : null
15730
+ ] })
15731
+ ] });
15732
+ };
15733
+
15734
+ // src/commands/x402/inspect.tsx
15735
+ import { Box as Box18, Text as Text19 } from "ink";
15736
+ import Spinner14 from "ink-spinner";
15737
+ import { useEffect as useEffect9, useReducer as useReducer7 } from "react";
15738
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
15739
+ function summarizeExtra(extra) {
15740
+ if (extra === void 0) return "\u2014";
15741
+ const keys = Object.keys(extra);
15742
+ if (keys.length === 0) return "\u2014";
15743
+ return [...keys].sort((a, b) => a.localeCompare(b)).join(", ");
15744
+ }
15745
+ function formatTimeout(seconds) {
15746
+ return `${String(seconds)}s`;
15747
+ }
15748
+ function formatAsset(asset) {
15749
+ return asset === "" ? "\u2014" : asset;
15750
+ }
15751
+ var COLUMNS5 = [
15752
+ { header: "Scheme", cell: (r) => r.scheme },
15753
+ { header: "Network", cell: (r) => r.network },
15754
+ { header: "Amount", cell: (r) => r.amount },
15755
+ { header: "Asset", cell: (r) => formatAsset(r.asset) },
15756
+ { header: "Pay To", cell: (r) => r.payTo },
15757
+ { header: "Timeout", cell: (r) => formatTimeout(r.maxTimeoutSeconds) },
15758
+ { header: "Extra", cell: (r) => summarizeExtra(r.extra) }
15759
+ ];
15760
+ var InspectView2 = ({ url, method, deps, onComplete }) => {
15761
+ const initial = { kind: "probing" };
15762
+ const [phase, dispatch] = useReducer7(reduceX402Inspect, initial);
15763
+ const { finish } = useFlowExit(onComplete);
15764
+ useEffect9(() => {
15765
+ let cancelled = false;
15766
+ void runInspectPipeline(deps, (event) => {
15767
+ if (!cancelled) dispatch(event);
15768
+ });
15769
+ return () => {
15770
+ cancelled = true;
15771
+ };
15772
+ }, [deps]);
15773
+ useEffect9(() => {
15774
+ if (phase.kind === "accepts" || phase.kind === "no-payment" || phase.kind === "error") {
15775
+ finish(phase);
15776
+ }
15777
+ }, [phase, finish]);
15778
+ if (phase.kind === "probing") {
15779
+ return /* @__PURE__ */ jsx23(Box18, { children: /* @__PURE__ */ jsxs18(Text19, { color: "cyan", children: [
15780
+ /* @__PURE__ */ jsx23(Spinner14, { type: "dots" }),
15781
+ " Probing ",
15782
+ method,
15783
+ " ",
15784
+ url,
15785
+ "..."
15786
+ ] }) });
15787
+ }
15788
+ if (phase.kind === "no-payment") {
15789
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15790
+ /* @__PURE__ */ jsx23(Text19, { color: "green", children: "\u2713 Seller accepted without payment" }),
15791
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: "Use `x402 pay` to fetch the body." })
15792
+ ] });
15793
+ }
15794
+ if (phase.kind === "error") {
15795
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15796
+ /* @__PURE__ */ jsxs18(Text19, { color: "red", children: [
15797
+ "\u2717 ",
15798
+ phase.code
15799
+ ] }),
15800
+ /* @__PURE__ */ jsx23(Text19, { color: "red", children: phase.message })
15801
+ ] });
15802
+ }
15803
+ const { result } = phase;
15804
+ const acceptsCount = result.accepts.length;
15805
+ const extensionsLine = result.extensions !== void 0 ? Object.keys(result.extensions).join(", ") : null;
15806
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15807
+ /* @__PURE__ */ jsxs18(Text19, { children: [
15808
+ /* @__PURE__ */ jsx23(Text19, { bold: true, children: "PAYMENT-REQUIRED" }),
15809
+ " for ",
15810
+ /* @__PURE__ */ jsx23(Text19, { color: "cyan", children: result.resource }),
15811
+ " \xB7 ",
15812
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `x402Version ${String(result.x402Version)}` }),
15813
+ " \xB7 ",
15814
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `${String(acceptsCount)} accept${acceptsCount === 1 ? "" : "s"}` })
15815
+ ] }),
15816
+ extensionsLine !== null ? /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `extensions: ${extensionsLine}` }) : null,
15817
+ /* @__PURE__ */ jsx23(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx23(Table, { columns: COLUMNS5, rows: result.accepts }) }),
15818
+ /* @__PURE__ */ jsx23(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: "Use --format json to inspect extras values." }) })
15819
+ ] });
15820
+ };
15821
+ function buildAcceptsFrame(result) {
15822
+ const frame = {
15823
+ outcome: "accepts",
15824
+ url: result.url,
15825
+ method: result.method,
15826
+ resource: result.resource,
15827
+ x402_version: result.x402Version,
15828
+ accepts: result.accepts.map((entry) => {
15829
+ const row = {
15830
+ scheme: entry.scheme,
15831
+ network: entry.network,
15832
+ amount: entry.amount,
15833
+ asset: entry.asset,
15834
+ pay_to: entry.payTo,
15835
+ max_timeout_seconds: entry.maxTimeoutSeconds
15836
+ };
15837
+ if (entry.extra !== void 0) row.extra = entry.extra;
15838
+ return row;
15839
+ })
15840
+ };
15841
+ if (result.extensions !== void 0) frame.extensions = result.extensions;
15842
+ return frame;
15843
+ }
15844
+ function buildNoPaymentFrame2(result) {
15845
+ const frame = {
15846
+ outcome: "no-payment-required",
15847
+ url: result.url,
15848
+ method: result.method,
15849
+ status: result.status,
15850
+ body_size_bytes: result.bodySizeBytes
15851
+ };
15852
+ if (result.contentType !== void 0) frame.content_type = result.contentType;
15853
+ return frame;
15854
+ }
15855
+
15856
+ // src/commands/x402/pay.tsx
15857
+ import { Box as Box19, Text as Text20, useInput as useInput4 } from "ink";
15858
+ import Spinner15 from "ink-spinner";
15859
+ import { useEffect as useEffect10, useReducer as useReducer8, useState as useState4 } from "react";
15860
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
15861
+ var PayView2 = ({ url, method, deps, onComplete, onCancel }) => {
15862
+ const initial = { kind: "probing" };
15863
+ const [phase, dispatch] = useReducer8(reducePay, initial);
15864
+ const [cancelling, setCancelling] = useState4(false);
15865
+ const { finish, cancelThenFinish } = useFlowExit(onComplete);
15866
+ useInput4(
15867
+ (_input, key) => {
15868
+ if (phase.kind !== "awaiting-approval") return;
15869
+ if (key.return) {
15870
+ openUrl(phase.approvalUrl);
15871
+ return;
15872
+ }
15873
+ if (key.escape) {
15874
+ const { approvalId } = phase.prepared;
15875
+ setCancelling(true);
15876
+ cancelThenFinish(() => onCancel?.(approvalId), {
15877
+ kind: "error",
15878
+ code: "APPROVAL_CANCELLED",
15879
+ message: `Approval ${approvalId} cancelled.`
15880
+ });
15881
+ }
15882
+ },
15883
+ { isActive: phase.kind === "awaiting-approval" && !cancelling }
15884
+ );
15885
+ useEffect10(() => {
15886
+ const controller = new AbortController();
15887
+ let cancelled = false;
15888
+ const runDeps = { ...deps, signOptions: { ...deps.signOptions, signal: controller.signal } };
15889
+ void runPayPipeline(runDeps, (event) => {
15890
+ if (!cancelled) dispatch(event);
15891
+ });
15892
+ return () => {
15893
+ cancelled = true;
15894
+ controller.abort();
15895
+ };
15896
+ }, [deps]);
15897
+ useEffect10(() => {
15898
+ if (phase.kind === "success" || phase.kind === "replay-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
15899
+ finish(phase);
14296
15900
  }
14297
- }, [phase, onComplete, exit]);
15901
+ }, [phase, finish]);
15902
+ if (cancelling) {
15903
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "yellow", children: [
15904
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
15905
+ " Cancelling approval..."
15906
+ ] }) });
15907
+ }
14298
15908
  if (phase.kind === "probing") {
14299
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14300
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15909
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15910
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14301
15911
  " Probing ",
14302
15912
  method,
14303
15913
  " ",
@@ -14306,23 +15916,23 @@ var PayView = ({ url, method, deps, onComplete }) => {
14306
15916
  ] }) });
14307
15917
  }
14308
15918
  if (phase.kind === "no-payment") {
14309
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14310
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15919
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15920
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14311
15921
  " Seller accepted without payment (status ",
14312
15922
  String(phase.probe.status),
14313
15923
  "); finalising..."
14314
15924
  ] }) });
14315
15925
  }
14316
15926
  if (phase.kind === "matching") {
14317
- return /* @__PURE__ */ jsx17(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14318
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15927
+ return /* @__PURE__ */ jsx24(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15928
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14319
15929
  " Decoding seller requirements..."
14320
15930
  ] }) });
14321
15931
  }
14322
15932
  if (phase.kind === "preparing") {
14323
15933
  const summary = summarizeAccepts([phase.requirement]);
14324
- return /* @__PURE__ */ jsx17(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14325
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15934
+ return /* @__PURE__ */ jsx24(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15935
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14326
15936
  " Preparing payment (",
14327
15937
  summary[0]?.scheme ?? phase.requirement.scheme,
14328
15938
  " /",
@@ -14332,188 +15942,167 @@ var PayView = ({ url, method, deps, onComplete }) => {
14332
15942
  ] }) });
14333
15943
  }
14334
15944
  if (phase.kind === "awaiting-approval") {
14335
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingY: 1, children: [
14336
- /* @__PURE__ */ jsx17(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx17(Text14, { bold: true, children: "Approval required" }) }),
14337
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14338
- /* @__PURE__ */ jsxs13(Text14, { children: [
15945
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", paddingY: 1, children: [
15946
+ /* @__PURE__ */ jsx24(Box19, { marginBottom: 1, children: /* @__PURE__ */ jsx24(Text20, { bold: true, children: "Approval required" }) }),
15947
+ /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
15948
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14339
15949
  "Open: ",
14340
- /* @__PURE__ */ jsx17(Text14, { bold: true, color: "cyan", children: phase.approvalUrl })
15950
+ /* @__PURE__ */ jsx24(Text20, { bold: true, color: "cyan", children: phase.approvalUrl })
14341
15951
  ] }),
14342
- /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "Press Enter to open in browser." })
15952
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "Press Enter to open in browser." }),
15953
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "Press Escape to cancel." })
14343
15954
  ] }),
14344
- /* @__PURE__ */ jsx17(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14345
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15955
+ /* @__PURE__ */ jsx24(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15956
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14346
15957
  " Waiting for approval..."
14347
15958
  ] }) })
14348
15959
  ] });
14349
15960
  }
14350
15961
  if (phase.kind === "replaying") {
14351
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14352
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15962
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15963
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14353
15964
  " Replaying request with PAYMENT-SIGNATURE..."
14354
15965
  ] }) });
14355
15966
  }
14356
15967
  if (phase.kind === "success") {
14357
15968
  const { result } = phase;
14358
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14359
- /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
15969
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
15970
+ /* @__PURE__ */ jsxs19(Text20, { color: "green", children: [
14360
15971
  "\u2713 Paid ",
14361
15972
  result.scheme,
14362
15973
  " / ",
14363
15974
  result.network
14364
15975
  ] }),
14365
- /* @__PURE__ */ jsx17(Text14, { children: `status: ${String(result.responseStatus)}` }),
14366
- /* @__PURE__ */ jsx17(Text14, { children: `transaction: ${result.transactionId}` }),
14367
- result.settled?.network !== void 0 ? /* @__PURE__ */ jsx17(Text14, { children: `settled via: ${result.settled.network}${result.settled.transaction !== void 0 ? ` (${result.settled.transaction})` : ""}` }) : null,
14368
- /* @__PURE__ */ jsx17(Text14, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14369
- result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs13(Text14, { children: [
15976
+ /* @__PURE__ */ jsx24(Text20, { children: `transaction: ${result.transactionId}` }),
15977
+ result.settled?.network !== void 0 ? /* @__PURE__ */ jsx24(Text20, { children: `settled via: ${result.settled.network}${result.settled.transaction !== void 0 ? ` (${result.settled.transaction})` : ""}` }) : null,
15978
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14370
15979
  "Saved to: ",
14371
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
15980
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14372
15981
  ] }) : null,
14373
- result.body !== void 0 ? /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
14374
- /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "response body:" }),
14375
- /* @__PURE__ */ jsx17(Text14, { children: result.body })
15982
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
15983
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
15984
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14376
15985
  ] }) : null
14377
15986
  ] });
14378
15987
  }
14379
15988
  if (phase.kind === "replay-rejected") {
14380
15989
  const { result } = phase;
14381
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14382
- /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
15990
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
15991
+ /* @__PURE__ */ jsxs19(Text20, { color: "red", children: [
14383
15992
  "\u2717 Payment not accepted (",
14384
15993
  result.scheme,
14385
15994
  " / ",
14386
15995
  result.network,
14387
15996
  ")"
14388
15997
  ] }),
14389
- /* @__PURE__ */ jsx17(Text14, { children: `status: ${String(result.responseStatus)}` }),
14390
- /* @__PURE__ */ jsx17(Text14, { children: `transaction: ${result.transactionId}` }),
14391
- /* @__PURE__ */ jsx17(Text14, { children: `approval: ${result.approvalId}` }),
14392
- /* @__PURE__ */ jsx17(Text14, { children: `approval url: ${result.approvalUrl}` }),
14393
- /* @__PURE__ */ jsx17(Text14, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14394
- result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs13(Text14, { children: [
15998
+ /* @__PURE__ */ jsx24(Text20, { children: `transaction: ${result.transactionId}` }),
15999
+ /* @__PURE__ */ jsx24(Text20, { children: `approval: ${result.approvalId}` }),
16000
+ /* @__PURE__ */ jsx24(Text20, { children: `approval url: ${result.approvalUrl}` }),
16001
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14395
16002
  "Saved to: ",
14396
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
16003
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14397
16004
  ] }) : null,
14398
- result.body !== void 0 ? /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
14399
- /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "response body:" }),
14400
- /* @__PURE__ */ jsx17(Text14, { children: result.body })
16005
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
16006
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
16007
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14401
16008
  ] }) : null
14402
16009
  ] });
14403
16010
  }
14404
16011
  if (phase.kind === "no-payment-final") {
14405
16012
  const { result } = phase;
14406
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14407
- /* @__PURE__ */ jsx17(Text14, { color: "green", children: "\u2713 Seller accepted without payment" }),
14408
- /* @__PURE__ */ jsx17(Text14, { children: `status: ${String(result.status)}` }),
14409
- /* @__PURE__ */ jsx17(Text14, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14410
- result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs13(Text14, { children: [
16013
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
16014
+ /* @__PURE__ */ jsx24(Text20, { color: "green", children: "\u2713 Seller accepted without payment" }),
16015
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14411
16016
  "Saved to: ",
14412
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
16017
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14413
16018
  ] }) : null,
14414
- result.body !== void 0 ? /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
14415
- /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "response body:" }),
14416
- /* @__PURE__ */ jsx17(Text14, { children: result.body })
16019
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
16020
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
16021
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14417
16022
  ] }) : null
14418
16023
  ] });
14419
16024
  }
14420
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14421
- /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
16025
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
16026
+ /* @__PURE__ */ jsxs19(Text20, { color: "red", children: [
14422
16027
  "\u2717 ",
14423
16028
  phase.code
14424
16029
  ] }),
14425
- /* @__PURE__ */ jsx17(Text14, { color: "red", children: phase.message })
16030
+ /* @__PURE__ */ jsx24(Text20, { color: "red", children: phase.message })
14426
16031
  ] });
14427
16032
  };
14428
16033
 
14429
16034
  // src/commands/x402/schema.ts
14430
- import { z as z5 } from "incur";
14431
- var payArgs = z5.object({
14432
- url: z5.string().describe("The x402-protected resource URL to pay for.")
16035
+ import { z as z6 } from "incur";
16036
+ var payArgs2 = z6.object({
16037
+ url: z6.string().describe("The x402-protected resource URL to pay for.")
14433
16038
  });
14434
- var payOptions = z5.object({
14435
- method: z5.string().default("GET").describe("HTTP method for the seller request."),
14436
- data: z5.string().optional().describe(
16039
+ var payOptions2 = z6.object({
16040
+ scheme: z6.string().optional().describe('Only consider `accepts[]` entries with this scheme (e.g. "exact", "balance").'),
16041
+ network: z6.string().optional().describe('Only consider entries on this network (e.g. "eip155:84532").'),
16042
+ asset: z6.string().optional().describe("Only consider entries with this on-chain asset id (ERC-20 address or SVM mint)."),
16043
+ assetName: z6.string().optional().describe('Only consider entries whose `extra.assetName` symbol matches (e.g. "USDC").'),
16044
+ method: z6.string().default("GET").describe("HTTP method for the seller request."),
16045
+ data: z6.string().optional().describe(
14437
16046
  "Request body. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
14438
16047
  ),
14439
- header: z5.array(z5.string()).default([]).describe('Repeatable. "Name: Value" format.'),
14440
- interval: z5.coerce.number().default(0).describe(
16048
+ header: z6.array(z6.string()).default([]).describe('Repeatable. "Name: Value" format.'),
16049
+ interval: z6.coerce.number().default(0).describe(
14441
16050
  "Inline poll cadence in seconds while awaiting approval. 0 returns the approval URL and a follow-up command hint without blocking."
14442
16051
  ),
14443
- maxAttempts: z5.coerce.number().default(0).describe("Hard cap on poll attempts when --interval > 0. 0 means unlimited."),
14444
- timeout: z5.coerce.number().default(900).describe("Polling deadline in seconds. Default 900s (matches x402-buyer)."),
14445
- paymentId: z5.string().min(16).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional().describe(
16052
+ maxAttempts: z6.coerce.number().default(0).describe("Hard cap on poll attempts when --interval > 0. 0 means unlimited."),
16053
+ timeout: z6.coerce.number().default(900).describe("Polling deadline in seconds. Default 900s (matches x402-buyer)."),
16054
+ paymentId: z6.string().min(16).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional().describe(
14446
16055
  "Caller-supplied payment identifier. 16-128 chars, ^[a-zA-Z0-9_-]+$. Forwarded to the server as remotePaymentId."
14447
16056
  ),
14448
- showBody: z5.boolean().default(true).describe(
16057
+ showBody: z6.boolean().default(true).describe(
14449
16058
  "Include the seller response body in the result. Default true so AI assistants paying for content receive the deliverable. Pass --no-show-body to suppress (e.g. for binary downloads paired with --output-file)."
14450
16059
  ),
14451
- outputFile: z5.string().optional().describe(
16060
+ outputFile: z6.string().optional().describe(
14452
16061
  "Write the seller response body bytes to this file path (overwrites silently). When set, the result frame includes `output_saved_to: <absolute_path>` instead of `body` / `body_base64`. Natural choice for binary content (PDFs, images, downloads)."
14453
16062
  ),
14454
- payloadFile: z5.string().optional().describe(
16063
+ payloadFile: z6.string().optional().describe(
14455
16064
  "Write the signed `encoded_payload` bytes to this file path (mode 0o600, overwrites silently). When set, the result frame includes `payload_saved_to: <absolute_path>` instead of `encoded_payload`. Use to keep one-time payment credentials out of chat transcripts and logs."
14456
- ),
14457
- scheme: z5.string().optional().describe(
14458
- 'Constrain selection to seller `accepts[]` entries whose `scheme` matches exactly (e.g. "balance", "exact"). Combine with --network/--asset/--asset-name for a tighter filter; any flag can be set independently. When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH instead of falling through to the buyer\'s prefer order.'
14459
- ),
14460
- network: z5.string().optional().describe(
14461
- 'Constrain selection to seller `accepts[]` entries whose `network` matches exactly (e.g. "inflow:1", "eip155:84532", "solana:..."). Combine with --scheme/--asset/--asset-name for a tighter filter. When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH.'
14462
- ),
14463
- asset: z5.string().optional().describe(
14464
- "Constrain selection to seller `accepts[]` entries whose `asset` matches exactly \u2014 the on-chain asset identifier (ERC-20 contract address for EVM, mint pubkey for SVM). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH."
14465
- ),
14466
- assetName: z5.string().optional().describe(
14467
- 'Constrain selection to seller `accepts[]` entries whose `extra.name` matches exactly \u2014 the human-readable symbol/name the seller advertises (e.g. "USDC"). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH.'
14468
16065
  )
14469
16066
  });
14470
- var statusArgs = z5.object({
14471
- transactionId: z5.string().describe("The transaction id returned by `x402 pay`.")
16067
+ var statusArgs2 = z6.object({
16068
+ transactionId: z6.string().describe("The transaction id returned by `x402 pay`.")
14472
16069
  });
14473
- var statusOptions2 = z5.object({
14474
- interval: z5.coerce.number().default(0).describe(
16070
+ var statusOptions3 = z6.object({
16071
+ interval: z6.coerce.number().default(0).describe(
14475
16072
  "Poll cadence in seconds. 0 returns the current snapshot; positive values yield on every change until signed or terminal."
14476
16073
  ),
14477
- maxAttempts: z5.coerce.number().default(0).describe("Hard cap on poll attempts. 0 means unlimited."),
14478
- timeout: z5.coerce.number().default(900).describe("Polling deadline in seconds."),
14479
- payloadFile: z5.string().optional().describe(
16074
+ maxAttempts: z6.coerce.number().default(0).describe("Hard cap on poll attempts. 0 means unlimited."),
16075
+ timeout: z6.coerce.number().default(900).describe("Polling deadline in seconds."),
16076
+ payloadFile: z6.string().optional().describe(
14480
16077
  "Write the signed `encoded_payload` bytes to this file path (mode 0o600, overwrites silently). When set, status frames include `payload_saved_to: <absolute_path>` instead of `encoded_payload`. Use to keep one-time payment credentials out of chat transcripts and logs."
14481
16078
  )
14482
16079
  });
14483
- var cancelArgs = z5.object({
14484
- approvalId: z5.string().describe("The approval id returned by `x402 pay`.")
16080
+ var cancelArgs2 = z6.object({
16081
+ approvalId: z6.string().describe("The approval id returned by `x402 pay`.")
14485
16082
  });
14486
- var decodeArgs = z5.object({
14487
- header: z5.string().describe("Raw PAYMENT-REQUIRED header value (base64).")
16083
+ var decodeArgs2 = z6.object({
16084
+ header: z6.string().describe("Raw PAYMENT-REQUIRED header value (base64).")
14488
16085
  });
14489
- var inspectArgs = z5.object({
14490
- url: z5.string().describe("The x402-protected resource URL to probe. No payment is made.")
16086
+ var inspectArgs2 = z6.object({
16087
+ url: z6.string().describe("The x402-protected resource URL to probe. No payment is made.")
14491
16088
  });
14492
- var inspectOptions = z5.object({
14493
- method: z5.string().default("GET").describe("HTTP method for the probe request."),
14494
- data: z5.string().optional().describe(
16089
+ var inspectOptions2 = z6.object({
16090
+ scheme: z6.string().optional().describe('Only show `accepts[]` entries with this scheme (e.g. "exact", "balance").'),
16091
+ network: z6.string().optional().describe('Only show entries on this network (e.g. "eip155:84532").'),
16092
+ asset: z6.string().optional().describe("Only show entries with this on-chain asset id (ERC-20 address or SVM mint)."),
16093
+ assetName: z6.string().optional().describe('Only show entries whose `extra.assetName` symbol matches (e.g. "USDC").'),
16094
+ method: z6.string().default("GET").describe("HTTP method for the probe request."),
16095
+ data: z6.string().optional().describe(
14495
16096
  "Request body for the probe. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
14496
16097
  ),
14497
- header: z5.array(z5.string()).default([]).describe('Repeatable. "Name: Value" format.'),
14498
- scheme: z5.string().optional().describe(
14499
- 'Constrain the rendered accepts to entries whose `scheme` matches exactly (e.g. "balance", "exact"). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH.'
14500
- ),
14501
- network: z5.string().optional().describe(
14502
- 'Constrain the rendered accepts to entries whose `network` matches exactly (e.g. "inflow:1", "eip155:84532"). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH.'
14503
- ),
14504
- asset: z5.string().optional().describe(
14505
- "Constrain the rendered accepts to entries whose `asset` matches exactly \u2014 the on-chain asset identifier (ERC-20 contract address for EVM, mint pubkey for SVM). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH."
14506
- ),
14507
- assetName: z5.string().optional().describe(
14508
- 'Constrain the rendered accepts to entries whose `extra.name` matches exactly \u2014 the human-readable symbol/name the seller advertises (e.g. "USDC"). When the filter empties the accepts list, the command fails with NO_FILTERED_MATCH.'
14509
- )
16098
+ header: z6.array(z6.string()).default([]).describe('Repeatable. "Name: Value" format.')
14510
16099
  });
14511
16100
 
14512
16101
  // src/commands/x402/status.tsx
14513
- import { Box as Box14, Text as Text15, useApp as useApp12 } from "ink";
14514
- import Spinner11 from "ink-spinner";
14515
- import { useEffect as useEffect8, useReducer as useReducer6 } from "react";
14516
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
16102
+ import { Box as Box20, Text as Text21 } from "ink";
16103
+ import Spinner16 from "ink-spinner";
16104
+ import { useEffect as useEffect11, useReducer as useReducer9 } from "react";
16105
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
14517
16106
  var X402StatusView = ({
14518
16107
  transactionId,
14519
16108
  fetchOnce,
@@ -14522,10 +16111,10 @@ var X402StatusView = ({
14522
16111
  timeout,
14523
16112
  onComplete
14524
16113
  }) => {
14525
- const { exit } = useApp12();
14526
16114
  const initial = { kind: "polling" };
14527
- const [phase, dispatch] = useReducer6(reduceX402Status, initial);
14528
- useEffect8(() => {
16115
+ const [phase, dispatch] = useReducer9(reduceX402Status, initial);
16116
+ const { finish } = useFlowExit(onComplete);
16117
+ useEffect11(() => {
14529
16118
  const run = runX402Status({ fetchOnce, interval, maxAttempts, timeout });
14530
16119
  let cancelled = false;
14531
16120
  void (async () => {
@@ -14538,16 +16127,15 @@ var X402StatusView = ({
14538
16127
  cancelled = true;
14539
16128
  };
14540
16129
  }, [fetchOnce, interval, maxAttempts, timeout]);
14541
- useEffect8(() => {
16130
+ useEffect11(() => {
14542
16131
  if (phase.kind === "signed" || phase.kind === "failed" || phase.kind === "timeout" || phase.kind === "error") {
14543
- onComplete(phase);
14544
- exit();
16132
+ finish(phase);
14545
16133
  }
14546
- }, [phase, onComplete, exit]);
16134
+ }, [phase, finish]);
14547
16135
  if (phase.kind === "polling") {
14548
16136
  const statusText = phase.latest?.status ?? "pending";
14549
- return /* @__PURE__ */ jsx18(Box14, { flexDirection: "column", children: /* @__PURE__ */ jsxs14(Text15, { color: "cyan", children: [
14550
- /* @__PURE__ */ jsx18(Spinner11, { type: "dots" }),
16137
+ return /* @__PURE__ */ jsx25(Box20, { flexDirection: "column", children: /* @__PURE__ */ jsxs20(Text21, { color: "cyan", children: [
16138
+ /* @__PURE__ */ jsx25(Spinner16, { type: "dots" }),
14551
16139
  " Polling transaction ",
14552
16140
  transactionId,
14553
16141
  " (status: ",
@@ -14558,80 +16146,73 @@ var X402StatusView = ({
14558
16146
  if (phase.kind === "signed") {
14559
16147
  const encoded = phase.response.encodedPayload ?? "";
14560
16148
  const preview = encoded.length > 32 ? `${encoded.slice(0, 32)}...` : encoded;
14561
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14562
- /* @__PURE__ */ jsx18(Text15, { color: "green", children: "\u2713 Signed" }),
14563
- /* @__PURE__ */ jsx18(Text15, { children: `status: ${phase.response.status}` }),
14564
- /* @__PURE__ */ jsx18(Text15, { children: `encodedPayload: ${preview}` })
16149
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16150
+ /* @__PURE__ */ jsx25(Text21, { color: "green", children: "\u2713 Signed" }),
16151
+ /* @__PURE__ */ jsx25(Text21, { children: `status: ${phase.response.status}` }),
16152
+ /* @__PURE__ */ jsx25(Text21, { children: `encodedPayload: ${preview}` })
14565
16153
  ] });
14566
16154
  }
14567
16155
  if (phase.kind === "failed") {
14568
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14569
- /* @__PURE__ */ jsx18(Text15, { color: "red", children: "\u2717 Approval did not settle" }),
14570
- /* @__PURE__ */ jsx18(Text15, { color: "red", children: `status: ${phase.response.status}` })
16156
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16157
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: "\u2717 Approval did not settle" }),
16158
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: `status: ${phase.response.status}` })
14571
16159
  ] });
14572
16160
  }
14573
16161
  if (phase.kind === "timeout") {
14574
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14575
- /* @__PURE__ */ jsx18(Text15, { color: "yellow", children: "Polling timed out before the transaction reached a signed state." }),
14576
- phase.response !== void 0 ? /* @__PURE__ */ jsx18(Text15, { children: `last status: ${phase.response.status}` }) : null
16162
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16163
+ /* @__PURE__ */ jsx25(Text21, { color: "yellow", children: "Polling timed out before the transaction reached a signed state." }),
16164
+ phase.response !== void 0 ? /* @__PURE__ */ jsx25(Text21, { children: `last status: ${phase.response.status}` }) : null
14577
16165
  ] });
14578
16166
  }
14579
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14580
- /* @__PURE__ */ jsx18(Text15, { color: "red", children: "\u2717 Polling failed" }),
14581
- /* @__PURE__ */ jsx18(Text15, { color: "red", children: phase.message })
16167
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16168
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: "\u2717 Polling failed" }),
16169
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: phase.message })
14582
16170
  ] });
14583
16171
  };
14584
16172
 
14585
16173
  // src/commands/x402/supported.tsx
14586
- import { Box as Box15, Text as Text16, useApp as useApp13 } from "ink";
14587
- import Spinner12 from "ink-spinner";
14588
- import { useCallback as useCallback6 } from "react";
14589
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
14590
- var COLUMNS4 = [
16174
+ import { Box as Box21, Text as Text22 } from "ink";
16175
+ import Spinner17 from "ink-spinner";
16176
+ import { useCallback as useCallback9 } from "react";
16177
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
16178
+ var COLUMNS6 = [
14591
16179
  { header: "Scheme", cell: (k) => k.scheme },
14592
16180
  { header: "Network", cell: (k) => k.network }
14593
16181
  ];
14594
- var SupportedView = ({ load, onComplete }) => {
14595
- const { exit } = useApp13();
14596
- const action = useCallback6(() => load(), [load]);
14597
- const handleComplete = useCallback6(
14598
- (result) => {
14599
- onComplete(result);
14600
- exit();
14601
- },
14602
- [onComplete, exit]
14603
- );
14604
- const { status, data, error } = useFlowState(action, handleComplete);
16182
+ var SupportedView2 = ({ load, onComplete }) => {
16183
+ const action = useCallback9(() => load(), [load]);
16184
+ const { finish } = useFlowExit(onComplete);
16185
+ const { status, data, error } = useFlowState(action, finish);
14605
16186
  if (status === "loading") {
14606
- return /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsxs15(Text16, { color: "cyan", children: [
14607
- /* @__PURE__ */ jsx19(Spinner12, { type: "dots" }),
16187
+ return /* @__PURE__ */ jsx26(Box21, { children: /* @__PURE__ */ jsxs21(Text22, { color: "cyan", children: [
16188
+ /* @__PURE__ */ jsx26(Spinner17, { type: "dots" }),
14608
16189
  " Loading supported schemes..."
14609
16190
  ] }) });
14610
16191
  }
14611
16192
  if (status === "error") {
14612
- return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
14613
- /* @__PURE__ */ jsx19(Text16, { color: "red", children: "Failed to load supported schemes" }),
14614
- /* @__PURE__ */ jsx19(Text16, { color: "red", children: error })
16193
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
16194
+ /* @__PURE__ */ jsx26(Text22, { color: "red", children: "Failed to load supported schemes" }),
16195
+ /* @__PURE__ */ jsx26(Text22, { color: "red", children: error })
14615
16196
  ] });
14616
16197
  }
14617
16198
  const kinds = data?.kinds ?? [];
14618
16199
  if (kinds.length === 0) {
14619
- return /* @__PURE__ */ jsx19(Box15, { flexDirection: "column", children: /* @__PURE__ */ jsx19(Text16, { children: "No supported (scheme, network) pairs returned for this account." }) });
16200
+ return /* @__PURE__ */ jsx26(Box21, { flexDirection: "column", children: /* @__PURE__ */ jsx26(Text22, { children: "No supported (scheme, network) pairs returned for this account." }) });
14620
16201
  }
14621
- return /* @__PURE__ */ jsx19(Table, { columns: COLUMNS4, rows: kinds });
16202
+ return /* @__PURE__ */ jsx26(Table, { columns: COLUMNS6, rows: kinds });
14622
16203
  };
14623
16204
 
14624
16205
  // src/commands/x402/index.tsx
14625
- import { jsx as jsx20 } from "react/jsx-runtime";
16206
+ import { jsx as jsx27 } from "react/jsx-runtime";
14626
16207
  var POST_PAY_INSTRUCTION = "Present the approval_url to the user and ask them to approve in the InFlow mobile app or dashboard. Then call `x402 status <transaction_id> --interval 5 --max-attempts 60` to poll until signed. Once the transaction is signed, replay the request manually using the encoded_payload from the status response as the PAYMENT-SIGNATURE header.";
14627
- var POLLING_INSTRUCTION = "Approval polling is happening inline. The yield stream emits each status change; the final frame includes the encoded_payload when signing completes.";
16208
+ var POLLING_INSTRUCTION2 = "Approval polling is happening inline. The yield stream emits each status change; the final frame includes the encoded_payload when signing completes.";
14628
16209
  function buildSignOptions(options) {
14629
16210
  const out = { timeoutMs: options.timeout * 1e3 };
14630
16211
  if (options.interval > 0) out.pollIntervalMs = options.interval * 1e3;
14631
16212
  if (options.paymentId !== void 0) out.paymentId = options.paymentId;
14632
16213
  return out;
14633
16214
  }
14634
- function parseHeaderFlagsOrFail(c, flags) {
16215
+ function parseHeaderFlagsOrFail2(c, flags) {
14635
16216
  try {
14636
16217
  return parseHeaderFlags(flags);
14637
16218
  } catch (err) {
@@ -14643,16 +16224,16 @@ function parseHeaderFlagsOrFail(c, flags) {
14643
16224
  }
14644
16225
  function decoratePayloadField(frame, encoded, payloadFile) {
14645
16226
  if (payloadFile !== void 0 && payloadFile.length > 0) {
14646
- const absolute = resolvePath2(payloadFile);
14647
- writeFileSync2(absolute, Buffer.from(encoded, "utf-8"), { mode: 384 });
14648
- chmodSync(absolute, 384);
16227
+ const absolute = resolvePath3(payloadFile);
16228
+ writeFileSync3(absolute, Buffer.from(encoded, "utf-8"), { mode: 384 });
16229
+ chmodSync2(absolute, 384);
14649
16230
  frame.payload_saved_to = absolute;
14650
16231
  return;
14651
16232
  }
14652
16233
  frame.encoded_payload = encoded;
14653
16234
  }
14654
- function buildPayPipelineInput(c) {
14655
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16235
+ function buildPayPipelineInput2(c) {
16236
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14656
16237
  const probeOptions = {
14657
16238
  method: c.options.method,
14658
16239
  headers: probeHeaders,
@@ -14670,19 +16251,19 @@ function buildPayPipelineInput(c) {
14670
16251
  ...c.options.assetName !== void 0 ? { assetNameFilter: c.options.assetName } : {}
14671
16252
  };
14672
16253
  }
14673
- function attachBodyFields(frame, result) {
16254
+ function attachBodyFields2(frame, result) {
14674
16255
  frame.body_size_bytes = result.bodySizeBytes;
14675
16256
  if (result.body !== void 0) frame.body = result.body;
14676
16257
  if (result.bodyBase64 !== void 0) frame.body_base64 = result.bodyBase64;
14677
16258
  if (result.outputSavedTo !== void 0) frame.output_saved_to = result.outputSavedTo;
14678
16259
  }
14679
- function noPaymentFrameFromResult(result) {
16260
+ function noPaymentFrameFromResult2(result) {
14680
16261
  const frame = {
14681
16262
  outcome: "no-payment-required",
14682
16263
  status: result.status
14683
16264
  };
14684
16265
  if (result.contentType !== void 0) frame.content_type = result.contentType;
14685
- attachBodyFields(frame, result);
16266
+ attachBodyFields2(frame, result);
14686
16267
  return frame;
14687
16268
  }
14688
16269
  function initialPayFrame(event, interval, maxAttempts) {
@@ -14693,7 +16274,7 @@ function initialPayFrame(event, interval, maxAttempts) {
14693
16274
  resource: event.decoded.resource.url,
14694
16275
  scheme: event.requirement.scheme,
14695
16276
  network: event.requirement.network,
14696
- instruction: interval > 0 ? POLLING_INSTRUCTION : POST_PAY_INSTRUCTION
16277
+ instruction: interval > 0 ? POLLING_INSTRUCTION2 : POST_PAY_INSTRUCTION
14697
16278
  };
14698
16279
  if (event.requirement.amount !== "") frame.amount = event.requirement.amount;
14699
16280
  if (event.requirement.asset !== "") frame.asset = event.requirement.asset;
@@ -14707,7 +16288,7 @@ function initialPayFrame(event, interval, maxAttempts) {
14707
16288
  }
14708
16289
  return frame;
14709
16290
  }
14710
- function paidFrameFromResult(result, payloadFile) {
16291
+ function paidFrameFromResult2(result, payloadFile) {
14711
16292
  const frame = {
14712
16293
  outcome: "paid",
14713
16294
  transaction_id: result.transactionId,
@@ -14719,10 +16300,10 @@ function paidFrameFromResult(result, payloadFile) {
14719
16300
  decoratePayloadField(frame, result.encodedPayload, payloadFile);
14720
16301
  if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
14721
16302
  if (result.settled !== void 0) frame.settled = result.settled;
14722
- attachBodyFields(frame, result);
16303
+ attachBodyFields2(frame, result);
14723
16304
  return frame;
14724
16305
  }
14725
- function rejectedFrameFromResult(result) {
16306
+ function rejectedFrameFromResult2(result) {
14726
16307
  const frame = {
14727
16308
  outcome: "replay-rejected",
14728
16309
  transaction_id: result.transactionId,
@@ -14733,14 +16314,14 @@ function rejectedFrameFromResult(result) {
14733
16314
  response_status: result.responseStatus
14734
16315
  };
14735
16316
  if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
14736
- attachBodyFields(frame, result);
16317
+ attachBodyFields2(frame, result);
14737
16318
  return frame;
14738
16319
  }
14739
- async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
16320
+ async function* runPayCommand2(c, inflow2, authStorage2, apiBaseUrl2) {
14740
16321
  assertSessionGuard(c, authStorage2, inflow2);
14741
16322
  if (!c.agent && !c.formatExplicit) {
14742
16323
  const client = await inflow2.x402.client();
14743
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16324
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14744
16325
  const probeOptions = {
14745
16326
  method: c.options.method,
14746
16327
  headers: probeHeaders,
@@ -14748,8 +16329,8 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14748
16329
  };
14749
16330
  let finalPhase = null;
14750
16331
  await renderInkUntilExit(
14751
- /* @__PURE__ */ jsx20(
14752
- PayView,
16332
+ /* @__PURE__ */ jsx27(
16333
+ PayView2,
14753
16334
  {
14754
16335
  url: c.args.url,
14755
16336
  method: c.options.method,
@@ -14768,7 +16349,8 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14768
16349
  },
14769
16350
  onComplete: (phase) => {
14770
16351
  finalPhase = phase;
14771
- }
16352
+ },
16353
+ onCancel: (approvalId) => inflow2.x402.cancel({ approvalId })
14772
16354
  }
14773
16355
  )
14774
16356
  );
@@ -14787,12 +16369,12 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14787
16369
  return;
14788
16370
  }
14789
16371
  const run = inflow2.x402.pay({
14790
- ...buildPayPipelineInput(c),
16372
+ ...buildPayPipelineInput2(c),
14791
16373
  awaitPayment: c.options.interval > 0
14792
16374
  });
14793
16375
  for await (const event of run.events) {
14794
16376
  if (event.type === "short-circuited") {
14795
- yield sanitizeDeep(noPaymentFrameFromResult(event.result));
16377
+ yield sanitizeDeep(noPaymentFrameFromResult2(event.result));
14796
16378
  return;
14797
16379
  }
14798
16380
  if (event.type === "prepared") {
@@ -14800,11 +16382,11 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14800
16382
  continue;
14801
16383
  }
14802
16384
  if (event.type === "replayed") {
14803
- yield sanitizeDeep(paidFrameFromResult(event.result, c.options.payloadFile));
16385
+ yield sanitizeDeep(paidFrameFromResult2(event.result, c.options.payloadFile));
14804
16386
  return;
14805
16387
  }
14806
16388
  if (event.type === "rejected") {
14807
- yield sanitizeDeep(rejectedFrameFromResult(event.result));
16389
+ yield sanitizeDeep(rejectedFrameFromResult2(event.result));
14808
16390
  c.error({
14809
16391
  code: PAYMENT_NOT_ACCEPTED_CODE,
14810
16392
  message: `Seller rejected the signed payment with status ${String(event.result.responseStatus)}. The approval was completed but the seller did not honour the payment; see approval_url in the previous frame for details.`
@@ -14816,12 +16398,12 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14816
16398
  }
14817
16399
  }
14818
16400
  }
14819
- async function* runStatusCommand(c, inflow2, authStorage2) {
16401
+ async function* runStatusCommand2(c, inflow2, authStorage2) {
14820
16402
  assertSessionGuard(c, authStorage2, inflow2);
14821
16403
  if (!c.agent && !c.formatExplicit) {
14822
16404
  const client2 = await inflow2.x402.client();
14823
16405
  await renderInkUntilExit(
14824
- /* @__PURE__ */ jsx20(
16406
+ /* @__PURE__ */ jsx27(
14825
16407
  X402StatusView,
14826
16408
  {
14827
16409
  transactionId: c.args.transactionId,
@@ -14839,7 +16421,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14839
16421
  const fetchOnce = () => client.getX402Payload(c.args.transactionId);
14840
16422
  if (c.options.interval <= 0) {
14841
16423
  const snapshot = await fetchOnce();
14842
- yield sanitizeDeep(toStatusFrame(c.args.transactionId, snapshot, c.options.payloadFile));
16424
+ yield sanitizeDeep(toStatusFrame2(c.args.transactionId, snapshot, c.options.payloadFile));
14843
16425
  return;
14844
16426
  }
14845
16427
  const generator = pollAsync({
@@ -14851,7 +16433,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14851
16433
  timeout: c.options.timeout
14852
16434
  });
14853
16435
  for await (const outcome of generator) {
14854
- yield sanitizeDeep(toStatusFrame(c.args.transactionId, outcome.value, c.options.payloadFile));
16436
+ yield sanitizeDeep(toStatusFrame2(c.args.transactionId, outcome.value, c.options.payloadFile));
14855
16437
  if (!outcome.terminal) continue;
14856
16438
  if (outcome.reason !== void 0) {
14857
16439
  c.error({
@@ -14869,7 +16451,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14869
16451
  return;
14870
16452
  }
14871
16453
  }
14872
- function toStatusFrame(transactionId, response, payloadFile) {
16454
+ function toStatusFrame2(transactionId, response, payloadFile) {
14873
16455
  const frame = {
14874
16456
  transaction_id: transactionId,
14875
16457
  status: response.status
@@ -14880,12 +16462,12 @@ function toStatusFrame(transactionId, response, payloadFile) {
14880
16462
  if (response.paymentPayload !== void 0) frame.payment_payload = response.paymentPayload;
14881
16463
  return frame;
14882
16464
  }
14883
- async function runCancelCommand(c, inflow2, authStorage2) {
16465
+ async function runCancelCommand2(c, inflow2, authStorage2) {
14884
16466
  assertSessionGuard(c, authStorage2, inflow2);
14885
16467
  if (!c.agent && !c.formatExplicit) {
14886
16468
  await renderInkUntilExit(
14887
- /* @__PURE__ */ jsx20(
14888
- CancelView,
16469
+ /* @__PURE__ */ jsx27(
16470
+ CancelView2,
14889
16471
  {
14890
16472
  approvalId: c.args.approvalId,
14891
16473
  cancel: () => inflow2.x402.cancel({ approvalId: c.args.approvalId }).then(() => void 0),
@@ -14901,7 +16483,7 @@ async function runCancelCommand(c, inflow2, authStorage2) {
14901
16483
  }
14902
16484
  return inflow2.x402.cancel({ approvalId: c.args.approvalId });
14903
16485
  }
14904
- async function runDecodeCommand(c) {
16486
+ async function runDecodeCommand2(c) {
14905
16487
  let decoded;
14906
16488
  try {
14907
16489
  decoded = decodeHeader(c.args.header);
@@ -14912,22 +16494,22 @@ async function runDecodeCommand(c) {
14912
16494
  });
14913
16495
  }
14914
16496
  if (!c.agent && !c.formatExplicit) {
14915
- await renderInkUntilExit(/* @__PURE__ */ jsx20(DecodeView, { decoded }));
16497
+ await renderInkUntilExit(/* @__PURE__ */ jsx27(DecodeView2, { decoded }));
14916
16498
  return void 0;
14917
16499
  }
14918
16500
  return sanitizeDeep(decoded);
14919
16501
  }
14920
- async function runSupportedCommand(c, inflow2, authStorage2) {
16502
+ async function runSupportedCommand2(c, inflow2, authStorage2) {
14921
16503
  assertSessionGuard(c, authStorage2, inflow2);
14922
16504
  if (!c.agent && !c.formatExplicit) {
14923
- await renderInkUntilExit(/* @__PURE__ */ jsx20(SupportedView, { load: () => inflow2.x402.supported(), onComplete: () => void 0 }));
16505
+ await renderInkUntilExit(/* @__PURE__ */ jsx27(SupportedView2, { load: () => inflow2.x402.supported(), onComplete: () => void 0 }));
14924
16506
  return void 0;
14925
16507
  }
14926
16508
  const response = await inflow2.x402.supported();
14927
16509
  return sanitizeDeep(response);
14928
16510
  }
14929
- async function runInspectCommand(c) {
14930
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16511
+ async function runInspectCommand2(c) {
16512
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14931
16513
  const probeOptions = {
14932
16514
  method: c.options.method,
14933
16515
  headers: probeHeaders,
@@ -14944,8 +16526,8 @@ async function runInspectCommand(c) {
14944
16526
  if (!c.agent && !c.formatExplicit) {
14945
16527
  let finalPhase = null;
14946
16528
  await renderInkUntilExit(
14947
- /* @__PURE__ */ jsx20(
14948
- InspectView,
16529
+ /* @__PURE__ */ jsx27(
16530
+ InspectView2,
14949
16531
  {
14950
16532
  url: c.args.url,
14951
16533
  method: c.options.method,
@@ -14989,60 +16571,60 @@ async function runInspectCommand(c) {
14989
16571
  if (kind === "accepts") {
14990
16572
  return sanitizeDeep(buildAcceptsFrame(payload));
14991
16573
  }
14992
- return sanitizeDeep(buildNoPaymentFrame(payload));
16574
+ return sanitizeDeep(buildNoPaymentFrame2(payload));
14993
16575
  }
14994
16576
  function createX402Cli(inflow2, authStorage2, apiBaseUrl2) {
14995
- const cli2 = Cli5.create("x402", {
16577
+ const cli2 = Cli6.create("x402", {
14996
16578
  description: "x402 payment commands (pay, inspect, status, cancel, decode, supported)."
14997
16579
  });
14998
16580
  cli2.command("pay", {
14999
16581
  description: "Pay an x402-protected resource and return the seller response.",
15000
- args: payArgs,
15001
- options: payOptions,
16582
+ args: payArgs2,
16583
+ options: payOptions2,
15002
16584
  outputPolicy: "agent-only",
15003
16585
  async *run(c) {
15004
- yield* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2);
16586
+ yield* runPayCommand2(c, inflow2, authStorage2, apiBaseUrl2);
15005
16587
  }
15006
16588
  });
15007
16589
  cli2.command("status", {
15008
16590
  description: "Poll the signing state of an in-flight x402 transaction.",
15009
- args: statusArgs,
15010
- options: statusOptions2,
16591
+ args: statusArgs2,
16592
+ options: statusOptions3,
15011
16593
  outputPolicy: "agent-only",
15012
16594
  async *run(c) {
15013
- yield* runStatusCommand(c, inflow2, authStorage2);
16595
+ yield* runStatusCommand2(c, inflow2, authStorage2);
15014
16596
  }
15015
16597
  });
15016
16598
  cli2.command("cancel", {
15017
16599
  description: "Best-effort cancel of an x402 approval.",
15018
- args: cancelArgs,
16600
+ args: cancelArgs2,
15019
16601
  outputPolicy: "agent-only",
15020
16602
  async run(c) {
15021
- return runCancelCommand(c, inflow2, authStorage2);
16603
+ return runCancelCommand2(c, inflow2, authStorage2);
15022
16604
  }
15023
16605
  });
15024
16606
  cli2.command("decode", {
15025
16607
  description: "Decode a raw PAYMENT-REQUIRED header value.",
15026
- args: decodeArgs,
16608
+ args: decodeArgs2,
15027
16609
  outputPolicy: "agent-only",
15028
16610
  async run(c) {
15029
- return runDecodeCommand(c);
16611
+ return runDecodeCommand2(c);
15030
16612
  }
15031
16613
  });
15032
16614
  cli2.command("supported", {
15033
16615
  description: "List the buyer-side capability cache (scheme x network).",
15034
16616
  outputPolicy: "agent-only",
15035
16617
  async run(c) {
15036
- return runSupportedCommand(c, inflow2, authStorage2);
16618
+ return runSupportedCommand2(c, inflow2, authStorage2);
15037
16619
  }
15038
16620
  });
15039
16621
  cli2.command("inspect", {
15040
16622
  description: "Show the seller's PAYMENT-REQUIRED accepts for a URL. Read-only probe \u2014 no auth, no payment.",
15041
- args: inspectArgs,
15042
- options: inspectOptions,
16623
+ args: inspectArgs2,
16624
+ options: inspectOptions2,
15043
16625
  outputPolicy: "agent-only",
15044
16626
  async run(c) {
15045
- return runInspectCommand(c);
16627
+ return runInspectCommand2(c);
15046
16628
  }
15047
16629
  });
15048
16630
  return cli2;
@@ -15116,9 +16698,9 @@ function formatUpdateNotice(info) {
15116
16698
  }
15117
16699
 
15118
16700
  // src/cli.tsx
15119
- var cliVersion = "0.5.2";
16701
+ var cliVersion = "0.6.0";
15120
16702
  var cliName = "@inflowpayai/inflow";
15121
- var skillBody = '# Agentic Payments\n\n## Installing\n\nInstall with `npm install -g @inflowpayai/inflow`. Or run directly with `npx @inflowpayai/inflow`.\n\n## Running\n\nInFlow runs as a **standalone CLI** or an **MCP server**.\n\n**MCP**: add to your MCP client config:\n\n```json\n{\n "mcpServers": {\n "inflow": {\n "command": "npx",\n "args": ["-y", "@inflowpayai/inflow", "--mcp"]\n }\n }\n}\n```\n\nThe `-y` flag suppresses npx\'s confirmation prompt \u2014 without it the MCP host can stall on first run.\n\n**MCP mode** exposes every CLI command as a tool. Call `tools/list` on the MCP server for the authoritative inventory; arguments mirror the CLI flags one-to-one.\n\n### Common commands / options\n\n- `inflow --llms` (or `--llms-full` for parameter detail) \u2014 discover all commands. `inflow <command> --schema` for a single command\'s JSON Schema.\n- `inflow --skill` \u2014 print this playbook (no frontmatter) to stdout. Use it to paste into the system-prompt field of an MCP host that doesn\'t natively load skills: `inflow --skill | pbcopy`.\n- Default output is `toon`. Override with `--format <fmt>`; for programmatic parsing prefer `json` (single document) or `jsonl` (line-delimited). `inflow <command> --schema` enumerates every option for the command.\n- Multi-step flows return `_next.command` \u2014 run it to continue.\n- `--auth <path>` overrides the credentials file location.\n- `--api-key <key>` or `INFLOW_API_KEY=<key>` is an alternative to device-flow auth.\n\n## Core flow\n\n**Sequencing.** Run the steps in order. Don\'t skip ahead \u2014 Step 3 fails or double-charges if Steps 1-2 didn\'t clear. `x402 inspect` and `x402 decode` are read-only and don\'t require auth, so they may run before Step 1 if useful (e.g. when sizing up a paywall before committing the user to a login).\n\nCopy this checklist and track progress:\n\n- Step 1: Authenticate with InFlow.\n- Step 2: Pre-flight evaluation (probe seller, check supported pairs, check balance).\n- Step 3: Pay via x402.\n\n### Step 1: Authenticate\n\nCheck the current state first \u2014 the user may already be logged in:\n\n```bash\ninflow auth status\n```\n\nAuthenticated response shape (`access_token` is a 20-char preview, not the full token):\n\n```json\n{\n "authenticated": true,\n "auth_method": "device_token",\n "access_token": "inf_3LtKpQ7nWxYzA1bC...",\n "credentials_path": "/Users/.../inflow/auth.json",\n "connection": { "environment": "production", "apiBaseUrl": "https://api.inflowpay.ai" },\n "update": { "current": "0.4.6", "latest": "0.5.1" }\n}\n```\n\n`auth_method` is `device_token` or `api_key`. For the user\'s identity (email, handle, account id), call `inflow user get` \u2014 `auth status` deliberately doesn\'t include it.\n\nIf the response includes an `update` field, a newer version of `inflow` is published.\n\n**Surface and defer.** Tell the user a newer version is available and how to upgrade \u2014 `npm install -g @inflowpayai/inflow@latest` (or `npx @inflowpayai/inflow@latest`). Then **proceed with the current version**. Only block on the upgrade if a subsequent command fails with `VERSION_UNSUPPORTED` (or an HTTP 426 from the API), at which point the upgrade is mandatory and you should not retry until it lands.\n\nIf `authenticated` is `false`, start the device flow:\n\n```bash\ninflow auth login --client-name "<your-agent-name>"\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (for example `"Personal Assistant"`, `"Shopping Bot"`). The device-authorization page in the user\'s browser displays this name when they approve the connection. Use a clear, unique, identifiable name.\n\nThe response includes a `_next.command` \u2014 run it immediately to poll until authenticated. **Do not wait for the user to respond before starting the poll.** Example response:\n\n```json\n{\n "verification_url": "https://app.inflowpay.ai/device/?code=ABCD-EFGH",\n "phrase": "ABCD-EFGH",\n "_next": {\n "command": "auth status --interval 5 --max-attempts 60",\n "poll_interval_seconds": 5,\n "until": "authenticated is true"\n }\n}\n```\n\nPresent `verification_url` to the user. Start polling with the `_next.command` immediately \u2014 don\'t wait for them to reply.\n\nIf your environment can\'t relay the verification phrase to the user while a separate polling command blocks I/O, use inline polling instead:\n\n```bash\ninflow auth login --client-name "<name>" --interval 5 --timeout 300\n```\n\n**API key alternative:** if the user provides an API key, set `INFLOW_API_KEY=<key>` in the environment (or pass `--api-key <key>` to any command) instead of running `auth login`. The API key takes precedence over a saved device token.\n\n### Step 2: Pre-flight evaluation\n\nThree commands, in this order:\n\n```bash\n# 1. Probe the seller without paying\ninflow x402 inspect <url>\n# Returns the seller\'s accepts[]: { scheme, network, asset, max_amount_required, extra.name, ... }\n\n# 2. List which scheme \xD7 network pairs the user\'s account supports\ninflow x402 supported\n# Returns: { "kinds": [{ "scheme": "exact", "network": "solana:mainnet" }, ...] }\n\n# 3. Check balances for the candidate asset(s)\ninflow balances list\n# Returns: [{ "available": "100.5", "currency": "USDC" }, ...]\n```\n\n**Shortcut:** If the agent already received a 402 with a `PAYMENT-REQUIRED` header from a prior HTTP call (e.g., the browsing tool hit a paywall), skip step 1 and decode the header directly \u2014 no second probe needed:\n\n```bash\ninflow x402 decode \'<PAYMENT-REQUIRED-header-value>\'\n# Returns the same accepts[] shape as `inspect`, parsed from the header you already have.\n```\n\nNow you have the three facts an agent needs: what the seller wants, what the account can pay with, and whether there\'s enough.\n\n- If `inspect.accepts \u2229 supported.kinds` is empty \u2192 stop with `NO_INFLOW_MATCH`. Tell the user the seller doesn\'t accept any scheme \xD7 network their account supports.\n- If the intersection exists but `balances.available < max_amount_required` for every match \u2192 stop and tell the user to fund the account on a matching network. Run `inflow deposit-addresses list` and surface the deposit address(es) in full.\n- Otherwise: proceed to Step 3.\n\n**Optional filters** to narrow the match before `x402 pay`:\n\n- `--scheme <s>` \u2014 e.g. `exact`, `balance`\n- `--network <n>` \u2014 e.g. `solana:mainnet`, `eip155:84532`, `inflow:1`\n- `--asset <a>` \u2014 on-chain asset identifier (ERC-20 contract address for EVM, mint pubkey for SVM)\n- `--asset-name <name>` \u2014 human-readable symbol the seller advertises (e.g. `USDC`)\n\nWhen any filter empties the accepts list, the command fails with `NO_FILTERED_MATCH` instead of falling through to the buyer\'s default prefer order.\n\n**Decimal precision.** `balances.available` and `max_amount_required` are decimal strings preserving BigDecimal precision. **Never parse them to a JS `Number`** \u2014 that drops precision. Compare as strings, or use a `BigInt` / `decimal.js`-style library.\n\n### Step 3: Pay via x402\n\nDon\'t retry with the same `transaction_id` after `encoded_payload` is consumed \u2014 create a new transaction instead.\n\nBefore initiating the call, summarize the intent to the user in chat: amount, currency, resource URL, scheme, network. The user verifies the canonical details on the approval screen; the chat summary is what they read first. Example:\n\n> "I\'m about to pay 0.10 USDC on Solana mainnet to api.foo.dev for /article-3. Requesting approval next."\n\n**Fast path (recommended).** When the agent can block until the payment finishes, set `--interval N` and let the CLI handle the full flow in one call \u2014 probe, decode, prepare, await approval, replay against the seller, return the body. One tool call, one result:\n\n```bash\ninflow x402 pay <url> --interval 5 --max-attempts 60\n```\n\nThe result includes `outcome: "paid"`, `transaction_id`, `response_status`, `settled`, and the seller body inline (or `output_saved_to` if `--output-file` is set). To surface `approval_url` *before* the call returns (rather than at the end as part of the single result), add `--format jsonl` \u2014 frames stream line-by-line. With the default `json` (or `toon`), the agent only sees the final buffered result.\n\n**Two-step path.** Use this when the agent\'s host can\'t block I/O long enough for the user to approve (chat UIs that need to yield between turns). Drop `--interval`; the first call returns `approval_url` + a `_next.command` for the status poll, and the agent drives the replay itself after `encoded_payload` arrives.\n\n```bash\ninflow x402 pay <url>\n```\n\nFor non-GET requests, pass `--method`, `--data`, `--header` (repeatable):\n\n```bash\ninflow x402 pay https://seller.example.com/api/widgets \\\n --method POST \\\n --data \'{"sku":"widget-1"}\' \\\n --header "X-Custom: value" \\\n --interval 5 --max-attempts 60\n```\n\n**Retries and idempotency.** Set `--payment-id <id>` whenever a retry on transport failure is possible. The server treats two requests with the same `payment-id` as the same logical payment, so a retry after a network blip won\'t double-charge. Format: 16\u2013128 chars, `^[a-zA-Z0-9_-]+$`.\n\n**Discipline:** set `--payment-id` to a stable random opaque value generated once per intent. Reuse the same id on transport retry. Regenerate only when the user explicitly wants a fresh charge. Don\'t tie the id to wall-clock time \u2014 a date-based id silently double-charges on next-day "buy this again" requests.\n\n```bash\ninflow x402 pay <url> --payment-id "<stable-opaque-id>"\n```\n\nWithout `--payment-id`, the server generates one each call \u2014 fine for one-shots, unsafe for retries.\n\n**Sensitive / binary output.** `encoded_payload` (returned by `x402 status` after approval) is a one-time bearer credential \u2014 don\'t echo it back in chat. Use `--payload-file <path>` to write payload bytes to disk at mode `0o600`; the response then carries `payload_saved_to: <path>` in place of `encoded_payload`. For the seller\'s response body, `--output-file <path>` writes bytes to disk and replaces `body` / `body_base64` with `output_saved_to: <path>` \u2014 pair with `--no-show-body` for binary content (PDFs, images, audio, datasets) so bytes never appear inline as base64:\n\n```bash\ninflow x402 pay https://api.foo.dev/report.pdf --interval 5 --max-attempts 60 \\\n --output-file /tmp/report.pdf --no-show-body\n```\n\n**Polling discipline.** Persist `transaction_id` as soon as `x402 pay` returns it. Then:\n\n- Run `_next.command` (or `x402 status <transaction_id> --interval N`) immediately. Don\'t wait for the user to confirm before polling starts.\n- If polling is interrupted \u2014 network drop, session bounce, user kills the agent \u2014 resume with `inflow x402 status <transaction_id> --interval 5 --max-attempts 60`. Only create a new transaction if the original expired (`APPROVAL_TIMEOUT`), was denied/cancelled, or its `encoded_payload` is already consumed.\n- If `POLLING_TIMEOUT` fires before approval, ask the user whether to keep waiting or cancel \u2014 don\'t silently restart the poll.\n- If >12 minutes elapsed without a user response, surface that explicitly so they can act before `APPROVAL_TIMEOUT` lands.\n- If the user aborts ("nevermind", "cancel that"), call `inflow x402 cancel <approval_id>` before exiting. Otherwise the approval sits pending for 15 minutes and triggers phantom notifications in the user\'s InFlow app.\n\nOnce `x402 status` returns `encoded_payload`, replay the original seller request with `PAYMENT-SIGNATURE: <encoded_payload>` (or `PAYMENT-SIGNATURE: $(cat <payload-file>)` if you used `--payload-file`). The seller\'s protected response comes back on the replay.\n\n## Worked example\n\nEnd-to-end: a user asks the agent to fetch a paywalled article at `https://api.foo.dev/article-3`.\n\nAfter running the Step 2 pre-flight commands, the intersection lands on `exact` \xD7 `solana:mainnet`, and the user\'s 100.5 USDC balance easily covers the 0.10 USDC the seller requires. Proceed.\n\n> "I\'m about to pay 0.10 USDC on Solana mainnet to api.foo.dev for /article-3.\n> Your balance is 100.5 USDC \u2014 plenty. Requesting approval next."\n\n```bash\ninflow x402 pay https://api.foo.dev/article-3 \\\n --payment-id "<stable-opaque-id>" --interval 5 --max-attempts 60\n# Persist transaction_id from the response in case polling gets interrupted.\n# -> { "outcome": "paid", "transaction_id": "txn_abc", "response_status": 200,\n# "body": "{ \\"title\\": \\"How to brew coffee\\", ... }", "settled": { ... } }\n```\n\n> "Approval requested \u2014 confirm in the InFlow app: https://app.inflowpay.ai/approvals/appr_xyz\n> I\'ll keep polling. 15-min window."\n\nOnce the result arrives:\n\n> "Paid 0.10 USDC. Transaction txn_abc. Server returned: \'How to brew coffee \u2014 ...\'"\n\n**Two-step variant** (host can\'t block long enough for approval): drop `--interval`, present `approval_url`, run the returned `_next.command`, then replay against the seller yourself \u2014 use `--payload-file <path>` on the status call and `PAYMENT-SIGNATURE: $(cat <path>)` on the replay.\n\n## What to surface when something goes wrong\n\nMatch each terminal failure to a clear user-facing prompt \u2014 don\'t dump the raw error.\n\n- **`APPROVAL_TIMEOUT`** \u2014 "You didn\'t approve within 15 minutes, so the request expired. Want me to start a new payment, or stop here?"\n- **`APPROVAL_FAILED`** (declined / insufficient funds / generic) \u2014 "Approval didn\'t go through (declined or insufficient funds in the matched asset). Want me to try a different funding source, top up, or stop?"\n- **`APPROVAL_CANCELLED`** \u2014 "You cancelled the approval. Stopping here unless you want to start a new payment."\n- **`NO_INFLOW_MATCH`** \u2014 "The seller accepts `<scheme>` on `<network>`, but your account is funded on `<other-network>`. Either fund your account on `<network>`, or pick a different seller."\n- **`NO_FILTERED_MATCH`** \u2014 "Your filter (`--scheme/--network/--asset`) removed every option the seller accepts. Loosen the filter or check the seller\'s `accepts` list with `inflow x402 inspect`."\n- **`POLLING_TIMEOUT`** \u2014 "Still waiting on your approval \u2014 want me to keep polling, or cancel the request? (`inflow x402 cancel <approval_id>` cancels it.)"\n- **`VERSION_UNSUPPORTED` / HTTP 426** \u2014 "The installed `inflow` CLI is below the minimum supported version. Run `npm install -g @inflowpayai/inflow@latest` and re-try."\n\n## Limits\n\n| Limit | Value |\n| ------------------------------ | --------------------------------------------------------------------------------------------------- |\n| Approval window | 15 minutes from `x402 pay` creating the transaction (`--timeout` overrides the polling deadline) |\n| Default polling max-attempts | Unlimited (`--max-attempts 0`). Set a positive cap when you need a hard stop |\n| `--payment-id` format | 16\u2013128 chars, `^[a-zA-Z0-9_-]+$` |\n| `encoded_payload` reuse | One-time. Consumed by the first seller replay. Not reusable \u2014 failed seller calls require a new pay |\n\n## Important\n\n- Treat OAuth tokens and API keys as secrets \u2014 never echo them. The `encoded_payload` returned by `x402 status` is a one-time bearer credential; replay it directly against the seller and discard, don\'t paste it back to the user.\n- Respect `/agents.txt` and `/llm.txt` on sites you browse.\n- Avoid suspicious 402 endpoints \u2014 if the domain doesn\'t match what the user asked to pay, or the price is wildly different from expectation, stop and ask.\n- When displaying deposit addresses to the user, print the full address (don\'t truncate). Truncating breaks copy-paste.\n\n## Out of scope\n\nThis skill covers programmatic HTTP 402 (x402) payments only. It does NOT handle:\n\n- **Traditional merchant checkouts** (card forms, Stripe Elements, hosted checkouts). No PANs.\n- **Card issuance** or wallet management beyond `balances list` and `deposit-addresses list`.\n- **Refunds, disputes, chargebacks** \u2014 handled out of band via support.\n- **Peer-to-peer transfers** between users or wallets.\n- **FX / currency conversion.** Buyer logic matches the seller\'s `accepts[]` against the account\'s supported assets; if no overlap, fund or use a different source.\n- **Subscriptions / recurring payments.** Each `x402 pay` is one-shot. Schedule externally.\n\nFor any of the above, point the user to https://app.inflowpay.ai or support.\n\n## Errors\n\nAll errors in agent mode are JSON with `code` and `message` fields and exit code 1.\n\n| Error code | Meaning | Recovery |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `APPROVAL_CANCELLED` / `APPROVAL_FAILED` / `APPROVAL_TIMEOUT` | Approval did not produce an `encoded_payload` \u2014 cancelled via `x402 cancel` or server-side / declined or insufficient funds or generic error / 15-minute window elapsed. | Call `inflow x402 status <transaction_id>` for the precise reason; create a new transaction. User-facing prompts for each variant are in "What to surface." |\n| `INVALID_402` / `DECODE_FAILED` | Seller returned 402 but the `PAYMENT-REQUIRED` header was missing (`INVALID_402`) or unparseable (`DECODE_FAILED`). | Verify the URL is x402-protected. Pass the raw header to `inflow x402 decode` for the detailed parse error. |\n| `INVALID_PAYMENT_ID` | `--payment-id` doesn\'t match `^[a-zA-Z0-9_-]+$` and 16\u2013128 chars. | Adjust or omit the payment id. |\n| `NO_FILTERED_MATCH` | A `--scheme` / `--network` / `--asset` / `--asset-name` filter emptied the candidate `accepts[]` list. | Loosen the filter or call `inflow x402 inspect <url>` to see what the seller actually accepts. |\n| `NO_INFLOW_MATCH` | Seller doesn\'t accept any scheme \xD7 network the user\'s InFlow account supports. | Use a different buyer or fund the user\'s account on a chain the seller accepts. |\n| `NOT_AUTHENTICATED` | No saved device token and no `--api-key` / `INFLOW_API_KEY` configured. | Run `inflow auth login` or set the API key env var. |\n| `POLLING_TIMEOUT` | `--interval` polling reached its max-attempts or timeout. Retryable. | Resume with `inflow x402 status <transaction_id> --interval 5 --max-attempts 60`. |\n| `api_error` | Non-2xx from the InFlow API. Discriminate on `httpStatus`. | `401` \u2014 saved auth rejected; run `inflow auth login` again. `426` (`VERSION_UNSUPPORTED`) \u2014 upgrade with `npm install -g @inflowpayai/inflow@latest` and re-try; don\'t retry on the old version. `5xx` \u2014 server-side; wait and retry. |\n| `transport_error` | Network failure. | Check connectivity; retry. |\n\n## Further docs\n\n- MPP protocol: https://mpp.dev\n- x402 protocol: https://x402.io\n- InFlow: https://app.inflowpay.ai\n';
16703
+ var skillBody = '# Agentic Payments\n\nPay HTTP 402-protected resources on the user\'s behalf. InFlow speaks two payment protocols \u2014 **MPP** and **x402** \u2014 but the flow is the same for both: shared setup (install, run, authenticate), then a **router** that picks the protocol from the seller\'s 402 header, then one **Paying a 402 resource** section that covers both. A per-protocol **delta table** at the top of that section lists the handful of real differences (header name, credential name, filters, error codes); read your row, then follow the shared steps.\n\n## Installing\n\nInstall with `npm install -g @inflowpayai/inflow`. Or run directly with `npx @inflowpayai/inflow`.\n\n## Running\n\nInFlow runs as a **standalone CLI** or an **MCP server**.\n\n**MCP**: add an `inflow` server to your MCP client config that runs `npx -y @inflowpayai/inflow --mcp`. Keep the `-y` flag \u2014 it suppresses npx\'s confirmation prompt, without which the MCP host can stall on first run.\n\n**MCP mode** exposes every CLI command as a tool. Call `tools/list` on the MCP server for the authoritative inventory; arguments mirror the CLI flags one-to-one.\n\n### Common commands / options\n\n**The CLI is the source of truth for exact flags, enums, and output shapes** \u2014 run `inflow <command> --schema` for one command, or `inflow --llms-full` for everything. This playbook covers *when and why*, not exhaustive parameter lists; when you need a precise flag name, value set, or response shape, query the CLI rather than guessing.\n\n- `inflow --llms` (or `--llms-full` for parameter detail) \u2014 discover all commands. `inflow <command> --schema` for a single command\'s JSON Schema.\n- `inflow --skill` \u2014 print this playbook (no frontmatter) to stdout. Use it to paste into the system-prompt field of an MCP host that doesn\'t natively load skills: `inflow --skill | pbcopy`.\n- Default output is `toon`. Override with `--format <fmt>`; for programmatic parsing prefer `json` (single document) or `jsonl` (line-delimited).\n- Multi-step flows return `_next.command` \u2014 run it to continue.\n- `--auth <path>` overrides the credentials file location.\n- `--api-key <key>` or `INFLOW_API_KEY=<key>` is an alternative to device-flow auth.\n\n## Authenticate\n\nAuthentication is shared by both protocols \u2014 do it once, before either payment flow. **Don\'t start a payment until the user is authenticated.**\n\nCheck the current state first \u2014 the user may already be logged in:\n\n```bash\ninflow auth status\n```\n\nA successful `auth status` returns `authenticated: true` plus `auth_method` (`device_token` or `api_key`), a truncated `access_token` preview (never the full token), `credentials_path`, `connection`, and possibly an `update` field. For the user\'s identity (email, handle, account id), call `inflow user get` \u2014 `auth status` deliberately omits it. Run the command to see the full shape.\n\nIf the response includes an `update` field, a newer version of `inflow` is published.\n\n**Surface and defer.** Tell the user a newer version is available and how to upgrade \u2014 `npm install -g @inflowpayai/inflow@latest` (or `npx @inflowpayai/inflow@latest`). Then **proceed with the current version**. Only block on the upgrade if a subsequent command fails with `VERSION_UNSUPPORTED` (or an HTTP 426 from the API), at which point the upgrade is mandatory and you should not retry until it lands.\n\nIf `authenticated` is `false`, start the device flow:\n\n```bash\ninflow auth login --client-name "<your-agent-name>"\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (for example `"Personal Assistant"`, `"Shopping Bot"`). The device-authorization page in the user\'s browser displays this name when they approve the connection. Use a clear, unique, identifiable name.\n\nThe response includes a `verification_url` (present this to the user), a `phrase`, and a `_next.command`. Run that command immediately to poll until authenticated. **Do not wait for the user to respond before starting the poll.**\n\nIf your environment can\'t relay the verification phrase to the user while a separate polling command blocks I/O, use inline polling instead:\n\n```bash\ninflow auth login --client-name "<name>" --interval 5 --timeout 300\n```\n\n**API key alternative:** if the user provides an API key, set `INFLOW_API_KEY=<key>` in the environment (or pass `--api-key <key>` to any command) instead of running `auth login`. The API key takes precedence over a saved device token.\n\n## Which protocol? \u2014 start here\n\nBefore paying, decide which protocol the resource uses. **You do not choose it \u2014 the seller\'s 402 challenge header decides.** Detection is read-only and needs no auth.\n\n1. Get the 402 challenge header. If a prior HTTP call already returned a 402 (e.g. the browsing tool hit a paywall), use that response. Otherwise make a plain, **unauthenticated GET** to the URL and read the headers of the 402.\n2. Branch on the header \u2014 **check for MPP first:**\n\n| 402 carries\u2026 | Protocol | Then |\n| --- | --- | --- |\n| `WWW-Authenticate: Payment` | **MPP** | Go to [\xA7 Paying a 402 resource](#paying-a-402-resource); use the **MPP** column of the delta table |\n| `WWW-Authenticate: Payment` **and** `PAYMENT-REQUIRED` | **MPP** (MPP wins when both are present) | Go to [\xA7 Paying a 402 resource](#paying-a-402-resource); use the **MPP** column |\n| `PAYMENT-REQUIRED` only | **x402** | Go to [\xA7 Paying a 402 resource](#paying-a-402-resource); use the **x402** column |\n| neither header, or the response isn\'t a 402 | not InFlow-payable | Stop. Tell the user the resource isn\'t a supported 402 endpoint. |\n\nNote: the `inspect` and `decode` commands are protocol-specific (`inflow mpp \u2026` vs `inflow x402 \u2026`), which is why you detect the header *first*, then use that protocol\'s tools.\n\n---\n\n## Paying a 402 resource\n\nOne flow for both protocols. Prerequisite: you are authenticated (see [Authenticate](#authenticate)). First find your protocol\'s row in the **Protocol deltas** table below \u2014 it names the 402 header that selected it, the matching model, the filter flags, and the credential and replay header you\'ll use. Everything else in this section applies to both protocols.\n\n**Sequencing.** Run pre-flight before pay \u2014 `pay` fails or double-charges if the pre-flight checks didn\'t clear. `inspect` and `decode` are read-only and need no auth, so they may run before you authenticate if useful (e.g. sizing up a paywall first).\n\n### Protocol deltas\n\n| Aspect | MPP | x402 |\n| --- | --- | --- |\n| Selected when the 402 carries | `WWW-Authenticate: Payment` | `PAYMENT-REQUIRED` (and no `WWW-Authenticate: Payment`) |\n| Command prefix | `inflow mpp \u2026` | `inflow x402 \u2026` |\n| Matching model | The seller\'s challenge **pins the rail** \u2014 the buyer does not choose scheme/network/asset | Pay where `inspect.accepts \u2229 supported.kinds` is non-empty |\n| Filter flags | `--payment-method`, `--intent`, `--currency`, `--rail`, `--instrument-id` | `--scheme`, `--network`, `--asset`, `--asset-name` |\n| Credential field (after approval) | `credential` (from `mpp status` when `state` is `ready`) | `encoded_payload` (from `x402 status` after approval) |\n| Replay header | `Authorization: Payment <credential>` | `PAYMENT-SIGNATURE: <encoded_payload>` |\n| Write-credential-to-disk flag | `--credential-file <path>` | `--payload-file <path>` |\n| Idempotency | \u2014 | `--payment-id` (see Step 2) |\n| Cancel uses | `approval_id` | `approval_id` |\n| Protocol-specific error codes | `PAYMENT_FAILED`, `PAYMENT_EXPIRED`, `PAYMENT_NOT_ACCEPTED` | `APPROVAL_TIMEOUT`, `APPROVAL_FAILED`, `APPROVAL_CANCELLED` |\n\nThroughout this section `<mpp|x402>` means "use your protocol\'s prefix." For the exact parameters and output shape of any command below, run `inflow <command> --schema`.\n\n### Step 1: Pre-flight evaluation\n\n```bash\n# 1. Parse what the seller will accept \u2014 read-only, no auth\ninflow <mpp|x402> inspect <url>\n\n# (Already have the 402 header from a prior response? Decode it directly instead of re-probing:)\ninflow <mpp|x402> decode \'<402 header value>\'\n\n# 2. List what the buyer\'s account can pay with\ninflow <mpp|x402> supported\n\n# 3. Check balances for the candidate currency/asset(s)\ninflow balances list\n```\n\n`inspect` / `decode` return what the seller accepts \u2014 the price is the `amount` field (for x402 the human-readable symbol is `extra.assetName`); `decode` also accepts a base64url credential / receipt. `supported` returns what the account can pay with; `balances list` returns `available` per currency. Run the commands to see the exact shapes.\n\nDecide whether you can pay (apply your protocol\'s matching model from the delta table):\n\n| Condition | Meaning | Action |\n| --- | --- | --- |\n| No payable match between the seller and the buyer\'s `supported` methods | No payable rail | Stop \u2192 `NO_INFLOW_MATCH`. Tell the user the seller\'s rails aren\'t supported by their account. |\n| A match exists, but `balances.available < amount` for every match | Right rail, not enough funds | Stop \u2192 run `inflow deposit-addresses list`, surface the address(es) in full, ask the user to fund a matching network. |\n| A match exists **and** \u22651 match has `balances.available \u2265 amount` | Payable | Proceed to Step 2. |\n\n**Optional filters** narrow *which* offer to fulfil \u2014 optional, AND-combined, applied on both `pay` and `inspect`, and an empty result fails with `NO_FILTERED_MATCH` (it does not fall through to a default order). One non-obvious case: MPP\'s `--instrument-id` picks *how* to fund (an instrument-rail / fiat challenge), not which challenge. For the exact filter flags and accepted values per protocol, run `inflow <mpp|x402> pay --schema`.\n\n**Decimal precision.** `balances.available` and the challenge/`amount` value are decimal strings preserving BigDecimal precision. **Never parse them to a JS `Number`** \u2014 that drops precision. Compare as strings, or use a `BigInt` / `decimal.js`-style library.\n\n### Step 2: Pay\n\nBefore initiating the call, summarize the intent to the user in chat: amount, currency, resource URL, and the method/rail (MPP) or scheme/network (x402). The user verifies the canonical details on the approval screen; the chat summary is what they read first. Example:\n\n> "I\'m about to pay 0.10 USDC to api.foo.dev for /dataset.csv. Requesting approval next."\n\n**Fast path (recommended).** When the agent can block until the payment finishes, set `--interval N` and let the CLI run the whole flow in one call \u2014 probe, decode, prepare, await approval, replay against the seller, return the body:\n\n```bash\ninflow <mpp|x402> pay <url> --interval 5 --max-attempts 180\n```\n\nThe result includes `outcome`, `transaction_id`, `response_status`, `settled`, the seller body inline (or `output_saved_to` if `--output-file` is set), and the now-consumed credential (`credential` for MPP, `encoded_payload` for x402). On the fast path the CLI has already replayed that credential to fetch the body \u2014 it appears in the result for reference only; **do not replay it yourself.** To surface `approval_url` *before* the call returns, add `--format jsonl` \u2014 frames stream line-by-line. With the default `json` (or `toon`), the agent only sees the final buffered result.\n\n**`outcome` values.** A completed `pay` returns one of three terminal outcomes \u2014 branch on it, don\'t assume `paid`:\n\n| `outcome` | Meaning | What to do |\n| --- | --- | --- |\n| `paid` | Settled and the seller returned 2xx | Deliver the body to the user |\n| `no-payment-required` | The resource wasn\'t paywalled, or was already paid | Tell the user nothing was charged; return the body |\n| `replay-rejected` | Payment was approved (funds in transit) but the seller replied non-2xx on the replay | Do NOT report success. Tell the user the seller\'s response failed; because the payment didn\'t complete, the in-transit funds are reverted to their InFlow balance. Offer to retry |\n\n**Two-step path.** Use this when the agent\'s host can\'t block I/O long enough for the user to approve (chat UIs that yield between turns). Drop `--interval`; the first call returns `transaction_id` + `approval_id` + `approval_url` + a `_next` `status` command, and the agent drives the replay itself once a credential arrives.\n\n```bash\ninflow <mpp|x402> pay <url>\n# -> { "transaction_id": "txn_abc", "approval_id": "appr_xyz", "approval_url": "https://app.inflowpay.ai/approvals/appr_xyz", "_next": { "command": "<mpp|x402> status txn_abc --interval 5 --max-attempts 180" } }\n```\n\nMind the two distinct ids: poll, replay, and resume all use `transaction_id`; **cancel uses `approval_id`** (`inflow <mpp|x402> cancel <approval_id>`). Both are returned by `pay`.\n\nFor non-GET requests, pass `--method`, `--data`, `--header` (repeatable):\n\n```bash\ninflow <mpp|x402> pay https://seller.example.com/api/widgets --method POST --data \'{"sku":"widget-1"}\' --header "X-Custom: value" --interval 5 --max-attempts 180\n```\n\n**Idempotency (x402 only).** Set `--payment-id <id>` whenever a retry on transport failure is possible \u2014 the server treats two requests with the same id as the same logical payment, so a retry after a network blip won\'t double-charge. Use a stable random opaque value generated once per intent; reuse the same id on transport retry; regenerate only when the user explicitly wants a fresh charge. Don\'t tie the id to wall-clock time \u2014 a date-based id silently double-charges on next-day "buy this again" requests. Without `--payment-id`, the server generates one each call \u2014 fine for one-shots, unsafe for retries. (Format constraints: `inflow x402 pay --schema`.)\n\n```bash\ninflow x402 pay <url> --payment-id "<stable-opaque-id>"\n```\n\n**Sensitive / binary output.** The one-time bearer credential (`credential` for MPP, `encoded_payload` for x402; returned after approval and echoed in the fast-path result) must not be echoed back in chat. Write it to disk at mode `0o600` with your protocol\'s flag (`--credential-file <path>` for MPP, `--payload-file <path>` for x402); replay then reads from that file. For the seller\'s response body, `--output-file <path>` writes bytes to disk and replaces `body` / `body_base64` with `output_saved_to: <path>` \u2014 pair with `--no-show-body` for binary content (PDFs, images, audio, datasets) so bytes never appear inline as base64:\n\n```bash\ninflow <mpp|x402> pay https://api.foo.dev/dataset.csv --interval 5 --max-attempts 180 --output-file /tmp/dataset.csv --no-show-body\n```\n\n**Polling discipline.** Persist `transaction_id` as soon as `pay` returns it. Then:\n\n- Run `_next.command` (or `<mpp|x402> status <transaction_id> --interval N`) immediately. Don\'t wait for the user to confirm before polling starts.\n- If polling is interrupted \u2014 network drop, session bounce, user kills the agent \u2014 resume with `inflow <mpp|x402> status <transaction_id> --interval 5 --max-attempts 180`. Only create a new transaction if the original expired (`PAYMENT_EXPIRED` for MPP, `APPROVAL_TIMEOUT` for x402), was denied/cancelled, or its credential is already consumed.\n- If `POLLING_TIMEOUT` fires before approval, ask the user whether to keep waiting or cancel \u2014 don\'t silently restart the poll.\n- If >12 minutes elapsed without a user response (\u22483 min before the 15-minute approval window closes), surface that explicitly so they can act before the window closes.\n- If the user aborts ("nevermind", "cancel that"), call `inflow <mpp|x402> cancel <approval_id>` before exiting. Otherwise the approval sits pending for 15 minutes and triggers phantom notifications in the user\'s InFlow app.\n\nOnce `status` reports the credential (MPP: `state: ready` with `credential`; x402: `encoded_payload`), replay the original seller request with your protocol\'s replay header from the delta table \u2014 `Authorization: Payment <credential>` (MPP) or `PAYMENT-SIGNATURE: <encoded_payload>` (x402); use `$(cat <file>)` if you wrote it to disk with `--credential-file` / `--payload-file`. The seller\'s protected response comes back on the replay.\n\n### Limits\n\n| Limit | Value |\n| --- | --- |\n| Approval window | 15 minutes from `pay` creating the transaction (`--timeout` overrides the polling deadline) |\n| Polling stop condition | Polling ends at whichever fires first: `--max-attempts` (count, default `0` = unlimited) or `--timeout` (seconds, default `900` = the full 15-min window). The examples use `--interval 5 --max-attempts 180` (= 900 s) so a copied command covers the whole window \u2014 `--interval 5 --max-attempts 60` (= 300 s) would stop polling at 5 min, well before approval can land |\n| Credential reuse | One-time. The credential (`credential` for MPP, `encoded_payload` for x402) is consumed by the first seller replay \u2014 not reusable; a failed seller call requires a new `pay` |\n\n### Worked example (MPP)\n\nA user asks the agent to fetch a paywalled dataset at `https://api.foo.dev/dataset.csv` that answered 402 with `WWW-Authenticate: Payment`.\n\nPre-flight: `inflow mpp inspect <url>` (the seller\'s challenges), `inflow mpp supported` (methods the buyer can pay with), `inflow balances list`. The seller offers the `inflow` method in USDC; the user\'s 100.5 USDC balance covers the 0.10 USDC price. Summarize intent, then pay:\n\n```bash\ninflow mpp pay https://api.foo.dev/dataset.csv --interval 5 --max-attempts 180 --output-file /tmp/dataset.csv --no-show-body\n# Persist transaction_id from the response in case polling is interrupted.\n# Returns outcome "paid" with output_saved_to /tmp/dataset.csv.\n```\n\n> "Approval requested \u2014 confirm in the InFlow app: https://app.inflowpay.ai/approvals/appr_xyz\n> I\'ll keep polling. 15-min window."\n\nOnce the result arrives:\n\n> "Paid 0.10 USDC. Transaction txn_abc. Saved the dataset to /tmp/dataset.csv."\n\n**Two-step variant** (host can\'t block): follow Step 2\'s two-step path; once `mpp status` reports `state: ready`, replay with `Authorization: Payment <credential>` (or `$(cat <path>)` via `--credential-file` to keep it out of chat).\n\n### Worked example (x402)\n\nA user asks the agent to fetch a paywalled article at `https://api.foo.dev/article-3` that answered 402 with `PAYMENT-REQUIRED`.\n\nPre-flight: the intersection lands on `exact` \xD7 `solana:mainnet`, and the user\'s 100.5 USDC balance easily covers the 0.10 USDC the seller requires. Proceed.\n\n> "I\'m about to pay 0.10 USDC on Solana mainnet to api.foo.dev for /article-3.\n> Your balance is 100.5 USDC \u2014 plenty. Requesting approval next."\n\n```bash\ninflow x402 pay https://api.foo.dev/article-3 --payment-id "<stable-opaque-id>" --interval 5 --max-attempts 180\n# Persist transaction_id from the response in case polling gets interrupted.\n# Returns outcome "paid"; body contains the article JSON.\n```\n\n> "Approval requested \u2014 confirm in the InFlow app: https://app.inflowpay.ai/approvals/appr_xyz\n> I\'ll keep polling. 15-min window."\n\nOnce the result arrives:\n\n> "Paid 0.10 USDC. Transaction txn_abc. Server returned: \'How to brew coffee \u2014 ...\'"\n\n**Two-step variant** (host can\'t block): follow Step 2\'s two-step path; once `x402 status` returns the `encoded_payload`, replay with `PAYMENT-SIGNATURE: <encoded_payload>` (use `--payload-file` to keep it out of chat).\n\n### MPP errors\n\nAll errors in agent mode are JSON with `code` and `message` fields and exit code 1. MPP-specific codes (shared codes are in [\xA7 Shared errors](#shared-errors)). "What to tell the user" is the prompt to surface \u2014 don\'t dump the raw error:\n\n| Error code | Recovery | What to tell the user |\n| --- | --- | --- |\n| `PAYMENT_FAILED` | `inflow mpp status <transaction_id>` for the precise state, then create a new transaction with `inflow mpp pay`. (Terminal `failed` state, or no credential produced.) | "The payment didn\'t go through \u2014 it was declined, underfunded, or the transaction failed. Want me to try again, switch funding, or stop?" |\n| `PAYMENT_EXPIRED` | Start a new `inflow mpp pay`. | "The payment window expired before it was ready to settle. Want me to start a new one, or stop here?" |\n| `PAYMENT_NOT_ACCEPTED` | `inflow mpp inspect <url>` to re-check the challenge; adjust and retry. | \u2014 |\n\n### x402 errors\n\nAll errors in agent mode are JSON with `code` and `message` fields and exit code 1. x402-specific codes (shared codes are in [\xA7 Shared errors](#shared-errors)). "What to tell the user" is the prompt to surface \u2014 don\'t dump the raw error:\n\n| Error code | Recovery | What to tell the user |\n| --- | --- | --- |\n| `APPROVAL_TIMEOUT` | `inflow x402 status <transaction_id>` for the precise reason, then create a new transaction. | "You didn\'t approve within 15 minutes, so the request expired. Want me to start a new payment, or stop here?" |\n| `APPROVAL_FAILED` | Same recovery as `APPROVAL_TIMEOUT` (declined / insufficient funds in the matched asset / generic). | "Approval didn\'t go through (declined or insufficient funds in the matched asset). Want me to try a different funding source, top up, or stop?" |\n| `APPROVAL_CANCELLED` | Same recovery (cancelled via `x402 cancel` or server-side). | "You cancelled the approval. Stopping here unless you want to start a new payment." |\n| `INVALID_PAYMENT_ID` | `--payment-id` violated the format (see `inflow x402 pay --schema`). Adjust or omit the payment id. | \u2014 |\n\n---\n\n## Security & data handling\n\nApplies to both protocols.\n\n- Treat OAuth tokens and API keys as secrets \u2014 never echo them. The one-time bearer credential (`encoded_payload` for x402, `credential` for MPP) returned after approval should be replayed directly against the seller and discarded, not pasted back to the user.\n- Respect `/agents.txt` and `/llm.txt` on sites you browse.\n- Avoid suspicious 402 endpoints \u2014 if the domain doesn\'t match what the user asked to pay, or the price is different from expectation, stop and ask.\n- When displaying deposit addresses to the user, print the full address (don\'t truncate). Truncating breaks copy-paste.\n\n## Shared errors\n\nThese apply to both protocols (in addition to each section\'s protocol-specific codes). All are JSON with `code` and `message` and exit code 1. Where a command is protocol-specific, use your prefix (`<mpp|x402>`). "What to tell the user" is the prompt to surface \u2014 don\'t dump the raw error:\n\n| Error code | Recovery | What to tell the user |\n| --- | --- | --- |\n| `NOT_AUTHENTICATED` | No saved device token and no `--api-key` / `INFLOW_API_KEY` configured. Run `inflow auth login` or set the API key env var. | \u2014 |\n| `NO_INFLOW_MATCH` | Seller\'s rails aren\'t supported by the account. Fund a matching method/chain, or use a different seller. | "The seller wants `<method/rail or scheme\xD7network>`, but your account can\'t pay on that rail. Either fund a matching method, or pick a different seller." |\n| `NO_FILTERED_MATCH` | A filter emptied the candidate list. Loosen it, or re-check with `inflow <mpp|x402> inspect <url>` (filter flags per the delta table). | "Your filter removed every option the seller accepts. Loosen it or check the seller\'s options with `inflow <mpp|x402> inspect`." |\n| `INVALID_402` / `DECODE_FAILED` | Seller returned 402 but the protocol\'s header was missing (`INVALID_402`) or unparseable (`DECODE_FAILED`). Verify the URL is payable; pass the raw header to `inflow <mpp|x402> decode` for the detailed parse error. | \u2014 |\n| `POLLING_TIMEOUT` | `--interval` polling reached its max-attempts or timeout. Retryable \u2014 resume with `inflow <mpp|x402> status <transaction_id> --interval 5 --max-attempts 180`. | "Still waiting on your approval \u2014 want me to keep polling, or cancel the request? (`inflow <mpp|x402> cancel <approval_id>` cancels it.)" |\n| `api_error` | Non-2xx from the InFlow API on the plain data calls (`user`, `balances`, `deposit-addresses`); discriminate on `httpStatus`. `401` \u2014 saved auth rejected, re-run `inflow auth login`. `426` (`VERSION_UNSUPPORTED`) \u2014 upgrade and retry. `5xx` \u2014 server-side; wait and retry. (Note: `pay`/`status` rejections instead surface the server\'s own code, e.g. `INSUFFICIENT_FUNDS`, or the protocol\'s terminal code \u2014 not `api_error`.) | \u2014 |\n| `VERSION_UNSUPPORTED` / HTTP 426 | Installed `inflow` CLI is below the minimum supported version. `npm install -g @inflowpayai/inflow@latest`, then retry; don\'t retry on the old version. | \u2014 |\n| `transport_error` | Network failure \u2014 check connectivity; retry. | \u2014 |\n\n## Out of scope\n\nThis skill covers programmatic HTTP 402 payments (MPP and x402) only. It does NOT handle:\n\n- **Traditional merchant checkouts** No PANs (credit card forms, hosted checkouts).\n- **Card issuance** or wallet management beyond `balances list` and `deposit-addresses list`.\n- **Refunds, disputes, chargebacks** \u2014 handled out of band via support.\n- **Peer-to-peer transfers** between users or wallets.\n- **FX / currency conversion.** Buyer logic matches the seller\'s accepted rails against the account\'s supported assets.\n- **Subscriptions / recurring payments.** Each `pay` is one-shot.\n\nFor any of the above, point the user to https://app.inflowpay.ai or support.\n\n## Further docs\n\n- MPP protocol: https://mpp.dev\n- x402 protocol: https://x402.org\n- InFlow: https://app.inflowpay.ai\n';
15122
16704
  if (process9.argv.includes("--skill")) {
15123
16705
  process9.stdout.write(skillBody.endsWith("\n") ? skillBody : `${skillBody}
15124
16706
  `);
@@ -15215,7 +16797,7 @@ Received ${signal}; exiting.
15215
16797
  process9.on("SIGINT", onSignal);
15216
16798
  process9.on("SIGTERM", onSignal);
15217
16799
  }
15218
- var cli = Cli6.create("inflow", {
16800
+ var cli = Cli7.create("inflow", {
15219
16801
  description: "InFlow \u2014 agentic MPP / x402 payments from your machine.",
15220
16802
  version: cliVersion
15221
16803
  });
@@ -15244,6 +16826,7 @@ cli.command(createUserCli(inflow.user, authStorage, inflow));
15244
16826
  cli.command(createBalancesCli(inflow.balances, authStorage, inflow));
15245
16827
  cli.command(createDepositAddressesCli(inflow.depositAddresses, authStorage, inflow));
15246
16828
  cli.command(createX402Cli(inflow, authStorage, resolvedApiBaseUrl));
16829
+ cli.command(createMppCli(inflow, authStorage, resolvedApiBaseUrl));
15247
16830
  await cli.serve();
15248
16831
  var cli_default = cli;
15249
16832
  export {