primcli 3.0.0-preview.1 → 3.0.0-preview.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
- import { $ as followUpCommandPrefix, A as clearConsumedPendingMail, At as ConnectedAgentKeyRequiredError, B as replyInteractionWarning, Bt as fetchCliOrgs, C as serverMuted, Ct as detectRuntimeSession, D as wakeRecipientField, Dt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, E as wakeReadCommand, Et as withFlagSuggestion, F as removePendingMail, Ft as refuseOrgSelectionForProfile, G as apiResultHttpStatus, H as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, Ht as switchCurrentOrg, I as interactionHumanLine, It as resolveCliApiRequestConfig, J as buildThrownSendFailureEnvelope, K as assertValidIdempotencyKey, L as interactionNextActions, Lt as selectedOrgFromEnv, M as pendingMailPath, Mt as createAuthenticatedCliApiClient, N as readPendingMail, Nt as createCliApiClient, O as wakeRelationship, Ot as ATTACHMENT_WAIT_MAX_MS, P as recordPendingMail, Pt as refreshStoredCliCredentials, Q as excludeSignalReplies, R as readEmailInteraction, Rt as CliOrgRefusedError, S as sentInThread, St as detectAgentRuntime, Tt as formatAlreadySentNotice, U as MAX_SEND_WAIT_TIMEOUT_MS, Vt as revokeUserLogin, W as SEND_OUTCOME_HELP, X as classifySendError, Y as checkPriorReplies, Z as deriveSendIdempotencyKey, _ as describeWake, a as extractErrorCode, at as priorRepliesThatWentOut, b as latestOwnOutbound, bt as loadSkillLine, c as hasControlCharacter$1, ct as sendOutcomeExitCode, d as readTextFileFlag, dt as serializeErrorPayload, et as formatDeletedEarlierSendNotice, f as runWithTiming, ft as successfulSendOutcome, g as bareAddress, gt as connectNextSteps, h as explainPendingMailNotFound, ht as cliInvocation, i as createOperationCommand, it as formatSendRecordFailureSummary, j as clearReadPendingMail, k as claimPendingMail, kt as withAttachmentWait, l as headerFlagName, lt as sendWaitClampNotice, m as writeErrorWithHints, n as API_ERROR_CODES, nt as formatPriorRepliesWarning, o as extractErrorPayload, ot as reportSendCommandResult, p as surfaceUnauthorizedHint, pt as thrownErrorExitCode, q as buildFollowUpCommand, r as TIME_FLAG_DESCRIPTION, rt as formatSendFailureSummary, s as formatErrorPayload, st as sendIdentityFields, t as API_BASE_URL_FLAG_DESCRIPTION, tt as formatPriorRepliesCheckSkipped, u as isGrantRefusal, ut as sentHistoryWindowStart, v as displayAddress, vt as generatedAgentName, w as serverRelationship, wt as runtimeSessionVariableList, x as readThreadContext, xt as currentMailSessionKey, y as formatMailWakeLine, yt as identitySuggestions, z as replyExpectation, zt as credentialsFromUserLogin } from "../api-command-ctHBqTL5.js";
2
- import { $ as AutomatedFilterUnsupportedError, A as sleep$3, At as getAccount, B as hasReplyState, Bn as startAgentSignup, Bt as getInboxStatus, C as collectNewAcceptedEmails, Ct as deleteWakeSchedule, D as filtersFromFlags, Dn as replyToEmail, Dt as downloadSentAttachmentArchiveMember, E as fetchEmailSearchPage, En as reorderRoutes, F as assertAwaitingFilterRows, Fn as setFunctionRoute, Ft as getDefaultNetworkAgent, Gn as updateDefaultNetworkMember, Gt as getSentEmail, H as queryUsesAwaiting, Hn as stopRepeatFromEmail, Ht as getOrgRoutingTopology, I as awaitingRejectedError, In as setFunctionSecret, It as getEmail, J as parseWebhookEvent, Jn as updateRoute, K as isEmailReceivedEvent, Kn as updateFunction, Kt as getThread, L as ensureSearchReportsReplyState, Ln as setMemory, Lt as getFunction, M as AWAITING_VALUES, Mn as searchMemories, N as REPLY_STATE_UNSUPPORTED_CODE, Nn as semanticSearch, Nt as getConversation, O as quoteDslValue, On as resendAgentSignupVerification, Ot as downloadSentAttachmentByFilename, P as ReplyStateUnsupportedError, Pn as sendEmail, Pt as getCreditBalance, Q as AUTOMATED_VALUES, Qn as verifyAgentSignup, Qt as listAgentNetworks, R as formatAwaitingCell, Rn as simulateRoute, Rt as getFunctionRouting, S as transientFailure, St as deleteWakeAuthorization, T as encodeReceivedAtSearchCursor, Tn as removeDefaultNetworkMember, Tt as downloadEmailAttachmentByFilename, Un as testFunction, Ut as getOwnGrant, V as isAwaitingRejectedError, Vn as startCliLogin, Vt as getMemory, Wn as unsetFunctionRoute, Wt as getRepeatingSend, X as AUTOMATED_FILTER_UNSUPPORTED_CODE, Xn as updateWakeSchedule, Y as parseFromHeader, Yn as updateWakeAuthorization, Yt as leaveOrganization, Z as AUTOMATED_FLAG_DESCRIPTION, Zn as verifyAgentClaim, Zt as listAgentContacts, _ as describeApiFailure, _n as listWakeDispatches, _t as createWakeSchedule, a as REPEAT_IDLE_MAX_MINUTES, an as listEmails, at as hasAutomatedVerdict, b as retryTransient, bn as pullWebhookEvent, bt as deleteMemory, cn as listFunctions, ct as cancelSentEmail, d as parseRepeatTick, dn as listRepeatingSends, dt as cliLogout, en as listCredentials, et as assertAutomatedVerdict, f as preparePresenceAliveEmail, fn as listRoles, ft as completeWebhookEvent, g as WaitDeadlineReached, gn as listWakeAuthorizations, gt as createWakeAuthorization, hn as listSentEmails, ht as createRoute, i as REPEAT_EVERY_MAX_MINUTES, in as listEmailAttachmentArchiveMembers, j as AWAITING_FLAG_DESCRIPTION, jn as searchEmails, jt as getAgentContactPolicy, k as sinceFromFlags, kn as runWakeSchedule, kt as getAccessMode, l as parseInteractionEnvelope, ln as listGroups, lt as cancelSentEmailByIdempotencyKey, m as repeatStopCommand, mn as listSentAttachmentArchiveMembers, mt as createFunction, nn as listDefaultNetworkMembers, o as REPEAT_MAX_SENDS_MAX, on as listEndpoints, ot as isAutomatedRejectedError, p as prepareSignalEmail, pn as listRoutes, pt as createEndpoint, qn as updateRepeatingSend, qt as getWakeSchedule, rn as listDomains, s as buildRepeatStopBody, sn as listFunctionLogs, st as addDefaultNetworkMember, tn as listDefaultNetworkAgents, tt as automatedRejectedError, u as parsePresenceEnvelope, un as listMailboxAccess, ut as checkDefaultNetworkContactAdmission, vn as listWakeSchedules, w as cursorFromAcceptedRows, wn as redeemCreditCode, wt as downloadEmailAttachmentArchiveMember, x as retryingOperation, xt as deleteRoute, y as pauseForTransientFailure, yn as pollCliLogin, z as formatRepliesCell, zn as startAgentClaim, zt as getFunctionTestRunTrace } from "../repeats-Dj_o6c-X-Bf9G0xds.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-BN0JTPZ3.js";
1
+ import { $ as deriveSendIdempotencyKey, A as wakeRelationship, At as ATTACHMENT_WAIT_MAX_MS, B as readEmailInteraction, Bt as CliOrgRefusedError, C as readThreadContext, Ct as currentMailSessionKey, Dt as formatAlreadySentNotice, E as serverRelationship, Et as runtimeSessionVariableList, F as readPendingMail, Ft as createCliApiClient, G as MAX_SEND_WAIT_TIMEOUT_MS, H as replyInteractionWarning, Ht as fetchCliOrgs, I as recordPendingMail, It as refreshStoredCliCredentials, J as assertValidIdempotencyKey, K as SEND_OUTCOME_HELP, L as removePendingMail, Lt as refuseOrgSelectionForProfile, M as clearConsumedPendingMail, Mt as ConnectedAgentKeyRequiredError, N as clearReadPendingMail, O as wakeReadCommand, Ot as withFlagSuggestion, P as pendingMailPath, Pt as createAuthenticatedCliApiClient, Q as classifySendError, R as interactionHumanLine, Rt as resolveCliApiRequestConfig, S as latestOwnOutbound, St as loadSkillLine, T as serverMuted, Tt as detectRuntimeSession, Ut as revokeUserLogin, V as replyExpectation, Vt as credentialsFromUserLogin, W as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, Wt as switchCurrentOrg, X as buildThrownSendFailureEnvelope, Y as buildFollowUpCommand, Z as checkPriorReplies, _ as explainPendingMailNotFound, _t as cliInvocation, a as createOperationCommand, at as formatSendFailureSummary, b as displayAddress, bt as generatedAgentName, c as formatErrorPayload, ct as reportSendCommandResult, d as isGrantRefusal, dt as sendWaitClampNotice, et as excludeSignalReplies, f as parameterFlagName, ft as sentHistoryWindowStart, g as writeErrorWithHints, h as surfaceUnauthorizedHint, ht as thrownErrorExitCode, i as TIME_FLAG_DESCRIPTION, it as formatPriorRepliesWarning, j as claimPendingMail, jt as withAttachmentWait, k as wakeRecipientField, kt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, l as hasControlCharacter$1, lt as sendIdentityFields, m as runWithTiming, mt as successfulSendOutcome, n as API_ERROR_CODES, nt as formatDeletedEarlierSendNotice, o as extractErrorCode, ot as formatSendRecordFailureSummary, p as readTextFileFlag, pt as serializeErrorPayload, q as apiResultHttpStatus, r as OPERATION_PATH_FLAGS, rt as formatPriorRepliesCheckSkipped, s as extractErrorPayload, st as priorRepliesThatWentOut, t as API_BASE_URL_FLAG_DESCRIPTION, tt as followUpCommandPrefix, u as headerFlagName, ut as sendOutcomeExitCode, v as bareAddress, vt as connectNextSteps, w as sentInThread, wt as detectAgentRuntime, x as formatMailWakeLine, xt as identitySuggestions, y as describeWake, z as interactionNextActions, zt as selectedOrgFromEnv } from "../api-command-DWjtyqH9.js";
2
+ import { $ as AutomatedFilterUnsupportedError, $n as updateRoute, $t as listAgentContacts, A as sleep$3, An as resendAgentSignupVerification, At as getAccessMode, B as hasReplyState, Bn as setFunctionSecret, Bt as getFunctionTestRunTrace, C as collectNewAcceptedEmails, Ct as deleteWakeAuthorization, D as filtersFromFlags, Dn as removeDefaultNetworkMember, E as fetchEmailSearchPage, En as redeemCreditCode, Et as downloadEmailAttachmentByFilename, F as assertAwaitingFilterRows, Fn as searchEmails, Ft as getCreditBalance, Gn as startCliLogin, Gt as getOwnGrant, H as queryUsesAwaiting, Hn as simulateRoute, Ht as getKey, I as awaitingRejectedError, In as searchMemories, It as getDefaultNetworkAgent, J as parseWebhookEvent, Jn as unsetFunctionRoute, Jt as getThread, K as isEmailReceivedEvent, Kn as stopRepeatFromEmail, Kt as getRepeatingSend, L as ensureSearchReportsReplyState, Ln as semanticSearch, Lt as getEmail, M as AWAITING_VALUES, Mn as rotateKey, Mt as getAgentContactPolicy, N as REPLY_STATE_UNSUPPORTED_CODE, Nn as runWakeSchedule, O as quoteDslValue, On as reorderRoutes, Ot as downloadSentAttachmentArchiveMember, P as ReplyStateUnsupportedError, Pt as getConversation, Q as AUTOMATED_VALUES, Qn as updateRepeatingSend, R as formatAwaitingCell, Rn as sendEmail, Rt as getFunction, S as transientFailure, Sn as pullWebhookEvent, St as deleteRoute, T as encodeReceivedAtSearchCursor, Tt as downloadEmailAttachmentArchiveMember, Un as startAgentClaim, Ut as getMemory, V as isAwaitingRejectedError, Vn as setMemory, Vt as getInboxStatus, Wn as startAgentSignup, Wt as getOrgRoutingTopology, X as AUTOMATED_FILTER_UNSUPPORTED_CODE, Xn as updateFunction, Y as parseFromHeader, Yn as updateDefaultNetworkMember, Yt as getWakeSchedule, Z as AUTOMATED_FLAG_DESCRIPTION, Zn as updateKey, Zt as leaveOrganization, _ as describeApiFailure, _n as listSentEmails, _t as createWakeAuthorization, a as REPEAT_IDLE_MAX_MINUTES, an as listEmailAttachmentArchiveMembers, at as hasAutomatedVerdict, b as retryTransient, bn as listWakeSchedules, cn as listFunctionLogs, ct as cancelSentEmail, d as parseRepeatTick, dn as listKeys, dt as cliLogout, en as listAgentNetworks, er as updateWakeAuthorization, et as assertAutomatedVerdict, f as preparePresenceAliveEmail, fn as listMailboxAccess, ft as completeWebhookEvent, g as WaitDeadlineReached, gn as listSentAttachmentArchiveMembers, gt as createRoute, hn as listRoutes, ht as createKey, i as REPEAT_EVERY_MAX_MINUTES, in as listDomains, j as AWAITING_FLAG_DESCRIPTION, jn as revokeCredential, jt as getAccount, k as sinceFromFlags, kn as replyToEmail, kt as downloadSentAttachmentByFilename, l as parseInteractionEnvelope, ln as listFunctions, lt as cancelSentEmailByIdempotencyKey, m as repeatStopCommand, mn as listRoles, mt as createFunction, nn as listDefaultNetworkAgents, nr as verifyAgentClaim, o as REPEAT_MAX_SENDS_MAX, on as listEmails, ot as isAutomatedRejectedError, p as prepareSignalEmail, pn as listRepeatingSends, pt as createEndpoint, qn as testFunction, qt as getSentEmail, rn as listDefaultNetworkMembers, rr as verifyAgentSignup, s as buildRepeatStopBody, sn as listEndpoints, st as addDefaultNetworkMember, tr as updateWakeSchedule, tt as automatedRejectedError, u as parsePresenceEnvelope, un as listGroups, ut as checkDefaultNetworkContactAdmission, vn as listWakeAuthorizations, vt as createWakeSchedule, w as cursorFromAcceptedRows, wt as deleteWakeSchedule, x as retryingOperation, xn as pollCliLogin, xt as deleteMemory, y as pauseForTransientFailure, yn as listWakeDispatches, z as formatRepliesCell, zn as setFunctionRoute, zt as getFunctionRouting } from "../repeats-Dj_o6c-X-BKHiZsYv.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-8Jv4jJzI.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-Cp_eQ9t_.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-DZ42V3jq.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-BG4getHB.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-_AEpylPV.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-CRUMUCNa.js";
10
- import { n as jsonOutputRequested, r as runStreamingJson, t as guardJsonOutputCommands } from "../json-output-BzSBR-KX.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-CBHAR7zj.js";
10
+ import { i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-DWrJMakZ.js";
11
11
  import { c as startBackgroundListen, i as backgroundListenToken, l as stopBackgroundListen, o as runBackgroundListen, r as backgroundListenStatus, u as verifyBackgroundListenTarget } from "../listen-background-C0F9gNDD.js";
12
12
  import { ORG_SELECTION_ENV } from "./global-org-flag.js";
13
13
  import { n as createConfig, t as createClient } from "../client.gen-6rZvmHfx.js";
@@ -172,7 +172,7 @@ const openapiDocument = {
172
172
  },
173
173
  {
174
174
  "name": "Mailbox access",
175
- "description": "Who may read, draft and send for each mailbox, and the groups access can be given to. Readable by people and org-wide API keys; changed by a dashboard session, an org-wide API key, or the person's own Primitive CLI. Other OAuth apps need a session step-up to change it. Answers 404 feature_disabled while mailbox access is not enabled for the organization.",
175
+ "description": "Who may read, draft and send for each mailbox, and the groups access can be given to. Readable by people and org-wide API keys; changed by a dashboard session, an org-wide API key, or the person's own Primitive CLI. Other OAuth apps need a session step-up to change it. Answers 404 feature_disabled, with details.feature naming the feature (mailbox_access, or roles for groups), while it is not enabled for the organization; only Primitive turns it on.",
176
176
  "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/mailbox-access"
177
177
  },
178
178
  {
@@ -187,7 +187,7 @@ const openapiDocument = {
187
187
  },
188
188
  {
189
189
  "name": "Roles and members",
190
- "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.",
190
+ "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; only Primitive turns it on.",
191
191
  "x-openapi-spec": "https://api.primitive.dev/v1/openapi/tags/roles-and-members"
192
192
  },
193
193
  {
@@ -20885,7 +20885,7 @@ const openapiDocument = {
20885
20885
  }
20886
20886
  },
20887
20887
  "403": {
20888
- "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization; grant_forbidden: the credential does not hold the permission",
20888
+ "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; only Primitive turns them on. grant_forbidden: the credential does not hold the permission",
20889
20889
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
20890
20890
  },
20891
20891
  "429": {
@@ -20972,7 +20972,7 @@ const openapiDocument = {
20972
20972
  }
20973
20973
  },
20974
20974
  "403": {
20975
- "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization; grant_forbidden: the credential does not hold the permission",
20975
+ "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; only Primitive turns them on. grant_forbidden: the credential does not hold the permission",
20976
20976
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
20977
20977
  },
20978
20978
  "429": {
@@ -21064,7 +21064,7 @@ const openapiDocument = {
21064
21064
  }
21065
21065
  },
21066
21066
  "403": {
21067
- "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization; grant_forbidden: the credential does not hold the permission",
21067
+ "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; only Primitive turns them on. grant_forbidden: the credential does not hold the permission",
21068
21068
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21069
21069
  },
21070
21070
  "404": {
@@ -21184,7 +21184,7 @@ const openapiDocument = {
21184
21184
  }
21185
21185
  },
21186
21186
  "403": {
21187
- "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization; grant_forbidden: the credential does not hold the permission",
21187
+ "description": "feature_disabled: interactions (or, for wake.dispatch, Wake dispatch) are not enabled for this organization, and details.feature is interactions or wake_dispatch; only Primitive turns them on. grant_forbidden: the credential does not hold the permission",
21188
21188
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21189
21189
  },
21190
21190
  "404": {
@@ -21648,7 +21648,7 @@ const openapiDocument = {
21648
21648
  "/email-rules/test": { "post": {
21649
21649
  "operationId": "testEmailRule",
21650
21650
  "summary": "Test a rule against received mail",
21651
- "description": "Runs a rule over received mail and reports what it would decide for each email: matched, near_miss, no_match, unclassified or not_found. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version, run with its author's access). Either way the rule reads only mail its author can read. Without email_ids, the newest emails it can read are tested, at most 200 and none older than 30 days. Emails not classified yet that arrived more than 2 hours ago are classified first and their classification is saved, unless classify_missing is false; newer ones are left to normal classification and reported unclassified with reason pending. The rule itself records nothing and grants nothing. Classification and the rule's own questions stop at about 90 seconds; run the test again to continue where it stopped. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21651
+ "description": "Runs a rule over received mail and reports what it would decide for each email: matched, near_miss, no_match, unclassified or not_found. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version, run with its author's access). Either way the rule reads only mail its author can read, and only what its scope covers; an unsaved tree covers only your own mail unless evaluation_scope names more. Without email_ids, the newest emails it can read are tested, at most 200 and none older than 30 days. Emails not classified yet that arrived more than 2 hours ago are classified first and their classification is saved, unless classify_missing is false; newer ones are left to normal classification and reported unclassified with reason pending. The rule itself records nothing and grants nothing. Classification and the rule's own questions stop at about 90 seconds; run the test again to continue where it stopped. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21652
21652
  "tags": ["Email Rules"],
21653
21653
  "security": [{ "BearerAuth": [] }],
21654
21654
  "requestBody": {
@@ -21671,7 +21671,7 @@ const openapiDocument = {
21671
21671
  }
21672
21672
  },
21673
21673
  "400": {
21674
- "description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids, and a tree that is valid for the column contract. A saved rule whose current version no longer renders answers details.reason version_not_renderable.",
21674
+ "description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids, and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule whose current version no longer renders answers details.reason version_not_renderable.",
21675
21675
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21676
21676
  },
21677
21677
  "401": {
@@ -21694,7 +21694,7 @@ const openapiDocument = {
21694
21694
  }
21695
21695
  },
21696
21696
  "403": {
21697
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and a rule decides on what emails say.",
21697
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. grant_forbidden with details.reason mail_depth: your access to mail shows headers only, and a rule decides on what emails say. email_rule_scope_not_readable: scope_selection names something you cannot read.",
21698
21698
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21699
21699
  },
21700
21700
  "404": {
@@ -21702,7 +21702,7 @@ const openapiDocument = {
21702
21702
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21703
21703
  },
21704
21704
  "409": {
21705
- "description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, the caller). details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox or no_shareable_mailbox.",
21705
+ "description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, the caller). details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox, no_shareable_mailbox or no_selected_mailbox.",
21706
21706
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21707
21707
  },
21708
21708
  "429": {
@@ -21745,7 +21745,7 @@ const openapiDocument = {
21745
21745
  "/email-rules/test-sample": { "post": {
21746
21746
  "operationId": "testEmailRuleOnSample",
21747
21747
  "summary": "Test a rule against a made-up email",
21748
- "description": "Reports what a rule would decide for an email you describe: its sender, recipients, subject, plain text and attachment types, and optionally whether it is automated or from a mailing list and its spam score. The email is classified the way received mail is, and the rule's conditions and its own questions are decided as they would be for received mail with that content and those signals, received now. Without them it is not automated and not from a mailing list; a rule that reads a spam score you gave none for is decided on no score, and spam_score is listed in unset_columns. Nothing is stored: no email, classification or decision. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version). The answer includes what the classifier read from the email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21748
+ "description": "Reports what a rule would decide for an email you describe: its sender, recipients, subject, plain text and attachment types, and optionally whether it is automated or from a mailing list and its spam score. The email is classified the way received mail is, and the rule's conditions and its own questions are decided as they would be for received mail with that content and those signals, received now. Without them it is not automated and not from a mailing list; a rule that reads a spam score you gave none for is decided on no score, and spam_score is listed in unset_columns. Nothing is stored: no email, classification or decision. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version); an unsaved tree is run as covering only your own mail unless evaluation_scope names more, as on POST /email-rules/test. The answer includes what the classifier read from the email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21749
21749
  "tags": ["Email Rules"],
21750
21750
  "security": [{ "BearerAuth": [] }],
21751
21751
  "requestBody": {
@@ -21768,7 +21768,7 @@ const openapiDocument = {
21768
21768
  }
21769
21769
  },
21770
21770
  "400": {
21771
- "description": "validation_error: give exactly one of tree and rule_id, an email with a sender, 1 to 50 To addresses (and up to 50 Cc), and a text body, and a tree that is valid for the column contract and within the size limits for a saved rule. A saved rule whose current version no longer renders answers details.reason version_not_renderable.",
21771
+ "description": "validation_error: give exactly one of tree and rule_id (evaluation_scope, scope_selection and kind only with tree), an email with a sender, 1 to 50 To addresses (and up to 50 Cc), and a text body, and a tree that is valid for the column contract and within the size limits for a saved rule. A saved rule whose current version no longer renders answers details.reason version_not_renderable.",
21772
21772
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21773
21773
  },
21774
21774
  "401": {
@@ -21791,7 +21791,7 @@ const openapiDocument = {
21791
21791
  }
21792
21792
  },
21793
21793
  "403": {
21794
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
21794
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_scope_not_readable: scope_selection names something you cannot read.",
21795
21795
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21796
21796
  },
21797
21797
  "404": {
@@ -21799,7 +21799,7 @@ const openapiDocument = {
21799
21799
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21800
21800
  },
21801
21801
  "409": {
21802
- "description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, you), so it would decide nothing for real. details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox or no_shareable_mailbox.",
21802
+ "description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree, you), so it would decide nothing for real. details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox, no_shareable_mailbox or no_selected_mailbox.",
21803
21803
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21804
21804
  },
21805
21805
  "429": {
@@ -21843,7 +21843,7 @@ const openapiDocument = {
21843
21843
  "post": {
21844
21844
  "operationId": "createEmailRule",
21845
21845
  "summary": "Create a rule",
21846
- "description": "Saves a rule as version 1, from a compile response or a tree you wrote. The tree is checked against the column contract. New rules start in observe, where what the rule matches is recorded and nothing is granted; draft is not evaluated. The caller is the rule's author, and the rule never reaches mail the author cannot read. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21846
+ "description": "Saves a rule as version 1, from a compile response or a tree you wrote. The tree is checked against the column contract. New rules start in observe, where what the rule matches is recorded and nothing is granted or blocked; draft is not evaluated. The caller is the rule's author, and the rule never reaches mail the author cannot read. evaluation_scope chooses whose mail it runs over: own_mailboxes (only your own mail) when omitted, so any other scope (author for all you can read, or selected) is named; scope_selection may name only what you can read. The answer carries the scope as stored. kind do_not_share makes a rule that blocks sharing: it runs before every other rule, and while it is active no rule shares an email it matches or cannot decide. A key cannot list one (details.reason rule_blocks_sharing on the key). Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21847
21847
  "tags": ["Email Rules"],
21848
21848
  "security": [{ "BearerAuth": [] }],
21849
21849
  "requestBody": {
@@ -21892,7 +21892,11 @@ const openapiDocument = {
21892
21892
  }
21893
21893
  },
21894
21894
  "403": {
21895
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
21895
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_scope_not_readable: scope_selection names something you cannot read (details.addresses, details.domains).",
21896
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21897
+ },
21898
+ "409": {
21899
+ "description": "email_rule_author_unresolvable: the rule could not run as you over the chosen scope. details.reason is left, revoked, suspended, membership_ended, no_mail_read, no_own_mailbox or no_shareable_mailbox. With no evaluation_scope it covers only your own mail, so no_own_mailbox or no_shareable_mailbox there means a scope must be named.",
21896
21900
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21897
21901
  },
21898
21902
  "429": {
@@ -21935,7 +21939,7 @@ const openapiDocument = {
21935
21939
  "get": {
21936
21940
  "operationId": "listEmailRules",
21937
21941
  "summary": "List rules",
21938
- "description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. With matched_email_id, only the rules that matched that email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21942
+ "description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule). With matched_email_id, only the rules that matched that email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
21939
21943
  "tags": ["Email Rules"],
21940
21944
  "security": [{ "BearerAuth": [] }],
21941
21945
  "parameters": [
@@ -22055,7 +22059,7 @@ const openapiDocument = {
22055
22059
  "get": {
22056
22060
  "operationId": "getEmailRule",
22057
22061
  "summary": "Read a rule",
22058
- "description": "A rule and its current version. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22062
+ "description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule) and its current version. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22059
22063
  "tags": ["Email Rules"],
22060
22064
  "security": [{ "BearerAuth": [] }],
22061
22065
  "parameters": [{
@@ -22170,7 +22174,7 @@ const openapiDocument = {
22170
22174
  "patch": {
22171
22175
  "operationId": "updateEmailRule",
22172
22176
  "summary": "Change a rule",
22173
- "description": "Renames the rule, saves a new version (english, tree and explanation together; earlier versions are kept unchanged) or changes its state: draft (disabled), observe or active. Only the rule's author may rename it, add a version or move it to observe or active; anyone else who can see it may only disable it (state draft), and while it is shared with another member only if they manage members from the dashboard. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22177
+ "description": "Renames the rule, saves a new version (english, tree and explanation together; earlier versions are kept unchanged), changes its state (draft, disabled; observe; or active) or changes whose mail it runs over (evaluation_scope, with scope_selection when selected). Only the rule's author may rename it, add a version, change its scope or move it to observe or active; anyone else who can see it may only disable it (state draft), and while it is shared with another member only if they manage members from the dashboard. Omit evaluation_scope to keep the current scope. A scope change saves a new version with the same conditions; what was granted stays. A shared rule keeps its scope. A rule's kind never changes. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22174
22178
  "tags": ["Email Rules"],
22175
22179
  "security": [{ "BearerAuth": [] }],
22176
22180
  "parameters": [{
@@ -22235,7 +22239,7 @@ const openapiDocument = {
22235
22239
  }
22236
22240
  },
22237
22241
  "403": {
22238
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden with details.reason rule_shared: the rule is shared with another member, so only its author, or someone who manages members from the dashboard, can turn it off.",
22242
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden with details.reason rule_shared: the rule is shared with another member, so only its author, or someone who manages members from the dashboard, can turn it off. email_rule_scope_not_readable: scope_selection names something you cannot read.",
22239
22243
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22240
22244
  },
22241
22245
  "404": {
@@ -22252,7 +22256,7 @@ const openapiDocument = {
22252
22256
  } }
22253
22257
  },
22254
22258
  "409": {
22255
- "description": "email_rule_conflict: the rule changed while saving; read it again and retry.",
22259
+ "description": "email_rule_conflict: the rule changed while saving; read it again and retry. email_rule_scope_locked: the rule is shared. email_rule_author_unresolvable: the rule could not run as you over the chosen scope.",
22256
22260
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22257
22261
  },
22258
22262
  "429": {
@@ -22516,252 +22520,17 @@ const openapiDocument = {
22516
22520
  },
22517
22521
  "x-primitive-permission": ["mail:read"]
22518
22522
  } },
22519
- "/email-rule-requests": {
22520
- "post": {
22521
- "operationId": "createEmailRuleRequest",
22522
- "summary": "Ask a member for a rule",
22523
- "description": "Asks another member of the organization for a rule over their own mail, from a compile response or a tree you wrote. Name them by user id, or by their member mailbox address (at most 30 lookups by address an hour). Nothing is evaluated and nothing is shared until they accept, and they may change the rule first; they see the request and can preview what it matches in their own mail. You may have at most 5 pending requests to one member and 50 in all. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22524
- "tags": ["Email Rules"],
22525
- "security": [{ "BearerAuth": [] }],
22526
- "requestBody": {
22527
- "required": true,
22528
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateEmailRuleRequestRequest" } } }
22529
- },
22530
- "responses": {
22531
- "201": {
22532
- "description": "Ask a member for a rule",
22533
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22534
- "type": "object",
22535
- "required": ["data"],
22536
- "properties": { "data": { "$ref": "#/components/schemas/CreateEmailRuleRequestResult" } }
22537
- }] } } }
22538
- },
22539
- "400": {
22540
- "description": "validation_error: give exactly one of target_user_id and target_email and a tree that is valid for the column contract; details.reason self_request when the member named is you.",
22541
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22542
- },
22543
- "401": {
22544
- "description": "Invalid or missing API key",
22545
- "content": { "application/json": {
22546
- "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22547
- "example": {
22548
- "success": false,
22549
- "error": {
22550
- "code": "unauthorized",
22551
- "message": "Invalid or missing API key"
22552
- }
22553
- }
22554
- } },
22555
- "headers": {
22556
- "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22557
- "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22558
- "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22559
- "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22560
- }
22561
- },
22562
- "403": {
22563
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22564
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22565
- },
22566
- "404": {
22567
- "description": "not_found: no current member of this organization matches the member named.",
22568
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22569
- },
22570
- "409": {
22571
- "description": "email_rule_request_limit: you already have the most pending requests allowed (details.limit, and details.scope target or total).",
22572
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22573
- },
22574
- "429": {
22575
- "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.",
22576
- "headers": {
22577
- "Retry-After": {
22578
- "schema": { "type": "integer" },
22579
- "description": "Seconds to wait before retrying"
22580
- },
22581
- "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22582
- "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22583
- "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22584
- "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22585
- },
22586
- "content": { "application/json": {
22587
- "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22588
- "example": {
22589
- "success": false,
22590
- "error": {
22591
- "code": "rate_limit_exceeded",
22592
- "message": "Rate limit exceeded"
22593
- }
22594
- }
22595
- } }
22596
- },
22597
- "503": {
22598
- "description": "service_unavailable: looking members up by address is unavailable right now. Try again in a moment, or name them by user id.",
22599
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22600
- }
22601
- },
22602
- "x-primitive-permission": ["mail:read"],
22603
- "parameters": [{
22604
- "name": "Idempotency-Key",
22605
- "in": "header",
22606
- "required": false,
22607
- "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.",
22608
- "schema": {
22609
- "type": "string",
22610
- "minLength": 1,
22611
- "maxLength": 255
22612
- }
22613
- }]
22614
- },
22615
- "get": {
22616
- "operationId": "listEmailRuleRequests",
22617
- "summary": "List rule requests",
22618
- "description": "Rule requests, newest first: those made to you (box incoming, the default), those you made (outgoing), or every one you can see (all). Pending ones by default; status decided lists accepted, declined and withdrawn ones, and any lists both. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22619
- "tags": ["Email Rules"],
22620
- "security": [{ "BearerAuth": [] }],
22621
- "parameters": [
22622
- {
22623
- "name": "box",
22624
- "in": "query",
22625
- "required": false,
22626
- "description": "incoming, outgoing or all.",
22627
- "schema": {
22628
- "default": "incoming",
22629
- "description": "incoming: requests made to you; outgoing: requests you made; all: every request you can see (owners and admins see the organization's).",
22630
- "type": "string",
22631
- "enum": [
22632
- "incoming",
22633
- "outgoing",
22634
- "all"
22635
- ]
22636
- }
22637
- },
22638
- {
22639
- "name": "status",
22640
- "in": "query",
22641
- "required": false,
22642
- "description": "pending, decided or any.",
22643
- "schema": {
22644
- "default": "pending",
22645
- "type": "string",
22646
- "enum": [
22647
- "pending",
22648
- "decided",
22649
- "any"
22650
- ]
22651
- }
22652
- },
22653
- {
22654
- "name": "limit",
22655
- "in": "query",
22656
- "required": false,
22657
- "description": "Maximum number of items to return.",
22658
- "schema": {
22659
- "default": 25,
22660
- "type": "integer",
22661
- "minimum": 1,
22662
- "maximum": 100
22663
- }
22664
- },
22665
- {
22666
- "name": "cursor",
22667
- "in": "query",
22668
- "required": false,
22669
- "description": "Opaque cursor from the previous page's next_cursor. Omit for the first page.",
22670
- "schema": {
22671
- "type": "string",
22672
- "maxLength": 200
22673
- }
22674
- }
22675
- ],
22676
- "responses": {
22677
- "200": {
22678
- "description": "List rule requests",
22679
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22680
- "type": "object",
22681
- "required": ["data"],
22682
- "properties": { "data": { "$ref": "#/components/schemas/ListEmailRuleRequestsResult" } }
22683
- }] } } },
22684
- "headers": {
22685
- "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
22686
- "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
22687
- "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22688
- "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
22689
- }
22690
- },
22691
- "400": {
22692
- "description": "Invalid request parameters",
22693
- "content": { "application/json": {
22694
- "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22695
- "example": {
22696
- "success": false,
22697
- "error": {
22698
- "code": "validation_error",
22699
- "message": "Invalid domain format"
22700
- }
22701
- }
22702
- } }
22703
- },
22704
- "401": {
22705
- "description": "Invalid or missing API key",
22706
- "content": { "application/json": {
22707
- "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22708
- "example": {
22709
- "success": false,
22710
- "error": {
22711
- "code": "unauthorized",
22712
- "message": "Invalid or missing API key"
22713
- }
22714
- }
22715
- } },
22716
- "headers": {
22717
- "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22718
- "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22719
- "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22720
- "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22721
- }
22722
- },
22723
- "403": {
22724
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22725
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22726
- },
22727
- "429": {
22728
- "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.",
22729
- "headers": {
22730
- "Retry-After": {
22731
- "schema": { "type": "integer" },
22732
- "description": "Seconds to wait before retrying"
22733
- },
22734
- "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22735
- "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22736
- "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22737
- "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22738
- },
22739
- "content": { "application/json": {
22740
- "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22741
- "example": {
22742
- "success": false,
22743
- "error": {
22744
- "code": "rate_limit_exceeded",
22745
- "message": "Rate limit exceeded"
22746
- }
22747
- }
22748
- } }
22749
- }
22750
- },
22751
- "x-primitive-permission": ["mail:read"]
22752
- }
22753
- },
22754
- "/email-rule-requests/{id}": { "get": {
22755
- "operationId": "getEmailRuleRequest",
22756
- "summary": "Read a rule request",
22757
- "description": "A rule request: the rule as proposed, who made it and to whom, and whether each of them is still a member. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22523
+ "/emails/{id}/rule-results": { "get": {
22524
+ "operationId": "listEmailRuleResults",
22525
+ "summary": "List every rule's result on an email",
22526
+ "description": "Every rule the caller can see and what it decided about this email: those that matched, those that did not, and those that have not run on it, with what is known about them. A result names the version of the rule that decided and carries that version's conditions and questions. A matched rule with kind do_not_share keeps rules from sharing the email while it is active; anything shared before then stays shared. Only a caller who can read the email gets an answer, and a rule the caller cannot see is left out. At most 50 rules are listed, those with a result first; hidden counts the rest. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22758
22527
  "tags": ["Email Rules"],
22759
22528
  "security": [{ "BearerAuth": [] }],
22760
22529
  "parameters": [{
22761
22530
  "name": "id",
22762
22531
  "in": "path",
22763
22532
  "required": true,
22764
- "description": "The request id.",
22533
+ "description": "The email id.",
22765
22534
  "schema": {
22766
22535
  "type": "string",
22767
22536
  "format": "uuid"
@@ -22769,11 +22538,11 @@ const openapiDocument = {
22769
22538
  }],
22770
22539
  "responses": {
22771
22540
  "200": {
22772
- "description": "Read a rule request",
22541
+ "description": "List every rule's result on an email",
22773
22542
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22774
22543
  "type": "object",
22775
22544
  "required": ["data"],
22776
- "properties": { "data": { "$ref": "#/components/schemas/GetEmailRuleRequestResult" } }
22545
+ "properties": { "data": { "$ref": "#/components/schemas/ListEmailRuleResultsResult" } }
22777
22546
  }] } } },
22778
22547
  "headers": {
22779
22548
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -22806,7 +22575,7 @@ const openapiDocument = {
22806
22575
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22807
22576
  },
22808
22577
  "404": {
22809
- "description": "not_found: no request you can see has this id.",
22578
+ "description": "not_found: no such email, or not one the caller can read.",
22810
22579
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22811
22580
  },
22812
22581
  "429": {
@@ -22835,39 +22604,39 @@ const openapiDocument = {
22835
22604
  },
22836
22605
  "x-primitive-permission": ["mail:read"]
22837
22606
  } },
22838
- "/email-rule-requests/{id}/decline": { "post": {
22839
- "operationId": "declineEmailRuleRequest",
22840
- "summary": "Decline a rule request",
22841
- "description": "Declines a pending request made to you. Nothing is written but the decision. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22607
+ "/email-rules/{id}/versions": { "get": {
22608
+ "operationId": "listEmailRuleVersions",
22609
+ "summary": "Read versions of a rule",
22610
+ "description": "The named versions of a rule, as they were written: what each one's conditions and questions were, so a decision can be read against the version that made it. Numbers that are not versions of the rule are left out. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22842
22611
  "tags": ["Email Rules"],
22843
22612
  "security": [{ "BearerAuth": [] }],
22844
22613
  "parameters": [{
22845
22614
  "name": "id",
22846
22615
  "in": "path",
22847
22616
  "required": true,
22848
- "description": "The request id.",
22617
+ "description": "The rule id.",
22849
22618
  "schema": {
22850
22619
  "type": "string",
22851
22620
  "format": "uuid"
22852
22621
  }
22853
22622
  }, {
22854
- "name": "Idempotency-Key",
22855
- "in": "header",
22856
- "required": false,
22857
- "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.",
22623
+ "name": "versions",
22624
+ "in": "query",
22625
+ "required": true,
22626
+ "description": "The version numbers, comma-separated, at most 100.",
22858
22627
  "schema": {
22859
22628
  "type": "string",
22860
- "minLength": 1,
22861
- "maxLength": 255
22629
+ "pattern": "^[1-9]\\d{0,8}(,[1-9]\\d{0,8}){0,99}$",
22630
+ "description": "The version numbers to read, comma-separated."
22862
22631
  }
22863
22632
  }],
22864
22633
  "responses": {
22865
22634
  "200": {
22866
- "description": "Decline a rule request",
22635
+ "description": "Read versions of a rule",
22867
22636
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22868
22637
  "type": "object",
22869
22638
  "required": ["data"],
22870
- "properties": { "data": { "$ref": "#/components/schemas/DeclineEmailRuleRequestResult" } }
22639
+ "properties": { "data": { "$ref": "#/components/schemas/ListEmailRuleVersionsResult" } }
22871
22640
  }] } } },
22872
22641
  "headers": {
22873
22642
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -22876,6 +22645,19 @@ const openapiDocument = {
22876
22645
  "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
22877
22646
  }
22878
22647
  },
22648
+ "400": {
22649
+ "description": "Invalid request parameters",
22650
+ "content": { "application/json": {
22651
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22652
+ "example": {
22653
+ "success": false,
22654
+ "error": {
22655
+ "code": "validation_error",
22656
+ "message": "Invalid domain format"
22657
+ }
22658
+ }
22659
+ } }
22660
+ },
22879
22661
  "401": {
22880
22662
  "description": "Invalid or missing API key",
22881
22663
  "content": { "application/json": {
@@ -22896,16 +22678,620 @@ const openapiDocument = {
22896
22678
  }
22897
22679
  },
22898
22680
  "403": {
22899
- "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_request_target_only: only the member the request was made to may decline it.",
22681
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22900
22682
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22901
22683
  },
22902
22684
  "404": {
22903
- "description": "not_found: no request you can see has this id.",
22904
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22905
- },
22906
- "409": {
22907
- "description": "email_rule_request_not_pending: the request was already accepted, declined or withdrawn (details.status).",
22908
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22685
+ "description": "Resource not found",
22686
+ "content": { "application/json": {
22687
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22688
+ "example": {
22689
+ "success": false,
22690
+ "error": {
22691
+ "code": "not_found",
22692
+ "message": "Resource not found"
22693
+ }
22694
+ }
22695
+ } }
22696
+ },
22697
+ "429": {
22698
+ "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.",
22699
+ "headers": {
22700
+ "Retry-After": {
22701
+ "schema": { "type": "integer" },
22702
+ "description": "Seconds to wait before retrying"
22703
+ },
22704
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22705
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22706
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22707
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22708
+ },
22709
+ "content": { "application/json": {
22710
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22711
+ "example": {
22712
+ "success": false,
22713
+ "error": {
22714
+ "code": "rate_limit_exceeded",
22715
+ "message": "Rate limit exceeded"
22716
+ }
22717
+ }
22718
+ } }
22719
+ }
22720
+ },
22721
+ "x-primitive-permission": ["mail:read"]
22722
+ } },
22723
+ "/email-rules/{id}/scope": { "get": {
22724
+ "operationId": "getEmailRuleScope",
22725
+ "summary": "Read what a rule covers",
22726
+ "description": "Whose mail the rule runs over, and what that comes to now: a rule never reaches further than what its author can read when it runs, so what a selected rule names and its author can no longer read stops matching. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22727
+ "tags": ["Email Rules"],
22728
+ "security": [{ "BearerAuth": [] }],
22729
+ "parameters": [{
22730
+ "name": "id",
22731
+ "in": "path",
22732
+ "required": true,
22733
+ "description": "The rule id.",
22734
+ "schema": {
22735
+ "type": "string",
22736
+ "format": "uuid"
22737
+ }
22738
+ }],
22739
+ "responses": {
22740
+ "200": {
22741
+ "description": "Read what a rule covers",
22742
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22743
+ "type": "object",
22744
+ "required": ["data"],
22745
+ "properties": { "data": { "$ref": "#/components/schemas/GetEmailRuleScopeResult" } }
22746
+ }] } } },
22747
+ "headers": {
22748
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
22749
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
22750
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22751
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
22752
+ }
22753
+ },
22754
+ "401": {
22755
+ "description": "Invalid or missing API key",
22756
+ "content": { "application/json": {
22757
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22758
+ "example": {
22759
+ "success": false,
22760
+ "error": {
22761
+ "code": "unauthorized",
22762
+ "message": "Invalid or missing API key"
22763
+ }
22764
+ }
22765
+ } },
22766
+ "headers": {
22767
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22768
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22769
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22770
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22771
+ }
22772
+ },
22773
+ "403": {
22774
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22775
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22776
+ },
22777
+ "404": {
22778
+ "description": "not_found: no such rule, or not one you can see.",
22779
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22780
+ },
22781
+ "429": {
22782
+ "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.",
22783
+ "headers": {
22784
+ "Retry-After": {
22785
+ "schema": { "type": "integer" },
22786
+ "description": "Seconds to wait before retrying"
22787
+ },
22788
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22789
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22790
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22791
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22792
+ },
22793
+ "content": { "application/json": {
22794
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22795
+ "example": {
22796
+ "success": false,
22797
+ "error": {
22798
+ "code": "rate_limit_exceeded",
22799
+ "message": "Rate limit exceeded"
22800
+ }
22801
+ }
22802
+ } }
22803
+ }
22804
+ },
22805
+ "x-primitive-permission": ["mail:read"]
22806
+ } },
22807
+ "/email-rule-scope-options": { "get": {
22808
+ "operationId": "listEmailRuleScopeOptions",
22809
+ "summary": "List what a rule can cover",
22810
+ "description": "How much the caller can read, and the inboxes and domains the caller can read, which is what they may name when a rule runs over selected inboxes and domains. For a caller who reads the whole organization, addresses lists the inboxes known as someone's (member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access), not every address that receives mail: may_name_unlisted says when an address that is not listed, such as an inbox nobody has claimed, may be named too. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22811
+ "tags": ["Email Rules"],
22812
+ "security": [{ "BearerAuth": [] }],
22813
+ "parameters": [{
22814
+ "name": "q",
22815
+ "in": "query",
22816
+ "required": false,
22817
+ "description": "Only inboxes and domains containing this text.",
22818
+ "schema": {
22819
+ "description": "Only inboxes and domains containing this text.",
22820
+ "type": "string",
22821
+ "maxLength": 200
22822
+ }
22823
+ }],
22824
+ "responses": {
22825
+ "200": {
22826
+ "description": "List what a rule can cover",
22827
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22828
+ "type": "object",
22829
+ "required": ["data"],
22830
+ "properties": { "data": { "$ref": "#/components/schemas/ListEmailRuleScopeOptionsResult" } }
22831
+ }] } } },
22832
+ "headers": {
22833
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
22834
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
22835
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22836
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
22837
+ }
22838
+ },
22839
+ "400": {
22840
+ "description": "Invalid request parameters",
22841
+ "content": { "application/json": {
22842
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22843
+ "example": {
22844
+ "success": false,
22845
+ "error": {
22846
+ "code": "validation_error",
22847
+ "message": "Invalid domain format"
22848
+ }
22849
+ }
22850
+ } }
22851
+ },
22852
+ "401": {
22853
+ "description": "Invalid or missing API key",
22854
+ "content": { "application/json": {
22855
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22856
+ "example": {
22857
+ "success": false,
22858
+ "error": {
22859
+ "code": "unauthorized",
22860
+ "message": "Invalid or missing API key"
22861
+ }
22862
+ }
22863
+ } },
22864
+ "headers": {
22865
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22866
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22867
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22868
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22869
+ }
22870
+ },
22871
+ "403": {
22872
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22873
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22874
+ },
22875
+ "409": {
22876
+ "description": "email_rule_author_unresolvable: rules cannot run as you right now.",
22877
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22878
+ },
22879
+ "429": {
22880
+ "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.",
22881
+ "headers": {
22882
+ "Retry-After": {
22883
+ "schema": { "type": "integer" },
22884
+ "description": "Seconds to wait before retrying"
22885
+ },
22886
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22887
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22888
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22889
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22890
+ },
22891
+ "content": { "application/json": {
22892
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22893
+ "example": {
22894
+ "success": false,
22895
+ "error": {
22896
+ "code": "rate_limit_exceeded",
22897
+ "message": "Rate limit exceeded"
22898
+ }
22899
+ }
22900
+ } }
22901
+ }
22902
+ },
22903
+ "x-primitive-permission": ["mail:read"]
22904
+ } },
22905
+ "/email-rule-requests": {
22906
+ "post": {
22907
+ "operationId": "createEmailRuleRequest",
22908
+ "summary": "Ask a member for a rule",
22909
+ "description": "Asks another member of the organization for a rule over their own mail, from a compile response or a tree you wrote. Name them by user id, or by their member mailbox address (at most 30 lookups by address an hour). Nothing is evaluated and nothing is shared until they accept, and they may change the rule first; they see the request and can preview what it matches in their own mail. You may have at most 5 pending requests to one member and 50 in all. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
22910
+ "tags": ["Email Rules"],
22911
+ "security": [{ "BearerAuth": [] }],
22912
+ "requestBody": {
22913
+ "required": true,
22914
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateEmailRuleRequestRequest" } } }
22915
+ },
22916
+ "responses": {
22917
+ "201": {
22918
+ "description": "Ask a member for a rule",
22919
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
22920
+ "type": "object",
22921
+ "required": ["data"],
22922
+ "properties": { "data": { "$ref": "#/components/schemas/CreateEmailRuleRequestResult" } }
22923
+ }] } } }
22924
+ },
22925
+ "400": {
22926
+ "description": "validation_error: give exactly one of target_user_id and target_email and a tree that is valid for the column contract; details.reason self_request when the member named is you.",
22927
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22928
+ },
22929
+ "401": {
22930
+ "description": "Invalid or missing API key",
22931
+ "content": { "application/json": {
22932
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22933
+ "example": {
22934
+ "success": false,
22935
+ "error": {
22936
+ "code": "unauthorized",
22937
+ "message": "Invalid or missing API key"
22938
+ }
22939
+ }
22940
+ } },
22941
+ "headers": {
22942
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
22943
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
22944
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
22945
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
22946
+ }
22947
+ },
22948
+ "403": {
22949
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
22950
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22951
+ },
22952
+ "404": {
22953
+ "description": "not_found: no current member of this organization matches the member named.",
22954
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22955
+ },
22956
+ "409": {
22957
+ "description": "email_rule_request_limit: you already have the most pending requests allowed (details.limit, and details.scope target or total).",
22958
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22959
+ },
22960
+ "429": {
22961
+ "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.",
22962
+ "headers": {
22963
+ "Retry-After": {
22964
+ "schema": { "type": "integer" },
22965
+ "description": "Seconds to wait before retrying"
22966
+ },
22967
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
22968
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
22969
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
22970
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
22971
+ },
22972
+ "content": { "application/json": {
22973
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
22974
+ "example": {
22975
+ "success": false,
22976
+ "error": {
22977
+ "code": "rate_limit_exceeded",
22978
+ "message": "Rate limit exceeded"
22979
+ }
22980
+ }
22981
+ } }
22982
+ },
22983
+ "503": {
22984
+ "description": "service_unavailable: looking members up by address is unavailable right now. Try again in a moment, or name them by user id.",
22985
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22986
+ }
22987
+ },
22988
+ "x-primitive-permission": ["mail:read"],
22989
+ "parameters": [{
22990
+ "name": "Idempotency-Key",
22991
+ "in": "header",
22992
+ "required": false,
22993
+ "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.",
22994
+ "schema": {
22995
+ "type": "string",
22996
+ "minLength": 1,
22997
+ "maxLength": 255
22998
+ }
22999
+ }]
23000
+ },
23001
+ "get": {
23002
+ "operationId": "listEmailRuleRequests",
23003
+ "summary": "List rule requests",
23004
+ "description": "Rule requests, newest first: those made to you (box incoming, the default), those you made (outgoing), or every one you can see (all). Pending ones by default; status decided lists accepted, declined and withdrawn ones, and any lists both. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
23005
+ "tags": ["Email Rules"],
23006
+ "security": [{ "BearerAuth": [] }],
23007
+ "parameters": [
23008
+ {
23009
+ "name": "box",
23010
+ "in": "query",
23011
+ "required": false,
23012
+ "description": "incoming, outgoing or all.",
23013
+ "schema": {
23014
+ "default": "incoming",
23015
+ "description": "incoming: requests made to you; outgoing: requests you made; all: every request you can see (owners and admins see the organization's).",
23016
+ "type": "string",
23017
+ "enum": [
23018
+ "incoming",
23019
+ "outgoing",
23020
+ "all"
23021
+ ]
23022
+ }
23023
+ },
23024
+ {
23025
+ "name": "status",
23026
+ "in": "query",
23027
+ "required": false,
23028
+ "description": "pending, decided or any.",
23029
+ "schema": {
23030
+ "default": "pending",
23031
+ "type": "string",
23032
+ "enum": [
23033
+ "pending",
23034
+ "decided",
23035
+ "any"
23036
+ ]
23037
+ }
23038
+ },
23039
+ {
23040
+ "name": "limit",
23041
+ "in": "query",
23042
+ "required": false,
23043
+ "description": "Maximum number of items to return.",
23044
+ "schema": {
23045
+ "default": 25,
23046
+ "type": "integer",
23047
+ "minimum": 1,
23048
+ "maximum": 100
23049
+ }
23050
+ },
23051
+ {
23052
+ "name": "cursor",
23053
+ "in": "query",
23054
+ "required": false,
23055
+ "description": "Opaque cursor from the previous page's next_cursor. Omit for the first page.",
23056
+ "schema": {
23057
+ "type": "string",
23058
+ "maxLength": 200
23059
+ }
23060
+ }
23061
+ ],
23062
+ "responses": {
23063
+ "200": {
23064
+ "description": "List rule requests",
23065
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
23066
+ "type": "object",
23067
+ "required": ["data"],
23068
+ "properties": { "data": { "$ref": "#/components/schemas/ListEmailRuleRequestsResult" } }
23069
+ }] } } },
23070
+ "headers": {
23071
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
23072
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
23073
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
23074
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
23075
+ }
23076
+ },
23077
+ "400": {
23078
+ "description": "Invalid request parameters",
23079
+ "content": { "application/json": {
23080
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23081
+ "example": {
23082
+ "success": false,
23083
+ "error": {
23084
+ "code": "validation_error",
23085
+ "message": "Invalid domain format"
23086
+ }
23087
+ }
23088
+ } }
23089
+ },
23090
+ "401": {
23091
+ "description": "Invalid or missing API key",
23092
+ "content": { "application/json": {
23093
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23094
+ "example": {
23095
+ "success": false,
23096
+ "error": {
23097
+ "code": "unauthorized",
23098
+ "message": "Invalid or missing API key"
23099
+ }
23100
+ }
23101
+ } },
23102
+ "headers": {
23103
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
23104
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
23105
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
23106
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
23107
+ }
23108
+ },
23109
+ "403": {
23110
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
23111
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23112
+ },
23113
+ "429": {
23114
+ "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.",
23115
+ "headers": {
23116
+ "Retry-After": {
23117
+ "schema": { "type": "integer" },
23118
+ "description": "Seconds to wait before retrying"
23119
+ },
23120
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
23121
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
23122
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
23123
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
23124
+ },
23125
+ "content": { "application/json": {
23126
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23127
+ "example": {
23128
+ "success": false,
23129
+ "error": {
23130
+ "code": "rate_limit_exceeded",
23131
+ "message": "Rate limit exceeded"
23132
+ }
23133
+ }
23134
+ } }
23135
+ }
23136
+ },
23137
+ "x-primitive-permission": ["mail:read"]
23138
+ }
23139
+ },
23140
+ "/email-rule-requests/{id}": { "get": {
23141
+ "operationId": "getEmailRuleRequest",
23142
+ "summary": "Read a rule request",
23143
+ "description": "A rule request: the rule as proposed, who made it and to whom, and whether each of them is still a member. A request is visible to the member who made it, the member it was made to, and the organization's owners and admins. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
23144
+ "tags": ["Email Rules"],
23145
+ "security": [{ "BearerAuth": [] }],
23146
+ "parameters": [{
23147
+ "name": "id",
23148
+ "in": "path",
23149
+ "required": true,
23150
+ "description": "The request id.",
23151
+ "schema": {
23152
+ "type": "string",
23153
+ "format": "uuid"
23154
+ }
23155
+ }],
23156
+ "responses": {
23157
+ "200": {
23158
+ "description": "Read a rule request",
23159
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
23160
+ "type": "object",
23161
+ "required": ["data"],
23162
+ "properties": { "data": { "$ref": "#/components/schemas/GetEmailRuleRequestResult" } }
23163
+ }] } } },
23164
+ "headers": {
23165
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
23166
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
23167
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
23168
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
23169
+ }
23170
+ },
23171
+ "401": {
23172
+ "description": "Invalid or missing API key",
23173
+ "content": { "application/json": {
23174
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23175
+ "example": {
23176
+ "success": false,
23177
+ "error": {
23178
+ "code": "unauthorized",
23179
+ "message": "Invalid or missing API key"
23180
+ }
23181
+ }
23182
+ } },
23183
+ "headers": {
23184
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
23185
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
23186
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
23187
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
23188
+ }
23189
+ },
23190
+ "403": {
23191
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead.",
23192
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23193
+ },
23194
+ "404": {
23195
+ "description": "not_found: no request you can see has this id.",
23196
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23197
+ },
23198
+ "429": {
23199
+ "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.",
23200
+ "headers": {
23201
+ "Retry-After": {
23202
+ "schema": { "type": "integer" },
23203
+ "description": "Seconds to wait before retrying"
23204
+ },
23205
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
23206
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
23207
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
23208
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
23209
+ },
23210
+ "content": { "application/json": {
23211
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23212
+ "example": {
23213
+ "success": false,
23214
+ "error": {
23215
+ "code": "rate_limit_exceeded",
23216
+ "message": "Rate limit exceeded"
23217
+ }
23218
+ }
23219
+ } }
23220
+ }
23221
+ },
23222
+ "x-primitive-permission": ["mail:read"]
23223
+ } },
23224
+ "/email-rule-requests/{id}/decline": { "post": {
23225
+ "operationId": "declineEmailRuleRequest",
23226
+ "summary": "Decline a rule request",
23227
+ "description": "Declines a pending request made to you. Nothing is written but the decision. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
23228
+ "tags": ["Email Rules"],
23229
+ "security": [{ "BearerAuth": [] }],
23230
+ "parameters": [{
23231
+ "name": "id",
23232
+ "in": "path",
23233
+ "required": true,
23234
+ "description": "The request id.",
23235
+ "schema": {
23236
+ "type": "string",
23237
+ "format": "uuid"
23238
+ }
23239
+ }, {
23240
+ "name": "Idempotency-Key",
23241
+ "in": "header",
23242
+ "required": false,
23243
+ "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.",
23244
+ "schema": {
23245
+ "type": "string",
23246
+ "minLength": 1,
23247
+ "maxLength": 255
23248
+ }
23249
+ }],
23250
+ "responses": {
23251
+ "200": {
23252
+ "description": "Decline a rule request",
23253
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
23254
+ "type": "object",
23255
+ "required": ["data"],
23256
+ "properties": { "data": { "$ref": "#/components/schemas/DeclineEmailRuleRequestResult" } }
23257
+ }] } } },
23258
+ "headers": {
23259
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
23260
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
23261
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
23262
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
23263
+ }
23264
+ },
23265
+ "401": {
23266
+ "description": "Invalid or missing API key",
23267
+ "content": { "application/json": {
23268
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
23269
+ "example": {
23270
+ "success": false,
23271
+ "error": {
23272
+ "code": "unauthorized",
23273
+ "message": "Invalid or missing API key"
23274
+ }
23275
+ }
23276
+ } },
23277
+ "headers": {
23278
+ "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
23279
+ "ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
23280
+ "ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
23281
+ "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
23282
+ }
23283
+ },
23284
+ "403": {
23285
+ "description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_request_target_only: only the member the request was made to may decline it.",
23286
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23287
+ },
23288
+ "404": {
23289
+ "description": "not_found: no request you can see has this id.",
23290
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23291
+ },
23292
+ "409": {
23293
+ "description": "email_rule_request_not_pending: the request was already accepted, declined or withdrawn (details.status).",
23294
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
22909
23295
  },
22910
23296
  "429": {
22911
23297
  "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.",
@@ -30954,7 +31340,7 @@ const openapiDocument = {
30954
31340
  "summary": "List every credential",
30955
31341
  "tags": ["Credentials"],
30956
31342
  "security": [{ "BearerAuth": [] }],
30957
- "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
31343
+ "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
30958
31344
  "x-primitive-person-only": { "access": "session" },
30959
31345
  "parameters": [{
30960
31346
  "name": "kind",
@@ -31083,7 +31469,7 @@ const openapiDocument = {
31083
31469
  "summary": "Revoke a credential",
31084
31470
  "tags": ["Credentials"],
31085
31471
  "security": [{ "BearerAuth": [] }],
31086
- "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up keeps working and is listed in standing_objects for review. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
31472
+ "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up keeps working and is listed in standing_objects for review. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
31087
31473
  "x-primitive-person-only": { "access": "session" },
31088
31474
  "parameters": [{
31089
31475
  "name": "Idempotency-Key",
@@ -31223,7 +31609,7 @@ const openapiDocument = {
31223
31609
  "summary": "Revoke every credential (emergency)",
31224
31610
  "tags": ["Credentials"],
31225
31611
  "security": [{ "BearerAuth": [] }],
31226
- "description": "The emergency response to a leak: revokes every API key, agent connection key and connected app created since a time, rotates the organization Function key and every running Function's own key, and optionally suspends Functions. Owners only, from a dashboard session, and the body must confirm with \"revoke all\". Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:revoke_all. API keys, agent connection keys and agents are refused.",
31612
+ "description": "The emergency response to a leak: revokes every API key, agent connection key and connected app created since a time, rotates the organization Function key and every running Function's own key, and optionally suspends Functions. Owners only, from a dashboard session, and the body must confirm with \"revoke all\". Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:revoke_all. API keys, agent connection keys and agents are refused.",
31227
31613
  "x-primitive-person-only": { "access": "session" },
31228
31614
  "parameters": [{
31229
31615
  "name": "Idempotency-Key",
@@ -31350,7 +31736,7 @@ const openapiDocument = {
31350
31736
  "summary": "Read the credential audit trail",
31351
31737
  "tags": ["Credentials"],
31352
31738
  "security": [{ "BearerAuth": [] }],
31353
- "description": "Credential events (created, rotated, edited, revoked, used with a step-up, policy changes), newest first, with a cursor. format=csv returns the same page as CSV. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credential_events:read. API keys, agent connection keys and agents are refused.",
31739
+ "description": "Credential events (created, rotated, edited, revoked, used with a step-up, policy changes), newest first, with a cursor. format=csv returns the same page as CSV. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credential_events:read. API keys, agent connection keys and agents are refused.",
31354
31740
  "x-primitive-person-only": { "access": "session" },
31355
31741
  "parameters": [
31356
31742
  {
@@ -31552,7 +31938,7 @@ const openapiDocument = {
31552
31938
  "summary": "Read the credential policy",
31553
31939
  "tags": ["Credentials"],
31554
31940
  "security": [{ "BearerAuth": [] }],
31555
- "description": "The organization credential policy: key lifetime and expiry rules, who may create super admin keys, whether members may create keys and the other rules every new key is held to. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
31941
+ "description": "The organization credential policy: key lifetime and expiry rules, who may create super admin keys, whether members may create keys and the other rules every new key is held to. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
31556
31942
  "x-primitive-person-only": { "access": "session" },
31557
31943
  "parameters": [],
31558
31944
  "responses": {
@@ -31665,7 +32051,7 @@ const openapiDocument = {
31665
32051
  "summary": "Change the credential policy",
31666
32052
  "tags": ["Credentials"],
31667
32053
  "security": [{ "BearerAuth": [] }],
31668
- "description": "Change one or more policy rules. Admins may only tighten the policy; loosening any rule needs an owner. Owners and admins are emailed about every change. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credential_policy:manage. API keys, agent connection keys and agents are refused.",
32054
+ "description": "Change one or more policy rules. Admins may only tighten the policy; loosening any rule needs an owner. Owners and admins are emailed about every change. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credential_policy:manage. API keys, agent connection keys and agents are refused.",
31669
32055
  "x-primitive-person-only": { "access": "session" },
31670
32056
  "parameters": [],
31671
32057
  "responses": {
@@ -31783,7 +32169,7 @@ const openapiDocument = {
31783
32169
  "summary": "Read the key builder",
31784
32170
  "tags": ["Credentials"],
31785
32171
  "security": [{ "BearerAuth": [] }],
31786
- "description": "The key presets the caller may use (each with whether it is available and why not), the permission matrix for custom keys and the policy in force. An area marked org_wide_only (Members) is listed only to a person who holds its permission, and only a custom key with no addresses or domains may hold it. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
32172
+ "description": "The key presets the caller may use (each with whether it is available and why not), the permission matrix for custom keys and the policy in force. An area marked org_wide_only (Members) is listed only to a person who holds its permission, and only a custom key with no addresses or domains may hold it. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
31787
32173
  "x-primitive-person-only": { "access": "session" },
31788
32174
  "parameters": [],
31789
32175
  "responses": {
@@ -31897,10 +32283,11 @@ const openapiDocument = {
31897
32283
  "summary": "Create an API key",
31898
32284
  "tags": ["Credentials"],
31899
32285
  "security": [{ "BearerAuth": [] }],
31900
- "description": "Create an API key, super admin or narrowed to a preset or a custom permission set, optionally with an expiry and conditions. The secret is returned once. A key never holds more than the person creating it; the credential policy decides who may create which keys. grant is required: a request without it is refused (400 validation_error, details.reason grant_required), never treated as super admin. A custom key must say which mail it reaches: the addresses and domains it lists (grant.addresses, grant.domains), or every mailbox in the organization with grant.mail_scope \"org\", which cannot be combined with them; one that says neither is refused (400 validation_error, details.reason mail_scope_required). Super admin, Send only and Read only reach every mailbox by definition. members:read is only for an org-wide custom key: naming it with grant.addresses or grant.domains is refused (400). Owners and admins are emailed about every super admin key. Send an Idempotency-Key to retry safely: a second create by the same person under the same key makes no second key and is refused (409 idempotency_key_reused) with the first key's id in error.details.key_id, so a caller whose first answer was lost can revoke that key. The onboarding field is for the dashboard only. The Primitive CLI's token is refused, even with a step-up: keys are created in the dashboard or by an app the person connected. While the organization's keys API is not enabled, this answers 404 feature_disabled, except that a dashboard session may still create a super admin key (the dashboard's own create).\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
32286
+ "description": "Create an API key, super admin or narrowed to a preset or a custom permission set, optionally with an expiry and conditions. The secret is returned once. A key never holds more than the person creating it; the credential policy decides who may create which keys. grant is required: a request without it is refused (400 validation_error, details.reason grant_required), never treated as super admin. A custom key must say which mail it reaches: the addresses and domains it lists (grant.addresses, grant.domains), or every mailbox in the organization with grant.mail_scope \"org\", which cannot be combined with them; one that says neither is refused (400 validation_error, details.reason mail_scope_required). Super admin, Send only and Read only reach every mailbox by definition. members:read is only for an org-wide custom key: naming it with grant.addresses or grant.domains is refused (400). Owners and admins are emailed about every super admin key. Send an Idempotency-Key to retry safely: a second create by the same person under the same key makes no second key and is refused (409 idempotency_key_reused) with the first key's id in error.details.key_id, so a caller whose first answer was lost can revoke that key. The onboarding field is for the dashboard only. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. While the organization's keys API is not enabled, this answers 404 feature_disabled (details.feature keys_api; only Primitive turns it on), except that a dashboard session may still create a super admin key (the dashboard's own create).\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
31901
32287
  "x-primitive-person-only": {
31902
32288
  "access": "step_up",
31903
- "step_up_purpose": "keys.create"
32289
+ "step_up_purpose": "keys.create",
32290
+ "cli_without_step_up": true
31904
32291
  },
31905
32292
  "parameters": [
31906
32293
  {
@@ -31927,7 +32314,7 @@ const openapiDocument = {
31927
32314
  "name": "Primitive-Step-Up",
31928
32315
  "in": "header",
31929
32316
  "required": false,
31930
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
32317
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
31931
32318
  "schema": {
31932
32319
  "type": "string",
31933
32320
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -32055,7 +32442,7 @@ const openapiDocument = {
32055
32442
  "summary": "List API keys",
32056
32443
  "tags": ["Credentials"],
32057
32444
  "security": [{ "BearerAuth": [] }],
32058
- "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry and suspension. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
32445
+ "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry and suspension. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
32059
32446
  "x-primitive-person-only": { "access": "session_or_cli" },
32060
32447
  "parameters": [{
32061
32448
  "name": "cursor",
@@ -32178,10 +32565,11 @@ const openapiDocument = {
32178
32565
  "summary": "Rotate an API key",
32179
32566
  "tags": ["Credentials"],
32180
32567
  "security": [{ "BearerAuth": [] }],
32181
- "description": "Mint a successor with the same grant and return its secret once. The old key keeps working for overlap_seconds, then stops. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner created is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The Primitive CLI's token is refused, even with a step-up. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
32568
+ "description": "Mint a successor with the same grant and return its secret once. The old key keeps working for overlap_seconds, then stops. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner created is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
32182
32569
  "x-primitive-person-only": {
32183
32570
  "access": "step_up",
32184
- "step_up_purpose": "keys.create"
32571
+ "step_up_purpose": "keys.create",
32572
+ "cli_without_step_up": true
32185
32573
  },
32186
32574
  "parameters": [
32187
32575
  {
@@ -32198,7 +32586,7 @@ const openapiDocument = {
32198
32586
  "name": "Primitive-Step-Up",
32199
32587
  "in": "header",
32200
32588
  "required": false,
32201
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
32589
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
32202
32590
  "schema": {
32203
32591
  "type": "string",
32204
32592
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -32344,10 +32732,11 @@ const openapiDocument = {
32344
32732
  "summary": "Change an API key",
32345
32733
  "tags": ["Credentials"],
32346
32734
  "security": [{ "BearerAuth": [] }],
32347
- "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The Primitive CLI's token is refused, even with a step-up. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
32735
+ "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
32348
32736
  "x-primitive-person-only": {
32349
32737
  "access": "step_up",
32350
- "step_up_purpose": "keys.create"
32738
+ "step_up_purpose": "keys.create",
32739
+ "cli_without_step_up": true
32351
32740
  },
32352
32741
  "parameters": [{
32353
32742
  "name": "id",
@@ -32362,7 +32751,7 @@ const openapiDocument = {
32362
32751
  "name": "Primitive-Step-Up",
32363
32752
  "in": "header",
32364
32753
  "required": false,
32365
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
32754
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
32366
32755
  "schema": {
32367
32756
  "type": "string",
32368
32757
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -32495,7 +32884,7 @@ const openapiDocument = {
32495
32884
  "summary": "Get an API key",
32496
32885
  "tags": ["Credentials"],
32497
32886
  "security": [{ "BearerAuth": [] }],
32498
- "description": "One key's metadata, never its secret. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
32887
+ "description": "One key's metadata, never its secret. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
32499
32888
  "x-primitive-person-only": { "access": "session_or_cli" },
32500
32889
  "parameters": [{
32501
32890
  "name": "id",
@@ -32618,7 +33007,7 @@ const openapiDocument = {
32618
33007
  "summary": "Suggest a least-privilege grant for a key",
32619
33008
  "tags": ["Credentials"],
32620
33009
  "security": [{ "BearerAuth": [] }],
32621
- "description": "The permissions a key used over the last 30 days, as a custom grant that would have served every successful call (also as builder areas), the narrowest mail scope its calls support (depth is deprecated: raw when it downloaded raw MIME, otherwise content) (the mailboxes of the messages it named and sent, with notes on what the request log cannot account for), and whether it is unused. Calls to routes only the super admin key can use (groups and mailbox access) derive no permission, since a custom key is refused there; super_admin_only_calls counts them. usage gives the same window anonymized: successful and failed requests per UTC day, the routes called by their declared template (never the called path) with the permission each needed, and how many mailboxes the key read and sent from (counts only, and only for a person with access to the whole organization's mail). Nothing is applied: narrowing is a change to the key (PATCH /v1/keys/:id). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
33010
+ "description": "The permissions a key used over the last 30 days, as a custom grant that would have served every successful call (also as builder areas), the narrowest mail scope its calls support (depth is deprecated: raw when it downloaded raw MIME, otherwise content) (the mailboxes of the messages it named and sent, with notes on what the request log cannot account for), and whether it is unused. Calls to routes only the super admin key can use (groups and mailbox access) derive no permission, since a custom key is refused there; super_admin_only_calls counts them. usage gives the same window anonymized: successful and failed requests per UTC day, the routes called by their declared template (never the called path) with the permission each needed, and how many mailboxes the key read and sent from (counts only, and only for a person with access to the whole organization's mail). Nothing is applied: narrowing is a change to the key (PATCH /v1/keys/:id). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
32622
33011
  "x-primitive-person-only": { "access": "session" },
32623
33012
  "parameters": [{
32624
33013
  "name": "id",
@@ -36159,7 +36548,7 @@ const openapiDocument = {
36159
36548
  "/access-suspensions/{id}/decision": { "post": {
36160
36549
  "operationId": "decideSuspension",
36161
36550
  "summary": "Decide a suspension",
36162
- "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key, needs the decider to cover it (revoking an uncovered key is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (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 members:manage. API keys are refused.",
36551
+ "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key, needs the decider to cover it (revoking an uncovered key is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (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 members:manage. API keys are refused.",
36163
36552
  "tags": ["Roles and members"],
36164
36553
  "security": [{ "BearerAuth": [] }],
36165
36554
  "parameters": [
@@ -37851,6 +38240,8 @@ const openapiDocument = {
37851
38240
  "email_rule_request_party_left",
37852
38241
  "email_rule_request_proposer_only",
37853
38242
  "email_rule_request_target_only",
38243
+ "email_rule_scope_locked",
38244
+ "email_rule_scope_not_readable",
37854
38245
  "email_rule_share_ended",
37855
38246
  "email_rule_share_limit",
37856
38247
  "embedding_provider_unavailable",
@@ -38000,6 +38391,7 @@ const openapiDocument = {
38000
38391
  "signal_not_allowed",
38001
38392
  "signal_parent_not_ready",
38002
38393
  "signal_sender_unauthenticated",
38394
+ "signing_secret_unavailable",
38003
38395
  "signup_failed",
38004
38396
  "standing_owner_revoked",
38005
38397
  "step_up_invalid",
@@ -47520,96 +47912,132 @@ const openapiDocument = {
47520
47912
  "vocabulary",
47521
47913
  "when",
47522
47914
  "questions"
47523
- ]
47524
- },
47525
- "explanation": {
47526
- "type": "string",
47527
- "description": "What the rule matches, in plain English."
47528
- },
47529
- "assumptions": {
47530
- "type": "array",
47531
- "items": { "type": "string" },
47532
- "description": "Readings the compiler chose where the description was open to more than one."
47533
- },
47534
- "warnings": {
47535
- "type": "array",
47536
- "items": { "type": "string" },
47537
- "description": "Parts of the description the column contract cannot express, and how the rule handles them."
47915
+ ]
47916
+ },
47917
+ "explanation": {
47918
+ "type": "string",
47919
+ "description": "What the rule matches, in plain English."
47920
+ },
47921
+ "assumptions": {
47922
+ "type": "array",
47923
+ "items": { "type": "string" },
47924
+ "description": "Readings the compiler chose where the description was open to more than one."
47925
+ },
47926
+ "warnings": {
47927
+ "type": "array",
47928
+ "items": { "type": "string" },
47929
+ "description": "Parts of the description the column contract cannot express, and how the rule handles them."
47930
+ },
47931
+ "compiled_by": {
47932
+ "type": "string",
47933
+ "description": "An opaque name for the version of the rule compiler that produced the rule, such as rule-compiler-v1. Send it back as compiled_by when you save the rule."
47934
+ },
47935
+ "sql": {
47936
+ "description": "Present only when include_sql was true.",
47937
+ "type": "object",
47938
+ "properties": {
47939
+ "expression": {
47940
+ "type": "string",
47941
+ "description": "A parameterized boolean expression over one row per email."
47942
+ },
47943
+ "params": {
47944
+ "type": "array",
47945
+ "items": {},
47946
+ "description": "The values bound to $1, $2 and so on."
47947
+ }
47948
+ },
47949
+ "required": ["expression", "params"]
47950
+ }
47951
+ },
47952
+ "required": [
47953
+ "tree",
47954
+ "explanation",
47955
+ "assumptions",
47956
+ "warnings",
47957
+ "compiled_by"
47958
+ ]
47959
+ },
47960
+ "TestEmailRuleRequest": {
47961
+ "type": "object",
47962
+ "properties": {
47963
+ "tree": {
47964
+ "description": "An unsaved rule tree, as POST /email-rules/compile returned it. Give tree or rule_id, not both.",
47965
+ "type": "object",
47966
+ "properties": {
47967
+ "version": {
47968
+ "type": "number",
47969
+ "const": 1
47970
+ },
47971
+ "vocabulary": {
47972
+ "type": "string",
47973
+ "description": "The column contract version the rule is written against, for example v1."
47974
+ },
47975
+ "when": {
47976
+ "type": "object",
47977
+ "propertyNames": { "type": "string" },
47978
+ "additionalProperties": {},
47979
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
47980
+ },
47981
+ "questions": {
47982
+ "type": "array",
47983
+ "items": { "type": "string" },
47984
+ "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
47985
+ }
47986
+ },
47987
+ "required": [
47988
+ "version",
47989
+ "vocabulary",
47990
+ "when",
47991
+ "questions"
47992
+ ],
47993
+ "additionalProperties": false
47538
47994
  },
47539
- "compiled_by": {
47995
+ "evaluation_scope": {
47540
47996
  "type": "string",
47541
- "description": "An opaque name for the version of the rule compiler that produced the rule, such as rule-compiler-v1. Send it back as compiled_by when you save the rule."
47997
+ "enum": [
47998
+ "author",
47999
+ "own_mailboxes",
48000
+ "selected"
48001
+ ],
48002
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
47542
48003
  },
47543
- "sql": {
47544
- "description": "Present only when include_sql was true.",
48004
+ "scope_selection": {
47545
48005
  "type": "object",
47546
48006
  "properties": {
47547
- "expression": {
47548
- "type": "string",
47549
- "description": "A parameterized boolean expression over one row per email."
47550
- },
47551
- "params": {
48007
+ "addresses": {
48008
+ "maxItems": 100,
47552
48009
  "type": "array",
47553
- "items": {},
47554
- "description": "The values bound to $1, $2 and so on."
47555
- }
47556
- },
47557
- "required": ["expression", "params"]
47558
- }
47559
- },
47560
- "required": [
47561
- "tree",
47562
- "explanation",
47563
- "assumptions",
47564
- "warnings",
47565
- "compiled_by"
47566
- ]
47567
- },
47568
- "TestEmailRuleRequest": {
47569
- "type": "object",
47570
- "properties": {
47571
- "tree": {
47572
- "description": "An unsaved rule tree, as POST /email-rules/compile returned it. Give tree or rule_id, not both.",
47573
- "type": "object",
47574
- "properties": {
47575
- "version": {
47576
- "type": "number",
47577
- "const": 1
47578
- },
47579
- "vocabulary": {
47580
- "type": "string",
47581
- "description": "The column contract version the rule is written against, for example v1."
47582
- },
47583
- "when": {
47584
- "type": "object",
47585
- "propertyNames": { "type": "string" },
47586
- "additionalProperties": {},
47587
- "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
48010
+ "items": {
48011
+ "type": "string",
48012
+ "minLength": 3,
48013
+ "maxLength": 320,
48014
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
48015
+ }
47588
48016
  },
47589
- "questions": {
48017
+ "domains": {
48018
+ "maxItems": 50,
47590
48019
  "type": "array",
47591
- "items": { "type": "string" },
47592
- "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
48020
+ "items": {
48021
+ "type": "string",
48022
+ "minLength": 1,
48023
+ "maxLength": 253,
48024
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48025
+ }
47593
48026
  }
47594
48027
  },
47595
- "required": [
47596
- "version",
47597
- "vocabulary",
47598
- "when",
47599
- "questions"
47600
- ],
47601
- "additionalProperties": false
47602
- },
47603
- "evaluation_scope": {
47604
- "type": "string",
47605
- "enum": ["author", "own_mailboxes"],
47606
- "description": "Whose mail an unsaved tree runs over. author, the default: all the mail you can read. own_mailboxes: only mail to your own addresses. Only with tree: a saved rule uses its own."
48028
+ "additionalProperties": false,
48029
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
47607
48030
  },
47608
48031
  "rule_id": {
47609
48032
  "description": "A saved rule you can see. Its current version is tested, with its author's access.",
47610
48033
  "type": "string",
47611
48034
  "format": "uuid"
47612
48035
  },
48036
+ "kind": {
48037
+ "description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
48038
+ "type": "string",
48039
+ "enum": ["grant", "do_not_share"]
48040
+ },
47613
48041
  "email_ids": {
47614
48042
  "description": "Test exactly these emails. Without it, the newest emails the rule can read are tested, at most 200 and none older than 30 days.",
47615
48043
  "minItems": 1,
@@ -47874,14 +48302,50 @@ const openapiDocument = {
47874
48302
  },
47875
48303
  "evaluation_scope": {
47876
48304
  "type": "string",
47877
- "enum": ["author", "own_mailboxes"],
47878
- "description": "Whose mail an unsaved tree runs over. author, the default: all the mail you can read. own_mailboxes: only mail to your own addresses. Only with tree: a saved rule uses its own."
48305
+ "enum": [
48306
+ "author",
48307
+ "own_mailboxes",
48308
+ "selected"
48309
+ ],
48310
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
48311
+ },
48312
+ "scope_selection": {
48313
+ "type": "object",
48314
+ "properties": {
48315
+ "addresses": {
48316
+ "maxItems": 100,
48317
+ "type": "array",
48318
+ "items": {
48319
+ "type": "string",
48320
+ "minLength": 3,
48321
+ "maxLength": 320,
48322
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
48323
+ }
48324
+ },
48325
+ "domains": {
48326
+ "maxItems": 50,
48327
+ "type": "array",
48328
+ "items": {
48329
+ "type": "string",
48330
+ "minLength": 1,
48331
+ "maxLength": 253,
48332
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48333
+ }
48334
+ }
48335
+ },
48336
+ "additionalProperties": false,
48337
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
47879
48338
  },
47880
48339
  "rule_id": {
47881
48340
  "description": "A saved rule you can see. Its current version is tested.",
47882
48341
  "type": "string",
47883
48342
  "format": "uuid"
47884
48343
  },
48344
+ "kind": {
48345
+ "description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
48346
+ "type": "string",
48347
+ "enum": ["grant", "do_not_share"]
48348
+ },
47885
48349
  "email": {
47886
48350
  "type": "object",
47887
48351
  "properties": {
@@ -48105,11 +48569,52 @@ const openapiDocument = {
48105
48569
  "maxLength": 200,
48106
48570
  "description": "A short name for the rule."
48107
48571
  },
48572
+ "kind": {
48573
+ "description": "grant (the default): what the rule matches can be shared. do_not_share: the rule runs before every other rule, and while it is active no rule shares an email it matches; an email it cannot decide counts as matched. Set when the rule is created; it never changes.",
48574
+ "type": "string",
48575
+ "enum": ["grant", "do_not_share"]
48576
+ },
48108
48577
  "state": {
48109
- "description": "observe (the default) records what the rule would match and grants nothing; draft is not evaluated.",
48578
+ "description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated.",
48110
48579
  "type": "string",
48111
48580
  "enum": ["draft", "observe"]
48112
48581
  },
48582
+ "evaluation_scope": {
48583
+ "type": "string",
48584
+ "enum": [
48585
+ "author",
48586
+ "own_mailboxes",
48587
+ "selected"
48588
+ ],
48589
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
48590
+ },
48591
+ "scope_selection": {
48592
+ "type": "object",
48593
+ "properties": {
48594
+ "addresses": {
48595
+ "maxItems": 100,
48596
+ "type": "array",
48597
+ "items": {
48598
+ "type": "string",
48599
+ "minLength": 3,
48600
+ "maxLength": 320,
48601
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
48602
+ }
48603
+ },
48604
+ "domains": {
48605
+ "maxItems": 50,
48606
+ "type": "array",
48607
+ "items": {
48608
+ "type": "string",
48609
+ "minLength": 1,
48610
+ "maxLength": 253,
48611
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48612
+ }
48613
+ }
48614
+ },
48615
+ "additionalProperties": false,
48616
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
48617
+ },
48113
48618
  "english": {
48114
48619
  "type": "string",
48115
48620
  "minLength": 1,
@@ -48205,6 +48710,35 @@ const openapiDocument = {
48205
48710
  "active"
48206
48711
  ]
48207
48712
  },
48713
+ "kind": {
48714
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
48715
+ "type": "string",
48716
+ "enum": ["grant", "do_not_share"]
48717
+ },
48718
+ "evaluation_scope": {
48719
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
48720
+ "type": "string",
48721
+ "enum": [
48722
+ "author",
48723
+ "own_mailboxes",
48724
+ "selected"
48725
+ ]
48726
+ },
48727
+ "scope_selection": {
48728
+ "type": ["object", "null"],
48729
+ "properties": {
48730
+ "addresses": {
48731
+ "type": "array",
48732
+ "items": { "type": "string" }
48733
+ },
48734
+ "domains": {
48735
+ "type": "array",
48736
+ "items": { "type": "string" }
48737
+ }
48738
+ },
48739
+ "required": ["addresses", "domains"],
48740
+ "description": "What a selected rule names; null for any other scope."
48741
+ },
48208
48742
  "current_version": {
48209
48743
  "type": ["integer", "null"],
48210
48744
  "exclusiveMinimum": 0
@@ -48341,6 +48875,35 @@ const openapiDocument = {
48341
48875
  "active"
48342
48876
  ]
48343
48877
  },
48878
+ "kind": {
48879
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
48880
+ "type": "string",
48881
+ "enum": ["grant", "do_not_share"]
48882
+ },
48883
+ "evaluation_scope": {
48884
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
48885
+ "type": "string",
48886
+ "enum": [
48887
+ "author",
48888
+ "own_mailboxes",
48889
+ "selected"
48890
+ ]
48891
+ },
48892
+ "scope_selection": {
48893
+ "type": ["object", "null"],
48894
+ "properties": {
48895
+ "addresses": {
48896
+ "type": "array",
48897
+ "items": { "type": "string" }
48898
+ },
48899
+ "domains": {
48900
+ "type": "array",
48901
+ "items": { "type": "string" }
48902
+ }
48903
+ },
48904
+ "required": ["addresses", "domains"],
48905
+ "description": "What a selected rule names; null for any other scope."
48906
+ },
48344
48907
  "current_version": {
48345
48908
  "type": ["integer", "null"],
48346
48909
  "exclusiveMinimum": 0
@@ -48390,6 +48953,35 @@ const openapiDocument = {
48390
48953
  "active"
48391
48954
  ]
48392
48955
  },
48956
+ "kind": {
48957
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
48958
+ "type": "string",
48959
+ "enum": ["grant", "do_not_share"]
48960
+ },
48961
+ "evaluation_scope": {
48962
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
48963
+ "type": "string",
48964
+ "enum": [
48965
+ "author",
48966
+ "own_mailboxes",
48967
+ "selected"
48968
+ ]
48969
+ },
48970
+ "scope_selection": {
48971
+ "type": ["object", "null"],
48972
+ "properties": {
48973
+ "addresses": {
48974
+ "type": "array",
48975
+ "items": { "type": "string" }
48976
+ },
48977
+ "domains": {
48978
+ "type": "array",
48979
+ "items": { "type": "string" }
48980
+ }
48981
+ },
48982
+ "required": ["addresses", "domains"],
48983
+ "description": "What a selected rule names; null for any other scope."
48984
+ },
48393
48985
  "current_version": {
48394
48986
  "type": ["integer", "null"],
48395
48987
  "exclusiveMinimum": 0
@@ -48518,7 +49110,7 @@ const openapiDocument = {
48518
49110
  "description": "A short name for the rule."
48519
49111
  },
48520
49112
  "state": {
48521
- "description": "draft disables the rule; observe records decisions; active also grants what it matches. Keys read and receive what a rule granted only while it is active: leaving active pauses that, and returning to active restores reads and the delivery of queued mail not yet skipped.",
49113
+ "description": "draft disables the rule; observe records decisions; active also grants what it matches, or for a do_not_share rule keeps it from being shared. Keys read and receive what a rule granted only while it is active: leaving active pauses that, and returning to active restores reads and the delivery of queued mail not yet skipped.",
48522
49114
  "type": "string",
48523
49115
  "enum": [
48524
49116
  "draft",
@@ -48595,6 +49187,42 @@ const openapiDocument = {
48595
49187
  "type": "string",
48596
49188
  "minLength": 1,
48597
49189
  "maxLength": 64
49190
+ },
49191
+ "evaluation_scope": {
49192
+ "type": "string",
49193
+ "enum": [
49194
+ "author",
49195
+ "own_mailboxes",
49196
+ "selected"
49197
+ ],
49198
+ "description": "author: all mail its author can read. own_mailboxes: mail to their own addresses. selected: what scope_selection names. Never more than the author can read."
49199
+ },
49200
+ "scope_selection": {
49201
+ "type": "object",
49202
+ "properties": {
49203
+ "addresses": {
49204
+ "maxItems": 100,
49205
+ "type": "array",
49206
+ "items": {
49207
+ "type": "string",
49208
+ "minLength": 3,
49209
+ "maxLength": 320,
49210
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
49211
+ }
49212
+ },
49213
+ "domains": {
49214
+ "maxItems": 50,
49215
+ "type": "array",
49216
+ "items": {
49217
+ "type": "string",
49218
+ "minLength": 1,
49219
+ "maxLength": 253,
49220
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
49221
+ }
49222
+ }
49223
+ },
49224
+ "additionalProperties": false,
49225
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
48598
49226
  }
48599
49227
  },
48600
49228
  "additionalProperties": false
@@ -48615,6 +49243,35 @@ const openapiDocument = {
48615
49243
  "active"
48616
49244
  ]
48617
49245
  },
49246
+ "kind": {
49247
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
49248
+ "type": "string",
49249
+ "enum": ["grant", "do_not_share"]
49250
+ },
49251
+ "evaluation_scope": {
49252
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
49253
+ "type": "string",
49254
+ "enum": [
49255
+ "author",
49256
+ "own_mailboxes",
49257
+ "selected"
49258
+ ]
49259
+ },
49260
+ "scope_selection": {
49261
+ "type": ["object", "null"],
49262
+ "properties": {
49263
+ "addresses": {
49264
+ "type": "array",
49265
+ "items": { "type": "string" }
49266
+ },
49267
+ "domains": {
49268
+ "type": "array",
49269
+ "items": { "type": "string" }
49270
+ }
49271
+ },
49272
+ "required": ["addresses", "domains"],
49273
+ "description": "What a selected rule names; null for any other scope."
49274
+ },
48618
49275
  "current_version": {
48619
49276
  "type": ["integer", "null"],
48620
49277
  "exclusiveMinimum": 0
@@ -48792,6 +49449,370 @@ const openapiDocument = {
48792
49449
  ]
48793
49450
  }
48794
49451
  },
49452
+ "ListEmailRuleResultsResult": {
49453
+ "type": "object",
49454
+ "properties": {
49455
+ "rules": {
49456
+ "type": "array",
49457
+ "items": {
49458
+ "type": "object",
49459
+ "properties": {
49460
+ "id": {
49461
+ "type": "string",
49462
+ "format": "uuid"
49463
+ },
49464
+ "name": { "type": "string" },
49465
+ "state": {
49466
+ "type": "string",
49467
+ "enum": [
49468
+ "draft",
49469
+ "observe",
49470
+ "active"
49471
+ ]
49472
+ },
49473
+ "kind": {
49474
+ "description": "do_not_share: the rule blocks sharing of what it matches. While it is active, no rule shares an email it has matched from then on; anything shared before, including while it was in observe, stays shared. Absent on a rule that shares what it matches.",
49475
+ "type": "string",
49476
+ "enum": ["grant", "do_not_share"]
49477
+ },
49478
+ "decision": {
49479
+ "type": ["object", "null"],
49480
+ "properties": {
49481
+ "rule_version": {
49482
+ "type": "integer",
49483
+ "exclusiveMinimum": 0,
49484
+ "description": "The version of the rule that decided."
49485
+ },
49486
+ "matched": { "type": "boolean" },
49487
+ "parts": {
49488
+ "type": "array",
49489
+ "items": {
49490
+ "type": "object",
49491
+ "properties": {
49492
+ "index": {
49493
+ "type": "integer",
49494
+ "minimum": 0
49495
+ },
49496
+ "matched": { "type": "boolean" },
49497
+ "columns": {
49498
+ "type": "array",
49499
+ "items": { "type": "string" }
49500
+ }
49501
+ },
49502
+ "required": [
49503
+ "index",
49504
+ "matched",
49505
+ "columns"
49506
+ ]
49507
+ },
49508
+ "description": "How each top-level part of the rule decided. Empty when a fact the rule reads was missing, so it was not evaluated."
49509
+ },
49510
+ "question_answers": {
49511
+ "type": "array",
49512
+ "items": {
49513
+ "type": "object",
49514
+ "properties": {
49515
+ "question": { "type": "string" },
49516
+ "answer": { "type": ["boolean", "null"] },
49517
+ "probability": { "type": ["number", "null"] },
49518
+ "error_class": { "type": "string" }
49519
+ },
49520
+ "required": [
49521
+ "question",
49522
+ "answer",
49523
+ "probability"
49524
+ ]
49525
+ },
49526
+ "description": "The answers to the rule's own questions, asked only when its conditions matched."
49527
+ },
49528
+ "tree": {
49529
+ "type": ["object", "null"],
49530
+ "properties": {
49531
+ "version": {
49532
+ "type": "number",
49533
+ "const": 1
49534
+ },
49535
+ "vocabulary": {
49536
+ "type": "string",
49537
+ "description": "The column contract version the rule is written against, for example v1."
49538
+ },
49539
+ "when": {
49540
+ "type": "object",
49541
+ "propertyNames": { "type": "string" },
49542
+ "additionalProperties": {},
49543
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
49544
+ },
49545
+ "questions": {
49546
+ "type": "array",
49547
+ "items": { "type": "string" },
49548
+ "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
49549
+ }
49550
+ },
49551
+ "required": [
49552
+ "version",
49553
+ "vocabulary",
49554
+ "when",
49555
+ "questions"
49556
+ ],
49557
+ "description": "The conditions and questions of the version that decided, so the result reads in that version's words."
49558
+ },
49559
+ "created_at": {
49560
+ "type": "string",
49561
+ "format": "date-time"
49562
+ }
49563
+ },
49564
+ "required": [
49565
+ "rule_version",
49566
+ "matched",
49567
+ "parts",
49568
+ "question_answers",
49569
+ "tree",
49570
+ "created_at"
49571
+ ],
49572
+ "description": "What the rule most recently decided about this email; null when it has not run on it."
49573
+ },
49574
+ "not_run_reason": {
49575
+ "type": ["string", "null"],
49576
+ "enum": [
49577
+ "draft",
49578
+ "created_after_email",
49579
+ null
49580
+ ],
49581
+ "description": "What is known about a rule with no result on this email. draft: the rule is a draft now. created_after_email: the rule was created after the email arrived. Null when it has a result, or when nothing is known."
49582
+ },
49583
+ "author_left": {
49584
+ "description": "On a do-not-share rule whose author is no longer a member of the organization: it blocks nothing now, whatever it decided. Absent otherwise.",
49585
+ "type": "boolean",
49586
+ "const": true
49587
+ }
49588
+ },
49589
+ "required": [
49590
+ "id",
49591
+ "name",
49592
+ "state",
49593
+ "decision",
49594
+ "not_run_reason"
49595
+ ]
49596
+ },
49597
+ "description": "Every rule the caller can see: those that matched this email, then those that did not, then those that have not run on it; newest rule first within each."
49598
+ },
49599
+ "hidden": {
49600
+ "type": "integer",
49601
+ "minimum": 0,
49602
+ "description": "How many more rules the caller can see that are not listed."
49603
+ }
49604
+ },
49605
+ "required": ["rules", "hidden"]
49606
+ },
49607
+ "ListEmailRuleVersionsResult": {
49608
+ "type": "array",
49609
+ "items": {
49610
+ "type": "object",
49611
+ "properties": {
49612
+ "version": {
49613
+ "type": "integer",
49614
+ "exclusiveMinimum": 0
49615
+ },
49616
+ "english": { "type": "string" },
49617
+ "tree": {
49618
+ "type": "object",
49619
+ "properties": {
49620
+ "version": {
49621
+ "type": "number",
49622
+ "const": 1
49623
+ },
49624
+ "vocabulary": {
49625
+ "type": "string",
49626
+ "description": "The column contract version the rule is written against, for example v1."
49627
+ },
49628
+ "when": {
49629
+ "type": "object",
49630
+ "propertyNames": { "type": "string" },
49631
+ "additionalProperties": {},
49632
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
49633
+ },
49634
+ "questions": {
49635
+ "type": "array",
49636
+ "items": { "type": "string" },
49637
+ "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
49638
+ }
49639
+ },
49640
+ "required": [
49641
+ "version",
49642
+ "vocabulary",
49643
+ "when",
49644
+ "questions"
49645
+ ]
49646
+ },
49647
+ "explanation": { "type": "string" },
49648
+ "assumptions": {
49649
+ "type": "array",
49650
+ "items": { "type": "string" }
49651
+ },
49652
+ "warnings": {
49653
+ "type": "array",
49654
+ "items": { "type": "string" }
49655
+ },
49656
+ "compiled_by": {
49657
+ "type": "string",
49658
+ "description": "Who produced the tree, as the caller stated it when saving (not verified), for example rule-compiler-v1 or member."
49659
+ },
49660
+ "vocabulary": { "type": "string" },
49661
+ "created_at": {
49662
+ "type": "string",
49663
+ "format": "date-time"
49664
+ },
49665
+ "sql": {
49666
+ "description": "Present only when include_sql was true.",
49667
+ "type": "object",
49668
+ "properties": {
49669
+ "expression": {
49670
+ "type": "string",
49671
+ "description": "A parameterized boolean expression over one row per email."
49672
+ },
49673
+ "params": {
49674
+ "type": "array",
49675
+ "items": {},
49676
+ "description": "The values bound to $1, $2 and so on."
49677
+ }
49678
+ },
49679
+ "required": ["expression", "params"]
49680
+ }
49681
+ },
49682
+ "required": [
49683
+ "version",
49684
+ "english",
49685
+ "tree",
49686
+ "explanation",
49687
+ "assumptions",
49688
+ "warnings",
49689
+ "compiled_by",
49690
+ "vocabulary",
49691
+ "created_at"
49692
+ ]
49693
+ }
49694
+ },
49695
+ "GetEmailRuleScopeResult": {
49696
+ "type": "object",
49697
+ "properties": {
49698
+ "evaluation_scope": {
49699
+ "type": "string",
49700
+ "enum": [
49701
+ "author",
49702
+ "own_mailboxes",
49703
+ "selected"
49704
+ ],
49705
+ "description": "author: all mail its author can read. own_mailboxes: mail to their own addresses. selected: what scope_selection names. Never more than the author can read."
49706
+ },
49707
+ "scope_selection": {
49708
+ "type": ["object", "null"],
49709
+ "properties": {
49710
+ "addresses": {
49711
+ "type": "array",
49712
+ "items": { "type": "string" }
49713
+ },
49714
+ "domains": {
49715
+ "type": "array",
49716
+ "items": { "type": "string" }
49717
+ }
49718
+ },
49719
+ "required": ["addresses", "domains"]
49720
+ },
49721
+ "locked": {
49722
+ "type": "boolean",
49723
+ "description": "Shared, so its scope cannot change."
49724
+ },
49725
+ "author_reach": {
49726
+ "type": ["string", "null"],
49727
+ "enum": [
49728
+ "organization",
49729
+ "limited",
49730
+ "own",
49731
+ null
49732
+ ],
49733
+ "description": "How much its author can read now; null when it cannot run as them."
49734
+ },
49735
+ "no_longer_readable": {
49736
+ "type": "object",
49737
+ "properties": {
49738
+ "addresses": {
49739
+ "type": "array",
49740
+ "items": { "type": "string" }
49741
+ },
49742
+ "domains": {
49743
+ "type": "array",
49744
+ "items": { "type": "string" }
49745
+ }
49746
+ },
49747
+ "required": ["addresses", "domains"],
49748
+ "description": "What scope_selection names that its author no longer reads in full."
49749
+ },
49750
+ "still_read": {
49751
+ "description": "Inboxes its author still reads at the domains in no_longer_readable; the rule still covers them.",
49752
+ "type": "array",
49753
+ "items": { "type": "string" }
49754
+ },
49755
+ "stopped_reason": {
49756
+ "description": "Why it cannot run now (a reason as on POST /email-rules/test), or null.",
49757
+ "type": ["string", "null"]
49758
+ }
49759
+ },
49760
+ "required": [
49761
+ "evaluation_scope",
49762
+ "scope_selection",
49763
+ "locked",
49764
+ "author_reach",
49765
+ "no_longer_readable",
49766
+ "stopped_reason"
49767
+ ]
49768
+ },
49769
+ "ListEmailRuleScopeOptionsResult": {
49770
+ "type": "object",
49771
+ "properties": {
49772
+ "reach": {
49773
+ "type": "string",
49774
+ "enum": [
49775
+ "organization",
49776
+ "limited",
49777
+ "own"
49778
+ ],
49779
+ "description": "organization: all of the organization's mail. own: only your own. limited: some inboxes or domains."
49780
+ },
49781
+ "own_mail": {
49782
+ "type": "boolean",
49783
+ "description": "Whether a rule over only your own mail would run for you."
49784
+ },
49785
+ "own_mail_to_block": {
49786
+ "description": "Whether a do_not_share rule over only your own mail would run for you: it covers every own mailbox you read, including ones you could not share.",
49787
+ "type": "boolean"
49788
+ },
49789
+ "addresses": {
49790
+ "type": "array",
49791
+ "items": { "type": "string" },
49792
+ "description": "Inboxes you can read, matching q. For someone who reads the whole organization, the inboxes known as someone's: member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access. Otherwise the inboxes your access lists (search with q for those past the first page). See may_name_unlisted for what else you may name."
49793
+ },
49794
+ "domains": {
49795
+ "type": "array",
49796
+ "items": { "type": "string" },
49797
+ "description": "Domains you can read, matching q."
49798
+ },
49799
+ "more": {
49800
+ "type": "boolean",
49801
+ "description": "More match than are listed."
49802
+ },
49803
+ "may_name_unlisted": {
49804
+ "description": "Whether you may also name an inbox this list never shows, however it is searched (more only says whether a search would show further ones): true when you read the whole organization (any address, such as an inbox at its domains nobody has claimed yet) or read a domain (any address there your access does not leave out). Name it in scope_selection.addresses as you would a listed one. Absent from a server that predates it.",
49805
+ "type": "boolean"
49806
+ }
49807
+ },
49808
+ "required": [
49809
+ "reach",
49810
+ "own_mail",
49811
+ "addresses",
49812
+ "domains",
49813
+ "more"
49814
+ ]
49815
+ },
48795
49816
  "CreateEmailRuleRequestRequest": {
48796
49817
  "type": "object",
48797
49818
  "properties": {
@@ -49974,6 +50995,35 @@ const openapiDocument = {
49974
50995
  "active"
49975
50996
  ]
49976
50997
  },
50998
+ "kind": {
50999
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
51000
+ "type": "string",
51001
+ "enum": ["grant", "do_not_share"]
51002
+ },
51003
+ "evaluation_scope": {
51004
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
51005
+ "type": "string",
51006
+ "enum": [
51007
+ "author",
51008
+ "own_mailboxes",
51009
+ "selected"
51010
+ ]
51011
+ },
51012
+ "scope_selection": {
51013
+ "type": ["object", "null"],
51014
+ "properties": {
51015
+ "addresses": {
51016
+ "type": "array",
51017
+ "items": { "type": "string" }
51018
+ },
51019
+ "domains": {
51020
+ "type": "array",
51021
+ "items": { "type": "string" }
51022
+ }
51023
+ },
51024
+ "required": ["addresses", "domains"],
51025
+ "description": "What a selected rule names; null for any other scope."
51026
+ },
49977
51027
  "current_version": {
49978
51028
  "type": ["integer", "null"],
49979
51029
  "exclusiveMinimum": 0
@@ -67819,7 +68869,7 @@ const operationManifest = [
67819
68869
  "binaryResponse": false,
67820
68870
  "bodyRequired": true,
67821
68871
  "command": "create-key",
67822
- "description": "Create an API key, super admin or narrowed to a preset or a custom permission set, optionally with an expiry and conditions. The secret is returned once. A key never holds more than the person creating it; the credential policy decides who may create which keys. grant is required: a request without it is refused (400 validation_error, details.reason grant_required), never treated as super admin. A custom key must say which mail it reaches: the addresses and domains it lists (grant.addresses, grant.domains), or every mailbox in the organization with grant.mail_scope \"org\", which cannot be combined with them; one that says neither is refused (400 validation_error, details.reason mail_scope_required). Super admin, Send only and Read only reach every mailbox by definition. members:read is only for an org-wide custom key: naming it with grant.addresses or grant.domains is refused (400). Owners and admins are emailed about every super admin key. Send an Idempotency-Key to retry safely: a second create by the same person under the same key makes no second key and is refused (409 idempotency_key_reused) with the first key's id in error.details.key_id, so a caller whose first answer was lost can revoke that key. The onboarding field is for the dashboard only. The Primitive CLI's token is refused, even with a step-up: keys are created in the dashboard or by an app the person connected. While the organization's keys API is not enabled, this answers 404 feature_disabled, except that a dashboard session may still create a super admin key (the dashboard's own create).\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
68872
+ "description": "Create an API key, super admin or narrowed to a preset or a custom permission set, optionally with an expiry and conditions. The secret is returned once. A key never holds more than the person creating it; the credential policy decides who may create which keys. grant is required: a request without it is refused (400 validation_error, details.reason grant_required), never treated as super admin. A custom key must say which mail it reaches: the addresses and domains it lists (grant.addresses, grant.domains), or every mailbox in the organization with grant.mail_scope \"org\", which cannot be combined with them; one that says neither is refused (400 validation_error, details.reason mail_scope_required). Super admin, Send only and Read only reach every mailbox by definition. members:read is only for an org-wide custom key: naming it with grant.addresses or grant.domains is refused (400). Owners and admins are emailed about every super admin key. Send an Idempotency-Key to retry safely: a second create by the same person under the same key makes no second key and is refused (409 idempotency_key_reused) with the first key's id in error.details.key_id, so a caller whose first answer was lost can revoke that key. The onboarding field is for the dashboard only. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. While the organization's keys API is not enabled, this answers 404 feature_disabled (details.feature keys_api; only Primitive turns it on), except that a dashboard session may still create a super admin key (the dashboard's own create).\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
67823
68873
  "hasJsonBody": true,
67824
68874
  "method": "POST",
67825
68875
  "operationId": "createKey",
@@ -67842,7 +68892,7 @@ const operationManifest = [
67842
68892
  "type": "string"
67843
68893
  },
67844
68894
  {
67845
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
68895
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
67846
68896
  "enum": null,
67847
68897
  "name": "Primitive-Step-Up",
67848
68898
  "required": false,
@@ -68071,7 +69121,7 @@ const operationManifest = [
68071
69121
  "binaryResponse": false,
68072
69122
  "bodyRequired": false,
68073
69123
  "command": "get-credential-policy",
68074
- "description": "The organization credential policy: key lifetime and expiry rules, who may create super admin keys, whether members may create keys and the other rules every new key is held to. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
69124
+ "description": "The organization credential policy: key lifetime and expiry rules, who may create super admin keys, whether members may create keys and the other rules every new key is held to. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
68075
69125
  "hasJsonBody": false,
68076
69126
  "method": "GET",
68077
69127
  "operationId": "getCredentialPolicy",
@@ -68130,7 +69180,7 @@ const operationManifest = [
68130
69180
  "binaryResponse": false,
68131
69181
  "bodyRequired": false,
68132
69182
  "command": "get-key",
68133
- "description": "One key's metadata, never its secret. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
69183
+ "description": "One key's metadata, never its secret. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
68134
69184
  "hasJsonBody": false,
68135
69185
  "method": "GET",
68136
69186
  "operationId": "getKey",
@@ -68208,7 +69258,7 @@ const operationManifest = [
68208
69258
  "binaryResponse": false,
68209
69259
  "bodyRequired": false,
68210
69260
  "command": "get-key-builder",
68211
- "description": "The key presets the caller may use (each with whether it is available and why not), the permission matrix for custom keys and the policy in force. An area marked org_wide_only (Members) is listed only to a person who holds its permission, and only a custom key with no addresses or domains may hold it. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
69261
+ "description": "The key presets the caller may use (each with whether it is available and why not), the permission matrix for custom keys and the policy in force. An area marked org_wide_only (Members) is listed only to a person who holds its permission, and only a custom key with no addresses or domains may hold it. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
68212
69262
  "hasJsonBody": false,
68213
69263
  "method": "GET",
68214
69264
  "operationId": "getKeyBuilder",
@@ -68341,7 +69391,7 @@ const operationManifest = [
68341
69391
  "binaryResponse": false,
68342
69392
  "bodyRequired": false,
68343
69393
  "command": "get-key-suggestion",
68344
- "description": "The permissions a key used over the last 30 days, as a custom grant that would have served every successful call (also as builder areas), the narrowest mail scope its calls support (depth is deprecated: raw when it downloaded raw MIME, otherwise content) (the mailboxes of the messages it named and sent, with notes on what the request log cannot account for), and whether it is unused. Calls to routes only the super admin key can use (groups and mailbox access) derive no permission, since a custom key is refused there; super_admin_only_calls counts them. usage gives the same window anonymized: successful and failed requests per UTC day, the routes called by their declared template (never the called path) with the permission each needed, and how many mailboxes the key read and sent from (counts only, and only for a person with access to the whole organization's mail). Nothing is applied: narrowing is a change to the key (PATCH /v1/keys/:id). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
69394
+ "description": "The permissions a key used over the last 30 days, as a custom grant that would have served every successful call (also as builder areas), the narrowest mail scope its calls support (depth is deprecated: raw when it downloaded raw MIME, otherwise content) (the mailboxes of the messages it named and sent, with notes on what the request log cannot account for), and whether it is unused. Calls to routes only the super admin key can use (groups and mailbox access) derive no permission, since a custom key is refused there; super_admin_only_calls counts them. usage gives the same window anonymized: successful and failed requests per UTC day, the routes called by their declared template (never the called path) with the permission each needed, and how many mailboxes the key read and sent from (counts only, and only for a person with access to the whole organization's mail). Nothing is applied: narrowing is a change to the key (PATCH /v1/keys/:id). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
68345
69395
  "hasJsonBody": false,
68346
69396
  "method": "GET",
68347
69397
  "operationId": "getKeySuggestion",
@@ -68585,7 +69635,7 @@ const operationManifest = [
68585
69635
  "binaryResponse": false,
68586
69636
  "bodyRequired": false,
68587
69637
  "command": "list-credential-events",
68588
- "description": "Credential events (created, rotated, edited, revoked, used with a step-up, policy changes), newest first, with a cursor. format=csv returns the same page as CSV. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credential_events:read. API keys, agent connection keys and agents are refused.",
69638
+ "description": "Credential events (created, rotated, edited, revoked, used with a step-up, policy changes), newest first, with a cursor. format=csv returns the same page as CSV. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credential_events:read. API keys, agent connection keys and agents are refused.",
68589
69639
  "hasJsonBody": false,
68590
69640
  "method": "GET",
68591
69641
  "operationId": "listCredentialEvents",
@@ -68714,7 +69764,7 @@ const operationManifest = [
68714
69764
  "binaryResponse": false,
68715
69765
  "bodyRequired": false,
68716
69766
  "command": "list-credentials",
68717
- "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
69767
+ "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
68718
69768
  "hasJsonBody": false,
68719
69769
  "method": "GET",
68720
69770
  "operationId": "listCredentials",
@@ -68950,7 +70000,7 @@ const operationManifest = [
68950
70000
  "binaryResponse": false,
68951
70001
  "bodyRequired": false,
68952
70002
  "command": "list-keys",
68953
- "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry and suspension. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
70003
+ "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry and suspension. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
68954
70004
  "hasJsonBody": false,
68955
70005
  "method": "GET",
68956
70006
  "operationId": "listKeys",
@@ -69039,7 +70089,7 @@ const operationManifest = [
69039
70089
  "binaryResponse": false,
69040
70090
  "bodyRequired": true,
69041
70091
  "command": "revoke-all-credentials",
69042
- "description": "The emergency response to a leak: revokes every API key, agent connection key and connected app created since a time, rotates the organization Function key and every running Function's own key, and optionally suspends Functions. Owners only, from a dashboard session, and the body must confirm with \"revoke all\". Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credentials:revoke_all. API keys, agent connection keys and agents are refused.",
70092
+ "description": "The emergency response to a leak: revokes every API key, agent connection key and connected app created since a time, rotates the organization Function key and every running Function's own key, and optionally suspends Functions. Owners only, from a dashboard session, and the body must confirm with \"revoke all\". Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credentials:revoke_all. API keys, agent connection keys and agents are refused.",
69043
70093
  "hasJsonBody": true,
69044
70094
  "method": "POST",
69045
70095
  "operationId": "revokeAllCredentials",
@@ -69124,7 +70174,7 @@ const operationManifest = [
69124
70174
  "binaryResponse": false,
69125
70175
  "bodyRequired": true,
69126
70176
  "command": "revoke-credential",
69127
- "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up keeps working and is listed in standing_objects for review. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
70177
+ "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up keeps working and is listed in standing_objects for review. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
69128
70178
  "hasJsonBody": true,
69129
70179
  "method": "POST",
69130
70180
  "operationId": "revokeCredential",
@@ -69246,7 +70296,7 @@ const operationManifest = [
69246
70296
  "binaryResponse": false,
69247
70297
  "bodyRequired": true,
69248
70298
  "command": "rotate-key",
69249
- "description": "Mint a successor with the same grant and return its secret once. The old key keeps working for overlap_seconds, then stops. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner created is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The Primitive CLI's token is refused, even with a step-up. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
70299
+ "description": "Mint a successor with the same grant and return its secret once. The old key keeps working for overlap_seconds, then stops. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner created is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
69250
70300
  "hasJsonBody": true,
69251
70301
  "method": "POST",
69252
70302
  "operationId": "rotateKey",
@@ -69260,7 +70310,7 @@ const operationManifest = [
69260
70310
  }],
69261
70311
  "queryParams": [],
69262
70312
  "headerParams": [{
69263
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
70313
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
69264
70314
  "enum": null,
69265
70315
  "name": "Primitive-Step-Up",
69266
70316
  "required": false,
@@ -69318,7 +70368,7 @@ const operationManifest = [
69318
70368
  "binaryResponse": false,
69319
70369
  "bodyRequired": true,
69320
70370
  "command": "update-credential-policy",
69321
- "description": "Change one or more policy rules. Admins may only tighten the policy; loosening any rule needs an owner. Owners and admins are emailed about every change. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session. The person's role must hold credential_policy:manage. API keys, agent connection keys and agents are refused.",
70371
+ "description": "Change one or more policy rules. Admins may only tighten the policy; loosening any rule needs an owner. Owners and admins are emailed about every change. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session. The person's role must hold credential_policy:manage. API keys, agent connection keys and agents are refused.",
69322
70372
  "hasJsonBody": true,
69323
70373
  "method": "PATCH",
69324
70374
  "operationId": "updateCredentialPolicy",
@@ -69406,7 +70456,7 @@ const operationManifest = [
69406
70456
  "binaryResponse": false,
69407
70457
  "bodyRequired": true,
69408
70458
  "command": "update-key",
69409
- "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The Primitive CLI's token is refused, even with a step-up. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.\n\nPerson only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` 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 keys: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.",
70459
+ "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules); the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Only Primitive turns these on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other 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, or the client, could not do it anyway). The person's role must hold keys: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.",
69410
70460
  "hasJsonBody": true,
69411
70461
  "method": "PATCH",
69412
70462
  "operationId": "updateKey",
@@ -69420,7 +70470,7 @@ const operationManifest = [
69420
70470
  }],
69421
70471
  "queryParams": [],
69422
70472
  "headerParams": [{
69423
- "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive CLI and app are refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`).",
70473
+ "description": "A single-use step-up token of purpose `keys.create` (Create, rotate or change an API key), 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. The Primitive app is refused for this purpose even with a token (403 `grant_forbidden`, `details.reason` `dashboard_only`). The person's own Primitive CLI login needs no token for this purpose while the organization's keys API is on (a token it sends is still checked); while it is off, a token it sends is refused the same way.",
69424
70474
  "enum": null,
69425
70475
  "name": "Primitive-Step-Up",
69426
70476
  "required": false,
@@ -71684,6 +72734,35 @@ const operationManifest = [
71684
72734
  "active"
71685
72735
  ]
71686
72736
  },
72737
+ "kind": {
72738
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
72739
+ "type": "string",
72740
+ "enum": ["grant", "do_not_share"]
72741
+ },
72742
+ "evaluation_scope": {
72743
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
72744
+ "type": "string",
72745
+ "enum": [
72746
+ "author",
72747
+ "own_mailboxes",
72748
+ "selected"
72749
+ ]
72750
+ },
72751
+ "scope_selection": {
72752
+ "type": ["object", "null"],
72753
+ "properties": {
72754
+ "addresses": {
72755
+ "type": "array",
72756
+ "items": { "type": "string" }
72757
+ },
72758
+ "domains": {
72759
+ "type": "array",
72760
+ "items": { "type": "string" }
72761
+ }
72762
+ },
72763
+ "required": ["addresses", "domains"],
72764
+ "description": "What a selected rule names; null for any other scope."
72765
+ },
71687
72766
  "current_version": {
71688
72767
  "type": ["integer", "null"],
71689
72768
  "exclusiveMinimum": 0
@@ -72090,7 +73169,7 @@ const operationManifest = [
72090
73169
  "binaryResponse": false,
72091
73170
  "bodyRequired": true,
72092
73171
  "command": "create-email-rule",
72093
- "description": "Saves a rule as version 1, from a compile response or a tree you wrote. The tree is checked against the column contract. New rules start in observe, where what the rule matches is recorded and nothing is granted; draft is not evaluated. The caller is the rule's author, and the rule never reaches mail the author cannot read. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
73172
+ "description": "Saves a rule as version 1, from a compile response or a tree you wrote. The tree is checked against the column contract. New rules start in observe, where what the rule matches is recorded and nothing is granted or blocked; draft is not evaluated. The caller is the rule's author, and the rule never reaches mail the author cannot read. evaluation_scope chooses whose mail it runs over: own_mailboxes (only your own mail) when omitted, so any other scope (author for all you can read, or selected) is named; scope_selection may name only what you can read. The answer carries the scope as stored. kind do_not_share makes a rule that blocks sharing: it runs before every other rule, and while it is active no rule shares an email it matches or cannot decide. A key cannot list one (details.reason rule_blocks_sharing on the key). Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
72094
73173
  "hasJsonBody": true,
72095
73174
  "method": "POST",
72096
73175
  "operationId": "createEmailRule",
@@ -72114,11 +73193,52 @@ const operationManifest = [
72114
73193
  "maxLength": 200,
72115
73194
  "description": "A short name for the rule."
72116
73195
  },
73196
+ "kind": {
73197
+ "description": "grant (the default): what the rule matches can be shared. do_not_share: the rule runs before every other rule, and while it is active no rule shares an email it matches; an email it cannot decide counts as matched. Set when the rule is created; it never changes.",
73198
+ "type": "string",
73199
+ "enum": ["grant", "do_not_share"]
73200
+ },
72117
73201
  "state": {
72118
- "description": "observe (the default) records what the rule would match and grants nothing; draft is not evaluated.",
73202
+ "description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated.",
72119
73203
  "type": "string",
72120
73204
  "enum": ["draft", "observe"]
72121
73205
  },
73206
+ "evaluation_scope": {
73207
+ "type": "string",
73208
+ "enum": [
73209
+ "author",
73210
+ "own_mailboxes",
73211
+ "selected"
73212
+ ],
73213
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
73214
+ },
73215
+ "scope_selection": {
73216
+ "type": "object",
73217
+ "properties": {
73218
+ "addresses": {
73219
+ "maxItems": 100,
73220
+ "type": "array",
73221
+ "items": {
73222
+ "type": "string",
73223
+ "minLength": 3,
73224
+ "maxLength": 320,
73225
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
73226
+ }
73227
+ },
73228
+ "domains": {
73229
+ "maxItems": 50,
73230
+ "type": "array",
73231
+ "items": {
73232
+ "type": "string",
73233
+ "minLength": 1,
73234
+ "maxLength": 253,
73235
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
73236
+ }
73237
+ }
73238
+ },
73239
+ "additionalProperties": false,
73240
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
73241
+ },
72122
73242
  "english": {
72123
73243
  "type": "string",
72124
73244
  "minLength": 1,
@@ -72214,6 +73334,35 @@ const operationManifest = [
72214
73334
  "active"
72215
73335
  ]
72216
73336
  },
73337
+ "kind": {
73338
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
73339
+ "type": "string",
73340
+ "enum": ["grant", "do_not_share"]
73341
+ },
73342
+ "evaluation_scope": {
73343
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
73344
+ "type": "string",
73345
+ "enum": [
73346
+ "author",
73347
+ "own_mailboxes",
73348
+ "selected"
73349
+ ]
73350
+ },
73351
+ "scope_selection": {
73352
+ "type": ["object", "null"],
73353
+ "properties": {
73354
+ "addresses": {
73355
+ "type": "array",
73356
+ "items": { "type": "string" }
73357
+ },
73358
+ "domains": {
73359
+ "type": "array",
73360
+ "items": { "type": "string" }
73361
+ }
73362
+ },
73363
+ "required": ["addresses", "domains"],
73364
+ "description": "What a selected rule names; null for any other scope."
73365
+ },
72217
73366
  "current_version": {
72218
73367
  "type": ["integer", "null"],
72219
73368
  "exclusiveMinimum": 0
@@ -72882,7 +74031,7 @@ const operationManifest = [
72882
74031
  "binaryResponse": false,
72883
74032
  "bodyRequired": false,
72884
74033
  "command": "get-email-rule",
72885
- "description": "A rule and its current version. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
74034
+ "description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule) and its current version. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
72886
74035
  "hasJsonBody": false,
72887
74036
  "method": "GET",
72888
74037
  "operationId": "getEmailRule",
@@ -72920,6 +74069,35 @@ const operationManifest = [
72920
74069
  "active"
72921
74070
  ]
72922
74071
  },
74072
+ "kind": {
74073
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
74074
+ "type": "string",
74075
+ "enum": ["grant", "do_not_share"]
74076
+ },
74077
+ "evaluation_scope": {
74078
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
74079
+ "type": "string",
74080
+ "enum": [
74081
+ "author",
74082
+ "own_mailboxes",
74083
+ "selected"
74084
+ ]
74085
+ },
74086
+ "scope_selection": {
74087
+ "type": ["object", "null"],
74088
+ "properties": {
74089
+ "addresses": {
74090
+ "type": "array",
74091
+ "items": { "type": "string" }
74092
+ },
74093
+ "domains": {
74094
+ "type": "array",
74095
+ "items": { "type": "string" }
74096
+ }
74097
+ },
74098
+ "required": ["addresses", "domains"],
74099
+ "description": "What a selected rule names; null for any other scope."
74100
+ },
72923
74101
  "current_version": {
72924
74102
  "type": ["integer", "null"],
72925
74103
  "exclusiveMinimum": 0
@@ -73231,6 +74409,105 @@ const operationManifest = [
73231
74409
  "tag": "Email Rules",
73232
74410
  "tagCommand": "email-rules"
73233
74411
  },
74412
+ {
74413
+ "binaryResponse": false,
74414
+ "bodyRequired": false,
74415
+ "command": "get-email-rule-scope",
74416
+ "description": "Whose mail the rule runs over, and what that comes to now: a rule never reaches further than what its author can read when it runs, so what a selected rule names and its author can no longer read stops matching. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
74417
+ "hasJsonBody": false,
74418
+ "method": "GET",
74419
+ "operationId": "getEmailRuleScope",
74420
+ "path": "/email-rules/{id}/scope",
74421
+ "pathParams": [{
74422
+ "description": "The rule id.",
74423
+ "enum": null,
74424
+ "name": "id",
74425
+ "required": true,
74426
+ "type": "string"
74427
+ }],
74428
+ "queryParams": [],
74429
+ "headerParams": [],
74430
+ "permissions": ["mail:read"],
74431
+ "requestSchema": null,
74432
+ "responseSchema": {
74433
+ "type": "object",
74434
+ "properties": {
74435
+ "evaluation_scope": {
74436
+ "type": "string",
74437
+ "enum": [
74438
+ "author",
74439
+ "own_mailboxes",
74440
+ "selected"
74441
+ ],
74442
+ "description": "author: all mail its author can read. own_mailboxes: mail to their own addresses. selected: what scope_selection names. Never more than the author can read."
74443
+ },
74444
+ "scope_selection": {
74445
+ "type": ["object", "null"],
74446
+ "properties": {
74447
+ "addresses": {
74448
+ "type": "array",
74449
+ "items": { "type": "string" }
74450
+ },
74451
+ "domains": {
74452
+ "type": "array",
74453
+ "items": { "type": "string" }
74454
+ }
74455
+ },
74456
+ "required": ["addresses", "domains"]
74457
+ },
74458
+ "locked": {
74459
+ "type": "boolean",
74460
+ "description": "Shared, so its scope cannot change."
74461
+ },
74462
+ "author_reach": {
74463
+ "type": ["string", "null"],
74464
+ "enum": [
74465
+ "organization",
74466
+ "limited",
74467
+ "own",
74468
+ null
74469
+ ],
74470
+ "description": "How much its author can read now; null when it cannot run as them."
74471
+ },
74472
+ "no_longer_readable": {
74473
+ "type": "object",
74474
+ "properties": {
74475
+ "addresses": {
74476
+ "type": "array",
74477
+ "items": { "type": "string" }
74478
+ },
74479
+ "domains": {
74480
+ "type": "array",
74481
+ "items": { "type": "string" }
74482
+ }
74483
+ },
74484
+ "required": ["addresses", "domains"],
74485
+ "description": "What scope_selection names that its author no longer reads in full."
74486
+ },
74487
+ "still_read": {
74488
+ "description": "Inboxes its author still reads at the domains in no_longer_readable; the rule still covers them.",
74489
+ "type": "array",
74490
+ "items": { "type": "string" }
74491
+ },
74492
+ "stopped_reason": {
74493
+ "description": "Why it cannot run now (a reason as on POST /email-rules/test), or null.",
74494
+ "type": ["string", "null"]
74495
+ }
74496
+ },
74497
+ "required": [
74498
+ "evaluation_scope",
74499
+ "scope_selection",
74500
+ "locked",
74501
+ "author_reach",
74502
+ "no_longer_readable",
74503
+ "stopped_reason"
74504
+ ]
74505
+ },
74506
+ "sdkName": "getEmailRuleScope",
74507
+ "summary": "Read what a rule covers",
74508
+ "tag": "Email Rules",
74509
+ "tagCommand": "email-rules"
74510
+ },
73234
74511
  {
73235
74512
  "binaryResponse": false,
73236
74513
  "bodyRequired": false,
@@ -73742,6 +75019,258 @@ const operationManifest = [
73742
75019
  "tag": "Email Rules",
73743
75020
  "tagCommand": "email-rules"
73744
75021
  },
75022
+ {
75023
+ "binaryResponse": false,
75024
+ "bodyRequired": false,
75025
+ "command": "list-email-rule-results",
75026
+ "description": "Every rule the caller can see and what it decided about this email: those that matched, those that did not, and those that have not run on it, with what is known about them. A result names the version of the rule that decided and carries that version's conditions and questions. A matched rule with kind do_not_share keeps rules from sharing the email while it is active; anything shared before then stays shared. Only a caller who can read the email gets an answer, and a rule the caller cannot see is left out. At most 50 rules are listed, those with a result first; hidden counts the rest. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
75027
+ "hasJsonBody": false,
75028
+ "method": "GET",
75029
+ "operationId": "listEmailRuleResults",
75030
+ "path": "/emails/{id}/rule-results",
75031
+ "pathParams": [{
75032
+ "description": "The email id.",
75033
+ "enum": null,
75034
+ "name": "id",
75035
+ "required": true,
75036
+ "type": "string"
75037
+ }],
75038
+ "queryParams": [],
75039
+ "headerParams": [],
75040
+ "permissions": ["mail:read"],
75041
+ "requestSchema": null,
75042
+ "responseSchema": {
75043
+ "type": "object",
75044
+ "properties": {
75045
+ "rules": {
75046
+ "type": "array",
75047
+ "items": {
75048
+ "type": "object",
75049
+ "properties": {
75050
+ "id": {
75051
+ "type": "string",
75052
+ "format": "uuid"
75053
+ },
75054
+ "name": { "type": "string" },
75055
+ "state": {
75056
+ "type": "string",
75057
+ "enum": [
75058
+ "draft",
75059
+ "observe",
75060
+ "active"
75061
+ ]
75062
+ },
75063
+ "kind": {
75064
+ "description": "do_not_share: the rule blocks sharing of what it matches. While it is active, no rule shares an email it has matched from then on; anything shared before, including while it was in observe, stays shared. Absent on a rule that shares what it matches.",
75065
+ "type": "string",
75066
+ "enum": ["grant", "do_not_share"]
75067
+ },
75068
+ "decision": {
75069
+ "type": ["object", "null"],
75070
+ "properties": {
75071
+ "rule_version": {
75072
+ "type": "integer",
75073
+ "exclusiveMinimum": 0,
75074
+ "description": "The version of the rule that decided."
75075
+ },
75076
+ "matched": { "type": "boolean" },
75077
+ "parts": {
75078
+ "type": "array",
75079
+ "items": {
75080
+ "type": "object",
75081
+ "properties": {
75082
+ "index": {
75083
+ "type": "integer",
75084
+ "minimum": 0
75085
+ },
75086
+ "matched": { "type": "boolean" },
75087
+ "columns": {
75088
+ "type": "array",
75089
+ "items": { "type": "string" }
75090
+ }
75091
+ },
75092
+ "required": [
75093
+ "index",
75094
+ "matched",
75095
+ "columns"
75096
+ ]
75097
+ },
75098
+ "description": "How each top-level part of the rule decided. Empty when a fact the rule reads was missing, so it was not evaluated."
75099
+ },
75100
+ "question_answers": {
75101
+ "type": "array",
75102
+ "items": {
75103
+ "type": "object",
75104
+ "properties": {
75105
+ "question": { "type": "string" },
75106
+ "answer": { "type": ["boolean", "null"] },
75107
+ "probability": { "type": ["number", "null"] },
75108
+ "error_class": { "type": "string" }
75109
+ },
75110
+ "required": [
75111
+ "question",
75112
+ "answer",
75113
+ "probability"
75114
+ ]
75115
+ },
75116
+ "description": "The answers to the rule's own questions, asked only when its conditions matched."
75117
+ },
75118
+ "tree": {
75119
+ "type": ["object", "null"],
75120
+ "properties": {
75121
+ "version": {
75122
+ "type": "number",
75123
+ "const": 1
75124
+ },
75125
+ "vocabulary": {
75126
+ "type": "string",
75127
+ "description": "The column contract version the rule is written against, for example v1."
75128
+ },
75129
+ "when": {
75130
+ "type": "object",
75131
+ "propertyNames": { "type": "string" },
75132
+ "additionalProperties": {},
75133
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
75134
+ },
75135
+ "questions": {
75136
+ "type": "array",
75137
+ "items": { "type": "string" },
75138
+ "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
75139
+ }
75140
+ },
75141
+ "required": [
75142
+ "version",
75143
+ "vocabulary",
75144
+ "when",
75145
+ "questions"
75146
+ ],
75147
+ "description": "The conditions and questions of the version that decided, so the result reads in that version's words."
75148
+ },
75149
+ "created_at": {
75150
+ "type": "string",
75151
+ "format": "date-time"
75152
+ }
75153
+ },
75154
+ "required": [
75155
+ "rule_version",
75156
+ "matched",
75157
+ "parts",
75158
+ "question_answers",
75159
+ "tree",
75160
+ "created_at"
75161
+ ],
75162
+ "description": "What the rule most recently decided about this email; null when it has not run on it."
75163
+ },
75164
+ "not_run_reason": {
75165
+ "type": ["string", "null"],
75166
+ "enum": [
75167
+ "draft",
75168
+ "created_after_email",
75169
+ null
75170
+ ],
75171
+ "description": "What is known about a rule with no result on this email. draft: the rule is a draft now. created_after_email: the rule was created after the email arrived. Null when it has a result, or when nothing is known."
75172
+ },
75173
+ "author_left": {
75174
+ "description": "On a do-not-share rule whose author is no longer a member of the organization: it blocks nothing now, whatever it decided. Absent otherwise.",
75175
+ "type": "boolean",
75176
+ "const": true
75177
+ }
75178
+ },
75179
+ "required": [
75180
+ "id",
75181
+ "name",
75182
+ "state",
75183
+ "decision",
75184
+ "not_run_reason"
75185
+ ]
75186
+ },
75187
+ "description": "Every rule the caller can see: those that matched this email, then those that did not, then those that have not run on it; newest rule first within each."
75188
+ },
75189
+ "hidden": {
75190
+ "type": "integer",
75191
+ "minimum": 0,
75192
+ "description": "How many more rules the caller can see that are not listed."
75193
+ }
75194
+ },
75195
+ "required": ["rules", "hidden"]
75196
+ },
75197
+ "sdkName": "listEmailRuleResults",
75198
+ "summary": "List every rule's result on an email",
75199
+ "tag": "Email Rules",
75200
+ "tagCommand": "email-rules"
75201
+ },
75202
+ {
75203
+ "binaryResponse": false,
75204
+ "bodyRequired": false,
75205
+ "command": "list-email-rule-scope-options",
75206
+ "description": "How much the caller can read, and the inboxes and domains the caller can read, which is what they may name when a rule runs over selected inboxes and domains. For a caller who reads the whole organization, addresses lists the inboxes known as someone's (member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access), not every address that receives mail: may_name_unlisted says when an address that is not listed, such as an inbox nobody has claimed, may be named too. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
75207
+ "hasJsonBody": false,
75208
+ "method": "GET",
75209
+ "operationId": "listEmailRuleScopeOptions",
75210
+ "path": "/email-rule-scope-options",
75211
+ "pathParams": [],
75212
+ "queryParams": [{
75213
+ "description": "Only inboxes and domains containing this text.",
75214
+ "enum": null,
75215
+ "name": "q",
75216
+ "required": false,
75217
+ "type": "string"
75218
+ }],
75219
+ "headerParams": [],
75220
+ "permissions": ["mail:read"],
75221
+ "requestSchema": null,
75222
+ "responseSchema": {
75223
+ "type": "object",
75224
+ "properties": {
75225
+ "reach": {
75226
+ "type": "string",
75227
+ "enum": [
75228
+ "organization",
75229
+ "limited",
75230
+ "own"
75231
+ ],
75232
+ "description": "organization: all of the organization's mail. own: only your own. limited: some inboxes or domains."
75233
+ },
75234
+ "own_mail": {
75235
+ "type": "boolean",
75236
+ "description": "Whether a rule over only your own mail would run for you."
75237
+ },
75238
+ "own_mail_to_block": {
75239
+ "description": "Whether a do_not_share rule over only your own mail would run for you: it covers every own mailbox you read, including ones you could not share.",
75240
+ "type": "boolean"
75241
+ },
75242
+ "addresses": {
75243
+ "type": "array",
75244
+ "items": { "type": "string" },
75245
+ "description": "Inboxes you can read, matching q. For someone who reads the whole organization, the inboxes known as someone's: member addresses, members' verified work emails at the organization's domains, agents' addresses and inboxes with shared access. Otherwise the inboxes your access lists (search with q for those past the first page). See may_name_unlisted for what else you may name."
75246
+ },
75247
+ "domains": {
75248
+ "type": "array",
75249
+ "items": { "type": "string" },
75250
+ "description": "Domains you can read, matching q."
75251
+ },
75252
+ "more": {
75253
+ "type": "boolean",
75254
+ "description": "More match than are listed."
75255
+ },
75256
+ "may_name_unlisted": {
75257
+ "description": "Whether you may also name an inbox this list never shows, however it is searched (more only says whether a search would show further ones): true when you read the whole organization (any address, such as an inbox at its domains nobody has claimed yet) or read a domain (any address there your access does not leave out). Name it in scope_selection.addresses as you would a listed one. Absent from a server that predates it.",
75258
+ "type": "boolean"
75259
+ }
75260
+ },
75261
+ "required": [
75262
+ "reach",
75263
+ "own_mail",
75264
+ "addresses",
75265
+ "domains",
75266
+ "more"
75267
+ ]
75268
+ },
75269
+ "sdkName": "listEmailRuleScopeOptions",
75270
+ "summary": "List what a rule can cover",
75271
+ "tag": "Email Rules",
75272
+ "tagCommand": "email-rules"
75273
+ },
73745
75274
  {
73746
75275
  "binaryResponse": false,
73747
75276
  "bodyRequired": false,
@@ -74266,11 +75795,130 @@ const operationManifest = [
74266
75795
  "tag": "Email Rules",
74267
75796
  "tagCommand": "email-rules"
74268
75797
  },
75798
+ {
75799
+ "binaryResponse": false,
75800
+ "bodyRequired": false,
75801
+ "command": "list-email-rule-versions",
75802
+ "description": "The named versions of a rule, as they were written: what each one's conditions and questions were, so a decision can be read against the version that made it. Numbers that are not versions of the rule are left out. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
75803
+ "hasJsonBody": false,
75804
+ "method": "GET",
75805
+ "operationId": "listEmailRuleVersions",
75806
+ "path": "/email-rules/{id}/versions",
75807
+ "pathParams": [{
75808
+ "description": "The rule id.",
75809
+ "enum": null,
75810
+ "name": "id",
75811
+ "required": true,
75812
+ "type": "string"
75813
+ }],
75814
+ "queryParams": [{
75815
+ "description": "The version numbers, comma-separated, at most 100.",
75816
+ "enum": null,
75817
+ "name": "versions",
75818
+ "required": true,
75819
+ "type": "string"
75820
+ }],
75821
+ "headerParams": [],
75822
+ "permissions": ["mail:read"],
75823
+ "requestSchema": null,
75824
+ "responseSchema": {
75825
+ "type": "array",
75826
+ "items": {
75827
+ "type": "object",
75828
+ "properties": {
75829
+ "version": {
75830
+ "type": "integer",
75831
+ "exclusiveMinimum": 0
75832
+ },
75833
+ "english": { "type": "string" },
75834
+ "tree": {
75835
+ "type": "object",
75836
+ "properties": {
75837
+ "version": {
75838
+ "type": "number",
75839
+ "const": 1
75840
+ },
75841
+ "vocabulary": {
75842
+ "type": "string",
75843
+ "description": "The column contract version the rule is written against, for example v1."
75844
+ },
75845
+ "when": {
75846
+ "type": "object",
75847
+ "propertyNames": { "type": "string" },
75848
+ "additionalProperties": {},
75849
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
75850
+ },
75851
+ "questions": {
75852
+ "type": "array",
75853
+ "items": { "type": "string" },
75854
+ "description": "Yes/no questions of the rule's own, asked about an email only after its conditions match. An email matches only if every answer is yes."
75855
+ }
75856
+ },
75857
+ "required": [
75858
+ "version",
75859
+ "vocabulary",
75860
+ "when",
75861
+ "questions"
75862
+ ]
75863
+ },
75864
+ "explanation": { "type": "string" },
75865
+ "assumptions": {
75866
+ "type": "array",
75867
+ "items": { "type": "string" }
75868
+ },
75869
+ "warnings": {
75870
+ "type": "array",
75871
+ "items": { "type": "string" }
75872
+ },
75873
+ "compiled_by": {
75874
+ "type": "string",
75875
+ "description": "Who produced the tree, as the caller stated it when saving (not verified), for example rule-compiler-v1 or member."
75876
+ },
75877
+ "vocabulary": { "type": "string" },
75878
+ "created_at": {
75879
+ "type": "string",
75880
+ "format": "date-time"
75881
+ },
75882
+ "sql": {
75883
+ "description": "Present only when include_sql was true.",
75884
+ "type": "object",
75885
+ "properties": {
75886
+ "expression": {
75887
+ "type": "string",
75888
+ "description": "A parameterized boolean expression over one row per email."
75889
+ },
75890
+ "params": {
75891
+ "type": "array",
75892
+ "items": {},
75893
+ "description": "The values bound to $1, $2 and so on."
75894
+ }
75895
+ },
75896
+ "required": ["expression", "params"]
75897
+ }
75898
+ },
75899
+ "required": [
75900
+ "version",
75901
+ "english",
75902
+ "tree",
75903
+ "explanation",
75904
+ "assumptions",
75905
+ "warnings",
75906
+ "compiled_by",
75907
+ "vocabulary",
75908
+ "created_at"
75909
+ ]
75910
+ }
75911
+ },
75912
+ "sdkName": "listEmailRuleVersions",
75913
+ "summary": "Read versions of a rule",
75914
+ "tag": "Email Rules",
75915
+ "tagCommand": "email-rules"
75916
+ },
74269
75917
  {
74270
75918
  "binaryResponse": false,
74271
75919
  "bodyRequired": false,
74272
75920
  "command": "list-email-rules",
74273
- "description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. With matched_email_id, only the rules that matched that email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
75921
+ "description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule). With matched_email_id, only the rules that matched that email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
74274
75922
  "hasJsonBody": false,
74275
75923
  "method": "GET",
74276
75924
  "operationId": "listEmailRules",
@@ -74323,6 +75971,35 @@ const operationManifest = [
74323
75971
  "active"
74324
75972
  ]
74325
75973
  },
75974
+ "kind": {
75975
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
75976
+ "type": "string",
75977
+ "enum": ["grant", "do_not_share"]
75978
+ },
75979
+ "evaluation_scope": {
75980
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
75981
+ "type": "string",
75982
+ "enum": [
75983
+ "author",
75984
+ "own_mailboxes",
75985
+ "selected"
75986
+ ]
75987
+ },
75988
+ "scope_selection": {
75989
+ "type": ["object", "null"],
75990
+ "properties": {
75991
+ "addresses": {
75992
+ "type": "array",
75993
+ "items": { "type": "string" }
75994
+ },
75995
+ "domains": {
75996
+ "type": "array",
75997
+ "items": { "type": "string" }
75998
+ }
75999
+ },
76000
+ "required": ["addresses", "domains"],
76001
+ "description": "What a selected rule names; null for any other scope."
76002
+ },
74326
76003
  "current_version": {
74327
76004
  "type": ["integer", "null"],
74328
76005
  "exclusiveMinimum": 0
@@ -74572,7 +76249,7 @@ const operationManifest = [
74572
76249
  "binaryResponse": false,
74573
76250
  "bodyRequired": true,
74574
76251
  "command": "test-email-rule",
74575
- "description": "Runs a rule over received mail and reports what it would decide for each email: matched, near_miss, no_match, unclassified or not_found. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version, run with its author's access). Either way the rule reads only mail its author can read. Without email_ids, the newest emails it can read are tested, at most 200 and none older than 30 days. Emails not classified yet that arrived more than 2 hours ago are classified first and their classification is saved, unless classify_missing is false; newer ones are left to normal classification and reported unclassified with reason pending. The rule itself records nothing and grants nothing. Classification and the rule's own questions stop at about 90 seconds; run the test again to continue where it stopped. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
76252
+ "description": "Runs a rule over received mail and reports what it would decide for each email: matched, near_miss, no_match, unclassified or not_found. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version, run with its author's access). Either way the rule reads only mail its author can read, and only what its scope covers; an unsaved tree covers only your own mail unless evaluation_scope names more. Without email_ids, the newest emails it can read are tested, at most 200 and none older than 30 days. Emails not classified yet that arrived more than 2 hours ago are classified first and their classification is saved, unless classify_missing is false; newer ones are left to normal classification and reported unclassified with reason pending. The rule itself records nothing and grants nothing. Classification and the rule's own questions stop at about 90 seconds; run the test again to continue where it stopped. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
74576
76253
  "hasJsonBody": true,
74577
76254
  "method": "POST",
74578
76255
  "operationId": "testEmailRule",
@@ -74624,14 +76301,50 @@ const operationManifest = [
74624
76301
  },
74625
76302
  "evaluation_scope": {
74626
76303
  "type": "string",
74627
- "enum": ["author", "own_mailboxes"],
74628
- "description": "Whose mail an unsaved tree runs over. author, the default: all the mail you can read. own_mailboxes: only mail to your own addresses. Only with tree: a saved rule uses its own."
76304
+ "enum": [
76305
+ "author",
76306
+ "own_mailboxes",
76307
+ "selected"
76308
+ ],
76309
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
76310
+ },
76311
+ "scope_selection": {
76312
+ "type": "object",
76313
+ "properties": {
76314
+ "addresses": {
76315
+ "maxItems": 100,
76316
+ "type": "array",
76317
+ "items": {
76318
+ "type": "string",
76319
+ "minLength": 3,
76320
+ "maxLength": 320,
76321
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
76322
+ }
76323
+ },
76324
+ "domains": {
76325
+ "maxItems": 50,
76326
+ "type": "array",
76327
+ "items": {
76328
+ "type": "string",
76329
+ "minLength": 1,
76330
+ "maxLength": 253,
76331
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
76332
+ }
76333
+ }
76334
+ },
76335
+ "additionalProperties": false,
76336
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
74629
76337
  },
74630
76338
  "rule_id": {
74631
76339
  "description": "A saved rule you can see. Its current version is tested, with its author's access.",
74632
76340
  "type": "string",
74633
76341
  "format": "uuid"
74634
76342
  },
76343
+ "kind": {
76344
+ "description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
76345
+ "type": "string",
76346
+ "enum": ["grant", "do_not_share"]
76347
+ },
74635
76348
  "email_ids": {
74636
76349
  "description": "Test exactly these emails. Without it, the newest emails the rule can read are tested, at most 200 and none older than 30 days.",
74637
76350
  "minItems": 1,
@@ -74868,7 +76581,7 @@ const operationManifest = [
74868
76581
  "binaryResponse": false,
74869
76582
  "bodyRequired": true,
74870
76583
  "command": "test-email-rule-on-sample",
74871
- "description": "Reports what a rule would decide for an email you describe: its sender, recipients, subject, plain text and attachment types, and optionally whether it is automated or from a mailing list and its spam score. The email is classified the way received mail is, and the rule's conditions and its own questions are decided as they would be for received mail with that content and those signals, received now. Without them it is not automated and not from a mailing list; a rule that reads a spam score you gave none for is decided on no score, and spam_score is listed in unset_columns. Nothing is stored: no email, classification or decision. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version). The answer includes what the classifier read from the email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
76584
+ "description": "Reports what a rule would decide for an email you describe: its sender, recipients, subject, plain text and attachment types, and optionally whether it is automated or from a mailing list and its spam score. The email is classified the way received mail is, and the rule's conditions and its own questions are decided as they would be for received mail with that content and those signals, received now. Without them it is not automated and not from a mailing list; a rule that reads a spam score you gave none for is decided on no score, and spam_score is listed in unset_columns. Nothing is stored: no email, classification or decision. Test an unsaved tree from a compile response, or a saved rule by rule_id (its current version); an unsaved tree is run as covering only your own mail unless evaluation_scope names more, as on POST /email-rules/test. The answer includes what the classifier read from the email. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
74872
76585
  "hasJsonBody": true,
74873
76586
  "method": "POST",
74874
76587
  "operationId": "testEmailRuleOnSample",
@@ -74920,14 +76633,50 @@ const operationManifest = [
74920
76633
  },
74921
76634
  "evaluation_scope": {
74922
76635
  "type": "string",
74923
- "enum": ["author", "own_mailboxes"],
74924
- "description": "Whose mail an unsaved tree runs over. author, the default: all the mail you can read. own_mailboxes: only mail to your own addresses. Only with tree: a saved rule uses its own."
76636
+ "enum": [
76637
+ "author",
76638
+ "own_mailboxes",
76639
+ "selected"
76640
+ ],
76641
+ "description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those you can send from). author: all mail you can read, including inboxes added later. selected: what scope_selection names."
76642
+ },
76643
+ "scope_selection": {
76644
+ "type": "object",
76645
+ "properties": {
76646
+ "addresses": {
76647
+ "maxItems": 100,
76648
+ "type": "array",
76649
+ "items": {
76650
+ "type": "string",
76651
+ "minLength": 3,
76652
+ "maxLength": 320,
76653
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
76654
+ }
76655
+ },
76656
+ "domains": {
76657
+ "maxItems": 50,
76658
+ "type": "array",
76659
+ "items": {
76660
+ "type": "string",
76661
+ "minLength": 1,
76662
+ "maxLength": 253,
76663
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
76664
+ }
76665
+ }
76666
+ },
76667
+ "additionalProperties": false,
76668
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
74925
76669
  },
74926
76670
  "rule_id": {
74927
76671
  "description": "A saved rule you can see. Its current version is tested.",
74928
76672
  "type": "string",
74929
76673
  "format": "uuid"
74930
76674
  },
76675
+ "kind": {
76676
+ "description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
76677
+ "type": "string",
76678
+ "enum": ["grant", "do_not_share"]
76679
+ },
74931
76680
  "email": {
74932
76681
  "type": "object",
74933
76682
  "properties": {
@@ -75151,7 +76900,7 @@ const operationManifest = [
75151
76900
  "binaryResponse": false,
75152
76901
  "bodyRequired": true,
75153
76902
  "command": "update-email-rule",
75154
- "description": "Renames the rule, saves a new version (english, tree and explanation together; earlier versions are kept unchanged) or changes its state: draft (disabled), observe or active. Only the rule's author may rename it, add a version or move it to observe or active; anyone else who can see it may only disable it (state draft), and while it is shared with another member only if they manage members from the dashboard. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
76903
+ "description": "Renames the rule, saves a new version (english, tree and explanation together; earlier versions are kept unchanged), changes its state (draft, disabled; observe; or active) or changes whose mail it runs over (evaluation_scope, with scope_selection when selected). Only the rule's author may rename it, add a version, change its scope or move it to observe or active; anyone else who can see it may only disable it (state draft), and while it is shared with another member only if they manage members from the dashboard. Omit evaluation_scope to keep the current scope. A scope change saves a new version with the same conditions; what was granted stays. A shared rule keeps its scope. A rule's kind never changes. Needs mail:read, a credential acting as a member with their full access (API keys, agent keys and narrowed tokens are refused), and the email rules feature on the organization.",
75155
76904
  "hasJsonBody": true,
75156
76905
  "method": "PATCH",
75157
76906
  "operationId": "updateEmailRule",
@@ -75176,7 +76925,7 @@ const operationManifest = [
75176
76925
  "description": "A short name for the rule."
75177
76926
  },
75178
76927
  "state": {
75179
- "description": "draft disables the rule; observe records decisions; active also grants what it matches. Keys read and receive what a rule granted only while it is active: leaving active pauses that, and returning to active restores reads and the delivery of queued mail not yet skipped.",
76928
+ "description": "draft disables the rule; observe records decisions; active also grants what it matches, or for a do_not_share rule keeps it from being shared. Keys read and receive what a rule granted only while it is active: leaving active pauses that, and returning to active restores reads and the delivery of queued mail not yet skipped.",
75180
76929
  "type": "string",
75181
76930
  "enum": [
75182
76931
  "draft",
@@ -75253,6 +77002,42 @@ const operationManifest = [
75253
77002
  "type": "string",
75254
77003
  "minLength": 1,
75255
77004
  "maxLength": 64
77005
+ },
77006
+ "evaluation_scope": {
77007
+ "type": "string",
77008
+ "enum": [
77009
+ "author",
77010
+ "own_mailboxes",
77011
+ "selected"
77012
+ ],
77013
+ "description": "author: all mail its author can read. own_mailboxes: mail to their own addresses. selected: what scope_selection names. Never more than the author can read."
77014
+ },
77015
+ "scope_selection": {
77016
+ "type": "object",
77017
+ "properties": {
77018
+ "addresses": {
77019
+ "maxItems": 100,
77020
+ "type": "array",
77021
+ "items": {
77022
+ "type": "string",
77023
+ "minLength": 3,
77024
+ "maxLength": 320,
77025
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
77026
+ }
77027
+ },
77028
+ "domains": {
77029
+ "maxItems": 50,
77030
+ "type": "array",
77031
+ "items": {
77032
+ "type": "string",
77033
+ "minLength": 1,
77034
+ "maxLength": 253,
77035
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
77036
+ }
77037
+ }
77038
+ },
77039
+ "additionalProperties": false,
77040
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
75256
77041
  }
75257
77042
  },
75258
77043
  "additionalProperties": false
@@ -75273,6 +77058,35 @@ const operationManifest = [
75273
77058
  "active"
75274
77059
  ]
75275
77060
  },
77061
+ "kind": {
77062
+ "description": "do_not_share: the rule blocks sharing of what it matches. Absent (or grant) on a rule that shares what it matches.",
77063
+ "type": "string",
77064
+ "enum": ["grant", "do_not_share"]
77065
+ },
77066
+ "evaluation_scope": {
77067
+ "description": "Whose mail the rule runs over, as stored; GET /email-rules/{id}/scope says what that comes to now.",
77068
+ "type": "string",
77069
+ "enum": [
77070
+ "author",
77071
+ "own_mailboxes",
77072
+ "selected"
77073
+ ]
77074
+ },
77075
+ "scope_selection": {
77076
+ "type": ["object", "null"],
77077
+ "properties": {
77078
+ "addresses": {
77079
+ "type": "array",
77080
+ "items": { "type": "string" }
77081
+ },
77082
+ "domains": {
77083
+ "type": "array",
77084
+ "items": { "type": "string" }
77085
+ }
77086
+ },
77087
+ "required": ["addresses", "domains"],
77088
+ "description": "What a selected rule names; null for any other scope."
77089
+ },
75276
77090
  "current_version": {
75277
77091
  "type": ["integer", "null"],
75278
77092
  "exclusiveMinimum": 0
@@ -90750,7 +92564,7 @@ const operationManifest = [
90750
92564
  "binaryResponse": false,
90751
92565
  "bodyRequired": true,
90752
92566
  "command": "decide-suspension",
90753
- "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key, needs the decider to cover it (revoking an uncovered key is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (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 members:manage. API keys are refused.",
92567
+ "description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key, needs the decider to cover it (revoking an uncovered key is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (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 members:manage. API keys are refused.",
90754
92568
  "hasJsonBody": true,
90755
92569
  "method": "POST",
90756
92570
  "operationId": "decideSuspension",
@@ -102204,14 +104018,14 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
102204
104018
  }
102205
104019
  }
102206
104020
  // src/oclif/cli-operations.ts
102207
- const DASHBOARD_ONLY_OPERATION_IDS = /* @__PURE__ */ new Set([
104021
+ const KEY_WRITE_OPERATION_IDS = /* @__PURE__ */ new Set([
102208
104022
  "credentials:create-key",
102209
104023
  "credentials:rotate-key",
102210
104024
  "credentials:update-key",
102211
104025
  "credentials:revoke-credential"
102212
104026
  ]);
102213
- /** The operations the CLI offers: the manifest without the dashboard-only ones. */
102214
- const cliOperationManifest = operationManifest.filter((operation) => !DASHBOARD_ONLY_OPERATION_IDS.has(`${operation.tagCommand}:${operation.command}`));
104027
+ /** The operations the CLI generates commands for: the manifest without the key writes. */
104028
+ const cliOperationManifest = operationManifest.filter((operation) => !KEY_WRITE_OPERATION_IDS.has(`${operation.tagCommand}:${operation.command}`));
102215
104029
  // src/oclif/agent-mail-check.ts
102216
104030
  /**
102217
104031
  * One check for mail that arrived since this profile's previous check. It is
@@ -108882,7 +110696,7 @@ async function checkAccount(client) {
108882
110696
  outcome: {
108883
110697
  status: "fail",
108884
110698
  message: `API rejected the key (${typeof apiError === "object" && apiError !== null ? JSON.stringify(apiError).slice(0, 300) : String(apiError).slice(0, 300)})`,
108885
- hint: "Run `primitive whoami` for the full error envelope. If the key was rotated, regenerate it in the dashboard."
110699
+ hint: "Run `primitive whoami` for the full error envelope. If the key was rotated, use its new secret; if it was revoked, create a key with `primitive keys create` or in the dashboard."
108886
110700
  },
108887
110701
  account: null
108888
110702
  };
@@ -111816,8 +113630,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
111816
113630
  name: "Primitive Team",
111817
113631
  url: "https://primitive.dev"
111818
113632
  };
111819
- const SDK_VERSION_RANGE = "^3.0.0-preview.1";
111820
- const CLI_VERSION_RANGE = "^3.0.0-preview.1";
113633
+ const SDK_VERSION_RANGE = "^3.0.0-preview.3";
113634
+ const CLI_VERSION_RANGE = "^3.0.0-preview.3";
111821
113635
  const ESBUILD_VERSION_RANGE = "^0.27.0";
111822
113636
  function renderHandler() {
111823
113637
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -112945,6 +114759,7 @@ var FunctionsRouteGetCommand = class FunctionsRouteGetCommand extends Command {
112945
114759
  static summary = "Show a function's current route binding";
112946
114760
  static examples = ["<%= config.bin %> functions route-get --id <fn-id>"];
112947
114761
  static flags = {
114762
+ json: JSON_OUTPUT_FLAG,
112948
114763
  "api-key": Flags.string({
112949
114764
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
112950
114765
  env: "PRIMITIVE_API_KEY"
@@ -113007,6 +114822,7 @@ var FunctionsRouteSetCommand = class FunctionsRouteSetCommand extends Command {
113007
114822
  "<%= config.bin %> functions route-set --id <fn-id> --domain <domain-id> --takeover"
113008
114823
  ];
113009
114824
  static flags = {
114825
+ json: JSON_OUTPUT_FLAG,
113010
114826
  "api-key": Flags.string({
113011
114827
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
113012
114828
  env: "PRIMITIVE_API_KEY"
@@ -113079,6 +114895,7 @@ var FunctionsRouteUnsetCommand = class FunctionsRouteUnsetCommand extends Comman
113079
114895
  static summary = "Unbind any route from a function";
113080
114896
  static examples = ["<%= config.bin %> functions route-unset --id <fn-id>"];
113081
114897
  static flags = {
114898
+ json: JSON_OUTPUT_FLAG,
113082
114899
  "api-key": Flags.string({
113083
114900
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
113084
114901
  env: "PRIMITIVE_API_KEY"
@@ -113129,6 +114946,7 @@ var FunctionsRoutingTopologyCommand = class FunctionsRoutingTopologyCommand exte
113129
114946
  static summary = "Show the org-wide function routing topology";
113130
114947
  static examples = ["<%= config.bin %> functions routing-topology"];
113131
114948
  static flags = {
114949
+ json: JSON_OUTPUT_FLAG,
113132
114950
  "api-key": Flags.string({
113133
114951
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
113134
114952
  env: "PRIMITIVE_API_KEY"
@@ -113255,6 +115073,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
113255
115073
  "printf '%s' \"$MY_API_KEY\" | <%= config.bin %> functions set-secret --id <fn-id> --key MY_API_KEY --stdin --redeploy"
113256
115074
  ];
113257
115075
  static flags = {
115076
+ json: JSON_OUTPUT_FLAG,
113258
115077
  "api-key": Flags.string({
113259
115078
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
113260
115079
  env: "PRIMITIVE_API_KEY"
@@ -114577,7 +116396,10 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
114577
116396
  }
114578
116397
  };
114579
116398
  // src/oclif/commands/keys.ts
114580
- const KEYS_MANAGED_IN_DASHBOARD = "API keys are created, rotated and revoked in the dashboard for now. CLI commands for this are planned.";
116399
+ const STORE_SECRET_NOTICE = "Store this key now. It is shown only once, and the CLI does not save it.";
116400
+ const HELD_REQUEST_NOTICE = "A request already held open with this key, such as a long poll or a wait, ends once because the key's permissions changed. Run it again to use the new permissions.";
116401
+ const STEP_UP_HEADER$1 = "Primitive-Step-Up";
116402
+ const LIST_PAGE_LIMIT = 50;
114581
116403
  const API_KEY_FLAG$1 = Flags.string({
114582
116404
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved login credentials).",
114583
116405
  env: "PRIMITIVE_API_KEY",
@@ -114588,68 +116410,695 @@ const API_BASE_URL_FLAG$2 = Flags.string({
114588
116410
  env: "PRIMITIVE_API_BASE_URL",
114589
116411
  hidden: true
114590
116412
  });
114591
- /** Print a refusal and set a failing exit code. */
114592
- function reportKeysError(error, context, json) {
114593
- const payload = extractErrorPayload(error);
114594
- if (json) process.stderr.write(`${JSON.stringify(payload)}\n`);
114595
- else {
114596
- writeErrorWithHints(payload);
114597
- if (payload?.code === "forbidden") process.stderr.write("Listing keys needs an owner or admin. Some of it is available only in the dashboard.\n");
116413
+ const APPROVAL_FLAGS = {
116414
+ "step-up-token": Flags.string({ description: "One-time token from the approval page, for a request you already approved in the browser. Sent in the Primitive-Step-Up header." }),
116415
+ browser: Flags.boolean({
116416
+ allowNo: true,
116417
+ default: true,
116418
+ description: "Open the approval page in your browser. Use --no-browser to only print its link."
116419
+ })
116420
+ };
116421
+ function unwrap(data) {
116422
+ return data?.data ?? data;
116423
+ }
116424
+ async function call(request) {
116425
+ try {
116426
+ const result = await request;
116427
+ if (result.error !== void 0 || !result.response?.ok) return {
116428
+ ok: false,
116429
+ payload: result.error === void 0 || result.error === null || typeof result.error === "string" ? {
116430
+ code: "http_error",
116431
+ message: typeof result.error === "string" && result.error.trim() || `HTTP ${result.response?.status ?? ""}`.trim()
116432
+ } : extractErrorPayload(result.error)
116433
+ };
116434
+ return {
116435
+ ok: true,
116436
+ data: unwrap(result.data)
116437
+ };
116438
+ } catch (error) {
116439
+ return {
116440
+ ok: false,
116441
+ payload: extractErrorPayload(error)
116442
+ };
114598
116443
  }
114599
- surfaceUnauthorizedHint({
114600
- ...context,
114601
- payload
116444
+ }
116445
+ /** The approval link of a `step_up_required` refusal, if that is what it is. */
116446
+ function stepUpUrlOf$1(payload) {
116447
+ if (extractErrorCode(payload) !== "step_up_required") return void 0;
116448
+ if (!payload || typeof payload !== "object") return void 0;
116449
+ const details = payload.details;
116450
+ if (!details || typeof details !== "object") return void 0;
116451
+ const url = details.step_up_url;
116452
+ return typeof url === "string" && url.length > 0 ? url : void 0;
116453
+ }
116454
+ /**
116455
+ * Send a request, and when the server asks the person to approve it, show
116456
+ * the approval link, open it where possible, wait for the token the page
116457
+ * shows, and send the request once more with it. Without a terminal to wait
116458
+ * in, the refusal is returned with its link, to rerun with --step-up-token.
116459
+ */
116460
+ async function withApproval(attempt, io) {
116461
+ const first = await attempt(io.stepUpToken);
116462
+ if (first.ok) return first;
116463
+ const stepUpUrl = stepUpUrlOf$1(first.payload);
116464
+ if (!stepUpUrl || io.stepUpToken || !io.interactive) return {
116465
+ ...first,
116466
+ ...stepUpUrl ? { stepUpUrl } : {}
116467
+ };
116468
+ io.write(`Approve this request in your browser: ${stepUpUrl}\n`);
116469
+ if (io.openBrowser) io.open(stepUpUrl);
116470
+ io.write("After you confirm, the page shows a one-time token. Paste it here to finish.\n");
116471
+ const token = (await io.prompt("Token: ")).trim();
116472
+ if (!token) return {
116473
+ ...first,
116474
+ stepUpUrl
116475
+ };
116476
+ return attempt(token);
116477
+ }
116478
+ async function promptLine$2(question) {
116479
+ const rl = createInterface({
116480
+ input: process.stdin,
116481
+ output: process.stderr
114602
116482
  });
114603
- process.exitCode = 1;
116483
+ try {
116484
+ return await rl.question(question);
116485
+ } finally {
116486
+ rl.close();
116487
+ }
114604
116488
  }
114605
- function unwrap(data) {
114606
- return data?.data ?? null;
116489
+ function approvalIo(flags) {
116490
+ return {
116491
+ stepUpToken: flags["step-up-token"],
116492
+ interactive: !flags.json && Boolean(process.stdin.isTTY && process.stderr.isTTY),
116493
+ openBrowser: flags.browser,
116494
+ open: openBrowser,
116495
+ prompt: promptLine$2,
116496
+ write: (text) => process.stderr.write(text)
116497
+ };
116498
+ }
116499
+ /** Comma-separated and repeated values, trimmed, without duplicates. */
116500
+ function listFlag(values) {
116501
+ const out = [];
116502
+ for (const value of values ?? []) for (const part of value.split(",")) {
116503
+ const item = part.trim();
116504
+ if (item && !out.includes(item)) out.push(item);
116505
+ }
116506
+ return out;
116507
+ }
116508
+ function stringList(value) {
116509
+ return Array.isArray(value) && value.every((v) => typeof v === "string") ? [...value] : null;
116510
+ }
116511
+ /** Which mail a key reaches, in words. */
116512
+ function describeMailScope(grant) {
116513
+ if (grant.template === "super_admin") return "every mailbox";
116514
+ if (grant.template === "connected_agent") return "its agent's address";
116515
+ const scope = grant.mail_scope;
116516
+ if (scope === "org") return "every mailbox";
116517
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return "unknown";
116518
+ const record = scope;
116519
+ const parts = [];
116520
+ const addresses = stringList(record.addresses) ?? [];
116521
+ const domains = stringList(record.domains) ?? [];
116522
+ const rules = stringList(record.rules) ?? [];
116523
+ if (addresses.length > 0) parts.push(`addresses ${addresses.join(", ")}`);
116524
+ if (domains.length > 0) parts.push(`domains ${domains.join(", ")}`);
116525
+ if (rules.length > 0) parts.push(`rules ${rules.join(", ")}`);
116526
+ return parts.length > 0 ? parts.join("; ") : "no mailbox";
116527
+ }
116528
+ /** What a key may do, in words. */
116529
+ function describePermissions(grant) {
116530
+ if (grant.template === "super_admin") return "super admin (every permission)";
116531
+ if (grant.template) return grant.template;
116532
+ const permissions = grant.permissions ?? [];
116533
+ return permissions.length > 0 ? permissions.join(", ") : "none";
114607
116534
  }
114608
116535
  function formatKeyRows(rows) {
114609
116536
  if (rows.length === 0) return "No API keys.";
114610
116537
  return rows.map((row) => {
114611
- const grant = row.grant.template ? row.grant.template : `${row.grant.permissions?.length ?? 0} permissions`;
114612
- const flags = row.flags.length > 0 ? ` [${row.flags.join(", ")}]` : "";
114613
- return `${row.id} ${row.prefix ?? ""} ${row.name ?? "(unnamed)"} ${grant} expires ${row.expires_at ?? "never"} last used ${row.last_used_at ?? "never"}${flags}`;
116538
+ const suspended = row.suspended ? " [suspended]" : "";
116539
+ 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}`;
114614
116540
  }).join("\n");
114615
116541
  }
116542
+ function formatKeyDetail(key) {
116543
+ const lines = [
116544
+ `Name: ${key.name ?? "(unnamed)"}`,
116545
+ `Id: ${key.id}`,
116546
+ `Prefix: ${key.key_prefix}`,
116547
+ `Kind: ${key.kind}`,
116548
+ `Mail: ${describeMailScope(key.grant)}`,
116549
+ `Created: ${key.created_at ?? "unknown"}`,
116550
+ `Expires: ${key.expires_at ?? "never"}`,
116551
+ `Last used: ${key.last_used_at ?? "never"}`,
116552
+ `Suspended: ${key.suspended ? "yes" : "no"}`
116553
+ ];
116554
+ if (key.agent_address) lines.push(`Agent: ${key.agent_address}`);
116555
+ if (key.grant.template) lines.push(`Permissions: ${describePermissions(key.grant)}`);
116556
+ else {
116557
+ lines.push("Permissions:");
116558
+ for (const permission of key.grant.permissions ?? []) lines.push(` ${permission}`);
116559
+ }
116560
+ return lines.join("\n");
116561
+ }
116562
+ const EVERY_MAILBOX_PRESETS = /* @__PURE__ */ new Set([
116563
+ "super_admin",
116564
+ "read_only",
116565
+ "send_only"
116566
+ ]);
116567
+ const LISTED_PRESETS = /* @__PURE__ */ new Set(["own_addresses", "own_addresses_read_only"]);
116568
+ const KEY_PRESETS = [...EVERY_MAILBOX_PRESETS, ...LISTED_PRESETS];
116569
+ /**
116570
+ * The grant a new key asks for. There is no default: the caller names a
116571
+ * preset or the permissions, and a key with chosen permissions also says
116572
+ * which mail it reaches (addresses, domains, email rules, or every mailbox).
116573
+ */
116574
+ function buildCreateGrant(choice) {
116575
+ const listed = choice.addresses.length > 0 || choice.domains.length > 0 || choice.rules.length > 0;
116576
+ const scope = {
116577
+ ...choice.addresses.length > 0 ? { addresses: choice.addresses } : {},
116578
+ ...choice.domains.length > 0 ? { domains: choice.domains } : {},
116579
+ ...choice.rules.length > 0 ? { rules: choice.rules } : {}
116580
+ };
116581
+ if (choice.permissions.length > 0) {
116582
+ if (choice.preset) return {
116583
+ ok: false,
116584
+ message: "Pass either --preset or --permission, not both."
116585
+ };
116586
+ if (listed === choice.allMailboxes) return {
116587
+ ok: false,
116588
+ message: choice.allMailboxes ? "--all-mailboxes reaches every mailbox, so it cannot be combined with --address, --domain or --rule." : "Say which mail the key reaches: --address, --domain or --rule (each repeatable), or --all-mailboxes."
116589
+ };
116590
+ return {
116591
+ ok: true,
116592
+ grant: {
116593
+ preset: "custom",
116594
+ permissions: choice.permissions,
116595
+ ...choice.allMailboxes ? { mail_scope: "org" } : scope
116596
+ }
116597
+ };
116598
+ }
116599
+ if (!choice.preset) return {
116600
+ ok: false,
116601
+ message: "Say what the key can do: pass --preset or --permission. There is no default."
116602
+ };
116603
+ if (choice.allMailboxes) return {
116604
+ ok: false,
116605
+ message: "--all-mailboxes is only for a key with --permission."
116606
+ };
116607
+ if (EVERY_MAILBOX_PRESETS.has(choice.preset)) {
116608
+ if (listed) return {
116609
+ ok: false,
116610
+ message: `${choice.preset} reaches every mailbox, so it takes no --address, --domain or --rule.`
116611
+ };
116612
+ return {
116613
+ ok: true,
116614
+ grant: choice.preset
116615
+ };
116616
+ }
116617
+ if (LISTED_PRESETS.has(choice.preset)) {
116618
+ if (!listed) return {
116619
+ ok: false,
116620
+ message: `${choice.preset} needs --address, --domain or --rule.`
116621
+ };
116622
+ return {
116623
+ ok: true,
116624
+ grant: {
116625
+ preset: choice.preset,
116626
+ ...scope
116627
+ }
116628
+ };
116629
+ }
116630
+ return {
116631
+ ok: false,
116632
+ message: `--preset must be one of ${KEY_PRESETS.join(", ")}.`
116633
+ };
116634
+ }
116635
+ /** The mail scope fields a change can send back unchanged. */
116636
+ const RESTATABLE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
116637
+ "addresses",
116638
+ "domains",
116639
+ "rules",
116640
+ "depth"
116641
+ ]);
116642
+ /**
116643
+ * The grant that restates a key's current grant with permissions added or
116644
+ * removed, or its email rules replaced, and everything else as it is. Only a
116645
+ * key with a permission list and a mail scope the CLI can restate exactly;
116646
+ * anything else is changed in the dashboard.
116647
+ */
116648
+ function buildUpdatedGrant(current, change) {
116649
+ if (current.template === "super_admin") return {
116650
+ ok: false,
116651
+ message: "This is a super admin key: it holds every permission already. To narrow it, create a key with only what you need, then revoke this one."
116652
+ };
116653
+ if (current.template) return {
116654
+ ok: false,
116655
+ message: `This key's permissions come from its template (${current.template}) and are not changed here.`
116656
+ };
116657
+ const resources = current.resources;
116658
+ if (resources !== void 0 && resources !== null && (typeof resources !== "object" || Object.keys(resources).length > 0)) return {
116659
+ ok: false,
116660
+ message: "This key is limited to selected resources, which the CLI cannot restate. Change it in the dashboard."
116661
+ };
116662
+ const held = stringList(current.permissions);
116663
+ if (!held) return {
116664
+ ok: false,
116665
+ message: "This key's permissions could not be read."
116666
+ };
116667
+ const permissions = [.../* @__PURE__ */ new Set([...held, ...change.add])].filter((p) => !change.remove.includes(p)).sort();
116668
+ if (permissions.length === 0) return {
116669
+ ok: false,
116670
+ message: "A key needs at least one permission. To stop it working, revoke it."
116671
+ };
116672
+ const scope = current.mail_scope;
116673
+ if (scope === "org") {
116674
+ if (change.rules) return {
116675
+ ok: false,
116676
+ message: "This key reaches every mailbox, so email rules cannot be set on it. Create a key limited to the rules instead."
116677
+ };
116678
+ return {
116679
+ ok: true,
116680
+ grant: {
116681
+ preset: "custom",
116682
+ permissions,
116683
+ mail_scope: "org"
116684
+ },
116685
+ permissions
116686
+ };
116687
+ }
116688
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return {
116689
+ ok: false,
116690
+ message: "This key's mail scope could not be read. Change it in the dashboard."
116691
+ };
116692
+ const record = scope;
116693
+ const addresses = stringList(record.addresses ?? []);
116694
+ const domains = stringList(record.domains ?? []);
116695
+ const rules = change.rules ?? stringList(record.rules ?? []);
116696
+ if (!addresses || !domains || !rules || Object.keys(record).some((field) => !RESTATABLE_SCOPE_FIELDS.has(field))) return {
116697
+ ok: false,
116698
+ message: "This key's mail scope has settings the CLI cannot restate. Change it in the dashboard."
116699
+ };
116700
+ if (addresses.length === 0 && domains.length === 0 && rules.length === 0) return {
116701
+ ok: false,
116702
+ message: "The key would reach no mail. Keep at least one address, domain or rule."
116703
+ };
116704
+ return {
116705
+ ok: true,
116706
+ grant: {
116707
+ preset: "custom",
116708
+ permissions,
116709
+ ...addresses.length > 0 ? { addresses } : {},
116710
+ ...domains.length > 0 ? { domains } : {},
116711
+ ...rules.length > 0 ? { rules } : {}
116712
+ },
116713
+ permissions
116714
+ };
116715
+ }
116716
+ async function authenticate(command, flags) {
116717
+ return createAuthenticatedCliApiClient({
116718
+ apiKey: flags["api-key"],
116719
+ apiBaseUrl: flags["api-base-url"],
116720
+ configDir: command.config.configDir
116721
+ });
116722
+ }
116723
+ /** Print a refusal and set a failing exit code. */
116724
+ function reportKeysError(command, outcome, auth, json) {
116725
+ if (json) process.stderr.write(`${JSON.stringify(outcome.payload)}\n`);
116726
+ else writeErrorWithHints(outcome.payload);
116727
+ if (outcome.stepUpUrl) process.stderr.write(`Approve this request in your browser: ${outcome.stepUpUrl}\nThen run the same command again with --step-up-token <token from the page>.\n`);
116728
+ surfaceUnauthorizedHint({
116729
+ auth: auth.auth,
116730
+ baseUrlOverridden: auth.baseUrlOverridden,
116731
+ configDir: command.config.configDir,
116732
+ payload: outcome.payload
116733
+ });
116734
+ process.exitCode = 1;
116735
+ }
116736
+ function writeWarnings(warnings) {
116737
+ for (const warning of warnings ?? []) if (typeof warning.description === "string") process.stderr.write(`Note: ${warning.description}\n`);
116738
+ }
114616
116739
  var KeysListCommand = class KeysListCommand extends Command {
114617
- static description = `List the organization's API keys from the credential inventory, with grant, expiry and last use. Only metadata is shown, never a secret.
116740
+ static description = `List the organization's API keys with their permissions, mail scope, expiry and last use. Only metadata is shown, never a secret.
114618
116741
 
114619
- ${KEYS_MANAGED_IN_DASHBOARD} The server answers this list only for an owner or admin in a dashboard session; from a CLI login it shows the server's refusal.`;
116742
+ You see your own keys, and every key if your role may read them all, as in the dashboard.`;
114620
116743
  static summary = "List API keys";
114621
116744
  static examples = ["<%= config.bin %> keys list", "<%= config.bin %> keys list --json"];
114622
116745
  static flags = {
114623
116746
  "api-key": API_KEY_FLAG$1,
114624
116747
  "api-base-url": API_BASE_URL_FLAG$2,
114625
- json: Flags.boolean({ description: "Print the inventory rows as JSON." })
116748
+ json: Flags.boolean({ description: "Print the keys as JSON." })
114626
116749
  };
114627
116750
  async run() {
114628
116751
  const { flags } = await this.parse(KeysListCommand);
114629
- const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
114630
- apiKey: flags["api-key"],
114631
- apiBaseUrl: flags["api-base-url"],
114632
- configDir: this.config.configDir
114633
- });
114634
- const result = await listCredentials({
114635
- client: apiClient.client,
114636
- query: { kind: "org_key" },
114637
- responseStyle: "fields"
116752
+ const auth = await authenticate(this, flags);
116753
+ const items = [];
116754
+ let cursor;
116755
+ for (let page = 0; page < LIST_PAGE_LIMIT; page += 1) {
116756
+ const result = await call(listKeys({
116757
+ client: auth.apiClient.client,
116758
+ ...cursor ? { query: { cursor } } : {}
116759
+ }));
116760
+ if (!result.ok) {
116761
+ reportKeysError(this, result, auth, Boolean(flags.json));
116762
+ return;
116763
+ }
116764
+ items.push(...result.data.items);
116765
+ cursor = result.data.next_cursor ?? void 0;
116766
+ if (!cursor) break;
116767
+ }
116768
+ if (flags.json) {
116769
+ this.log(JSON.stringify(items, null, 2));
116770
+ return;
116771
+ }
116772
+ this.log(formatKeyRows(items));
116773
+ }
116774
+ };
116775
+ var KeysGetCommand = class KeysGetCommand extends Command {
116776
+ static description = "Show one API key: its permissions, the mail it reaches, expiry and last use. Never a secret.";
116777
+ static summary = "Show an API key";
116778
+ static examples = ["<%= config.bin %> keys get 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys get 0f0e0d0c-0b0a-4908-8706-050403020100 --json"];
116779
+ static args = { id: Args.string({
116780
+ description: "The key's id.",
116781
+ required: true
116782
+ }) };
116783
+ static flags = {
116784
+ "api-key": API_KEY_FLAG$1,
116785
+ "api-base-url": API_BASE_URL_FLAG$2,
116786
+ json: Flags.boolean({ description: "Print the key as JSON." })
116787
+ };
116788
+ async run() {
116789
+ const { args, flags } = await this.parse(KeysGetCommand);
116790
+ const auth = await authenticate(this, flags);
116791
+ const result = await call(getKey({
116792
+ client: auth.apiClient.client,
116793
+ path: { id: args.id }
116794
+ }));
116795
+ if (!result.ok) {
116796
+ reportKeysError(this, result, auth, Boolean(flags.json));
116797
+ return;
116798
+ }
116799
+ this.log(flags.json ? JSON.stringify(result.data, null, 2) : formatKeyDetail(result.data));
116800
+ }
116801
+ };
116802
+ /** The new secret on stdout alone, everything else on stderr. */
116803
+ function printSecret(command, json, document, secret, summary) {
116804
+ if (json) {
116805
+ command.log(JSON.stringify({
116806
+ ...document,
116807
+ notice: STORE_SECRET_NOTICE
116808
+ }, null, 2));
116809
+ return;
116810
+ }
116811
+ process.stderr.write(`${summary}\n\n`);
116812
+ command.log(secret);
116813
+ process.stderr.write(`\n${STORE_SECRET_NOTICE}\n`);
116814
+ }
116815
+ var KeysCreateCommand = class KeysCreateCommand extends Command {
116816
+ static description = `Create an API key. Say what it can do with --preset or --permission; there is no default.
116817
+
116818
+ The key is created directly. If your organization requires approval, the command prints the approval link, opens it where it can, and waits for the one-time token the page shows. In a script it prints the link and exits: approve, then run it again with --step-up-token. The key never holds more than you do, and your organization's credential policy applies.
116819
+
116820
+ 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.
116821
+
116822
+ 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.`;
116823
+ static summary = "Create an API key";
116824
+ static examples = [
116825
+ "<%= config.bin %> keys create --name reader --preset read_only",
116826
+ "<%= config.bin %> keys create --name orders --permission mail:read --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f",
116827
+ "<%= config.bin %> keys create --name support --permission mail:read,mail:reply --address support@example.com --expires-in-days 90",
116828
+ "<%= config.bin %> keys create --name everything --preset super_admin"
116829
+ ];
116830
+ static flags = {
116831
+ "api-key": API_KEY_FLAG$1,
116832
+ "api-base-url": API_BASE_URL_FLAG$2,
116833
+ name: Flags.string({ description: "A name for the key." }),
116834
+ preset: Flags.string({
116835
+ description: "A preset grant.",
116836
+ options: [...KEY_PRESETS]
116837
+ }),
116838
+ permission: Flags.string({
116839
+ description: "A permission the key holds (repeatable, or comma-separated), for a custom key.",
116840
+ multiple: true
116841
+ }),
116842
+ address: Flags.string({
116843
+ description: "An address the key reaches (repeatable).",
116844
+ multiple: true
116845
+ }),
116846
+ domain: Flags.string({
116847
+ description: "A domain the key reaches (repeatable).",
116848
+ multiple: true
116849
+ }),
116850
+ rule: Flags.string({
116851
+ description: "An email rule id whose matching mail the key reaches (repeatable).",
116852
+ multiple: true
116853
+ }),
116854
+ "all-mailboxes": Flags.boolean({ description: "A key with --permission that reaches every mailbox." }),
116855
+ "expires-in-days": Flags.integer({
116856
+ description: "Expire the key after this many days.",
116857
+ min: 1
116858
+ }),
116859
+ "expires-at": Flags.string({
116860
+ description: "Expire the key at this ISO 8601 time.",
116861
+ exclusive: ["expires-in-days"]
116862
+ }),
116863
+ ...APPROVAL_FLAGS,
116864
+ json: Flags.boolean({ description: "Print the new key, its secret included, as JSON." })
116865
+ };
116866
+ async run() {
116867
+ const { flags } = await this.parse(KeysCreateCommand);
116868
+ const grant = buildCreateGrant({
116869
+ preset: flags.preset,
116870
+ permissions: listFlag(flags.permission),
116871
+ addresses: listFlag(flags.address),
116872
+ domains: listFlag(flags.domain),
116873
+ rules: listFlag(flags.rule),
116874
+ allMailboxes: flags["all-mailboxes"]
114638
116875
  });
114639
- if (result.error) {
114640
- reportKeysError(result.error, {
114641
- auth,
114642
- baseUrlOverridden,
114643
- configDir: this.config.configDir
114644
- }, Boolean(flags.json));
116876
+ if (!grant.ok) this.error(grant.message, { exit: 2 });
116877
+ const auth = await authenticate(this, flags);
116878
+ const body = {
116879
+ grant: grant.grant,
116880
+ ...flags.name ? { name: flags.name } : {},
116881
+ ...flags["expires-in-days"] ? { expires_in_days: flags["expires-in-days"] } : {},
116882
+ ...flags["expires-at"] ? { expires_at: flags["expires-at"] } : {}
116883
+ };
116884
+ const idempotencyKey = `cli-${randomUUID()}`;
116885
+ const result = await withApproval((token) => call(createKey({
116886
+ client: auth.apiClient.client,
116887
+ body,
116888
+ headers: {
116889
+ "Idempotency-Key": idempotencyKey,
116890
+ ...token ? { [STEP_UP_HEADER$1]: token } : {}
116891
+ }
116892
+ })), approvalIo(flags));
116893
+ if (!result.ok) {
116894
+ reportKeysError(this, result, auth, Boolean(flags.json));
116895
+ return;
116896
+ }
116897
+ const key = result.data;
116898
+ if (!flags.json) writeWarnings(key.warnings);
116899
+ printSecret(this, Boolean(flags.json), key, key.key, [
116900
+ `Created API key ${key.name ?? "(unnamed)"} (${key.id}), prefix ${key.key_prefix}.`,
116901
+ `Permissions: ${describePermissions({
116902
+ template: key.grant.template,
116903
+ permissions: key.grant.permissions,
116904
+ mail_scope: key.grant.mail_scope
116905
+ })}`,
116906
+ `Mail: ${describeMailScope({
116907
+ template: key.grant.template,
116908
+ permissions: key.grant.permissions,
116909
+ mail_scope: key.grant.mail_scope
116910
+ })}`,
116911
+ `Expires: ${key.expires_at ?? "never"}`
116912
+ ].join("\n"));
116913
+ }
116914
+ };
116915
+ var KeysRotateCommand = class KeysRotateCommand extends Command {
116916
+ static description = `Rotate an API key: make a successor with the same permissions and print its secret once. The old key keeps working for the overlap, then stops.
116917
+
116918
+ The rotation runs directly; if your organization requires approval, the command prints the approval link and takes --step-up-token, as for a new key. Store the new secret right away: it is not shown again and the CLI does not save it.`;
116919
+ static summary = "Rotate an API key";
116920
+ static examples = ["<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100 --overlap-seconds 0"];
116921
+ static args = { id: Args.string({
116922
+ description: "The key's id.",
116923
+ required: true
116924
+ }) };
116925
+ static flags = {
116926
+ "api-key": API_KEY_FLAG$1,
116927
+ "api-base-url": API_BASE_URL_FLAG$2,
116928
+ "overlap-seconds": Flags.integer({
116929
+ description: "How long the old key keeps working, in seconds. 0 stops it now.",
116930
+ min: 0
116931
+ }),
116932
+ ...APPROVAL_FLAGS,
116933
+ json: Flags.boolean({ description: "Print the new key, its secret included, as JSON." })
116934
+ };
116935
+ async run() {
116936
+ const { args, flags } = await this.parse(KeysRotateCommand);
116937
+ const auth = await authenticate(this, flags);
116938
+ const body = flags["overlap-seconds"] === void 0 ? {} : { overlap_seconds: flags["overlap-seconds"] };
116939
+ const result = await withApproval((token) => call(rotateKey({
116940
+ client: auth.apiClient.client,
116941
+ path: { id: args.id },
116942
+ body,
116943
+ ...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
116944
+ })), approvalIo(flags));
116945
+ if (!result.ok) {
116946
+ reportKeysError(this, result, auth, Boolean(flags.json));
116947
+ return;
116948
+ }
116949
+ const rotated = result.data;
116950
+ printSecret(this, Boolean(flags.json), rotated, rotated.key, `Rotated key ${rotated.previous_key_id}. The new key is ${rotated.id}, prefix ${rotated.key_prefix}. The old key works until ${rotated.previous_key_expires_at}.`);
116951
+ }
116952
+ };
116953
+ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
116954
+ static description = `Change an API key's permissions, email rules or name. It keeps its id and secret.
116955
+
116956
+ --add and --remove take permissions (repeatable, or comma-separated). --rule sets the email rules the key reaches, replacing the ones it has. Everything else about the key stays as it is.
116957
+
116958
+ 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.`;
116959
+ static summary = "Change an API key";
116960
+ static examples = [
116961
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --add mail:send,mail:reply",
116962
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --remove mail:send",
116963
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f"
116964
+ ];
116965
+ static args = { id: Args.string({
116966
+ description: "The key's id.",
116967
+ required: true
116968
+ }) };
116969
+ static flags = {
116970
+ "api-key": API_KEY_FLAG$1,
116971
+ "api-base-url": API_BASE_URL_FLAG$2,
116972
+ add: Flags.string({
116973
+ description: "Permissions to add (repeatable, or comma-separated).",
116974
+ multiple: true
116975
+ }),
116976
+ remove: Flags.string({
116977
+ description: "Permissions to remove (repeatable, or comma-separated).",
116978
+ multiple: true
116979
+ }),
116980
+ rule: Flags.string({
116981
+ description: "An email rule id the key reaches (repeatable). Replaces the key's rules.",
116982
+ multiple: true
116983
+ }),
116984
+ name: Flags.string({ description: "A new name for the key." }),
116985
+ ...APPROVAL_FLAGS,
116986
+ json: Flags.boolean({ description: "Print the result as JSON." })
116987
+ };
116988
+ async run() {
116989
+ const { args, flags } = await this.parse(KeysUpdateCommand);
116990
+ const add = listFlag(flags.add);
116991
+ const remove = listFlag(flags.remove);
116992
+ const rules = flags.rule ? listFlag(flags.rule) : void 0;
116993
+ const both = add.filter((p) => remove.includes(p));
116994
+ if (both.length > 0) this.error(`Both added and removed: ${both.join(", ")}.`, { exit: 2 });
116995
+ const changesGrant = add.length > 0 || remove.length > 0 || !!rules;
116996
+ if (!changesGrant && flags.name === void 0) this.error("Nothing to change: pass --add, --remove, --rule or --name.", { exit: 2 });
116997
+ const auth = await authenticate(this, flags);
116998
+ const body = { ...flags.name !== void 0 ? { name: flags.name } : {} };
116999
+ if (changesGrant) {
117000
+ const current = await call(getKey({
117001
+ client: auth.apiClient.client,
117002
+ path: { id: args.id }
117003
+ }));
117004
+ if (!current.ok) {
117005
+ reportKeysError(this, current, auth, Boolean(flags.json));
117006
+ return;
117007
+ }
117008
+ const next = buildUpdatedGrant(current.data.grant, {
117009
+ add,
117010
+ remove,
117011
+ ...rules ? { rules } : {}
117012
+ });
117013
+ if (!next.ok) this.error(next.message, { exit: 2 });
117014
+ body.grant = next.grant;
117015
+ const ref = current.data.grant.ref;
117016
+ if (typeof ref === "string") body.expected_grant_ref = ref;
117017
+ }
117018
+ const result = await withApproval((token) => call(updateKey({
117019
+ client: auth.apiClient.client,
117020
+ path: { id: args.id },
117021
+ body,
117022
+ ...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
117023
+ })), approvalIo(flags));
117024
+ if (!result.ok) {
117025
+ reportKeysError(this, result, auth, Boolean(flags.json));
114645
117026
  return;
114646
117027
  }
114647
- const rows = unwrap(result.data)?.items ?? [];
117028
+ const grantChanged = result.data.changed.includes("grant");
117029
+ const after = grantChanged ? await call(getKey({
117030
+ client: auth.apiClient.client,
117031
+ path: { id: args.id }
117032
+ })) : null;
114648
117033
  if (flags.json) {
114649
- this.log(JSON.stringify(rows, null, 2));
117034
+ this.log(JSON.stringify({
117035
+ ...result.data,
117036
+ ...after?.ok ? { key: after.data } : {},
117037
+ ...grantChanged ? { notice: HELD_REQUEST_NOTICE } : {}
117038
+ }, null, 2));
114650
117039
  return;
114651
117040
  }
114652
- this.log(formatKeyRows(rows));
117041
+ writeWarnings(result.data.warnings);
117042
+ const changed = result.data.changed;
117043
+ this.log(changed.length > 0 ? `Updated key ${args.id}: ${changed.join(", ")}.` : `Key ${args.id} already matched; nothing changed.`);
117044
+ if (after?.ok) {
117045
+ this.log(`Permissions: ${describePermissions(after.data.grant)}`);
117046
+ this.log(`Mail: ${describeMailScope(after.data.grant)}`);
117047
+ }
117048
+ if (grantChanged) process.stderr.write(`${HELD_REQUEST_NOTICE}\n`);
117049
+ }
117050
+ };
117051
+ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
117052
+ static description = `Revoke an API key. It stops working at once; what it set up keeps working and is listed for review.
117053
+
117054
+ Asks for confirmation first; pass --yes to skip the prompt in scripts.`;
117055
+ static summary = "Revoke an API key";
117056
+ static examples = ["<%= config.bin %> keys revoke 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys revoke 0f0e0d0c-0b0a-4908-8706-050403020100 --yes"];
117057
+ static args = { id: Args.string({
117058
+ description: "The key's id.",
117059
+ required: true
117060
+ }) };
117061
+ static flags = {
117062
+ "api-key": API_KEY_FLAG$1,
117063
+ "api-base-url": API_BASE_URL_FLAG$2,
117064
+ yes: Flags.boolean({
117065
+ char: "y",
117066
+ description: "Revoke without asking for confirmation."
117067
+ }),
117068
+ reason: Flags.string({ description: "Why, recorded with the revocation." }),
117069
+ json: Flags.boolean({ description: "Print the result as JSON." })
117070
+ };
117071
+ async run() {
117072
+ const { args, flags } = await this.parse(KeysRevokeCommand);
117073
+ if (!flags.yes) {
117074
+ if (flags.json || !(process.stdin.isTTY && process.stderr.isTTY)) this.error("Not revoking: no terminal to confirm in. Pass --yes to revoke without a prompt.", { exit: 2 });
117075
+ const answer = await promptLine$2(`Revoke key ${args.id}? It stops working at once. [y/N] `);
117076
+ if (!/^y(es)?$/i.test(answer.trim())) {
117077
+ process.stderr.write("Cancelled. The key still works.\n");
117078
+ process.exitCode = 1;
117079
+ return;
117080
+ }
117081
+ }
117082
+ const auth = await authenticate(this, flags);
117083
+ const result = await call(revokeCredential({
117084
+ client: auth.apiClient.client,
117085
+ body: {
117086
+ kind: "org_key",
117087
+ id: args.id,
117088
+ ...flags.reason ? { reason: flags.reason } : {}
117089
+ }
117090
+ }));
117091
+ if (!result.ok) {
117092
+ reportKeysError(this, result, auth, Boolean(flags.json));
117093
+ return;
117094
+ }
117095
+ if (flags.json) {
117096
+ this.log(JSON.stringify(result.data, null, 2));
117097
+ return;
117098
+ }
117099
+ this.log(`Revoked key ${args.id}.`);
117100
+ const standing = result.data.standing_objects;
117101
+ if (Array.isArray(standing) && standing.length > 0) this.log(`It set up ${standing.length} item(s) that keep working. Review them with --json.`);
114653
117102
  }
114654
117103
  };
114655
117104
  const hash = (value) => createHash("sha256").update(value).digest("hex");
@@ -119864,6 +122313,16 @@ function printData(command, responseData) {
119864
122313
  const data = responseData?.data ?? null;
119865
122314
  command.log(JSON.stringify(data, null, 2));
119866
122315
  }
122316
+ const CONTROL_CHARACTERS = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}]`);
122317
+ /**
122318
+ * The stderr line naming the next page's cursor. A memory key can hold a line
122319
+ * break or another control character, which would split or garble the line,
122320
+ * so such a cursor is printed JSON-encoded, with a note on how to pass it.
122321
+ */
122322
+ function nextCursorLine(cursor) {
122323
+ if (!CONTROL_CHARACTERS.test(cursor)) return `next cursor: ${cursor}\n`;
122324
+ return `next cursor (JSON-encoded, it holds control characters; decode it before passing it to --cursor, or read meta.cursor with --json --envelope): ${JSON.stringify(cursor)}\n`;
122325
+ }
119867
122326
  function handleMemoryInputError(error) {
119868
122327
  if (!(error instanceof MemoryCliInputError)) return false;
119869
122328
  process.stderr.write(`${error.message}\n`);
@@ -120003,6 +122462,7 @@ var MemoriesSetCommand = class MemoriesSetCommand extends Command {
120003
122462
  value: Args.string({ description: "JSON value. Strings must be quoted as JSON strings." })
120004
122463
  };
120005
122464
  static flags = {
122465
+ json: JSON_OUTPUT_FLAG,
120006
122466
  "api-key": API_KEY_FLAG,
120007
122467
  "api-base-url": API_BASE_URL_FLAG$1,
120008
122468
  ...SCOPE_FLAGS,
@@ -120094,6 +122554,7 @@ var MemoriesGetCommand = class MemoriesGetCommand extends Command {
120094
122554
  required: true
120095
122555
  }) };
120096
122556
  static flags = {
122557
+ json: JSON_OUTPUT_FLAG,
120097
122558
  "api-key": API_KEY_FLAG,
120098
122559
  "api-base-url": API_BASE_URL_FLAG$1,
120099
122560
  ...SCOPE_FLAGS,
@@ -120147,6 +122608,7 @@ var MemoriesDeleteCommand = class MemoriesDeleteCommand extends Command {
120147
122608
  required: true
120148
122609
  }) };
120149
122610
  static flags = {
122611
+ json: JSON_OUTPUT_FLAG,
120150
122612
  "api-key": API_KEY_FLAG,
120151
122613
  "api-base-url": API_BASE_URL_FLAG$1,
120152
122614
  ...SCOPE_FLAGS,
@@ -120201,6 +122663,7 @@ var MemoriesSearchCommand = class MemoriesSearchCommand extends Command {
120201
122663
  ];
120202
122664
  static args = { prefix: Args.string({ description: "Key prefix. Omit to list all active memories in scope." }) };
120203
122665
  static flags = {
122666
+ json: JSON_OUTPUT_FLAG,
120204
122667
  "api-key": API_KEY_FLAG,
120205
122668
  "api-base-url": API_BASE_URL_FLAG$1,
120206
122669
  ...SCOPE_FLAGS,
@@ -120210,7 +122673,8 @@ var MemoriesSearchCommand = class MemoriesSearchCommand extends Command {
120210
122673
  min: 1,
120211
122674
  max: 100
120212
122675
  }),
120213
- cursor: Flags.string({ description: "Key cursor from a previous response's meta.cursor." }),
122676
+ cursor: Flags.string({ description: "Key cursor from the previous page. The next page's cursor is meta.cursor, null on the last page: without --json it is printed on stderr as `next cursor: <value>` (JSON-encoded, with a note, when it holds control characters); with --json, add --envelope to get it, the reliable way for a script to page." }),
122677
+ envelope: Flags.boolean({ description: "Print the full response envelope, including meta.cursor for the next page. Defaults to printing only the data payload." }),
120214
122678
  "metadata-only": Flags.boolean({ description: "Omit memory values and return metadata only." }),
120215
122679
  "updated-after": Flags.string({ description: "Only include memories updated at or after this ISO timestamp." }),
120216
122680
  "updated-before": Flags.string({ description: "Only include memories updated at or before this ISO timestamp." }),
@@ -120247,7 +122711,11 @@ var MemoriesSearchCommand = class MemoriesSearchCommand extends Command {
120247
122711
  });
120248
122712
  return;
120249
122713
  }
120250
- printData(this, result.data);
122714
+ const envelope = result.data;
122715
+ const cursor = envelope?.meta?.cursor;
122716
+ if (cursor && !flags.json) process.stderr.write(nextCursorLine(cursor));
122717
+ if (flags.envelope) this.log(JSON.stringify(envelope ?? null, null, 2));
122718
+ else printData(this, result.data);
120251
122719
  });
120252
122720
  }
120253
122721
  };
@@ -120767,6 +123235,7 @@ var OrgSecretsListCommand = class OrgSecretsListCommand extends Command {
120767
123235
  static summary = "List global secrets (keys only; values never returned)";
120768
123236
  static examples = ["<%= config.bin %> org secrets list"];
120769
123237
  static flags = {
123238
+ json: JSON_OUTPUT_FLAG,
120770
123239
  "api-key": Flags.string({
120771
123240
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
120772
123241
  env: "PRIMITIVE_API_KEY"
@@ -120812,6 +123281,7 @@ var OrgSecretsRemoveCommand = class OrgSecretsRemoveCommand extends Command {
120812
123281
  static aliases = ["org:secrets:delete"];
120813
123282
  static examples = ["<%= config.bin %> org secrets remove --key MY_API_KEY"];
120814
123283
  static flags = {
123284
+ json: JSON_OUTPUT_FLAG,
120815
123285
  "api-key": Flags.string({
120816
123286
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
120817
123287
  env: "PRIMITIVE_API_KEY"
@@ -120872,6 +123342,7 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
120872
123342
  "printf '%s' \"$MY_API_KEY\" | <%= config.bin %> org secrets set --key MY_API_KEY --stdin"
120873
123343
  ];
120874
123344
  static flags = {
123345
+ json: JSON_OUTPUT_FLAG,
120875
123346
  "api-key": Flags.string({
120876
123347
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
120877
123348
  env: "PRIMITIVE_API_KEY"
@@ -122224,6 +124695,7 @@ var RoutesAddCommand = class RoutesAddCommand extends Command {
122224
124695
  required: true
122225
124696
  }) };
122226
124697
  static flags = {
124698
+ json: JSON_OUTPUT_FLAG,
122227
124699
  "api-key": Flags.string({
122228
124700
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122229
124701
  env: "PRIMITIVE_API_KEY"
@@ -122303,6 +124775,7 @@ var RoutesListCommand = class RoutesListCommand extends Command {
122303
124775
  static summary = "List recipient routes";
122304
124776
  static examples = ["<%= config.bin %> routes list"];
122305
124777
  static flags = {
124778
+ json: JSON_OUTPUT_FLAG,
122306
124779
  "api-key": Flags.string({
122307
124780
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122308
124781
  env: "PRIMITIVE_API_KEY"
@@ -122352,6 +124825,7 @@ var RoutesRemoveCommand = class RoutesRemoveCommand extends Command {
122352
124825
  required: true
122353
124826
  }) };
122354
124827
  static flags = {
124828
+ json: JSON_OUTPUT_FLAG,
122355
124829
  "api-key": Flags.string({
122356
124830
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122357
124831
  env: "PRIMITIVE_API_KEY"
@@ -122418,6 +124892,7 @@ var RoutesReorderCommand = class RoutesReorderCommand extends Command {
122418
124892
  static summary = "Reorder recipient routes";
122419
124893
  static examples = ["<%= config.bin %> routes reorder --set <route-id>=10", "<%= config.bin %> routes reorder --set <id-a>=10 --set <id-b>=20"];
122420
124894
  static flags = {
124895
+ json: JSON_OUTPUT_FLAG,
122421
124896
  "api-key": Flags.string({
122422
124897
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122423
124898
  env: "PRIMITIVE_API_KEY"
@@ -122483,6 +124958,7 @@ var RoutesTestCommand = class RoutesTestCommand extends Command {
122483
124958
  required: true
122484
124959
  }) };
122485
124960
  static flags = {
124961
+ json: JSON_OUTPUT_FLAG,
122486
124962
  "api-key": Flags.string({
122487
124963
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122488
124964
  env: "PRIMITIVE_API_KEY"
@@ -122555,6 +125031,7 @@ var RoutesUpdateCommand = class RoutesUpdateCommand extends Command {
122555
125031
  required: true
122556
125032
  }) };
122557
125033
  static flags = {
125034
+ json: JSON_OUTPUT_FLAG,
122558
125035
  "api-key": Flags.string({
122559
125036
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
122560
125037
  env: "PRIMITIVE_API_KEY"
@@ -124800,6 +127277,7 @@ var WakeAuthorizationsCreateCommand = class WakeAuthorizationsCreateCommand exte
124800
127277
  static summary = "Create a wake authorization";
124801
127278
  static examples = ["<%= config.bin %> wake authorizations create --endpoint <endpoint-id> --domain agents.acme.dev --command process_inbox"];
124802
127279
  static flags = {
127280
+ json: JSON_OUTPUT_FLAG,
124803
127281
  "api-key": Flags.string({
124804
127282
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
124805
127283
  env: "PRIMITIVE_API_KEY"
@@ -124870,6 +127348,7 @@ var WakeAuthorizationsDeleteCommand = class WakeAuthorizationsDeleteCommand exte
124870
127348
  required: true
124871
127349
  }) };
124872
127350
  static flags = {
127351
+ json: JSON_OUTPUT_FLAG,
124873
127352
  "api-key": Flags.string({
124874
127353
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
124875
127354
  env: "PRIMITIVE_API_KEY"
@@ -124916,6 +127395,7 @@ var WakeAuthorizationsListCommand = class WakeAuthorizationsListCommand extends
124916
127395
  static summary = "List wake authorizations";
124917
127396
  static examples = ["<%= config.bin %> wake authorizations list", "<%= config.bin %> wake authorizations list --endpoint <endpoint-id>"];
124918
127397
  static flags = {
127398
+ json: JSON_OUTPUT_FLAG,
124919
127399
  "api-key": Flags.string({
124920
127400
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
124921
127401
  env: "PRIMITIVE_API_KEY"
@@ -124967,6 +127447,7 @@ var WakeAuthorizationsUpdateCommand = class WakeAuthorizationsUpdateCommand exte
124967
127447
  required: true
124968
127448
  }) };
124969
127449
  static flags = {
127450
+ json: JSON_OUTPUT_FLAG,
124970
127451
  "api-key": Flags.string({
124971
127452
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
124972
127453
  env: "PRIMITIVE_API_KEY"
@@ -125019,6 +127500,7 @@ var WakeDispatchesListCommand = class WakeDispatchesListCommand extends Command
125019
127500
  static summary = "List recent wake dispatches";
125020
127501
  static examples = ["<%= config.bin %> wake dispatches list", "<%= config.bin %> wake dispatches list --limit 20"];
125021
127502
  static flags = {
127503
+ json: JSON_OUTPUT_FLAG,
125022
127504
  "api-key": Flags.string({
125023
127505
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125024
127506
  env: "PRIMITIVE_API_KEY"
@@ -125066,6 +127548,7 @@ var WakeSchedulesCreateCommand = class WakeSchedulesCreateCommand extends Comman
125066
127548
  static summary = "Create a wake schedule";
125067
127549
  static examples = ["<%= config.bin %> wake schedules create --from scheduler@acme.dev --to alice@acme.dev --command process_inbox --cron \"0 9 * * *\" --timezone America/New_York"];
125068
127550
  static flags = {
127551
+ json: JSON_OUTPUT_FLAG,
125069
127552
  "api-key": Flags.string({
125070
127553
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125071
127554
  env: "PRIMITIVE_API_KEY"
@@ -125151,6 +127634,7 @@ var WakeSchedulesDeleteCommand = class WakeSchedulesDeleteCommand extends Comman
125151
127634
  required: true
125152
127635
  }) };
125153
127636
  static flags = {
127637
+ json: JSON_OUTPUT_FLAG,
125154
127638
  "api-key": Flags.string({
125155
127639
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125156
127640
  env: "PRIMITIVE_API_KEY"
@@ -125201,6 +127685,7 @@ var WakeSchedulesGetCommand = class WakeSchedulesGetCommand extends Command {
125201
127685
  required: true
125202
127686
  }) };
125203
127687
  static flags = {
127688
+ json: JSON_OUTPUT_FLAG,
125204
127689
  "api-key": Flags.string({
125205
127690
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125206
127691
  env: "PRIMITIVE_API_KEY"
@@ -125247,6 +127732,7 @@ var WakeSchedulesListCommand = class WakeSchedulesListCommand extends Command {
125247
127732
  static summary = "List wake schedules";
125248
127733
  static examples = ["<%= config.bin %> wake schedules list"];
125249
127734
  static flags = {
127735
+ json: JSON_OUTPUT_FLAG,
125250
127736
  "api-key": Flags.string({
125251
127737
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125252
127738
  env: "PRIMITIVE_API_KEY"
@@ -125296,6 +127782,7 @@ var WakeSchedulesRunCommand = class WakeSchedulesRunCommand extends Command {
125296
127782
  required: true
125297
127783
  }) };
125298
127784
  static flags = {
127785
+ json: JSON_OUTPUT_FLAG,
125299
127786
  "api-key": Flags.string({
125300
127787
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125301
127788
  env: "PRIMITIVE_API_KEY"
@@ -125346,6 +127833,7 @@ var WakeSchedulesUpdateCommand = class WakeSchedulesUpdateCommand extends Comman
125346
127833
  required: true
125347
127834
  }) };
125348
127835
  static flags = {
127836
+ json: JSON_OUTPUT_FLAG,
125349
127837
  "api-key": Flags.string({
125350
127838
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
125351
127839
  env: "PRIMITIVE_API_KEY"
@@ -125708,7 +128196,7 @@ function renderFishCompletion(binName) {
125708
128196
  const topicOperations = cliOperationManifest.filter((operation) => operation.tagCommand === topic);
125709
128197
  for (const operation of topicOperations) {
125710
128198
  lines.push(`complete -c ${binName} -f -n '__fish_${binName}_topic_needs_subcommand ${fishEscape(topic)}' -a '${fishEscape(operation.command)}' -d '${fishEscape(operation.summary ?? `${operation.method} ${operation.path}`)}'`);
125711
- for (const parameter of [...operation.pathParams, ...operation.queryParams]) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameter.name.replace(/_/g, "-"))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
128199
+ for (const parameter of [...operation.pathParams, ...operation.queryParams]) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameterFlagName(operation.sdkName, parameter.name))}' -r -d '${fishEscape(OPERATION_PATH_FLAGS[operation.sdkName]?.[parameter.name]?.description ?? parameter.description ?? parameter.name)}'`);
125712
128200
  for (const parameter of operation.headerParams) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(headerFlagName(parameter.name))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
125713
128201
  lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)'`);
125714
128202
  if (!operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'envelope' -d 'Print the full response envelope, including pagination metadata'`);
@@ -125759,6 +128247,7 @@ function readCompletionFunction(cacheDir, bin, shell) {
125759
128247
  var ListOperationsCommand = class extends Command {
125760
128248
  static description = "List all generated API operations as JSON. Useful for piping to `jq` to discover available commands, their request/response schemas, and per-field descriptions. For inspecting a single operation in detail, prefer `primitive describe <command-or-operation-name>`.";
125761
128249
  static summary = "List all generated API operations (JSON)";
128250
+ static flags = { json: JSON_OUTPUT_FLAG };
125762
128251
  async run() {
125763
128252
  this.log(JSON.stringify(cliOperationManifest, null, 2));
125764
128253
  }
@@ -125854,6 +128343,7 @@ var DescribeCommand = class DescribeCommand extends Command {
125854
128343
  "<%= config.bin %> describe emails:get",
125855
128344
  "<%= config.bin %> describe sent:get"
125856
128345
  ];
128346
+ static flags = { json: JSON_OUTPUT_FLAG };
125857
128347
  async run() {
125858
128348
  const { args } = await this.parse(DescribeCommand);
125859
128349
  const { match, candidates } = lookupOperation(args.command);
@@ -126092,6 +128582,11 @@ const COMMANDS = {
126092
128582
  "org:use": OrgUseCommand,
126093
128583
  "cli:list-cli-orgs": OrgsCommand,
126094
128584
  "keys:list": KeysListCommand,
128585
+ "keys:get": KeysGetCommand,
128586
+ "keys:create": KeysCreateCommand,
128587
+ "keys:rotate": KeysRotateCommand,
128588
+ "keys:update": KeysUpdateCommand,
128589
+ "keys:revoke": KeysRevokeCommand,
126095
128590
  "roles:list": RolesListCommand,
126096
128591
  "roles:groups": RolesGroupsCommand,
126097
128592
  "roles:mailbox-access": RolesMailboxAccessCommand,