@inflowpayai/inflow 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +105 -37
  2. package/dist/cli.js +2111 -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,476 @@ 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 ("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.status !== "") out.status = receipt.status;
12433
+ if (receipt.timestamp !== "") out.timestamp = receipt.timestamp;
12434
+ return Object.keys(out).length > 0 ? out : void 0;
12435
+ }
12436
+ async function resolveTransaction(client, transactionId, deps) {
12437
+ const generator = pollAsync({
12438
+ fn: () => client.getTransaction(transactionId),
12439
+ isTerminal: (response) => response.state !== "pending",
12440
+ isEqual: (a, b) => a.state === b.state,
12441
+ // A 0 interval reaches here only on the TTY path (the agent path gates inline polling on interval > 0); fall back
12442
+ // to a 5s cadence so the poll loop doesn't spin.
12443
+ interval: deps.interval > 0 ? deps.interval : 5,
12444
+ maxAttempts: deps.maxAttempts,
12445
+ timeout: deps.timeout,
12446
+ ...deps.signal !== void 0 ? { signal: deps.signal } : {}
12447
+ });
12448
+ for await (const outcome of generator) {
12449
+ if (!outcome.terminal) continue;
12450
+ if (outcome.reason !== void 0) return { timedOut: true, latest: outcome.value };
12451
+ return { response: outcome.value };
12452
+ }
12453
+ return { timedOut: true };
12454
+ }
12455
+ async function runMppPayPipeline(deps, emit) {
12456
+ try {
12457
+ const probe = await sellerProbe4(deps.url, deps.probeOptions);
12458
+ if (probe.status !== 402) {
12459
+ if (!isSuccessStatus2(probe.status)) {
12460
+ emit({
12461
+ type: "errored",
12462
+ code: UNEXPECTED_PROBE_STATUS_CODE2,
12463
+ message: `Seller returned status ${String(probe.status)} during probe; expected 2xx (no payment) or 402 (payment required).`
12464
+ });
12465
+ return;
12466
+ }
12467
+ const attachment2 = await buildBodyAttachment(probe.bytes, deps.showBody, deps.outputFile);
12468
+ emit({
12469
+ type: "short-circuited",
12470
+ result: {
12471
+ outcome: "no-payment-required",
12472
+ url: deps.url,
12473
+ method: deps.probeOptions.method,
12474
+ status: probe.status,
12475
+ contentType: probe.contentType,
12476
+ ...attachment2
12477
+ }
12478
+ });
12479
+ return;
12480
+ }
12481
+ const headerValues = readHeaderAll2(probe.headers, HEADERS4.WWW_AUTHENTICATE);
12482
+ if (headerValues.length === 0) {
12483
+ emit({
12484
+ type: "errored",
12485
+ code: INVALID_402_CODE2,
12486
+ message: "Seller returned 402 but did not include a WWW-Authenticate: Payment header."
12487
+ });
12488
+ return;
12489
+ }
12490
+ let challenges;
12491
+ try {
12492
+ challenges = parseChallengeHeaders2(headerValues);
12493
+ } catch (err) {
12494
+ emit({ type: "errored", code: "DECODE_FAILED", message: err instanceof Error ? err.message : String(err) });
12495
+ return;
12496
+ }
12497
+ const inflowChallenges = filterInflowChallenges(challenges);
12498
+ if (inflowChallenges.length === 0) {
12499
+ emit({ type: "errored", code: NO_INFLOW_MATCH_CODE2, message: NO_INFLOW_MATCH_MESSAGE2 });
12500
+ return;
12501
+ }
12502
+ const filters = {
12503
+ ...deps.paymentMethodFilter !== void 0 ? { paymentMethod: deps.paymentMethodFilter } : {},
12504
+ ...deps.intentFilter !== void 0 ? { intent: deps.intentFilter } : {},
12505
+ ...deps.currencyFilter !== void 0 ? { currency: deps.currencyFilter } : {},
12506
+ ...deps.railFilter !== void 0 ? { rail: deps.railFilter } : {}
12507
+ };
12508
+ const selected = filterChallenges(inflowChallenges, filters);
12509
+ if (hasAnyChallengeFilter(filters) && selected.length === 0) {
12510
+ emit({
12511
+ type: "errored",
12512
+ code: NO_FILTERED_MATCH_CODE2,
12513
+ message: buildNoFilteredMatchMessage2(inflowChallenges, filters)
12514
+ });
12515
+ return;
12516
+ }
12517
+ const challenge = selected[0];
12518
+ emit({ type: "decoded", challenge: summarizeChallenge(challenge) });
12519
+ const options = deps.instrumentId !== void 0 ? { instrumentId: deps.instrumentId } : {};
12520
+ let created;
12521
+ try {
12522
+ created = await deps.client.createTransaction({ challenge, options });
12523
+ } catch (err) {
12524
+ const mapped = mapMppError(err);
12525
+ emit({ type: "errored", code: mapped.code, message: mapped.message });
12526
+ return;
12527
+ }
12528
+ const createdFrame = {
12529
+ transactionId: created.transactionId ?? "",
12530
+ state: created.state,
12531
+ challenge: summarizeChallenge(challenge),
12532
+ ...created.approvalId !== void 0 ? { approvalId: created.approvalId } : {},
12533
+ ...created.approvalId !== void 0 ? { approvalUrl: approvalUrlFor(deps.apiBaseUrl, created.approvalId) } : {},
12534
+ ...created.retryAfterSeconds !== void 0 ? { retryAfterSeconds: created.retryAfterSeconds } : {},
12535
+ ...created.expires !== void 0 ? { expires: created.expires } : {}
12536
+ };
12537
+ emit({ type: "created", created: createdFrame });
12538
+ let resolved = created;
12539
+ if (created.state === "pending") {
12540
+ if (deps.awaitPayment === false) return;
12541
+ if (createdFrame.transactionId === "") {
12542
+ emit({
12543
+ type: "errored",
12544
+ code: "PAYMENT_FAILED",
12545
+ message: "Pending transaction carried no transactionId to poll."
12546
+ });
12547
+ return;
12548
+ }
12549
+ const outcome = await resolveTransaction(deps.client, createdFrame.transactionId, deps);
12550
+ if ("timedOut" in outcome) {
12551
+ emit({
12552
+ type: "errored",
12553
+ code: "POLLING_TIMEOUT",
12554
+ message: "Polling timed out before the transaction reached a ready state."
12555
+ });
12556
+ return;
12557
+ }
12558
+ resolved = outcome.response;
12559
+ }
12560
+ if (resolved.state === "failed") {
12561
+ emit({
12562
+ type: "errored",
12563
+ code: "PAYMENT_FAILED",
12564
+ message: resolved.problem?.detail ?? resolved.problem?.title ?? "MPP transaction failed."
12565
+ });
12566
+ return;
12567
+ }
12568
+ if (resolved.state === "expired") {
12569
+ emit({ type: "errored", code: "PAYMENT_EXPIRED", message: "MPP transaction expired before it was ready." });
12570
+ return;
12571
+ }
12572
+ if (resolved.state !== "ready" || resolved.credential === void 0) {
12573
+ emit({
12574
+ type: "errored",
12575
+ code: "PAYMENT_FAILED",
12576
+ message: "Transaction reached a ready state without a credential."
12577
+ });
12578
+ return;
12579
+ }
12580
+ const credential = resolved.credential;
12581
+ const replay = await sellerProbe4(deps.url, {
12582
+ method: deps.probeOptions.method,
12583
+ headers: { ...deps.probeOptions.headers, [HEADERS4.AUTHORIZATION]: `${SCHEME_PAYMENT} ${credential}` },
12584
+ ...deps.probeOptions.data !== void 0 ? { data: deps.probeOptions.data } : {}
12585
+ });
12586
+ const attachment = await buildBodyAttachment(replay.bytes, deps.showBody, deps.outputFile);
12587
+ if (!isSuccessStatus2(replay.status)) {
12588
+ emit({
12589
+ type: "rejected",
12590
+ result: {
12591
+ outcome: "seller-rejected",
12592
+ url: deps.url,
12593
+ method: deps.probeOptions.method,
12594
+ transactionId: createdFrame.transactionId,
12595
+ challengeId: challenge.id,
12596
+ responseStatus: replay.status,
12597
+ responseContentType: replay.contentType,
12598
+ ...attachment
12599
+ }
12600
+ });
12601
+ return;
12602
+ }
12603
+ const settled = buildSettlement(replay.headers);
12604
+ emit({
12605
+ type: "replayed",
12606
+ result: {
12607
+ outcome: "paid",
12608
+ url: deps.url,
12609
+ method: deps.probeOptions.method,
12610
+ transactionId: createdFrame.transactionId,
12611
+ challengeId: challenge.id,
12612
+ intent: challenge.intent,
12613
+ credential,
12614
+ responseStatus: replay.status,
12615
+ responseContentType: replay.contentType,
12616
+ ...settled !== void 0 ? { settled } : {},
12617
+ ...attachment
12618
+ }
12619
+ });
12620
+ } catch (err) {
12621
+ const mapped = mapMppError(err);
12622
+ emit({ type: "errored", code: mapped.code, message: mapped.message });
12623
+ }
12624
+ }
12625
+ var TERMINAL_STATES = /* @__PURE__ */ new Set(["expired", "failed", "ready"]);
12626
+ function reduceMppStatus(state, event) {
12627
+ switch (event.type) {
12628
+ case "snapshot":
12629
+ return { kind: "polling", latest: event.response };
12630
+ case "ready":
12631
+ return { kind: "ready", response: event.response };
12632
+ case "failed":
12633
+ return { kind: "failed", response: event.response };
12634
+ case "expired":
12635
+ return { kind: "expired", response: event.response };
12636
+ case "timedOut":
12637
+ return event.response !== void 0 ? { kind: "timeout", response: event.response } : { kind: "timeout" };
12638
+ case "crashed":
12639
+ return { kind: "error", message: event.message };
12640
+ default:
12641
+ return state;
12642
+ }
12643
+ }
12644
+ function runMppStatus(input) {
12645
+ async function* generate() {
12646
+ try {
12647
+ const generator = pollAsync({
12648
+ fn: input.fetchOnce,
12649
+ isTerminal: (response) => TERMINAL_STATES.has(response.state),
12650
+ isEqual: (a, b) => a.state === b.state && a.credential !== void 0 === (b.credential !== void 0),
12651
+ interval: input.interval,
12652
+ maxAttempts: input.maxAttempts,
12653
+ timeout: input.timeout
12654
+ });
12655
+ for await (const outcome of generator) {
12656
+ if (!outcome.terminal) {
12657
+ yield { type: "snapshot", response: outcome.value };
12658
+ continue;
12659
+ }
12660
+ if (outcome.reason !== void 0) {
12661
+ yield { type: "timedOut", response: outcome.value };
12662
+ return;
12663
+ }
12664
+ const state = outcome.value.state;
12665
+ if (state === "ready") {
12666
+ yield { type: "ready", response: outcome.value };
12667
+ return;
12668
+ }
12669
+ if (state === "expired") {
12670
+ yield { type: "expired", response: outcome.value };
12671
+ return;
12672
+ }
12673
+ yield { type: "failed", response: outcome.value };
12674
+ return;
12675
+ }
12676
+ } catch (err) {
12677
+ yield { type: "crashed", message: userFacingErrorMessage(err) };
12678
+ }
12679
+ }
12680
+ return { events: generate() };
12681
+ }
12682
+ async function runMppSupported(input) {
12683
+ const client = await input.mpp.client();
12684
+ return client.getSupported();
12685
+ }
12171
12686
  function wrapEmittingPipeline(run) {
12172
12687
  const buffer = [];
12173
12688
  let started = false;
@@ -12308,16 +12823,43 @@ function augmentX402(x402Resource, resolvedApiBaseUrl2) {
12308
12823
  augmented.cancel = async (input) => runX402Cancel({ x402: x402Resource, approvalId: input.approvalId });
12309
12824
  return augmented;
12310
12825
  }
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
- };
12826
+ function augmentMpp(mppResource, resolvedApiBaseUrl2) {
12827
+ const augmented = mppResource;
12828
+ augmented.inspect = (input) => wrapEmittingPipeline((emit) => runMppInspectPipeline(input, emit));
12829
+ augmented.supported = async () => runMppSupported({ mpp: mppResource });
12830
+ augmented.pay = (input) => wrapEmittingPipeline(async (emit) => {
12831
+ const client = await mppResource.client();
12832
+ return runMppPayPipeline(
12833
+ {
12834
+ ...input,
12835
+ client,
12836
+ apiBaseUrl: input.apiBaseUrl ?? resolvedApiBaseUrl2
12837
+ },
12838
+ emit
12839
+ );
12840
+ });
12841
+ augmented.status = (input) => runMppStatus({
12842
+ fetchOnce: async () => {
12843
+ const client = await mppResource.client();
12844
+ return client.getTransaction(input.transactionId);
12845
+ },
12846
+ interval: input.interval,
12847
+ maxAttempts: input.maxAttempts,
12848
+ timeout: input.timeout
12849
+ });
12850
+ augmented.cancel = async (input) => runMppCancel({ mpp: mppResource, approvalId: input.approvalId });
12851
+ return augmented;
12852
+ }
12853
+ var DEFAULT_RETRIES = 3;
12854
+ var DEFAULT_TIMEOUT_MS = 3e4;
12855
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
12856
+ var SDK_USER_AGENT = `@inflowpayai/inflow-core/${true ? "0.6.1" : "0.0.0"}`;
12857
+ function sleep2(ms, signal) {
12858
+ return new Promise((resolve, reject) => {
12859
+ const onAbort = () => {
12860
+ clearTimeout(handle);
12861
+ reject(new InflowTransportError("Request aborted."));
12862
+ };
12321
12863
  if (signal?.aborted) {
12322
12864
  reject(new InflowTransportError("Request aborted."));
12323
12865
  return;
@@ -12827,12 +13369,33 @@ var X402Resource = class {
12827
13369
  return this.cached;
12828
13370
  }
12829
13371
  };
13372
+ var MppResource = class {
13373
+ constructor(opts) {
13374
+ this.opts = opts;
13375
+ }
13376
+ opts;
13377
+ cachedClient;
13378
+ cachedMethod;
13379
+ client() {
13380
+ if (!this.cachedClient) {
13381
+ this.cachedClient = Promise.resolve(new MppClient(this.opts));
13382
+ }
13383
+ return this.cachedClient;
13384
+ }
13385
+ cancelApproval(approvalId) {
13386
+ if (!this.cachedMethod) {
13387
+ this.cachedMethod = createInflowMppMethod(this.opts);
13388
+ }
13389
+ return this.cachedMethod.cancelApproval(approvalId);
13390
+ }
13391
+ };
12830
13392
  var Inflow = class {
12831
13393
  auth;
12832
13394
  balances;
12833
13395
  depositAddresses;
12834
13396
  user;
12835
13397
  x402;
13398
+ mpp;
12836
13399
  /**
12837
13400
  * The effective API base URL this client will hit, after resolution against `options.apiBaseUrl`, `INFLOW_BASE_URL`,
12838
13401
  * and the environment-derived default. Exposed for callers (CLI, MCP transports, etc.) that need to display "what URL
@@ -12855,6 +13418,8 @@ var Inflow = class {
12855
13418
  this.user = augmentUser(rawUser);
12856
13419
  const x402Internal = new X402Resource(this.resolveX402Options(options, dataOptions));
12857
13420
  this.x402 = augmentX402(x402Internal, this.resolvedApiBaseUrl);
13421
+ const mppInternal = new MppResource(this.resolveMppOptions(options, dataOptions));
13422
+ this.mpp = augmentMpp(mppInternal, this.resolvedApiBaseUrl);
12858
13423
  this.auth = augmentAuth(rawAuth, rawUser, options.authStorage);
12859
13424
  }
12860
13425
  /**
@@ -12891,6 +13456,20 @@ var Inflow = class {
12891
13456
  }
12892
13457
  return connection;
12893
13458
  }
13459
+ resolveMppOptions(options, dataOptions) {
13460
+ const connection = {
13461
+ ...options.environment !== void 0 ? { environment: options.environment } : {},
13462
+ ...options.apiBaseUrl !== void 0 ? { baseUrl: options.apiBaseUrl } : {}
13463
+ };
13464
+ if (options.apiKey !== void 0 && options.apiKey.length > 0) {
13465
+ return { ...connection, apiKey: options.apiKey };
13466
+ }
13467
+ if (dataOptions.getAccessToken !== void 0) {
13468
+ const provider = dataOptions.getAccessToken;
13469
+ return { ...connection, getAccessToken: () => provider() };
13470
+ }
13471
+ return connection;
13472
+ }
12894
13473
  };
12895
13474
  async function probeSession(userResource, options) {
12896
13475
  const controller = new AbortController();
@@ -12925,13 +13504,60 @@ async function runDepositAddressesList(input) {
12925
13504
  }
12926
13505
 
12927
13506
  // src/cli.tsx
12928
- import { Cli as Cli6 } from "incur";
13507
+ import { Cli as Cli7 } from "incur";
12929
13508
 
12930
13509
  // src/commands/auth/index.tsx
12931
13510
  import { Cli } from "incur";
12932
- import { Text as Text6, useApp as useApp5 } from "ink";
13511
+ import { Text as Text6 } from "ink";
12933
13512
  import { useEffect as useEffect5, useState as useState2 } from "react";
12934
13513
 
13514
+ // src/hooks/use-flow-exit.ts
13515
+ import { useApp } from "ink";
13516
+ import { useCallback, useRef } from "react";
13517
+
13518
+ // src/utils/best-effort-cancel.ts
13519
+ var CANCEL_GRACE_MS = 1500;
13520
+ function runBestEffortCancel(cancel, done, graceMs = CANCEL_GRACE_MS) {
13521
+ let finished = false;
13522
+ const finish = () => {
13523
+ if (finished) return;
13524
+ finished = true;
13525
+ done();
13526
+ };
13527
+ const timer = setTimeout(finish, graceMs);
13528
+ void Promise.resolve(cancel?.()).catch(() => void 0).finally(() => {
13529
+ clearTimeout(timer);
13530
+ finish();
13531
+ });
13532
+ }
13533
+
13534
+ // src/hooks/use-flow-exit.ts
13535
+ function useFlowExit(onComplete) {
13536
+ const { exit } = useApp();
13537
+ const onCompleteRef = useRef(onComplete);
13538
+ onCompleteRef.current = onComplete;
13539
+ const settledRef = useRef(false);
13540
+ const cancelStartedRef = useRef(false);
13541
+ const finish = useCallback(
13542
+ (...args) => {
13543
+ if (settledRef.current) return;
13544
+ settledRef.current = true;
13545
+ onCompleteRef.current(...args);
13546
+ exit();
13547
+ },
13548
+ [exit]
13549
+ );
13550
+ const cancelThenFinish = useCallback(
13551
+ (cancel, ...args) => {
13552
+ if (cancelStartedRef.current || settledRef.current) return;
13553
+ cancelStartedRef.current = true;
13554
+ runBestEffortCancel(cancel, () => finish(...args));
13555
+ },
13556
+ [finish]
13557
+ );
13558
+ return { finish, cancelThenFinish };
13559
+ }
13560
+
12935
13561
  // src/utils/render-ink-until-exit.tsx
12936
13562
  import { render } from "ink";
12937
13563
  async function renderInkUntilExit(element, resolveResult) {
@@ -12944,7 +13570,7 @@ async function renderInkUntilExit(element, resolveResult) {
12944
13570
  var NPM_INSTALL_COMMAND = "npm install -g @inflowpayai/inflow";
12945
13571
 
12946
13572
  // src/commands/auth/login.tsx
12947
- import { Box, Text, useApp, useInput } from "ink";
13573
+ import { Box, Text, useInput } from "ink";
12948
13574
  import Spinner from "ink-spinner";
12949
13575
  import { useEffect, useReducer } from "react";
12950
13576
 
@@ -12985,7 +13611,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
12985
13611
  var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) => {
12986
13612
  const initialPhase = { kind: "init" };
12987
13613
  const [phase, dispatch] = useReducer(reduceAuthLogin, initialPhase);
12988
- const { exit } = useApp();
13614
+ const { finish } = useFlowExit(onComplete);
12989
13615
  useInput(
12990
13616
  (_input, key) => {
12991
13617
  if (key.return && phase.kind === "awaiting") {
@@ -13014,10 +13640,9 @@ var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) =>
13014
13640
  }, [auth, clientName, connection, priorRefreshToken]);
13015
13641
  useEffect(() => {
13016
13642
  if (phase.kind === "success" || phase.kind === "expired" || phase.kind === "denied" || phase.kind === "failed") {
13017
- onComplete();
13018
- exit();
13643
+ finish();
13019
13644
  }
13020
- }, [phase, onComplete, exit]);
13645
+ }, [phase, finish]);
13021
13646
  if (phase.kind === "init") {
13022
13647
  return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
13023
13648
  /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
@@ -13069,14 +13694,14 @@ var Login = ({ auth, clientName, connection, priorRefreshToken, onComplete }) =>
13069
13694
  };
13070
13695
 
13071
13696
  // src/commands/auth/login-api-key.tsx
13072
- import { Box as Box2, Text as Text2, useApp as useApp2 } from "ink";
13697
+ import { Box as Box2, Text as Text2 } from "ink";
13073
13698
  import Spinner2 from "ink-spinner";
13074
13699
  import { useEffect as useEffect2, useReducer as useReducer2 } from "react";
13075
13700
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
13076
13701
  var LoginApiKey = ({ apiKey: apiKey2, auth, connection, onComplete }) => {
13077
13702
  const initial = { kind: "validating" };
13078
13703
  const [phase, dispatch] = useReducer2(reduceAuthLoginApiKey, initial);
13079
- const { exit } = useApp2();
13704
+ const { finish } = useFlowExit(onComplete);
13080
13705
  useEffect2(() => {
13081
13706
  const run = auth.loginApiKey({ apiKey: apiKey2, connection });
13082
13707
  let cancelled = false;
@@ -13092,9 +13717,8 @@ var LoginApiKey = ({ apiKey: apiKey2, auth, connection, onComplete }) => {
13092
13717
  }, [apiKey2, auth, connection]);
13093
13718
  useEffect2(() => {
13094
13719
  if (phase.kind === "validating") return;
13095
- onComplete();
13096
- exit();
13097
- }, [phase, onComplete, exit]);
13720
+ finish();
13721
+ }, [phase, finish]);
13098
13722
  if (phase.kind === "validating") {
13099
13723
  return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
13100
13724
  /* @__PURE__ */ jsx2(Spinner2, { type: "dots" }),
@@ -13136,19 +13760,19 @@ var LoginPrompt = ({ userDisplay, onAccept, onReject }) => {
13136
13760
  };
13137
13761
 
13138
13762
  // src/commands/auth/logout.tsx
13139
- import { Box as Box4, Text as Text4, useApp as useApp3 } from "ink";
13763
+ import { Box as Box4, Text as Text4 } from "ink";
13140
13764
  import Spinner3 from "ink-spinner";
13141
- import { useCallback } from "react";
13765
+ import { useCallback as useCallback2 } from "react";
13142
13766
 
13143
13767
  // src/hooks/use-flow-state.ts
13144
- import { useEffect as useEffect3, useRef, useState } from "react";
13768
+ import { useEffect as useEffect3, useRef as useRef2, useState } from "react";
13145
13769
  function useFlowState(action, onComplete) {
13146
13770
  const [status, setStatus] = useState("loading");
13147
13771
  const [data, setData] = useState(null);
13148
13772
  const [error, setError] = useState("");
13149
- const onCompleteRef = useRef(onComplete);
13773
+ const onCompleteRef = useRef2(onComplete);
13150
13774
  onCompleteRef.current = onComplete;
13151
- const completedRef = useRef(false);
13775
+ const completedRef = useRef2(false);
13152
13776
  useEffect3(() => {
13153
13777
  let cancelled = false;
13154
13778
  const run = async () => {
@@ -13184,13 +13808,9 @@ function useFlowState(action, onComplete) {
13184
13808
  // src/commands/auth/logout.tsx
13185
13809
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
13186
13810
  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);
13811
+ const action = useCallback2(() => auth.logout(), [auth]);
13812
+ const { finish } = useFlowExit(onComplete);
13813
+ const { status } = useFlowState(action, finish);
13194
13814
  if (status === "loading") {
13195
13815
  return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: "cyan", children: [
13196
13816
  /* @__PURE__ */ jsx4(Spinner3, { type: "dots" }),
@@ -13220,7 +13840,7 @@ var statusOptions = z.object({
13220
13840
  });
13221
13841
 
13222
13842
  // src/commands/auth/status.tsx
13223
- import { Box as Box5, Text as Text5, useApp as useApp4 } from "ink";
13843
+ import { Box as Box5, Text as Text5 } from "ink";
13224
13844
  import Spinner4 from "ink-spinner";
13225
13845
  import { useEffect as useEffect4, useReducer as useReducer3 } from "react";
13226
13846
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -13265,7 +13885,7 @@ var AuthStatus = ({
13265
13885
  }) => {
13266
13886
  const initial = { kind: "loading" };
13267
13887
  const [view, dispatch] = useReducer3(reduce, initial);
13268
- const { exit } = useApp4();
13888
+ const { finish } = useFlowExit(onComplete);
13269
13889
  useEffect4(() => {
13270
13890
  let cancelled = false;
13271
13891
  const options = composeOptions({ apiKey: apiKey2, displayConnection: displayConnection2, verbose: verbose2 });
@@ -13285,9 +13905,8 @@ var AuthStatus = ({
13285
13905
  }, [auth, probe, apiKey2, displayConnection2, verbose2]);
13286
13906
  useEffect4(() => {
13287
13907
  if (view.kind === "loading" || view.kind === "probing") return;
13288
- onComplete();
13289
- exit();
13290
- }, [view, onComplete, exit]);
13908
+ finish();
13909
+ }, [view, finish]);
13291
13910
  if (view.kind === "loading") return null;
13292
13911
  if (view.kind === "probing") {
13293
13912
  return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
@@ -13422,7 +14041,7 @@ var InteractiveLoginShell = ({
13422
14041
  }) => {
13423
14042
  const initial = { kind: "probing" };
13424
14043
  const [stage, setStage] = useState2(initial);
13425
- const { exit } = useApp5();
14044
+ const { finish } = useFlowExit(onComplete);
13426
14045
  useEffect5(() => {
13427
14046
  let cancelled = false;
13428
14047
  const auth = authStorage2.getAuth();
@@ -13451,9 +14070,8 @@ var InteractiveLoginShell = ({
13451
14070
  }, [authStorage2, userResource]);
13452
14071
  useEffect5(() => {
13453
14072
  if (stage.kind !== "declined") return;
13454
- onComplete();
13455
- exit();
13456
- }, [stage, onComplete, exit]);
14073
+ finish();
14074
+ }, [stage, finish]);
13457
14075
  if (stage.kind === "probing") return null;
13458
14076
  if (stage.kind === "prompt") {
13459
14077
  return /* @__PURE__ */ jsx6(
@@ -13741,9 +14359,9 @@ function assertSessionGuard(c, storage2, inflow2) {
13741
14359
  }
13742
14360
 
13743
14361
  // src/commands/balances/list.tsx
13744
- import { Box as Box7, Text as Text8, useApp as useApp6 } from "ink";
14362
+ import { Box as Box7, Text as Text8 } from "ink";
13745
14363
  import Spinner5 from "ink-spinner";
13746
- import { useCallback as useCallback2 } from "react";
14364
+ import { useCallback as useCallback3 } from "react";
13747
14365
 
13748
14366
  // src/utils/table.tsx
13749
14367
  import { Box as Box6, Text as Text7 } from "ink";
@@ -13814,16 +14432,9 @@ var COLUMNS = [
13814
14432
  { header: "Available", cell: (b) => b.available }
13815
14433
  ];
13816
14434
  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);
14435
+ const action = useCallback3(() => balanceResource.list(), [balanceResource]);
14436
+ const { finish } = useFlowExit(onComplete);
14437
+ const { status, data: balances, error } = useFlowState(action, finish);
13827
14438
  if (status === "loading") {
13828
14439
  return /* @__PURE__ */ jsx8(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: "cyan", children: [
13829
14440
  /* @__PURE__ */ jsx8(Spinner5, { type: "dots" }),
@@ -13887,9 +14498,9 @@ import { Cli as Cli3 } from "incur";
13887
14498
  import "react";
13888
14499
 
13889
14500
  // src/commands/deposit-addresses/list.tsx
13890
- import { Box as Box8, Text as Text9, useApp as useApp7 } from "ink";
14501
+ import { Box as Box8, Text as Text9 } from "ink";
13891
14502
  import Spinner6 from "ink-spinner";
13892
- import { useCallback as useCallback3 } from "react";
14503
+ import { useCallback as useCallback4 } from "react";
13893
14504
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
13894
14505
  var COLUMNS2 = [
13895
14506
  { header: "Blockchain", cell: (a) => a.blockchain },
@@ -13897,16 +14508,9 @@ var COLUMNS2 = [
13897
14508
  { header: "Currencies", cell: (a) => a.currencies.join(", ") }
13898
14509
  ];
13899
14510
  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);
14511
+ const action = useCallback4(() => runDepositAddressesList({ depositAddressResource }), [depositAddressResource]);
14512
+ const { finish } = useFlowExit(onComplete);
14513
+ const { status, data: addresses, error } = useFlowState(action, finish);
13910
14514
  if (status === "loading") {
13911
14515
  return /* @__PURE__ */ jsx10(Box8, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
13912
14516
  /* @__PURE__ */ jsx10(Spinner6, { type: "dots" }),
@@ -13971,204 +14575,112 @@ function createDepositAddressesCli(depositAddressResource, authStorage2, inflow2
13971
14575
  return cli2;
13972
14576
  }
13973
14577
 
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
14578
+ // src/commands/mpp/index.tsx
14076
14579
  import { chmodSync, writeFileSync as writeFileSync2 } from "fs";
14077
14580
  import { resolve as resolvePath2 } from "path";
14078
- import { Cli as Cli5 } from "incur";
14581
+ import { Cli as Cli4 } from "incur";
14079
14582
 
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";
14583
+ // src/commands/mpp/cancel.tsx
14584
+ import { Box as Box9, Text as Text10 } from "ink";
14585
+ import Spinner7 from "ink-spinner";
14083
14586
  import { useCallback as useCallback5 } from "react";
14084
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
14587
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
14085
14588
  var CancelView = ({ approvalId, cancel, onComplete }) => {
14086
- const { exit } = useApp9();
14087
14589
  const action = useCallback5(() => cancel(), [cancel]);
14088
- const handleComplete = useCallback5(() => {
14089
- onComplete();
14090
- exit();
14091
- }, [onComplete, exit]);
14092
- const { status } = useFlowState(action, handleComplete);
14590
+ const { finish } = useFlowExit(onComplete);
14591
+ const { status } = useFlowState(action, finish);
14093
14592
  if (status === "loading") {
14094
- return /* @__PURE__ */ jsx14(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
14095
- /* @__PURE__ */ jsx14(Spinner8, { type: "dots" }),
14593
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
14594
+ /* @__PURE__ */ jsx12(Spinner7, { type: "dots" }),
14096
14595
  " Cancelling approval ",
14097
14596
  approvalId,
14098
14597
  "..."
14099
14598
  ] }) });
14100
14599
  }
14101
- return /* @__PURE__ */ jsx14(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: "green", children: [
14600
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
14102
14601
  "\u2713 Cancelled approval ",
14103
14602
  approvalId,
14104
14603
  " (best-effort)"
14105
14604
  ] }) });
14106
14605
  };
14107
14606
 
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 }),
14607
+ // src/commands/mpp/decode.tsx
14608
+ import { Box as Box10, Text as Text11 } from "ink";
14609
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
14610
+ function ChallengeBody({ challenge }) {
14611
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14612
+ /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Challenge" }),
14613
+ /* @__PURE__ */ jsxs10(Text11, { children: [
14614
+ "method/intent: ",
14615
+ /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
14616
+ challenge.method,
14129
14617
  " / ",
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
14618
+ challenge.intent
14619
+ ] })
14620
+ ] }),
14621
+ /* @__PURE__ */ jsx13(Text11, { children: `id: ${challenge.id}` }),
14622
+ /* @__PURE__ */ jsx13(Text11, { children: `realm: ${challenge.realm}` }),
14623
+ challenge.amount !== void 0 ? /* @__PURE__ */ jsx13(Text11, { children: `amount: ${challenge.amount}${challenge.currency !== void 0 ? ` ${challenge.currency}` : ""}` }) : null,
14624
+ challenge.rail !== void 0 ? /* @__PURE__ */ jsx13(Text11, { children: `rail: ${challenge.rail}` }) : null,
14625
+ challenge.expires !== void 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `expires: ${challenge.expires}` }) : null
14626
+ ] });
14627
+ }
14628
+ var DecodeView = ({ result }) => {
14629
+ if (result.kind === "challenge") {
14630
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14631
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded WWW-Authenticate: Payment" }) }),
14632
+ /* @__PURE__ */ jsx13(ChallengeBody, { challenge: result.challenge })
14633
+ ] });
14634
+ }
14635
+ if (result.kind === "credential") {
14636
+ const { credential } = result;
14637
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14638
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded Authorization: Payment credential" }) }),
14639
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14640
+ /* @__PURE__ */ jsx13(Text11, { children: `challenge id: ${credential.challenge.id}` }),
14641
+ /* @__PURE__ */ jsx13(Text11, { children: `method: ${credential.challenge.method}` }),
14642
+ /* @__PURE__ */ jsx13(Text11, { children: `source: ${credential.source}` }),
14643
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `payload keys: ${Object.keys(credential.payload).join(", ") || "(none)"}` })
14644
+ ] })
14645
+ ] });
14646
+ }
14647
+ const { receipt } = result;
14648
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingY: 1, children: [
14649
+ /* @__PURE__ */ jsx13(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "Decoded Payment-Receipt" }) }),
14650
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14651
+ /* @__PURE__ */ jsxs10(Text11, { children: [
14652
+ "status: ",
14653
+ /* @__PURE__ */ jsx13(Text11, { color: "green", children: receipt.status })
14654
+ ] }),
14655
+ /* @__PURE__ */ jsx13(Text11, { children: `reference: ${receipt.reference}` }),
14656
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `timestamp: ${receipt.timestamp}` })
14135
14657
  ] })
14136
14658
  ] });
14137
14659
  };
14138
14660
 
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";
14661
+ // src/commands/mpp/inspect.tsx
14662
+ import { Box as Box11, Text as Text12 } from "ink";
14663
+ import Spinner8 from "ink-spinner";
14142
14664
  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;
14665
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
14666
+ function orDash(value) {
14667
+ return value === void 0 || value === "" ? "\u2014" : value;
14155
14668
  }
14156
14669
  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) }
14670
+ { header: "Method", cell: (c) => c.method },
14671
+ { header: "Intent", cell: (c) => c.intent },
14672
+ { header: "Amount", cell: (c) => orDash(c.amount) },
14673
+ { header: "Currency", cell: (c) => orDash(c.currency) },
14674
+ { header: "Rail", cell: (c) => orDash(c.rail) },
14675
+ { header: "Expires", cell: (c) => orDash(c.expires) }
14164
14676
  ];
14165
14677
  var InspectView = ({ url, method, deps, onComplete }) => {
14166
- const { exit } = useApp10();
14167
14678
  const initial = { kind: "probing" };
14168
- const [phase, dispatch] = useReducer4(reduceX402Inspect, initial);
14679
+ const [phase, dispatch] = useReducer4(reduceMppInspect, initial);
14680
+ const { finish } = useFlowExit(onComplete);
14169
14681
  useEffect6(() => {
14170
14682
  let cancelled = false;
14171
- void runInspectPipeline(deps, (event) => {
14683
+ void runMppInspectPipeline(deps, (event) => {
14172
14684
  if (!cancelled) dispatch(event);
14173
14685
  });
14174
14686
  return () => {
@@ -14176,14 +14688,13 @@ var InspectView = ({ url, method, deps, onComplete }) => {
14176
14688
  };
14177
14689
  }, [deps]);
14178
14690
  useEffect6(() => {
14179
- if (phase.kind === "accepts" || phase.kind === "no-payment" || phase.kind === "error") {
14180
- onComplete(phase);
14181
- exit();
14691
+ if (phase.kind === "challenges" || phase.kind === "no-payment" || phase.kind === "error") {
14692
+ finish(phase);
14182
14693
  }
14183
- }, [phase, onComplete, exit]);
14694
+ }, [phase, finish]);
14184
14695
  if (phase.kind === "probing") {
14185
- return /* @__PURE__ */ jsx16(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14186
- /* @__PURE__ */ jsx16(Spinner9, { type: "dots" }),
14696
+ return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
14697
+ /* @__PURE__ */ jsx14(Spinner8, { type: "dots" }),
14187
14698
  " Probing ",
14188
14699
  method,
14189
14700
  " ",
@@ -14193,63 +14704,64 @@ var InspectView = ({ url, method, deps, onComplete }) => {
14193
14704
  }
14194
14705
  if (phase.kind === "no-payment") {
14195
14706
  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." })
14707
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14708
+ /* @__PURE__ */ jsx14(Text12, { color: "green", children: "\u2713 Seller accepted without payment" }),
14709
+ /* @__PURE__ */ jsx14(Text12, { children: `status: ${String(result2.status)}` }),
14710
+ result2.contentType !== void 0 ? /* @__PURE__ */ jsx14(Text12, { children: `content-type: ${result2.contentType}` }) : null,
14711
+ /* @__PURE__ */ jsx14(Text12, { children: `response size: ${String(result2.bodySizeBytes)} bytes` }),
14712
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Use `mpp pay` to fetch the body." })
14202
14713
  ] });
14203
14714
  }
14204
14715
  if (phase.kind === "error") {
14205
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14206
- /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
14716
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14717
+ /* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
14207
14718
  "\u2717 ",
14208
14719
  phase.code
14209
14720
  ] }),
14210
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: phase.message })
14721
+ /* @__PURE__ */ jsx14(Text12, { color: "red", children: phase.message })
14211
14722
  ] });
14212
14723
  }
14213
14724
  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" }),
14725
+ const count = result.challenges.length;
14726
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
14727
+ /* @__PURE__ */ jsxs11(Text12, { children: [
14728
+ /* @__PURE__ */ jsx14(Text12, { bold: true, children: "WWW-Authenticate: Payment" }),
14219
14729
  " for ",
14220
- /* @__PURE__ */ jsx16(Text13, { color: "cyan", children: result.resource }),
14730
+ /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: result.url }),
14221
14731
  " \xB7 ",
14222
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: `x402Version ${String(result.x402Version)}` }),
14732
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `realm ${result.realm}` }),
14223
14733
  " \xB7 ",
14224
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: `${String(acceptsCount)} accept${acceptsCount === 1 ? "" : "s"}` })
14734
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `${String(count)} challenge${count === 1 ? "" : "s"}` })
14225
14735
  ] }),
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." }) })
14736
+ /* @__PURE__ */ jsx14(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx14(Table, { columns: COLUMNS3, rows: [...result.challenges] }) }),
14737
+ /* @__PURE__ */ jsx14(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Use --format json to see challenge ids and digests." }) })
14229
14738
  ] });
14230
14739
  };
14231
- function buildAcceptsFrame(result) {
14232
- const frame = {
14233
- outcome: "accepts",
14740
+ function challengeToFrame(challenge) {
14741
+ const row = {
14742
+ id: challenge.id,
14743
+ realm: challenge.realm,
14744
+ method: challenge.method,
14745
+ intent: challenge.intent
14746
+ };
14747
+ if (challenge.amount !== void 0) row.amount = challenge.amount;
14748
+ if (challenge.currency !== void 0) row.currency = challenge.currency;
14749
+ if (challenge.recipient !== void 0) row.recipient = challenge.recipient;
14750
+ if (challenge.rail !== void 0) row.rail = challenge.rail;
14751
+ if (challenge.instrumentId !== void 0) row.instrument_id = challenge.instrumentId;
14752
+ if (challenge.expires !== void 0) row.expires = challenge.expires;
14753
+ if (challenge.description !== void 0) row.description = challenge.description;
14754
+ if (challenge.digest !== void 0) row.digest = challenge.digest;
14755
+ return row;
14756
+ }
14757
+ function buildChallengesFrame(result) {
14758
+ return {
14759
+ outcome: "challenges",
14234
14760
  url: result.url,
14235
14761
  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
- })
14762
+ realm: result.realm,
14763
+ challenges: result.challenges.map(challengeToFrame)
14250
14764
  };
14251
- if (result.extensions !== void 0) frame.extensions = result.extensions;
14252
- return frame;
14253
14765
  }
14254
14766
  function buildNoPaymentFrame(result) {
14255
14767
  const frame = {
@@ -14263,41 +14775,1136 @@ function buildNoPaymentFrame(result) {
14263
14775
  return frame;
14264
14776
  }
14265
14777
 
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();
14778
+ // src/commands/mpp/pay.tsx
14779
+ import { Box as Box12, Text as Text13, useInput as useInput3 } from "ink";
14780
+ import Spinner9 from "ink-spinner";
14781
+ import { useEffect as useEffect7, useReducer as useReducer5, useState as useState3 } from "react";
14782
+ import { Fragment, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
14783
+ var PayView = ({ url, method, deps, onComplete, onCancel }) => {
14273
14784
  const initial = { kind: "probing" };
14274
- const [phase, dispatch] = useReducer5(reducePay, initial);
14785
+ const [phase, dispatch] = useReducer5(reduceMppPay, initial);
14786
+ const [cancelling, setCancelling] = useState3(false);
14787
+ const { finish, cancelThenFinish } = useFlowExit(onComplete);
14788
+ const created = phase.kind === "created" ? phase.created : void 0;
14789
+ const approvalUrl = created?.approvalUrl;
14790
+ const approvalId = created?.approvalId;
14275
14791
  useInput3(
14276
14792
  (_input, key) => {
14277
- if (key.return && phase.kind === "awaiting-approval") {
14278
- openUrl(phase.approvalUrl);
14793
+ if (approvalUrl === void 0) return;
14794
+ if (key.return) {
14795
+ openUrl(approvalUrl);
14796
+ return;
14797
+ }
14798
+ if (key.escape && approvalId !== void 0) {
14799
+ setCancelling(true);
14800
+ cancelThenFinish(() => onCancel?.(approvalId), {
14801
+ kind: "error",
14802
+ code: "APPROVAL_CANCELLED",
14803
+ message: `Approval ${approvalId} cancelled.`
14804
+ });
14279
14805
  }
14280
14806
  },
14281
- { isActive: phase.kind === "awaiting-approval" }
14807
+ { isActive: approvalUrl !== void 0 && !cancelling }
14282
14808
  );
14283
14809
  useEffect7(() => {
14810
+ const controller = new AbortController();
14284
14811
  let cancelled = false;
14285
- void runPayPipeline(deps, (event) => {
14812
+ const runDeps = { ...deps, signal: controller.signal };
14813
+ void runMppPayPipeline(runDeps, (event) => {
14286
14814
  if (!cancelled) dispatch(event);
14287
14815
  });
14288
14816
  return () => {
14289
14817
  cancelled = true;
14818
+ controller.abort();
14290
14819
  };
14291
14820
  }, [deps]);
14292
14821
  useEffect7(() => {
14293
- if (phase.kind === "success" || phase.kind === "replay-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
14294
- onComplete(phase);
14295
- exit();
14822
+ if (phase.kind === "success" || phase.kind === "seller-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
14823
+ finish(phase);
14824
+ }
14825
+ }, [phase, finish]);
14826
+ if (cancelling) {
14827
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "yellow", children: [
14828
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14829
+ " Cancelling approval..."
14830
+ ] }) });
14831
+ }
14832
+ if (phase.kind === "probing") {
14833
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14834
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14835
+ " Probing ",
14836
+ method,
14837
+ " ",
14838
+ url,
14839
+ "..."
14840
+ ] }) });
14841
+ }
14842
+ if (phase.kind === "no-payment") {
14843
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14844
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14845
+ " Seller accepted without payment (status ",
14846
+ String(phase.probe.status),
14847
+ "); finalising..."
14848
+ ] }) });
14849
+ }
14850
+ if (phase.kind === "decoded") {
14851
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14852
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14853
+ " Fulfilling ",
14854
+ phase.challenge.amount ?? "",
14855
+ " ",
14856
+ phase.challenge.currency ?? "",
14857
+ " ",
14858
+ "challenge..."
14859
+ ] }) });
14860
+ }
14861
+ if (phase.kind === "created") {
14862
+ const { created: created2 } = phase;
14863
+ if (created2.state !== "pending") {
14864
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14865
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14866
+ " Transaction ",
14867
+ created2.transactionId,
14868
+ " ready; replaying..."
14869
+ ] }) });
14870
+ }
14871
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingY: 1, children: [
14872
+ /* @__PURE__ */ jsx15(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text13, { bold: true, children: "Approval required" }) }),
14873
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
14874
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${created2.transactionId}` }),
14875
+ created2.approvalUrl !== void 0 ? /* @__PURE__ */ jsxs12(Fragment, { children: [
14876
+ /* @__PURE__ */ jsxs12(Text13, { children: [
14877
+ "Open: ",
14878
+ /* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: created2.approvalUrl })
14879
+ ] }),
14880
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Press Enter to open in browser." }),
14881
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Press Escape to cancel." })
14882
+ ] }) : null
14883
+ ] }),
14884
+ /* @__PURE__ */ jsx15(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14885
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14886
+ " Waiting for approval..."
14887
+ ] }) })
14888
+ ] });
14889
+ }
14890
+ if (phase.kind === "replaying") {
14891
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
14892
+ /* @__PURE__ */ jsx15(Spinner9, { type: "dots" }),
14893
+ " Replaying request with Authorization: Payment..."
14894
+ ] }) });
14895
+ }
14896
+ if (phase.kind === "success") {
14897
+ const { result } = phase;
14898
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14899
+ /* @__PURE__ */ jsxs12(Text13, { color: "green", children: [
14900
+ "\u2713 Paid (intent ",
14901
+ result.intent,
14902
+ ")"
14903
+ ] }),
14904
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.responseStatus)}` }),
14905
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${result.transactionId}` }),
14906
+ result.settled !== void 0 ? /* @__PURE__ */ jsx15(Text13, { children: `settled: ${result.settled.status ?? "success"} (ref ${result.settled.reference ?? "\u2014"})` }) : null,
14907
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14908
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs12(Text13, { children: [
14909
+ "Saved to: ",
14910
+ /* @__PURE__ */ jsx15(Text13, { bold: true, children: result.outputSavedTo })
14911
+ ] }) : null,
14912
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14913
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14914
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14915
+ ] }) : null
14916
+ ] });
14917
+ }
14918
+ if (phase.kind === "seller-rejected") {
14919
+ const { result } = phase;
14920
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14921
+ /* @__PURE__ */ jsx15(Text13, { color: "red", children: "\u2717 Payment not accepted by seller" }),
14922
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.responseStatus)}` }),
14923
+ /* @__PURE__ */ jsx15(Text13, { children: `transaction: ${result.transactionId}` }),
14924
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14925
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14926
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14927
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14928
+ ] }) : null
14929
+ ] });
14930
+ }
14931
+ if (phase.kind === "no-payment-final") {
14932
+ const { result } = phase;
14933
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14934
+ /* @__PURE__ */ jsx15(Text13, { color: "green", children: "\u2713 Seller accepted without payment" }),
14935
+ /* @__PURE__ */ jsx15(Text13, { children: `status: ${String(result.status)}` }),
14936
+ /* @__PURE__ */ jsx15(Text13, { children: `response size: ${String(result.bodySizeBytes)} bytes` }),
14937
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs12(Text13, { children: [
14938
+ "Saved to: ",
14939
+ /* @__PURE__ */ jsx15(Text13, { bold: true, children: result.outputSavedTo })
14940
+ ] }) : null,
14941
+ result.body !== void 0 ? /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
14942
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "response body:" }),
14943
+ /* @__PURE__ */ jsx15(Text13, { children: result.body })
14944
+ ] }) : null
14945
+ ] });
14946
+ }
14947
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
14948
+ /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
14949
+ "\u2717 ",
14950
+ phase.code
14951
+ ] }),
14952
+ /* @__PURE__ */ jsx15(Text13, { color: "red", children: phase.message })
14953
+ ] });
14954
+ };
14955
+
14956
+ // src/commands/mpp/schema.ts
14957
+ import { z as z4 } from "incur";
14958
+ var payArgs = z4.object({
14959
+ url: z4.string().describe("The MPP-protected resource URL to pay for.")
14960
+ });
14961
+ var payOptions = z4.object({
14962
+ paymentMethod: z4.string().optional().describe('Only consider challenges with this payment method (e.g. "inflow").'),
14963
+ intent: z4.string().optional().describe('Only consider challenges with this intent (e.g. "charge").'),
14964
+ currency: z4.string().optional().describe('Only consider challenges in this currency (e.g. "USDC").'),
14965
+ rail: z4.string().optional().describe('Only consider challenges on this settlement rail (e.g. "balance", "instrument").'),
14966
+ method: z4.string().default("GET").describe("HTTP method for the seller request."),
14967
+ data: z4.string().optional().describe(
14968
+ "Request body. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
14969
+ ),
14970
+ header: z4.array(z4.string()).default([]).describe('Repeatable. "Name: Value" format.'),
14971
+ interval: z4.coerce.number().default(0).describe(
14972
+ "Inline poll cadence in seconds while a transaction is pending. 0 returns the transaction id and a follow-up command hint without blocking."
14973
+ ),
14974
+ maxAttempts: z4.coerce.number().default(0).describe("Hard cap on poll attempts when --interval > 0. 0 means unlimited."),
14975
+ timeout: z4.coerce.number().default(900).describe("Polling deadline in seconds. Default 900s (matches the server-side approval expiry)."),
14976
+ instrumentId: z4.string().optional().describe(
14977
+ "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."
14978
+ ),
14979
+ showBody: z4.boolean().default(true).describe(
14980
+ "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)."
14981
+ ),
14982
+ outputFile: z4.string().optional().describe(
14983
+ "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)."
14984
+ ),
14985
+ credentialFile: z4.string().optional().describe(
14986
+ "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."
14987
+ )
14988
+ });
14989
+ var statusArgs = z4.object({
14990
+ transactionId: z4.string().describe("The transaction id returned by `mpp pay`.")
14991
+ });
14992
+ var statusOptions2 = z4.object({
14993
+ interval: z4.coerce.number().default(0).describe(
14994
+ "Poll cadence in seconds. 0 returns the current snapshot; positive values yield on every change until ready or terminal."
14995
+ ),
14996
+ maxAttempts: z4.coerce.number().default(0).describe("Hard cap on poll attempts. 0 means unlimited."),
14997
+ timeout: z4.coerce.number().default(900).describe("Polling deadline in seconds."),
14998
+ credentialFile: z4.string().optional().describe(
14999
+ "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."
15000
+ )
15001
+ });
15002
+ var cancelArgs = z4.object({
15003
+ approvalId: z4.string().describe("The approval id returned by `mpp pay` (on the pending frame).")
15004
+ });
15005
+ var decodeArgs = z4.object({
15006
+ value: z4.string().describe(
15007
+ "A raw `WWW-Authenticate: Payment` header value, or a base64url `Authorization: Payment` credential / `Payment-Receipt`. The kind is auto-detected."
15008
+ )
15009
+ });
15010
+ var inspectArgs = z4.object({
15011
+ url: z4.string().describe("The MPP-protected resource URL to probe. No payment is made.")
15012
+ });
15013
+ var inspectOptions = z4.object({
15014
+ paymentMethod: z4.string().optional().describe('Only show challenges with this payment method (e.g. "inflow").'),
15015
+ intent: z4.string().optional().describe('Only show challenges with this intent (e.g. "charge").'),
15016
+ currency: z4.string().optional().describe('Only show challenges in this currency (e.g. "USDC").'),
15017
+ rail: z4.string().optional().describe('Only show challenges on this settlement rail (e.g. "balance", "instrument").'),
15018
+ method: z4.string().default("GET").describe("HTTP method for the probe request."),
15019
+ data: z4.string().optional().describe(
15020
+ "Request body for the probe. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
15021
+ ),
15022
+ header: z4.array(z4.string()).default([]).describe('Repeatable. "Name: Value" format.')
15023
+ });
15024
+
15025
+ // src/commands/mpp/status.tsx
15026
+ import { Box as Box13, Text as Text14 } from "ink";
15027
+ import Spinner10 from "ink-spinner";
15028
+ import { useEffect as useEffect8, useReducer as useReducer6 } from "react";
15029
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
15030
+ var MppStatusView = ({
15031
+ transactionId,
15032
+ fetchOnce,
15033
+ interval,
15034
+ maxAttempts,
15035
+ timeout,
15036
+ onComplete
15037
+ }) => {
15038
+ const initial = { kind: "polling" };
15039
+ const [phase, dispatch] = useReducer6(reduceMppStatus, initial);
15040
+ const { finish } = useFlowExit(onComplete);
15041
+ useEffect8(() => {
15042
+ const run = runMppStatus({ fetchOnce, interval, maxAttempts, timeout });
15043
+ let cancelled = false;
15044
+ void (async () => {
15045
+ for await (const event of run.events) {
15046
+ if (cancelled) return;
15047
+ dispatch(event);
15048
+ }
15049
+ })();
15050
+ return () => {
15051
+ cancelled = true;
15052
+ };
15053
+ }, [fetchOnce, interval, maxAttempts, timeout]);
15054
+ useEffect8(() => {
15055
+ if (phase.kind === "ready" || phase.kind === "failed" || phase.kind === "expired" || phase.kind === "timeout" || phase.kind === "error") {
15056
+ finish(phase);
15057
+ }
15058
+ }, [phase, finish]);
15059
+ if (phase.kind === "polling") {
15060
+ const stateText = phase.latest?.state ?? "pending";
15061
+ return /* @__PURE__ */ jsx16(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
15062
+ /* @__PURE__ */ jsx16(Spinner10, { type: "dots" }),
15063
+ " Polling transaction ",
15064
+ transactionId,
15065
+ " (state: ",
15066
+ stateText,
15067
+ ")..."
15068
+ ] }) });
15069
+ }
15070
+ if (phase.kind === "ready") {
15071
+ const credential = phase.response.credential ?? "";
15072
+ const preview = credential.length > 32 ? `${credential.slice(0, 32)}...` : credential;
15073
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15074
+ /* @__PURE__ */ jsx16(Text14, { color: "green", children: "\u2713 Ready" }),
15075
+ /* @__PURE__ */ jsx16(Text14, { children: `credential: ${preview}` }),
15076
+ phase.response.expires !== void 0 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: `expires: ${phase.response.expires}` }) : null
15077
+ ] });
15078
+ }
15079
+ if (phase.kind === "failed") {
15080
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15081
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: "\u2717 Transaction failed" }),
15082
+ phase.response.problem !== void 0 ? /* @__PURE__ */ jsx16(Text14, { color: "red", children: phase.response.problem.detail ?? phase.response.problem.title }) : null
15083
+ ] });
15084
+ }
15085
+ if (phase.kind === "expired") {
15086
+ return /* @__PURE__ */ jsx16(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx16(Text14, { color: "yellow", children: "Transaction expired before it was ready." }) });
15087
+ }
15088
+ if (phase.kind === "timeout") {
15089
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15090
+ /* @__PURE__ */ jsx16(Text14, { color: "yellow", children: "Polling timed out before the transaction reached a ready state." }),
15091
+ phase.response !== void 0 ? /* @__PURE__ */ jsx16(Text14, { children: `last state: ${phase.response.state}` }) : null
15092
+ ] });
15093
+ }
15094
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
15095
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: "\u2717 Polling failed" }),
15096
+ /* @__PURE__ */ jsx16(Text14, { color: "red", children: phase.message })
15097
+ ] });
15098
+ };
15099
+
15100
+ // src/commands/mpp/supported.tsx
15101
+ import { Box as Box14, Text as Text15 } from "ink";
15102
+ import Spinner11 from "ink-spinner";
15103
+ import { useCallback as useCallback6 } from "react";
15104
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
15105
+ function flattenKinds(response) {
15106
+ const rows = [];
15107
+ for (const kind of response.kinds) {
15108
+ for (const intent of kind.intents) {
15109
+ for (const rail of intent.rails) {
15110
+ rows.push({
15111
+ method: kind.method,
15112
+ intent: intent.intent,
15113
+ rail: rail.rail,
15114
+ currencies: rail.currencies.join(", ") || "\u2014"
15115
+ });
15116
+ }
15117
+ }
15118
+ }
15119
+ return rows;
15120
+ }
15121
+ var COLUMNS4 = [
15122
+ { header: "Method", cell: (r) => r.method },
15123
+ { header: "Intent", cell: (r) => r.intent },
15124
+ { header: "Rail", cell: (r) => r.rail },
15125
+ { header: "Currencies", cell: (r) => r.currencies }
15126
+ ];
15127
+ var SupportedView = ({ load, onComplete }) => {
15128
+ const action = useCallback6(() => load(), [load]);
15129
+ const { finish } = useFlowExit(onComplete);
15130
+ const { status, data, error } = useFlowState(action, finish);
15131
+ if (status === "loading") {
15132
+ return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs14(Text15, { color: "cyan", children: [
15133
+ /* @__PURE__ */ jsx17(Spinner11, { type: "dots" }),
15134
+ " Loading supported MPP methods..."
15135
+ ] }) });
15136
+ }
15137
+ if (status === "error") {
15138
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
15139
+ /* @__PURE__ */ jsx17(Text15, { color: "red", children: "Failed to load supported MPP methods" }),
15140
+ /* @__PURE__ */ jsx17(Text15, { color: "red", children: error })
15141
+ ] });
15142
+ }
15143
+ const rows = data ? flattenKinds(data) : [];
15144
+ if (rows.length === 0) {
15145
+ return /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", children: /* @__PURE__ */ jsx17(Text15, { children: "No supported MPP methods returned for this account." }) });
15146
+ }
15147
+ return /* @__PURE__ */ jsx17(Table, { columns: COLUMNS4, rows });
15148
+ };
15149
+
15150
+ // src/commands/mpp/index.tsx
15151
+ import { jsx as jsx18 } from "react/jsx-runtime";
15152
+ 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.";
15153
+ 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.";
15154
+ function parseHeaderFlagsOrFail(c, flags) {
15155
+ try {
15156
+ return parseHeaderFlags(flags);
15157
+ } catch (err) {
15158
+ c.error({ code: "INVALID_HEADER", message: err instanceof Error ? err.message : String(err) });
15159
+ }
15160
+ }
15161
+ function decorateCredentialField(frame, credential, credentialFile) {
15162
+ if (credentialFile !== void 0 && credentialFile.length > 0) {
15163
+ const absolute = resolvePath2(credentialFile);
15164
+ writeFileSync2(absolute, Buffer.from(credential, "utf-8"), { mode: 384 });
15165
+ chmodSync(absolute, 384);
15166
+ frame.credential_saved_to = absolute;
15167
+ return;
15168
+ }
15169
+ frame.credential = credential;
15170
+ }
15171
+ function probeOptionsFrom(c) {
15172
+ const headers = parseHeaderFlagsOrFail(c, c.options.header);
15173
+ return {
15174
+ method: c.options.method,
15175
+ headers,
15176
+ ...c.options.data !== void 0 ? { data: c.options.data } : {}
15177
+ };
15178
+ }
15179
+ function buildPayPipelineInput(c) {
15180
+ return {
15181
+ probeOptions: probeOptionsFrom(c),
15182
+ url: c.args.url,
15183
+ showBody: c.options.showBody,
15184
+ interval: c.options.interval,
15185
+ maxAttempts: c.options.maxAttempts,
15186
+ timeout: c.options.timeout,
15187
+ ...c.options.instrumentId !== void 0 ? { instrumentId: c.options.instrumentId } : {},
15188
+ ...c.options.paymentMethod !== void 0 ? { paymentMethodFilter: c.options.paymentMethod } : {},
15189
+ ...c.options.intent !== void 0 ? { intentFilter: c.options.intent } : {},
15190
+ ...c.options.currency !== void 0 ? { currencyFilter: c.options.currency } : {},
15191
+ ...c.options.rail !== void 0 ? { railFilter: c.options.rail } : {},
15192
+ ...c.options.outputFile !== void 0 ? { outputFile: c.options.outputFile } : {}
15193
+ };
15194
+ }
15195
+ function attachBodyFields(frame, result) {
15196
+ frame.body_size_bytes = result.bodySizeBytes;
15197
+ if (result.body !== void 0) frame.body = result.body;
15198
+ if (result.bodyBase64 !== void 0) frame.body_base64 = result.bodyBase64;
15199
+ if (result.outputSavedTo !== void 0) frame.output_saved_to = result.outputSavedTo;
15200
+ }
15201
+ function challengeFields(challenge) {
15202
+ const out = {
15203
+ id: challenge.id,
15204
+ method: challenge.method,
15205
+ intent: challenge.intent
15206
+ };
15207
+ if (challenge.amount !== void 0) out.amount = challenge.amount;
15208
+ if (challenge.currency !== void 0) out.currency = challenge.currency;
15209
+ if (challenge.rail !== void 0) out.rail = challenge.rail;
15210
+ return out;
15211
+ }
15212
+ function noPaymentFrameFromResult(result) {
15213
+ const frame = { outcome: "no-payment-required", status: result.status };
15214
+ if (result.contentType !== void 0) frame.content_type = result.contentType;
15215
+ attachBodyFields(frame, result);
15216
+ return frame;
15217
+ }
15218
+ function createdFrameFromEvent(created, interval, maxAttempts) {
15219
+ const pending = created.state === "pending";
15220
+ const frame = {
15221
+ transaction_id: created.transactionId,
15222
+ state: created.state,
15223
+ challenge: challengeFields(created.challenge),
15224
+ instruction: interval > 0 ? POLLING_INSTRUCTION : POST_CREATE_INSTRUCTION
15225
+ };
15226
+ if (created.approvalId !== void 0) frame.approval_id = created.approvalId;
15227
+ if (created.approvalUrl !== void 0) frame.approval_url = created.approvalUrl;
15228
+ if (created.retryAfterSeconds !== void 0) frame.retry_after_seconds = created.retryAfterSeconds;
15229
+ if (created.expires !== void 0) frame.expires = created.expires;
15230
+ if (pending && interval <= 0) {
15231
+ const max = maxAttempts > 0 ? maxAttempts : 60;
15232
+ frame._next = {
15233
+ command: `mpp status ${created.transactionId} --interval 5 --max-attempts ${String(max)}`,
15234
+ poll_interval_seconds: 5,
15235
+ until: "state is ready (credential present)"
15236
+ };
15237
+ }
15238
+ return frame;
15239
+ }
15240
+ function paidFrameFromResult(result, credentialFile) {
15241
+ const frame = {
15242
+ outcome: "paid",
15243
+ transaction_id: result.transactionId,
15244
+ challenge_id: result.challengeId,
15245
+ intent: result.intent,
15246
+ response_status: result.responseStatus
15247
+ };
15248
+ decorateCredentialField(frame, result.credential, credentialFile);
15249
+ if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
15250
+ if (result.settled !== void 0) frame.settled = result.settled;
15251
+ attachBodyFields(frame, result);
15252
+ return frame;
15253
+ }
15254
+ function rejectedFrameFromResult(result) {
15255
+ const frame = {
15256
+ outcome: "seller-rejected",
15257
+ transaction_id: result.transactionId,
15258
+ challenge_id: result.challengeId,
15259
+ response_status: result.responseStatus
15260
+ };
15261
+ if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
15262
+ attachBodyFields(frame, result);
15263
+ return frame;
15264
+ }
15265
+ function toStatusFrame(response, credentialFile) {
15266
+ const frame = {
15267
+ transaction_id: response.transactionId ?? "",
15268
+ state: response.state
15269
+ };
15270
+ if (response.state === "ready" && response.credential !== void 0) {
15271
+ decorateCredentialField(frame, response.credential, credentialFile);
15272
+ if (response.expires !== void 0) frame.expires = response.expires;
15273
+ }
15274
+ if (response.state === "pending") {
15275
+ if (response.approvalId !== void 0) frame.approval_id = response.approvalId;
15276
+ if (response.retryAfterSeconds !== void 0) frame.retry_after_seconds = response.retryAfterSeconds;
15277
+ }
15278
+ if (response.problem !== void 0) frame.problem = response.problem;
15279
+ return frame;
15280
+ }
15281
+ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
15282
+ assertSessionGuard(c, authStorage2, inflow2);
15283
+ if (!c.agent && !c.formatExplicit) {
15284
+ const client = await inflow2.mpp.client();
15285
+ let finalPhase = null;
15286
+ await renderInkUntilExit(
15287
+ /* @__PURE__ */ jsx18(
15288
+ PayView,
15289
+ {
15290
+ url: c.args.url,
15291
+ method: c.options.method,
15292
+ deps: {
15293
+ ...buildPayPipelineInput(c),
15294
+ client,
15295
+ apiBaseUrl: apiBaseUrl2,
15296
+ awaitPayment: true
15297
+ },
15298
+ onComplete: (phase) => {
15299
+ finalPhase = phase;
15300
+ },
15301
+ onCancel: (approvalId) => inflow2.mpp.cancel({ approvalId })
15302
+ }
15303
+ )
15304
+ );
15305
+ if (finalPhase !== null) {
15306
+ const phase = finalPhase;
15307
+ if (phase.kind === "seller-rejected") {
15308
+ c.error({
15309
+ code: PAYMENT_NOT_ACCEPTED_CODE2,
15310
+ message: `Seller rejected the credential with status ${String(phase.result.responseStatus)}. The transaction was ready but the seller did not honour the payment.`
15311
+ });
15312
+ }
15313
+ if (phase.kind === "error") {
15314
+ c.error({ code: phase.code, message: phase.message });
15315
+ }
15316
+ }
15317
+ return;
15318
+ }
15319
+ const run = inflow2.mpp.pay({
15320
+ ...buildPayPipelineInput(c),
15321
+ awaitPayment: c.options.interval > 0
15322
+ });
15323
+ for await (const event of run.events) {
15324
+ if (event.type === "short-circuited") {
15325
+ yield sanitizeDeep(noPaymentFrameFromResult(event.result));
15326
+ return;
15327
+ }
15328
+ if (event.type === "created") {
15329
+ yield sanitizeDeep(createdFrameFromEvent(event.created, c.options.interval, c.options.maxAttempts));
15330
+ continue;
15331
+ }
15332
+ if (event.type === "replayed") {
15333
+ yield sanitizeDeep(paidFrameFromResult(event.result, c.options.credentialFile));
15334
+ return;
15335
+ }
15336
+ if (event.type === "rejected") {
15337
+ yield sanitizeDeep(rejectedFrameFromResult(event.result));
15338
+ c.error({
15339
+ code: PAYMENT_NOT_ACCEPTED_CODE2,
15340
+ 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.`
15341
+ });
15342
+ return;
15343
+ }
15344
+ if (event.type === "errored") {
15345
+ c.error({ code: event.code, message: event.message });
15346
+ }
15347
+ }
15348
+ }
15349
+ async function* runStatusCommand(c, inflow2, authStorage2) {
15350
+ assertSessionGuard(c, authStorage2, inflow2);
15351
+ if (!c.agent && !c.formatExplicit) {
15352
+ const client2 = await inflow2.mpp.client();
15353
+ await renderInkUntilExit(
15354
+ /* @__PURE__ */ jsx18(
15355
+ MppStatusView,
15356
+ {
15357
+ transactionId: c.args.transactionId,
15358
+ fetchOnce: () => client2.getTransaction(c.args.transactionId),
15359
+ interval: c.options.interval,
15360
+ maxAttempts: c.options.maxAttempts,
15361
+ timeout: c.options.timeout,
15362
+ onComplete: () => void 0
15363
+ }
15364
+ )
15365
+ );
15366
+ return;
15367
+ }
15368
+ const client = await inflow2.mpp.client();
15369
+ const fetchOnce = () => client.getTransaction(c.args.transactionId);
15370
+ if (c.options.interval <= 0) {
15371
+ const snapshot = await fetchOnce();
15372
+ yield sanitizeDeep(toStatusFrame(snapshot, c.options.credentialFile));
15373
+ return;
15374
+ }
15375
+ const run = runMppStatus({
15376
+ fetchOnce,
15377
+ interval: c.options.interval,
15378
+ maxAttempts: c.options.maxAttempts,
15379
+ timeout: c.options.timeout
15380
+ });
15381
+ for await (const event of run.events) {
15382
+ if (event.type === "snapshot") {
15383
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15384
+ continue;
15385
+ }
15386
+ if (event.type === "ready") {
15387
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15388
+ return;
15389
+ }
15390
+ if (event.type === "failed") {
15391
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15392
+ c.error({
15393
+ code: "PAYMENT_FAILED",
15394
+ message: event.response.problem?.detail ?? event.response.problem?.title ?? "MPP transaction failed."
15395
+ });
15396
+ }
15397
+ if (event.type === "expired") {
15398
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15399
+ c.error({ code: "PAYMENT_EXPIRED", message: "MPP transaction expired before it was ready." });
15400
+ }
15401
+ if (event.type === "timedOut") {
15402
+ if (event.response !== void 0) {
15403
+ yield sanitizeDeep(toStatusFrame(event.response, c.options.credentialFile));
15404
+ }
15405
+ c.error({
15406
+ code: "POLLING_TIMEOUT",
15407
+ message: "Polling timed out before the transaction reached a ready state.",
15408
+ retryable: true
15409
+ });
15410
+ }
15411
+ if (event.type === "crashed") {
15412
+ c.error({ code: "PAYMENT_FAILED", message: event.message });
15413
+ }
15414
+ }
15415
+ }
15416
+ async function runCancelCommand(c, inflow2, authStorage2) {
15417
+ assertSessionGuard(c, authStorage2, inflow2);
15418
+ if (!c.agent && !c.formatExplicit) {
15419
+ await renderInkUntilExit(
15420
+ /* @__PURE__ */ jsx18(
15421
+ CancelView,
15422
+ {
15423
+ approvalId: c.args.approvalId,
15424
+ cancel: () => inflow2.mpp.cancel({ approvalId: c.args.approvalId }).then(() => void 0),
15425
+ onComplete: () => void 0
15426
+ }
15427
+ )
15428
+ );
15429
+ return { approval_id: c.args.approvalId, cancelled: true, note: "best-effort; server-side state not verified" };
15430
+ }
15431
+ return inflow2.mpp.cancel({ approvalId: c.args.approvalId });
15432
+ }
15433
+ async function runDecodeCommand(c) {
15434
+ let result;
15435
+ try {
15436
+ result = decodeMppValue(c.args.value);
15437
+ } catch (err) {
15438
+ return c.error({ code: "DECODE_FAILED", message: err instanceof Error ? err.message : String(err) });
15439
+ }
15440
+ if (!c.agent && !c.formatExplicit) {
15441
+ await renderInkUntilExit(/* @__PURE__ */ jsx18(DecodeView, { result }));
15442
+ return void 0;
15443
+ }
15444
+ return sanitizeDeep(result);
15445
+ }
15446
+ async function runSupportedCommand(c, inflow2, authStorage2) {
15447
+ assertSessionGuard(c, authStorage2, inflow2);
15448
+ if (!c.agent && !c.formatExplicit) {
15449
+ await renderInkUntilExit(/* @__PURE__ */ jsx18(SupportedView, { load: () => inflow2.mpp.supported(), onComplete: () => void 0 }));
15450
+ return void 0;
15451
+ }
15452
+ const response = await inflow2.mpp.supported();
15453
+ return sanitizeDeep(response);
15454
+ }
15455
+ async function runInspectCommand(c) {
15456
+ const deps = {
15457
+ probeOptions: probeOptionsFrom(c),
15458
+ url: c.args.url,
15459
+ ...c.options.paymentMethod !== void 0 ? { paymentMethodFilter: c.options.paymentMethod } : {},
15460
+ ...c.options.intent !== void 0 ? { intentFilter: c.options.intent } : {},
15461
+ ...c.options.currency !== void 0 ? { currencyFilter: c.options.currency } : {},
15462
+ ...c.options.rail !== void 0 ? { railFilter: c.options.rail } : {}
15463
+ };
15464
+ if (!c.agent && !c.formatExplicit) {
15465
+ let finalPhase = null;
15466
+ await renderInkUntilExit(
15467
+ /* @__PURE__ */ jsx18(
15468
+ InspectView,
15469
+ {
15470
+ url: c.args.url,
15471
+ method: c.options.method,
15472
+ deps,
15473
+ onComplete: (phase) => {
15474
+ finalPhase = phase;
15475
+ }
15476
+ }
15477
+ )
15478
+ );
15479
+ if (finalPhase !== null) {
15480
+ const phase = finalPhase;
15481
+ if (phase.kind === "error") {
15482
+ c.error({ code: phase.code, message: phase.message });
15483
+ }
15484
+ }
15485
+ return void 0;
15486
+ }
15487
+ let finalEvent = null;
15488
+ await runMppInspectPipeline(deps, (event) => {
15489
+ if (event.type === "errored") {
15490
+ finalEvent = { kind: "error", payload: event };
15491
+ return;
15492
+ }
15493
+ if (event.type === "challenges") {
15494
+ finalEvent = { kind: "challenges", payload: event.result };
15495
+ return;
15496
+ }
15497
+ if (event.type === "no-payment") {
15498
+ finalEvent = { kind: "no-payment", payload: event.result };
15499
+ }
15500
+ });
15501
+ if (finalEvent === null) {
15502
+ return c.error({ code: "INSPECT_FAILED", message: "Inspect pipeline produced no result." });
15503
+ }
15504
+ const { kind, payload } = finalEvent;
15505
+ if (kind === "error") {
15506
+ const err = payload;
15507
+ return c.error({ code: err.code, message: err.message });
15508
+ }
15509
+ if (kind === "challenges") {
15510
+ return sanitizeDeep(buildChallengesFrame(payload));
15511
+ }
15512
+ return sanitizeDeep(buildNoPaymentFrame(payload));
15513
+ }
15514
+ function createMppCli(inflow2, authStorage2, apiBaseUrl2) {
15515
+ const cli2 = Cli4.create("mpp", {
15516
+ description: "MPP payment commands (pay, inspect, status, cancel, decode, supported)."
15517
+ });
15518
+ cli2.command("pay", {
15519
+ description: "Pay an MPP-protected resource and return the seller response.",
15520
+ args: payArgs,
15521
+ options: payOptions,
15522
+ outputPolicy: "agent-only",
15523
+ async *run(c) {
15524
+ yield* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2);
15525
+ }
15526
+ });
15527
+ cli2.command("status", {
15528
+ description: "Poll the buyer-side state of an in-flight MPP transaction.",
15529
+ args: statusArgs,
15530
+ options: statusOptions2,
15531
+ outputPolicy: "agent-only",
15532
+ async *run(c) {
15533
+ yield* runStatusCommand(c, inflow2, authStorage2);
15534
+ }
15535
+ });
15536
+ cli2.command("cancel", {
15537
+ description: "Best-effort cancel of an MPP approval.",
15538
+ args: cancelArgs,
15539
+ outputPolicy: "agent-only",
15540
+ async run(c) {
15541
+ return runCancelCommand(c, inflow2, authStorage2);
15542
+ }
15543
+ });
15544
+ cli2.command("decode", {
15545
+ description: "Decode a raw WWW-Authenticate: Payment header, or a base64url credential / receipt.",
15546
+ args: decodeArgs,
15547
+ outputPolicy: "agent-only",
15548
+ async run(c) {
15549
+ return runDecodeCommand(c);
15550
+ }
15551
+ });
15552
+ cli2.command("supported", {
15553
+ description: "List the methods the buyer can pay with \u2014 by intent, settlement rail, and currency.",
15554
+ outputPolicy: "agent-only",
15555
+ async run(c) {
15556
+ return runSupportedCommand(c, inflow2, authStorage2);
15557
+ }
15558
+ });
15559
+ cli2.command("inspect", {
15560
+ description: "Show the seller's MPP challenge(s) for a URL. Read-only probe \u2014 no auth, no payment.",
15561
+ args: inspectArgs,
15562
+ options: inspectOptions,
15563
+ outputPolicy: "agent-only",
15564
+ async run(c) {
15565
+ return runInspectCommand(c);
15566
+ }
15567
+ });
15568
+ return cli2;
15569
+ }
15570
+
15571
+ // src/commands/user/index.tsx
15572
+ import { Cli as Cli5, Errors as Errors2 } from "incur";
15573
+ import "react";
15574
+
15575
+ // src/commands/user/get.tsx
15576
+ import { Box as Box15, Text as Text16 } from "ink";
15577
+ import Spinner12 from "ink-spinner";
15578
+ import { useCallback as useCallback7, useRef as useRef3 } from "react";
15579
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
15580
+ var UserGet = ({ userResource, onComplete }) => {
15581
+ const action = useCallback7(() => userResource.retrieve(), [userResource]);
15582
+ const { finish } = useFlowExit(onComplete);
15583
+ const lastErrorRef = useRef3("");
15584
+ const handleLinger = useCallback7(
15585
+ (result) => {
15586
+ if (result !== null) {
15587
+ finish({ kind: "success", user: result });
15588
+ } else {
15589
+ finish({
15590
+ kind: "error",
15591
+ message: lastErrorRef.current || "No result produced."
15592
+ });
15593
+ }
15594
+ },
15595
+ [finish]
15596
+ );
15597
+ const { status, data: user, error } = useFlowState(action, handleLinger);
15598
+ lastErrorRef.current = error;
15599
+ if (status === "loading") {
15600
+ return /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsxs15(Text16, { color: "cyan", children: [
15601
+ /* @__PURE__ */ jsx19(Spinner12, { type: "dots" }),
15602
+ " Loading user..."
15603
+ ] }) });
15604
+ }
15605
+ if (status === "error") {
15606
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
15607
+ /* @__PURE__ */ jsx19(Text16, { color: "red", children: "\u2717 Failed to retrieve user" }),
15608
+ /* @__PURE__ */ jsx19(Text16, { color: "red", children: error })
15609
+ ] });
15610
+ }
15611
+ if (user === null) return null;
15612
+ const rows = buildProfileRows(user);
15613
+ return /* @__PURE__ */ jsx19(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: rows.map((row) => /* @__PURE__ */ jsxs15(Text16, { children: [
15614
+ `${row.label}: `,
15615
+ row.value !== null ? /* @__PURE__ */ jsx19(Text16, { bold: true, children: row.value }) : /* @__PURE__ */ jsx19(Text16, { dimColor: true, children: "\u2014" })
15616
+ ] }, row.label)) });
15617
+ };
15618
+
15619
+ // src/commands/user/schema.ts
15620
+ import { z as z5 } from "incur";
15621
+ var getOptions = z5.object({});
15622
+
15623
+ // src/commands/user/index.tsx
15624
+ import { jsx as jsx20 } from "react/jsx-runtime";
15625
+ var TTY_NO_RESULT_MESSAGE = "inflow user get exited without producing a result.";
15626
+ async function runUserGet2(c, deps) {
15627
+ assertSessionGuard(c, deps.authStorage, deps.inflow);
15628
+ if (!c.agent && !c.formatExplicit) {
15629
+ let captured = null;
15630
+ const outcome = await renderInkUntilExit(
15631
+ /* @__PURE__ */ jsx20(
15632
+ UserGet,
15633
+ {
15634
+ userResource: deps.user,
15635
+ onComplete: (value) => {
15636
+ captured = value;
15637
+ }
15638
+ }
15639
+ ),
15640
+ () => captured
15641
+ );
15642
+ if (outcome === null) {
15643
+ throw new Errors2.IncurError({
15644
+ code: "USER_GET_FAILED",
15645
+ message: TTY_NO_RESULT_MESSAGE
15646
+ });
15647
+ }
15648
+ if (outcome.kind === "error") {
15649
+ throw new Errors2.IncurError({
15650
+ code: "USER_GET_FAILED",
15651
+ message: outcome.message
15652
+ });
15653
+ }
15654
+ return projectUserPayload(outcome.user);
15655
+ }
15656
+ return deps.user.get();
15657
+ }
15658
+ function createUserCli(user, authStorage2, inflow2) {
15659
+ const cli2 = Cli5.create("user", { description: "User profile commands" });
15660
+ cli2.command("get", {
15661
+ description: "Retrieve the current authenticated user",
15662
+ options: getOptions,
15663
+ outputPolicy: "agent-only",
15664
+ async run(c) {
15665
+ return runUserGet2(c, { user, authStorage: authStorage2, inflow: inflow2 });
15666
+ }
15667
+ });
15668
+ return cli2;
15669
+ }
15670
+
15671
+ // src/commands/x402/index.tsx
15672
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync3 } from "fs";
15673
+ import { resolve as resolvePath3 } from "path";
15674
+ import { Cli as Cli6 } from "incur";
15675
+
15676
+ // src/commands/x402/cancel.tsx
15677
+ import { Box as Box16, Text as Text17 } from "ink";
15678
+ import Spinner13 from "ink-spinner";
15679
+ import { useCallback as useCallback8 } from "react";
15680
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
15681
+ var CancelView2 = ({ approvalId, cancel, onComplete }) => {
15682
+ const action = useCallback8(() => cancel(), [cancel]);
15683
+ const { finish } = useFlowExit(onComplete);
15684
+ const { status } = useFlowState(action, finish);
15685
+ if (status === "loading") {
15686
+ return /* @__PURE__ */ jsx21(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
15687
+ /* @__PURE__ */ jsx21(Spinner13, { type: "dots" }),
15688
+ " Cancelling approval ",
15689
+ approvalId,
15690
+ "..."
15691
+ ] }) });
15692
+ }
15693
+ return /* @__PURE__ */ jsx21(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "green", children: [
15694
+ "\u2713 Cancelled approval ",
15695
+ approvalId,
15696
+ " (best-effort)"
15697
+ ] }) });
15698
+ };
15699
+
15700
+ // src/commands/x402/decode.tsx
15701
+ import { fromFoundationRequirements as fromFoundationRequirements2 } from "@inflowpayai/x402-buyer";
15702
+ import { Box as Box17, Text as Text18 } from "ink";
15703
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
15704
+ var DecodeView2 = ({ decoded }) => {
15705
+ const summary = summarizeAccepts(fromFoundationRequirements2(decoded.accepts));
15706
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", paddingY: 1, children: [
15707
+ /* @__PURE__ */ jsx22(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsx22(Text18, { bold: true, children: "Decoded PAYMENT-REQUIRED" }) }),
15708
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
15709
+ /* @__PURE__ */ jsxs17(Text18, { children: [
15710
+ "x402Version: ",
15711
+ /* @__PURE__ */ jsx22(Text18, { color: "cyan", children: String(decoded.x402Version) })
15712
+ ] }),
15713
+ /* @__PURE__ */ jsxs17(Text18, { children: [
15714
+ "resource: ",
15715
+ /* @__PURE__ */ jsx22(Text18, { color: "cyan", children: decoded.resource.url })
15716
+ ] }),
15717
+ /* @__PURE__ */ jsx22(Text18, { children: `accepts (${String(summary.length)}):` }),
15718
+ summary.map((entry, idx) => /* @__PURE__ */ jsxs17(Text18, { children: [
15719
+ " ",
15720
+ /* @__PURE__ */ jsx22(Text18, { color: "yellow", children: entry.scheme }),
15721
+ " / ",
15722
+ /* @__PURE__ */ jsx22(Text18, { color: "yellow", children: entry.network }),
15723
+ entry.amount !== void 0 ? ` \xB7 amount ${entry.amount}` : "",
15724
+ entry.asset !== void 0 ? ` \xB7 ${entry.asset}` : ""
15725
+ ] }, `${entry.scheme}-${entry.network}-${String(idx)}`)),
15726
+ decoded.extensions !== void 0 ? /* @__PURE__ */ jsx22(Text18, { dimColor: true, children: `extensions: ${Object.keys(decoded.extensions).join(", ")}` }) : null
15727
+ ] })
15728
+ ] });
15729
+ };
15730
+
15731
+ // src/commands/x402/inspect.tsx
15732
+ import { Box as Box18, Text as Text19 } from "ink";
15733
+ import Spinner14 from "ink-spinner";
15734
+ import { useEffect as useEffect9, useReducer as useReducer7 } from "react";
15735
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
15736
+ function summarizeExtra(extra) {
15737
+ if (extra === void 0) return "\u2014";
15738
+ const keys = Object.keys(extra);
15739
+ if (keys.length === 0) return "\u2014";
15740
+ return [...keys].sort((a, b) => a.localeCompare(b)).join(", ");
15741
+ }
15742
+ function formatTimeout(seconds) {
15743
+ return `${String(seconds)}s`;
15744
+ }
15745
+ function formatAsset(asset) {
15746
+ return asset === "" ? "\u2014" : asset;
15747
+ }
15748
+ var COLUMNS5 = [
15749
+ { header: "Scheme", cell: (r) => r.scheme },
15750
+ { header: "Network", cell: (r) => r.network },
15751
+ { header: "Amount", cell: (r) => r.amount },
15752
+ { header: "Asset", cell: (r) => formatAsset(r.asset) },
15753
+ { header: "Pay To", cell: (r) => r.payTo },
15754
+ { header: "Timeout", cell: (r) => formatTimeout(r.maxTimeoutSeconds) },
15755
+ { header: "Extra", cell: (r) => summarizeExtra(r.extra) }
15756
+ ];
15757
+ var InspectView2 = ({ url, method, deps, onComplete }) => {
15758
+ const initial = { kind: "probing" };
15759
+ const [phase, dispatch] = useReducer7(reduceX402Inspect, initial);
15760
+ const { finish } = useFlowExit(onComplete);
15761
+ useEffect9(() => {
15762
+ let cancelled = false;
15763
+ void runInspectPipeline(deps, (event) => {
15764
+ if (!cancelled) dispatch(event);
15765
+ });
15766
+ return () => {
15767
+ cancelled = true;
15768
+ };
15769
+ }, [deps]);
15770
+ useEffect9(() => {
15771
+ if (phase.kind === "accepts" || phase.kind === "no-payment" || phase.kind === "error") {
15772
+ finish(phase);
15773
+ }
15774
+ }, [phase, finish]);
15775
+ if (phase.kind === "probing") {
15776
+ return /* @__PURE__ */ jsx23(Box18, { children: /* @__PURE__ */ jsxs18(Text19, { color: "cyan", children: [
15777
+ /* @__PURE__ */ jsx23(Spinner14, { type: "dots" }),
15778
+ " Probing ",
15779
+ method,
15780
+ " ",
15781
+ url,
15782
+ "..."
15783
+ ] }) });
15784
+ }
15785
+ if (phase.kind === "no-payment") {
15786
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15787
+ /* @__PURE__ */ jsx23(Text19, { color: "green", children: "\u2713 Seller accepted without payment" }),
15788
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: "Use `x402 pay` to fetch the body." })
15789
+ ] });
15790
+ }
15791
+ if (phase.kind === "error") {
15792
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15793
+ /* @__PURE__ */ jsxs18(Text19, { color: "red", children: [
15794
+ "\u2717 ",
15795
+ phase.code
15796
+ ] }),
15797
+ /* @__PURE__ */ jsx23(Text19, { color: "red", children: phase.message })
15798
+ ] });
15799
+ }
15800
+ const { result } = phase;
15801
+ const acceptsCount = result.accepts.length;
15802
+ const extensionsLine = result.extensions !== void 0 ? Object.keys(result.extensions).join(", ") : null;
15803
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
15804
+ /* @__PURE__ */ jsxs18(Text19, { children: [
15805
+ /* @__PURE__ */ jsx23(Text19, { bold: true, children: "PAYMENT-REQUIRED" }),
15806
+ " for ",
15807
+ /* @__PURE__ */ jsx23(Text19, { color: "cyan", children: result.resource }),
15808
+ " \xB7 ",
15809
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `x402Version ${String(result.x402Version)}` }),
15810
+ " \xB7 ",
15811
+ /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `${String(acceptsCount)} accept${acceptsCount === 1 ? "" : "s"}` })
15812
+ ] }),
15813
+ extensionsLine !== null ? /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: `extensions: ${extensionsLine}` }) : null,
15814
+ /* @__PURE__ */ jsx23(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx23(Table, { columns: COLUMNS5, rows: result.accepts }) }),
15815
+ /* @__PURE__ */ jsx23(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: "Use --format json to inspect extras values." }) })
15816
+ ] });
15817
+ };
15818
+ function buildAcceptsFrame(result) {
15819
+ const frame = {
15820
+ outcome: "accepts",
15821
+ url: result.url,
15822
+ method: result.method,
15823
+ resource: result.resource,
15824
+ x402_version: result.x402Version,
15825
+ accepts: result.accepts.map((entry) => {
15826
+ const row = {
15827
+ scheme: entry.scheme,
15828
+ network: entry.network,
15829
+ amount: entry.amount,
15830
+ asset: entry.asset,
15831
+ pay_to: entry.payTo,
15832
+ max_timeout_seconds: entry.maxTimeoutSeconds
15833
+ };
15834
+ if (entry.extra !== void 0) row.extra = entry.extra;
15835
+ return row;
15836
+ })
15837
+ };
15838
+ if (result.extensions !== void 0) frame.extensions = result.extensions;
15839
+ return frame;
15840
+ }
15841
+ function buildNoPaymentFrame2(result) {
15842
+ const frame = {
15843
+ outcome: "no-payment-required",
15844
+ url: result.url,
15845
+ method: result.method,
15846
+ status: result.status,
15847
+ body_size_bytes: result.bodySizeBytes
15848
+ };
15849
+ if (result.contentType !== void 0) frame.content_type = result.contentType;
15850
+ return frame;
15851
+ }
15852
+
15853
+ // src/commands/x402/pay.tsx
15854
+ import { Box as Box19, Text as Text20, useInput as useInput4 } from "ink";
15855
+ import Spinner15 from "ink-spinner";
15856
+ import { useEffect as useEffect10, useReducer as useReducer8, useState as useState4 } from "react";
15857
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
15858
+ var PayView2 = ({ url, method, deps, onComplete, onCancel }) => {
15859
+ const initial = { kind: "probing" };
15860
+ const [phase, dispatch] = useReducer8(reducePay, initial);
15861
+ const [cancelling, setCancelling] = useState4(false);
15862
+ const { finish, cancelThenFinish } = useFlowExit(onComplete);
15863
+ useInput4(
15864
+ (_input, key) => {
15865
+ if (phase.kind !== "awaiting-approval") return;
15866
+ if (key.return) {
15867
+ openUrl(phase.approvalUrl);
15868
+ return;
15869
+ }
15870
+ if (key.escape) {
15871
+ const { approvalId } = phase.prepared;
15872
+ setCancelling(true);
15873
+ cancelThenFinish(() => onCancel?.(approvalId), {
15874
+ kind: "error",
15875
+ code: "APPROVAL_CANCELLED",
15876
+ message: `Approval ${approvalId} cancelled.`
15877
+ });
15878
+ }
15879
+ },
15880
+ { isActive: phase.kind === "awaiting-approval" && !cancelling }
15881
+ );
15882
+ useEffect10(() => {
15883
+ const controller = new AbortController();
15884
+ let cancelled = false;
15885
+ const runDeps = { ...deps, signOptions: { ...deps.signOptions, signal: controller.signal } };
15886
+ void runPayPipeline(runDeps, (event) => {
15887
+ if (!cancelled) dispatch(event);
15888
+ });
15889
+ return () => {
15890
+ cancelled = true;
15891
+ controller.abort();
15892
+ };
15893
+ }, [deps]);
15894
+ useEffect10(() => {
15895
+ if (phase.kind === "success" || phase.kind === "replay-rejected" || phase.kind === "no-payment-final" || phase.kind === "error") {
15896
+ finish(phase);
14296
15897
  }
14297
- }, [phase, onComplete, exit]);
15898
+ }, [phase, finish]);
15899
+ if (cancelling) {
15900
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "yellow", children: [
15901
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
15902
+ " Cancelling approval..."
15903
+ ] }) });
15904
+ }
14298
15905
  if (phase.kind === "probing") {
14299
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14300
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15906
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15907
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14301
15908
  " Probing ",
14302
15909
  method,
14303
15910
  " ",
@@ -14306,23 +15913,23 @@ var PayView = ({ url, method, deps, onComplete }) => {
14306
15913
  ] }) });
14307
15914
  }
14308
15915
  if (phase.kind === "no-payment") {
14309
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14310
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15916
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15917
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14311
15918
  " Seller accepted without payment (status ",
14312
15919
  String(phase.probe.status),
14313
15920
  "); finalising..."
14314
15921
  ] }) });
14315
15922
  }
14316
15923
  if (phase.kind === "matching") {
14317
- return /* @__PURE__ */ jsx17(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14318
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15924
+ return /* @__PURE__ */ jsx24(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15925
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14319
15926
  " Decoding seller requirements..."
14320
15927
  ] }) });
14321
15928
  }
14322
15929
  if (phase.kind === "preparing") {
14323
15930
  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" }),
15931
+ return /* @__PURE__ */ jsx24(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15932
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14326
15933
  " Preparing payment (",
14327
15934
  summary[0]?.scheme ?? phase.requirement.scheme,
14328
15935
  " /",
@@ -14332,188 +15939,167 @@ var PayView = ({ url, method, deps, onComplete }) => {
14332
15939
  ] }) });
14333
15940
  }
14334
15941
  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: [
15942
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", paddingY: 1, children: [
15943
+ /* @__PURE__ */ jsx24(Box19, { marginBottom: 1, children: /* @__PURE__ */ jsx24(Text20, { bold: true, children: "Approval required" }) }),
15944
+ /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [
15945
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14339
15946
  "Open: ",
14340
- /* @__PURE__ */ jsx17(Text14, { bold: true, color: "cyan", children: phase.approvalUrl })
15947
+ /* @__PURE__ */ jsx24(Text20, { bold: true, color: "cyan", children: phase.approvalUrl })
14341
15948
  ] }),
14342
- /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "Press Enter to open in browser." })
15949
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "Press Enter to open in browser." }),
15950
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "Press Escape to cancel." })
14343
15951
  ] }),
14344
- /* @__PURE__ */ jsx17(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14345
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15952
+ /* @__PURE__ */ jsx24(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15953
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14346
15954
  " Waiting for approval..."
14347
15955
  ] }) })
14348
15956
  ] });
14349
15957
  }
14350
15958
  if (phase.kind === "replaying") {
14351
- return /* @__PURE__ */ jsx17(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
14352
- /* @__PURE__ */ jsx17(Spinner10, { type: "dots" }),
15959
+ return /* @__PURE__ */ jsx24(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
15960
+ /* @__PURE__ */ jsx24(Spinner15, { type: "dots" }),
14353
15961
  " Replaying request with PAYMENT-SIGNATURE..."
14354
15962
  ] }) });
14355
15963
  }
14356
15964
  if (phase.kind === "success") {
14357
15965
  const { result } = phase;
14358
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14359
- /* @__PURE__ */ jsxs13(Text14, { color: "green", children: [
15966
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
15967
+ /* @__PURE__ */ jsxs19(Text20, { color: "green", children: [
14360
15968
  "\u2713 Paid ",
14361
15969
  result.scheme,
14362
15970
  " / ",
14363
15971
  result.network
14364
15972
  ] }),
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: [
15973
+ /* @__PURE__ */ jsx24(Text20, { children: `transaction: ${result.transactionId}` }),
15974
+ result.settled?.network !== void 0 ? /* @__PURE__ */ jsx24(Text20, { children: `settled via: ${result.settled.network}${result.settled.transaction !== void 0 ? ` (${result.settled.transaction})` : ""}` }) : null,
15975
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14370
15976
  "Saved to: ",
14371
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
15977
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14372
15978
  ] }) : 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 })
15979
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
15980
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
15981
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14376
15982
  ] }) : null
14377
15983
  ] });
14378
15984
  }
14379
15985
  if (phase.kind === "replay-rejected") {
14380
15986
  const { result } = phase;
14381
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14382
- /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
15987
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
15988
+ /* @__PURE__ */ jsxs19(Text20, { color: "red", children: [
14383
15989
  "\u2717 Payment not accepted (",
14384
15990
  result.scheme,
14385
15991
  " / ",
14386
15992
  result.network,
14387
15993
  ")"
14388
15994
  ] }),
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: [
15995
+ /* @__PURE__ */ jsx24(Text20, { children: `transaction: ${result.transactionId}` }),
15996
+ /* @__PURE__ */ jsx24(Text20, { children: `approval: ${result.approvalId}` }),
15997
+ /* @__PURE__ */ jsx24(Text20, { children: `approval url: ${result.approvalUrl}` }),
15998
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14395
15999
  "Saved to: ",
14396
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
16000
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14397
16001
  ] }) : 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 })
16002
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
16003
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
16004
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14401
16005
  ] }) : null
14402
16006
  ] });
14403
16007
  }
14404
16008
  if (phase.kind === "no-payment-final") {
14405
16009
  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: [
16010
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
16011
+ /* @__PURE__ */ jsx24(Text20, { color: "green", children: "\u2713 Seller accepted without payment" }),
16012
+ result.outputSavedTo !== void 0 ? /* @__PURE__ */ jsxs19(Text20, { children: [
14411
16013
  "Saved to: ",
14412
- /* @__PURE__ */ jsx17(Text14, { bold: true, children: result.outputSavedTo })
16014
+ /* @__PURE__ */ jsx24(Text20, { bold: true, children: result.outputSavedTo })
14413
16015
  ] }) : 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 })
16016
+ result.body !== void 0 ? /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
16017
+ /* @__PURE__ */ jsx24(Text20, { dimColor: true, children: "response body:" }),
16018
+ /* @__PURE__ */ jsx24(Text20, { children: result.body })
14417
16019
  ] }) : null
14418
16020
  ] });
14419
16021
  }
14420
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
14421
- /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
16022
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
16023
+ /* @__PURE__ */ jsxs19(Text20, { color: "red", children: [
14422
16024
  "\u2717 ",
14423
16025
  phase.code
14424
16026
  ] }),
14425
- /* @__PURE__ */ jsx17(Text14, { color: "red", children: phase.message })
16027
+ /* @__PURE__ */ jsx24(Text20, { color: "red", children: phase.message })
14426
16028
  ] });
14427
16029
  };
14428
16030
 
14429
16031
  // 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.")
16032
+ import { z as z6 } from "incur";
16033
+ var payArgs2 = z6.object({
16034
+ url: z6.string().describe("The x402-protected resource URL to pay for.")
14433
16035
  });
14434
- var payOptions = z5.object({
14435
- method: z5.string().default("GET").describe("HTTP method for the seller request."),
14436
- data: z5.string().optional().describe(
16036
+ var payOptions2 = z6.object({
16037
+ scheme: z6.string().optional().describe('Only consider `accepts[]` entries with this scheme (e.g. "exact", "balance").'),
16038
+ network: z6.string().optional().describe('Only consider entries on this network (e.g. "eip155:84532").'),
16039
+ asset: z6.string().optional().describe("Only consider entries with this on-chain asset id (ERC-20 address or SVM mint)."),
16040
+ assetName: z6.string().optional().describe('Only consider entries whose `extra.assetName` symbol matches (e.g. "USDC").'),
16041
+ method: z6.string().default("GET").describe("HTTP method for the seller request."),
16042
+ data: z6.string().optional().describe(
14437
16043
  "Request body. JSON or raw text. Content-Type defaults to application/json when --data is set unless a --header overrides it."
14438
16044
  ),
14439
- header: z5.array(z5.string()).default([]).describe('Repeatable. "Name: Value" format.'),
14440
- interval: z5.coerce.number().default(0).describe(
16045
+ header: z6.array(z6.string()).default([]).describe('Repeatable. "Name: Value" format.'),
16046
+ interval: z6.coerce.number().default(0).describe(
14441
16047
  "Inline poll cadence in seconds while awaiting approval. 0 returns the approval URL and a follow-up command hint without blocking."
14442
16048
  ),
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(
16049
+ maxAttempts: z6.coerce.number().default(0).describe("Hard cap on poll attempts when --interval > 0. 0 means unlimited."),
16050
+ timeout: z6.coerce.number().default(900).describe("Polling deadline in seconds. Default 900s (matches x402-buyer)."),
16051
+ paymentId: z6.string().min(16).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional().describe(
14446
16052
  "Caller-supplied payment identifier. 16-128 chars, ^[a-zA-Z0-9_-]+$. Forwarded to the server as remotePaymentId."
14447
16053
  ),
14448
- showBody: z5.boolean().default(true).describe(
16054
+ showBody: z6.boolean().default(true).describe(
14449
16055
  "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
16056
  ),
14451
- outputFile: z5.string().optional().describe(
16057
+ outputFile: z6.string().optional().describe(
14452
16058
  "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
16059
  ),
14454
- payloadFile: z5.string().optional().describe(
16060
+ payloadFile: z6.string().optional().describe(
14455
16061
  "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
16062
  )
14469
16063
  });
14470
- var statusArgs = z5.object({
14471
- transactionId: z5.string().describe("The transaction id returned by `x402 pay`.")
16064
+ var statusArgs2 = z6.object({
16065
+ transactionId: z6.string().describe("The transaction id returned by `x402 pay`.")
14472
16066
  });
14473
- var statusOptions2 = z5.object({
14474
- interval: z5.coerce.number().default(0).describe(
16067
+ var statusOptions3 = z6.object({
16068
+ interval: z6.coerce.number().default(0).describe(
14475
16069
  "Poll cadence in seconds. 0 returns the current snapshot; positive values yield on every change until signed or terminal."
14476
16070
  ),
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(
16071
+ maxAttempts: z6.coerce.number().default(0).describe("Hard cap on poll attempts. 0 means unlimited."),
16072
+ timeout: z6.coerce.number().default(900).describe("Polling deadline in seconds."),
16073
+ payloadFile: z6.string().optional().describe(
14480
16074
  "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
16075
  )
14482
16076
  });
14483
- var cancelArgs = z5.object({
14484
- approvalId: z5.string().describe("The approval id returned by `x402 pay`.")
16077
+ var cancelArgs2 = z6.object({
16078
+ approvalId: z6.string().describe("The approval id returned by `x402 pay`.")
14485
16079
  });
14486
- var decodeArgs = z5.object({
14487
- header: z5.string().describe("Raw PAYMENT-REQUIRED header value (base64).")
16080
+ var decodeArgs2 = z6.object({
16081
+ header: z6.string().describe("Raw PAYMENT-REQUIRED header value (base64).")
14488
16082
  });
14489
- var inspectArgs = z5.object({
14490
- url: z5.string().describe("The x402-protected resource URL to probe. No payment is made.")
16083
+ var inspectArgs2 = z6.object({
16084
+ url: z6.string().describe("The x402-protected resource URL to probe. No payment is made.")
14491
16085
  });
14492
- var inspectOptions = z5.object({
14493
- method: z5.string().default("GET").describe("HTTP method for the probe request."),
14494
- data: z5.string().optional().describe(
16086
+ var inspectOptions2 = z6.object({
16087
+ scheme: z6.string().optional().describe('Only show `accepts[]` entries with this scheme (e.g. "exact", "balance").'),
16088
+ network: z6.string().optional().describe('Only show entries on this network (e.g. "eip155:84532").'),
16089
+ asset: z6.string().optional().describe("Only show entries with this on-chain asset id (ERC-20 address or SVM mint)."),
16090
+ assetName: z6.string().optional().describe('Only show entries whose `extra.assetName` symbol matches (e.g. "USDC").'),
16091
+ method: z6.string().default("GET").describe("HTTP method for the probe request."),
16092
+ data: z6.string().optional().describe(
14495
16093
  "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
16094
  ),
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
- )
16095
+ header: z6.array(z6.string()).default([]).describe('Repeatable. "Name: Value" format.')
14510
16096
  });
14511
16097
 
14512
16098
  // 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";
16099
+ import { Box as Box20, Text as Text21 } from "ink";
16100
+ import Spinner16 from "ink-spinner";
16101
+ import { useEffect as useEffect11, useReducer as useReducer9 } from "react";
16102
+ import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
14517
16103
  var X402StatusView = ({
14518
16104
  transactionId,
14519
16105
  fetchOnce,
@@ -14522,10 +16108,10 @@ var X402StatusView = ({
14522
16108
  timeout,
14523
16109
  onComplete
14524
16110
  }) => {
14525
- const { exit } = useApp12();
14526
16111
  const initial = { kind: "polling" };
14527
- const [phase, dispatch] = useReducer6(reduceX402Status, initial);
14528
- useEffect8(() => {
16112
+ const [phase, dispatch] = useReducer9(reduceX402Status, initial);
16113
+ const { finish } = useFlowExit(onComplete);
16114
+ useEffect11(() => {
14529
16115
  const run = runX402Status({ fetchOnce, interval, maxAttempts, timeout });
14530
16116
  let cancelled = false;
14531
16117
  void (async () => {
@@ -14538,16 +16124,15 @@ var X402StatusView = ({
14538
16124
  cancelled = true;
14539
16125
  };
14540
16126
  }, [fetchOnce, interval, maxAttempts, timeout]);
14541
- useEffect8(() => {
16127
+ useEffect11(() => {
14542
16128
  if (phase.kind === "signed" || phase.kind === "failed" || phase.kind === "timeout" || phase.kind === "error") {
14543
- onComplete(phase);
14544
- exit();
16129
+ finish(phase);
14545
16130
  }
14546
- }, [phase, onComplete, exit]);
16131
+ }, [phase, finish]);
14547
16132
  if (phase.kind === "polling") {
14548
16133
  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" }),
16134
+ return /* @__PURE__ */ jsx25(Box20, { flexDirection: "column", children: /* @__PURE__ */ jsxs20(Text21, { color: "cyan", children: [
16135
+ /* @__PURE__ */ jsx25(Spinner16, { type: "dots" }),
14551
16136
  " Polling transaction ",
14552
16137
  transactionId,
14553
16138
  " (status: ",
@@ -14558,80 +16143,73 @@ var X402StatusView = ({
14558
16143
  if (phase.kind === "signed") {
14559
16144
  const encoded = phase.response.encodedPayload ?? "";
14560
16145
  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}` })
16146
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16147
+ /* @__PURE__ */ jsx25(Text21, { color: "green", children: "\u2713 Signed" }),
16148
+ /* @__PURE__ */ jsx25(Text21, { children: `status: ${phase.response.status}` }),
16149
+ /* @__PURE__ */ jsx25(Text21, { children: `encodedPayload: ${preview}` })
14565
16150
  ] });
14566
16151
  }
14567
16152
  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}` })
16153
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16154
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: "\u2717 Approval did not settle" }),
16155
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: `status: ${phase.response.status}` })
14571
16156
  ] });
14572
16157
  }
14573
16158
  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
16159
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16160
+ /* @__PURE__ */ jsx25(Text21, { color: "yellow", children: "Polling timed out before the transaction reached a signed state." }),
16161
+ phase.response !== void 0 ? /* @__PURE__ */ jsx25(Text21, { children: `last status: ${phase.response.status}` }) : null
14577
16162
  ] });
14578
16163
  }
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 })
16164
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
16165
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: "\u2717 Polling failed" }),
16166
+ /* @__PURE__ */ jsx25(Text21, { color: "red", children: phase.message })
14582
16167
  ] });
14583
16168
  };
14584
16169
 
14585
16170
  // 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 = [
16171
+ import { Box as Box21, Text as Text22 } from "ink";
16172
+ import Spinner17 from "ink-spinner";
16173
+ import { useCallback as useCallback9 } from "react";
16174
+ import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
16175
+ var COLUMNS6 = [
14591
16176
  { header: "Scheme", cell: (k) => k.scheme },
14592
16177
  { header: "Network", cell: (k) => k.network }
14593
16178
  ];
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);
16179
+ var SupportedView2 = ({ load, onComplete }) => {
16180
+ const action = useCallback9(() => load(), [load]);
16181
+ const { finish } = useFlowExit(onComplete);
16182
+ const { status, data, error } = useFlowState(action, finish);
14605
16183
  if (status === "loading") {
14606
- return /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsxs15(Text16, { color: "cyan", children: [
14607
- /* @__PURE__ */ jsx19(Spinner12, { type: "dots" }),
16184
+ return /* @__PURE__ */ jsx26(Box21, { children: /* @__PURE__ */ jsxs21(Text22, { color: "cyan", children: [
16185
+ /* @__PURE__ */ jsx26(Spinner17, { type: "dots" }),
14608
16186
  " Loading supported schemes..."
14609
16187
  ] }) });
14610
16188
  }
14611
16189
  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 })
16190
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
16191
+ /* @__PURE__ */ jsx26(Text22, { color: "red", children: "Failed to load supported schemes" }),
16192
+ /* @__PURE__ */ jsx26(Text22, { color: "red", children: error })
14615
16193
  ] });
14616
16194
  }
14617
16195
  const kinds = data?.kinds ?? [];
14618
16196
  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." }) });
16197
+ return /* @__PURE__ */ jsx26(Box21, { flexDirection: "column", children: /* @__PURE__ */ jsx26(Text22, { children: "No supported (scheme, network) pairs returned for this account." }) });
14620
16198
  }
14621
- return /* @__PURE__ */ jsx19(Table, { columns: COLUMNS4, rows: kinds });
16199
+ return /* @__PURE__ */ jsx26(Table, { columns: COLUMNS6, rows: kinds });
14622
16200
  };
14623
16201
 
14624
16202
  // src/commands/x402/index.tsx
14625
- import { jsx as jsx20 } from "react/jsx-runtime";
16203
+ import { jsx as jsx27 } from "react/jsx-runtime";
14626
16204
  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.";
16205
+ 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
16206
  function buildSignOptions(options) {
14629
16207
  const out = { timeoutMs: options.timeout * 1e3 };
14630
16208
  if (options.interval > 0) out.pollIntervalMs = options.interval * 1e3;
14631
16209
  if (options.paymentId !== void 0) out.paymentId = options.paymentId;
14632
16210
  return out;
14633
16211
  }
14634
- function parseHeaderFlagsOrFail(c, flags) {
16212
+ function parseHeaderFlagsOrFail2(c, flags) {
14635
16213
  try {
14636
16214
  return parseHeaderFlags(flags);
14637
16215
  } catch (err) {
@@ -14643,16 +16221,16 @@ function parseHeaderFlagsOrFail(c, flags) {
14643
16221
  }
14644
16222
  function decoratePayloadField(frame, encoded, payloadFile) {
14645
16223
  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);
16224
+ const absolute = resolvePath3(payloadFile);
16225
+ writeFileSync3(absolute, Buffer.from(encoded, "utf-8"), { mode: 384 });
16226
+ chmodSync2(absolute, 384);
14649
16227
  frame.payload_saved_to = absolute;
14650
16228
  return;
14651
16229
  }
14652
16230
  frame.encoded_payload = encoded;
14653
16231
  }
14654
- function buildPayPipelineInput(c) {
14655
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16232
+ function buildPayPipelineInput2(c) {
16233
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14656
16234
  const probeOptions = {
14657
16235
  method: c.options.method,
14658
16236
  headers: probeHeaders,
@@ -14670,19 +16248,19 @@ function buildPayPipelineInput(c) {
14670
16248
  ...c.options.assetName !== void 0 ? { assetNameFilter: c.options.assetName } : {}
14671
16249
  };
14672
16250
  }
14673
- function attachBodyFields(frame, result) {
16251
+ function attachBodyFields2(frame, result) {
14674
16252
  frame.body_size_bytes = result.bodySizeBytes;
14675
16253
  if (result.body !== void 0) frame.body = result.body;
14676
16254
  if (result.bodyBase64 !== void 0) frame.body_base64 = result.bodyBase64;
14677
16255
  if (result.outputSavedTo !== void 0) frame.output_saved_to = result.outputSavedTo;
14678
16256
  }
14679
- function noPaymentFrameFromResult(result) {
16257
+ function noPaymentFrameFromResult2(result) {
14680
16258
  const frame = {
14681
16259
  outcome: "no-payment-required",
14682
16260
  status: result.status
14683
16261
  };
14684
16262
  if (result.contentType !== void 0) frame.content_type = result.contentType;
14685
- attachBodyFields(frame, result);
16263
+ attachBodyFields2(frame, result);
14686
16264
  return frame;
14687
16265
  }
14688
16266
  function initialPayFrame(event, interval, maxAttempts) {
@@ -14693,7 +16271,7 @@ function initialPayFrame(event, interval, maxAttempts) {
14693
16271
  resource: event.decoded.resource.url,
14694
16272
  scheme: event.requirement.scheme,
14695
16273
  network: event.requirement.network,
14696
- instruction: interval > 0 ? POLLING_INSTRUCTION : POST_PAY_INSTRUCTION
16274
+ instruction: interval > 0 ? POLLING_INSTRUCTION2 : POST_PAY_INSTRUCTION
14697
16275
  };
14698
16276
  if (event.requirement.amount !== "") frame.amount = event.requirement.amount;
14699
16277
  if (event.requirement.asset !== "") frame.asset = event.requirement.asset;
@@ -14707,7 +16285,7 @@ function initialPayFrame(event, interval, maxAttempts) {
14707
16285
  }
14708
16286
  return frame;
14709
16287
  }
14710
- function paidFrameFromResult(result, payloadFile) {
16288
+ function paidFrameFromResult2(result, payloadFile) {
14711
16289
  const frame = {
14712
16290
  outcome: "paid",
14713
16291
  transaction_id: result.transactionId,
@@ -14719,10 +16297,10 @@ function paidFrameFromResult(result, payloadFile) {
14719
16297
  decoratePayloadField(frame, result.encodedPayload, payloadFile);
14720
16298
  if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
14721
16299
  if (result.settled !== void 0) frame.settled = result.settled;
14722
- attachBodyFields(frame, result);
16300
+ attachBodyFields2(frame, result);
14723
16301
  return frame;
14724
16302
  }
14725
- function rejectedFrameFromResult(result) {
16303
+ function rejectedFrameFromResult2(result) {
14726
16304
  const frame = {
14727
16305
  outcome: "replay-rejected",
14728
16306
  transaction_id: result.transactionId,
@@ -14733,14 +16311,14 @@ function rejectedFrameFromResult(result) {
14733
16311
  response_status: result.responseStatus
14734
16312
  };
14735
16313
  if (result.responseContentType !== void 0) frame.response_content_type = result.responseContentType;
14736
- attachBodyFields(frame, result);
16314
+ attachBodyFields2(frame, result);
14737
16315
  return frame;
14738
16316
  }
14739
- async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
16317
+ async function* runPayCommand2(c, inflow2, authStorage2, apiBaseUrl2) {
14740
16318
  assertSessionGuard(c, authStorage2, inflow2);
14741
16319
  if (!c.agent && !c.formatExplicit) {
14742
16320
  const client = await inflow2.x402.client();
14743
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16321
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14744
16322
  const probeOptions = {
14745
16323
  method: c.options.method,
14746
16324
  headers: probeHeaders,
@@ -14748,8 +16326,8 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14748
16326
  };
14749
16327
  let finalPhase = null;
14750
16328
  await renderInkUntilExit(
14751
- /* @__PURE__ */ jsx20(
14752
- PayView,
16329
+ /* @__PURE__ */ jsx27(
16330
+ PayView2,
14753
16331
  {
14754
16332
  url: c.args.url,
14755
16333
  method: c.options.method,
@@ -14768,7 +16346,8 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14768
16346
  },
14769
16347
  onComplete: (phase) => {
14770
16348
  finalPhase = phase;
14771
- }
16349
+ },
16350
+ onCancel: (approvalId) => inflow2.x402.cancel({ approvalId })
14772
16351
  }
14773
16352
  )
14774
16353
  );
@@ -14787,12 +16366,12 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14787
16366
  return;
14788
16367
  }
14789
16368
  const run = inflow2.x402.pay({
14790
- ...buildPayPipelineInput(c),
16369
+ ...buildPayPipelineInput2(c),
14791
16370
  awaitPayment: c.options.interval > 0
14792
16371
  });
14793
16372
  for await (const event of run.events) {
14794
16373
  if (event.type === "short-circuited") {
14795
- yield sanitizeDeep(noPaymentFrameFromResult(event.result));
16374
+ yield sanitizeDeep(noPaymentFrameFromResult2(event.result));
14796
16375
  return;
14797
16376
  }
14798
16377
  if (event.type === "prepared") {
@@ -14800,11 +16379,11 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14800
16379
  continue;
14801
16380
  }
14802
16381
  if (event.type === "replayed") {
14803
- yield sanitizeDeep(paidFrameFromResult(event.result, c.options.payloadFile));
16382
+ yield sanitizeDeep(paidFrameFromResult2(event.result, c.options.payloadFile));
14804
16383
  return;
14805
16384
  }
14806
16385
  if (event.type === "rejected") {
14807
- yield sanitizeDeep(rejectedFrameFromResult(event.result));
16386
+ yield sanitizeDeep(rejectedFrameFromResult2(event.result));
14808
16387
  c.error({
14809
16388
  code: PAYMENT_NOT_ACCEPTED_CODE,
14810
16389
  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 +16395,12 @@ async function* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2) {
14816
16395
  }
14817
16396
  }
14818
16397
  }
14819
- async function* runStatusCommand(c, inflow2, authStorage2) {
16398
+ async function* runStatusCommand2(c, inflow2, authStorage2) {
14820
16399
  assertSessionGuard(c, authStorage2, inflow2);
14821
16400
  if (!c.agent && !c.formatExplicit) {
14822
16401
  const client2 = await inflow2.x402.client();
14823
16402
  await renderInkUntilExit(
14824
- /* @__PURE__ */ jsx20(
16403
+ /* @__PURE__ */ jsx27(
14825
16404
  X402StatusView,
14826
16405
  {
14827
16406
  transactionId: c.args.transactionId,
@@ -14839,7 +16418,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14839
16418
  const fetchOnce = () => client.getX402Payload(c.args.transactionId);
14840
16419
  if (c.options.interval <= 0) {
14841
16420
  const snapshot = await fetchOnce();
14842
- yield sanitizeDeep(toStatusFrame(c.args.transactionId, snapshot, c.options.payloadFile));
16421
+ yield sanitizeDeep(toStatusFrame2(c.args.transactionId, snapshot, c.options.payloadFile));
14843
16422
  return;
14844
16423
  }
14845
16424
  const generator = pollAsync({
@@ -14851,7 +16430,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14851
16430
  timeout: c.options.timeout
14852
16431
  });
14853
16432
  for await (const outcome of generator) {
14854
- yield sanitizeDeep(toStatusFrame(c.args.transactionId, outcome.value, c.options.payloadFile));
16433
+ yield sanitizeDeep(toStatusFrame2(c.args.transactionId, outcome.value, c.options.payloadFile));
14855
16434
  if (!outcome.terminal) continue;
14856
16435
  if (outcome.reason !== void 0) {
14857
16436
  c.error({
@@ -14869,7 +16448,7 @@ async function* runStatusCommand(c, inflow2, authStorage2) {
14869
16448
  return;
14870
16449
  }
14871
16450
  }
14872
- function toStatusFrame(transactionId, response, payloadFile) {
16451
+ function toStatusFrame2(transactionId, response, payloadFile) {
14873
16452
  const frame = {
14874
16453
  transaction_id: transactionId,
14875
16454
  status: response.status
@@ -14880,12 +16459,12 @@ function toStatusFrame(transactionId, response, payloadFile) {
14880
16459
  if (response.paymentPayload !== void 0) frame.payment_payload = response.paymentPayload;
14881
16460
  return frame;
14882
16461
  }
14883
- async function runCancelCommand(c, inflow2, authStorage2) {
16462
+ async function runCancelCommand2(c, inflow2, authStorage2) {
14884
16463
  assertSessionGuard(c, authStorage2, inflow2);
14885
16464
  if (!c.agent && !c.formatExplicit) {
14886
16465
  await renderInkUntilExit(
14887
- /* @__PURE__ */ jsx20(
14888
- CancelView,
16466
+ /* @__PURE__ */ jsx27(
16467
+ CancelView2,
14889
16468
  {
14890
16469
  approvalId: c.args.approvalId,
14891
16470
  cancel: () => inflow2.x402.cancel({ approvalId: c.args.approvalId }).then(() => void 0),
@@ -14901,7 +16480,7 @@ async function runCancelCommand(c, inflow2, authStorage2) {
14901
16480
  }
14902
16481
  return inflow2.x402.cancel({ approvalId: c.args.approvalId });
14903
16482
  }
14904
- async function runDecodeCommand(c) {
16483
+ async function runDecodeCommand2(c) {
14905
16484
  let decoded;
14906
16485
  try {
14907
16486
  decoded = decodeHeader(c.args.header);
@@ -14912,22 +16491,22 @@ async function runDecodeCommand(c) {
14912
16491
  });
14913
16492
  }
14914
16493
  if (!c.agent && !c.formatExplicit) {
14915
- await renderInkUntilExit(/* @__PURE__ */ jsx20(DecodeView, { decoded }));
16494
+ await renderInkUntilExit(/* @__PURE__ */ jsx27(DecodeView2, { decoded }));
14916
16495
  return void 0;
14917
16496
  }
14918
16497
  return sanitizeDeep(decoded);
14919
16498
  }
14920
- async function runSupportedCommand(c, inflow2, authStorage2) {
16499
+ async function runSupportedCommand2(c, inflow2, authStorage2) {
14921
16500
  assertSessionGuard(c, authStorage2, inflow2);
14922
16501
  if (!c.agent && !c.formatExplicit) {
14923
- await renderInkUntilExit(/* @__PURE__ */ jsx20(SupportedView, { load: () => inflow2.x402.supported(), onComplete: () => void 0 }));
16502
+ await renderInkUntilExit(/* @__PURE__ */ jsx27(SupportedView2, { load: () => inflow2.x402.supported(), onComplete: () => void 0 }));
14924
16503
  return void 0;
14925
16504
  }
14926
16505
  const response = await inflow2.x402.supported();
14927
16506
  return sanitizeDeep(response);
14928
16507
  }
14929
- async function runInspectCommand(c) {
14930
- const probeHeaders = parseHeaderFlagsOrFail(c, c.options.header);
16508
+ async function runInspectCommand2(c) {
16509
+ const probeHeaders = parseHeaderFlagsOrFail2(c, c.options.header);
14931
16510
  const probeOptions = {
14932
16511
  method: c.options.method,
14933
16512
  headers: probeHeaders,
@@ -14944,8 +16523,8 @@ async function runInspectCommand(c) {
14944
16523
  if (!c.agent && !c.formatExplicit) {
14945
16524
  let finalPhase = null;
14946
16525
  await renderInkUntilExit(
14947
- /* @__PURE__ */ jsx20(
14948
- InspectView,
16526
+ /* @__PURE__ */ jsx27(
16527
+ InspectView2,
14949
16528
  {
14950
16529
  url: c.args.url,
14951
16530
  method: c.options.method,
@@ -14989,60 +16568,60 @@ async function runInspectCommand(c) {
14989
16568
  if (kind === "accepts") {
14990
16569
  return sanitizeDeep(buildAcceptsFrame(payload));
14991
16570
  }
14992
- return sanitizeDeep(buildNoPaymentFrame(payload));
16571
+ return sanitizeDeep(buildNoPaymentFrame2(payload));
14993
16572
  }
14994
16573
  function createX402Cli(inflow2, authStorage2, apiBaseUrl2) {
14995
- const cli2 = Cli5.create("x402", {
16574
+ const cli2 = Cli6.create("x402", {
14996
16575
  description: "x402 payment commands (pay, inspect, status, cancel, decode, supported)."
14997
16576
  });
14998
16577
  cli2.command("pay", {
14999
16578
  description: "Pay an x402-protected resource and return the seller response.",
15000
- args: payArgs,
15001
- options: payOptions,
16579
+ args: payArgs2,
16580
+ options: payOptions2,
15002
16581
  outputPolicy: "agent-only",
15003
16582
  async *run(c) {
15004
- yield* runPayCommand(c, inflow2, authStorage2, apiBaseUrl2);
16583
+ yield* runPayCommand2(c, inflow2, authStorage2, apiBaseUrl2);
15005
16584
  }
15006
16585
  });
15007
16586
  cli2.command("status", {
15008
16587
  description: "Poll the signing state of an in-flight x402 transaction.",
15009
- args: statusArgs,
15010
- options: statusOptions2,
16588
+ args: statusArgs2,
16589
+ options: statusOptions3,
15011
16590
  outputPolicy: "agent-only",
15012
16591
  async *run(c) {
15013
- yield* runStatusCommand(c, inflow2, authStorage2);
16592
+ yield* runStatusCommand2(c, inflow2, authStorage2);
15014
16593
  }
15015
16594
  });
15016
16595
  cli2.command("cancel", {
15017
16596
  description: "Best-effort cancel of an x402 approval.",
15018
- args: cancelArgs,
16597
+ args: cancelArgs2,
15019
16598
  outputPolicy: "agent-only",
15020
16599
  async run(c) {
15021
- return runCancelCommand(c, inflow2, authStorage2);
16600
+ return runCancelCommand2(c, inflow2, authStorage2);
15022
16601
  }
15023
16602
  });
15024
16603
  cli2.command("decode", {
15025
16604
  description: "Decode a raw PAYMENT-REQUIRED header value.",
15026
- args: decodeArgs,
16605
+ args: decodeArgs2,
15027
16606
  outputPolicy: "agent-only",
15028
16607
  async run(c) {
15029
- return runDecodeCommand(c);
16608
+ return runDecodeCommand2(c);
15030
16609
  }
15031
16610
  });
15032
16611
  cli2.command("supported", {
15033
16612
  description: "List the buyer-side capability cache (scheme x network).",
15034
16613
  outputPolicy: "agent-only",
15035
16614
  async run(c) {
15036
- return runSupportedCommand(c, inflow2, authStorage2);
16615
+ return runSupportedCommand2(c, inflow2, authStorage2);
15037
16616
  }
15038
16617
  });
15039
16618
  cli2.command("inspect", {
15040
16619
  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,
16620
+ args: inspectArgs2,
16621
+ options: inspectOptions2,
15043
16622
  outputPolicy: "agent-only",
15044
16623
  async run(c) {
15045
- return runInspectCommand(c);
16624
+ return runInspectCommand2(c);
15046
16625
  }
15047
16626
  });
15048
16627
  return cli2;
@@ -15116,9 +16695,9 @@ function formatUpdateNotice(info) {
15116
16695
  }
15117
16696
 
15118
16697
  // src/cli.tsx
15119
- var cliVersion = "0.5.2";
16698
+ var cliVersion = "0.6.1";
15120
16699
  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';
16700
+ 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
16701
  if (process9.argv.includes("--skill")) {
15123
16702
  process9.stdout.write(skillBody.endsWith("\n") ? skillBody : `${skillBody}
15124
16703
  `);
@@ -15215,7 +16794,7 @@ Received ${signal}; exiting.
15215
16794
  process9.on("SIGINT", onSignal);
15216
16795
  process9.on("SIGTERM", onSignal);
15217
16796
  }
15218
- var cli = Cli6.create("inflow", {
16797
+ var cli = Cli7.create("inflow", {
15219
16798
  description: "InFlow \u2014 agentic MPP / x402 payments from your machine.",
15220
16799
  version: cliVersion
15221
16800
  });
@@ -15244,6 +16823,7 @@ cli.command(createUserCli(inflow.user, authStorage, inflow));
15244
16823
  cli.command(createBalancesCli(inflow.balances, authStorage, inflow));
15245
16824
  cli.command(createDepositAddressesCli(inflow.depositAddresses, authStorage, inflow));
15246
16825
  cli.command(createX402Cli(inflow, authStorage, resolvedApiBaseUrl));
16826
+ cli.command(createMppCli(inflow, authStorage, resolvedApiBaseUrl));
15247
16827
  await cli.serve();
15248
16828
  var cli_default = cli;
15249
16829
  export {