primcli 3.0.0-preview.1 → 3.0.0-preview.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
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 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-B37DPKFA.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-BzXj5ICh.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-Bo8WgG3q.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-g-xPd3X0.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-TJlyUTi7.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";
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-BO93dM2Q.js";
10
10
  import { n as jsonOutputRequested, r as runStreamingJson, t as guardJsonOutputCommands } from "../json-output-BzSBR-KX.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";
@@ -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. 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 and scope_selection 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": {
@@ -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 and scope_selection 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": {
@@ -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; draft is not evaluated. The caller is the rule's author, and the rule never reaches mail the author cannot read. evaluation_scope (author when omitted) chooses whose mail it runs over; scope_selection may name only what you can 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.",
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.",
21896
21900
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
21897
21901
  },
21898
21902
  "429": {
@@ -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. 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. 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. 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.",
@@ -31897,7 +32283,7 @@ 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 Primitive CLI takes the same step-up; the Primitive app's token is refused, even with one. 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.",
31901
32287
  "x-primitive-person-only": {
31902
32288
  "access": "step_up",
31903
32289
  "step_up_purpose": "keys.create"
@@ -31927,7 +32313,7 @@ const openapiDocument = {
31927
32313
  "name": "Primitive-Step-Up",
31928
32314
  "in": "header",
31929
32315
  "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`).",
32316
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
31931
32317
  "schema": {
31932
32318
  "type": "string",
31933
32319
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -32178,7 +32564,7 @@ const openapiDocument = {
32178
32564
  "summary": "Rotate an API key",
32179
32565
  "tags": ["Credentials"],
32180
32566
  "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.",
32567
+ "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 takes the same step-up; 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.\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.",
32182
32568
  "x-primitive-person-only": {
32183
32569
  "access": "step_up",
32184
32570
  "step_up_purpose": "keys.create"
@@ -32198,7 +32584,7 @@ const openapiDocument = {
32198
32584
  "name": "Primitive-Step-Up",
32199
32585
  "in": "header",
32200
32586
  "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`).",
32587
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
32202
32588
  "schema": {
32203
32589
  "type": "string",
32204
32590
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -32344,7 +32730,7 @@ const openapiDocument = {
32344
32730
  "summary": "Change an API key",
32345
32731
  "tags": ["Credentials"],
32346
32732
  "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.",
32733
+ "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 takes the same step-up; 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.\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.",
32348
32734
  "x-primitive-person-only": {
32349
32735
  "access": "step_up",
32350
32736
  "step_up_purpose": "keys.create"
@@ -32362,7 +32748,7 @@ const openapiDocument = {
32362
32748
  "name": "Primitive-Step-Up",
32363
32749
  "in": "header",
32364
32750
  "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`).",
32751
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
32366
32752
  "schema": {
32367
32753
  "type": "string",
32368
32754
  "pattern": "^pstep_[0-9a-f]{64}$"
@@ -37851,6 +38237,8 @@ const openapiDocument = {
37851
38237
  "email_rule_request_party_left",
37852
38238
  "email_rule_request_proposer_only",
37853
38239
  "email_rule_request_target_only",
38240
+ "email_rule_scope_locked",
38241
+ "email_rule_scope_not_readable",
37854
38242
  "email_rule_share_ended",
37855
38243
  "email_rule_share_limit",
37856
38244
  "embedding_provider_unavailable",
@@ -47602,280 +47990,342 @@ const openapiDocument = {
47602
47990
  },
47603
47991
  "evaluation_scope": {
47604
47992
  "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."
47607
- },
47608
- "rule_id": {
47609
- "description": "A saved rule you can see. Its current version is tested, with its author's access.",
47610
- "type": "string",
47611
- "format": "uuid"
47612
- },
47613
- "email_ids": {
47614
- "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
- "minItems": 1,
47616
- "maxItems": 200,
47617
- "type": "array",
47618
- "items": {
47619
- "type": "string",
47620
- "format": "uuid"
47621
- }
47622
- },
47623
- "classify_missing": {
47624
- "description": "Classify emails in the set that have not been classified yet and arrived more than 2 hours ago, and save their classification. Defaults to true. Newer ones are left to the normal classification and reported pending. The rule is never evaluated on them for real.",
47625
- "type": "boolean"
47626
- }
47627
- },
47628
- "additionalProperties": false
47629
- },
47630
- "TestEmailRuleResult": {
47631
- "type": "object",
47632
- "properties": {
47633
- "rule": {
47634
- "type": "object",
47635
- "properties": {
47636
- "id": {
47637
- "type": ["string", "null"],
47638
- "format": "uuid"
47639
- },
47640
- "version": {
47641
- "type": ["integer", "null"],
47642
- "exclusiveMinimum": 0
47643
- },
47644
- "vocabulary": { "type": "string" }
47645
- },
47646
- "required": [
47647
- "id",
47648
- "version",
47649
- "vocabulary"
47650
- ]
47651
- },
47652
- "window": {
47653
- "type": "object",
47654
- "properties": {
47655
- "mode": {
47656
- "type": "string",
47657
- "enum": ["recent", "ids"]
47658
- },
47659
- "since": {
47660
- "type": ["string", "null"],
47661
- "format": "date-time",
47662
- "description": "The oldest arrival the recent window reaches; null when email_ids were given."
47663
- },
47664
- "limit": {
47665
- "type": "integer",
47666
- "minimum": 0
47667
- }
47668
- },
47669
- "required": [
47670
- "mode",
47671
- "since",
47672
- "limit"
47673
- ]
47674
- },
47675
- "counts": {
47676
- "type": "object",
47677
- "properties": {
47678
- "emails": {
47679
- "type": "integer",
47680
- "minimum": 0
47681
- },
47682
- "matched": {
47683
- "type": "integer",
47684
- "minimum": 0
47685
- },
47686
- "near_miss": {
47687
- "type": "integer",
47688
- "minimum": 0
47689
- },
47690
- "no_match": {
47691
- "type": "integer",
47692
- "minimum": 0
47693
- },
47694
- "unclassified": {
47695
- "type": "integer",
47696
- "minimum": 0
47697
- },
47698
- "not_found": {
47699
- "type": "integer",
47700
- "minimum": 0
47701
- }
47702
- },
47703
- "required": [
47704
- "emails",
47705
- "matched",
47706
- "near_miss",
47707
- "no_match",
47708
- "unclassified",
47709
- "not_found"
47710
- ]
47711
- },
47712
- "classification": {
47713
- "type": "object",
47714
- "properties": {
47715
- "classified_now": {
47716
- "type": "integer",
47717
- "minimum": 0,
47718
- "description": "Emails this test classified and saved."
47719
- },
47720
- "still_unclassified": {
47721
- "type": "integer",
47722
- "minimum": 0,
47723
- "description": "Emails left without a classification."
47724
- }
47725
- },
47726
- "required": ["classified_now", "still_unclassified"]
47993
+ "enum": [
47994
+ "author",
47995
+ "own_mailboxes",
47996
+ "selected"
47997
+ ],
47998
+ "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."
47999
+ },
48000
+ "scope_selection": {
48001
+ "type": "object",
48002
+ "properties": {
48003
+ "addresses": {
48004
+ "maxItems": 100,
48005
+ "type": "array",
48006
+ "items": {
48007
+ "type": "string",
48008
+ "minLength": 3,
48009
+ "maxLength": 320,
48010
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
48011
+ }
48012
+ },
48013
+ "domains": {
48014
+ "maxItems": 50,
48015
+ "type": "array",
48016
+ "items": {
48017
+ "type": "string",
48018
+ "minLength": 1,
48019
+ "maxLength": 253,
48020
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48021
+ }
48022
+ }
48023
+ },
48024
+ "additionalProperties": false,
48025
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
48026
+ },
48027
+ "rule_id": {
48028
+ "description": "A saved rule you can see. Its current version is tested, with its author's access.",
48029
+ "type": "string",
48030
+ "format": "uuid"
48031
+ },
48032
+ "email_ids": {
48033
+ "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.",
48034
+ "minItems": 1,
48035
+ "maxItems": 200,
48036
+ "type": "array",
48037
+ "items": {
48038
+ "type": "string",
48039
+ "format": "uuid"
48040
+ }
48041
+ },
48042
+ "classify_missing": {
48043
+ "description": "Classify emails in the set that have not been classified yet and arrived more than 2 hours ago, and save their classification. Defaults to true. Newer ones are left to the normal classification and reported pending. The rule is never evaluated on them for real.",
48044
+ "type": "boolean"
48045
+ }
48046
+ },
48047
+ "additionalProperties": false
48048
+ },
48049
+ "TestEmailRuleResult": {
48050
+ "type": "object",
48051
+ "properties": {
48052
+ "rule": {
48053
+ "type": "object",
48054
+ "properties": {
48055
+ "id": {
48056
+ "type": ["string", "null"],
48057
+ "format": "uuid"
48058
+ },
48059
+ "version": {
48060
+ "type": ["integer", "null"],
48061
+ "exclusiveMinimum": 0
48062
+ },
48063
+ "vocabulary": { "type": "string" }
48064
+ },
48065
+ "required": [
48066
+ "id",
48067
+ "version",
48068
+ "vocabulary"
48069
+ ]
48070
+ },
48071
+ "window": {
48072
+ "type": "object",
48073
+ "properties": {
48074
+ "mode": {
48075
+ "type": "string",
48076
+ "enum": ["recent", "ids"]
48077
+ },
48078
+ "since": {
48079
+ "type": ["string", "null"],
48080
+ "format": "date-time",
48081
+ "description": "The oldest arrival the recent window reaches; null when email_ids were given."
48082
+ },
48083
+ "limit": {
48084
+ "type": "integer",
48085
+ "minimum": 0
48086
+ }
48087
+ },
48088
+ "required": [
48089
+ "mode",
48090
+ "since",
48091
+ "limit"
48092
+ ]
48093
+ },
48094
+ "counts": {
48095
+ "type": "object",
48096
+ "properties": {
48097
+ "emails": {
48098
+ "type": "integer",
48099
+ "minimum": 0
48100
+ },
48101
+ "matched": {
48102
+ "type": "integer",
48103
+ "minimum": 0
48104
+ },
48105
+ "near_miss": {
48106
+ "type": "integer",
48107
+ "minimum": 0
48108
+ },
48109
+ "no_match": {
48110
+ "type": "integer",
48111
+ "minimum": 0
48112
+ },
48113
+ "unclassified": {
48114
+ "type": "integer",
48115
+ "minimum": 0
48116
+ },
48117
+ "not_found": {
48118
+ "type": "integer",
48119
+ "minimum": 0
48120
+ }
48121
+ },
48122
+ "required": [
48123
+ "emails",
48124
+ "matched",
48125
+ "near_miss",
48126
+ "no_match",
48127
+ "unclassified",
48128
+ "not_found"
48129
+ ]
48130
+ },
48131
+ "classification": {
48132
+ "type": "object",
48133
+ "properties": {
48134
+ "classified_now": {
48135
+ "type": "integer",
48136
+ "minimum": 0,
48137
+ "description": "Emails this test classified and saved."
48138
+ },
48139
+ "still_unclassified": {
48140
+ "type": "integer",
48141
+ "minimum": 0,
48142
+ "description": "Emails left without a classification."
48143
+ }
48144
+ },
48145
+ "required": ["classified_now", "still_unclassified"]
48146
+ },
48147
+ "questions": {
48148
+ "type": "object",
48149
+ "properties": {
48150
+ "asked": {
48151
+ "type": "integer",
48152
+ "minimum": 0,
48153
+ "description": "Calls made to answer the rule's questions, one per matching email."
48154
+ },
48155
+ "capped": {
48156
+ "type": "boolean",
48157
+ "description": "Whether the per-test limit on those calls was reached."
48158
+ }
48159
+ },
48160
+ "required": ["asked", "capped"]
48161
+ },
48162
+ "results": {
48163
+ "type": "array",
48164
+ "items": {
48165
+ "type": "object",
48166
+ "properties": {
48167
+ "email_id": {
48168
+ "type": "string",
48169
+ "format": "uuid"
48170
+ },
48171
+ "outcome": {
48172
+ "type": "string",
48173
+ "enum": [
48174
+ "matched",
48175
+ "near_miss",
48176
+ "no_match",
48177
+ "unclassified",
48178
+ "not_found"
48179
+ ],
48180
+ "description": "not_found: no such email, or not one the rule can read (or not settled, held back, or being removed)."
48181
+ },
48182
+ "received_at": {
48183
+ "type": "string",
48184
+ "format": "date-time"
48185
+ },
48186
+ "from_address": { "type": ["string", "null"] },
48187
+ "subject": { "type": ["string", "null"] },
48188
+ "parts": {
48189
+ "description": "How each top-level part of the rule decided. Present for matched, near_miss and no_match.",
48190
+ "type": "array",
48191
+ "items": {
48192
+ "type": "object",
48193
+ "properties": {
48194
+ "index": {
48195
+ "type": "integer",
48196
+ "minimum": 0
48197
+ },
48198
+ "matched": { "type": "boolean" },
48199
+ "columns": {
48200
+ "type": "array",
48201
+ "items": { "type": "string" }
48202
+ }
48203
+ },
48204
+ "required": [
48205
+ "index",
48206
+ "matched",
48207
+ "columns"
48208
+ ]
48209
+ }
48210
+ },
48211
+ "missing_facts": {
48212
+ "description": "Facts the rule reads that this email's classification left out, so the rule did not match.",
48213
+ "type": "array",
48214
+ "items": { "type": "string" }
48215
+ },
48216
+ "question_answers": {
48217
+ "description": "The answers to the rule's own questions, asked only when its conditions matched.",
48218
+ "type": "array",
48219
+ "items": {
48220
+ "type": "object",
48221
+ "properties": {
48222
+ "question": { "type": "string" },
48223
+ "answer": { "type": ["boolean", "null"] },
48224
+ "probability": { "type": ["number", "null"] },
48225
+ "error_class": { "type": "string" }
48226
+ },
48227
+ "required": [
48228
+ "question",
48229
+ "answer",
48230
+ "probability"
48231
+ ]
48232
+ }
48233
+ },
48234
+ "unclassified_reason": {
48235
+ "description": "Why an unclassified email has no classification. pending: not reached before the test stopped, or not classified here (it arrived within the last 2 hours and normal classification has not finished with it, or classify_missing was false); run the test again later.",
48236
+ "type": "string",
48237
+ "enum": [
48238
+ "pending",
48239
+ "classifier_unavailable",
48240
+ "classification_failed",
48241
+ "not_classifiable"
48242
+ ]
48243
+ }
48244
+ },
48245
+ "required": ["email_id", "outcome"]
48246
+ },
48247
+ "description": "Newest first; with email_ids, the ids not found follow in the order given."
48248
+ }
48249
+ },
48250
+ "required": [
48251
+ "rule",
48252
+ "window",
48253
+ "counts",
48254
+ "classification",
48255
+ "questions",
48256
+ "results"
48257
+ ]
48258
+ },
48259
+ "TestEmailRuleOnSampleRequest": {
48260
+ "type": "object",
48261
+ "properties": {
48262
+ "tree": {
48263
+ "description": "An unsaved rule tree, as POST /email-rules/compile returned it. Give tree or rule_id, not both.",
48264
+ "type": "object",
48265
+ "properties": {
48266
+ "version": {
48267
+ "type": "number",
48268
+ "const": 1
48269
+ },
48270
+ "vocabulary": {
48271
+ "type": "string",
48272
+ "description": "The column contract version the rule is written against, for example v1."
48273
+ },
48274
+ "when": {
48275
+ "type": "object",
48276
+ "propertyNames": { "type": "string" },
48277
+ "additionalProperties": {},
48278
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
48279
+ },
48280
+ "questions": {
48281
+ "type": "array",
48282
+ "items": { "type": "string" },
48283
+ "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."
48284
+ }
48285
+ },
48286
+ "required": [
48287
+ "version",
48288
+ "vocabulary",
48289
+ "when",
48290
+ "questions"
48291
+ ],
48292
+ "additionalProperties": false
48293
+ },
48294
+ "evaluation_scope": {
48295
+ "type": "string",
48296
+ "enum": [
48297
+ "author",
48298
+ "own_mailboxes",
48299
+ "selected"
48300
+ ],
48301
+ "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."
47727
48302
  },
47728
- "questions": {
48303
+ "scope_selection": {
47729
48304
  "type": "object",
47730
48305
  "properties": {
47731
- "asked": {
47732
- "type": "integer",
47733
- "minimum": 0,
47734
- "description": "Calls made to answer the rule's questions, one per matching email."
47735
- },
47736
- "capped": {
47737
- "type": "boolean",
47738
- "description": "Whether the per-test limit on those calls was reached."
47739
- }
47740
- },
47741
- "required": ["asked", "capped"]
47742
- },
47743
- "results": {
47744
- "type": "array",
47745
- "items": {
47746
- "type": "object",
47747
- "properties": {
47748
- "email_id": {
47749
- "type": "string",
47750
- "format": "uuid"
47751
- },
47752
- "outcome": {
47753
- "type": "string",
47754
- "enum": [
47755
- "matched",
47756
- "near_miss",
47757
- "no_match",
47758
- "unclassified",
47759
- "not_found"
47760
- ],
47761
- "description": "not_found: no such email, or not one the rule can read (or not settled, held back, or being removed)."
47762
- },
47763
- "received_at": {
47764
- "type": "string",
47765
- "format": "date-time"
47766
- },
47767
- "from_address": { "type": ["string", "null"] },
47768
- "subject": { "type": ["string", "null"] },
47769
- "parts": {
47770
- "description": "How each top-level part of the rule decided. Present for matched, near_miss and no_match.",
47771
- "type": "array",
47772
- "items": {
47773
- "type": "object",
47774
- "properties": {
47775
- "index": {
47776
- "type": "integer",
47777
- "minimum": 0
47778
- },
47779
- "matched": { "type": "boolean" },
47780
- "columns": {
47781
- "type": "array",
47782
- "items": { "type": "string" }
47783
- }
47784
- },
47785
- "required": [
47786
- "index",
47787
- "matched",
47788
- "columns"
47789
- ]
47790
- }
47791
- },
47792
- "missing_facts": {
47793
- "description": "Facts the rule reads that this email's classification left out, so the rule did not match.",
47794
- "type": "array",
47795
- "items": { "type": "string" }
47796
- },
47797
- "question_answers": {
47798
- "description": "The answers to the rule's own questions, asked only when its conditions matched.",
47799
- "type": "array",
47800
- "items": {
47801
- "type": "object",
47802
- "properties": {
47803
- "question": { "type": "string" },
47804
- "answer": { "type": ["boolean", "null"] },
47805
- "probability": { "type": ["number", "null"] },
47806
- "error_class": { "type": "string" }
47807
- },
47808
- "required": [
47809
- "question",
47810
- "answer",
47811
- "probability"
47812
- ]
47813
- }
47814
- },
47815
- "unclassified_reason": {
47816
- "description": "Why an unclassified email has no classification. pending: not reached before the test stopped, or not classified here (it arrived within the last 2 hours and normal classification has not finished with it, or classify_missing was false); run the test again later.",
48306
+ "addresses": {
48307
+ "maxItems": 100,
48308
+ "type": "array",
48309
+ "items": {
47817
48310
  "type": "string",
47818
- "enum": [
47819
- "pending",
47820
- "classifier_unavailable",
47821
- "classification_failed",
47822
- "not_classifiable"
47823
- ]
48311
+ "minLength": 3,
48312
+ "maxLength": 320,
48313
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
47824
48314
  }
47825
48315
  },
47826
- "required": ["email_id", "outcome"]
47827
- },
47828
- "description": "Newest first; with email_ids, the ids not found follow in the order given."
47829
- }
47830
- },
47831
- "required": [
47832
- "rule",
47833
- "window",
47834
- "counts",
47835
- "classification",
47836
- "questions",
47837
- "results"
47838
- ]
47839
- },
47840
- "TestEmailRuleOnSampleRequest": {
47841
- "type": "object",
47842
- "properties": {
47843
- "tree": {
47844
- "description": "An unsaved rule tree, as POST /email-rules/compile returned it. Give tree or rule_id, not both.",
47845
- "type": "object",
47846
- "properties": {
47847
- "version": {
47848
- "type": "number",
47849
- "const": 1
47850
- },
47851
- "vocabulary": {
47852
- "type": "string",
47853
- "description": "The column contract version the rule is written against, for example v1."
47854
- },
47855
- "when": {
47856
- "type": "object",
47857
- "propertyNames": { "type": "string" },
47858
- "additionalProperties": {},
47859
- "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
47860
- },
47861
- "questions": {
48316
+ "domains": {
48317
+ "maxItems": 50,
47862
48318
  "type": "array",
47863
- "items": { "type": "string" },
47864
- "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."
48319
+ "items": {
48320
+ "type": "string",
48321
+ "minLength": 1,
48322
+ "maxLength": 253,
48323
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48324
+ }
47865
48325
  }
47866
48326
  },
47867
- "required": [
47868
- "version",
47869
- "vocabulary",
47870
- "when",
47871
- "questions"
47872
- ],
47873
- "additionalProperties": false
47874
- },
47875
- "evaluation_scope": {
47876
- "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."
48327
+ "additionalProperties": false,
48328
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
47879
48329
  },
47880
48330
  "rule_id": {
47881
48331
  "description": "A saved rule you can see. Its current version is tested.",
@@ -48110,6 +48560,42 @@ const openapiDocument = {
48110
48560
  "type": "string",
48111
48561
  "enum": ["draft", "observe"]
48112
48562
  },
48563
+ "evaluation_scope": {
48564
+ "type": "string",
48565
+ "enum": [
48566
+ "author",
48567
+ "own_mailboxes",
48568
+ "selected"
48569
+ ],
48570
+ "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."
48571
+ },
48572
+ "scope_selection": {
48573
+ "type": "object",
48574
+ "properties": {
48575
+ "addresses": {
48576
+ "maxItems": 100,
48577
+ "type": "array",
48578
+ "items": {
48579
+ "type": "string",
48580
+ "minLength": 3,
48581
+ "maxLength": 320,
48582
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
48583
+ }
48584
+ },
48585
+ "domains": {
48586
+ "maxItems": 50,
48587
+ "type": "array",
48588
+ "items": {
48589
+ "type": "string",
48590
+ "minLength": 1,
48591
+ "maxLength": 253,
48592
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
48593
+ }
48594
+ }
48595
+ },
48596
+ "additionalProperties": false,
48597
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
48598
+ },
48113
48599
  "english": {
48114
48600
  "type": "string",
48115
48601
  "minLength": 1,
@@ -48595,6 +49081,42 @@ const openapiDocument = {
48595
49081
  "type": "string",
48596
49082
  "minLength": 1,
48597
49083
  "maxLength": 64
49084
+ },
49085
+ "evaluation_scope": {
49086
+ "type": "string",
49087
+ "enum": [
49088
+ "author",
49089
+ "own_mailboxes",
49090
+ "selected"
49091
+ ],
49092
+ "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."
49093
+ },
49094
+ "scope_selection": {
49095
+ "type": "object",
49096
+ "properties": {
49097
+ "addresses": {
49098
+ "maxItems": 100,
49099
+ "type": "array",
49100
+ "items": {
49101
+ "type": "string",
49102
+ "minLength": 3,
49103
+ "maxLength": 320,
49104
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
49105
+ }
49106
+ },
49107
+ "domains": {
49108
+ "maxItems": 50,
49109
+ "type": "array",
49110
+ "items": {
49111
+ "type": "string",
49112
+ "minLength": 1,
49113
+ "maxLength": 253,
49114
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
49115
+ }
49116
+ }
49117
+ },
49118
+ "additionalProperties": false,
49119
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
48598
49120
  }
48599
49121
  },
48600
49122
  "additionalProperties": false
@@ -48792,6 +49314,347 @@ const openapiDocument = {
48792
49314
  ]
48793
49315
  }
48794
49316
  },
49317
+ "ListEmailRuleResultsResult": {
49318
+ "type": "object",
49319
+ "properties": {
49320
+ "rules": {
49321
+ "type": "array",
49322
+ "items": {
49323
+ "type": "object",
49324
+ "properties": {
49325
+ "id": {
49326
+ "type": "string",
49327
+ "format": "uuid"
49328
+ },
49329
+ "name": { "type": "string" },
49330
+ "state": {
49331
+ "type": "string",
49332
+ "enum": [
49333
+ "draft",
49334
+ "observe",
49335
+ "active"
49336
+ ]
49337
+ },
49338
+ "decision": {
49339
+ "type": ["object", "null"],
49340
+ "properties": {
49341
+ "rule_version": {
49342
+ "type": "integer",
49343
+ "exclusiveMinimum": 0,
49344
+ "description": "The version of the rule that decided."
49345
+ },
49346
+ "matched": { "type": "boolean" },
49347
+ "parts": {
49348
+ "type": "array",
49349
+ "items": {
49350
+ "type": "object",
49351
+ "properties": {
49352
+ "index": {
49353
+ "type": "integer",
49354
+ "minimum": 0
49355
+ },
49356
+ "matched": { "type": "boolean" },
49357
+ "columns": {
49358
+ "type": "array",
49359
+ "items": { "type": "string" }
49360
+ }
49361
+ },
49362
+ "required": [
49363
+ "index",
49364
+ "matched",
49365
+ "columns"
49366
+ ]
49367
+ },
49368
+ "description": "How each top-level part of the rule decided. Empty when a fact the rule reads was missing, so it was not evaluated."
49369
+ },
49370
+ "question_answers": {
49371
+ "type": "array",
49372
+ "items": {
49373
+ "type": "object",
49374
+ "properties": {
49375
+ "question": { "type": "string" },
49376
+ "answer": { "type": ["boolean", "null"] },
49377
+ "probability": { "type": ["number", "null"] },
49378
+ "error_class": { "type": "string" }
49379
+ },
49380
+ "required": [
49381
+ "question",
49382
+ "answer",
49383
+ "probability"
49384
+ ]
49385
+ },
49386
+ "description": "The answers to the rule's own questions, asked only when its conditions matched."
49387
+ },
49388
+ "tree": {
49389
+ "type": ["object", "null"],
49390
+ "properties": {
49391
+ "version": {
49392
+ "type": "number",
49393
+ "const": 1
49394
+ },
49395
+ "vocabulary": {
49396
+ "type": "string",
49397
+ "description": "The column contract version the rule is written against, for example v1."
49398
+ },
49399
+ "when": {
49400
+ "type": "object",
49401
+ "propertyNames": { "type": "string" },
49402
+ "additionalProperties": {},
49403
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
49404
+ },
49405
+ "questions": {
49406
+ "type": "array",
49407
+ "items": { "type": "string" },
49408
+ "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."
49409
+ }
49410
+ },
49411
+ "required": [
49412
+ "version",
49413
+ "vocabulary",
49414
+ "when",
49415
+ "questions"
49416
+ ],
49417
+ "description": "The conditions and questions of the version that decided, so the result reads in that version's words."
49418
+ },
49419
+ "created_at": {
49420
+ "type": "string",
49421
+ "format": "date-time"
49422
+ }
49423
+ },
49424
+ "required": [
49425
+ "rule_version",
49426
+ "matched",
49427
+ "parts",
49428
+ "question_answers",
49429
+ "tree",
49430
+ "created_at"
49431
+ ],
49432
+ "description": "What the rule most recently decided about this email; null when it has not run on it."
49433
+ },
49434
+ "not_run_reason": {
49435
+ "type": ["string", "null"],
49436
+ "enum": [
49437
+ "draft",
49438
+ "created_after_email",
49439
+ null
49440
+ ],
49441
+ "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."
49442
+ }
49443
+ },
49444
+ "required": [
49445
+ "id",
49446
+ "name",
49447
+ "state",
49448
+ "decision",
49449
+ "not_run_reason"
49450
+ ]
49451
+ },
49452
+ "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."
49453
+ },
49454
+ "hidden": {
49455
+ "type": "integer",
49456
+ "minimum": 0,
49457
+ "description": "How many more rules the caller can see that are not listed."
49458
+ }
49459
+ },
49460
+ "required": ["rules", "hidden"]
49461
+ },
49462
+ "ListEmailRuleVersionsResult": {
49463
+ "type": "array",
49464
+ "items": {
49465
+ "type": "object",
49466
+ "properties": {
49467
+ "version": {
49468
+ "type": "integer",
49469
+ "exclusiveMinimum": 0
49470
+ },
49471
+ "english": { "type": "string" },
49472
+ "tree": {
49473
+ "type": "object",
49474
+ "properties": {
49475
+ "version": {
49476
+ "type": "number",
49477
+ "const": 1
49478
+ },
49479
+ "vocabulary": {
49480
+ "type": "string",
49481
+ "description": "The column contract version the rule is written against, for example v1."
49482
+ },
49483
+ "when": {
49484
+ "type": "object",
49485
+ "propertyNames": { "type": "string" },
49486
+ "additionalProperties": {},
49487
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
49488
+ },
49489
+ "questions": {
49490
+ "type": "array",
49491
+ "items": { "type": "string" },
49492
+ "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."
49493
+ }
49494
+ },
49495
+ "required": [
49496
+ "version",
49497
+ "vocabulary",
49498
+ "when",
49499
+ "questions"
49500
+ ]
49501
+ },
49502
+ "explanation": { "type": "string" },
49503
+ "assumptions": {
49504
+ "type": "array",
49505
+ "items": { "type": "string" }
49506
+ },
49507
+ "warnings": {
49508
+ "type": "array",
49509
+ "items": { "type": "string" }
49510
+ },
49511
+ "compiled_by": {
49512
+ "type": "string",
49513
+ "description": "Who produced the tree, as the caller stated it when saving (not verified), for example rule-compiler-v1 or member."
49514
+ },
49515
+ "vocabulary": { "type": "string" },
49516
+ "created_at": {
49517
+ "type": "string",
49518
+ "format": "date-time"
49519
+ },
49520
+ "sql": {
49521
+ "description": "Present only when include_sql was true.",
49522
+ "type": "object",
49523
+ "properties": {
49524
+ "expression": {
49525
+ "type": "string",
49526
+ "description": "A parameterized boolean expression over one row per email."
49527
+ },
49528
+ "params": {
49529
+ "type": "array",
49530
+ "items": {},
49531
+ "description": "The values bound to $1, $2 and so on."
49532
+ }
49533
+ },
49534
+ "required": ["expression", "params"]
49535
+ }
49536
+ },
49537
+ "required": [
49538
+ "version",
49539
+ "english",
49540
+ "tree",
49541
+ "explanation",
49542
+ "assumptions",
49543
+ "warnings",
49544
+ "compiled_by",
49545
+ "vocabulary",
49546
+ "created_at"
49547
+ ]
49548
+ }
49549
+ },
49550
+ "GetEmailRuleScopeResult": {
49551
+ "type": "object",
49552
+ "properties": {
49553
+ "evaluation_scope": {
49554
+ "type": "string",
49555
+ "enum": [
49556
+ "author",
49557
+ "own_mailboxes",
49558
+ "selected"
49559
+ ],
49560
+ "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."
49561
+ },
49562
+ "scope_selection": {
49563
+ "type": ["object", "null"],
49564
+ "properties": {
49565
+ "addresses": {
49566
+ "type": "array",
49567
+ "items": { "type": "string" }
49568
+ },
49569
+ "domains": {
49570
+ "type": "array",
49571
+ "items": { "type": "string" }
49572
+ }
49573
+ },
49574
+ "required": ["addresses", "domains"]
49575
+ },
49576
+ "locked": {
49577
+ "type": "boolean",
49578
+ "description": "Shared, so its scope cannot change."
49579
+ },
49580
+ "author_reach": {
49581
+ "type": ["string", "null"],
49582
+ "enum": [
49583
+ "organization",
49584
+ "limited",
49585
+ "own",
49586
+ null
49587
+ ],
49588
+ "description": "How much its author can read now; null when it cannot run as them."
49589
+ },
49590
+ "no_longer_readable": {
49591
+ "type": "object",
49592
+ "properties": {
49593
+ "addresses": {
49594
+ "type": "array",
49595
+ "items": { "type": "string" }
49596
+ },
49597
+ "domains": {
49598
+ "type": "array",
49599
+ "items": { "type": "string" }
49600
+ }
49601
+ },
49602
+ "required": ["addresses", "domains"],
49603
+ "description": "What scope_selection names that its author no longer reads in full."
49604
+ },
49605
+ "stopped_reason": {
49606
+ "description": "Why it cannot run now (a reason as on POST /email-rules/test), or null.",
49607
+ "type": ["string", "null"]
49608
+ }
49609
+ },
49610
+ "required": [
49611
+ "evaluation_scope",
49612
+ "scope_selection",
49613
+ "locked",
49614
+ "author_reach",
49615
+ "no_longer_readable",
49616
+ "stopped_reason"
49617
+ ]
49618
+ },
49619
+ "ListEmailRuleScopeOptionsResult": {
49620
+ "type": "object",
49621
+ "properties": {
49622
+ "reach": {
49623
+ "type": "string",
49624
+ "enum": [
49625
+ "organization",
49626
+ "limited",
49627
+ "own"
49628
+ ],
49629
+ "description": "organization: all of the organization's mail. own: only your own. limited: some inboxes or domains."
49630
+ },
49631
+ "own_mail": {
49632
+ "type": "boolean",
49633
+ "description": "Whether a rule over only your own mail would run for you."
49634
+ },
49635
+ "addresses": {
49636
+ "type": "array",
49637
+ "items": { "type": "string" },
49638
+ "description": "Inboxes you can read, matching q."
49639
+ },
49640
+ "domains": {
49641
+ "type": "array",
49642
+ "items": { "type": "string" },
49643
+ "description": "Domains you can read, matching q."
49644
+ },
49645
+ "more": {
49646
+ "type": "boolean",
49647
+ "description": "More match than are listed."
49648
+ }
49649
+ },
49650
+ "required": [
49651
+ "reach",
49652
+ "own_mail",
49653
+ "addresses",
49654
+ "domains",
49655
+ "more"
49656
+ ]
49657
+ },
48795
49658
  "CreateEmailRuleRequestRequest": {
48796
49659
  "type": "object",
48797
49660
  "properties": {
@@ -67819,7 +68682,7 @@ const operationManifest = [
67819
68682
  "binaryResponse": false,
67820
68683
  "bodyRequired": true,
67821
68684
  "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.",
68685
+ "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 takes the same step-up; the Primitive app's token is refused, even with one. 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.",
67823
68686
  "hasJsonBody": true,
67824
68687
  "method": "POST",
67825
68688
  "operationId": "createKey",
@@ -67842,7 +68705,7 @@ const operationManifest = [
67842
68705
  "type": "string"
67843
68706
  },
67844
68707
  {
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`).",
68708
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
67846
68709
  "enum": null,
67847
68710
  "name": "Primitive-Step-Up",
67848
68711
  "required": false,
@@ -69246,7 +70109,7 @@ const operationManifest = [
69246
70109
  "binaryResponse": false,
69247
70110
  "bodyRequired": true,
69248
70111
  "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.",
70112
+ "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 takes the same step-up; 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.\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.",
69250
70113
  "hasJsonBody": true,
69251
70114
  "method": "POST",
69252
70115
  "operationId": "rotateKey",
@@ -69260,7 +70123,7 @@ const operationManifest = [
69260
70123
  }],
69261
70124
  "queryParams": [],
69262
70125
  "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`).",
70126
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
69264
70127
  "enum": null,
69265
70128
  "name": "Primitive-Step-Up",
69266
70129
  "required": false,
@@ -69406,7 +70269,7 @@ const operationManifest = [
69406
70269
  "binaryResponse": false,
69407
70270
  "bodyRequired": true,
69408
70271
  "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.",
70272
+ "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 takes the same step-up; 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.\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.",
69410
70273
  "hasJsonBody": true,
69411
70274
  "method": "PATCH",
69412
70275
  "operationId": "updateKey",
@@ -69420,7 +70283,7 @@ const operationManifest = [
69420
70283
  }],
69421
70284
  "queryParams": [],
69422
70285
  "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`).",
70286
+ "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 Primitive CLI uses one like any app while the organization's keys API is on, and is refused the same way while it is off.",
69424
70287
  "enum": null,
69425
70288
  "name": "Primitive-Step-Up",
69426
70289
  "required": false,
@@ -72090,7 +72953,7 @@ const operationManifest = [
72090
72953
  "binaryResponse": false,
72091
72954
  "bodyRequired": true,
72092
72955
  "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.",
72956
+ "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. evaluation_scope (author when omitted) chooses whose mail it runs over; scope_selection may name only what you can 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.",
72094
72957
  "hasJsonBody": true,
72095
72958
  "method": "POST",
72096
72959
  "operationId": "createEmailRule",
@@ -72119,6 +72982,42 @@ const operationManifest = [
72119
72982
  "type": "string",
72120
72983
  "enum": ["draft", "observe"]
72121
72984
  },
72985
+ "evaluation_scope": {
72986
+ "type": "string",
72987
+ "enum": [
72988
+ "author",
72989
+ "own_mailboxes",
72990
+ "selected"
72991
+ ],
72992
+ "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."
72993
+ },
72994
+ "scope_selection": {
72995
+ "type": "object",
72996
+ "properties": {
72997
+ "addresses": {
72998
+ "maxItems": 100,
72999
+ "type": "array",
73000
+ "items": {
73001
+ "type": "string",
73002
+ "minLength": 3,
73003
+ "maxLength": 320,
73004
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
73005
+ }
73006
+ },
73007
+ "domains": {
73008
+ "maxItems": 50,
73009
+ "type": "array",
73010
+ "items": {
73011
+ "type": "string",
73012
+ "minLength": 1,
73013
+ "maxLength": 253,
73014
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
73015
+ }
73016
+ }
73017
+ },
73018
+ "additionalProperties": false,
73019
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
73020
+ },
72122
73021
  "english": {
72123
73022
  "type": "string",
72124
73023
  "minLength": 1,
@@ -73231,6 +74130,100 @@ const operationManifest = [
73231
74130
  "tag": "Email Rules",
73232
74131
  "tagCommand": "email-rules"
73233
74132
  },
74133
+ {
74134
+ "binaryResponse": false,
74135
+ "bodyRequired": false,
74136
+ "command": "get-email-rule-scope",
74137
+ "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.",
74138
+ "hasJsonBody": false,
74139
+ "method": "GET",
74140
+ "operationId": "getEmailRuleScope",
74141
+ "path": "/email-rules/{id}/scope",
74142
+ "pathParams": [{
74143
+ "description": "The rule id.",
74144
+ "enum": null,
74145
+ "name": "id",
74146
+ "required": true,
74147
+ "type": "string"
74148
+ }],
74149
+ "queryParams": [],
74150
+ "headerParams": [],
74151
+ "permissions": ["mail:read"],
74152
+ "requestSchema": null,
74153
+ "responseSchema": {
74154
+ "type": "object",
74155
+ "properties": {
74156
+ "evaluation_scope": {
74157
+ "type": "string",
74158
+ "enum": [
74159
+ "author",
74160
+ "own_mailboxes",
74161
+ "selected"
74162
+ ],
74163
+ "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."
74164
+ },
74165
+ "scope_selection": {
74166
+ "type": ["object", "null"],
74167
+ "properties": {
74168
+ "addresses": {
74169
+ "type": "array",
74170
+ "items": { "type": "string" }
74171
+ },
74172
+ "domains": {
74173
+ "type": "array",
74174
+ "items": { "type": "string" }
74175
+ }
74176
+ },
74177
+ "required": ["addresses", "domains"]
74178
+ },
74179
+ "locked": {
74180
+ "type": "boolean",
74181
+ "description": "Shared, so its scope cannot change."
74182
+ },
74183
+ "author_reach": {
74184
+ "type": ["string", "null"],
74185
+ "enum": [
74186
+ "organization",
74187
+ "limited",
74188
+ "own",
74189
+ null
74190
+ ],
74191
+ "description": "How much its author can read now; null when it cannot run as them."
74192
+ },
74193
+ "no_longer_readable": {
74194
+ "type": "object",
74195
+ "properties": {
74196
+ "addresses": {
74197
+ "type": "array",
74198
+ "items": { "type": "string" }
74199
+ },
74200
+ "domains": {
74201
+ "type": "array",
74202
+ "items": { "type": "string" }
74203
+ }
74204
+ },
74205
+ "required": ["addresses", "domains"],
74206
+ "description": "What scope_selection names that its author no longer reads in full."
74207
+ },
74208
+ "stopped_reason": {
74209
+ "description": "Why it cannot run now (a reason as on POST /email-rules/test), or null.",
74210
+ "type": ["string", "null"]
74211
+ }
74212
+ },
74213
+ "required": [
74214
+ "evaluation_scope",
74215
+ "scope_selection",
74216
+ "locked",
74217
+ "author_reach",
74218
+ "no_longer_readable",
74219
+ "stopped_reason"
74220
+ ]
74221
+ },
74222
+ "sdkName": "getEmailRuleScope",
74223
+ "summary": "Read what a rule covers",
74224
+ "tag": "Email Rules",
74225
+ "tagCommand": "email-rules"
74226
+ },
73234
74227
  {
73235
74228
  "binaryResponse": false,
73236
74229
  "bodyRequired": false,
@@ -73742,6 +74735,240 @@ const operationManifest = [
73742
74735
  "tag": "Email Rules",
73743
74736
  "tagCommand": "email-rules"
73744
74737
  },
74738
+ {
74739
+ "binaryResponse": false,
74740
+ "bodyRequired": false,
74741
+ "command": "list-email-rule-results",
74742
+ "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. 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.",
74743
+ "hasJsonBody": false,
74744
+ "method": "GET",
74745
+ "operationId": "listEmailRuleResults",
74746
+ "path": "/emails/{id}/rule-results",
74747
+ "pathParams": [{
74748
+ "description": "The email id.",
74749
+ "enum": null,
74750
+ "name": "id",
74751
+ "required": true,
74752
+ "type": "string"
74753
+ }],
74754
+ "queryParams": [],
74755
+ "headerParams": [],
74756
+ "permissions": ["mail:read"],
74757
+ "requestSchema": null,
74758
+ "responseSchema": {
74759
+ "type": "object",
74760
+ "properties": {
74761
+ "rules": {
74762
+ "type": "array",
74763
+ "items": {
74764
+ "type": "object",
74765
+ "properties": {
74766
+ "id": {
74767
+ "type": "string",
74768
+ "format": "uuid"
74769
+ },
74770
+ "name": { "type": "string" },
74771
+ "state": {
74772
+ "type": "string",
74773
+ "enum": [
74774
+ "draft",
74775
+ "observe",
74776
+ "active"
74777
+ ]
74778
+ },
74779
+ "decision": {
74780
+ "type": ["object", "null"],
74781
+ "properties": {
74782
+ "rule_version": {
74783
+ "type": "integer",
74784
+ "exclusiveMinimum": 0,
74785
+ "description": "The version of the rule that decided."
74786
+ },
74787
+ "matched": { "type": "boolean" },
74788
+ "parts": {
74789
+ "type": "array",
74790
+ "items": {
74791
+ "type": "object",
74792
+ "properties": {
74793
+ "index": {
74794
+ "type": "integer",
74795
+ "minimum": 0
74796
+ },
74797
+ "matched": { "type": "boolean" },
74798
+ "columns": {
74799
+ "type": "array",
74800
+ "items": { "type": "string" }
74801
+ }
74802
+ },
74803
+ "required": [
74804
+ "index",
74805
+ "matched",
74806
+ "columns"
74807
+ ]
74808
+ },
74809
+ "description": "How each top-level part of the rule decided. Empty when a fact the rule reads was missing, so it was not evaluated."
74810
+ },
74811
+ "question_answers": {
74812
+ "type": "array",
74813
+ "items": {
74814
+ "type": "object",
74815
+ "properties": {
74816
+ "question": { "type": "string" },
74817
+ "answer": { "type": ["boolean", "null"] },
74818
+ "probability": { "type": ["number", "null"] },
74819
+ "error_class": { "type": "string" }
74820
+ },
74821
+ "required": [
74822
+ "question",
74823
+ "answer",
74824
+ "probability"
74825
+ ]
74826
+ },
74827
+ "description": "The answers to the rule's own questions, asked only when its conditions matched."
74828
+ },
74829
+ "tree": {
74830
+ "type": ["object", "null"],
74831
+ "properties": {
74832
+ "version": {
74833
+ "type": "number",
74834
+ "const": 1
74835
+ },
74836
+ "vocabulary": {
74837
+ "type": "string",
74838
+ "description": "The column contract version the rule is written against, for example v1."
74839
+ },
74840
+ "when": {
74841
+ "type": "object",
74842
+ "propertyNames": { "type": "string" },
74843
+ "additionalProperties": {},
74844
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
74845
+ },
74846
+ "questions": {
74847
+ "type": "array",
74848
+ "items": { "type": "string" },
74849
+ "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."
74850
+ }
74851
+ },
74852
+ "required": [
74853
+ "version",
74854
+ "vocabulary",
74855
+ "when",
74856
+ "questions"
74857
+ ],
74858
+ "description": "The conditions and questions of the version that decided, so the result reads in that version's words."
74859
+ },
74860
+ "created_at": {
74861
+ "type": "string",
74862
+ "format": "date-time"
74863
+ }
74864
+ },
74865
+ "required": [
74866
+ "rule_version",
74867
+ "matched",
74868
+ "parts",
74869
+ "question_answers",
74870
+ "tree",
74871
+ "created_at"
74872
+ ],
74873
+ "description": "What the rule most recently decided about this email; null when it has not run on it."
74874
+ },
74875
+ "not_run_reason": {
74876
+ "type": ["string", "null"],
74877
+ "enum": [
74878
+ "draft",
74879
+ "created_after_email",
74880
+ null
74881
+ ],
74882
+ "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."
74883
+ }
74884
+ },
74885
+ "required": [
74886
+ "id",
74887
+ "name",
74888
+ "state",
74889
+ "decision",
74890
+ "not_run_reason"
74891
+ ]
74892
+ },
74893
+ "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."
74894
+ },
74895
+ "hidden": {
74896
+ "type": "integer",
74897
+ "minimum": 0,
74898
+ "description": "How many more rules the caller can see that are not listed."
74899
+ }
74900
+ },
74901
+ "required": ["rules", "hidden"]
74902
+ },
74903
+ "sdkName": "listEmailRuleResults",
74904
+ "summary": "List every rule's result on an email",
74905
+ "tag": "Email Rules",
74906
+ "tagCommand": "email-rules"
74907
+ },
74908
+ {
74909
+ "binaryResponse": false,
74910
+ "bodyRequired": false,
74911
+ "command": "list-email-rule-scope-options",
74912
+ "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. 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.",
74913
+ "hasJsonBody": false,
74914
+ "method": "GET",
74915
+ "operationId": "listEmailRuleScopeOptions",
74916
+ "path": "/email-rule-scope-options",
74917
+ "pathParams": [],
74918
+ "queryParams": [{
74919
+ "description": "Only inboxes and domains containing this text.",
74920
+ "enum": null,
74921
+ "name": "q",
74922
+ "required": false,
74923
+ "type": "string"
74924
+ }],
74925
+ "headerParams": [],
74926
+ "permissions": ["mail:read"],
74927
+ "requestSchema": null,
74928
+ "responseSchema": {
74929
+ "type": "object",
74930
+ "properties": {
74931
+ "reach": {
74932
+ "type": "string",
74933
+ "enum": [
74934
+ "organization",
74935
+ "limited",
74936
+ "own"
74937
+ ],
74938
+ "description": "organization: all of the organization's mail. own: only your own. limited: some inboxes or domains."
74939
+ },
74940
+ "own_mail": {
74941
+ "type": "boolean",
74942
+ "description": "Whether a rule over only your own mail would run for you."
74943
+ },
74944
+ "addresses": {
74945
+ "type": "array",
74946
+ "items": { "type": "string" },
74947
+ "description": "Inboxes you can read, matching q."
74948
+ },
74949
+ "domains": {
74950
+ "type": "array",
74951
+ "items": { "type": "string" },
74952
+ "description": "Domains you can read, matching q."
74953
+ },
74954
+ "more": {
74955
+ "type": "boolean",
74956
+ "description": "More match than are listed."
74957
+ }
74958
+ },
74959
+ "required": [
74960
+ "reach",
74961
+ "own_mail",
74962
+ "addresses",
74963
+ "domains",
74964
+ "more"
74965
+ ]
74966
+ },
74967
+ "sdkName": "listEmailRuleScopeOptions",
74968
+ "summary": "List what a rule can cover",
74969
+ "tag": "Email Rules",
74970
+ "tagCommand": "email-rules"
74971
+ },
73745
74972
  {
73746
74973
  "binaryResponse": false,
73747
74974
  "bodyRequired": false,
@@ -74266,6 +75493,125 @@ const operationManifest = [
74266
75493
  "tag": "Email Rules",
74267
75494
  "tagCommand": "email-rules"
74268
75495
  },
75496
+ {
75497
+ "binaryResponse": false,
75498
+ "bodyRequired": false,
75499
+ "command": "list-email-rule-versions",
75500
+ "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.",
75501
+ "hasJsonBody": false,
75502
+ "method": "GET",
75503
+ "operationId": "listEmailRuleVersions",
75504
+ "path": "/email-rules/{id}/versions",
75505
+ "pathParams": [{
75506
+ "description": "The rule id.",
75507
+ "enum": null,
75508
+ "name": "id",
75509
+ "required": true,
75510
+ "type": "string"
75511
+ }],
75512
+ "queryParams": [{
75513
+ "description": "The version numbers, comma-separated, at most 100.",
75514
+ "enum": null,
75515
+ "name": "versions",
75516
+ "required": true,
75517
+ "type": "string"
75518
+ }],
75519
+ "headerParams": [],
75520
+ "permissions": ["mail:read"],
75521
+ "requestSchema": null,
75522
+ "responseSchema": {
75523
+ "type": "array",
75524
+ "items": {
75525
+ "type": "object",
75526
+ "properties": {
75527
+ "version": {
75528
+ "type": "integer",
75529
+ "exclusiveMinimum": 0
75530
+ },
75531
+ "english": { "type": "string" },
75532
+ "tree": {
75533
+ "type": "object",
75534
+ "properties": {
75535
+ "version": {
75536
+ "type": "number",
75537
+ "const": 1
75538
+ },
75539
+ "vocabulary": {
75540
+ "type": "string",
75541
+ "description": "The column contract version the rule is written against, for example v1."
75542
+ },
75543
+ "when": {
75544
+ "type": "object",
75545
+ "propertyNames": { "type": "string" },
75546
+ "additionalProperties": {},
75547
+ "description": "The rule's conditions: a condition ({column, op, value?, min_confidence?}) or a group ({all: [...]}, {any: [...]} or {not: ...})."
75548
+ },
75549
+ "questions": {
75550
+ "type": "array",
75551
+ "items": { "type": "string" },
75552
+ "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."
75553
+ }
75554
+ },
75555
+ "required": [
75556
+ "version",
75557
+ "vocabulary",
75558
+ "when",
75559
+ "questions"
75560
+ ]
75561
+ },
75562
+ "explanation": { "type": "string" },
75563
+ "assumptions": {
75564
+ "type": "array",
75565
+ "items": { "type": "string" }
75566
+ },
75567
+ "warnings": {
75568
+ "type": "array",
75569
+ "items": { "type": "string" }
75570
+ },
75571
+ "compiled_by": {
75572
+ "type": "string",
75573
+ "description": "Who produced the tree, as the caller stated it when saving (not verified), for example rule-compiler-v1 or member."
75574
+ },
75575
+ "vocabulary": { "type": "string" },
75576
+ "created_at": {
75577
+ "type": "string",
75578
+ "format": "date-time"
75579
+ },
75580
+ "sql": {
75581
+ "description": "Present only when include_sql was true.",
75582
+ "type": "object",
75583
+ "properties": {
75584
+ "expression": {
75585
+ "type": "string",
75586
+ "description": "A parameterized boolean expression over one row per email."
75587
+ },
75588
+ "params": {
75589
+ "type": "array",
75590
+ "items": {},
75591
+ "description": "The values bound to $1, $2 and so on."
75592
+ }
75593
+ },
75594
+ "required": ["expression", "params"]
75595
+ }
75596
+ },
75597
+ "required": [
75598
+ "version",
75599
+ "english",
75600
+ "tree",
75601
+ "explanation",
75602
+ "assumptions",
75603
+ "warnings",
75604
+ "compiled_by",
75605
+ "vocabulary",
75606
+ "created_at"
75607
+ ]
75608
+ }
75609
+ },
75610
+ "sdkName": "listEmailRuleVersions",
75611
+ "summary": "Read versions of a rule",
75612
+ "tag": "Email Rules",
75613
+ "tagCommand": "email-rules"
75614
+ },
74269
75615
  {
74270
75616
  "binaryResponse": false,
74271
75617
  "bodyRequired": false,
@@ -74572,7 +75918,7 @@ const operationManifest = [
74572
75918
  "binaryResponse": false,
74573
75919
  "bodyRequired": true,
74574
75920
  "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.",
75921
+ "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. 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
75922
  "hasJsonBody": true,
74577
75923
  "method": "POST",
74578
75924
  "operationId": "testEmailRule",
@@ -74624,8 +75970,39 @@ const operationManifest = [
74624
75970
  },
74625
75971
  "evaluation_scope": {
74626
75972
  "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."
75973
+ "enum": [
75974
+ "author",
75975
+ "own_mailboxes",
75976
+ "selected"
75977
+ ],
75978
+ "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."
75979
+ },
75980
+ "scope_selection": {
75981
+ "type": "object",
75982
+ "properties": {
75983
+ "addresses": {
75984
+ "maxItems": 100,
75985
+ "type": "array",
75986
+ "items": {
75987
+ "type": "string",
75988
+ "minLength": 3,
75989
+ "maxLength": 320,
75990
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
75991
+ }
75992
+ },
75993
+ "domains": {
75994
+ "maxItems": 50,
75995
+ "type": "array",
75996
+ "items": {
75997
+ "type": "string",
75998
+ "minLength": 1,
75999
+ "maxLength": 253,
76000
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
76001
+ }
76002
+ }
76003
+ },
76004
+ "additionalProperties": false,
76005
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
74629
76006
  },
74630
76007
  "rule_id": {
74631
76008
  "description": "A saved rule you can see. Its current version is tested, with its author's access.",
@@ -74920,8 +76297,39 @@ const operationManifest = [
74920
76297
  },
74921
76298
  "evaluation_scope": {
74922
76299
  "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."
76300
+ "enum": [
76301
+ "author",
76302
+ "own_mailboxes",
76303
+ "selected"
76304
+ ],
76305
+ "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."
76306
+ },
76307
+ "scope_selection": {
76308
+ "type": "object",
76309
+ "properties": {
76310
+ "addresses": {
76311
+ "maxItems": 100,
76312
+ "type": "array",
76313
+ "items": {
76314
+ "type": "string",
76315
+ "minLength": 3,
76316
+ "maxLength": 320,
76317
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
76318
+ }
76319
+ },
76320
+ "domains": {
76321
+ "maxItems": 50,
76322
+ "type": "array",
76323
+ "items": {
76324
+ "type": "string",
76325
+ "minLength": 1,
76326
+ "maxLength": 253,
76327
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
76328
+ }
76329
+ }
76330
+ },
76331
+ "additionalProperties": false,
76332
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
74925
76333
  },
74926
76334
  "rule_id": {
74927
76335
  "description": "A saved rule you can see. Its current version is tested.",
@@ -75151,7 +76559,7 @@ const operationManifest = [
75151
76559
  "binaryResponse": false,
75152
76560
  "bodyRequired": true,
75153
76561
  "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.",
76562
+ "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. 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
76563
  "hasJsonBody": true,
75156
76564
  "method": "PATCH",
75157
76565
  "operationId": "updateEmailRule",
@@ -75253,6 +76661,42 @@ const operationManifest = [
75253
76661
  "type": "string",
75254
76662
  "minLength": 1,
75255
76663
  "maxLength": 64
76664
+ },
76665
+ "evaluation_scope": {
76666
+ "type": "string",
76667
+ "enum": [
76668
+ "author",
76669
+ "own_mailboxes",
76670
+ "selected"
76671
+ ],
76672
+ "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."
76673
+ },
76674
+ "scope_selection": {
76675
+ "type": "object",
76676
+ "properties": {
76677
+ "addresses": {
76678
+ "maxItems": 100,
76679
+ "type": "array",
76680
+ "items": {
76681
+ "type": "string",
76682
+ "minLength": 3,
76683
+ "maxLength": 320,
76684
+ "pattern": "^[^\\s@]+@[^\\s@]+$"
76685
+ }
76686
+ },
76687
+ "domains": {
76688
+ "maxItems": 50,
76689
+ "type": "array",
76690
+ "items": {
76691
+ "type": "string",
76692
+ "minLength": 1,
76693
+ "maxLength": 253,
76694
+ "pattern": "^[^\\s@]+\\.[^\\s@]+$"
76695
+ }
76696
+ }
76697
+ },
76698
+ "additionalProperties": false,
76699
+ "description": "Inboxes, and domains (every address at them the author can read), for a selected rule; each one you can read."
75256
76700
  }
75257
76701
  },
75258
76702
  "additionalProperties": false
@@ -102204,14 +103648,14 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
102204
103648
  }
102205
103649
  }
102206
103650
  // src/oclif/cli-operations.ts
102207
- const DASHBOARD_ONLY_OPERATION_IDS = /* @__PURE__ */ new Set([
103651
+ const KEY_WRITE_OPERATION_IDS = /* @__PURE__ */ new Set([
102208
103652
  "credentials:create-key",
102209
103653
  "credentials:rotate-key",
102210
103654
  "credentials:update-key",
102211
103655
  "credentials:revoke-credential"
102212
103656
  ]);
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}`));
103657
+ /** The operations the CLI generates commands for: the manifest without the key writes. */
103658
+ const cliOperationManifest = operationManifest.filter((operation) => !KEY_WRITE_OPERATION_IDS.has(`${operation.tagCommand}:${operation.command}`));
102215
103659
  // src/oclif/agent-mail-check.ts
102216
103660
  /**
102217
103661
  * One check for mail that arrived since this profile's previous check. It is
@@ -108882,7 +110326,7 @@ async function checkAccount(client) {
108882
110326
  outcome: {
108883
110327
  status: "fail",
108884
110328
  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."
110329
+ 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
110330
  },
108887
110331
  account: null
108888
110332
  };
@@ -111816,8 +113260,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
111816
113260
  name: "Primitive Team",
111817
113261
  url: "https://primitive.dev"
111818
113262
  };
111819
- const SDK_VERSION_RANGE = "^3.0.0-preview.1";
111820
- const CLI_VERSION_RANGE = "^3.0.0-preview.1";
113263
+ const SDK_VERSION_RANGE = "^3.0.0-preview.2";
113264
+ const CLI_VERSION_RANGE = "^3.0.0-preview.2";
111821
113265
  const ESBUILD_VERSION_RANGE = "^0.27.0";
111822
113266
  function renderHandler() {
111823
113267
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -114577,7 +116021,10 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
114577
116021
  }
114578
116022
  };
114579
116023
  // 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.";
116024
+ const STORE_SECRET_NOTICE = "Store this key now. It is shown only once, and the CLI does not save it.";
116025
+ 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.";
116026
+ const STEP_UP_HEADER$1 = "Primitive-Step-Up";
116027
+ const LIST_PAGE_LIMIT = 50;
114581
116028
  const API_KEY_FLAG$1 = Flags.string({
114582
116029
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved login credentials).",
114583
116030
  env: "PRIMITIVE_API_KEY",
@@ -114588,68 +116035,695 @@ const API_BASE_URL_FLAG$2 = Flags.string({
114588
116035
  env: "PRIMITIVE_API_BASE_URL",
114589
116036
  hidden: true
114590
116037
  });
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");
116038
+ const APPROVAL_FLAGS = {
116039
+ "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." }),
116040
+ browser: Flags.boolean({
116041
+ allowNo: true,
116042
+ default: true,
116043
+ description: "Open the approval page in your browser. Use --no-browser to only print its link."
116044
+ })
116045
+ };
116046
+ function unwrap(data) {
116047
+ return data?.data ?? data;
116048
+ }
116049
+ async function call(request) {
116050
+ try {
116051
+ const result = await request;
116052
+ if (result.error !== void 0 || !result.response?.ok) return {
116053
+ ok: false,
116054
+ payload: result.error === void 0 || result.error === null || typeof result.error === "string" ? {
116055
+ code: "http_error",
116056
+ message: typeof result.error === "string" && result.error.trim() || `HTTP ${result.response?.status ?? ""}`.trim()
116057
+ } : extractErrorPayload(result.error)
116058
+ };
116059
+ return {
116060
+ ok: true,
116061
+ data: unwrap(result.data)
116062
+ };
116063
+ } catch (error) {
116064
+ return {
116065
+ ok: false,
116066
+ payload: extractErrorPayload(error)
116067
+ };
114598
116068
  }
114599
- surfaceUnauthorizedHint({
114600
- ...context,
114601
- payload
116069
+ }
116070
+ /** The approval link of a `step_up_required` refusal, if that is what it is. */
116071
+ function stepUpUrlOf$1(payload) {
116072
+ if (extractErrorCode(payload) !== "step_up_required") return void 0;
116073
+ if (!payload || typeof payload !== "object") return void 0;
116074
+ const details = payload.details;
116075
+ if (!details || typeof details !== "object") return void 0;
116076
+ const url = details.step_up_url;
116077
+ return typeof url === "string" && url.length > 0 ? url : void 0;
116078
+ }
116079
+ /**
116080
+ * Send a request, and when the server asks the person to approve it, show
116081
+ * the approval link, open it where possible, wait for the token the page
116082
+ * shows, and send the request once more with it. Without a terminal to wait
116083
+ * in, the refusal is returned with its link, to rerun with --step-up-token.
116084
+ */
116085
+ async function withApproval(attempt, io) {
116086
+ const first = await attempt(io.stepUpToken);
116087
+ if (first.ok) return first;
116088
+ const stepUpUrl = stepUpUrlOf$1(first.payload);
116089
+ if (!stepUpUrl || io.stepUpToken || !io.interactive) return {
116090
+ ...first,
116091
+ ...stepUpUrl ? { stepUpUrl } : {}
116092
+ };
116093
+ io.write(`Approve this request in your browser: ${stepUpUrl}\n`);
116094
+ if (io.openBrowser) io.open(stepUpUrl);
116095
+ io.write("After you confirm, the page shows a one-time token. Paste it here to finish.\n");
116096
+ const token = (await io.prompt("Token: ")).trim();
116097
+ if (!token) return {
116098
+ ...first,
116099
+ stepUpUrl
116100
+ };
116101
+ return attempt(token);
116102
+ }
116103
+ async function promptLine$2(question) {
116104
+ const rl = createInterface({
116105
+ input: process.stdin,
116106
+ output: process.stderr
114602
116107
  });
114603
- process.exitCode = 1;
116108
+ try {
116109
+ return await rl.question(question);
116110
+ } finally {
116111
+ rl.close();
116112
+ }
114604
116113
  }
114605
- function unwrap(data) {
114606
- return data?.data ?? null;
116114
+ function approvalIo(flags) {
116115
+ return {
116116
+ stepUpToken: flags["step-up-token"],
116117
+ interactive: !flags.json && Boolean(process.stdin.isTTY && process.stderr.isTTY),
116118
+ openBrowser: flags.browser,
116119
+ open: openBrowser,
116120
+ prompt: promptLine$2,
116121
+ write: (text) => process.stderr.write(text)
116122
+ };
116123
+ }
116124
+ /** Comma-separated and repeated values, trimmed, without duplicates. */
116125
+ function listFlag(values) {
116126
+ const out = [];
116127
+ for (const value of values ?? []) for (const part of value.split(",")) {
116128
+ const item = part.trim();
116129
+ if (item && !out.includes(item)) out.push(item);
116130
+ }
116131
+ return out;
116132
+ }
116133
+ function stringList(value) {
116134
+ return Array.isArray(value) && value.every((v) => typeof v === "string") ? [...value] : null;
116135
+ }
116136
+ /** Which mail a key reaches, in words. */
116137
+ function describeMailScope(grant) {
116138
+ if (grant.template === "super_admin") return "every mailbox";
116139
+ if (grant.template === "connected_agent") return "its agent's address";
116140
+ const scope = grant.mail_scope;
116141
+ if (scope === "org") return "every mailbox";
116142
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return "unknown";
116143
+ const record = scope;
116144
+ const parts = [];
116145
+ const addresses = stringList(record.addresses) ?? [];
116146
+ const domains = stringList(record.domains) ?? [];
116147
+ const rules = stringList(record.rules) ?? [];
116148
+ if (addresses.length > 0) parts.push(`addresses ${addresses.join(", ")}`);
116149
+ if (domains.length > 0) parts.push(`domains ${domains.join(", ")}`);
116150
+ if (rules.length > 0) parts.push(`rules ${rules.join(", ")}`);
116151
+ return parts.length > 0 ? parts.join("; ") : "no mailbox";
116152
+ }
116153
+ /** What a key may do, in words. */
116154
+ function describePermissions(grant) {
116155
+ if (grant.template === "super_admin") return "super admin (every permission)";
116156
+ if (grant.template) return grant.template;
116157
+ const permissions = grant.permissions ?? [];
116158
+ return permissions.length > 0 ? permissions.join(", ") : "none";
114607
116159
  }
114608
116160
  function formatKeyRows(rows) {
114609
116161
  if (rows.length === 0) return "No API keys.";
114610
116162
  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}`;
116163
+ const suspended = row.suspended ? " [suspended]" : "";
116164
+ 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
116165
  }).join("\n");
114615
116166
  }
116167
+ function formatKeyDetail(key) {
116168
+ const lines = [
116169
+ `Name: ${key.name ?? "(unnamed)"}`,
116170
+ `Id: ${key.id}`,
116171
+ `Prefix: ${key.key_prefix}`,
116172
+ `Kind: ${key.kind}`,
116173
+ `Mail: ${describeMailScope(key.grant)}`,
116174
+ `Created: ${key.created_at ?? "unknown"}`,
116175
+ `Expires: ${key.expires_at ?? "never"}`,
116176
+ `Last used: ${key.last_used_at ?? "never"}`,
116177
+ `Suspended: ${key.suspended ? "yes" : "no"}`
116178
+ ];
116179
+ if (key.agent_address) lines.push(`Agent: ${key.agent_address}`);
116180
+ if (key.grant.template) lines.push(`Permissions: ${describePermissions(key.grant)}`);
116181
+ else {
116182
+ lines.push("Permissions:");
116183
+ for (const permission of key.grant.permissions ?? []) lines.push(` ${permission}`);
116184
+ }
116185
+ return lines.join("\n");
116186
+ }
116187
+ const EVERY_MAILBOX_PRESETS = /* @__PURE__ */ new Set([
116188
+ "super_admin",
116189
+ "read_only",
116190
+ "send_only"
116191
+ ]);
116192
+ const LISTED_PRESETS = /* @__PURE__ */ new Set(["own_addresses", "own_addresses_read_only"]);
116193
+ const KEY_PRESETS = [...EVERY_MAILBOX_PRESETS, ...LISTED_PRESETS];
116194
+ /**
116195
+ * The grant a new key asks for. There is no default: the caller names a
116196
+ * preset or the permissions, and a key with chosen permissions also says
116197
+ * which mail it reaches (addresses, domains, email rules, or every mailbox).
116198
+ */
116199
+ function buildCreateGrant(choice) {
116200
+ const listed = choice.addresses.length > 0 || choice.domains.length > 0 || choice.rules.length > 0;
116201
+ const scope = {
116202
+ ...choice.addresses.length > 0 ? { addresses: choice.addresses } : {},
116203
+ ...choice.domains.length > 0 ? { domains: choice.domains } : {},
116204
+ ...choice.rules.length > 0 ? { rules: choice.rules } : {}
116205
+ };
116206
+ if (choice.permissions.length > 0) {
116207
+ if (choice.preset) return {
116208
+ ok: false,
116209
+ message: "Pass either --preset or --permission, not both."
116210
+ };
116211
+ if (listed === choice.allMailboxes) return {
116212
+ ok: false,
116213
+ 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."
116214
+ };
116215
+ return {
116216
+ ok: true,
116217
+ grant: {
116218
+ preset: "custom",
116219
+ permissions: choice.permissions,
116220
+ ...choice.allMailboxes ? { mail_scope: "org" } : scope
116221
+ }
116222
+ };
116223
+ }
116224
+ if (!choice.preset) return {
116225
+ ok: false,
116226
+ message: "Say what the key can do: pass --preset or --permission. There is no default."
116227
+ };
116228
+ if (choice.allMailboxes) return {
116229
+ ok: false,
116230
+ message: "--all-mailboxes is only for a key with --permission."
116231
+ };
116232
+ if (EVERY_MAILBOX_PRESETS.has(choice.preset)) {
116233
+ if (listed) return {
116234
+ ok: false,
116235
+ message: `${choice.preset} reaches every mailbox, so it takes no --address, --domain or --rule.`
116236
+ };
116237
+ return {
116238
+ ok: true,
116239
+ grant: choice.preset
116240
+ };
116241
+ }
116242
+ if (LISTED_PRESETS.has(choice.preset)) {
116243
+ if (!listed) return {
116244
+ ok: false,
116245
+ message: `${choice.preset} needs --address, --domain or --rule.`
116246
+ };
116247
+ return {
116248
+ ok: true,
116249
+ grant: {
116250
+ preset: choice.preset,
116251
+ ...scope
116252
+ }
116253
+ };
116254
+ }
116255
+ return {
116256
+ ok: false,
116257
+ message: `--preset must be one of ${KEY_PRESETS.join(", ")}.`
116258
+ };
116259
+ }
116260
+ /** The mail scope fields a change can send back unchanged. */
116261
+ const RESTATABLE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
116262
+ "addresses",
116263
+ "domains",
116264
+ "rules",
116265
+ "depth"
116266
+ ]);
116267
+ /**
116268
+ * The grant that restates a key's current grant with permissions added or
116269
+ * removed, or its email rules replaced, and everything else as it is. Only a
116270
+ * key with a permission list and a mail scope the CLI can restate exactly;
116271
+ * anything else is changed in the dashboard.
116272
+ */
116273
+ function buildUpdatedGrant(current, change) {
116274
+ if (current.template === "super_admin") return {
116275
+ ok: false,
116276
+ 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."
116277
+ };
116278
+ if (current.template) return {
116279
+ ok: false,
116280
+ message: `This key's permissions come from its template (${current.template}) and are not changed here.`
116281
+ };
116282
+ const resources = current.resources;
116283
+ if (resources !== void 0 && resources !== null && (typeof resources !== "object" || Object.keys(resources).length > 0)) return {
116284
+ ok: false,
116285
+ message: "This key is limited to selected resources, which the CLI cannot restate. Change it in the dashboard."
116286
+ };
116287
+ const held = stringList(current.permissions);
116288
+ if (!held) return {
116289
+ ok: false,
116290
+ message: "This key's permissions could not be read."
116291
+ };
116292
+ const permissions = [.../* @__PURE__ */ new Set([...held, ...change.add])].filter((p) => !change.remove.includes(p)).sort();
116293
+ if (permissions.length === 0) return {
116294
+ ok: false,
116295
+ message: "A key needs at least one permission. To stop it working, revoke it."
116296
+ };
116297
+ const scope = current.mail_scope;
116298
+ if (scope === "org") {
116299
+ if (change.rules) return {
116300
+ ok: false,
116301
+ message: "This key reaches every mailbox, so email rules cannot be set on it. Create a key limited to the rules instead."
116302
+ };
116303
+ return {
116304
+ ok: true,
116305
+ grant: {
116306
+ preset: "custom",
116307
+ permissions,
116308
+ mail_scope: "org"
116309
+ },
116310
+ permissions
116311
+ };
116312
+ }
116313
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return {
116314
+ ok: false,
116315
+ message: "This key's mail scope could not be read. Change it in the dashboard."
116316
+ };
116317
+ const record = scope;
116318
+ const addresses = stringList(record.addresses ?? []);
116319
+ const domains = stringList(record.domains ?? []);
116320
+ const rules = change.rules ?? stringList(record.rules ?? []);
116321
+ if (!addresses || !domains || !rules || Object.keys(record).some((field) => !RESTATABLE_SCOPE_FIELDS.has(field))) return {
116322
+ ok: false,
116323
+ message: "This key's mail scope has settings the CLI cannot restate. Change it in the dashboard."
116324
+ };
116325
+ if (addresses.length === 0 && domains.length === 0 && rules.length === 0) return {
116326
+ ok: false,
116327
+ message: "The key would reach no mail. Keep at least one address, domain or rule."
116328
+ };
116329
+ return {
116330
+ ok: true,
116331
+ grant: {
116332
+ preset: "custom",
116333
+ permissions,
116334
+ ...addresses.length > 0 ? { addresses } : {},
116335
+ ...domains.length > 0 ? { domains } : {},
116336
+ ...rules.length > 0 ? { rules } : {}
116337
+ },
116338
+ permissions
116339
+ };
116340
+ }
116341
+ async function authenticate(command, flags) {
116342
+ return createAuthenticatedCliApiClient({
116343
+ apiKey: flags["api-key"],
116344
+ apiBaseUrl: flags["api-base-url"],
116345
+ configDir: command.config.configDir
116346
+ });
116347
+ }
116348
+ /** Print a refusal and set a failing exit code. */
116349
+ function reportKeysError(command, outcome, auth, json) {
116350
+ if (json) process.stderr.write(`${JSON.stringify(outcome.payload)}\n`);
116351
+ else writeErrorWithHints(outcome.payload);
116352
+ 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`);
116353
+ surfaceUnauthorizedHint({
116354
+ auth: auth.auth,
116355
+ baseUrlOverridden: auth.baseUrlOverridden,
116356
+ configDir: command.config.configDir,
116357
+ payload: outcome.payload
116358
+ });
116359
+ process.exitCode = 1;
116360
+ }
116361
+ function writeWarnings(warnings) {
116362
+ for (const warning of warnings ?? []) if (typeof warning.description === "string") process.stderr.write(`Note: ${warning.description}\n`);
116363
+ }
114616
116364
  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.
116365
+ 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
116366
 
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.`;
116367
+ You see your own keys, and every key if your role may read them all, as in the dashboard.`;
114620
116368
  static summary = "List API keys";
114621
116369
  static examples = ["<%= config.bin %> keys list", "<%= config.bin %> keys list --json"];
114622
116370
  static flags = {
114623
116371
  "api-key": API_KEY_FLAG$1,
114624
116372
  "api-base-url": API_BASE_URL_FLAG$2,
114625
- json: Flags.boolean({ description: "Print the inventory rows as JSON." })
116373
+ json: Flags.boolean({ description: "Print the keys as JSON." })
114626
116374
  };
114627
116375
  async run() {
114628
116376
  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"
116377
+ const auth = await authenticate(this, flags);
116378
+ const items = [];
116379
+ let cursor;
116380
+ for (let page = 0; page < LIST_PAGE_LIMIT; page += 1) {
116381
+ const result = await call(listKeys({
116382
+ client: auth.apiClient.client,
116383
+ ...cursor ? { query: { cursor } } : {}
116384
+ }));
116385
+ if (!result.ok) {
116386
+ reportKeysError(this, result, auth, Boolean(flags.json));
116387
+ return;
116388
+ }
116389
+ items.push(...result.data.items);
116390
+ cursor = result.data.next_cursor ?? void 0;
116391
+ if (!cursor) break;
116392
+ }
116393
+ if (flags.json) {
116394
+ this.log(JSON.stringify(items, null, 2));
116395
+ return;
116396
+ }
116397
+ this.log(formatKeyRows(items));
116398
+ }
116399
+ };
116400
+ var KeysGetCommand = class KeysGetCommand extends Command {
116401
+ static description = "Show one API key: its permissions, the mail it reaches, expiry and last use. Never a secret.";
116402
+ static summary = "Show an API key";
116403
+ static examples = ["<%= config.bin %> keys get 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys get 0f0e0d0c-0b0a-4908-8706-050403020100 --json"];
116404
+ static args = { id: Args.string({
116405
+ description: "The key's id.",
116406
+ required: true
116407
+ }) };
116408
+ static flags = {
116409
+ "api-key": API_KEY_FLAG$1,
116410
+ "api-base-url": API_BASE_URL_FLAG$2,
116411
+ json: Flags.boolean({ description: "Print the key as JSON." })
116412
+ };
116413
+ async run() {
116414
+ const { args, flags } = await this.parse(KeysGetCommand);
116415
+ const auth = await authenticate(this, flags);
116416
+ const result = await call(getKey({
116417
+ client: auth.apiClient.client,
116418
+ path: { id: args.id }
116419
+ }));
116420
+ if (!result.ok) {
116421
+ reportKeysError(this, result, auth, Boolean(flags.json));
116422
+ return;
116423
+ }
116424
+ this.log(flags.json ? JSON.stringify(result.data, null, 2) : formatKeyDetail(result.data));
116425
+ }
116426
+ };
116427
+ /** The new secret on stdout alone, everything else on stderr. */
116428
+ function printSecret(command, json, document, secret, summary) {
116429
+ if (json) {
116430
+ command.log(JSON.stringify({
116431
+ ...document,
116432
+ notice: STORE_SECRET_NOTICE
116433
+ }, null, 2));
116434
+ return;
116435
+ }
116436
+ process.stderr.write(`${summary}\n\n`);
116437
+ command.log(secret);
116438
+ process.stderr.write(`\n${STORE_SECRET_NOTICE}\n`);
116439
+ }
116440
+ var KeysCreateCommand = class KeysCreateCommand extends Command {
116441
+ static description = `Create an API key. Say what it can do with --preset or --permission; there is no default.
116442
+
116443
+ You approve each new key in your browser: the command prints the approval link, opens it where it can, and waits for the one-time token the page shows. In a script, approve first and pass that token with --step-up-token. The key never holds more than you do, and your organization's credential policy applies.
116444
+
116445
+ 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.
116446
+
116447
+ 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.`;
116448
+ static summary = "Create an API key";
116449
+ static examples = [
116450
+ "<%= config.bin %> keys create --name reader --preset read_only",
116451
+ "<%= config.bin %> keys create --name orders --permission mail:read --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f",
116452
+ "<%= config.bin %> keys create --name support --permission mail:read,mail:reply --address support@example.com --expires-in-days 90",
116453
+ "<%= config.bin %> keys create --name everything --preset super_admin"
116454
+ ];
116455
+ static flags = {
116456
+ "api-key": API_KEY_FLAG$1,
116457
+ "api-base-url": API_BASE_URL_FLAG$2,
116458
+ name: Flags.string({ description: "A name for the key." }),
116459
+ preset: Flags.string({
116460
+ description: "A preset grant.",
116461
+ options: [...KEY_PRESETS]
116462
+ }),
116463
+ permission: Flags.string({
116464
+ description: "A permission the key holds (repeatable, or comma-separated), for a custom key.",
116465
+ multiple: true
116466
+ }),
116467
+ address: Flags.string({
116468
+ description: "An address the key reaches (repeatable).",
116469
+ multiple: true
116470
+ }),
116471
+ domain: Flags.string({
116472
+ description: "A domain the key reaches (repeatable).",
116473
+ multiple: true
116474
+ }),
116475
+ rule: Flags.string({
116476
+ description: "An email rule id whose matching mail the key reaches (repeatable).",
116477
+ multiple: true
116478
+ }),
116479
+ "all-mailboxes": Flags.boolean({ description: "A key with --permission that reaches every mailbox." }),
116480
+ "expires-in-days": Flags.integer({
116481
+ description: "Expire the key after this many days.",
116482
+ min: 1
116483
+ }),
116484
+ "expires-at": Flags.string({
116485
+ description: "Expire the key at this ISO 8601 time.",
116486
+ exclusive: ["expires-in-days"]
116487
+ }),
116488
+ ...APPROVAL_FLAGS,
116489
+ json: Flags.boolean({ description: "Print the new key, its secret included, as JSON." })
116490
+ };
116491
+ async run() {
116492
+ const { flags } = await this.parse(KeysCreateCommand);
116493
+ const grant = buildCreateGrant({
116494
+ preset: flags.preset,
116495
+ permissions: listFlag(flags.permission),
116496
+ addresses: listFlag(flags.address),
116497
+ domains: listFlag(flags.domain),
116498
+ rules: listFlag(flags.rule),
116499
+ allMailboxes: flags["all-mailboxes"]
114638
116500
  });
114639
- if (result.error) {
114640
- reportKeysError(result.error, {
114641
- auth,
114642
- baseUrlOverridden,
114643
- configDir: this.config.configDir
114644
- }, Boolean(flags.json));
116501
+ if (!grant.ok) this.error(grant.message, { exit: 2 });
116502
+ const auth = await authenticate(this, flags);
116503
+ const body = {
116504
+ grant: grant.grant,
116505
+ ...flags.name ? { name: flags.name } : {},
116506
+ ...flags["expires-in-days"] ? { expires_in_days: flags["expires-in-days"] } : {},
116507
+ ...flags["expires-at"] ? { expires_at: flags["expires-at"] } : {}
116508
+ };
116509
+ const idempotencyKey = `cli-${randomUUID()}`;
116510
+ const result = await withApproval((token) => call(createKey({
116511
+ client: auth.apiClient.client,
116512
+ body,
116513
+ headers: {
116514
+ "Idempotency-Key": idempotencyKey,
116515
+ ...token ? { [STEP_UP_HEADER$1]: token } : {}
116516
+ }
116517
+ })), approvalIo(flags));
116518
+ if (!result.ok) {
116519
+ reportKeysError(this, result, auth, Boolean(flags.json));
116520
+ return;
116521
+ }
116522
+ const key = result.data;
116523
+ if (!flags.json) writeWarnings(key.warnings);
116524
+ printSecret(this, Boolean(flags.json), key, key.key, [
116525
+ `Created API key ${key.name ?? "(unnamed)"} (${key.id}), prefix ${key.key_prefix}.`,
116526
+ `Permissions: ${describePermissions({
116527
+ template: key.grant.template,
116528
+ permissions: key.grant.permissions,
116529
+ mail_scope: key.grant.mail_scope
116530
+ })}`,
116531
+ `Mail: ${describeMailScope({
116532
+ template: key.grant.template,
116533
+ permissions: key.grant.permissions,
116534
+ mail_scope: key.grant.mail_scope
116535
+ })}`,
116536
+ `Expires: ${key.expires_at ?? "never"}`
116537
+ ].join("\n"));
116538
+ }
116539
+ };
116540
+ var KeysRotateCommand = class KeysRotateCommand extends Command {
116541
+ 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.
116542
+
116543
+ You approve the rotation in your browser, as for a new key. Store the new secret right away: it is not shown again and the CLI does not save it.`;
116544
+ static summary = "Rotate an API key";
116545
+ static examples = ["<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100 --overlap-seconds 0"];
116546
+ static args = { id: Args.string({
116547
+ description: "The key's id.",
116548
+ required: true
116549
+ }) };
116550
+ static flags = {
116551
+ "api-key": API_KEY_FLAG$1,
116552
+ "api-base-url": API_BASE_URL_FLAG$2,
116553
+ "overlap-seconds": Flags.integer({
116554
+ description: "How long the old key keeps working, in seconds. 0 stops it now.",
116555
+ min: 0
116556
+ }),
116557
+ ...APPROVAL_FLAGS,
116558
+ json: Flags.boolean({ description: "Print the new key, its secret included, as JSON." })
116559
+ };
116560
+ async run() {
116561
+ const { args, flags } = await this.parse(KeysRotateCommand);
116562
+ const auth = await authenticate(this, flags);
116563
+ const body = flags["overlap-seconds"] === void 0 ? {} : { overlap_seconds: flags["overlap-seconds"] };
116564
+ const result = await withApproval((token) => call(rotateKey({
116565
+ client: auth.apiClient.client,
116566
+ path: { id: args.id },
116567
+ body,
116568
+ ...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
116569
+ })), approvalIo(flags));
116570
+ if (!result.ok) {
116571
+ reportKeysError(this, result, auth, Boolean(flags.json));
116572
+ return;
116573
+ }
116574
+ const rotated = result.data;
116575
+ 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}.`);
116576
+ }
116577
+ };
116578
+ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
116579
+ static description = `Change an API key's permissions, email rules or name. It keeps its id and secret.
116580
+
116581
+ --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.
116582
+
116583
+ When the server asks for it, you approve the change in your browser, as for a new key. A key never holds more than you do.`;
116584
+ static summary = "Change an API key";
116585
+ static examples = [
116586
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --add mail:send,mail:reply",
116587
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --remove mail:send",
116588
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f"
116589
+ ];
116590
+ static args = { id: Args.string({
116591
+ description: "The key's id.",
116592
+ required: true
116593
+ }) };
116594
+ static flags = {
116595
+ "api-key": API_KEY_FLAG$1,
116596
+ "api-base-url": API_BASE_URL_FLAG$2,
116597
+ add: Flags.string({
116598
+ description: "Permissions to add (repeatable, or comma-separated).",
116599
+ multiple: true
116600
+ }),
116601
+ remove: Flags.string({
116602
+ description: "Permissions to remove (repeatable, or comma-separated).",
116603
+ multiple: true
116604
+ }),
116605
+ rule: Flags.string({
116606
+ description: "An email rule id the key reaches (repeatable). Replaces the key's rules.",
116607
+ multiple: true
116608
+ }),
116609
+ name: Flags.string({ description: "A new name for the key." }),
116610
+ ...APPROVAL_FLAGS,
116611
+ json: Flags.boolean({ description: "Print the result as JSON." })
116612
+ };
116613
+ async run() {
116614
+ const { args, flags } = await this.parse(KeysUpdateCommand);
116615
+ const add = listFlag(flags.add);
116616
+ const remove = listFlag(flags.remove);
116617
+ const rules = flags.rule ? listFlag(flags.rule) : void 0;
116618
+ const both = add.filter((p) => remove.includes(p));
116619
+ if (both.length > 0) this.error(`Both added and removed: ${both.join(", ")}.`, { exit: 2 });
116620
+ const changesGrant = add.length > 0 || remove.length > 0 || !!rules;
116621
+ if (!changesGrant && flags.name === void 0) this.error("Nothing to change: pass --add, --remove, --rule or --name.", { exit: 2 });
116622
+ const auth = await authenticate(this, flags);
116623
+ const body = { ...flags.name !== void 0 ? { name: flags.name } : {} };
116624
+ if (changesGrant) {
116625
+ const current = await call(getKey({
116626
+ client: auth.apiClient.client,
116627
+ path: { id: args.id }
116628
+ }));
116629
+ if (!current.ok) {
116630
+ reportKeysError(this, current, auth, Boolean(flags.json));
116631
+ return;
116632
+ }
116633
+ const next = buildUpdatedGrant(current.data.grant, {
116634
+ add,
116635
+ remove,
116636
+ ...rules ? { rules } : {}
116637
+ });
116638
+ if (!next.ok) this.error(next.message, { exit: 2 });
116639
+ body.grant = next.grant;
116640
+ const ref = current.data.grant.ref;
116641
+ if (typeof ref === "string") body.expected_grant_ref = ref;
116642
+ }
116643
+ const result = await withApproval((token) => call(updateKey({
116644
+ client: auth.apiClient.client,
116645
+ path: { id: args.id },
116646
+ body,
116647
+ ...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
116648
+ })), approvalIo(flags));
116649
+ if (!result.ok) {
116650
+ reportKeysError(this, result, auth, Boolean(flags.json));
116651
+ return;
116652
+ }
116653
+ const grantChanged = result.data.changed.includes("grant");
116654
+ const after = grantChanged ? await call(getKey({
116655
+ client: auth.apiClient.client,
116656
+ path: { id: args.id }
116657
+ })) : null;
116658
+ if (flags.json) {
116659
+ this.log(JSON.stringify({
116660
+ ...result.data,
116661
+ ...after?.ok ? { key: after.data } : {},
116662
+ ...grantChanged ? { notice: HELD_REQUEST_NOTICE } : {}
116663
+ }, null, 2));
116664
+ return;
116665
+ }
116666
+ writeWarnings(result.data.warnings);
116667
+ const changed = result.data.changed;
116668
+ this.log(changed.length > 0 ? `Updated key ${args.id}: ${changed.join(", ")}.` : `Key ${args.id} already matched; nothing changed.`);
116669
+ if (after?.ok) {
116670
+ this.log(`Permissions: ${describePermissions(after.data.grant)}`);
116671
+ this.log(`Mail: ${describeMailScope(after.data.grant)}`);
116672
+ }
116673
+ if (grantChanged) process.stderr.write(`${HELD_REQUEST_NOTICE}\n`);
116674
+ }
116675
+ };
116676
+ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
116677
+ static description = `Revoke an API key. It stops working at once; what it set up keeps working and is listed for review.
116678
+
116679
+ Asks for confirmation first; pass --yes to skip the prompt in scripts.`;
116680
+ static summary = "Revoke an API key";
116681
+ static examples = ["<%= config.bin %> keys revoke 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys revoke 0f0e0d0c-0b0a-4908-8706-050403020100 --yes"];
116682
+ static args = { id: Args.string({
116683
+ description: "The key's id.",
116684
+ required: true
116685
+ }) };
116686
+ static flags = {
116687
+ "api-key": API_KEY_FLAG$1,
116688
+ "api-base-url": API_BASE_URL_FLAG$2,
116689
+ yes: Flags.boolean({
116690
+ char: "y",
116691
+ description: "Revoke without asking for confirmation."
116692
+ }),
116693
+ reason: Flags.string({ description: "Why, recorded with the revocation." }),
116694
+ json: Flags.boolean({ description: "Print the result as JSON." })
116695
+ };
116696
+ async run() {
116697
+ const { args, flags } = await this.parse(KeysRevokeCommand);
116698
+ if (!flags.yes) {
116699
+ 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 });
116700
+ const answer = await promptLine$2(`Revoke key ${args.id}? It stops working at once. [y/N] `);
116701
+ if (!/^y(es)?$/i.test(answer.trim())) {
116702
+ process.stderr.write("Cancelled. The key still works.\n");
116703
+ process.exitCode = 1;
116704
+ return;
116705
+ }
116706
+ }
116707
+ const auth = await authenticate(this, flags);
116708
+ const result = await call(revokeCredential({
116709
+ client: auth.apiClient.client,
116710
+ body: {
116711
+ kind: "org_key",
116712
+ id: args.id,
116713
+ ...flags.reason ? { reason: flags.reason } : {}
116714
+ }
116715
+ }));
116716
+ if (!result.ok) {
116717
+ reportKeysError(this, result, auth, Boolean(flags.json));
114645
116718
  return;
114646
116719
  }
114647
- const rows = unwrap(result.data)?.items ?? [];
114648
116720
  if (flags.json) {
114649
- this.log(JSON.stringify(rows, null, 2));
116721
+ this.log(JSON.stringify(result.data, null, 2));
114650
116722
  return;
114651
116723
  }
114652
- this.log(formatKeyRows(rows));
116724
+ this.log(`Revoked key ${args.id}.`);
116725
+ const standing = result.data.standing_objects;
116726
+ if (Array.isArray(standing) && standing.length > 0) this.log(`It set up ${standing.length} item(s) that keep working. Review them with --json.`);
114653
116727
  }
114654
116728
  };
114655
116729
  const hash = (value) => createHash("sha256").update(value).digest("hex");
@@ -126092,6 +128166,11 @@ const COMMANDS = {
126092
128166
  "org:use": OrgUseCommand,
126093
128167
  "cli:list-cli-orgs": OrgsCommand,
126094
128168
  "keys:list": KeysListCommand,
128169
+ "keys:get": KeysGetCommand,
128170
+ "keys:create": KeysCreateCommand,
128171
+ "keys:rotate": KeysRotateCommand,
128172
+ "keys:update": KeysUpdateCommand,
128173
+ "keys:revoke": KeysRevokeCommand,
126095
128174
  "roles:list": RolesListCommand,
126096
128175
  "roles:groups": RolesGroupsCommand,
126097
128176
  "roles:mailbox-access": RolesMailboxAccessCommand,