primcli 1.13.3 → 1.14.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 (2) hide show
  1. package/dist/oclif/index.js +579 -171
  2. package/package.json +1 -1
@@ -7,6 +7,7 @@ import process$1 from "node:process";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { hostname } from "node:os";
9
9
  import { spawn } from "node:child_process";
10
+ import { gunzipSync } from "node:zlib";
10
11
  import { concat, getAddress, hexToBytes, keccak256, stringToBytes } from "viem";
11
12
  import { privateKeyToAccount } from "viem/accounts";
12
13
  //#region \0rolldown/runtime.js
@@ -8974,56 +8975,25 @@ const openapiDocument = {
8974
8975
  "consecutive_fails"
8975
8976
  ]
8976
8977
  },
8977
- "CreateHttpEndpointInput": {
8978
+ "CreateEndpointInput": {
8978
8979
  "type": "object",
8979
8980
  "additionalProperties": false,
8980
8981
  "properties": {
8981
8982
  "kind": {
8982
8983
  "type": "string",
8983
- "enum": ["http"],
8984
+ "enum": ["http", "function"],
8984
8985
  "default": "http",
8985
- "description": "Deliver to a webhook URL."
8986
+ "description": "http: deliver to a webhook URL (provide url). function: invoke a Primitive Function (provide function_id, omit url)."
8986
8987
  },
8987
8988
  "url": {
8988
8989
  "type": "string",
8989
8990
  "minLength": 1,
8990
- "description": "The webhook URL to deliver events to."
8991
- },
8992
- "enabled": {
8993
- "type": "boolean",
8994
- "default": true,
8995
- "description": "Whether the endpoint is active"
8996
- },
8997
- "domain_id": {
8998
- "type": ["string", "null"],
8999
- "format": "uuid",
9000
- "description": "Restrict to emails from a specific domain"
9001
- },
9002
- "rules": {
9003
- "type": "object",
9004
- "description": "Endpoint-specific filtering rules"
9005
- },
9006
- "is_route_target": {
9007
- "type": "boolean",
9008
- "default": false,
9009
- "description": "Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"
9010
- }
9011
- },
9012
- "required": ["url"]
9013
- },
9014
- "CreateFunctionEndpointInput": {
9015
- "type": "object",
9016
- "additionalProperties": false,
9017
- "properties": {
9018
- "kind": {
9019
- "type": "string",
9020
- "enum": ["function"],
9021
- "description": "Invoke a Primitive Function."
8991
+ "description": "The webhook URL to deliver events to. Required when kind is http; omit for function endpoints."
9022
8992
  },
9023
8993
  "function_id": {
9024
8994
  "type": "string",
9025
8995
  "format": "uuid",
9026
- "description": "The Function to invoke."
8996
+ "description": "The Function to invoke. Required when kind is function."
9027
8997
  },
9028
8998
  "enabled": {
9029
8999
  "type": "boolean",
@@ -9044,17 +9014,6 @@ const openapiDocument = {
9044
9014
  "default": false,
9045
9015
  "description": "Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"
9046
9016
  }
9047
- },
9048
- "required": ["kind", "function_id"]
9049
- },
9050
- "CreateEndpointInput": {
9051
- "oneOf": [{ "$ref": "#/components/schemas/CreateHttpEndpointInput" }, { "$ref": "#/components/schemas/CreateFunctionEndpointInput" }],
9052
- "discriminator": {
9053
- "propertyName": "kind",
9054
- "mapping": {
9055
- "http": "#/components/schemas/CreateHttpEndpointInput",
9056
- "function": "#/components/schemas/CreateFunctionEndpointInput"
9057
- }
9058
9017
  }
9059
9018
  },
9060
9019
  "UpdateEndpointInput": {
@@ -13369,83 +13328,43 @@ const operationManifest = [
13369
13328
  "pathParams": [],
13370
13329
  "queryParams": [],
13371
13330
  "requestSchema": {
13372
- "oneOf": [{
13373
- "type": "object",
13374
- "additionalProperties": false,
13375
- "properties": {
13376
- "kind": {
13377
- "type": "string",
13378
- "enum": ["http"],
13379
- "default": "http",
13380
- "description": "Deliver to a webhook URL."
13381
- },
13382
- "url": {
13383
- "type": "string",
13384
- "minLength": 1,
13385
- "description": "The webhook URL to deliver events to."
13386
- },
13387
- "enabled": {
13388
- "type": "boolean",
13389
- "default": true,
13390
- "description": "Whether the endpoint is active"
13391
- },
13392
- "domain_id": {
13393
- "type": ["string", "null"],
13394
- "format": "uuid",
13395
- "description": "Restrict to emails from a specific domain"
13396
- },
13397
- "rules": {
13398
- "type": "object",
13399
- "description": "Endpoint-specific filtering rules"
13400
- },
13401
- "is_route_target": {
13402
- "type": "boolean",
13403
- "default": false,
13404
- "description": "Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"
13405
- }
13331
+ "type": "object",
13332
+ "additionalProperties": false,
13333
+ "properties": {
13334
+ "kind": {
13335
+ "type": "string",
13336
+ "enum": ["http", "function"],
13337
+ "default": "http",
13338
+ "description": "http: deliver to a webhook URL (provide url). function: invoke a Primitive Function (provide function_id, omit url)."
13406
13339
  },
13407
- "required": ["url"]
13408
- }, {
13409
- "type": "object",
13410
- "additionalProperties": false,
13411
- "properties": {
13412
- "kind": {
13413
- "type": "string",
13414
- "enum": ["function"],
13415
- "description": "Invoke a Primitive Function."
13416
- },
13417
- "function_id": {
13418
- "type": "string",
13419
- "format": "uuid",
13420
- "description": "The Function to invoke."
13421
- },
13422
- "enabled": {
13423
- "type": "boolean",
13424
- "default": true,
13425
- "description": "Whether the endpoint is active"
13426
- },
13427
- "domain_id": {
13428
- "type": ["string", "null"],
13429
- "format": "uuid",
13430
- "description": "Restrict to emails from a specific domain"
13431
- },
13432
- "rules": {
13433
- "type": "object",
13434
- "description": "Endpoint-specific filtering rules"
13435
- },
13436
- "is_route_target": {
13437
- "type": "boolean",
13438
- "default": false,
13439
- "description": "Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"
13440
- }
13340
+ "url": {
13341
+ "type": "string",
13342
+ "minLength": 1,
13343
+ "description": "The webhook URL to deliver events to. Required when kind is http; omit for function endpoints."
13441
13344
  },
13442
- "required": ["kind", "function_id"]
13443
- }],
13444
- "discriminator": {
13445
- "propertyName": "kind",
13446
- "mapping": {
13447
- "http": "#/components/schemas/CreateHttpEndpointInput",
13448
- "function": "#/components/schemas/CreateFunctionEndpointInput"
13345
+ "function_id": {
13346
+ "type": "string",
13347
+ "format": "uuid",
13348
+ "description": "The Function to invoke. Required when kind is function."
13349
+ },
13350
+ "enabled": {
13351
+ "type": "boolean",
13352
+ "default": true,
13353
+ "description": "Whether the endpoint is active"
13354
+ },
13355
+ "domain_id": {
13356
+ "type": ["string", "null"],
13357
+ "format": "uuid",
13358
+ "description": "Restrict to emails from a specific domain"
13359
+ },
13360
+ "rules": {
13361
+ "type": "object",
13362
+ "description": "Endpoint-specific filtering rules"
13363
+ },
13364
+ "is_route_target": {
13365
+ "type": "boolean",
13366
+ "default": false,
13367
+ "description": "Create this endpoint as a route-target: reachable only via an\nexplicit recipient route, never a domain's default destination, and\nexempt from the one-endpoint-per-domain rule.\n"
13449
13368
  }
13450
13369
  }
13451
13370
  },
@@ -23864,8 +23783,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
23864
23783
  name: "Primitive Team",
23865
23784
  url: "https://primitive.dev"
23866
23785
  };
23867
- const SDK_VERSION_RANGE = "^1.13.3";
23868
- const CLI_VERSION_RANGE = "^1.13.3";
23786
+ const SDK_VERSION_RANGE = "^1.14.0";
23787
+ const CLI_VERSION_RANGE = "^1.14.0";
23869
23788
  const ESBUILD_VERSION_RANGE = "^0.27.0";
23870
23789
  function renderHandler() {
23871
23790
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -27547,7 +27466,80 @@ var X402Error = class extends Error {
27547
27466
  this.retryAfter = options?.retryAfter ?? null;
27548
27467
  }
27549
27468
  };
27550
- new RegExp(`^${/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.source.slice(1, -1)}@[^\\s@]+$`, "i");
27469
+ /** A UUID, as carried by `interaction_id`'s local part and the step ids. */
27470
+ const EXTRACT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27471
+ /** An interaction id is `uuid@domain`. */
27472
+ const EXTRACT_WIRE_ID_RE = new RegExp(`^${EXTRACT_UUID_RE.source.slice(1, -1)}@[^\\s@]+$`, "i");
27473
+ /**
27474
+ * Parse the bytes of an inbound `interaction.json` MIME part into a typed
27475
+ * {@link X402EmailChallenge} ready for {@link X402Client.payEmailChallenge}.
27476
+ *
27477
+ * A payer receives the x402 challenge as an `interaction.json` attachment on an
27478
+ * inbound email (filename `interaction.json`, content type `application/json`).
27479
+ * This validates the envelope (the strict snake_case wire shape, that it is the
27480
+ * `x402.payment` `challenge` step, and that the embedded payload carries the
27481
+ * fields a payer signs over) and re-assembles the nonce binding from the
27482
+ * envelope's `interaction_id` + `step_id` + the payload's `challenge_nonce`, so
27483
+ * the caller never has to hand-parse the part.
27484
+ *
27485
+ * Accepts the part body as a UTF-8 string, a `Uint8Array`/`Buffer`, or an
27486
+ * already-parsed envelope object. Throws {@link X402Error} (status 0) on any
27487
+ * malformed or non-challenge part.
27488
+ *
27489
+ * The resulting `challenge_id` is empty: the platform's private challenge id is
27490
+ * not carried on the wire, and `payEmailChallenge` does not need it (it binds to
27491
+ * the `interaction_id` and the challenge step id).
27492
+ */
27493
+ function parseEmailChallengeFromPart(part) {
27494
+ const bad = (field) => {
27495
+ throw new X402Error(`interaction.json part is not a valid x402 challenge: ${field}`, 0);
27496
+ };
27497
+ let envelope;
27498
+ if (typeof part === "string" || part instanceof Uint8Array) {
27499
+ const text = typeof part === "string" ? part : new TextDecoder().decode(part);
27500
+ let parsed;
27501
+ try {
27502
+ parsed = JSON.parse(text);
27503
+ } catch (cause) {
27504
+ throw new X402Error(`interaction.json part is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, 0, void 0, { cause });
27505
+ }
27506
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) bad("envelope (expected a JSON object)");
27507
+ envelope = parsed;
27508
+ } else if (part && typeof part === "object" && !Array.isArray(part) && !(part instanceof Uint8Array)) envelope = part;
27509
+ else return bad("envelope (expected JSON bytes, a string, or an object)");
27510
+ if (envelope.interaction_version !== 1) bad("interaction_version (expected 1)");
27511
+ const interactionId = envelope.interaction_id;
27512
+ if (typeof interactionId !== "string" || !EXTRACT_WIRE_ID_RE.test(interactionId)) bad("interaction_id (expected uuid@domain)");
27513
+ if (envelope.protocol !== "x402.payment") bad(`protocol (expected "${X402_INTERACTION_PROTOCOL}")`);
27514
+ if (envelope.protocol_version !== 1) bad(`protocol_version (expected 1)`);
27515
+ if (envelope.step !== "challenge") bad(`step (expected "challenge", got "${String(envelope.step)}")`);
27516
+ const stepId = envelope.step_id;
27517
+ if (typeof stepId !== "string" || !EXTRACT_UUID_RE.test(stepId)) bad("step_id (expected a uuid)");
27518
+ const expiresAt = envelope.expires_at;
27519
+ if (typeof expiresAt !== "string" || !expiresAt) bad("expires_at (expected an ISO-8601 timestamp on the challenge step)");
27520
+ const payload = envelope.payload;
27521
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) bad("payload (expected an object)");
27522
+ const p = payload;
27523
+ const challengeNonce = p.challenge_nonce;
27524
+ if (typeof challengeNonce !== "string" || !/^[0-9a-f]{64}$/.test(challengeNonce)) bad("payload.challenge_nonce (expected 64 lowercase hex chars)");
27525
+ const paymentRequirements = p.payment_requirements;
27526
+ if (!paymentRequirements || typeof paymentRequirements !== "object" || Array.isArray(paymentRequirements)) bad("payload.payment_requirements (expected an object)");
27527
+ const challenge = {
27528
+ interaction_id: interactionId,
27529
+ challenge_id: "",
27530
+ challenge: {
27531
+ payment_requirements: paymentRequirements,
27532
+ nonce_binding: {
27533
+ interaction_id: interactionId,
27534
+ challenge_step_id: stepId,
27535
+ challenge_nonce: challengeNonce
27536
+ },
27537
+ expires_at: expiresAt
27538
+ }
27539
+ };
27540
+ validateEmailChallenge(challenge);
27541
+ return challenge;
27542
+ }
27551
27543
  /**
27552
27544
  * Assert a challenge is fully hydrated before signing, so a missing field fails
27553
27545
  * with a named X402Error instead of an opaque viem/BigInt error mid-sign.
@@ -27867,6 +27859,149 @@ function createX402Client(options = {}) {
27867
27859
  return new X402Client(options);
27868
27860
  }
27869
27861
  //#endregion
27862
+ //#region src/oclif/commands/payments-email-challenge.ts
27863
+ /** The exact attachment part name the x402 challenge travels in. */
27864
+ const INTERACTION_PART_FILENAME$1 = "interaction.json";
27865
+ /** Content type the challenge attachment is sent with. */
27866
+ const INTERACTION_PART_CONTENT_TYPE$1 = "application/json";
27867
+ /**
27868
+ * Strip a leading `<digits>_` part-index prefix from an archive entry's
27869
+ * basename. The attachments archive names each member `<part_index>_<filename>`,
27870
+ * so `0_interaction.json` -> `interaction.json`.
27871
+ */
27872
+ function stripPartIndexPrefix(basename) {
27873
+ return basename.replace(/^\d+_/, "");
27874
+ }
27875
+ /**
27876
+ * Resolve the exact archive entry name for the challenge attachment from email
27877
+ * attachment metadata. Returns the matching entry's `tar_path` (e.g.
27878
+ * `0_interaction.json`), or null when no `interaction.json` attachment is
27879
+ * described. Prefers an exact `filename === "interaction.json"` match; falls back
27880
+ * to a single `application/json` part when the filename is absent.
27881
+ */
27882
+ function interactionTarPathFromMeta(attachments) {
27883
+ if (!attachments || attachments.length === 0) return null;
27884
+ const chosen = attachments.find((a) => a.filename === "interaction.json") ?? attachments.find((a) => a.content_type === INTERACTION_PART_CONTENT_TYPE$1);
27885
+ if (!chosen) return null;
27886
+ if (chosen.tar_path) return chosen.tar_path;
27887
+ if (typeof chosen.part_index === "number" && chosen.filename) return `${chosen.part_index}_${chosen.filename}`;
27888
+ return chosen.filename ?? null;
27889
+ }
27890
+ /** Build the attachments-archive URL for an inbound email id. */
27891
+ function attachmentsArchiveUrl(baseUrl, emailId) {
27892
+ return `${baseUrl.replace(/\/$/, "")}/emails/${encodeURIComponent(emailId)}/attachments.tar.gz`;
27893
+ }
27894
+ /**
27895
+ * Minimal POSIX (ustar) tar reader: yield each regular-file entry's name and
27896
+ * bytes. We avoid a third-party tar dependency because the published CLI bundles
27897
+ * everything (the tarball-isolation check forbids unexpected runtime deps) and a
27898
+ * tar archive of email attachments is a flat list of small regular files, which
27899
+ * the base format already covers.
27900
+ *
27901
+ * Tar layout: a sequence of 512-byte records. Each file is one 512-byte header
27902
+ * followed by ceil(size/512) data records. Two consecutive all-zero headers mark
27903
+ * the end. The header's name is at offset 0 (100 bytes), size at offset 124 (12
27904
+ * bytes, octal), and the type flag at offset 156 ('0' or '\0' = regular file).
27905
+ * GNU long names (type 'L') and the ustar `prefix` field are handled so a longer
27906
+ * path still resolves; other special types are skipped.
27907
+ */
27908
+ function* readTarEntries(buffer) {
27909
+ const BLOCK = 512;
27910
+ let offset = 0;
27911
+ let longName = null;
27912
+ const readString = (start, length) => {
27913
+ const slice = buffer.subarray(start, start + length);
27914
+ let end = slice.indexOf(0);
27915
+ if (end === -1) end = slice.length;
27916
+ return new TextDecoder().decode(slice.subarray(0, end));
27917
+ };
27918
+ while (offset + BLOCK <= buffer.length) {
27919
+ const header = buffer.subarray(offset, offset + BLOCK);
27920
+ if (header.every((byte) => byte === 0)) break;
27921
+ const sizeField = readString(offset + 124, 12).trim();
27922
+ const size = sizeField ? Number.parseInt(sizeField, 8) : 0;
27923
+ const safeSize = Number.isFinite(size) && size >= 0 ? size : 0;
27924
+ const typeFlag = String.fromCharCode(header[156] ?? 0);
27925
+ const dataStart = offset + BLOCK;
27926
+ const dataEnd = dataStart + safeSize;
27927
+ if (typeFlag === "L") longName = readString(dataStart, safeSize).replace(/\0+$/, "");
27928
+ else if (typeFlag === "0" || typeFlag === "\0") {
27929
+ const name = longName ?? (() => {
27930
+ const prefix = readString(offset + 345, 155);
27931
+ const base = readString(offset, 100);
27932
+ return prefix ? `${prefix}/${base}` : base;
27933
+ })();
27934
+ longName = null;
27935
+ yield {
27936
+ name,
27937
+ bytes: buffer.subarray(dataStart, dataEnd)
27938
+ };
27939
+ } else longName = null;
27940
+ offset = dataStart + Math.ceil(safeSize / BLOCK) * BLOCK;
27941
+ }
27942
+ }
27943
+ /**
27944
+ * Pull the `interaction.json` bytes out of a gzipped tar attachments archive.
27945
+ * Returns null when the archive has no such member (e.g. the inbound email is
27946
+ * not an x402 challenge).
27947
+ *
27948
+ * When `tarPath` is given (the exact entry name from the email's attachment
27949
+ * metadata, e.g. `0_interaction.json`), the member is matched by that exact name
27950
+ * first. As a fallback (and when no `tarPath` is supplied), the member is matched
27951
+ * by basename after stripping any leading `<digits>_` part-index prefix, so both
27952
+ * `0_interaction.json` and a bare `interaction.json` resolve, and a leading
27953
+ * directory in the archive path does not hide it.
27954
+ */
27955
+ function interactionJsonFromArchive(gzipped, tarPath) {
27956
+ const tar = gunzipSync(gzipped);
27957
+ const wantedBase = tarPath ? tarPath.split("/").pop() ?? tarPath : null;
27958
+ for (const entry of readTarEntries(tar)) {
27959
+ const base = entry.name.split("/").pop() ?? entry.name;
27960
+ if (wantedBase && base === wantedBase) return entry.bytes;
27961
+ if (!wantedBase && stripPartIndexPrefix(base) === "interaction.json") return entry.bytes;
27962
+ }
27963
+ if (wantedBase) {
27964
+ for (const entry of readTarEntries(tar)) if (stripPartIndexPrefix(entry.name.split("/").pop() ?? entry.name) === "interaction.json") return entry.bytes;
27965
+ }
27966
+ return null;
27967
+ }
27968
+ /**
27969
+ * Fetch an inbound email's attachments archive and return the raw
27970
+ * `interaction.json` bytes, or null when the email carries no such part.
27971
+ *
27972
+ * Mirrors the direct-fetch + bearer-auth pattern the zone-file download command
27973
+ * uses: the typed SDK client returns the archive as a Blob, which is awkward to
27974
+ * decompress; a plain fetch hands us an ArrayBuffer directly. `fetchImpl` is
27975
+ * injectable so tests can stub the network boundary.
27976
+ */
27977
+ async function fetchInteractionJsonBytes(params) {
27978
+ const response = await (params.fetchImpl ?? fetch)(attachmentsArchiveUrl(params.baseUrl, params.emailId), { headers: {
27979
+ ...params.headers ?? {},
27980
+ ...params.apiKey ? { authorization: `Bearer ${params.apiKey}` } : {}
27981
+ } });
27982
+ if (!response.ok) {
27983
+ const text = await response.text().catch(() => "");
27984
+ throw new Error(`Could not download attachments for inbound email ${params.emailId}: HTTP ${response.status}${text.trim() ? ` ${text.trim().slice(0, 200)}` : ""}`);
27985
+ }
27986
+ return interactionJsonFromArchive(new Uint8Array(await response.arrayBuffer()), interactionTarPathFromMeta(params.attachments));
27987
+ }
27988
+ /**
27989
+ * Derive the signable {@link X402EmailChallenge} from an inbound payment-request
27990
+ * email's `interaction.json` attachment. This is the auto-derive path behind
27991
+ * `pay-email --in-reply-to <id>` (with no `--challenge`) and
27992
+ * `challenge-from-email`: download the part, then reshape the wire envelope into
27993
+ * the challenge object via the SDK's canonical `parseEmailChallengeFromPart`.
27994
+ *
27995
+ * Throws a descriptive error when the email has no `interaction.json` part (so a
27996
+ * non-challenge email gives a clear message instead of a confusing signer crash)
27997
+ * or when the part is not a valid x402 challenge envelope (the SDK validator).
27998
+ */
27999
+ async function deriveEmailChallengeFromInbound(params) {
28000
+ const bytes = await fetchInteractionJsonBytes(params);
28001
+ if (!bytes) throw new Error(`Inbound email ${params.emailId} has no ${INTERACTION_PART_FILENAME$1} attachment, so it is not an x402 payment request. Pass --challenge / --challenge-file if you have the challenge from another source.`);
28002
+ return parseEmailChallengeFromPart(bytes);
28003
+ }
28004
+ //#endregion
27870
28005
  //#region src/oclif/commands/payments-shared.ts
27871
28006
  const PRIVATE_KEY_ENV = "PRIMITIVE_X402_PRIVATE_KEY";
27872
28007
  const PRIVATE_KEY_FLAG_DESCRIPTION = `Hex private key (0x-prefixed) of the wallet that signs. Prefer the ${PRIVATE_KEY_ENV} environment variable so the key is not exposed in shell history or the process list. The key is used locally to sign and never sent to Primitive.`;
@@ -27981,6 +28116,71 @@ function reportX402Error(error, ctx) {
27981
28116
  });
27982
28117
  }
27983
28118
  //#endregion
28119
+ //#region src/oclif/commands/payments-challenge-from-email.ts
28120
+ var PaymentsChallengeFromEmailCommand = class PaymentsChallengeFromEmailCommand extends Command {
28121
+ static description = `Derive and print the email x402 challenge object from a received payment-request email.
28122
+
28123
+ A payer receives an x402 payment request as an email carrying an
28124
+ \`interaction.json\` attachment (the wire envelope). The signing commands,
28125
+ however, take the challenge object the payee's \`create-email-challenge\` API
28126
+ returns, which has a different field layout. This command downloads the inbound
28127
+ email's \`interaction.json\` attachment and reshapes it into that challenge
28128
+ object, printing it to stdout.
28129
+
28130
+ Use it when you want the challenge as a portable artifact (e.g. to feed
28131
+ \`pay-email-step\` for offline signing, or an SDK \`payEmailChallenge\` call).
28132
+ For the simple one-shot, prefer \`pay-email --in-reply-to <id>\`, which derives
28133
+ the challenge internally and needs no intermediate file.
28134
+
28135
+ primitive payments challenge-from-email --id <inbound-id> > challenge.json
28136
+ primitive payments challenge-from-email --id <inbound-id> \\
28137
+ | primitive payments pay-email-step > interaction.json`;
28138
+ static summary = "Print the email x402 challenge object derived from a received payment-request email";
28139
+ static examples = ["<%= config.bin %> payments challenge-from-email --id <inbound-email-id>", "<%= config.bin %> payments challenge-from-email --id <inbound-email-id> | <%= config.bin %> payments pay-email-step > interaction.json"];
28140
+ static flags = {
28141
+ "api-key": Flags.string({
28142
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
28143
+ env: "PRIMITIVE_API_KEY"
28144
+ }),
28145
+ "api-base-url": Flags.string({
28146
+ description: API_BASE_URL_FLAG_DESCRIPTION,
28147
+ env: "PRIMITIVE_API_BASE_URL",
28148
+ hidden: true
28149
+ }),
28150
+ id: Flags.string({
28151
+ description: "Id of the inbound payment-request email whose interaction.json attachment carries the challenge.",
28152
+ required: true
28153
+ }),
28154
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
28155
+ };
28156
+ async run() {
28157
+ const { flags } = await this.parse(PaymentsChallengeFromEmailCommand);
28158
+ const { auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
28159
+ apiKey: flags["api-key"],
28160
+ apiBaseUrl: flags["api-base-url"],
28161
+ configDir: this.config.configDir
28162
+ });
28163
+ await runWithTiming(flags.time, async () => {
28164
+ try {
28165
+ const challenge = await deriveEmailChallengeFromInbound({
28166
+ baseUrl: auth.apiBaseUrl,
28167
+ emailId: flags.id,
28168
+ apiKey: auth.apiKey,
28169
+ headers: requestConfig.headers
28170
+ });
28171
+ this.log(JSON.stringify(challenge, null, 2));
28172
+ } catch (error) {
28173
+ reportX402Error(error, {
28174
+ auth,
28175
+ baseUrlOverridden,
28176
+ configDir: this.config.configDir
28177
+ });
28178
+ process.exitCode = 1;
28179
+ }
28180
+ });
28181
+ }
28182
+ };
28183
+ //#endregion
27984
28184
  //#region src/oclif/commands/payments-charge.ts
27985
28185
  var PaymentsChargeCommand = class PaymentsChargeCommand extends Command {
27986
28186
  static description = `Request an x402 payment by creating a challenge (payee side).
@@ -28289,7 +28489,136 @@ var PaymentsPayEmailStepCommand = class PaymentsPayEmailStepCommand extends Comm
28289
28489
  }
28290
28490
  };
28291
28491
  //#endregion
28492
+ //#region src/oclif/commands/payments-settlement.ts
28493
+ /** The human-readable notice describing the async-settlement model. */
28494
+ const settlementWaitNotice = "Note: the payment has been SENT, but x402 settlement is asynchronous. --wait only confirms email delivery (SMTP 250), not on-chain settlement. The settle_tx hash arrives in a follow-up x402 settlement interaction email from the payee. Use --wait-settle to poll for it.";
28495
+ /** The steps a payer has already seen / sent, so a matching later step is the receipt. */
28496
+ const NON_RECEIPT_STEPS = new Set(["challenge", "payment"]);
28497
+ /**
28498
+ * Best-effort extraction of a `settle_tx` from a receipt envelope. The exact
28499
+ * receipt wire shape is owned by the platform and not pinned in the SDK, so we
28500
+ * do not assert a rigid schema: we look for a `settle_tx` string at the top
28501
+ * level or one level into `payload` (the two places interaction envelopes carry
28502
+ * step data). Returns null when absent; the caller always prints the full
28503
+ * envelope regardless, so a miss never hides data.
28504
+ */
28505
+ function extractSettleTx(envelope) {
28506
+ const top = envelope.settle_tx;
28507
+ if (typeof top === "string" && top) return top;
28508
+ const payload = envelope.payload;
28509
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
28510
+ const nested = payload.settle_tx;
28511
+ if (typeof nested === "string" && nested) return nested;
28512
+ }
28513
+ return null;
28514
+ }
28515
+ /**
28516
+ * Parse interaction.json bytes into an envelope object, or null when the bytes
28517
+ * are not a JSON object (so a non-interaction attachment is simply skipped).
28518
+ */
28519
+ function parseInteractionEnvelope(bytes) {
28520
+ let parsed;
28521
+ try {
28522
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
28523
+ } catch {
28524
+ return null;
28525
+ }
28526
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
28527
+ return parsed;
28528
+ }
28529
+ /**
28530
+ * True when a downloaded interaction.json envelope is the settlement receipt for
28531
+ * the interaction we paid: same `interaction_id`, and a step the payer has not
28532
+ * already produced/received (i.e. not `challenge` / `payment`). Matching on the
28533
+ * confirmed interaction_id keeps this correct without depending on the exact
28534
+ * receipt step name, which the platform owns.
28535
+ */
28536
+ function isSettlementReceiptFor(envelope, interactionId) {
28537
+ if (envelope.interaction_id !== interactionId) return false;
28538
+ const step = envelope.step;
28539
+ if (typeof step === "string" && NON_RECEIPT_STEPS.has(step)) return false;
28540
+ return true;
28541
+ }
28542
+ /**
28543
+ * Poll the inbox for the x402 settlement interaction email that answers the
28544
+ * payment we just sent, and return it once found. Returns null on timeout.
28545
+ *
28546
+ * Searches inbound mail from the payee (with attachments) received since the
28547
+ * send, downloads each candidate's interaction.json, and matches it by
28548
+ * interaction_id via {@link isSettlementReceiptFor}. Network access is via the
28549
+ * injected api client (search) plus a plain fetch for the attachment archive,
28550
+ * mirroring how the rest of the payments surface reads attachments; `fetchImpl`
28551
+ * is injectable for tests.
28552
+ */
28553
+ async function pollForSettlementInteraction(params) {
28554
+ const deadline = params.timeoutSeconds === 0 ? null : Date.now() + params.timeoutSeconds * 1e3;
28555
+ const checked = /* @__PURE__ */ new Set();
28556
+ while (deadline === null || Date.now() < deadline) {
28557
+ let cursor = null;
28558
+ let receipt = null;
28559
+ let drained = false;
28560
+ while (!drained) {
28561
+ const page = await fetchEmailSearchPage({
28562
+ apiClient: params.apiClient,
28563
+ cursor,
28564
+ filters: {
28565
+ from: params.payeeFrom,
28566
+ hasAttachment: true
28567
+ },
28568
+ pageSize: 50,
28569
+ since: params.since
28570
+ });
28571
+ if (!page.ok) break;
28572
+ for (const row of page.rows) {
28573
+ if (checked.has(row.id)) continue;
28574
+ let bytes;
28575
+ try {
28576
+ bytes = await fetchInteractionJsonBytes({
28577
+ baseUrl: params.baseUrl,
28578
+ emailId: row.id,
28579
+ apiKey: params.apiKey,
28580
+ headers: params.headers,
28581
+ fetchImpl: params.fetchImpl
28582
+ });
28583
+ } catch {
28584
+ continue;
28585
+ }
28586
+ if (!bytes) continue;
28587
+ const envelope = parseInteractionEnvelope(bytes);
28588
+ if (!envelope) continue;
28589
+ checked.add(row.id);
28590
+ if (isSettlementReceiptFor(envelope, params.interactionId)) {
28591
+ receipt = {
28592
+ emailId: row.id,
28593
+ envelope,
28594
+ settleTx: extractSettleTx(envelope)
28595
+ };
28596
+ break;
28597
+ }
28598
+ }
28599
+ if (receipt) break;
28600
+ const nextCursor = page.cursor;
28601
+ if (!nextCursor || nextCursor === cursor || page.rows.length === 0) drained = true;
28602
+ else cursor = nextCursor;
28603
+ }
28604
+ if (receipt) return receipt;
28605
+ if (deadline !== null && Date.now() >= deadline) break;
28606
+ await sleep$1(params.intervalSeconds * 1e3);
28607
+ }
28608
+ return null;
28609
+ }
28610
+ //#endregion
28292
28611
  //#region src/oclif/commands/payments-pay-email.ts
28612
+ /**
28613
+ * True when none of the explicit challenge sources are present (no --challenge,
28614
+ * no --challenge-file, and stdin is an interactive TTY rather than a pipe). In
28615
+ * that case `pay-email` auto-derives the challenge from the inbound email's
28616
+ * interaction.json attachment. We treat a TTY stdin as "no challenge piped" so
28617
+ * the one-shot does not block on readFileSync(0) waiting for the user to paste.
28618
+ */
28619
+ function shouldDeriveChallenge(flags) {
28620
+ return flags.challenge === void 0 && flags["challenge-file"] === void 0;
28621
+ }
28293
28622
  /** The exact part name + content type the inbound matcher requires. */
28294
28623
  const INTERACTION_PART_FILENAME = "interaction.json";
28295
28624
  const INTERACTION_PART_CONTENT_TYPE = "application/json";
@@ -28312,34 +28641,64 @@ function emailDetailFromEnvelope(data) {
28312
28641
  return data?.data ?? null;
28313
28642
  }
28314
28643
  var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28315
- static description = `Pay an email-native x402 challenge in one step: sign it and send the signed interaction.json in-thread.
28644
+ static description = `Pay an email-native x402 challenge in one step: sign it and send the signed interaction.json.
28316
28645
 
28317
- Reads the email challenge the payee issued (the JSON from
28318
- \`payments create-email-challenge\`), derives and signs the interaction-bound
28319
- EIP-3009 authorization locally with your wallet key (read from
28320
- ${PRIVATE_KEY_ENV} by default), and sends the signed \`interaction.json\`
28321
- attached as an \`application/json\` part. The key never leaves your machine.
28646
+ THE ONE-COMMAND PAYER FLOW. You received a payment-request email carrying an
28647
+ \`interaction.json\` attachment. Run:
28322
28648
 
28323
- This is the recommended payer path. It replaces the two-step
28324
- \`pay-email-step\` + \`send --attachment interaction.json\` dance: no manual
28325
- address or threading wrangling. Use \`pay-email-step\` only when you need the
28326
- signed bytes as a portable artifact without sending.
28649
+ <%= config.bin %> payments pay-email --in-reply-to <inbound-email-id>
28327
28650
 
28328
- --in-reply-to is the id of the inbound challenge email you received. It is
28329
- fetched to derive the recipient (the payee, from the inbound's sender) and the
28330
- From (you, the payer, from the inbound's recipient). The outgoing send does
28331
- NOT thread under the challenge: threading would trigger the send endpoint's
28332
- parent-thread dedup and the payment would never settle. The interaction
28333
- associates by interaction_id instead. Pass --from only to override the derived
28334
- payer From.
28651
+ with your wallet key in ${PRIVATE_KEY_ENV}, and this command auto-derives the
28652
+ challenge from that inbound email's attachment, signs the interaction-bound
28653
+ EIP-3009 authorization locally, and sends the signed \`interaction.json\` back
28654
+ to the payee. The key never leaves your machine. You do NOT need --challenge or
28655
+ --challenge-file: with just --in-reply-to the challenge is derived for you.
28335
28656
 
28336
- Provide the challenge inline with --challenge, from a file with
28337
- --challenge-file, or piped on stdin.`;
28338
- static summary = "Sign an email x402 challenge and send the signed interaction.json in-thread (one step)";
28657
+ THE CHALLENGE OBJECT (only needed for the --challenge / --challenge-file
28658
+ override). \`--challenge\` expects the object the PAYEE's
28659
+ \`create-email-challenge\` API returns, NOT the inbound email's
28660
+ \`interaction.json\` attachment (those are different shapes). Its fields are:
28661
+
28662
+ {
28663
+ "interaction_id": "<uuid@domain>", // the email thread id
28664
+ "challenge": {
28665
+ "payment_requirements": { ... }, // scheme, network, payTo, asset, ...
28666
+ "nonce_binding": {
28667
+ "interaction_id": "<uuid@domain>", // must equal the outer interaction_id
28668
+ "challenge_step_id": "<uuid>", // the challenge step id
28669
+ "challenge_nonce": "<64 hex chars>"
28670
+ },
28671
+ "expires_at": "<ISO-8601>"
28672
+ }
28673
+ }
28674
+
28675
+ As a real payer you usually have only the inbound email's attachment, whose
28676
+ layout differs (top-level \`step\`, \`step_id\`, \`expires_at\`, and a nested
28677
+ \`payload.payment_requirements\` / \`payload.challenge_nonce\`). You do NOT
28678
+ reshape it by hand: omit --challenge and let --in-reply-to derive it, or run
28679
+ \`payments challenge-from-email --id <inbound-id>\` to print the correctly
28680
+ shaped challenge object. Pass --challenge / --challenge-file only when you have
28681
+ the payee-side challenge from another source; it is used as-is and overrides
28682
+ the auto-derive.
28683
+
28684
+ SETTLEMENT IS ASYNC. --wait only confirms the receiving MTA accepted the
28685
+ message (SMTP 250); it does NOT confirm on-chain settlement. The \`settle_tx\`
28686
+ hash arrives later in a follow-up inbound x402 settlement interaction email
28687
+ from the payee. Use --wait-settle to poll for that settlement email after
28688
+ sending and surface the receipt.
28689
+
28690
+ ADDRESSING. --in-reply-to is the inbound challenge email's id. It is fetched to
28691
+ derive the recipient (the payee, from the inbound's sender) and the From (you,
28692
+ the payer, from the inbound's recipient). The outgoing send does NOT thread
28693
+ under the challenge: threading would trigger the send endpoint's parent-thread
28694
+ dedup and the payment would never settle. The interaction associates by
28695
+ interaction_id instead. Pass --from only to override the derived payer From.`;
28696
+ static summary = "Sign an email x402 challenge and send the signed interaction.json (one step; derives the challenge from --in-reply-to)";
28339
28697
  static examples = [
28698
+ "<%= config.bin %> payments pay-email --in-reply-to <inbound-email-id>",
28699
+ "<%= config.bin %> payments pay-email --in-reply-to <inbound-email-id> --wait-settle",
28340
28700
  "<%= config.bin %> payments pay-email --challenge-file challenge.json --in-reply-to <inbound-email-id>",
28341
- "cat challenge.json | <%= config.bin %> payments pay-email --in-reply-to <inbound-email-id> --wait",
28342
- "<%= config.bin %> payments pay-email --challenge-file challenge.json --in-reply-to <inbound-email-id> --from 'Payer <payer@your-domain.example>'"
28701
+ "<%= config.bin %> payments pay-email --in-reply-to <inbound-email-id> --from 'Payer <payer@your-domain.example>'"
28343
28702
  ];
28344
28703
  static flags = {
28345
28704
  "api-key": Flags.string({
@@ -28356,26 +28715,37 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28356
28715
  env: PRIVATE_KEY_ENV
28357
28716
  }),
28358
28717
  challenge: Flags.string({
28359
- description: "The email challenge object as a JSON string.",
28718
+ description: "OVERRIDE: the payee-side email challenge object (the create-email-challenge response shape, NOT the inbound interaction.json attachment) as a JSON string. Optional: omit it and the challenge is auto-derived from the --in-reply-to email's attachment.",
28360
28719
  exclusive: ["challenge-file"]
28361
28720
  }),
28362
28721
  "challenge-file": Flags.string({
28363
- description: "Path to a file containing the email challenge JSON.",
28722
+ description: "OVERRIDE: path to a file containing the payee-side email challenge JSON (the create-email-challenge response shape). Optional: omit it and the challenge is auto-derived from the --in-reply-to email's attachment.",
28364
28723
  exclusive: ["challenge"]
28365
28724
  }),
28366
28725
  "in-reply-to": Flags.string({
28367
- description: "Id of the inbound challenge email you received. It is fetched to derive the payee recipient and the payer From. The outgoing send is not threaded under the challenge; the payment associates by interaction_id.",
28726
+ description: "Id of the inbound challenge email you received. With no --challenge / --challenge-file, its interaction.json attachment is downloaded and reshaped into the challenge to sign. It is also fetched to derive the payee recipient and the payer From. The outgoing send is not threaded under the challenge; the payment associates by interaction_id.",
28368
28727
  required: true
28369
28728
  }),
28370
28729
  from: Flags.string({ description: "Optional From header override. Defaults to the inbound's recipient (the payer the challenge was addressed to)." }),
28371
28730
  body: Flags.string({ description: `Plain-text body. The signed authorization travels in the interaction.json attachment; this is the human-readable accompanying note. Defaults to "${DEFAULT_BODY_TEXT}".` }),
28372
- wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
28731
+ wait: Flags.boolean({ description: "Block until the receiving MTA returns a delivery outcome (SMTP 250). This confirms email DELIVERY only, NOT on-chain settlement. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
28732
+ "wait-settle": Flags.boolean({ description: "After sending, poll the inbox for the follow-up x402 settlement interaction email from the payee and print the settlement receipt (including settle_tx when present). Settlement is async, so this can take a while; tune with --settle-timeout / --settle-interval." }),
28733
+ "settle-timeout": Flags.integer({
28734
+ default: 180,
28735
+ description: "With --wait-settle: seconds to wait for the settlement email before giving up (0 waits forever).",
28736
+ min: 0
28737
+ }),
28738
+ "settle-interval": Flags.integer({
28739
+ default: 5,
28740
+ description: "With --wait-settle: seconds between settlement-email polls.",
28741
+ min: 1
28742
+ }),
28373
28743
  json: Flags.boolean({ description: "Print a JSON object with the signed interaction step and the send result." }),
28374
28744
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
28375
28745
  };
28376
28746
  async run() {
28377
28747
  const { flags } = await this.parse(PaymentsPayEmailCommand);
28378
- const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
28748
+ const { apiClient, auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
28379
28749
  apiKey: flags["api-key"],
28380
28750
  apiBaseUrl: flags["api-base-url"],
28381
28751
  configDir: this.config.configDir
@@ -28386,22 +28756,6 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28386
28756
  configDir: this.config.configDir
28387
28757
  };
28388
28758
  await runWithTiming(flags.time, async () => {
28389
- let built;
28390
- try {
28391
- built = await signEmailChallenge({
28392
- challenge: readEmailChallenge({
28393
- inline: flags.challenge,
28394
- file: flags["challenge-file"]
28395
- }),
28396
- privateKey: flags["private-key"] ?? "",
28397
- resolvedApiBaseUrl: auth.apiBaseUrl,
28398
- apiKey: flags["api-key"]
28399
- });
28400
- } catch (error) {
28401
- reportX402Error(error, authFailureContext);
28402
- process.exitCode = 1;
28403
- return;
28404
- }
28405
28759
  const inboundResult = await getEmail({
28406
28760
  client: apiClient.client,
28407
28761
  path: { id: flags["in-reply-to"] },
@@ -28423,6 +28777,31 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28423
28777
  process.exitCode = 1;
28424
28778
  return;
28425
28779
  }
28780
+ let built;
28781
+ try {
28782
+ let challenge;
28783
+ if (shouldDeriveChallenge(flags)) challenge = await deriveEmailChallengeFromInbound({
28784
+ baseUrl: auth.apiBaseUrl,
28785
+ emailId: flags["in-reply-to"],
28786
+ apiKey: auth.apiKey,
28787
+ headers: requestConfig.headers,
28788
+ attachments: inbound.parsed?.attachments
28789
+ });
28790
+ else challenge = readEmailChallenge({
28791
+ inline: flags.challenge,
28792
+ file: flags["challenge-file"]
28793
+ });
28794
+ built = await signEmailChallenge({
28795
+ challenge,
28796
+ privateKey: flags["private-key"] ?? "",
28797
+ resolvedApiBaseUrl: auth.apiBaseUrl,
28798
+ apiKey: flags["api-key"]
28799
+ });
28800
+ } catch (error) {
28801
+ reportX402Error(error, authFailureContext);
28802
+ process.exitCode = 1;
28803
+ return;
28804
+ }
28426
28805
  const payeeTo = inbound.from_email;
28427
28806
  const derivedPayerFrom = inbound.to_email || inbound.recipient;
28428
28807
  const from = flags.from ?? derivedPayerFrom;
@@ -28436,6 +28815,7 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28436
28815
  process.exitCode = 1;
28437
28816
  return;
28438
28817
  }
28818
+ const sendStartedAt = (/* @__PURE__ */ new Date()).toISOString();
28439
28819
  const result = await sendEmail({
28440
28820
  body: {
28441
28821
  from,
@@ -28463,13 +28843,40 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28463
28843
  if (replayed) writeIdempotentReplayBannerIfReplay(sent, { write: (chunk) => {
28464
28844
  process.stderr.write(chunk);
28465
28845
  } });
28846
+ let settlement = null;
28847
+ if (flags["wait-settle"] && !replayed && payeeTo) {
28848
+ process.stderr.write("Payment sent. Waiting for the x402 settlement interaction email (settlement is async)...\n");
28849
+ settlement = await pollForSettlementInteraction({
28850
+ apiClient,
28851
+ baseUrl: auth.apiBaseUrl,
28852
+ apiKey: auth.apiKey,
28853
+ headers: requestConfig.headers,
28854
+ interactionId: built.envelope.interaction_id,
28855
+ payeeFrom: payeeTo,
28856
+ since: sendStartedAt,
28857
+ timeoutSeconds: flags["settle-timeout"],
28858
+ intervalSeconds: flags["settle-interval"]
28859
+ });
28860
+ if (settlement) process.stderr.write(settlement.settleTx ? `Settled. settle_tx: ${settlement.settleTx}\n` : "Settlement interaction received (no settle_tx field present; full receipt below).\n");
28861
+ else process.stderr.write("Timed out waiting for the x402 settlement interaction email. The payment was sent; the settle_tx will arrive in a follow-up x402 settlement interaction email from the payee. Re-run with --wait-settle, or check your inbox for an x402 settlement message.\n");
28862
+ }
28466
28863
  if (flags.json) this.log(JSON.stringify({
28467
28864
  interaction: built.envelope,
28468
28865
  sent,
28469
- idempotent_replay: replayed
28866
+ idempotent_replay: replayed,
28867
+ ...flags["wait-settle"] ? { settlement: settlement ? {
28868
+ email_id: settlement.emailId,
28869
+ settle_tx: settlement.settleTx,
28870
+ receipt: settlement.envelope
28871
+ } : null } : {}
28470
28872
  }, null, 2));
28471
- else this.log(JSON.stringify(sent, null, 2));
28873
+ else {
28874
+ this.log(JSON.stringify(sent, null, 2));
28875
+ if (!flags["wait-settle"]) process.stderr.write(`${settlementWaitNotice}\n`);
28876
+ else if (settlement) this.log(JSON.stringify(settlement.envelope, null, 2));
28877
+ }
28472
28878
  if (replayed) process.exitCode = 1;
28879
+ else if (flags["wait-settle"] && !settlement) process.exitCode = 1;
28473
28880
  });
28474
28881
  }
28475
28882
  };
@@ -30087,7 +30494,8 @@ const COMMANDS = {
30087
30494
  "payments:pay-challenge": PaymentsPayCommand,
30088
30495
  "payments:pay": PaymentsPayCommand,
30089
30496
  "payments:pay-email": PaymentsPayEmailCommand,
30090
- "payments:pay-email-step": PaymentsPayEmailStepCommand
30497
+ "payments:pay-email-step": PaymentsPayEmailStepCommand,
30498
+ "payments:challenge-from-email": PaymentsChallengeFromEmailCommand
30091
30499
  };
30092
30500
  //#endregion
30093
30501
  export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.13.3",
3
+ "version": "1.14.1",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,