primcli 1.13.0 → 1.13.2

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,15 +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.
102
+ # locally with your wallet key AND sends the signed interaction.json in-thread,
103
+ # so you skip the manual sign-then-send dance. --in-reply-to is the inbound
104
+ # challenge email you received; it is fetched to address the payment to the
105
+ # payee and thread it under the challenge, with From defaulting to the payer it
106
+ # was sent to. The message carries a short default note alongside the
107
+ # attachment; pass --body to customize it.
106
108
  primitive payments pay-email --challenge-file challenge.json \
107
109
  --in-reply-to <inbound-challenge-email-id> --wait
108
110
 
109
111
  # Advanced: sign only, without sending. Emits the portable interaction.json
110
- # 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`).
111
113
  primitive payments pay-email-step --challenge-file challenge.json > interaction.json
112
114
 
113
115
  # Inspect a challenge by id, or list your registered payout addresses.
@@ -122,7 +124,7 @@ primitive payments update-spend-policy --max-per-payment 5000000
122
124
 
123
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.
124
126
 
125
- 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:
126
128
 
127
129
  ```bash
128
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.0";
23868
- const CLI_VERSION_RANGE = "^1.13.0";
23867
+ const SDK_VERSION_RANGE = "^1.13.2";
23868
+ const CLI_VERSION_RANGE = "^1.13.2";
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,6 +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_SUBJECT = "x402 payment authorization";
28297
+ const DEFAULT_BODY_TEXT = "x402 payment authorization attached (interaction.json).";
28296
28298
  /** Build the `interaction.json` attachment from a signed payment step. */
28297
28299
  function interactionAttachment(built) {
28298
28300
  return {
@@ -28301,29 +28303,37 @@ function interactionAttachment(built) {
28301
28303
  content_base64: Buffer.from(built.json, "utf8").toString("base64")
28302
28304
  };
28303
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
+ }
28304
28314
  var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28305
- 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.
28306
28316
 
28307
28317
  Reads the email challenge the payee issued (the JSON from
28308
28318
  \`payments create-email-challenge\`), derives and signs the interaction-bound
28309
28319
  EIP-3009 authorization locally with your wallet key (read from
28310
- ${PRIVATE_KEY_ENV} by default), and replies in-thread to the challenge email
28311
- with the signed \`interaction.json\` attached as an \`application/json\` part.
28312
- 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.
28313
28322
 
28314
28323
  This is the recommended payer path. It replaces the two-step
28315
- \`pay-email-step\` + \`reply --attachment interaction.json\` dance: no manual
28324
+ \`pay-email-step\` + \`send --attachment interaction.json\` dance: no manual
28316
28325
  address or threading wrangling. Use \`pay-email-step\` only when you need the
28317
28326
  signed bytes as a portable artifact without sending.
28318
28327
 
28319
- --in-reply-to is the id of the inbound challenge email you received; the reply
28320
- endpoint derives the recipient (the payee) and threading headers from it, and
28321
- defaults the From to the inbound's recipient (you, the payer). Pass --from
28322
- 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), the
28330
+ From (you, the payer, from the inbound's recipient), and the In-Reply-To
28331
+ Message-Id used to thread the authorization under the challenge. Pass --from
28332
+ only to override the derived payer From.
28323
28333
 
28324
28334
  Provide the challenge inline with --challenge, from a file with
28325
28335
  --challenge-file, or piped on stdin.`;
28326
- static summary = "Sign an email x402 challenge and send it as an in-thread reply (one step)";
28336
+ static summary = "Sign an email x402 challenge and send the signed interaction.json in-thread (one step)";
28327
28337
  static examples = [
28328
28338
  "<%= config.bin %> payments pay-email --challenge-file challenge.json --in-reply-to <inbound-email-id>",
28329
28339
  "cat challenge.json | <%= config.bin %> payments pay-email --in-reply-to <inbound-email-id> --wait",
@@ -28352,11 +28362,12 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28352
28362
  exclusive: ["challenge"]
28353
28363
  }),
28354
28364
  "in-reply-to": Flags.string({
28355
- description: "Id of the inbound challenge email you received. The reply is threaded to it and addressed to the payee derived from it.",
28365
+ description: "Id of the inbound challenge email you received. It is fetched to derive the payee recipient, the payer From, and the Message-Id used to thread the authorization.",
28356
28366
  required: true
28357
28367
  }),
28358
28368
  from: Flags.string({ description: "Optional From header override. Defaults to the inbound's recipient (the payer the challenge was addressed to)." }),
28359
- 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." }),
28369
+ 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}".` }),
28370
+ 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." }),
28360
28371
  json: Flags.boolean({ description: "Print a JSON object with the signed interaction step and the send result." }),
28361
28372
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
28362
28373
  };
@@ -28389,14 +28400,51 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28389
28400
  process.exitCode = 1;
28390
28401
  return;
28391
28402
  }
28392
- const result = await replyToEmail({
28403
+ const inboundResult = await getEmail({
28404
+ client: apiClient.client,
28405
+ path: { id: flags["in-reply-to"] },
28406
+ responseStyle: "fields"
28407
+ });
28408
+ if (inboundResult.error) {
28409
+ const errorPayload = extractErrorPayload(inboundResult.error);
28410
+ writeErrorWithHints(errorPayload);
28411
+ surfaceUnauthorizedHint({
28412
+ ...authFailureContext,
28413
+ payload: errorPayload
28414
+ });
28415
+ process.exitCode = 1;
28416
+ return;
28417
+ }
28418
+ const inbound = emailDetailFromEnvelope(inboundResult.data);
28419
+ if (!inbound) {
28420
+ process.stderr.write(`Could not load inbound challenge email ${flags["in-reply-to"]}: the API returned no email.\n`);
28421
+ process.exitCode = 1;
28422
+ return;
28423
+ }
28424
+ const payeeTo = inbound.from_email;
28425
+ const derivedPayerFrom = inbound.to_email || inbound.recipient;
28426
+ const from = flags.from ?? derivedPayerFrom;
28427
+ if (!payeeTo) {
28428
+ process.stderr.write(`Inbound challenge email ${flags["in-reply-to"]} has no resolvable sender to address the payment to.\n`);
28429
+ process.exitCode = 1;
28430
+ return;
28431
+ }
28432
+ if (!from) {
28433
+ process.stderr.write(`Could not derive the payer From from inbound challenge email ${flags["in-reply-to"]}; pass --from explicitly.\n`);
28434
+ process.exitCode = 1;
28435
+ return;
28436
+ }
28437
+ const result = await sendEmail({
28393
28438
  body: {
28439
+ from,
28440
+ to: payeeTo,
28441
+ subject: DEFAULT_SUBJECT,
28442
+ body_text: flags.body?.trim() ? flags.body : DEFAULT_BODY_TEXT,
28394
28443
  attachments: [interactionAttachment(built)],
28395
- ...flags.from !== void 0 ? { from: flags.from } : {},
28444
+ ...inbound.message_id ? { in_reply_to: inbound.message_id } : {},
28396
28445
  ...flags.wait !== void 0 ? { wait: flags.wait } : {}
28397
28446
  },
28398
28447
  client: apiClient.client,
28399
- path: { id: flags["in-reply-to"] },
28400
28448
  responseStyle: "fields"
28401
28449
  });
28402
28450
  if (result.error) {
@@ -28410,11 +28458,17 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28410
28458
  return;
28411
28459
  }
28412
28460
  const sent = result.data?.data ?? null;
28461
+ const replayed = sent?.idempotent_replay === true;
28462
+ if (replayed) writeIdempotentReplayBannerIfReplay(sent, { write: (chunk) => {
28463
+ process.stderr.write(chunk);
28464
+ } });
28413
28465
  if (flags.json) this.log(JSON.stringify({
28414
28466
  interaction: built.envelope,
28415
- sent
28467
+ sent,
28468
+ idempotent_replay: replayed
28416
28469
  }, null, 2));
28417
28470
  else this.log(JSON.stringify(sent, null, 2));
28471
+ if (replayed) process.exitCode = 1;
28418
28472
  });
28419
28473
  }
28420
28474
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
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,