primcli 3.0.0-preview.13 → 3.0.0-preview.14

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.
@@ -1,12 +1,12 @@
1
- import { $ as readEmailInteraction, A as explainPendingMailNotFound, At as cliInvocation, Bt as formatAlreadySentNotice, C as promptLine$2, Ct as sendOutcomeExitCode, D as formatBrowserLoginInstructions, Dt as successfulSendOutcome, E as canOfferCopyHotkey, Et as serializeErrorPayload, F as latestOwnOutbound, Ft as loadSkillLine, G as clearConsumedPendingMail, Gt as classificationFactLines, H as wakeRecipientField, Ht as emptyEmailListHint, I as readThreadContext, It as currentMailSessionKey, J as readPendingMail, Jt as noFactsMessage, K as clearReadPendingMail, Kt as classificationOf, L as sentInThread, Lt as detectAgentRuntime, M as describeWake, N as displayAddress, Nt as generatedAgentName, O as listenForCopyHotkey, Ot as thrownErrorExitCode, P as formatMailWakeLine, Pt as identitySuggestions, Q as interactionNextActions, Qt as ConnectedAgentKeyRequiredError, R as serverMuted, Rt as detectRuntimeSession, S as approveThenRerunHint, St as sendIdentityFields, T as withApproval, Tt as sentHistoryWindowStart, U as wakeRelationship, Ut as mailReach, V as wakeReadCommand, Vt as withFlagSuggestion, W as claimPendingMail, Wt as readCallerMailFor, X as removePendingMail, Xt as ATTACHMENT_WAIT_MAX_MS, Y as recordPendingMail, Yt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, Z as interactionHumanLine, Zt as withAttachmentWait, _ as takesStepUp, _n as setKnownCommands, _t as formatPriorRepliesWarning, a as createOperationCommand, an as selectedOrgFromEnv, at as SEND_OUTCOME_HELP, b as STEP_UP_HEADER$1, bt as priorRepliesThatWentOut, c as formatErrorPayload, cn as credentialsFromUserLogin, ct as buildFollowUpCommand, d as isGrantRefusal, dn as revokeUserLogin, dt as classifySendError, en as createAuthenticatedCliApiClient, et as replyExpectation, f as parameterFlagName, fn as roleKeyLabel, ft as deriveSendIdempotencyKey, g as surfaceUnauthorizedHint, gn as sanitizeForTerminal, gt as formatPriorRepliesCheckSkipped, h as runWithTiming, hn as routeWarningLines, ht as formatDeletedEarlierSendNotice, i as TIME_FLAG_DESCRIPTION, in as resolveCliApiRequestConfig, it as MAX_SEND_WAIT_TIMEOUT_MS, j as bareAddress, jt as connectNextSteps, k as openBrowser, l as hasControlCharacter$1, ln as fetchCliOrgs, lt as buildThrownSendFailureEnvelope, m as refusalDetails, mn as switchCurrentOrg, mt as followUpCommandPrefix, n as API_ERROR_CODES, nn as refreshStoredCliCredentials, o as extractErrorCode, on as CliOrgRefusedError, ot as apiResultHttpStatus, p as readTextFileFlag, pn as roleKeyText, pt as excludeSignalReplies, q as pendingMailPath, qt as isEmailId, r as OPERATION_PATH_FLAGS, rn as refuseOrgSelectionForProfile, rt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, s as extractErrorPayload, sn as RESTRICTED_ROLE_NOTE, st as assertValidIdempotencyKey, t as API_BASE_URL_FLAG_DESCRIPTION, tn as createCliApiClient, tt as replyInteractionWarning, u as headerFlagName, un as isBuiltInRoleKey, ut as checkPriorReplies, v as writeErrorWithHints, vn as operationManifest, vt as formatSendFailureSummary, w as terminalApprovalIo, wt as sendWaitClampNotice, x as STEP_UP_TOKEN_FLAG_DESCRIPTION, xt as reportSendCommandResult, y as BROWSER_FLAG_DESCRIPTION, yt as formatSendRecordFailureSummary, z as serverRelationship, zt as runtimeSessionVariableList } from "../api-command-D5Tj9C-K.js";
2
- import { $ as parseWebhookEvent, $n as simulateRoute, $t as getRepeatingSend, A as lateMailReread, An as listWakeDispatches, At as deleteWakeSchedule, B as ensureSearchReportsReplyState, Bn as replyToEmail, Bt as getConversation, C as waitOutRetryAfter, Cn as listOrganizationPeople, Ct as createWakeAuthorization, D as encodeReceivedAtSearchCursor, Dn as listSentAttachmentArchiveMembers, Dt as deleteMemory, E as cursorFromAcceptedRows, En as listRoutes, F as AWAITING_VALUES, Ft as downloadSentAttachmentByFilename, G as queryUsesAwaiting, Gt as getFunctionRouting, H as formatRepliesCell, Hn as revokeCredential, Ht as getDefaultNetworkAgent, I as REPLY_STATE_UNSUPPORTED_CODE, It as getAccessMode, Jn as semanticSearch, Jt as getKey, Kn as searchEmails, Kt as getFunctionTestRunTrace, L as ReplyStateUnsupportedError, Ln as redeemCreditCode, Lt as getAccount, M as sinceFromFlags, Mn as pollCliLogin, Mt as downloadEmailAttachmentByFilename, N as sleep$3, Nn as pullWebhookEvent, O as fetchEmailSearchPage, On as listSentEmails, Ot as deleteRoute, P as AWAITING_FLAG_DESCRIPTION, Pt as downloadSentAttachmentArchiveMember, Qn as setMemory, Qt as getOwnGrant, R as assertAwaitingFilterRows, Rn as removeDefaultNetworkMember, Rt as getAgentContactPolicy, S as transientFailure, Sn as listMailboxAccess, St as createRoute, T as collectNewAcceptedEmails, Tn as listRoles, U as hasReplyState, Un as rotateKey, Ut as getEmail, V as formatAwaitingCell, Vn as resendAgentSignupVerification, Vt as getCreditBalance, W as isAwaitingRejectedError, Wn as runWakeSchedule, Wt as getFunction, Xn as setFunctionRoute, Xt as getOrgRoutingTopology, Yn as sendEmail, Yt as getMemory, Z as isEmailReceivedEvent, Zn as setFunctionSecret, Zt as getOutboundStatus, _ as describeApiFailure, _n as listEndpoints, _t as cliLogout, a as REPEAT_IDLE_MAX_MINUTES, an as leaveOrganization, ar as unsetFunctionRoute, at as assertAutomatedVerdict, b as retryTransient, bn as listGroups, bt as createFunction, cn as listAgentContacts, cr as updateKey, d as parseRepeatTick, dn as listDefaultNetworkAgents, dr as updateWakeAuthorization, dt as isAutomatedRejectedError, en as getSendFrom, er as startAgentClaim, et as parseFromHeader, f as preparePresenceAliveEmail, fn as listDefaultNetworkMembers, fr as updateWakeSchedule, ft as addDefaultNetworkMember, g as WaitDeadlineReached, gn as listEmails, gt as checkSendability, hn as listEmailRuleResults, hr as whoami, ht as checkDefaultNetworkContactAdmission, i as REPEAT_EVERY_MAX_MINUTES, ir as testFunction, it as AutomatedFilterUnsupportedError, j as quoteDslValue, jn as listWakeSchedules, jt as downloadEmailAttachmentArchiveMember, k as filtersFromFlags, kn as listWakeAuthorizations, kt as deleteWakeAuthorization, l as parseInteractionEnvelope, ln as listAgentNetworks, lr as updateRepeatingSend, m as repeatStopCommand, mn as listEmailAttachmentArchiveMembers, mr as verifyAgentSignup, mt as cancelSentEmailByIdempotencyKey, nn as getThread, nr as startCliLogin, nt as AUTOMATED_FLAG_DESCRIPTION, o as REPEAT_MAX_SENDS_MAX, on as listActivityObjects, or as updateDefaultNetworkMember, ot as automatedRejectedError, p as prepareSignalEmail, pn as listDomains, pr as verifyAgentClaim, pt as cancelSentEmail, qn as searchMemories, qt as getInboxStatus, rn as getWakeSchedule, rr as stopRepeatFromEmail, rt as AUTOMATED_VALUES, s as buildRepeatStopBody, sr as updateFunction, tn as getSentEmail, tr as startAgentSignup, tt as AUTOMATED_FILTER_UNSUPPORTED_CODE, u as parsePresenceEnvelope, ur as updateRoute, ut as hasAutomatedVerdict, vn as listFunctionLogs, vt as completeWebhookEvent, w as SINCE_VALUE_HELP, wn as listRepeatingSends, wt as createWakeSchedule, x as retryingOperation, xn as listKeys, xt as createKey, y as pauseForTransientFailure, yn as listFunctions, yt as createEndpoint, z as awaitingRejectedError, zn as reorderRoutes } from "../repeats-Dj_o6c-X-ykfsraq6.js";
3
- import { S as readBundledConnectSkill, _ as NotificationOutcomeUnknownError, a as NotificationRetryError, b as installedConnectSkillFile, c as readConversationStatusContent, d as SESSION_UUID$1, g as NativeSessionNotLoadedError, h as NativeSessionError, i as notificationReceiptPage, l as createListenHandler, m as NativeSessionDisconnectedError, n as notificationSenders, o as isRoutineNotificationContent, p as defaultSessionSocket, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, u as readableDelivery, v as connectSkillTarget, x as installedVersion, y as installConnectSkill } from "../notify-session-cIUz_kxR.js";
1
+ import { $ as interactionNextActions, $t as ConnectedAgentKeyRequiredError, A as openBrowser, B as serverRelationship, Bt as runtimeSessionVariableList, C as approveThenRerunHint, Ct as sendIdentityFields, D as canOfferCopyHotkey, Dt as serializeErrorPayload, E as withApproval, Et as sentHistoryWindowStart, F as formatMailWakeLine, Ft as identitySuggestions, G as claimPendingMail, Gt as readCallerMailFor, H as wakeReadCommand, Ht as withFlagSuggestion, I as latestOwnOutbound, It as loadSkillLine, J as pendingMailPath, Jt as isEmailId, K as clearConsumedPendingMail, Kt as classificationFactLines, L as readThreadContext, Lt as currentMailSessionKey, M as bareAddress, Mt as connectNextSteps, N as describeWake, O as formatBrowserLoginInstructions, Ot as successfulSendOutcome, P as displayAddress, Pt as generatedAgentName, Q as interactionHumanLine, Qt as withAttachmentWait, R as sentInThread, Rt as detectAgentRuntime, S as STEP_UP_TOKEN_FLAG_DESCRIPTION, St as reportSendCommandResult, T as terminalApprovalIo, Tt as sendWaitClampNotice, U as wakeRecipientField, Ut as emptyEmailListHint, Vt as formatAlreadySentNotice, W as wakeRelationship, Wt as mailReach, X as recordPendingMail, Xt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, Y as readPendingMail, Yt as noFactsMessage, Z as removePendingMail, Zt as ATTACHMENT_WAIT_MAX_MS, _ as surfaceUnauthorizedHint, _n as sanitizeForTerminal, _t as formatPriorRepliesCheckSkipped, a as createOperationCommand, an as resolveCliApiRequestConfig, at as MAX_SEND_WAIT_TIMEOUT_MS, b as BROWSER_FLAG_DESCRIPTION, bt as formatSendRecordFailureSummary, c as formatErrorPayload, cn as RESTRICTED_ROLE_NOTE, ct as assertValidIdempotencyKey, d as isGrantRefusal, dn as isBuiltInRoleKey, dt as checkPriorReplies, et as readEmailInteraction, f as networkErrorHint, fn as revokeUserLogin, ft as classifySendError, g as runWithTiming, gn as routeWarningLines, gt as formatDeletedEarlierSendNotice, h as refusalDetails, hn as switchCurrentOrg, ht as followUpCommandPrefix, i as TIME_FLAG_DESCRIPTION, in as refuseOrgSelectionForProfile, it as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, j as explainPendingMailNotFound, jt as cliInvocation, k as listenForCopyHotkey, kt as thrownErrorExitCode, l as hasControlCharacter$1, ln as credentialsFromUserLogin, lt as buildFollowUpCommand, m as readTextFileFlag, mn as roleKeyText, mt as excludeSignalReplies, n as API_ERROR_CODES, nn as createCliApiClient, nt as replyInteractionWarning, o as extractErrorCode, on as selectedOrgFromEnv, ot as SEND_OUTCOME_HELP, p as parameterFlagName, pn as roleKeyLabel, pt as deriveSendIdempotencyKey, q as clearReadPendingMail, qt as classificationOf, r as OPERATION_PATH_FLAGS, rn as refreshStoredCliCredentials, s as extractErrorPayload, sn as CliOrgRefusedError, st as apiResultHttpStatus, t as API_BASE_URL_FLAG_DESCRIPTION, tn as createAuthenticatedCliApiClient, tt as replyExpectation, u as headerFlagName, un as fetchCliOrgs, ut as buildThrownSendFailureEnvelope, v as takesStepUp, vn as setKnownCommands, vt as formatPriorRepliesWarning, w as promptLine$2, wt as sendOutcomeExitCode, x as STEP_UP_HEADER$1, xt as priorRepliesThatWentOut, y as writeErrorWithHints, yn as operationManifest, yt as formatSendFailureSummary, z as serverMuted, zt as detectRuntimeSession } from "../api-command-BTOIanV1.js";
2
+ import { $n as setMemory, $t as getOwnGrant, A as filtersFromFlags, An as listWakeAuthorizations, At as deleteWakeAuthorization, B as awaitingRejectedError, Bn as reorderRoutes, C as transientFailure, Cn as listMailboxAccess, Ct as createRoute, D as cursorFromAcceptedRows, Dn as listRoutes, E as collectNewAcceptedEmails, En as listRoles, F as AWAITING_FLAG_DESCRIPTION, Ft as downloadSentAttachmentArchiveMember, G as isAwaitingRejectedError, Gn as runWakeSchedule, Gt as getFunction, H as formatAwaitingCell, Hn as resendAgentSignupVerification, Ht as getCreditBalance, I as AWAITING_VALUES, It as downloadSentAttachmentByFilename, Jn as searchMemories, Jt as getInboxStatus, K as queryUsesAwaiting, Kt as getFunctionRouting, L as REPLY_STATE_UNSUPPORTED_CODE, Lt as getAccessMode, M as quoteDslValue, Mn as listWakeSchedules, Mt as downloadEmailAttachmentArchiveMember, N as sinceFromFlags, Nn as pollCliLogin, Nt as downloadEmailAttachmentByFilename, O as encodeReceivedAtSearchCursor, On as listSentAttachmentArchiveMembers, Ot as deleteMemory, P as sleep$3, Pn as pullWebhookEvent, Q as isEmailReceivedEvent, Qn as setFunctionSecret, Qt as getOutboundStatus, R as ReplyStateUnsupportedError, Rn as redeemCreditCode, Rt as getAccount, S as retryingOperation, Sn as listKeys, St as createKey, T as SINCE_VALUE_HELP, Tn as listRepeatingSends, Tt as createWakeSchedule, U as formatRepliesCell, Un as revokeCredential, Ut as getDefaultNetworkAgent, V as ensureSearchReportsReplyState, Vn as replyToEmail, Vt as getConversation, W as hasReplyState, Wn as rotateKey, Wt as getEmail, Xn as sendEmail, Xt as getMemory, Yn as semanticSearch, Yt as getKey, Zn as setFunctionRoute, Zt as getOrgRoutingTopology, _ as backoffDelay, _n as listEmails, _t as checkSendability, a as REPEAT_IDLE_MAX_MINUTES, ar as testFunction, at as AutomatedFilterUnsupportedError, b as pauseForTransientFailure, bn as listFunctions, bt as createEndpoint, cr as updateFunction, d as parseRepeatTick, dr as updateRoute, dt as hasAutomatedVerdict, en as getRepeatingSend, er as simulateRoute, et as parseWebhookEvent, f as preparePresenceAliveEmail, fn as listDefaultNetworkAgents, fr as updateWakeAuthorization, ft as isAutomatedRejectedError, g as WaitDeadlineReached, gn as listEmailRuleResults, gr as whoami, gt as checkDefaultNetworkContactAdmission, hn as listEmailAttachmentArchiveMembers, hr as verifyAgentSignup, ht as cancelSentEmailByIdempotencyKey, i as REPEAT_EVERY_MAX_MINUTES, in as getWakeSchedule, ir as stopRepeatFromEmail, it as AUTOMATED_VALUES, j as lateMailReread, jn as listWakeDispatches, jt as deleteWakeSchedule, k as fetchEmailSearchPage, kn as listSentEmails, kt as deleteRoute, l as parseInteractionEnvelope, ln as listAgentContacts, lr as updateKey, m as repeatStopCommand, mn as listDomains, mr as verifyAgentClaim, mt as cancelSentEmail, nn as getSentEmail, nr as startAgentSignup, nt as AUTOMATED_FILTER_UNSUPPORTED_CODE, o as REPEAT_MAX_SENDS_MAX, on as leaveOrganization, or as unsetFunctionRoute, ot as assertAutomatedVerdict, p as prepareSignalEmail, pn as listDefaultNetworkMembers, pr as updateWakeSchedule, pt as addDefaultNetworkMember, qn as searchEmails, qt as getFunctionTestRunTrace, rn as getThread, rr as startCliLogin, rt as AUTOMATED_FLAG_DESCRIPTION, s as buildRepeatStopBody, sn as listActivityObjects, sr as updateDefaultNetworkMember, st as automatedRejectedError, tn as getSendFrom, tr as startAgentClaim, tt as parseFromHeader, u as parsePresenceEnvelope, un as listAgentNetworks, ur as updateRepeatingSend, v as describeApiFailure, vn as listEndpoints, vt as cliLogout, w as waitOutRetryAfter, wn as listOrganizationPeople, wt as createWakeAuthorization, x as retryTransient, xn as listGroups, xt as createFunction, yn as listFunctionLogs, yt as completeWebhookEvent, z as assertAwaitingFilterRows, zn as removeDefaultNetworkMember, zt as getAgentContactPolicy } from "../repeats-Dj_o6c-X-B7sSj0oc.js";
3
+ import { S as readBundledConnectSkill, _ as NotificationOutcomeUnknownError, a as NotificationRetryError, b as installedConnectSkillFile, c as readConversationStatusContent, d as SESSION_UUID$1, g as NativeSessionNotLoadedError, h as NativeSessionError, i as notificationReceiptPage, l as createListenHandler, m as NativeSessionDisconnectedError, n as notificationSenders, o as isRoutineNotificationContent, p as defaultSessionSocket, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, u as readableDelivery, v as connectSkillTarget, x as installedVersion, y as installConnectSkill } from "../notify-session-b_0jyPIk.js";
4
4
  import { A as AGENT_ADDRESS_ENV, C as saveCliCredentials, D as loadActiveChatState, E as deleteChatState, F as agentProfilesDirectory, H as requireDefaultLoginProfile, M as AgentConnectionSetupError, N as agentProfileDirectory, O as loadChatConversationByLocalId, P as agentProfileName, R as loadConnectedAgentProfile, S as resolveCliAuth, T as chatStatePath, W as PrimitiveApiClient, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as AGENT_PROFILE_ENV, k as saveActiveChatState, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveSignupCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-Ms_0vNjq.js";
5
5
  import { C as resolveListenSubscription, S as normalizeListenOrigin, _ as acquireListenLock, a as mailLockBusy, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, m as withMailLock, o as mailObject, u as privateMailPermissions, y as listenIdentity } from "../shared-mail-files-BAQSGxxz.js";
6
- import { $ as saveConnectionName, A as ContactPolicyReadRetryError, B as AddressNotesApiError, C as RECEIVER_MODES, Ct as upsertManagedBlock, D as registrationSummary, Dt as refreshOwnerMemberAddressPeriodically, E as failedSessionRegistration, Et as addSessionReceiver, F as canonicalContactSelector, G as contactAgentAddress, H as runAddressNotesRequest, I as AgentDisconnectError, J as agentInvitationHash, K as runContactRequest, L as claimOverRevokedSetup, M as NetworkAdmissionPendingError, N as createNotificationContactPolicy, O as registrationSummaryLine, Ot as withOwnerMemberAddress, P as evaluateContactPolicy, Q as refusalAfterReplacement, R as clearRevokedAddresses, S as runAgentConnect, St as readManagedFile, T as verificationReplySubmitted, Tt as readHookLauncherWarning, U as ContactsApiError, V as addressNoteTarget, W as contactAddress, X as readAgentInvitation, Y as connectAgent, Z as receiverStatusCommand, _ as enrollmentResumes, _t as samePath, a as endSession, at as boundSessionProfiles, b as invitationProfileName, bt as inspectManagedBlock, c as pendingSessionDisconnects, ct as describeSessionHookChange, d as registerSession, dt as ephemeralCliReason, et as savedReceiver, f as retryPendingDisconnects, ft as findOnPath, g as enrollAgent, gt as repairClaudeHooks, h as staleSessionAgents, ht as readClaudeSettings, i as connectedProfilesForSession, it as recordHookMailCheck, j as NETWORK_ADMISSION_PENDING_RETRY_MS, k as waitForRegistrations, l as psProcessInfo, lt as describeSessionHookItem, m as runDetached, mt as inspectClaudeHooks, n as OMP_MAIN, nt as releaseRegistrationsForProfile, o as headlessClaudeRun, ot as claudeConfigDir, p as revokeStaleSessionAgents, pt as hasMachineSessionHooks, q as agentConnectionStatus, r as agentConnectionState, rt as installClaudeWakeHook, st as currentCliLocation, t as HOOK_END_GRACE_MS, tt as MACHINE_RUNTIMES, u as readClaudeHookInput, ut as ensureHookLauncher, v as connectWarnings, vt as writeClaudeSettings, w as externalReceivingState, wt as writeManagedFile, x as pollCheckCommand, xt as jsonIndentation, y as defaultAgentProfileName, yt as MachineFileError, z as disconnectAgent } from "../machine-session-BMr-zPLx.js";
7
- import { a as readBeforeDeadline, c as readPresenceProjection, i as readApiBeforeDeadline, n as isPlainChatReply, o as scopedChatSenderTrust, r as isScopedChatReply, s as presenceDisposition, t as isConnectedChatCredential } from "../scoped-chat-DVSsLSRw.js";
6
+ import { $ as saveConnectionName, A as ContactPolicyReadRetryError, B as AddressNotesApiError, C as RECEIVER_MODES, Ct as upsertManagedBlock, D as registrationSummary, Dt as refreshOwnerMemberAddressPeriodically, E as failedSessionRegistration, Et as addSessionReceiver, F as canonicalContactSelector, G as contactAgentAddress, H as runAddressNotesRequest, I as AgentDisconnectError, J as agentInvitationHash, K as runContactRequest, L as claimOverRevokedSetup, M as NetworkAdmissionPendingError, N as createNotificationContactPolicy, O as registrationSummaryLine, Ot as withOwnerMemberAddress, P as evaluateContactPolicy, Q as refusalAfterReplacement, R as clearRevokedAddresses, S as runAgentConnect, St as readManagedFile, T as verificationReplySubmitted, Tt as readHookLauncherWarning, U as ContactsApiError, V as addressNoteTarget, W as contactAddress, X as readAgentInvitation, Y as connectAgent, Z as receiverStatusCommand, _ as enrollmentResumes, _t as samePath, a as endSession, at as boundSessionProfiles, b as invitationProfileName, bt as inspectManagedBlock, c as pendingSessionDisconnects, ct as describeSessionHookChange, d as registerSession, dt as ephemeralCliReason, et as savedReceiver, f as retryPendingDisconnects, ft as findOnPath, g as enrollAgent, gt as repairClaudeHooks, h as staleSessionAgents, ht as readClaudeSettings, i as connectedProfilesForSession, it as recordHookMailCheck, j as NETWORK_ADMISSION_PENDING_RETRY_MS, k as waitForRegistrations, l as psProcessInfo, lt as describeSessionHookItem, m as runDetached, mt as inspectClaudeHooks, n as OMP_MAIN, nt as releaseRegistrationsForProfile, o as headlessClaudeRun, ot as claudeConfigDir, p as revokeStaleSessionAgents, pt as hasMachineSessionHooks, q as agentConnectionStatus, r as agentConnectionState, rt as installClaudeWakeHook, st as currentCliLocation, t as HOOK_END_GRACE_MS, tt as MACHINE_RUNTIMES, u as readClaudeHookInput, ut as ensureHookLauncher, v as connectWarnings, vt as writeClaudeSettings, w as externalReceivingState, wt as writeManagedFile, x as pollCheckCommand, xt as jsonIndentation, y as defaultAgentProfileName, yt as MachineFileError, z as disconnectAgent } from "../machine-session-BTkDAKda.js";
7
+ import { a as readBeforeDeadline, c as readPresenceProjection, i as readApiBeforeDeadline, n as isPlainChatReply, o as scopedChatSenderTrust, r as isScopedChatReply, s as presenceDisposition, t as isConnectedChatCredential } from "../scoped-chat-Cqmn-k7q.js";
8
8
  import { autoSignalEligible, dispatchAutoRead, dispatchAutoWorking, haltAutoWorking, isSentSignal, readWorkingLease, restoreAutoWorking } from "./auto-signals.js";
9
- import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-gpH2AhIi.js";
9
+ import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-ewPBzvjU.js";
10
10
  import { a as thrownErrorBody, i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-ch756TXV.js";
11
11
  import { a as backgroundListenStatus, c as runBackgroundListen, d as stopBackgroundListen, f as verifyBackgroundListenTarget, o as backgroundListenToken, r as backgroundListenConfiguration, u as startBackgroundListen } from "../listen-background-DyBYca28.js";
12
12
  import { ORG_SELECTION_ENV } from "./global-org-flag.js";
@@ -184,6 +184,11 @@ const openapiDocument = {
184
184
  "description": "Organization administration for people: your own connected apps and CLI logins, the organization's connected apps, its name and logo, and semantic search consent. API keys, agent connection keys and agents are refused. Billing and deleting the organization are done in the dashboard only.",
185
185
  "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/organization-settings"
186
186
  },
187
+ {
188
+ "name": "Email notifications",
189
+ "description": "The notification emails Primitive sends an organization's members: the organization chooses, per kind, whether it is off, left to each member or required, and each member chooses where it is left to them. Some kinds are always sent. API keys, agent connection keys and agents are refused.",
190
+ "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/email-notifications"
191
+ },
187
192
  {
188
193
  "name": "Roles and members",
189
194
  "description": "Roles, member roles and removal, the mailbox access mode, break-glass, transfers and suspensions. Reads are open to people; every write is person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up. API keys never reach these. Answers 404 feature_disabled while the feature is not enabled for the organization, with details.feature naming the feature; contact Primitive to turn it on.",
@@ -21774,7 +21779,7 @@ const openapiDocument = {
21774
21779
  "/activity/objects": { "get": {
21775
21780
  "operationId": "listActivityObjects",
21776
21781
  "summary": "List what you created, or what you were asked",
21777
- "description": "The objects the caller created, newest first, each with where it stands now, including ones since disabled, deleted or revoked where the object keeps a row: Functions, endpoints, Function triggers, pull subscriptions, recipient routes, filters, wake schedules, repeating sends and scheduled sends. An API key, an app or an agent sees what it created itself; a webhook given to an API key lists under that key. A person (a dashboard session, or their own Primitive CLI or app login) sees what they created through any credential of theirs, each item naming it, and also their API keys, personal agents, email rules, rule requests and objections to rules over their mail (objections only on the dashboard or the Primitive CLI). Each kind is listed only if the caller could list it through that kind's own route; a kind it could not is named in withheld, with no count. Ids, names and states only: never a webhook URL or mail content. Needs activity:read_self. With relation=asked it lists instead what other members asked of the person, open or answered: the rule requests made to them, and the objections to rules they wrote, under their current membership, each naming the member who asked or objected in created_through. A key, an app or an agent has none, and gets both kinds in withheld. With relation=concerning it lists instead what other members did that concerns the person's own mail: each time a rule started covering one of their mailboxes or saved a new version while covering it, named as the rule was then, naming its author in created_through, under their current membership; only on the dashboard or the Primitive CLI. A key, an app or an agent has none, and gets email_rule_coverage in withheld. A notice never reads as ended. Notices are written by a periodic job and can lag: until the notices record a rule starting to cover you, the first page lists that rule as pending (pending_complete says whether that check finished), and as_of says about when the notices last looked. For what covers you now, read GET /email-rules/covering-me.",
21782
+ "description": "The objects the caller created, newest first, each with where it stands now, including ones since disabled, deleted or revoked where the object keeps a row: Functions, endpoints, Function triggers, pull subscriptions, recipient routes, filters, wake schedules, repeating sends and scheduled sends. An API key, an app or an agent sees what it created itself; a webhook given to an API key lists under that key. A person (a dashboard session, or their own Primitive CLI or app login) sees what they created through any credential of theirs, each item naming it, and also their API keys, personal agents, email rules, rule requests and objections to rules over their mail (objections only on the dashboard or the Primitive CLI). Each kind is listed only if the caller could list it through that kind's own route; a kind it could not is named in withheld, with no count. Ids, names and states only: never a webhook URL or mail content. Needs activity:read_self. With relation=asked it lists instead what other members asked of the person, open or answered: the rule requests made to them, and the objections to rules they wrote, under their current membership, each naming the member who asked or objected in created_through. A key, an app or an agent has none, and gets both kinds in withheld. With relation=concerning it lists instead what other members did that concerns the person's own mail: each time a rule started covering one of their mailboxes, saved a new version, or changed who receives its matches while covering it, named as the rule was then, naming its author in created_through, under their current membership; only on the dashboard or the Primitive CLI. A key, an app or an agent has none, and gets email_rule_coverage in withheld. A notice never reads as ended. Notices are written by a periodic job and can lag: until the notices record a rule starting to cover you, the first page lists that rule as pending (pending_complete says whether that check finished), and as_of says about when the notices last looked. For what covers you now, read GET /email-rules/covering-me.",
21778
21783
  "tags": ["Activity"],
21779
21784
  "security": [{ "BearerAuth": [] }],
21780
21785
  "parameters": [
@@ -21785,7 +21790,7 @@ const openapiDocument = {
21785
21790
  "description": "created (the default), asked or concerning.",
21786
21791
  "schema": {
21787
21792
  "default": "created",
21788
- "description": "created (the default): what you created. asked: what other members asked of you, open or answered: rule requests made to you, and objections to rules you wrote; kind may then name only email_rule_request and email_rule_objection. concerning: what other members did that concerns your own mail: an active rule that started covering one of your mailboxes, or saved a new version while covering it (email_rule_coverage, the only kind it takes). A rule in draft or observe is not told of; GET /v1/email-rules/covering-me lists it. asked and concerning are for a person's own view only.",
21793
+ "description": "created (the default): what you created. asked: what other members asked of you, open or answered: rule requests made to you, and objections to rules you wrote; kind may then name only email_rule_request and email_rule_objection. concerning: what other members did that concerns your own mail: an active rule that started covering one of your mailboxes, saved a new version, or changed who receives its matches while covering it (email_rule_coverage, the only kind it takes). A rule in draft or observe is not told of; GET /v1/email-rules/covering-me lists it. asked and concerning are for a person's own view only.",
21789
21794
  "type": "string",
21790
21795
  "enum": [
21791
21796
  "created",
@@ -36033,7 +36038,7 @@ const openapiDocument = {
36033
36038
  "name": "Primitive-Step-Up",
36034
36039
  "in": "header",
36035
36040
  "required": false,
36036
- "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo or semantic search setting), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36041
+ "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36037
36042
  "schema": {
36038
36043
  "type": "string",
36039
36044
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -36184,7 +36189,7 @@ const openapiDocument = {
36184
36189
  "name": "Primitive-Step-Up",
36185
36190
  "in": "header",
36186
36191
  "required": false,
36187
- "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo or semantic search setting), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36192
+ "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36188
36193
  "schema": {
36189
36194
  "type": "string",
36190
36195
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -36333,7 +36338,7 @@ const openapiDocument = {
36333
36338
  "name": "Primitive-Step-Up",
36334
36339
  "in": "header",
36335
36340
  "required": false,
36336
- "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo or semantic search setting), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36341
+ "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36337
36342
  "schema": {
36338
36343
  "type": "string",
36339
36344
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -36593,7 +36598,7 @@ const openapiDocument = {
36593
36598
  "name": "Primitive-Step-Up",
36594
36599
  "in": "header",
36595
36600
  "required": false,
36596
- "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo or semantic search setting), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36601
+ "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36597
36602
  "schema": {
36598
36603
  "type": "string",
36599
36604
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -36722,21 +36727,29 @@ const openapiDocument = {
36722
36727
  "x-primitive-permission": ["search_settings:manage"]
36723
36728
  }
36724
36729
  },
36725
- "/roles": {
36730
+ "/email-notification-settings": {
36726
36731
  "get": {
36727
- "operationId": "listRoles",
36728
- "summary": "List roles",
36729
- "description": "Every role in the organization (built-in, template copies and custom roles), with how many members and groups hold each, the templates not yet copied, and whether roles can be assigned now. `unassigned` describes role restricted on a member with no role_id, which is not a role and is never in `roles`. Needs members:read (owners, admins, members and roles that grant it); API keys are refused.",
36730
- "tags": ["Roles and members"],
36732
+ "operationId": "getEmailNotificationSettings",
36733
+ "summary": "Get the organization's email notification settings",
36734
+ "tags": ["Email notifications"],
36731
36735
  "security": [{ "BearerAuth": [] }],
36736
+ "description": "Every kind of notification email Primitive sends the organization's members, and who may turn it off. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). API keys, agent connection keys and agents are refused.",
36737
+ "x-primitive-person-only": { "access": "person" },
36738
+ "parameters": [],
36732
36739
  "responses": {
36733
36740
  "200": {
36734
- "description": "List roles",
36735
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
36741
+ "description": "Success",
36742
+ "content": { "application/json": { "schema": {
36736
36743
  "type": "object",
36737
- "required": ["data"],
36738
- "properties": { "data": { "$ref": "#/components/schemas/ListRolesResult" } }
36739
- }] } } },
36744
+ "required": ["success", "data"],
36745
+ "properties": {
36746
+ "success": {
36747
+ "type": "boolean",
36748
+ "const": true
36749
+ },
36750
+ "data": { "$ref": "#/components/schemas/EmailNotifications_getEmailNotificationSettings_Result" }
36751
+ }
36752
+ } } },
36740
36753
  "headers": {
36741
36754
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
36742
36755
  "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
@@ -36744,6 +36757,19 @@ const openapiDocument = {
36744
36757
  "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
36745
36758
  }
36746
36759
  },
36760
+ "400": {
36761
+ "description": "Invalid request parameters",
36762
+ "content": { "application/json": {
36763
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
36764
+ "example": {
36765
+ "success": false,
36766
+ "error": {
36767
+ "code": "validation_error",
36768
+ "message": "Invalid domain format"
36769
+ }
36770
+ }
36771
+ } }
36772
+ },
36747
36773
  "401": {
36748
36774
  "description": "Invalid or missing API key",
36749
36775
  "content": { "application/json": {
@@ -36777,12 +36803,17 @@ const openapiDocument = {
36777
36803
  } }
36778
36804
  },
36779
36805
  "404": {
36780
- "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
36781
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
36782
- },
36783
- "409": {
36784
- "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
36785
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
36806
+ "description": "Resource not found",
36807
+ "content": { "application/json": {
36808
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
36809
+ "example": {
36810
+ "success": false,
36811
+ "error": {
36812
+ "code": "not_found",
36813
+ "message": "Resource not found"
36814
+ }
36815
+ }
36816
+ } }
36786
36817
  },
36787
36818
  "429": {
36788
36819
  "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
@@ -36808,27 +36839,48 @@ const openapiDocument = {
36808
36839
  } }
36809
36840
  }
36810
36841
  },
36811
- "x-primitive-permission": ["members:read"],
36812
- "x-primitive-builtin-role-keys": { "$ref": "#/components/schemas/BuiltinRoleKey" }
36842
+ "x-primitive-permission": ["org:read"]
36813
36843
  },
36814
- "post": {
36815
- "operationId": "createRole",
36816
- "summary": "Create a custom role",
36817
- "description": "A role nobody can use to gain access they do not hold: its permissions and mail scope must be within the author's own. mail_scope is \"org\" (the default) or { addresses, domains }; any other value is refused with 400 `validation_error`. Every role that reaches mail reads it in full, and raw MIME follows the role's mail.raw:read; `depth` is deprecated and ignored. Its mail scope names no other member's personal mailbox: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission must have what it requires in the same role: mail:read for mail.raw:read, mail.search:semantic, mail:share_own, changes:read and webhooks:read; mail:send for mail:send_batch and mail:send_as_member; functions:deploy for templates:install; agent_connections:manage for agent_connections:custom_domains. Otherwise 400 `validation_error` with `details.reason` `unmet_requirement`, `details.permission` and `details.requires`. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
36818
- "tags": ["Roles and members"],
36844
+ "put": {
36845
+ "operationId": "updateEmailNotificationSettings",
36846
+ "summary": "Change the organization's email notification settings",
36847
+ "tags": ["Email notifications"],
36819
36848
  "security": [{ "BearerAuth": [] }],
36820
- "requestBody": {
36821
- "required": true,
36822
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateRoleRequest" } } }
36849
+ "description": "Set the organization's mode for one or more kinds: off (nobody gets it), optional_on (each member gets it unless they turn it off), optional_off (only members who turn it on get it) or required (every member gets it). Kinds left out keep their mode. An unknown kind is refused with 400 (validation_error, details.reason unknown_kind). A platform_required kind is refused with 409 (conflict, details.reason platform_required), and required for an optional kind with 409 (conflict, details.reason mode_not_allowed); nothing is changed. Answers the settings as they now stand. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `org_settings.write` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold notification_policy:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
36850
+ "x-primitive-person-only": {
36851
+ "access": "step_up",
36852
+ "step_up_purpose": "org_settings.write"
36823
36853
  },
36854
+ "parameters": [{
36855
+ "name": "Primitive-Step-Up",
36856
+ "in": "header",
36857
+ "required": false,
36858
+ "description": "A single-use step-up token of purpose `org_settings.write` (Change the organization name, logo, semantic search setting or email notification settings), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36859
+ "schema": {
36860
+ "type": "string",
36861
+ "pattern": "^pstep_[0-9a-f]{64}$"
36862
+ }
36863
+ }],
36824
36864
  "responses": {
36825
- "201": {
36826
- "description": "Create a custom role",
36827
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
36865
+ "200": {
36866
+ "description": "Success",
36867
+ "content": { "application/json": { "schema": {
36828
36868
  "type": "object",
36829
- "required": ["data"],
36830
- "properties": { "data": { "$ref": "#/components/schemas/CreateRoleResult" } }
36831
- }] } } }
36869
+ "required": ["success", "data"],
36870
+ "properties": {
36871
+ "success": {
36872
+ "type": "boolean",
36873
+ "const": true
36874
+ },
36875
+ "data": { "$ref": "#/components/schemas/EmailNotifications_updateEmailNotificationSettings_Result" }
36876
+ }
36877
+ } } },
36878
+ "headers": {
36879
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
36880
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
36881
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
36882
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
36883
+ }
36832
36884
  },
36833
36885
  "400": {
36834
36886
  "description": "Invalid request parameters",
@@ -36876,12 +36928,30 @@ const openapiDocument = {
36876
36928
  } }
36877
36929
  },
36878
36930
  "404": {
36879
- "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
36880
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
36931
+ "description": "Resource not found",
36932
+ "content": { "application/json": {
36933
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
36934
+ "example": {
36935
+ "success": false,
36936
+ "error": {
36937
+ "code": "not_found",
36938
+ "message": "Resource not found"
36939
+ }
36940
+ }
36941
+ } }
36881
36942
  },
36882
36943
  "409": {
36883
- "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
36884
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
36944
+ "description": "The request conflicts with the current state of the resource",
36945
+ "content": { "application/json": {
36946
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
36947
+ "example": {
36948
+ "success": false,
36949
+ "error": {
36950
+ "code": "conflict",
36951
+ "message": "The request conflicts with the current state of the resource"
36952
+ }
36953
+ }
36954
+ } }
36885
36955
  },
36886
36956
  "429": {
36887
36957
  "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
@@ -36907,67 +36977,36 @@ const openapiDocument = {
36907
36977
  } }
36908
36978
  }
36909
36979
  },
36910
- "x-primitive-permission": ["roles:assign"],
36911
- "parameters": [{
36912
- "name": "Primitive-Step-Up",
36913
- "in": "header",
36914
- "required": false,
36915
- "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36916
- "schema": {
36917
- "type": "string",
36918
- "pattern": "^pstep_[0-9a-f]{64}$"
36919
- }
36920
- }, {
36921
- "name": "Idempotency-Key",
36922
- "in": "header",
36923
- "required": false,
36924
- "description": "Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.",
36925
- "schema": {
36926
- "type": "string",
36927
- "minLength": 1,
36928
- "maxLength": 255
36929
- }
36930
- }]
36931
- }
36932
- },
36933
- "/roles/{id}": {
36934
- "patch": {
36935
- "operationId": "updateRole",
36936
- "summary": "Change a custom role",
36937
- "description": "A new version of the role, with a new `id`: the version you edited is retired, the response is the new one (same `key`, `version` one higher), and every member and group holding the role moves to it. The old id answers 404 `not_found` from then on, so store the returned `id` (or look the role up by `key` in GET /roles) before editing or assigning it again. Every holder whose access shrinks goes through the reduction path. mail_scope follows the same rules as creating a role; a role whose stored scope cannot be read must be sent a new mail_scope, otherwise 400 `validation_error` with `details.reason` `invalid_mail_scope`. A personal mailbox the new version adds must be the author's own, and every holder must be the member of each personal mailbox it names: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission in the new version must have what it requires in the same role, as when creating one, including permissions carried over unchanged. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
36938
- "tags": ["Roles and members"],
36939
- "security": [{ "BearerAuth": [] }],
36940
- "parameters": [{
36941
- "name": "id",
36942
- "in": "path",
36943
- "required": true,
36944
- "description": "The id of the group, role, member, grant or suspension the path names.",
36945
- "schema": {
36946
- "type": "string",
36947
- "format": "uuid"
36948
- }
36949
- }, {
36950
- "name": "Primitive-Step-Up",
36951
- "in": "header",
36952
- "required": false,
36953
- "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
36954
- "schema": {
36955
- "type": "string",
36956
- "pattern": "^pstep_[0-9a-f]{64}$"
36957
- }
36958
- }],
36959
36980
  "requestBody": {
36960
36981
  "required": true,
36961
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateRoleRequest" } } }
36982
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailNotifications_updateEmailNotificationSettings_Input" } } }
36962
36983
  },
36984
+ "x-primitive-permission": ["notification_policy:manage"]
36985
+ }
36986
+ },
36987
+ "/email-notification-preferences": {
36988
+ "get": {
36989
+ "operationId": "getEmailNotificationPreferences",
36990
+ "summary": "Get my email notification preferences",
36991
+ "tags": ["Email notifications"],
36992
+ "security": [{ "BearerAuth": [] }],
36993
+ "description": "The notification emails the calling member gets, and which of them they may turn on or off. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). The person's role must hold notifications:manage. API keys, agent connection keys and agents are refused.",
36994
+ "x-primitive-person-only": { "access": "person" },
36995
+ "parameters": [],
36963
36996
  "responses": {
36964
36997
  "200": {
36965
- "description": "Change a custom role",
36966
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
36998
+ "description": "Success",
36999
+ "content": { "application/json": { "schema": {
36967
37000
  "type": "object",
36968
- "required": ["data"],
36969
- "properties": { "data": { "$ref": "#/components/schemas/UpdateRoleResult" } }
36970
- }] } } },
37001
+ "required": ["success", "data"],
37002
+ "properties": {
37003
+ "success": {
37004
+ "type": "boolean",
37005
+ "const": true
37006
+ },
37007
+ "data": { "$ref": "#/components/schemas/EmailNotifications_getEmailNotificationPreferences_Result" }
37008
+ }
37009
+ } } },
36971
37010
  "headers": {
36972
37011
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
36973
37012
  "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
@@ -37021,12 +37060,17 @@ const openapiDocument = {
37021
37060
  } }
37022
37061
  },
37023
37062
  "404": {
37024
- "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
37025
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37026
- },
37027
- "409": {
37028
- "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
37029
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37063
+ "description": "Resource not found",
37064
+ "content": { "application/json": {
37065
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37066
+ "example": {
37067
+ "success": false,
37068
+ "error": {
37069
+ "code": "not_found",
37070
+ "message": "Resource not found"
37071
+ }
37072
+ }
37073
+ } }
37030
37074
  },
37031
37075
  "429": {
37032
37076
  "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
@@ -37052,41 +37096,504 @@ const openapiDocument = {
37052
37096
  } }
37053
37097
  }
37054
37098
  },
37055
- "x-primitive-permission": ["roles:assign"]
37099
+ "x-primitive-permission": ["notifications:manage"]
37056
37100
  },
37057
- "delete": {
37058
- "operationId": "deleteRole",
37059
- "summary": "Delete a custom role",
37060
- "description": "Only a role nobody holds. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
37061
- "tags": ["Roles and members"],
37101
+ "put": {
37102
+ "operationId": "updateEmailNotificationPreferences",
37103
+ "summary": "Change my email notification preferences",
37104
+ "tags": ["Email notifications"],
37062
37105
  "security": [{ "BearerAuth": [] }],
37063
- "parameters": [{
37064
- "name": "id",
37065
- "in": "path",
37066
- "required": true,
37067
- "description": "The id of the group, role, member, grant or suspension the path names.",
37068
- "schema": {
37069
- "type": "string",
37070
- "format": "uuid"
37071
- }
37072
- }, {
37073
- "name": "Primitive-Step-Up",
37074
- "in": "header",
37075
- "required": false,
37076
- "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
37077
- "schema": {
37078
- "type": "string",
37079
- "pattern": "^pstep_[0-9a-f]{64}$"
37080
- }
37081
- }],
37106
+ "description": "Turn one or more kinds on or off for the calling member. Only a kind whose organization mode is optional_on or optional_off can be changed: a platform_required kind, one the organization requires, and one it turned off are refused with 409 (conflict, details.reason not_member_choice, details.org_mode saying which), and nothing is changed. An unknown kind is refused with 400. Answers the preferences as they now stand. Each kind lists its label and description, its class, whether it needs an entitlement and whether the organization has it, the organization's mode (null for platform_required) and the modes it may choose, and whether the calling member gets it and may change that.\n\nPerson only: a dashboard session or the person's own OAuth token (the CLI or an app they connected). The person's role must hold notifications:manage. API keys, agent connection keys and agents are refused.",
37107
+ "x-primitive-person-only": { "access": "person" },
37108
+ "parameters": [],
37082
37109
  "responses": {
37083
37110
  "200": {
37084
- "description": "Delete a custom role",
37085
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
37111
+ "description": "Success",
37112
+ "content": { "application/json": { "schema": {
37086
37113
  "type": "object",
37087
- "required": ["data"],
37088
- "properties": { "data": { "$ref": "#/components/schemas/DeleteRoleResult" } }
37089
- }] } } },
37114
+ "required": ["success", "data"],
37115
+ "properties": {
37116
+ "success": {
37117
+ "type": "boolean",
37118
+ "const": true
37119
+ },
37120
+ "data": { "$ref": "#/components/schemas/EmailNotifications_updateEmailNotificationPreferences_Result" }
37121
+ }
37122
+ } } },
37123
+ "headers": {
37124
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
37125
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
37126
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37127
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
37128
+ }
37129
+ },
37130
+ "400": {
37131
+ "description": "Invalid request parameters",
37132
+ "content": { "application/json": {
37133
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37134
+ "example": {
37135
+ "success": false,
37136
+ "error": {
37137
+ "code": "validation_error",
37138
+ "message": "Invalid domain format"
37139
+ }
37140
+ }
37141
+ } }
37142
+ },
37143
+ "401": {
37144
+ "description": "Invalid or missing API key",
37145
+ "content": { "application/json": {
37146
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37147
+ "example": {
37148
+ "success": false,
37149
+ "error": {
37150
+ "code": "unauthorized",
37151
+ "message": "Invalid or missing API key"
37152
+ }
37153
+ }
37154
+ } },
37155
+ "headers": {
37156
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
37157
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
37158
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
37159
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
37160
+ }
37161
+ },
37162
+ "403": {
37163
+ "description": "Authenticated caller lacks permission for the operation",
37164
+ "content": { "application/json": {
37165
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37166
+ "example": {
37167
+ "success": false,
37168
+ "error": {
37169
+ "code": "forbidden",
37170
+ "message": "Insufficient permissions"
37171
+ }
37172
+ }
37173
+ } }
37174
+ },
37175
+ "404": {
37176
+ "description": "Resource not found",
37177
+ "content": { "application/json": {
37178
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37179
+ "example": {
37180
+ "success": false,
37181
+ "error": {
37182
+ "code": "not_found",
37183
+ "message": "Resource not found"
37184
+ }
37185
+ }
37186
+ } }
37187
+ },
37188
+ "409": {
37189
+ "description": "The request conflicts with the current state of the resource",
37190
+ "content": { "application/json": {
37191
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37192
+ "example": {
37193
+ "success": false,
37194
+ "error": {
37195
+ "code": "conflict",
37196
+ "message": "The request conflicts with the current state of the resource"
37197
+ }
37198
+ }
37199
+ } }
37200
+ },
37201
+ "429": {
37202
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
37203
+ "headers": {
37204
+ "Retry-After": {
37205
+ "schema": { "type": "integer" },
37206
+ "description": "Seconds to wait before retrying"
37207
+ },
37208
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
37209
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
37210
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37211
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
37212
+ },
37213
+ "content": { "application/json": {
37214
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37215
+ "example": {
37216
+ "success": false,
37217
+ "error": {
37218
+ "code": "rate_limit_exceeded",
37219
+ "message": "Rate limit exceeded"
37220
+ }
37221
+ }
37222
+ } }
37223
+ }
37224
+ },
37225
+ "requestBody": {
37226
+ "required": true,
37227
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailNotifications_updateEmailNotificationPreferences_Input" } } }
37228
+ },
37229
+ "x-primitive-permission": ["notifications:manage"]
37230
+ }
37231
+ },
37232
+ "/roles": {
37233
+ "get": {
37234
+ "operationId": "listRoles",
37235
+ "summary": "List roles",
37236
+ "description": "Every role in the organization (built-in, template copies and custom roles), with how many members and groups hold each, the templates not yet copied, and whether roles can be assigned now. `unassigned` describes role restricted on a member with no role_id, which is not a role and is never in `roles`. Needs members:read (owners, admins, members and roles that grant it); API keys are refused.",
37237
+ "tags": ["Roles and members"],
37238
+ "security": [{ "BearerAuth": [] }],
37239
+ "responses": {
37240
+ "200": {
37241
+ "description": "List roles",
37242
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
37243
+ "type": "object",
37244
+ "required": ["data"],
37245
+ "properties": { "data": { "$ref": "#/components/schemas/ListRolesResult" } }
37246
+ }] } } },
37247
+ "headers": {
37248
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
37249
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
37250
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37251
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
37252
+ }
37253
+ },
37254
+ "401": {
37255
+ "description": "Invalid or missing API key",
37256
+ "content": { "application/json": {
37257
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37258
+ "example": {
37259
+ "success": false,
37260
+ "error": {
37261
+ "code": "unauthorized",
37262
+ "message": "Invalid or missing API key"
37263
+ }
37264
+ }
37265
+ } },
37266
+ "headers": {
37267
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
37268
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
37269
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
37270
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
37271
+ }
37272
+ },
37273
+ "403": {
37274
+ "description": "Authenticated caller lacks permission for the operation",
37275
+ "content": { "application/json": {
37276
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37277
+ "example": {
37278
+ "success": false,
37279
+ "error": {
37280
+ "code": "forbidden",
37281
+ "message": "Insufficient permissions"
37282
+ }
37283
+ }
37284
+ } }
37285
+ },
37286
+ "404": {
37287
+ "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
37288
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37289
+ },
37290
+ "409": {
37291
+ "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
37292
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37293
+ },
37294
+ "429": {
37295
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
37296
+ "headers": {
37297
+ "Retry-After": {
37298
+ "schema": { "type": "integer" },
37299
+ "description": "Seconds to wait before retrying"
37300
+ },
37301
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
37302
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
37303
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37304
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
37305
+ },
37306
+ "content": { "application/json": {
37307
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37308
+ "example": {
37309
+ "success": false,
37310
+ "error": {
37311
+ "code": "rate_limit_exceeded",
37312
+ "message": "Rate limit exceeded"
37313
+ }
37314
+ }
37315
+ } }
37316
+ }
37317
+ },
37318
+ "x-primitive-permission": ["members:read"],
37319
+ "x-primitive-builtin-role-keys": { "$ref": "#/components/schemas/BuiltinRoleKey" }
37320
+ },
37321
+ "post": {
37322
+ "operationId": "createRole",
37323
+ "summary": "Create a custom role",
37324
+ "description": "A role nobody can use to gain access they do not hold: its permissions and mail scope must be within the author's own. mail_scope is \"org\" (the default) or { addresses, domains }; any other value is refused with 400 `validation_error`. Every role that reaches mail reads it in full, and raw MIME follows the role's mail.raw:read; `depth` is deprecated and ignored. Its mail scope names no other member's personal mailbox: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission must have what it requires in the same role: mail:read for mail.raw:read, mail.search:semantic, mail:share_own, changes:read and webhooks:read; mail:send for mail:send_batch and mail:send_as_member; functions:deploy for templates:install; agent_connections:manage for agent_connections:custom_domains. Otherwise 400 `validation_error` with `details.reason` `unmet_requirement`, `details.permission` and `details.requires`. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
37325
+ "tags": ["Roles and members"],
37326
+ "security": [{ "BearerAuth": [] }],
37327
+ "requestBody": {
37328
+ "required": true,
37329
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateRoleRequest" } } }
37330
+ },
37331
+ "responses": {
37332
+ "201": {
37333
+ "description": "Create a custom role",
37334
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
37335
+ "type": "object",
37336
+ "required": ["data"],
37337
+ "properties": { "data": { "$ref": "#/components/schemas/CreateRoleResult" } }
37338
+ }] } } }
37339
+ },
37340
+ "400": {
37341
+ "description": "Invalid request parameters",
37342
+ "content": { "application/json": {
37343
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37344
+ "example": {
37345
+ "success": false,
37346
+ "error": {
37347
+ "code": "validation_error",
37348
+ "message": "Invalid domain format"
37349
+ }
37350
+ }
37351
+ } }
37352
+ },
37353
+ "401": {
37354
+ "description": "Invalid or missing API key",
37355
+ "content": { "application/json": {
37356
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37357
+ "example": {
37358
+ "success": false,
37359
+ "error": {
37360
+ "code": "unauthorized",
37361
+ "message": "Invalid or missing API key"
37362
+ }
37363
+ }
37364
+ } },
37365
+ "headers": {
37366
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
37367
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
37368
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
37369
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
37370
+ }
37371
+ },
37372
+ "403": {
37373
+ "description": "Authenticated caller lacks permission for the operation",
37374
+ "content": { "application/json": {
37375
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37376
+ "example": {
37377
+ "success": false,
37378
+ "error": {
37379
+ "code": "forbidden",
37380
+ "message": "Insufficient permissions"
37381
+ }
37382
+ }
37383
+ } }
37384
+ },
37385
+ "404": {
37386
+ "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
37387
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37388
+ },
37389
+ "409": {
37390
+ "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
37391
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37392
+ },
37393
+ "429": {
37394
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
37395
+ "headers": {
37396
+ "Retry-After": {
37397
+ "schema": { "type": "integer" },
37398
+ "description": "Seconds to wait before retrying"
37399
+ },
37400
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
37401
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
37402
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37403
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
37404
+ },
37405
+ "content": { "application/json": {
37406
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37407
+ "example": {
37408
+ "success": false,
37409
+ "error": {
37410
+ "code": "rate_limit_exceeded",
37411
+ "message": "Rate limit exceeded"
37412
+ }
37413
+ }
37414
+ } }
37415
+ }
37416
+ },
37417
+ "x-primitive-permission": ["roles:assign"],
37418
+ "parameters": [{
37419
+ "name": "Primitive-Step-Up",
37420
+ "in": "header",
37421
+ "required": false,
37422
+ "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
37423
+ "schema": {
37424
+ "type": "string",
37425
+ "pattern": "^pstep_[0-9a-f]{64}$"
37426
+ }
37427
+ }, {
37428
+ "name": "Idempotency-Key",
37429
+ "in": "header",
37430
+ "required": false,
37431
+ "description": "Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.",
37432
+ "schema": {
37433
+ "type": "string",
37434
+ "minLength": 1,
37435
+ "maxLength": 255
37436
+ }
37437
+ }]
37438
+ }
37439
+ },
37440
+ "/roles/{id}": {
37441
+ "patch": {
37442
+ "operationId": "updateRole",
37443
+ "summary": "Change a custom role",
37444
+ "description": "A new version of the role, with a new `id`: the version you edited is retired, the response is the new one (same `key`, `version` one higher), and every member and group holding the role moves to it. The old id answers 404 `not_found` from then on, so store the returned `id` (or look the role up by `key` in GET /roles) before editing or assigning it again. Every holder whose access shrinks goes through the reduction path. mail_scope follows the same rules as creating a role; a role whose stored scope cannot be read must be sent a new mail_scope, otherwise 400 `validation_error` with `details.reason` `invalid_mail_scope`. A personal mailbox the new version adds must be the author's own, and every holder must be the member of each personal mailbox it names: otherwise 403 `grant_forbidden` with `details.reason` `personal_mailbox_member_only`. Each permission in the new version must have what it requires in the same role, as when creating one, including permissions carried over unchanged. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
37445
+ "tags": ["Roles and members"],
37446
+ "security": [{ "BearerAuth": [] }],
37447
+ "parameters": [{
37448
+ "name": "id",
37449
+ "in": "path",
37450
+ "required": true,
37451
+ "description": "The id of the group, role, member, grant or suspension the path names.",
37452
+ "schema": {
37453
+ "type": "string",
37454
+ "format": "uuid"
37455
+ }
37456
+ }, {
37457
+ "name": "Primitive-Step-Up",
37458
+ "in": "header",
37459
+ "required": false,
37460
+ "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
37461
+ "schema": {
37462
+ "type": "string",
37463
+ "pattern": "^pstep_[0-9a-f]{64}$"
37464
+ }
37465
+ }],
37466
+ "requestBody": {
37467
+ "required": true,
37468
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateRoleRequest" } } }
37469
+ },
37470
+ "responses": {
37471
+ "200": {
37472
+ "description": "Change a custom role",
37473
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
37474
+ "type": "object",
37475
+ "required": ["data"],
37476
+ "properties": { "data": { "$ref": "#/components/schemas/UpdateRoleResult" } }
37477
+ }] } } },
37478
+ "headers": {
37479
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
37480
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
37481
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37482
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
37483
+ }
37484
+ },
37485
+ "400": {
37486
+ "description": "Invalid request parameters",
37487
+ "content": { "application/json": {
37488
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37489
+ "example": {
37490
+ "success": false,
37491
+ "error": {
37492
+ "code": "validation_error",
37493
+ "message": "Invalid domain format"
37494
+ }
37495
+ }
37496
+ } }
37497
+ },
37498
+ "401": {
37499
+ "description": "Invalid or missing API key",
37500
+ "content": { "application/json": {
37501
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37502
+ "example": {
37503
+ "success": false,
37504
+ "error": {
37505
+ "code": "unauthorized",
37506
+ "message": "Invalid or missing API key"
37507
+ }
37508
+ }
37509
+ } },
37510
+ "headers": {
37511
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
37512
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
37513
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
37514
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
37515
+ }
37516
+ },
37517
+ "403": {
37518
+ "description": "Authenticated caller lacks permission for the operation",
37519
+ "content": { "application/json": {
37520
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37521
+ "example": {
37522
+ "success": false,
37523
+ "error": {
37524
+ "code": "forbidden",
37525
+ "message": "Insufficient permissions"
37526
+ }
37527
+ }
37528
+ } }
37529
+ },
37530
+ "404": {
37531
+ "description": "feature_disabled: the feature is not enabled for the organization; or not_found: the item the path names does not exist.",
37532
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37533
+ },
37534
+ "409": {
37535
+ "description": "conflict: the item changed while you edited it, or a member was removed and added back. Look again, then retry.",
37536
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
37537
+ },
37538
+ "429": {
37539
+ "description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
37540
+ "headers": {
37541
+ "Retry-After": {
37542
+ "schema": { "type": "integer" },
37543
+ "description": "Seconds to wait before retrying"
37544
+ },
37545
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
37546
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
37547
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
37548
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
37549
+ },
37550
+ "content": { "application/json": {
37551
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
37552
+ "example": {
37553
+ "success": false,
37554
+ "error": {
37555
+ "code": "rate_limit_exceeded",
37556
+ "message": "Rate limit exceeded"
37557
+ }
37558
+ }
37559
+ } }
37560
+ }
37561
+ },
37562
+ "x-primitive-permission": ["roles:assign"]
37563
+ },
37564
+ "delete": {
37565
+ "operationId": "deleteRole",
37566
+ "summary": "Delete a custom role",
37567
+ "description": "Only a role nobody holds. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `roles.write` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs roles:assign. API keys are refused.",
37568
+ "tags": ["Roles and members"],
37569
+ "security": [{ "BearerAuth": [] }],
37570
+ "parameters": [{
37571
+ "name": "id",
37572
+ "in": "path",
37573
+ "required": true,
37574
+ "description": "The id of the group, role, member, grant or suspension the path names.",
37575
+ "schema": {
37576
+ "type": "string",
37577
+ "format": "uuid"
37578
+ }
37579
+ }, {
37580
+ "name": "Primitive-Step-Up",
37581
+ "in": "header",
37582
+ "required": false,
37583
+ "description": "A single-use step-up token of purpose `roles.write` (Change a member role), valid 5 minutes. When this operation answers 403 `step_up_required`, the person confirms at the link in `details.step_up_url` and the request is retried with the token that page shows, in this header. Accepted only with the person's own OAuth token of the client it was confirmed for; refused with a token issued for MCP, with any API key or agent connection key, and with a dashboard session.",
37584
+ "schema": {
37585
+ "type": "string",
37586
+ "pattern": "^pstep_[0-9a-f]{64}$"
37587
+ }
37588
+ }],
37589
+ "responses": {
37590
+ "200": {
37591
+ "description": "Delete a custom role",
37592
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
37593
+ "type": "object",
37594
+ "required": ["data"],
37595
+ "properties": { "data": { "$ref": "#/components/schemas/DeleteRoleResult" } }
37596
+ }] } } },
37090
37597
  "headers": {
37091
37598
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
37092
37599
  "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
@@ -40654,6 +41161,7 @@ const openapiDocument = {
40654
41161
  "agent_work_version_conflict",
40655
41162
  "already_claimed",
40656
41163
  "already_member",
41164
+ "always_sent",
40657
41165
  "archive_busy",
40658
41166
  "archive_invalid",
40659
41167
  "archive_limit_exceeded",
@@ -40846,6 +41354,7 @@ const openapiDocument = {
40846
41354
  "removal_failed",
40847
41355
  "removal_timeout",
40848
41356
  "request_expired",
41357
+ "required_by_organization",
40849
41358
  "resend_limit_exceeded",
40850
41359
  "resend_too_soon",
40851
41360
  "role_assigned",
@@ -50757,7 +51266,7 @@ const openapiDocument = {
50757
51266
  },
50758
51267
  "state": {
50759
51268
  "type": "string",
50760
- "description": "Where it stands now: enabled, disabled, deleted or replaced (endpoints and routes), enabled or disabled (filters, wake schedules), live, suspended or deleted (Functions), active, paused or ended (repeating sends), scheduled (scheduled sends), active, suspended, expired or revoked (API keys), pending, claimed, connected or revoked (agents), draft, observe or active (email rules), pending, accepted, declined or withdrawn (rule requests and objections), and for a coverage notice what it told: started (a rule was made or turned active over your mailboxes, or an active rule came to reach you; once per rule, not per mailbox), new_version, or baseline (it already covered you when notices began). pending: a rule started covering your mailbox and the notices have not recorded that start yet (a restart within an hour of the last start notice is recorded without a new one); its id is then the rule's, and created_at when the rule last changed."
51269
+ "description": "Where it stands now: enabled, disabled, deleted or replaced (endpoints and routes), enabled or disabled (filters, wake schedules), live, suspended or deleted (Functions), active, paused or ended (repeating sends), scheduled (scheduled sends), active, suspended, expired or revoked (API keys), pending, claimed, connected or revoked (agents), draft, observe or active (email rules), pending, accepted, declined or withdrawn (rule requests and objections), and for a coverage notice what it told: started (a rule was made or turned active over your mailboxes, or an active rule came to reach you; once per rule, not per mailbox), new_version, receivers_changed (who receives an active rule's matches changed: its keys, webhooks or share), or baseline (it already covered you when notices began). pending: a rule started covering your mailbox and the notices have not recorded that start yet (a restart within an hour of the last start notice is recorded without a new one); its id is then the rule's, and created_at when the rule last changed."
50761
51270
  },
50762
51271
  "created_at": {
50763
51272
  "type": "string",
@@ -50799,6 +51308,69 @@ const openapiDocument = {
50799
51308
  "description": "For email_rule_coverage, always: the rule it is about, since names need not be unique (for pending, the same as id). GET /v1/email-rules/covering-me/{rule_id} shows the rule while it covers you, or as it last decided on your mail; otherwise it answers not_found.",
50800
51309
  "type": "string",
50801
51310
  "format": "uuid"
51311
+ },
51312
+ "receivers_change": {
51313
+ "description": "For a receivers_changed notice: who received the rule's matches before and after, in totals. They can be equal when only an owner or a webhook site changed; covering-me shows them by owner. A rotated key counts once here while the old one still works; covering-me counts both.",
51314
+ "type": "object",
51315
+ "properties": {
51316
+ "before": {
51317
+ "type": "object",
51318
+ "properties": {
51319
+ "keys": {
51320
+ "type": "integer",
51321
+ "minimum": 0
51322
+ },
51323
+ "reads_in_full": {
51324
+ "type": "integer",
51325
+ "minimum": 0,
51326
+ "description": "Of the keys, how many also read your own mail beyond the rule."
51327
+ },
51328
+ "webhooks": {
51329
+ "type": "integer",
51330
+ "minimum": 0
51331
+ },
51332
+ "shares": {
51333
+ "type": "integer",
51334
+ "minimum": 0
51335
+ }
51336
+ },
51337
+ "required": [
51338
+ "keys",
51339
+ "reads_in_full",
51340
+ "webhooks",
51341
+ "shares"
51342
+ ]
51343
+ },
51344
+ "after": {
51345
+ "type": "object",
51346
+ "properties": {
51347
+ "keys": {
51348
+ "type": "integer",
51349
+ "minimum": 0
51350
+ },
51351
+ "reads_in_full": {
51352
+ "type": "integer",
51353
+ "minimum": 0,
51354
+ "description": "Of the keys, how many also read your own mail beyond the rule."
51355
+ },
51356
+ "webhooks": {
51357
+ "type": "integer",
51358
+ "minimum": 0
51359
+ },
51360
+ "shares": {
51361
+ "type": "integer",
51362
+ "minimum": 0
51363
+ }
51364
+ },
51365
+ "required": [
51366
+ "keys",
51367
+ "reads_in_full",
51368
+ "webhooks",
51369
+ "shares"
51370
+ ]
51371
+ }
51372
+ },
51373
+ "required": ["before", "after"]
50802
51374
  }
50803
51375
  },
50804
51376
  "required": [
@@ -53469,115 +54041,130 @@ const openapiDocument = {
53469
54041
  "user_id",
53470
54042
  "name"
53471
54043
  ]
53472
- }, {
53473
- "type": "object",
53474
- "properties": { "kind": {
53475
- "type": "string",
53476
- "const": "organization",
53477
- "description": "Held by no member: an organization key, or one whose holder has left."
53478
- } },
53479
- "required": ["kind"]
53480
- }] },
53481
- "count": {
53482
- "type": "integer",
53483
- "exclusiveMinimum": 0
54044
+ }, {
54045
+ "type": "object",
54046
+ "properties": { "kind": {
54047
+ "type": "string",
54048
+ "const": "organization",
54049
+ "description": "Held by no member: an organization key, or one whose holder has left."
54050
+ } },
54051
+ "required": ["kind"]
54052
+ }] },
54053
+ "count": {
54054
+ "type": "integer",
54055
+ "exclusiveMinimum": 0
54056
+ },
54057
+ "reads_in_full": {
54058
+ "description": "On a rule over your mail: how many of these keys also list one of your own mailboxes, or its domain, so they read your mail there beyond what this rule matches.",
54059
+ "type": "integer",
54060
+ "minimum": 0
54061
+ },
54062
+ "newest_at": {
54063
+ "description": "When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.",
54064
+ "type": "string",
54065
+ "format": "date-time"
54066
+ }
54067
+ },
54068
+ "required": ["owner", "count"]
54069
+ },
54070
+ "description": "API keys that work now (not revoked, expired or suspended) and hold mail:read, whose access lists the rule or its live share. Owner: who holds each, or the organization."
54071
+ },
54072
+ "webhooks": {
54073
+ "type": "array",
54074
+ "items": {
54075
+ "type": "object",
54076
+ "properties": {
54077
+ "owner": { "anyOf": [{
54078
+ "type": "object",
54079
+ "properties": {
54080
+ "kind": {
54081
+ "type": "string",
54082
+ "const": "member"
54083
+ },
54084
+ "user_id": {
54085
+ "type": "string",
54086
+ "description": "The member's user id.",
54087
+ "format": "uuid"
54088
+ },
54089
+ "name": {
54090
+ "description": "Their name; null when they have none set.",
54091
+ "type": ["string", "null"]
54092
+ }
54093
+ },
54094
+ "required": [
54095
+ "kind",
54096
+ "user_id",
54097
+ "name"
54098
+ ]
54099
+ }, {
54100
+ "type": "object",
54101
+ "properties": { "kind": {
54102
+ "type": "string",
54103
+ "const": "organization",
54104
+ "description": "Held by no member: an organization key, or one whose holder has left."
54105
+ } },
54106
+ "required": ["kind"]
54107
+ }] },
54108
+ "count": {
54109
+ "type": "integer",
54110
+ "exclusiveMinimum": 0
54111
+ },
54112
+ "hosts": {
54113
+ "description": "Distinct sites they deliver to (the domain a host is registered under, an app's own name on a known app platform, or a function URL service in its region), sorted, at most 10; \"unknown host\" (last) for any that cannot be named.",
54114
+ "type": "array",
54115
+ "items": { "type": "string" }
54116
+ },
54117
+ "hosts_not_listed": {
54118
+ "description": "Hosts past those listed.",
54119
+ "type": "integer",
54120
+ "minimum": 0
54121
+ },
54122
+ "newest_at": {
54123
+ "description": "When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.",
54124
+ "type": "string",
54125
+ "format": "date-time"
54126
+ }
54127
+ },
54128
+ "required": ["owner", "count"]
54129
+ },
54130
+ "description": "Webhooks taking received mail, bounces or reports that are sent what it matches: those of the key delivery acts as, when it lists the rule or its live share (owner: its holder, or the organization), and those of the member it is shared with (owner: that member)."
54131
+ },
54132
+ "shares": {
54133
+ "type": "array",
54134
+ "items": {
54135
+ "type": "object",
54136
+ "properties": {
54137
+ "beneficiary": {
54138
+ "type": "object",
54139
+ "properties": {
54140
+ "kind": {
54141
+ "type": "string",
54142
+ "const": "member"
54143
+ },
54144
+ "user_id": {
54145
+ "type": "string",
54146
+ "description": "The member's user id.",
54147
+ "format": "uuid"
54148
+ },
54149
+ "name": {
54150
+ "description": "Their name; null when they have none set.",
54151
+ "type": ["string", "null"]
54152
+ }
54153
+ },
54154
+ "required": [
54155
+ "kind",
54156
+ "user_id",
54157
+ "name"
54158
+ ]
53484
54159
  },
53485
- "reads_in_full": {
53486
- "description": "On a rule over your mail: how many of these keys also list one of your own mailboxes, or its domain, so they read your mail there beyond what this rule matches.",
53487
- "type": "integer",
53488
- "minimum": 0
53489
- }
53490
- },
53491
- "required": ["owner", "count"]
53492
- },
53493
- "description": "API keys that work now (not revoked, expired or suspended) and hold mail:read, whose access lists the rule or its live share. Owner: who holds each, or the organization."
53494
- },
53495
- "webhooks": {
53496
- "type": "array",
53497
- "items": {
53498
- "type": "object",
53499
- "properties": {
53500
- "owner": { "anyOf": [{
53501
- "type": "object",
53502
- "properties": {
53503
- "kind": {
53504
- "type": "string",
53505
- "const": "member"
53506
- },
53507
- "user_id": {
53508
- "type": "string",
53509
- "description": "The member's user id.",
53510
- "format": "uuid"
53511
- },
53512
- "name": {
53513
- "description": "Their name; null when they have none set.",
53514
- "type": ["string", "null"]
53515
- }
53516
- },
53517
- "required": [
53518
- "kind",
53519
- "user_id",
53520
- "name"
53521
- ]
53522
- }, {
53523
- "type": "object",
53524
- "properties": { "kind": {
53525
- "type": "string",
53526
- "const": "organization",
53527
- "description": "Held by no member: an organization key, or one whose holder has left."
53528
- } },
53529
- "required": ["kind"]
53530
- }] },
53531
54160
  "count": {
53532
54161
  "type": "integer",
53533
54162
  "exclusiveMinimum": 0
53534
54163
  },
53535
- "hosts": {
53536
- "description": "Distinct sites they deliver to (the domain a host is registered under, an app's own name on a known app platform, or a function URL service in its region), sorted, at most 10; \"unknown host\" (last) for any that cannot be named.",
53537
- "type": "array",
53538
- "items": { "type": "string" }
53539
- },
53540
- "hosts_not_listed": {
53541
- "description": "Hosts past those listed.",
53542
- "type": "integer",
53543
- "minimum": 0
53544
- }
53545
- },
53546
- "required": ["owner", "count"]
53547
- },
53548
- "description": "Webhooks taking received mail, bounces or reports that are sent what it matches: those of the key delivery acts as, when it lists the rule or its live share (owner: its holder, or the organization), and those of the member it is shared with (owner: that member)."
53549
- },
53550
- "shares": {
53551
- "type": "array",
53552
- "items": {
53553
- "type": "object",
53554
- "properties": {
53555
- "beneficiary": {
53556
- "type": "object",
53557
- "properties": {
53558
- "kind": {
53559
- "type": "string",
53560
- "const": "member"
53561
- },
53562
- "user_id": {
53563
- "type": "string",
53564
- "description": "The member's user id.",
53565
- "format": "uuid"
53566
- },
53567
- "name": {
53568
- "description": "Their name; null when they have none set.",
53569
- "type": ["string", "null"]
53570
- }
53571
- },
53572
- "required": [
53573
- "kind",
53574
- "user_id",
53575
- "name"
53576
- ]
53577
- },
53578
- "count": {
53579
- "type": "integer",
53580
- "exclusiveMinimum": 0
54164
+ "newest_at": {
54165
+ "description": "When the newest of these was made, to the minute: a key, a webhook or the share, or the rule (the share, for what reaches it only through the share) if that was later. Not when a key was given access to this rule, or anything turned back on; a rotated key shows as newly made, and one key on two rules can show the same minute under both.",
54166
+ "type": "string",
54167
+ "format": "date-time"
53581
54168
  }
53582
54169
  },
53583
54170
  "required": ["beneficiary", "count"]
@@ -59904,6 +60491,14 @@ const openapiDocument = {
59904
60491
  "additionalProperties": {}
59905
60492
  }
59906
60493
  },
60494
+ "mail_limits_available": {
60495
+ "description": "Whether a key limited to some mail (addresses, domains or email rules) would be created now. While false, creating one is refused, so a client offers neither the listed-address presets nor a listed mail reach for a custom key.",
60496
+ "type": "boolean"
60497
+ },
60498
+ "org_reach_available": {
60499
+ "description": "Whether the credential policy lets a new key reach every mailbox. While false, creating one is refused, so a client offers no preset that reaches every mailbox, and a custom key lists its addresses or domains. With mail_limits_available false as well, no key can be created and every preset is unavailable.",
60500
+ "type": "boolean"
60501
+ },
59907
60502
  "matrix": {
59908
60503
  "type": "array",
59909
60504
  "items": {
@@ -61178,6 +61773,304 @@ const openapiDocument = {
61178
61773
  "required": ["semantic_search_opt_in"],
61179
61774
  "additionalProperties": false
61180
61775
  },
61776
+ "EmailNotifications_getEmailNotificationSettings_Result": {
61777
+ "type": "object",
61778
+ "properties": {
61779
+ "entitled": { "type": "boolean" },
61780
+ "kinds": {
61781
+ "type": "array",
61782
+ "items": {
61783
+ "type": "object",
61784
+ "properties": {
61785
+ "kind": { "type": "string" },
61786
+ "label": { "type": "string" },
61787
+ "description": { "type": "string" },
61788
+ "class": {
61789
+ "type": "string",
61790
+ "enum": [
61791
+ "platform_required",
61792
+ "org_requirable",
61793
+ "optional"
61794
+ ]
61795
+ },
61796
+ "entitlement_required": { "type": "boolean" },
61797
+ "entitled": { "type": "boolean" },
61798
+ "org_mode": {
61799
+ "type": ["string", "null"],
61800
+ "enum": [
61801
+ "off",
61802
+ "optional_on",
61803
+ "optional_off",
61804
+ "required",
61805
+ null
61806
+ ]
61807
+ },
61808
+ "org_mode_is_default": { "type": "boolean" },
61809
+ "org_modes": {
61810
+ "type": "array",
61811
+ "items": {
61812
+ "type": "string",
61813
+ "enum": [
61814
+ "off",
61815
+ "optional_on",
61816
+ "optional_off",
61817
+ "required"
61818
+ ]
61819
+ }
61820
+ },
61821
+ "member_enabled": { "type": "boolean" },
61822
+ "member_can_change": { "type": "boolean" }
61823
+ },
61824
+ "required": [
61825
+ "kind",
61826
+ "label",
61827
+ "description",
61828
+ "class",
61829
+ "entitlement_required",
61830
+ "entitled",
61831
+ "org_mode",
61832
+ "org_mode_is_default",
61833
+ "org_modes",
61834
+ "member_enabled",
61835
+ "member_can_change"
61836
+ ]
61837
+ }
61838
+ }
61839
+ },
61840
+ "required": ["entitled", "kinds"]
61841
+ },
61842
+ "EmailNotifications_updateEmailNotificationSettings_Result": {
61843
+ "type": "object",
61844
+ "properties": {
61845
+ "entitled": { "type": "boolean" },
61846
+ "kinds": {
61847
+ "type": "array",
61848
+ "items": {
61849
+ "type": "object",
61850
+ "properties": {
61851
+ "kind": { "type": "string" },
61852
+ "label": { "type": "string" },
61853
+ "description": { "type": "string" },
61854
+ "class": {
61855
+ "type": "string",
61856
+ "enum": [
61857
+ "platform_required",
61858
+ "org_requirable",
61859
+ "optional"
61860
+ ]
61861
+ },
61862
+ "entitlement_required": { "type": "boolean" },
61863
+ "entitled": { "type": "boolean" },
61864
+ "org_mode": {
61865
+ "type": ["string", "null"],
61866
+ "enum": [
61867
+ "off",
61868
+ "optional_on",
61869
+ "optional_off",
61870
+ "required",
61871
+ null
61872
+ ]
61873
+ },
61874
+ "org_mode_is_default": { "type": "boolean" },
61875
+ "org_modes": {
61876
+ "type": "array",
61877
+ "items": {
61878
+ "type": "string",
61879
+ "enum": [
61880
+ "off",
61881
+ "optional_on",
61882
+ "optional_off",
61883
+ "required"
61884
+ ]
61885
+ }
61886
+ },
61887
+ "member_enabled": { "type": "boolean" },
61888
+ "member_can_change": { "type": "boolean" }
61889
+ },
61890
+ "required": [
61891
+ "kind",
61892
+ "label",
61893
+ "description",
61894
+ "class",
61895
+ "entitlement_required",
61896
+ "entitled",
61897
+ "org_mode",
61898
+ "org_mode_is_default",
61899
+ "org_modes",
61900
+ "member_enabled",
61901
+ "member_can_change"
61902
+ ]
61903
+ }
61904
+ }
61905
+ },
61906
+ "required": ["entitled", "kinds"]
61907
+ },
61908
+ "EmailNotifications_updateEmailNotificationSettings_Input": {
61909
+ "type": "object",
61910
+ "properties": { "modes": {
61911
+ "type": "object",
61912
+ "propertyNames": {
61913
+ "type": "string",
61914
+ "pattern": "^[a-z][a-z0-9_]{0,63}$"
61915
+ },
61916
+ "additionalProperties": {
61917
+ "type": "string",
61918
+ "enum": [
61919
+ "off",
61920
+ "optional_on",
61921
+ "optional_off",
61922
+ "required"
61923
+ ]
61924
+ }
61925
+ } },
61926
+ "required": ["modes"],
61927
+ "additionalProperties": false
61928
+ },
61929
+ "EmailNotifications_getEmailNotificationPreferences_Result": {
61930
+ "type": "object",
61931
+ "properties": {
61932
+ "entitled": { "type": "boolean" },
61933
+ "kinds": {
61934
+ "type": "array",
61935
+ "items": {
61936
+ "type": "object",
61937
+ "properties": {
61938
+ "kind": { "type": "string" },
61939
+ "label": { "type": "string" },
61940
+ "description": { "type": "string" },
61941
+ "class": {
61942
+ "type": "string",
61943
+ "enum": [
61944
+ "platform_required",
61945
+ "org_requirable",
61946
+ "optional"
61947
+ ]
61948
+ },
61949
+ "entitlement_required": { "type": "boolean" },
61950
+ "entitled": { "type": "boolean" },
61951
+ "org_mode": {
61952
+ "type": ["string", "null"],
61953
+ "enum": [
61954
+ "off",
61955
+ "optional_on",
61956
+ "optional_off",
61957
+ "required",
61958
+ null
61959
+ ]
61960
+ },
61961
+ "org_mode_is_default": { "type": "boolean" },
61962
+ "org_modes": {
61963
+ "type": "array",
61964
+ "items": {
61965
+ "type": "string",
61966
+ "enum": [
61967
+ "off",
61968
+ "optional_on",
61969
+ "optional_off",
61970
+ "required"
61971
+ ]
61972
+ }
61973
+ },
61974
+ "member_enabled": { "type": "boolean" },
61975
+ "member_can_change": { "type": "boolean" }
61976
+ },
61977
+ "required": [
61978
+ "kind",
61979
+ "label",
61980
+ "description",
61981
+ "class",
61982
+ "entitlement_required",
61983
+ "entitled",
61984
+ "org_mode",
61985
+ "org_mode_is_default",
61986
+ "org_modes",
61987
+ "member_enabled",
61988
+ "member_can_change"
61989
+ ]
61990
+ }
61991
+ }
61992
+ },
61993
+ "required": ["entitled", "kinds"]
61994
+ },
61995
+ "EmailNotifications_updateEmailNotificationPreferences_Result": {
61996
+ "type": "object",
61997
+ "properties": {
61998
+ "entitled": { "type": "boolean" },
61999
+ "kinds": {
62000
+ "type": "array",
62001
+ "items": {
62002
+ "type": "object",
62003
+ "properties": {
62004
+ "kind": { "type": "string" },
62005
+ "label": { "type": "string" },
62006
+ "description": { "type": "string" },
62007
+ "class": {
62008
+ "type": "string",
62009
+ "enum": [
62010
+ "platform_required",
62011
+ "org_requirable",
62012
+ "optional"
62013
+ ]
62014
+ },
62015
+ "entitlement_required": { "type": "boolean" },
62016
+ "entitled": { "type": "boolean" },
62017
+ "org_mode": {
62018
+ "type": ["string", "null"],
62019
+ "enum": [
62020
+ "off",
62021
+ "optional_on",
62022
+ "optional_off",
62023
+ "required",
62024
+ null
62025
+ ]
62026
+ },
62027
+ "org_mode_is_default": { "type": "boolean" },
62028
+ "org_modes": {
62029
+ "type": "array",
62030
+ "items": {
62031
+ "type": "string",
62032
+ "enum": [
62033
+ "off",
62034
+ "optional_on",
62035
+ "optional_off",
62036
+ "required"
62037
+ ]
62038
+ }
62039
+ },
62040
+ "member_enabled": { "type": "boolean" },
62041
+ "member_can_change": { "type": "boolean" }
62042
+ },
62043
+ "required": [
62044
+ "kind",
62045
+ "label",
62046
+ "description",
62047
+ "class",
62048
+ "entitlement_required",
62049
+ "entitled",
62050
+ "org_mode",
62051
+ "org_mode_is_default",
62052
+ "org_modes",
62053
+ "member_enabled",
62054
+ "member_can_change"
62055
+ ]
62056
+ }
62057
+ }
62058
+ },
62059
+ "required": ["entitled", "kinds"]
62060
+ },
62061
+ "EmailNotifications_updateEmailNotificationPreferences_Input": {
62062
+ "type": "object",
62063
+ "properties": { "preferences": {
62064
+ "type": "object",
62065
+ "propertyNames": {
62066
+ "type": "string",
62067
+ "pattern": "^[a-z][a-z0-9_]{0,63}$"
62068
+ },
62069
+ "additionalProperties": { "type": "boolean" }
62070
+ } },
62071
+ "required": ["preferences"],
62072
+ "additionalProperties": false
62073
+ },
61181
62074
  "ListRolesResult": {
61182
62075
  "type": "object",
61183
62076
  "properties": {
@@ -64076,8 +64969,18 @@ const KEY_WRITE_OPERATION_IDS = /* @__PURE__ */ new Set([
64076
64969
  "credentials:revoke-credential"
64077
64970
  ]);
64078
64971
  const CREDENTIAL_HEADER_OPERATION_IDS = /* @__PURE__ */ new Set(["emails:import-google-mail-message"]);
64972
+ const NOT_YET_OFFERED_OPERATION_IDS = /* @__PURE__ */ new Set([
64973
+ "email-notifications:get-email-notification-preferences",
64974
+ "email-notifications:update-email-notification-preferences",
64975
+ "email-notifications:get-email-notification-settings",
64976
+ "email-notifications:update-email-notification-settings"
64977
+ ]);
64079
64978
  /** Every operation id the CLI generates no command for. */
64080
- const CLI_OMITTED_OPERATION_IDS = /* @__PURE__ */ new Set([...KEY_WRITE_OPERATION_IDS, ...CREDENTIAL_HEADER_OPERATION_IDS]);
64979
+ const CLI_OMITTED_OPERATION_IDS = /* @__PURE__ */ new Set([
64980
+ ...KEY_WRITE_OPERATION_IDS,
64981
+ ...CREDENTIAL_HEADER_OPERATION_IDS,
64982
+ ...NOT_YET_OFFERED_OPERATION_IDS
64983
+ ]);
64081
64984
  /** The operations the CLI generates commands for: the manifest without CLI_OMITTED_OPERATION_IDS. */
64082
64985
  const cliOperationManifest = operationManifest.filter((operation) => !CLI_OMITTED_OPERATION_IDS.has(`${operation.tagCommand}:${operation.command}`));
64083
64986
  // src/oclif/commands/activity.ts
@@ -64091,16 +64994,79 @@ const API_BASE_URL_FLAG$4 = Flags.string({
64091
64994
  hidden: true
64092
64995
  });
64093
64996
  const DEFAULT_LIMIT$3 = 50;
64094
- async function call$1(request) {
64997
+ /** How long a read of the feed keeps retrying a transient failure. */
64998
+ const ACTIVITY_RETRY_MS = 15e3;
64999
+ /** The message printed when Primitive could not be reached at all. */
65000
+ const NETWORK_ERROR_MESSAGE = "Could not reach Primitive (network error). Try again in a moment.";
65001
+ /**
65002
+ * The error for a read that got no answer at all: always a plain object, so
65003
+ * it prints the same with and without --json. The transport's own code (a
65004
+ * host that does not resolve, say) is kept as `cause`, as a word, with no
65005
+ * local detail.
65006
+ */
65007
+ function networkErrorPayload(error) {
65008
+ const cause = error instanceof Error ? error.cause : void 0;
65009
+ const code = cause && typeof cause === "object" ? cause.code : void 0;
65010
+ return {
65011
+ code: "network_error",
65012
+ message: NETWORK_ERROR_MESSAGE,
65013
+ ...typeof code === "string" && /^[A-Z0-9_]+$/.test(code) ? { cause: code } : {}
65014
+ };
65015
+ }
65016
+ /**
65017
+ * One page read, retried as the CLI's waits retry a read: a rate limit, a
65018
+ * 502, 503 or 504, or a network error is tried again with backoff, up to
65019
+ * ACTIVITY_ATTEMPTS times within ACTIVITY_RETRY_MS. A read that throws is
65020
+ * taken as the network error it is. A wait (a Retry-After, or the backoff)
65021
+ * longer than the time left is not slept on: the failure is returned at once.
65022
+ */
65023
+ async function readWithRetry(read) {
65024
+ const deadline = Date.now() + ACTIVITY_RETRY_MS;
65025
+ for (let attempt = 0;; attempt += 1) {
65026
+ let result;
65027
+ try {
65028
+ result = await read();
65029
+ } catch (error) {
65030
+ result = { error };
65031
+ }
65032
+ const failure = transientFailure(result);
65033
+ if (!failure || attempt + 1 >= 4) return result;
65034
+ const wait = failure.retryAfterMs ?? backoffDelay(attempt);
65035
+ if (wait > deadline - Date.now()) return result;
65036
+ if (!await pauseForTransientFailure({
65037
+ ...failure,
65038
+ retryAfterMs: wait
65039
+ }, attempt, { deadline })) return result;
65040
+ }
65041
+ }
65042
+ async function call$1(read) {
64095
65043
  try {
64096
- const result = await request;
64097
- if (result.error !== void 0 || !result.response?.ok) return {
65044
+ const result = await readWithRetry(read);
65045
+ if (transientFailure(result)?.reason === "network") return {
64098
65046
  ok: false,
64099
- payload: result.error === void 0 || result.error === null || typeof result.error === "string" ? {
64100
- code: "http_error",
64101
- message: typeof result.error === "string" && result.error.trim() || `HTTP ${result.response?.status ?? ""}`.trim()
64102
- } : extractErrorPayload(result.error)
65047
+ payload: networkErrorPayload(result.error)
64103
65048
  };
65049
+ if (result.error !== void 0 || !result.response?.ok) {
65050
+ const status = result.response?.status;
65051
+ if (result.error instanceof Error) return {
65052
+ ok: false,
65053
+ response: result.response,
65054
+ payload: {
65055
+ code: "response_error",
65056
+ message: `The answer could not be read${status ? ` (HTTP ${status})` : ""}. Try again in a moment.`
65057
+ }
65058
+ };
65059
+ const extracted = result.error === void 0 || result.error === null || typeof result.error === "string" ? void 0 : extractErrorPayload(result.error);
65060
+ const coded = extracted !== null && typeof extracted === "object" && typeof extracted.code === "string";
65061
+ return {
65062
+ ok: false,
65063
+ response: result.response,
65064
+ payload: coded ? extracted : {
65065
+ code: "http_error",
65066
+ message: typeof result.error === "string" && result.error.trim() || `HTTP ${status ?? ""}`.trim()
65067
+ }
65068
+ };
65069
+ }
64104
65070
  const data = result.data;
64105
65071
  return {
64106
65072
  ok: true,
@@ -64109,7 +65075,7 @@ async function call$1(request) {
64109
65075
  } catch (error) {
64110
65076
  return {
64111
65077
  ok: false,
64112
- payload: extractErrorPayload(error)
65078
+ payload: networkErrorPayload(error)
64113
65079
  };
64114
65080
  }
64115
65081
  }
@@ -64175,15 +65141,56 @@ function formatAskedRows(items, complete = false) {
64175
65141
  const line = (cells) => cells.map((cell, i) => i === cells.length - 1 ? cell : cell.padEnd(widths[i] ?? 0)).join(" ");
64176
65142
  return [line(header), ...rows.map(line)].join("\n");
64177
65143
  }
65144
+ const TOTAL_KEYS = [
65145
+ "keys",
65146
+ "reads_in_full",
65147
+ "webhooks",
65148
+ "shares"
65149
+ ];
65150
+ /** `value` as receiver totals: an object whose four counts are all non-negative integers. */
65151
+ function receiverTotals(value) {
65152
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
65153
+ const totals = value;
65154
+ return TOTAL_KEYS.every((key) => {
65155
+ const count = totals[key];
65156
+ return typeof count === "number" && Number.isInteger(count) && count >= 0;
65157
+ }) ? totals : null;
65158
+ }
65159
+ /**
65160
+ * What a receivers_changed notice says changed, from its totals before and
65161
+ * after: "keys 3 to 2 (1 to 0 also reading your mail beyond the rule),
65162
+ * webhooks 2 to 1",
65163
+ * or, when the totals are equal (an owner or a webhook site changed), that
65164
+ * they are. Null for any other notice, and for one whose totals are missing
65165
+ * or not counts (an older server).
65166
+ */
65167
+ function receiversChangeText(item) {
65168
+ if (item.state !== "receivers_changed") return null;
65169
+ const change = item.receivers_change;
65170
+ const before = receiverTotals(change?.before);
65171
+ const after = receiverTotals(change?.after);
65172
+ if (!before || !after) return null;
65173
+ const moved = (key) => before[key] !== after[key];
65174
+ const span = (key) => `${before[key]} to ${after[key]}`;
65175
+ const full = moved("reads_in_full") ? ` (${span("reads_in_full")} also reading your mail beyond the rule)` : "";
65176
+ const changed = [
65177
+ ...moved("keys") ? [`keys ${span("keys")}${full}`] : full ? [`keys ${after.keys}${full}`] : [],
65178
+ ...moved("webhooks") ? [`webhooks ${span("webhooks")}`] : [],
65179
+ ...moved("shares") ? [`shares ${span("shares")}`] : []
65180
+ ];
65181
+ return changed.length > 0 ? changed.join(", ") : "same totals, an owner or webhook site changed";
65182
+ }
64178
65183
  /** Where to see what covers you now, whatever the notices have caught up with. */
64179
65184
  const COVERING_NOW = `\`primitive email-rule-oversight list-email-rules-covering-me\` shows what covers you now.`;
64180
65185
  /**
64181
- * One line per coverage notice: what it told (started, new_version, or
64182
- * baseline for a rule that already covered you when notices began), when, the
64183
- * rule's name then, and its author; and, first, each rule that covers you now
64184
- * whose notice is not written yet (pending, dated when the rule last
64185
- * changed), then the rule's id (rule_id; a pending row's own id is the
64186
- * rule's), never the notice's own id, which no command takes.
65186
+ * One line per coverage notice: what it told (started, new_version,
65187
+ * receivers_changed, or baseline for a rule that already covered you when
65188
+ * notices began), when, the rule's name then, and its author; and, first,
65189
+ * each rule that covers you now whose notice is not written yet (pending,
65190
+ * dated when the rule last changed), then the rule's id (rule_id; a pending
65191
+ * row's own id is the rule's), never the notice's own id, which no command
65192
+ * takes; and, when any row is a receivers_changed notice with its totals,
65193
+ * what changed in them (CHANGE).
64187
65194
  * `complete` says the listing is the whole of it (from the first page to the
64188
65195
  * last, nothing withheld). Even then an empty one is no promise that nothing
64189
65196
  * covers you: notices can lag, so it
@@ -64199,20 +65206,26 @@ function formatConcerningRows(items, complete = false, asOf, pendingComplete) {
64199
65206
  item.created_through.name ?? item.created_through.id ?? "",
64200
65207
  item.rule_id ?? (item.state === "pending" ? item.id : "")
64201
65208
  ]);
65209
+ const changes = items.map((item) => receiversChangeText(item));
65210
+ const withChange = changes.some((change) => change !== null);
65211
+ if (withChange) for (const [i, row] of rows.entries()) row.push(changes[i] ?? "");
64202
65212
  const header = [
64203
65213
  "NOTICE",
64204
65214
  "WHEN (UTC)",
64205
65215
  "RULE",
64206
65216
  "AUTHOR",
64207
- "RULE ID"
65217
+ "RULE ID",
65218
+ ...withChange ? ["CHANGE"] : []
64208
65219
  ];
64209
65220
  const widths = header.map((h, i) => Math.max(h.length, ...rows.map((row) => (row[i] ?? "").length)));
64210
65221
  const line = (cells) => cells.map((cell, i) => i === cells.length - 1 ? cell : cell.padEnd(widths[i] ?? 0)).join(" ").trimEnd();
64211
65222
  const pending = items.some((item) => item.state === "pending") ? ["", `pending: the rule covers you now and its notice is not written yet. ${COVERING_NOW}`] : [];
65223
+ const receivers = items.some((item) => item.state === "receivers_changed") ? ["", "receivers_changed: who receives the rule's matches changed, with the totals before and after where the server sends them. `primitive email-rule-oversight get-email-rule-covering-me <rule-id>` shows who receives them now, by owner, while the rule covers you."] : [];
64212
65224
  return [
64213
65225
  line(header),
64214
65226
  ...rows.map(line),
64215
65227
  ...pending,
65228
+ ...receivers,
64216
65229
  ...unfinished ? ["", pending.length > 0 ? unfinished : `${unfinished} ${COVERING_NOW}`] : []
64217
65230
  ].join("\n");
64218
65231
  }
@@ -64250,7 +65263,7 @@ async function runActivityList(command, flags, relation) {
64250
65263
  let pendingComplete;
64251
65264
  let cursor = flags.cursor;
64252
65265
  for (let page = 0; page < (flags.all ? 50 : 1); page += 1) {
64253
- const result = await call$1(listActivityObjects({
65266
+ const result = await call$1(() => listActivityObjects({
64254
65267
  client: auth.apiClient.client,
64255
65268
  query: {
64256
65269
  ...relation !== "created" ? { relation } : {},
@@ -64262,7 +65275,12 @@ async function runActivityList(command, flags, relation) {
64262
65275
  }));
64263
65276
  if (!result.ok) {
64264
65277
  if (flags.json) process.stderr.write(`${JSON.stringify(result.payload)}\n`);
64265
- else writeErrorWithHints(result.payload);
65278
+ else {
65279
+ writeErrorWithHints(result.payload, result.response);
65280
+ const cause = result.payload?.cause;
65281
+ const hint = typeof cause === "string" ? networkErrorHint(cause) : void 0;
65282
+ if (hint) process.stderr.write(`${hint}\n`);
65283
+ }
64266
65284
  surfaceUnauthorizedHint({
64267
65285
  auth: auth.auth,
64268
65286
  baseUrlOverridden: auth.baseUrlOverridden,
@@ -64378,7 +65396,7 @@ var ActivityAskedCommand = class ActivityAskedCommand extends Command {
64378
65396
  }
64379
65397
  };
64380
65398
  var ActivityConcerningCommand = class ActivityConcerningCommand extends Command {
64381
- static description = `List what other members did that concerns your own mail, newest first: each time another member's rule started covering your mailboxes, that is, it was made or turned active over them or an active rule came to reach you, once per rule, not per mailbox (started), or saved a new version while covering it (new_version), named as the rule was then, with its author. A rule that already covered you when these notices began is listed once as baseline. A rule in draft or observe grants and blocks nothing and gets no notice while it stays there, though \`primitive email-rule-oversight list-email-rules-covering-me\` lists it; turning it active is what is listed, and a rule that covers you again within an hour of its last started or baseline notice gets no new one, though a version it saved meanwhile is listed as new_version. A rule that stops covering you, a rename, and changes to its keys, webhooks or share are not listed.
65399
+ static description = `List what other members did that concerns your own mail, newest first: each time another member's rule started covering your mailboxes, that is, it was made or turned active over them or an active rule came to reach you, once per rule, not per mailbox (started), saved a new version while covering it (new_version), or changed who receives its matches: its keys, webhooks or share (receivers_changed, with the totals before and after in the table, and under receivers_change with --json, at most once every 15 minutes for a rule), named as the rule was then, with its author. A rule that already covered you when these notices began is listed once as baseline. A rule in draft or observe grants and blocks nothing and gets no notice while it stays there, though \`primitive email-rule-oversight list-email-rules-covering-me\` lists it; turning it active is what is listed, and a rule that covers you again within an hour of its last started or baseline notice gets no new one, though a version it saved meanwhile is listed as new_version, and a change in who receives its matches as receivers_changed. A rule that stops covering you and a rename are not listed.
64382
65400
 
64383
65401
  Notices can lag. Until a start is written, a rule that now covers you is listed first as pending, dated when the rule last changed, and with --json its id is the rule's. When the check for such rules did not finish, the listing says so and --json carries pending_complete: false; run it again in a moment.
64384
65402
 
@@ -71553,7 +72571,7 @@ async function checkAskedOfYou(client, now = Date.now()) {
71553
72571
  const waiting = asked.more ? [`at least ${requests + objections} rule requests and objections`] : [...requests > 0 ? [plural$1(requests, "rule request")] : [], ...objections > 0 ? [plural$1(objections, "objection")] : []];
71554
72572
  const items = concerning?.items ?? [];
71555
72573
  const pending = items.filter((item) => item.state === "pending").length;
71556
- const notices = items.filter((item) => (item.state === "started" || item.state === "new_version") && Date.parse(item.created_at) >= since).length;
72574
+ const notices = items.filter((item) => (item.state === "started" || item.state === "new_version" || item.state === "receivers_changed") && Date.parse(item.created_at) >= since).length;
71557
72575
  const parts = [
71558
72576
  ...waiting.length > 0 ? [`${waiting.join(" and ")} waiting for you`] : [],
71559
72577
  ...ASKED_KINDS.filter((kind) => hidden.has(kind)).map((kind) => `${kind === "email_rule_request" ? "rule requests" : "objections"} not shown to this credential`),
@@ -71603,7 +72621,7 @@ function checkReceiverStart(configDir, profileName) {
71603
72621
  }
71604
72622
  }
71605
72623
  var DoctorCommand = class DoctorCommand extends Command {
71606
- static description = `Run a one-shot environment health check: Node version, proxy env, CLI auth resolution, /account reachability, verified-domain status, and, signed in as yourself, the rule requests and objections waiting for you and the rules that recently started covering your mail. With PRIMITIVE_AGENT_PROFILE selected, reports saved identity offline and points to whoami and exact listener status instead of account-only checks. Prints actionable hints for each warning or failure.`;
72624
+ static description = `Run a one-shot environment health check: Node version, proxy env, CLI auth resolution, /account reachability, verified-domain status, and, signed in as yourself, the rule requests and objections waiting for you and the rules over your mail that recently started covering it or changed. With PRIMITIVE_AGENT_PROFILE selected, reports saved identity offline and points to whoami and exact listener status instead of account-only checks. Prints actionable hints for each warning or failure.`;
71607
72625
  static summary = "Check the local environment and live API for common problems";
71608
72626
  static examples = ["<%= config.bin %> doctor", "<%= config.bin %> doctor --api-key prim_..."];
71609
72627
  static flags = {
@@ -74703,8 +75721,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
74703
75721
  name: "Primitive Team",
74704
75722
  url: "https://primitive.dev"
74705
75723
  };
74706
- const SDK_VERSION_RANGE = "^3.0.0-preview.13";
74707
- const CLI_VERSION_RANGE = "^3.0.0-preview.13";
75724
+ const SDK_VERSION_RANGE = "^3.0.0-preview.14";
75725
+ const CLI_VERSION_RANGE = "^3.0.0-preview.14";
74708
75726
  const ESBUILD_VERSION_RANGE = "^0.27.0";
74709
75727
  function renderHandler() {
74710
75728
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -78092,6 +79110,39 @@ function keyNotes(key) {
78092
79110
  return typeof description === "string" ? [description] : [];
78093
79111
  });
78094
79112
  }
79113
+ /** Whether a note says the key reads a whole mailbox beside its email rules. */
79114
+ function isWholeMailboxNote(note) {
79115
+ return note !== null && typeof note === "object" && note.reason === "whole_mailbox";
79116
+ }
79117
+ /**
79118
+ * The addresses and domains a key reads in full beside its email rules: the
79119
+ * whole_mailbox notes of its address_warnings, read loosely.
79120
+ */
79121
+ function wholeMailboxAddresses(notes) {
79122
+ if (!Array.isArray(notes)) return [];
79123
+ return notes.flatMap((note) => {
79124
+ if (!isWholeMailboxNote(note)) return [];
79125
+ const address = note.address;
79126
+ return typeof address === "string" && address ? [address] : [];
79127
+ });
79128
+ }
79129
+ /**
79130
+ * What a key create or update says when the key reads whole mailboxes beside
79131
+ * its email rules: on stderr as a Warning line, or under --json in the
79132
+ * document's whole_mailbox_warning (the JSON guard keeps stderr empty, and the
79133
+ * answer's warnings are the server's objects). Null when it does not. The
79134
+ * exit code is not changed: the key is made or changed as asked.
79135
+ */
79136
+ function wholeMailboxWarning(keyId, notes) {
79137
+ if (!Array.isArray(notes) || !notes.some(isWholeMailboxNote)) return null;
79138
+ const addresses = wholeMailboxAddresses(notes);
79139
+ return `This key reads ${addresses.length > 0 ? `every email at ${addresses.join(", ")}` : "every email at an address or domain it lists"}, not only the emails its rules grant. An address or domain listed beside a rule is read in full. To limit a key to the rules' emails, create one with the same permissions and only --rule for its mail (no --address or --domain), then revoke this one with \`primitive keys revoke ${keyId}\`.`;
79140
+ }
79141
+ /** The notes other than whole_mailbox, which wholeMailboxWarning says instead. */
79142
+ function otherNotes(notes) {
79143
+ if (!Array.isArray(notes)) return [];
79144
+ return notes.filter((note) => note !== null && typeof note === "object" && !isWholeMailboxNote(note));
79145
+ }
78095
79146
  function formatKeyRows(rows, options = {}) {
78096
79147
  if (rows.length === 0) return options.includeRevoked ? "No API keys, revoked or not." : "No API keys. Revoked keys are left out; pass --include-revoked to list them too.";
78097
79148
  return rows.map((row) => {
@@ -78099,9 +79150,16 @@ function formatKeyRows(rows, options = {}) {
78099
79150
  const revoked = row.status === "revoked" ? ` [revoked${row.revoked_at ? ` ${row.revoked_at}` : ""}]` : "";
78100
79151
  const count = keyNotes(row).length;
78101
79152
  const notes = count > 0 ? ` [${count} ${count === 1 ? "note" : "notes"}: primitive keys get ${row.id}]` : "";
78102
- return `${row.id} ${row.key_prefix} ${row.name ?? "(unnamed)"} ${describePermissions(row.grant)} mail: ${describeMailScope(row.grant)} expires ${row.expires_at ?? "never"} last used ${row.last_used_at ?? "never"}${suspended}${revoked}${notes}`;
79153
+ const inFull = wholeMailboxAddresses(row.address_warnings);
79154
+ const whole = inFull.length > 0 ? ` [reads all mail at ${inFull.join(", ")}, not only its rules' emails]` : "";
79155
+ return `${row.id} ${row.key_prefix} ${row.name ?? "(unnamed)"} ${describePermissions(row.grant)} mail: ${describeMailScope(row.grant)} expires ${row.expires_at ?? "never"} last used ${row.last_used_at ?? "never"}${suspended}${revoked}${whole}${notes}`;
78103
79156
  }).join("\n");
78104
79157
  }
79158
+ /** The detail line for the mailboxes a key reads in full beside its rules. */
79159
+ function wholeMailboxLines(key) {
79160
+ const inFull = wholeMailboxAddresses(key.address_warnings);
79161
+ return inFull.length > 0 ? [`In full: ${inFull.join(", ")} (listed beside its rules, so every email there is read, not only what the rules grant)`] : [];
79162
+ }
78105
79163
  function formatKeyDetail(key) {
78106
79164
  const lines = [
78107
79165
  `Name: ${key.name ?? "(unnamed)"}`,
@@ -78109,6 +79167,7 @@ function formatKeyDetail(key) {
78109
79167
  `Prefix: ${key.key_prefix}`,
78110
79168
  `Kind: ${key.kind}`,
78111
79169
  `Mail: ${describeMailScope(key.grant)}`,
79170
+ ...wholeMailboxLines(key),
78112
79171
  `Created: ${key.created_at ?? "unknown"}`,
78113
79172
  `Expires: ${key.expires_at ?? "never"}`,
78114
79173
  `Last used: ${key.last_used_at ?? "never"}`,
@@ -78208,13 +79267,16 @@ function buildCreateGrant(choice) {
78208
79267
  * The mail scope fields a change can restate. `ended_rules` (rules the key
78209
79268
  * lists that read nothing any more: a deleted rule, or a share that ended) is
78210
79269
  * read only: a change never sends it back, so the key is saved without them.
79270
+ * `rule_versions` (each listed rule's current version, its state and when
79271
+ * that version was saved) is read only too, and is never sent back.
78211
79272
  */
78212
79273
  const RESTATABLE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
78213
79274
  "addresses",
78214
79275
  "domains",
78215
79276
  "rules",
78216
79277
  "depth",
78217
- "ended_rules"
79278
+ "ended_rules",
79279
+ "rule_versions"
78218
79280
  ]);
78219
79281
  /** Whether a change touches the key's email rules at all. */
78220
79282
  function changesRules(change) {
@@ -78311,7 +79373,7 @@ function buildUpdatedGrant(current, change) {
78311
79373
  }
78312
79374
  if (!scope || typeof scope !== "object" || Array.isArray(scope)) return {
78313
79375
  ok: false,
78314
- message: "This key's mail scope could not be read. Change it in the dashboard."
79376
+ message: "This key's mail scope could not be read. Change the key in the dashboard, or create a key with what you need and revoke this one."
78315
79377
  };
78316
79378
  const record = scope;
78317
79379
  const addresses = stringList(record.addresses ?? []);
@@ -78324,9 +79386,21 @@ function buildUpdatedGrant(current, change) {
78324
79386
  message: `${readdedEnded.id} reads nothing any more (${readdedEnded.reason}), so it cannot be added back to the key.`
78325
79387
  };
78326
79388
  const rules = heldRules ? nextRules(heldRules, change) : null;
78327
- if (!addresses || !domains || !rules || Object.keys(record).some((field) => !RESTATABLE_SCOPE_FIELDS.has(field))) return {
79389
+ const unknownFields = Object.keys(record).filter((field) => !RESTATABLE_SCOPE_FIELDS.has(field));
79390
+ if (unknownFields.length > 0) {
79391
+ const one = unknownFields.length === 1;
79392
+ return {
79393
+ ok: false,
79394
+ message: `This key's mail scope has ${one ? "a setting" : "settings"} the CLI cannot restate (${unknownFields.join(", ")}), and a change here could drop ${one ? "it" : "them"}. Change the key in the dashboard, or create a key with what you need and revoke this one.`
79395
+ };
79396
+ }
79397
+ if (!addresses || !domains || !rules) return {
78328
79398
  ok: false,
78329
- message: "This key's mail scope has settings the CLI cannot restate. Change it in the dashboard."
79399
+ message: `This key's mail scope has ${[
79400
+ ...addresses ? [] : ["addresses"],
79401
+ ...domains ? [] : ["domains"],
79402
+ ...rules ? [] : ["rules"]
79403
+ ].join(" and ")} the CLI could not read. Change the key in the dashboard, or create a key with what you need and revoke this one.`
78330
79404
  };
78331
79405
  if (addresses.length === 0 && domains.length === 0 && rules.length === 0) {
78332
79406
  if ((heldRules?.length ?? 0) === 0 && ended.length > 0) return {
@@ -78469,7 +79543,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
78469
79543
 
78470
79544
  The secret is printed once, on its own line on stdout (in the JSON with --json). Store it right away: it is not shown again and the CLI does not save it.
78471
79545
 
78472
- A key limited to email rules (--rule, repeatable) reaches only the mail those rules match. Give it mail:read with --permission, or use --preset own_addresses_read_only.
79546
+ A key limited to email rules (--rule, repeatable) reaches only the mail those rules match. Give it mail:read with --permission, or use --preset own_addresses_read_only. An --address or --domain given beside --rule is read in full, every email there and not only what the rules match: the key is still made, and the command warns on stderr, or in whole_mailbox_warning with --json.
78473
79547
 
78474
79548
  ${keyPermissionsHelp()}`;
78475
79549
  static summary = "Create an API key";
@@ -78549,9 +79623,13 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
78549
79623
  const key = result.data;
78550
79624
  if (!flags.json) {
78551
79625
  writeWarnings(key.warnings);
78552
- writeWarnings(key.address_warnings);
79626
+ writeWarnings(otherNotes(key.address_warnings));
78553
79627
  }
78554
- printSecret(this, Boolean(flags.json), key, key.key, [
79628
+ const whole = wholeMailboxWarning(key.id, key.address_warnings);
79629
+ printSecret(this, Boolean(flags.json), whole ? {
79630
+ ...key,
79631
+ whole_mailbox_warning: whole
79632
+ } : key, key.key, [
78555
79633
  `Created API key ${key.name ?? "(unnamed)"} (${key.id}), prefix ${key.key_prefix}.`,
78556
79634
  `Permissions: ${describePermissions({
78557
79635
  template: key.grant.template,
@@ -78565,6 +79643,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
78565
79643
  })}`,
78566
79644
  `Expires: ${key.expires_at ?? "never"}`
78567
79645
  ].join("\n"));
79646
+ if (whole && !flags.json) process.stderr.write(`Warning: ${whole}\n`);
78568
79647
  }
78569
79648
  };
78570
79649
  var KeysRotateCommand = class KeysRotateCommand extends Command {
@@ -78627,11 +79706,11 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
78627
79706
  var KeysUpdateCommand = class KeysUpdateCommand extends Command {
78628
79707
  static description = `Change an API key's permissions, email rules, name or expiry. It keeps its id and secret.
78629
79708
 
78630
- --add and --remove take permissions (repeatable, or comma-separated); \`primitive keys create --help-full\` lists the permissions a key can hold. --add-rule and --remove-rule add email rules to the key or take them off, and keep its other rules. --rule replaces every rule the key has with the ones given, so pass all the rules it should keep; it cannot be combined with --add-rule or --remove-rule. --expires-at gives it a new expiry: a later one extends it, within your organization's credential policy, and is how to keep a key working past its expiry (rotating keeps the expiry). --no-expiry removes it where the policy allows. An expired key cannot be extended. Everything else about the key stays as it is, and the output lists any rules a change took off.
79709
+ --add and --remove take permissions (repeatable, or comma-separated); \`primitive keys create --help-full\` lists the permissions a key can hold. --add-rule and --remove-rule add email rules to the key or take them off, and keep its other rules. --rule replaces every rule the key has with the ones given, so pass all the rules it should keep; it cannot be combined with --add-rule or --remove-rule. --expires-at gives it a new expiry: a later one extends it, within your organization's credential policy, and is how to keep a key working past its expiry (rotating keeps the expiry). --no-expiry removes it where the policy allows. An expired key cannot be extended. Everything else about the key stays as it is, and the output lists any rules a change took off. When the key's grant is written and it lists an address or domain beside its rules, which it reads in full, the command warns on stderr, or in whole_mailbox_warning with --json.
78631
79710
 
78632
79711
  The change runs directly; if your organization requires approval, the command prints the approval link and takes --step-up-token, as for a new key. A key never holds more than you do.
78633
79712
 
78634
- With --json it prints the key as it is now, in the shape keys get prints, with changed, warnings and address_warnings alongside, notice when its permissions or mail changed, rules_added when a change put rules on it (each with granted_emails, how many emails the rule has already shared), rules_removed when a change took rules off, and rules_not_on_key for rules asked to be taken off that it did not have. A permission or rule change that leaves the grant as it is, with no --name, --expires-at or --no-expiry, sends nothing and prints the key with changed: []. If the key cannot be read back after the change, key_error says why and only the change's fields are printed.`;
79713
+ With --json it prints the key as it is now, in the shape keys get prints, with changed, warnings and address_warnings alongside, notice when its permissions or mail changed, rules_added when a change put rules on it (each with granted_emails, how many emails the rule has already shared), rules_removed when a change took rules off, rules_not_on_key for rules asked to be taken off that it did not have, and whole_mailbox_warning when the grant written reads a whole mailbox beside its rules. A permission or rule change that leaves the grant as it is, with no --name, --expires-at or --no-expiry, sends nothing and prints the key with changed: []. If the key cannot be read back after the change, key_error says why and only the change's fields are printed.`;
78635
79714
  static summary = "Change an API key";
78636
79715
  static examples = [
78637
79716
  "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --add mail:send,mail:reply",
@@ -78765,6 +79844,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
78765
79844
  path: { id: args.id }
78766
79845
  })) : null;
78767
79846
  const standingNotes = after?.ok ? after.data.address_warnings : void 0;
79847
+ const whole = grantChanged ? wholeMailboxWarning(args.id, result.data.address_warnings) : null;
78768
79848
  if (flags.json) {
78769
79849
  this.log(JSON.stringify({
78770
79850
  ...after?.ok ? after.data : {},
@@ -78773,12 +79853,13 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
78773
79853
  ...grantChanged ? { notice: HELD_REQUEST_NOTICE } : {},
78774
79854
  ...grantChanged && rulesRemoved.length > 0 ? { rules_removed: rulesRemoved } : {},
78775
79855
  ...rulesNotOnKey.length > 0 ? { rules_not_on_key: rulesNotOnKey } : {},
78776
- ...after && !after.ok ? { key_error: after.payload } : {}
79856
+ ...after && !after.ok ? { key_error: after.payload } : {},
79857
+ ...whole ? { whole_mailbox_warning: whole } : {}
78777
79858
  }, null, 2));
78778
79859
  return;
78779
79860
  }
78780
79861
  writeWarnings(result.data.warnings);
78781
- writeWarnings(result.data.address_warnings);
79862
+ writeWarnings(whole ? otherNotes(result.data.address_warnings) : result.data.address_warnings);
78782
79863
  const changed = result.data.changed;
78783
79864
  this.log(changed.length > 0 ? `Updated key ${args.id}: ${changed.join(", ")}.` : `Key ${args.id} already matched; nothing changed.`);
78784
79865
  if (after?.ok) {
@@ -78788,6 +79869,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
78788
79869
  if (grantChanged && rulesRemoved.length > 0) this.log(`Rules taken off: ${rulesRemoved.join(", ")}`);
78789
79870
  if (changed.includes("expires_at")) this.log(`Expires: ${expiry.expiresAt ?? "never"}`);
78790
79871
  if (grantChanged) process.stderr.write(`${HELD_REQUEST_NOTICE}\n`);
79872
+ if (whole) process.stderr.write(`Warning: ${whole}\n`);
78791
79873
  }
78792
79874
  };
78793
79875
  /**
@@ -83241,9 +84323,51 @@ function compareVersions(a, b) {
83241
84323
  }
83242
84324
  return 0;
83243
84325
  }
83244
- async function npmLatestVersion(fetchImpl) {
84326
+ function parseVersion(value) {
84327
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value.trim());
84328
+ if (!match) return null;
84329
+ return {
84330
+ core: match.slice(1, 4).map(Number),
84331
+ prerelease: match[4] ? match[4].split(".") : []
84332
+ };
84333
+ }
84334
+ /** Whether a version is a prerelease, such as 3.0.0-preview.12. */
84335
+ function isPrerelease(version) {
84336
+ return (parseVersion(version)?.prerelease.length ?? 0) > 0;
84337
+ }
84338
+ /**
84339
+ * Compare two versions in semver order, prereleases included: 3.0.0-preview.9
84340
+ * is older than 3.0.0-preview.12, which is older than 3.0.0. Null when either
84341
+ * is not a version.
84342
+ */
84343
+ function compareReleaseVersions(a, b) {
84344
+ const left = parseVersion(a);
84345
+ const right = parseVersion(b);
84346
+ if (!left || !right) return null;
84347
+ for (let index = 0; index < 3; index++) {
84348
+ const difference = (left.core[index] ?? 0) - (right.core[index] ?? 0);
84349
+ if (difference) return Math.sign(difference);
84350
+ }
84351
+ if (left.prerelease.length === 0 || right.prerelease.length === 0) return Math.sign(right.prerelease.length - left.prerelease.length);
84352
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
84353
+ for (let index = 0; index < length; index++) {
84354
+ const l = left.prerelease[index];
84355
+ const r = right.prerelease[index];
84356
+ if (l === void 0) return -1;
84357
+ if (r === void 0) return 1;
84358
+ const ln = /^\d+$/.test(l) ? Number(l) : null;
84359
+ const rn = /^\d+$/.test(r) ? Number(r) : null;
84360
+ if (ln !== null && rn !== null) {
84361
+ if (ln !== rn) return Math.sign(ln - rn);
84362
+ } else if (ln !== null) return -1;
84363
+ else if (rn !== null) return 1;
84364
+ else if (l !== r) return l < r ? -1 : 1;
84365
+ }
84366
+ return 0;
84367
+ }
84368
+ async function npmLatestVersion(fetchImpl, tag = "latest") {
83245
84369
  try {
83246
- const response = await fetchImpl("https://registry.npmjs.org/primitive/latest", {
84370
+ const response = await fetchImpl(`https://registry.npmjs.org/primitive/${tag}`, {
83247
84371
  headers: { accept: "application/json" },
83248
84372
  redirect: "follow",
83249
84373
  signal: AbortSignal.timeout(3e3)
@@ -83920,11 +85044,23 @@ async function runMachineDoctor(options) {
83920
85044
  "cli.version": { inspect: async () => {
83921
85045
  const version = options.cliVersion;
83922
85046
  if (options.minCliVersion) {
83923
- const compared = compareVersions(version, options.minCliVersion);
85047
+ const compared = isPrerelease(version) ? compareReleaseVersions(version, options.minCliVersion) : compareVersions(version, options.minCliVersion);
83924
85048
  if (compared === null) return check("cli.version", "warn", `Cannot compare ${version} with the required ${options.minCliVersion}.`);
83925
85049
  if (compared < 0) return check("cli.version", "fail", `${version} is older than the required ${options.minCliVersion}.`, { action: "update_cli" });
83926
85050
  }
83927
- const latest = await (options.latestVersion ?? (() => npmLatestVersion(fetchImpl)))();
85051
+ const readLatest = options.latestVersion ?? (() => npmLatestVersion(fetchImpl));
85052
+ if (isPrerelease(version)) {
85053
+ const [latest, preview] = await Promise.all([readLatest(), (options.previewVersion ?? (() => npmLatestVersion(fetchImpl, "preview")))()]);
85054
+ const released = latest ? compareReleaseVersions(version, latest) : null;
85055
+ if (released !== null && released < 0) return check("cli.version", "warn", `${version} is a preview older than the latest release ${latest}. Install it with \`npm install -g primitive@latest\`.`, { action: "update_cli" });
85056
+ const previewBehind = preview ? compareReleaseVersions(version, preview) : null;
85057
+ if (previewBehind !== null && previewBehind < 0) return check("cli.version", "warn", `${version} is older than the newest preview ${preview}. Install it with \`npm install -g primitive@preview\`.`, { action: "update_cli" });
85058
+ if (previewBehind === 0) return check("cli.version", "ok", `${version} is the newest preview.`);
85059
+ if (previewBehind !== null) return check("cli.version", "ok", `${version} is not older than the newest preview ${preview}.`);
85060
+ const met = options.minCliVersion ? ` meets the required ${options.minCliVersion};` : ";";
85061
+ return check("cli.version", released !== null || options.minCliVersion ? "ok" : "skip", preview ? `${version}; the newest preview could not be compared with it (the tag is not a version).` : released !== null ? `${version} is not older than the latest release ${latest}; the newest preview could not be checked.` : `${version}${met} neither the latest release nor the newest preview could be checked.`);
85062
+ }
85063
+ const latest = await readLatest();
83928
85064
  if (!latest) return options.minCliVersion ? check("cli.version", "ok", `${version} meets the required ${options.minCliVersion}; the latest release could not be checked.`) : check("cli.version", "skip", `${version}; the latest release could not be checked.`);
83929
85065
  const behind = compareVersions(version, latest);
83930
85066
  if (behind !== null && behind < 0) return check("cli.version", "warn", `${version} is older than the latest release ${latest}.`, { action: "update_cli" });
@@ -86208,7 +87344,10 @@ var ReplyCommand = class ReplyCommand extends Command {
86208
87344
  description: "Reply to everyone on the email: the sender (or Reply-To) as To, every other To and Cc address as Cc, minus your own address. Bcc is never included.",
86209
87345
  exclusive: ["fyi"]
86210
87346
  }),
86211
- body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required, except with --fyi." }),
87347
+ body: Flags.string({
87348
+ aliases: ["text"],
87349
+ description: "Plain-text reply body (--text is the same flag). Either --body or --html (or both) is required, except with --fyi."
87350
+ }),
86212
87351
  "body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file; this does not attach the file. Use --attachment for attachments. Mutually exclusive with --body and --body-stdin." }),
86213
87352
  "body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
86214
87353
  html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
@@ -87545,7 +88684,10 @@ var SendCommand = class SendCommand extends Command {
87545
88684
  }),
87546
88685
  from: Flags.string({ description: "Sender address. Without it: a connected agent's own address, your member address on your own login, or a key's one granted address; otherwise the command asks for --from, except that a credential without mail:send is refused by that permission." }),
87547
88686
  subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
87548
- body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
88687
+ body: Flags.string({
88688
+ aliases: ["text"],
88689
+ description: "Plain-text message body (--text is the same flag). Either --body or --html (or both) is required."
88690
+ }),
87549
88691
  "body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. This does not attach the file; use --attachment for file attachments. Mutually exclusive with --body and --body-stdin." }),
87550
88692
  "body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
87551
88693
  html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),