primcli 1.63.0 → 1.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1036,7 +1036,13 @@ It gives the session an address once, named `<runtime>-<repository>`, using the
1036
1036
  saved member login, and only re-verifies on resume; a session whose agent was
1037
1037
  disconnected or removed never gets a second address. The SessionEnd hook runs
1038
1038
  `primitive agent session-end`, which disconnects only agents that
1039
- `session-register` created. The end is recorded first, so a registration still
1039
+ `session-register` created, and only while the session's profile still holds
1040
+ that exact credential: an agent later connected into the same profile (for
1041
+ example with `primitive agent connect`) is left connected, and only the
1042
+ registered agent's own receive hooks are removed. Claude reports the same
1043
+ SessionEnd for a restart, a switch to another session and a real exit, so the
1044
+ hook waits five minutes before disconnecting, and a start of the same session
1045
+ within that time cancels the end. The end is recorded first, so a registration still
1040
1046
  running disconnects the agent it creates; when the local credential is gone it
1041
1047
  revokes by address with the member login, and an unconfirmed disconnect is
1042
1048
  reported as `disconnect_pending` and retried by `machine doctor --fix`. Both always exit 0 and finish slow work in the
@@ -1,4 +1,4 @@
1
- import { b as __exportAll } from "./repeats-x4QM2yoG-DTpqXyRo.js";
1
+ import { S as __exportAll, _ as WEBHOOK_EVENT_TYPES, g as SENT_EMAIL_EVENT_TYPES } from "./repeats-BL7ZC-_6-Dzangtkf.js";
2
2
  import { C as resolveCliAuth, F as agentProfilesDirectory, G as createClient, H as requireDefaultLoginProfile, K as createConfig, N as agentProfileDirectory, R as loadConnectedAgentProfile, S as normalizeApiBaseUrl, T as saveSignupCredentials, W as PrimitiveApiClient, _ as deleteCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, i as loadCliConfig, j as AGENT_PROFILE_ENV, m as cliAccessTokenExpiresAt, p as acquireCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "./cli-config-DMszFbGn.js";
3
3
  import { h as writeMailJson, m as withMailLock, p as syncMailDirectory, u as privateMailPermissions } from "./shared-mail-files-MwlVE90c.js";
4
4
  import { Command, Errors, Flags } from "@oclif/core";
@@ -1549,8 +1549,12 @@ const updateRoute = (options) => (options.client ?? client).patch({
1549
1549
  /**
1550
1550
  * List webhook deliveries
1551
1551
  *
1552
- * Returns a paginated list of webhook delivery attempts. Each delivery
1553
- * includes a nested `email` object with sender, recipient, and subject.
1552
+ * Returns a paginated list of webhook delivery attempts, newest first.
1553
+ * A delivery of an inbound email includes a nested `email` object with
1554
+ * sender, recipient, and subject. A delivery of an opt-in
1555
+ * `sent_email.*` event has `email_id` and `email` set to null and
1556
+ * `sent_email_id` set to the send it is about. Filter by `event_type`
1557
+ * and `sent_email_id` to find the deliveries of one send.
1554
1558
  *
1555
1559
  */
1556
1560
  const listDeliveries = (options) => (options?.client ?? client).get({
@@ -1564,11 +1568,15 @@ const listDeliveries = (options) => (options?.client ?? client).get({
1564
1568
  /**
1565
1569
  * Replay a webhook delivery
1566
1570
  *
1567
- * Re-sends the stored webhook payload from a previous delivery attempt.
1568
- * If the original endpoint is still active, it is targeted. If the
1569
- * original endpoint was deleted, the oldest active endpoint is used.
1570
- * Deactivated endpoints cannot be replayed to. Rate limited per-org,
1571
- * sharing an org-wide budget with email replays.
1571
+ * Re-sends a previous delivery to its original endpoint. If that
1572
+ * endpoint was deleted or deactivated, the replay is rejected.
1573
+ * Supports inbound email deliveries and `sent_email.*` deliveries.
1574
+ * An inbound email delivery is replayed with its original stored
1575
+ * payload and event id. A `sent_email.*` delivery is replayed with the
1576
+ * same event id but a payload rebuilt from the send as it is now; it
1577
+ * is rejected if the send was deleted or the endpoint is no longer an
1578
+ * active http endpoint. Rate limited per-org, sharing an org-wide
1579
+ * budget with email replays.
1572
1580
  *
1573
1581
  */
1574
1582
  const replayDelivery = (options) => (options.client ?? client).post({
@@ -3701,6 +3709,79 @@ function ensureSearchReportsAutomated(apiClient) {
3701
3709
  return probe;
3702
3710
  }
3703
3711
  //#endregion
3712
+ //#region src/oclif/endpoint-event-types.ts
3713
+ const EVENT_TYPES_FLAG_OPERATIONS = /* @__PURE__ */ new Set(["createEndpoint", "updateEndpoint"]);
3714
+ const EVENT_TYPES_FLAG_DESCRIPTION = "Event types this endpoint subscribes to, comma separated or repeated (sets rules.event_types). Omitting the subscription means every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them; sent_email.* expands to all four. Keep email.received in the list if the endpoint also handles inbound mail. On update, the endpoint's other rules are kept.";
3715
+ const SENT_EMAIL_WILDCARD = "sent_email.*";
3716
+ const KNOWN_EVENT_TYPES = new Set(WEBHOOK_EVENT_TYPES);
3717
+ var EventTypesFlagError = class extends Error {};
3718
+ /**
3719
+ * Turn the raw `--event-types` values into the list to store: split on
3720
+ * commas, trim, expand `sent_email.*`, and drop duplicates while keeping the
3721
+ * order the caller gave.
3722
+ */
3723
+ function parseEventTypesFlag(values) {
3724
+ const out = [];
3725
+ const seen = /* @__PURE__ */ new Set();
3726
+ for (const value of values) for (const raw of value.split(",")) {
3727
+ const name = raw.trim();
3728
+ if (name === "") continue;
3729
+ const names = name === SENT_EMAIL_WILDCARD ? [...SENT_EMAIL_EVENT_TYPES] : [name];
3730
+ for (const entry of names) {
3731
+ if (seen.has(entry)) continue;
3732
+ seen.add(entry);
3733
+ out.push(entry);
3734
+ }
3735
+ }
3736
+ if (out.length === 0) throw new EventTypesFlagError("--event-types needs at least one event type. To receive every event type except the opt-in sent_email.* events, omit the subscription instead.");
3737
+ if (out.length > 50) throw new EventTypesFlagError(`--event-types accepts at most 50 event types (got ${out.length}).`);
3738
+ return out;
3739
+ }
3740
+ /** The names in `eventTypes` this CLI does not recognise. */
3741
+ function unknownEventTypes(eventTypes) {
3742
+ return eventTypes.filter((name) => !KNOWN_EVENT_TYPES.has(name));
3743
+ }
3744
+ function unknownEventTypesWarning(unknown) {
3745
+ return `Warning: ${unknown.join(", ")} ${unknown.length === 1 ? "is" : "are"} not an event type this CLI knows. Subscriptions match exact names, so a misspelled name receives nothing. Continuing; newer event types may not be known to this CLI yet.\n`;
3746
+ }
3747
+ function isPlainObject(value) {
3748
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3749
+ }
3750
+ /**
3751
+ * The rules an update starts from: the endpoint's current rules, read from
3752
+ * the endpoint list. Throws when the endpoint cannot be found, so an update
3753
+ * never replaces rules it could not read.
3754
+ */
3755
+ async function currentEndpointRules(endpointId, listEndpoints) {
3756
+ const response = await listEndpoints();
3757
+ if (response.error) throw new EventTypesFlagError("Could not read the endpoint's current rules to keep them. Retry, or pass the full rules with --raw-body.");
3758
+ const row = (response.data?.data ?? []).find((endpoint) => endpoint.id === endpointId);
3759
+ if (!row) throw new EventTypesFlagError(`No endpoint ${endpointId} is visible to this credential, so its rules cannot be updated.`);
3760
+ return isPlainObject(row.rules) ? row.rules : {};
3761
+ }
3762
+ /**
3763
+ * Put `eventTypes` in the request body's `rules.event_types`, on top of
3764
+ * `baseRules` (rules the caller passed in the body win over `baseRules`).
3765
+ */
3766
+ function withEventTypes(body, eventTypes, baseRules = {}) {
3767
+ if (body !== void 0 && !isPlainObject(body)) throw new EventTypesFlagError("--raw-body must be a JSON object when also passing --event-types.");
3768
+ const current = body ?? {};
3769
+ if (current.rules !== void 0 && !isPlainObject(current.rules)) throw new EventTypesFlagError("rules in --raw-body must be a JSON object when also passing --event-types.");
3770
+ const explicitRules = current.rules ?? {};
3771
+ return {
3772
+ ...current,
3773
+ rules: {
3774
+ ...baseRules,
3775
+ ...explicitRules,
3776
+ event_types: [...eventTypes]
3777
+ }
3778
+ };
3779
+ }
3780
+ /** True when the caller already passed `rules` in the request body. */
3781
+ function bodyHasRules(body) {
3782
+ return isPlainObject(body) && body.rules !== void 0;
3783
+ }
3784
+ //#endregion
3704
3785
  //#region src/oclif/endpoints-test-redirect.ts
3705
3786
  async function detectFunctionEndpoint(endpointId, listEndpoints) {
3706
3787
  let response;
@@ -5144,7 +5225,7 @@ function savedConnectedProfiles(configDir, exclude) {
5144
5225
  */
5145
5226
  async function otherProfiles(configDir, session, current) {
5146
5227
  if (session) try {
5147
- const { connectedProfilesForSession } = await import("./machine-session-NjXIXTRX.js").then((n) => n.s);
5228
+ const { connectedProfilesForSession } = await import("./machine-session-B3JznlvO.js").then((n) => n.s);
5148
5229
  return {
5149
5230
  inSession: true,
5150
5231
  profiles: connectedProfilesForSession(configDir, session, current)
@@ -5751,7 +5832,18 @@ function buildFlags(operation) {
5751
5832
  flags[name] = bodyFieldFlag(field, aliasesForOperation?.[field.name]);
5752
5833
  bodyFieldFlagToProperty.set(name, field.name);
5753
5834
  }
5835
+ const extraFlags = OPERATION_EXTRA_BODY_FLAGS[operation.sdkName] ?? {};
5836
+ for (const [name, extra] of Object.entries(extraFlags)) {
5837
+ if (RESERVED_FLAG_NAMES.has(name)) continue;
5838
+ if (flags[name] !== void 0) continue;
5839
+ flags[name] = Flags.boolean({ description: extra.description });
5840
+ bodyFieldFlagToProperty.set(name, extra.property);
5841
+ }
5754
5842
  }
5843
+ if (EVENT_TYPES_FLAG_OPERATIONS.has(operation.sdkName)) flags["event-types"] = Flags.string({
5844
+ description: EVENT_TYPES_FLAG_DESCRIPTION,
5845
+ multiple: true
5846
+ });
5755
5847
  if (operation.binaryResponse) flags.output = Flags.string({ description: "Write binary response bytes to a file" });
5756
5848
  if (ATTACHMENT_WAIT_OPERATIONS.has(operation.sdkName)) flags.wait = Flags.boolean({
5757
5849
  allowNo: true,
@@ -5772,6 +5864,18 @@ function collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty) {
5772
5864
  }
5773
5865
  return result;
5774
5866
  }
5867
+ async function applyEventTypesFlag(params) {
5868
+ try {
5869
+ const eventTypes = parseEventTypesFlag(params.values);
5870
+ const unknown = unknownEventTypes(eventTypes);
5871
+ if (unknown.length > 0 && !params.quiet) process.stderr.write(unknownEventTypesWarning(unknown));
5872
+ const baseRules = params.endpointId !== void 0 && !bodyHasRules(params.body) ? await currentEndpointRules(params.endpointId, params.listEndpoints) : {};
5873
+ return withEventTypes(params.body, eventTypes, baseRules);
5874
+ } catch (error) {
5875
+ if (error instanceof EventTypesFlagError) throw new Errors.CLIError(error.message);
5876
+ throw error;
5877
+ }
5878
+ }
5775
5879
  function collectValues(parameters, flags) {
5776
5880
  const values = {};
5777
5881
  for (const parameter of parameters) {
@@ -5821,6 +5925,17 @@ const OPERATION_HINTS = {
5821
5925
  verifyAgentSignup: "Tip: pass --verification-code <code> (or --code; both work). The response carries OAuth tokens but not your assigned inbox domain; run `primitive domains list` (or `primitive whoami`) after success to see the managed *.primitive.email address that routes to this account."
5822
5926
  };
5823
5927
  const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
5928
+ const OPERATION_EXTRA_BODY_FLAGS = { addDomain: { "mail-relay": {
5929
+ property: "mail_relay",
5930
+ description: "Keep your existing mailbox provider: the domain's MX points at a Primitive mail relay, which forwards mail to your mailboxes and stores a copy in Primitive. Requires mail relay to be enabled for your account. Google Workspace only for now."
5931
+ } } };
5932
+ const MAIL_RELAY_NOT_ENABLED_MESSAGE = "Mail relay is not enabled for this account, so the domain was not added. Retry without --mail-relay to point the domain's MX at Primitive directly, or ask Primitive to enable mail relay for your account.";
5933
+ const MAIL_RELAY_UNAVAILABLE_MESSAGE = "No mail relay is available on this API server right now, so the domain was not added. Retry later, or retry without --mail-relay to point the domain's MX at Primitive directly.";
5934
+ const OPERATION_ERROR_HINTS = { addDomain: (code, body) => {
5935
+ if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
5936
+ const mailRelay = body !== null && typeof body === "object" && body.mail_relay === true;
5937
+ if (code === "feature_disabled" && mailRelay) return MAIL_RELAY_NOT_ENABLED_MESSAGE;
5938
+ } };
5824
5939
  const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, apiBaseUrl, writeStderr }) => {
5825
5940
  const data = envelope?.data;
5826
5941
  if (!data?.access_token || !data?.refresh_token) return;
@@ -5876,6 +5991,16 @@ function createOperationCommand(operation) {
5876
5991
  }
5877
5992
  } else body = explicit;
5878
5993
  }
5994
+ if (EVENT_TYPES_FLAG_OPERATIONS.has(operation.sdkName) && Array.isArray(parsedFlags["event-types"])) body = await applyEventTypesFlag({
5995
+ body,
5996
+ values: parsedFlags["event-types"],
5997
+ endpointId: operation.sdkName === "updateEndpoint" && typeof parsedFlags.id === "string" ? parsedFlags.id : void 0,
5998
+ listEndpoints: () => listEndpoints({
5999
+ client: apiClient.client,
6000
+ responseStyle: "fields"
6001
+ }),
6002
+ quiet: parsedFlags.json === true
6003
+ });
5879
6004
  if (operation.bodyRequired && body === void 0) throw new Errors.CLIError(`Operation ${operation.operationId} requires a body. Pass each field as a --flag (see --help) or supply JSON via --raw-body / --body-file.`);
5880
6005
  const operationFn = sdk_gen_exports[operation.sdkName];
5881
6006
  const query = collectValues(operation.queryParams, parsedFlags);
@@ -5899,6 +6024,8 @@ function createOperationCommand(operation) {
5899
6024
  if (result.error) {
5900
6025
  const errorPayload = extractErrorPayload(result.error);
5901
6026
  writeErrorWithHints(errorPayload);
6027
+ const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body);
6028
+ if (operationErrorHint) process.stderr.write(`${operationErrorHint}\n`);
5902
6029
  if (replyStateSurface && isAwaitingRejectedError(errorPayload)) process.stderr.write(`${awaitingRejectedError(replyStateSurface).message}\n`);
5903
6030
  if (automatedSurface && isAutomatedRejectedError(errorPayload)) process.stderr.write(`${automatedRejectedError(automatedSurface).message}\n`);
5904
6031
  surfaceUnauthorizedHint({
@@ -6002,7 +6129,7 @@ function createOperationCommand(operation) {
6002
6129
  return OperationCommand;
6003
6130
  }
6004
6131
  const EMPTY_RESULT_HINTS = {
6005
- listDeliveries: "(no results) No webhook deliveries logged yet. If you have an endpoint configured but expected to see test fires here: test deliveries from `primitive endpoints test` are NOT logged in this list, they're synchronous and visible only in the test-endpoint command's response. Real deliveries are logged when an inbound `email.received` event fans out to your endpoints. If you have no endpoints, run `primitive endpoints list` to check.",
6132
+ listDeliveries: "(no results) No webhook deliveries logged yet. If you have an endpoint configured but expected to see test fires here: test deliveries from `primitive endpoints test` are NOT logged in this list, they're synchronous and visible only in the test-endpoint command's response. Real deliveries are logged when an event fans out to your endpoints: an inbound `email.received`, or a `sent_email.*` event for an endpoint that lists it in rules.event_types (they arrive within a few minutes of the result). If you have no endpoints, run `primitive endpoints list` to check.",
6006
6133
  listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints create --url <your-url>`.",
6007
6134
  listEmails: "(no results) No inbound emails received yet on this account. Send one to a verified domain to populate this list. For a compact view, prefer `primitive emails latest`.",
6008
6135
  listDomains: "(no results) No domains on this account. Add one with `primitive domains add --domain <yourdomain.example>`.",