primcli 1.63.0 → 1.65.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 +24 -1
- package/dist/{api-command-CDjvGDQS.js → api-command-0u5Qd_a-.js} +220 -19
- package/dist/{machine-session-NjXIXTRX.js → machine-session-Bmv7ZOQh.js} +826 -345
- package/dist/{notify-session-7JzG7GNX.js → notify-session-BaS84f3y.js} +2 -2
- package/dist/oclif/auto-signal-worker.js +2 -2
- package/dist/oclif/auto-signals.js +1 -1
- package/dist/oclif/index.js +377 -68
- package/dist/oclif/receiver-heal.js +1 -1
- package/dist/{repeats-x4QM2yoG-DTpqXyRo.js → repeats-BL7ZC-_6-Dzangtkf.js} +7654 -88
- package/dist/{scoped-chat-BvkUTggT.js → scoped-chat-BW041zEY.js} +1 -1
- package/dist/{signal-command-50rw5Sh-.js → signal-command-CwIFkT9e.js} +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -324,6 +324,23 @@ still goes out, and different replies to the same email are separate sends. To
|
|
|
324
324
|
retry an uncertain send after the window, pass the key it reported with
|
|
325
325
|
`--idempotency-key`.
|
|
326
326
|
|
|
327
|
+
When a send is answered with an earlier one, nothing goes out and the stderr
|
|
328
|
+
notice says which send answered it and why:
|
|
329
|
+
|
|
330
|
+
| Why | Notice |
|
|
331
|
+
|---|---|
|
|
332
|
+
| The same content inside the five-minute window, with no key of your own | `Not sent: identical to <id> sent 42s ago (status delivered). Pass --idempotency-key with a new key to send again.` |
|
|
333
|
+
| A key you passed that was already used | `Not sent: idempotency key <key> was already used for <id> sent 3h ago (status delivered). A key you pass never expires. Use a different key only for a different message.` |
|
|
334
|
+
| A Function sending again for the email or event that invoked it | `Not sent: this Function already sent this message as <id> sent 2m ago (status delivered), for the same email or event that invoked it. A Function that runs again for one trigger sends once.` |
|
|
335
|
+
| A reply with no key to an email that already has a reply | `Not sent: the email already has a reply, <id> sent 5m ago (status delivered). Pass an idempotency key to send another reply.` |
|
|
336
|
+
|
|
337
|
+
The notice goes to stderr only, so stdout stays parseable. With `--json` it is
|
|
338
|
+
the envelope's `outcome_message`, and `sent.idempotency` carries the same facts
|
|
339
|
+
as fields (`key_source`, `original_sent_email_id`, `original_created_at`,
|
|
340
|
+
`window_seconds`). The outcome is `already_sent` and the exit code is 0 in every
|
|
341
|
+
case. An API response without the `idempotency` object gets the general notice,
|
|
342
|
+
`Already sent: this exact message went out earlier (...). Nothing new was sent.`
|
|
343
|
+
|
|
327
344
|
Without `--json`, `send` and `reply` keep printing the send record on stdout exactly
|
|
328
345
|
as before and add a one-line stderr summary such as `Reply sent (queued for
|
|
329
346
|
delivery, id X). Do not resend.` Before sending, `primitive reply` (and
|
|
@@ -1036,7 +1053,13 @@ It gives the session an address once, named `<runtime>-<repository>`, using the
|
|
|
1036
1053
|
saved member login, and only re-verifies on resume; a session whose agent was
|
|
1037
1054
|
disconnected or removed never gets a second address. The SessionEnd hook runs
|
|
1038
1055
|
`primitive agent session-end`, which disconnects only agents that
|
|
1039
|
-
`session-register` created
|
|
1056
|
+
`session-register` created, and only while the session's profile still holds
|
|
1057
|
+
that exact credential: an agent later connected into the same profile (for
|
|
1058
|
+
example with `primitive agent connect`) is left connected, and only the
|
|
1059
|
+
registered agent's own receive hooks are removed. Claude reports the same
|
|
1060
|
+
SessionEnd for a restart, a switch to another session and a real exit, so the
|
|
1061
|
+
hook waits five minutes before disconnecting, and a start of the same session
|
|
1062
|
+
within that time cancels the end. The end is recorded first, so a registration still
|
|
1040
1063
|
running disconnects the agent it creates; when the local credential is gone it
|
|
1041
1064
|
revokes by address with the member login, and an unconfirmed disconnect is
|
|
1042
1065
|
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 {
|
|
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
|
|
1553
|
-
* includes a nested `email` object with
|
|
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
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
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;
|
|
@@ -3813,22 +3894,93 @@ function withFlagSuggestion(error, valid) {
|
|
|
3813
3894
|
//#region src/oclif/idempotent-replay-banner.ts
|
|
3814
3895
|
/**
|
|
3815
3896
|
* If `data` carries `idempotent_replay: true`, write a stderr notice
|
|
3816
|
-
* that
|
|
3817
|
-
* Otherwise no-op.
|
|
3818
|
-
*
|
|
3819
|
-
* The notice deliberately gives no advice on how to force a fresh
|
|
3820
|
-
* copy. Whoever sees a replay has already sent this message; telling
|
|
3821
|
-
* them to vary the content is an instruction to double-send.
|
|
3897
|
+
* that nothing new was sent and why. Otherwise no-op.
|
|
3822
3898
|
*/
|
|
3823
3899
|
function writeIdempotentReplayBannerIfReplay(data, options) {
|
|
3824
3900
|
if (!isReplay(data)) return;
|
|
3825
|
-
|
|
3901
|
+
const { write, ...notice } = options;
|
|
3902
|
+
write(`${formatAlreadySentNotice(data, notice)}\n`);
|
|
3826
3903
|
}
|
|
3827
3904
|
function isReplay(data) {
|
|
3828
3905
|
if (data === null || typeof data !== "object") return false;
|
|
3829
3906
|
return data.idempotent_replay === true;
|
|
3830
3907
|
}
|
|
3831
|
-
function
|
|
3908
|
+
function nonEmptyString(value) {
|
|
3909
|
+
return typeof value === "string" && value ? value : null;
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3912
|
+
* Read the API's `idempotency` object. Null when it is absent or names
|
|
3913
|
+
* a key source this CLI does not know, so the general notice is used
|
|
3914
|
+
* and nothing is claimed about a case it cannot describe.
|
|
3915
|
+
*/
|
|
3916
|
+
function readReplayDetail(sent, keyOrigin) {
|
|
3917
|
+
const raw = sent.idempotency;
|
|
3918
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
3919
|
+
const object = raw;
|
|
3920
|
+
const source = object.key_source;
|
|
3921
|
+
let kind;
|
|
3922
|
+
if (sent.dedup_reason === "parent_already_replied") kind = "parent_already_replied";
|
|
3923
|
+
else if (source === "auto_content" || source === "function_trigger") kind = source;
|
|
3924
|
+
else if (source === "explicit") kind = keyOrigin === "cli_derived" ? "auto_content" : "explicit";
|
|
3925
|
+
else return null;
|
|
3926
|
+
return {
|
|
3927
|
+
kind,
|
|
3928
|
+
originalId: nonEmptyString(object.original_sent_email_id) ?? nonEmptyString(sent.id),
|
|
3929
|
+
originalCreatedAt: nonEmptyString(object.original_created_at)
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
/** "12s ago", "4m ago", "3h ago", "9d ago"; null when the time is unusable. */
|
|
3933
|
+
function formatReplayAge(createdAt, now) {
|
|
3934
|
+
if (createdAt === null) return null;
|
|
3935
|
+
const created = Date.parse(createdAt);
|
|
3936
|
+
if (Number.isNaN(created)) return null;
|
|
3937
|
+
const seconds = Math.max(0, Math.round((now - created) / 1e3));
|
|
3938
|
+
if (seconds < 120) return `${seconds}s ago`;
|
|
3939
|
+
const minutes = Math.round(seconds / 60);
|
|
3940
|
+
if (minutes < 120) return `${minutes}m ago`;
|
|
3941
|
+
const hours = Math.round(minutes / 60);
|
|
3942
|
+
if (hours < 48) return `${hours}h ago`;
|
|
3943
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
3944
|
+
}
|
|
3945
|
+
function statusSuffix(sent) {
|
|
3946
|
+
const status = nonEmptyString(sent.status);
|
|
3947
|
+
const deliveryStatus = nonEmptyString(sent.delivery_status);
|
|
3948
|
+
const details = [];
|
|
3949
|
+
if (status) details.push(`status ${status}`);
|
|
3950
|
+
if (deliveryStatus && deliveryStatus !== status) details.push(`delivery status ${deliveryStatus}`);
|
|
3951
|
+
return details.length > 0 ? ` (${details.join(", ")})` : "";
|
|
3952
|
+
}
|
|
3953
|
+
function formatSpecificNotice(sent, detail, options) {
|
|
3954
|
+
const age = formatReplayAge(detail.originalCreatedAt, options.now ?? Date.now());
|
|
3955
|
+
const when = age ? ` sent ${age}` : "";
|
|
3956
|
+
const id = detail.originalId ?? "an earlier send";
|
|
3957
|
+
const state = statusSuffix(sent);
|
|
3958
|
+
const advise = options.advise !== false;
|
|
3959
|
+
const keyHint = options.keyHint ?? "an idempotency key";
|
|
3960
|
+
switch (detail.kind) {
|
|
3961
|
+
case "auto_content": return `Not sent: identical to ${id}${when}${state}.${advise ? ` Pass ${keyHint} to send again.` : ""}`;
|
|
3962
|
+
case "explicit": {
|
|
3963
|
+
const key = nonEmptyString(sent.client_idempotency_key);
|
|
3964
|
+
return `Not sent: ${key ? `idempotency key ${key}` : "this idempotency key"} was already used for ${id}${when}${state}. A key you pass never expires.${advise ? " Use a different key only for a different message." : ""}`;
|
|
3965
|
+
}
|
|
3966
|
+
case "function_trigger": return `Not sent: this Function already sent this message as ${id}${when}${state}, for the same email or event that invoked it. A Function that runs again for one trigger sends once.`;
|
|
3967
|
+
case "parent_already_replied": return `Not sent: the email already has a reply, ${id}${when}${state}.${advise ? ` Pass ${keyHint} to send another reply.` : ""}`;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
/**
|
|
3971
|
+
* The notice for a send that was answered with an existing send. Worded
|
|
3972
|
+
* from the API's `idempotency` object when the response carries one;
|
|
3973
|
+
* the general "already sent" notice otherwise.
|
|
3974
|
+
*
|
|
3975
|
+
* The advice never says to change the message. Whoever sees this has a
|
|
3976
|
+
* send on record already, and varying the content to get past it is how
|
|
3977
|
+
* a retry becomes a double send. Sending the same thing again on
|
|
3978
|
+
* purpose is a different request, and an idempotency key is how the API
|
|
3979
|
+
* is told so.
|
|
3980
|
+
*/
|
|
3981
|
+
function formatAlreadySentNotice(sent, options = {}) {
|
|
3982
|
+
const detail = readReplayDetail(sent, options.keyOrigin ?? "caller");
|
|
3983
|
+
if (detail) return formatSpecificNotice(sent, detail, options);
|
|
3832
3984
|
const details = [];
|
|
3833
3985
|
if (typeof sent.id === "string" && sent.id) details.push(`sent id ${sent.id}`);
|
|
3834
3986
|
const status = typeof sent.status === "string" && sent.status ? sent.status : null;
|
|
@@ -4297,7 +4449,10 @@ function reportSendCommandResult(params) {
|
|
|
4297
4449
|
errorPayload = { message: "The API returned no send record." };
|
|
4298
4450
|
} else {
|
|
4299
4451
|
outcome = successfulSendOutcome(sent);
|
|
4300
|
-
outcomeMessage = outcome === "not_sent" || outcome === "uncertain" ? formatSendRecordFailureSummary(params.noun, outcome, sent) : outcome === "already_sent" ? formatAlreadySentNotice(sent
|
|
4452
|
+
outcomeMessage = outcome === "not_sent" || outcome === "uncertain" ? formatSendRecordFailureSummary(params.noun, outcome, sent) : outcome === "already_sent" ? formatAlreadySentNotice(sent, {
|
|
4453
|
+
keyOrigin: params.idempotencyKeyOrigin ?? "caller",
|
|
4454
|
+
keyHint: "--idempotency-key with a new key"
|
|
4455
|
+
}) : formatSentSummary(params.noun, sent);
|
|
4301
4456
|
}
|
|
4302
4457
|
if (!params.json) params.writeStderr(`${outcomeMessage}\n`);
|
|
4303
4458
|
}
|
|
@@ -5144,7 +5299,7 @@ function savedConnectedProfiles(configDir, exclude) {
|
|
|
5144
5299
|
*/
|
|
5145
5300
|
async function otherProfiles(configDir, session, current) {
|
|
5146
5301
|
if (session) try {
|
|
5147
|
-
const { connectedProfilesForSession } = await import("./machine-session-
|
|
5302
|
+
const { connectedProfilesForSession } = await import("./machine-session-Bmv7ZOQh.js").then((n) => n.s);
|
|
5148
5303
|
return {
|
|
5149
5304
|
inSession: true,
|
|
5150
5305
|
profiles: connectedProfilesForSession(configDir, session, current)
|
|
@@ -5751,7 +5906,18 @@ function buildFlags(operation) {
|
|
|
5751
5906
|
flags[name] = bodyFieldFlag(field, aliasesForOperation?.[field.name]);
|
|
5752
5907
|
bodyFieldFlagToProperty.set(name, field.name);
|
|
5753
5908
|
}
|
|
5909
|
+
const extraFlags = OPERATION_EXTRA_BODY_FLAGS[operation.sdkName] ?? {};
|
|
5910
|
+
for (const [name, extra] of Object.entries(extraFlags)) {
|
|
5911
|
+
if (RESERVED_FLAG_NAMES.has(name)) continue;
|
|
5912
|
+
if (flags[name] !== void 0) continue;
|
|
5913
|
+
flags[name] = Flags.boolean({ description: extra.description });
|
|
5914
|
+
bodyFieldFlagToProperty.set(name, extra.property);
|
|
5915
|
+
}
|
|
5754
5916
|
}
|
|
5917
|
+
if (EVENT_TYPES_FLAG_OPERATIONS.has(operation.sdkName)) flags["event-types"] = Flags.string({
|
|
5918
|
+
description: EVENT_TYPES_FLAG_DESCRIPTION,
|
|
5919
|
+
multiple: true
|
|
5920
|
+
});
|
|
5755
5921
|
if (operation.binaryResponse) flags.output = Flags.string({ description: "Write binary response bytes to a file" });
|
|
5756
5922
|
if (ATTACHMENT_WAIT_OPERATIONS.has(operation.sdkName)) flags.wait = Flags.boolean({
|
|
5757
5923
|
allowNo: true,
|
|
@@ -5772,6 +5938,18 @@ function collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty) {
|
|
|
5772
5938
|
}
|
|
5773
5939
|
return result;
|
|
5774
5940
|
}
|
|
5941
|
+
async function applyEventTypesFlag(params) {
|
|
5942
|
+
try {
|
|
5943
|
+
const eventTypes = parseEventTypesFlag(params.values);
|
|
5944
|
+
const unknown = unknownEventTypes(eventTypes);
|
|
5945
|
+
if (unknown.length > 0 && !params.quiet) process.stderr.write(unknownEventTypesWarning(unknown));
|
|
5946
|
+
const baseRules = params.endpointId !== void 0 && !bodyHasRules(params.body) ? await currentEndpointRules(params.endpointId, params.listEndpoints) : {};
|
|
5947
|
+
return withEventTypes(params.body, eventTypes, baseRules);
|
|
5948
|
+
} catch (error) {
|
|
5949
|
+
if (error instanceof EventTypesFlagError) throw new Errors.CLIError(error.message);
|
|
5950
|
+
throw error;
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5775
5953
|
function collectValues(parameters, flags) {
|
|
5776
5954
|
const values = {};
|
|
5777
5955
|
for (const parameter of parameters) {
|
|
@@ -5821,6 +5999,17 @@ const OPERATION_HINTS = {
|
|
|
5821
5999
|
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
6000
|
};
|
|
5823
6001
|
const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
|
|
6002
|
+
const OPERATION_EXTRA_BODY_FLAGS = { addDomain: { "mail-relay": {
|
|
6003
|
+
property: "mail_relay",
|
|
6004
|
+
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."
|
|
6005
|
+
} } };
|
|
6006
|
+
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.";
|
|
6007
|
+
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.";
|
|
6008
|
+
const OPERATION_ERROR_HINTS = { addDomain: (code, body) => {
|
|
6009
|
+
if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
|
|
6010
|
+
const mailRelay = body !== null && typeof body === "object" && body.mail_relay === true;
|
|
6011
|
+
if (code === "feature_disabled" && mailRelay) return MAIL_RELAY_NOT_ENABLED_MESSAGE;
|
|
6012
|
+
} };
|
|
5824
6013
|
const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, apiBaseUrl, writeStderr }) => {
|
|
5825
6014
|
const data = envelope?.data;
|
|
5826
6015
|
if (!data?.access_token || !data?.refresh_token) return;
|
|
@@ -5876,6 +6065,16 @@ function createOperationCommand(operation) {
|
|
|
5876
6065
|
}
|
|
5877
6066
|
} else body = explicit;
|
|
5878
6067
|
}
|
|
6068
|
+
if (EVENT_TYPES_FLAG_OPERATIONS.has(operation.sdkName) && Array.isArray(parsedFlags["event-types"])) body = await applyEventTypesFlag({
|
|
6069
|
+
body,
|
|
6070
|
+
values: parsedFlags["event-types"],
|
|
6071
|
+
endpointId: operation.sdkName === "updateEndpoint" && typeof parsedFlags.id === "string" ? parsedFlags.id : void 0,
|
|
6072
|
+
listEndpoints: () => listEndpoints({
|
|
6073
|
+
client: apiClient.client,
|
|
6074
|
+
responseStyle: "fields"
|
|
6075
|
+
}),
|
|
6076
|
+
quiet: parsedFlags.json === true
|
|
6077
|
+
});
|
|
5879
6078
|
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
6079
|
const operationFn = sdk_gen_exports[operation.sdkName];
|
|
5881
6080
|
const query = collectValues(operation.queryParams, parsedFlags);
|
|
@@ -5899,6 +6098,8 @@ function createOperationCommand(operation) {
|
|
|
5899
6098
|
if (result.error) {
|
|
5900
6099
|
const errorPayload = extractErrorPayload(result.error);
|
|
5901
6100
|
writeErrorWithHints(errorPayload);
|
|
6101
|
+
const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body);
|
|
6102
|
+
if (operationErrorHint) process.stderr.write(`${operationErrorHint}\n`);
|
|
5902
6103
|
if (replyStateSurface && isAwaitingRejectedError(errorPayload)) process.stderr.write(`${awaitingRejectedError(replyStateSurface).message}\n`);
|
|
5903
6104
|
if (automatedSurface && isAutomatedRejectedError(errorPayload)) process.stderr.write(`${automatedRejectedError(automatedSurface).message}\n`);
|
|
5904
6105
|
surfaceUnauthorizedHint({
|
|
@@ -6002,7 +6203,7 @@ function createOperationCommand(operation) {
|
|
|
6002
6203
|
return OperationCommand;
|
|
6003
6204
|
}
|
|
6004
6205
|
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
|
|
6206
|
+
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
6207
|
listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints create --url <your-url>`.",
|
|
6007
6208
|
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
6209
|
listDomains: "(no results) No domains on this account. Add one with `primitive domains add --domain <yourdomain.example>`.",
|