primcli 1.13.1 → 1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,17 +99,17 @@ cat challenge.json | primitive payments pay
99
99
  primitive payments create-email-challenge --from payee@your-domain.example \
100
100
  --to payer@their-domain.example --amount 10000 --network base-sepolia
101
101
  # Payer (recommended): pay the email challenge in one step. Signs the challenge
102
- # locally with your wallet key AND sends the signed interaction.json as an
103
- # in-thread reply, so you skip the manual sign-then-reply dance. --in-reply-to
104
- # is the inbound challenge email you received; the reply is threaded to it and
105
- # addressed to the payee, and From defaults to the payer it was sent to. The
106
- # reply carries a short default note alongside the attachment; pass --body to
107
- # customize it.
102
+ # locally with your wallet key AND sends the signed interaction.json, so you
103
+ # skip the manual sign-then-send dance. --in-reply-to is the inbound challenge
104
+ # email you received; it is fetched to address the payment to the payee, with
105
+ # From defaulting to the payer it was sent to. The send is not threaded under
106
+ # the challenge (the payment associates by interaction_id). The message carries
107
+ # a short default note alongside the attachment; pass --body to customize it.
108
108
  primitive payments pay-email --challenge-file challenge.json \
109
109
  --in-reply-to <inbound-challenge-email-id> --wait
110
110
 
111
111
  # Advanced: sign only, without sending. Emits the portable interaction.json
112
- # artifact you can attach to a reply yourself (e.g. with `primitive reply`).
112
+ # artifact you can attach yourself (e.g. with `primitive send --attachment`).
113
113
  primitive payments pay-email-step --challenge-file challenge.json > interaction.json
114
114
 
115
115
  # Inspect a challenge by id, or list your registered payout addresses.
@@ -124,7 +124,7 @@ primitive payments update-spend-policy --max-per-payment 5000000
124
124
 
125
125
  `charge` is the friendly verb that matches the SDK `charge` and accepts `--amount-usdc`; `create-challenge` is the lower-level command that takes base-unit `--amount`. Either creates a challenge. The signing commands print a human-readable summary by default; pass `--json` for raw JSON suitable for piping.
126
126
 
127
- The signing commands (`register-payout-address`, `pay`, `pay-email`, and `pay-email-step`) need your wallet key. `pay-email` is the recommended payer path for the email-native flow: it signs and replies in one step. `pay-email-step` signs only and emits the `interaction.json` artifact for advanced use where you want to deliver it yourself. Set the key in `PRIMITIVE_X402_PRIVATE_KEY` (a `0x`-prefixed hex private key) so it never lands in shell history or the process list:
127
+ The signing commands (`register-payout-address`, `pay`, `pay-email`, and `pay-email-step`) need your wallet key. `pay-email` is the recommended payer path for the email-native flow: it signs and sends in one step. `pay-email-step` signs only and emits the `interaction.json` artifact for advanced use where you want to deliver it yourself. Set the key in `PRIMITIVE_X402_PRIVATE_KEY` (a `0x`-prefixed hex private key) so it never lands in shell history or the process list:
128
128
 
129
129
  ```bash
130
130
  export PRIMITIVE_X402_PRIVATE_KEY=0x...
@@ -20955,7 +20955,7 @@ function assertParentMatchesRecipient(parent, recipient) {
20955
20955
  if (normalizeEmailAddress(parent.from_email) === normalizeEmailAddress(recipient)) return;
20956
20956
  throw cliError$6(`Inbound email ${parent.id} is from ${parent.from_email}, not ${recipient}. Use \`primitive chat ${parent.from_email} --reply <message> --reply-to-email-id ${parent.id}\` or omit --reply-to-email-id to continue the latest inbound from ${recipient}.`);
20957
20957
  }
20958
- function emailDetailFromEnvelope(envelope) {
20958
+ function emailDetailFromEnvelope$1(envelope) {
20959
20959
  return envelope?.data ?? envelope ?? null;
20960
20960
  }
20961
20961
  function buildCommand(kind, description, argv, options = {}) {
@@ -21205,7 +21205,7 @@ async function loadInboundEmailDetail(params) {
21205
21205
  });
21206
21206
  throw new Errors.CLIError(`Could not load inbound email ${params.id}.`, { exit: 1 });
21207
21207
  }
21208
- const detail = emailDetailFromEnvelope(result.data);
21208
+ const detail = emailDetailFromEnvelope$1(result.data);
21209
21209
  if (!detail) throw new Errors.CLIError(`Could not load inbound email ${params.id}: the API returned no email body.`, { exit: 1 });
21210
21210
  return detail;
21211
21211
  }
@@ -23864,8 +23864,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
23864
23864
  name: "Primitive Team",
23865
23865
  url: "https://primitive.dev"
23866
23866
  };
23867
- const SDK_VERSION_RANGE = "^1.13.1";
23868
- const CLI_VERSION_RANGE = "^1.13.1";
23867
+ const SDK_VERSION_RANGE = "^1.13.3";
23868
+ const CLI_VERSION_RANGE = "^1.13.3";
23869
23869
  const ESBUILD_VERSION_RANGE = "^0.27.0";
23870
23870
  function renderHandler() {
23871
23871
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -28293,7 +28293,8 @@ var PaymentsPayEmailStepCommand = class PaymentsPayEmailStepCommand extends Comm
28293
28293
  /** The exact part name + content type the inbound matcher requires. */
28294
28294
  const INTERACTION_PART_FILENAME = "interaction.json";
28295
28295
  const INTERACTION_PART_CONTENT_TYPE = "application/json";
28296
- const DEFAULT_REPLY_BODY_TEXT = "x402 payment authorization attached (interaction.json).";
28296
+ const DEFAULT_SUBJECT = "x402 payment authorization";
28297
+ const DEFAULT_BODY_TEXT = "x402 payment authorization attached (interaction.json).";
28297
28298
  /** Build the `interaction.json` attachment from a signed payment step. */
28298
28299
  function interactionAttachment(built) {
28299
28300
  return {
@@ -28302,29 +28303,39 @@ function interactionAttachment(built) {
28302
28303
  content_base64: Buffer.from(built.json, "utf8").toString("base64")
28303
28304
  };
28304
28305
  }
28306
+ /**
28307
+ * Pull the `EmailDetail` out of the get-email envelope. The fields wrapper
28308
+ * returns `{ data: { data: EmailDetail } }`; normalize to the inner detail or
28309
+ * null when the API returned no body.
28310
+ */
28311
+ function emailDetailFromEnvelope(data) {
28312
+ return data?.data ?? null;
28313
+ }
28305
28314
  var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28306
- static description = `Pay an email-native x402 challenge in one step: sign it and send the signed interaction.json as an in-thread reply.
28315
+ static description = `Pay an email-native x402 challenge in one step: sign it and send the signed interaction.json in-thread.
28307
28316
 
28308
28317
  Reads the email challenge the payee issued (the JSON from
28309
28318
  \`payments create-email-challenge\`), derives and signs the interaction-bound
28310
28319
  EIP-3009 authorization locally with your wallet key (read from
28311
- ${PRIVATE_KEY_ENV} by default), and replies in-thread to the challenge email
28312
- with the signed \`interaction.json\` attached as an \`application/json\` part.
28313
- The key never leaves your machine.
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.
28314
28322
 
28315
28323
  This is the recommended payer path. It replaces the two-step
28316
- \`pay-email-step\` + \`reply --attachment interaction.json\` dance: no manual
28324
+ \`pay-email-step\` + \`send --attachment interaction.json\` dance: no manual
28317
28325
  address or threading wrangling. Use \`pay-email-step\` only when you need the
28318
28326
  signed bytes as a portable artifact without sending.
28319
28327
 
28320
- --in-reply-to is the id of the inbound challenge email you received; the reply
28321
- endpoint derives the recipient (the payee) and threading headers from it, and
28322
- defaults the From to the inbound's recipient (you, the payer). Pass --from
28323
- only to override that default.
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.
28324
28335
 
28325
28336
  Provide the challenge inline with --challenge, from a file with
28326
28337
  --challenge-file, or piped on stdin.`;
28327
- static summary = "Sign an email x402 challenge and send it as an in-thread reply (one step)";
28338
+ static summary = "Sign an email x402 challenge and send the signed interaction.json in-thread (one step)";
28328
28339
  static examples = [
28329
28340
  "<%= config.bin %> payments pay-email --challenge-file challenge.json --in-reply-to <inbound-email-id>",
28330
28341
  "cat challenge.json | <%= config.bin %> payments pay-email --in-reply-to <inbound-email-id> --wait",
@@ -28353,12 +28364,12 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28353
28364
  exclusive: ["challenge"]
28354
28365
  }),
28355
28366
  "in-reply-to": Flags.string({
28356
- description: "Id of the inbound challenge email you received. The reply is threaded to it and addressed to the payee derived from it.",
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.",
28357
28368
  required: true
28358
28369
  }),
28359
28370
  from: Flags.string({ description: "Optional From header override. Defaults to the inbound's recipient (the payer the challenge was addressed to)." }),
28360
- body: Flags.string({ description: `Plain-text reply body. The signed authorization travels in the interaction.json attachment; this is the human-readable accompanying note. Defaults to "${DEFAULT_REPLY_BODY_TEXT}".` }),
28361
- wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
28371
+ 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." }),
28362
28373
  json: Flags.boolean({ description: "Print a JSON object with the signed interaction step and the send result." }),
28363
28374
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
28364
28375
  };
@@ -28391,15 +28402,50 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28391
28402
  process.exitCode = 1;
28392
28403
  return;
28393
28404
  }
28394
- const result = await replyToEmail({
28405
+ const inboundResult = await getEmail({
28406
+ client: apiClient.client,
28407
+ path: { id: flags["in-reply-to"] },
28408
+ responseStyle: "fields"
28409
+ });
28410
+ if (inboundResult.error) {
28411
+ const errorPayload = extractErrorPayload(inboundResult.error);
28412
+ writeErrorWithHints(errorPayload);
28413
+ surfaceUnauthorizedHint({
28414
+ ...authFailureContext,
28415
+ payload: errorPayload
28416
+ });
28417
+ process.exitCode = 1;
28418
+ return;
28419
+ }
28420
+ const inbound = emailDetailFromEnvelope(inboundResult.data);
28421
+ if (!inbound) {
28422
+ process.stderr.write(`Could not load inbound challenge email ${flags["in-reply-to"]}: the API returned no email.\n`);
28423
+ process.exitCode = 1;
28424
+ return;
28425
+ }
28426
+ const payeeTo = inbound.from_email;
28427
+ const derivedPayerFrom = inbound.to_email || inbound.recipient;
28428
+ const from = flags.from ?? derivedPayerFrom;
28429
+ if (!payeeTo) {
28430
+ process.stderr.write(`Inbound challenge email ${flags["in-reply-to"]} has no resolvable sender to address the payment to.\n`);
28431
+ process.exitCode = 1;
28432
+ return;
28433
+ }
28434
+ if (!from) {
28435
+ process.stderr.write(`Could not derive the payer From from inbound challenge email ${flags["in-reply-to"]}; pass --from explicitly.\n`);
28436
+ process.exitCode = 1;
28437
+ return;
28438
+ }
28439
+ const result = await sendEmail({
28395
28440
  body: {
28396
- body_text: flags.body?.trim() ? flags.body : DEFAULT_REPLY_BODY_TEXT,
28441
+ from,
28442
+ to: payeeTo,
28443
+ subject: DEFAULT_SUBJECT,
28444
+ body_text: flags.body?.trim() ? flags.body : DEFAULT_BODY_TEXT,
28397
28445
  attachments: [interactionAttachment(built)],
28398
- ...flags.from !== void 0 ? { from: flags.from } : {},
28399
28446
  ...flags.wait !== void 0 ? { wait: flags.wait } : {}
28400
28447
  },
28401
28448
  client: apiClient.client,
28402
- path: { id: flags["in-reply-to"] },
28403
28449
  responseStyle: "fields"
28404
28450
  });
28405
28451
  if (result.error) {
@@ -28413,11 +28459,17 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28413
28459
  return;
28414
28460
  }
28415
28461
  const sent = result.data?.data ?? null;
28462
+ const replayed = sent?.idempotent_replay === true;
28463
+ if (replayed) writeIdempotentReplayBannerIfReplay(sent, { write: (chunk) => {
28464
+ process.stderr.write(chunk);
28465
+ } });
28416
28466
  if (flags.json) this.log(JSON.stringify({
28417
28467
  interaction: built.envelope,
28418
- sent
28468
+ sent,
28469
+ idempotent_replay: replayed
28419
28470
  }, null, 2));
28420
28471
  else this.log(JSON.stringify(sent, null, 2));
28472
+ if (replayed) process.exitCode = 1;
28421
28473
  });
28422
28474
  }
28423
28475
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
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,