primcli 3.0.0-preview.5 → 3.0.0-preview.6

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,14 +1,14 @@
1
- import { $ as replyExpectation, $t as refuseOrgSelectionForProfile, A as bareAddress, At as connectNextSteps, B as wakeReadCommand, Bt as withFlagSuggestion, C as terminalApprovalIo, Ct as sendWaitClampNotice, D as listenForCopyHotkey, Dt as thrownErrorExitCode, E as formatBrowserLoginInstructions, Et as successfulSendOutcome, F as readThreadContext, Ft as currentMailSessionKey, G as clearReadPendingMail, Gt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, H as wakeRelationship, Ht as classificationOf, I as sentInThread, It as detectAgentRuntime, J as recordPendingMail, Jt as ConnectedAgentKeyRequiredError, K as pendingMailPath, Kt as ATTACHMENT_WAIT_MAX_MS, L as serverMuted, Lt as detectRuntimeSession, M as displayAddress, Mt as generatedAgentName, N as formatMailWakeLine, Nt as identitySuggestions, O as openBrowser, P as latestOwnOutbound, Pt as loadSkillLine, Q as readEmailInteraction, Qt as refreshStoredCliCredentials, R as serverRelationship, Rt as runtimeSessionVariableList, S as promptLine$2, St as sendOutcomeExitCode, T as canOfferCopyHotkey, Tt as serializeErrorPayload, U as claimPendingMail, Ut as isEmailId, V as wakeRecipientField, Vt as classificationFactLines, W as clearConsumedPendingMail, Wt as noFactsMessage, X as interactionHumanLine, Xt as createAuthenticatedCliApiClient, Y as removePendingMail, Z as interactionNextActions, Zt as createCliApiClient, _ as writeErrorWithHints, _t as formatSendFailureSummary, a as createOperationCommand, an as revokeUserLogin, at as apiResultHttpStatus, b as STEP_UP_TOKEN_FLAG_DESCRIPTION, bt as reportSendCommandResult, c as formatErrorPayload, cn as sanitizeForTerminal, ct as buildThrownSendFailureEnvelope, d as isGrantRefusal, dt as deriveSendIdempotencyKey, en as resolveCliApiRequestConfig, et as replyInteractionWarning, f as parameterFlagName, ft as excludeSignalReplies, g as takesStepUp, gt as formatPriorRepliesWarning, h as surfaceUnauthorizedHint, ht as formatPriorRepliesCheckSkipped, i as TIME_FLAG_DESCRIPTION, in as fetchCliOrgs, it as SEND_OUTCOME_HELP, j as describeWake, k as explainPendingMailNotFound, kt as cliInvocation, l as hasControlCharacter$1, ln as setKnownCommands, lt as checkPriorReplies, m as runWithTiming, mt as formatDeletedEarlierSendNotice, n as API_ERROR_CODES, nn as CliOrgRefusedError, nt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, o as extractErrorCode, on as switchCurrentOrg, ot as assertValidIdempotencyKey, p as readTextFileFlag, pt as followUpCommandPrefix, q as readPendingMail, qt as withAttachmentWait, r as OPERATION_PATH_FLAGS, rn as credentialsFromUserLogin, rt as MAX_SEND_WAIT_TIMEOUT_MS, s as extractErrorPayload, sn as routeWarningLines, st as buildFollowUpCommand, t as API_BASE_URL_FLAG_DESCRIPTION, tn as selectedOrgFromEnv, u as headerFlagName, un as operationManifest, ut as classifySendError, v as BROWSER_FLAG_DESCRIPTION, vt as formatSendRecordFailureSummary, w as withApproval, wt as sentHistoryWindowStart, x as approveThenRerunHint, xt as sendIdentityFields, y as STEP_UP_HEADER$1, yt as priorRepliesThatWentOut, zt as formatAlreadySentNotice } from "../api-command-DIo8Rg0w.js";
2
- import { $ as AUTOMATED_VALUES, $n as stopRepeatFromEmail, $t as getWakeSchedule, A as sinceFromFlags, At as downloadSentAttachmentArchiveMember, B as formatRepliesCell, Bn as runWakeSchedule, Bt as getFunction, C as SINCE_VALUE_HELP, Cn as listSentAttachmentArchiveMembers, Ct as deleteMemory, D as fetchEmailSearchPage, Dn as listWakeSchedules, Dt as downloadEmailAttachmentArchiveMember, E as encodeReceivedAtSearchCursor, En as listWakeDispatches, Et as deleteWakeSchedule, F as ReplyStateUnsupportedError, Fn as reorderRoutes, Gn as sendEmail, Gt as getMemory, H as isAwaitingRejectedError, Hn as searchEmails, Ht as getFunctionTestRunTrace, I as assertAwaitingFilterRows, In as replyToEmail, It as getConversation, Jn as setMemory, Jt as getOwnGrant, Kn as setFunctionRoute, Kt as getOrgRoutingTopology, L as awaitingRejectedError, Ln as resendAgentSignupVerification, Lt as getCreditBalance, M as AWAITING_FLAG_DESCRIPTION, Mt as getAccessMode, N as AWAITING_VALUES, Nn as redeemCreditCode, Nt as getAccount, O as filtersFromFlags, On as pollCliLogin, Ot as downloadEmailAttachmentByFilename, P as REPLY_STATE_UNSUPPORTED_CODE, Pn as removeDefaultNetworkMember, Pt as getAgentContactPolicy, Q as AUTOMATED_FLAG_DESCRIPTION, Qn as startCliLogin, Qt as getThread, R as ensureSearchReportsReplyState, Rn as revokeCredential, Rt as getDefaultNetworkAgent, S as transientFailure, Sn as listRoutes, T as cursorFromAcceptedRows, Tn as listWakeAuthorizations, Tt as deleteWakeAuthorization, U as queryUsesAwaiting, Un as searchMemories, Ut as getInboxStatus, V as hasReplyState, Vt as getFunctionRouting, Wn as semanticSearch, Wt as getKey, X as parseFromHeader, Xn as startAgentClaim, Xt as getSendFrom, Y as parseWebhookEvent, Yn as simulateRoute, Yt as getRepeatingSend, Z as AUTOMATED_FILTER_UNSUPPORTED_CODE, Zn as startAgentSignup, Zt as getSentEmail, _ as describeApiFailure, _n as listKeys, _t as createKey, a as REPEAT_IDLE_MAX_MINUTES, an as listAgentNetworks, ar as updateRepeatingSend, b as retryTransient, bn as listRepeatingSends, bt as createWakeSchedule, cn as listDefaultNetworkMembers, cr as updateWakeSchedule, ct as addDefaultNetworkMember, d as parseRepeatTick, dn as listEmailRuleResults, dr as whoami, dt as checkDefaultNetworkContactAdmission, er as testFunction, et as AutomatedFilterUnsupportedError, f as preparePresenceAliveEmail, fn as listEmails, ft as checkSendability, g as WaitDeadlineReached, gn as listGroups, gt as createFunction, hn as listFunctions, ht as createEndpoint, i as REPEAT_EVERY_MAX_MINUTES, in as listAgentContacts, ir as updateKey, j as sleep$3, jt as downloadSentAttachmentByFilename, k as quoteDslValue, kn as pullWebhookEvent, l as parseInteractionEnvelope, ln as listDomains, lr as verifyAgentClaim, lt as cancelSentEmail, m as repeatStopCommand, mn as listFunctionLogs, mt as completeWebhookEvent, nn as listActivityObjects, nr as updateDefaultNetworkMember, nt as automatedRejectedError, o as REPEAT_MAX_SENDS_MAX, or as updateRoute, ot as hasAutomatedVerdict, p as prepareSignalEmail, pn as listEndpoints, pt as cliLogout, q as isEmailReceivedEvent, qn as setFunctionSecret, qt as getOutboundStatus, rr as updateFunction, s as buildRepeatStopBody, sn as listDefaultNetworkAgents, sr as updateWakeAuthorization, st as isAutomatedRejectedError, tn as leaveOrganization, tr as unsetFunctionRoute, tt as assertAutomatedVerdict, u as parsePresenceEnvelope, un as listEmailAttachmentArchiveMembers, ur as verifyAgentSignup, ut as cancelSentEmailByIdempotencyKey, vn as listMailboxAccess, vt as createRoute, w as collectNewAcceptedEmails, wn as listSentEmails, wt as deleteRoute, x as retryingOperation, xn as listRoles, y as pauseForTransientFailure, yn as listOrganizationPeople, yt as createWakeAuthorization, z as formatAwaitingCell, zn as rotateKey, zt as getEmail } from "../repeats-Dj_o6c-X-Ix28GRRc.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-o9hEtJ_c.js";
1
+ import { $ as replyExpectation, $t as createCliApiClient, A as bareAddress, At as connectNextSteps, B as wakeReadCommand, Bt as withFlagSuggestion, C as terminalApprovalIo, Ct as sendWaitClampNotice, D as listenForCopyHotkey, Dt as thrownErrorExitCode, E as formatBrowserLoginInstructions, Et as successfulSendOutcome, F as readThreadContext, Ft as currentMailSessionKey, G as clearReadPendingMail, Gt as isEmailId, H as wakeRelationship, Ht as readCallerMailFor, I as sentInThread, It as detectAgentRuntime, J as recordPendingMail, Jt as ATTACHMENT_WAIT_MAX_MS, K as pendingMailPath, Kt as noFactsMessage, L as serverMuted, Lt as detectRuntimeSession, M as displayAddress, Mt as generatedAgentName, N as formatMailWakeLine, Nt as identitySuggestions, O as openBrowser, P as latestOwnOutbound, Pt as loadSkillLine, Q as readEmailInteraction, Qt as createAuthenticatedCliApiClient, R as serverRelationship, Rt as runtimeSessionVariableList, S as promptLine$2, St as sendOutcomeExitCode, T as canOfferCopyHotkey, Tt as serializeErrorPayload, U as claimPendingMail, Ut as classificationFactLines, V as wakeRecipientField, Vt as emptyEmailListHint, W as clearConsumedPendingMail, Wt as classificationOf, X as interactionHumanLine, Xt as ConnectedAgentKeyRequiredError, Y as removePendingMail, Yt as withAttachmentWait, Z as interactionNextActions, _ as writeErrorWithHints, _t as formatSendFailureSummary, a as createOperationCommand, an as RESTRICTED_ROLE_NOTE, at as apiResultHttpStatus, b as STEP_UP_TOKEN_FLAG_DESCRIPTION, bt as reportSendCommandResult, c as formatErrorPayload, cn as isBuiltInRoleKey, ct as buildThrownSendFailureEnvelope, d as isGrantRefusal, dn as roleKeyText, dt as deriveSendIdempotencyKey, en as refreshStoredCliCredentials, et as replyInteractionWarning, f as parameterFlagName, fn as switchCurrentOrg, ft as excludeSignalReplies, g as takesStepUp, gn as operationManifest, gt as formatPriorRepliesWarning, h as surfaceUnauthorizedHint, hn as setKnownCommands, ht as formatPriorRepliesCheckSkipped, i as TIME_FLAG_DESCRIPTION, in as CliOrgRefusedError, it as SEND_OUTCOME_HELP, j as describeWake, k as explainPendingMailNotFound, kt as cliInvocation, l as hasControlCharacter$1, ln as revokeUserLogin, lt as checkPriorReplies, m as runWithTiming, mn as sanitizeForTerminal, mt as formatDeletedEarlierSendNotice, n as API_ERROR_CODES, nn as resolveCliApiRequestConfig, nt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, o as extractErrorCode, on as credentialsFromUserLogin, ot as assertValidIdempotencyKey, p as readTextFileFlag, pn as routeWarningLines, pt as followUpCommandPrefix, q as readPendingMail, qt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, r as OPERATION_PATH_FLAGS, rn as selectedOrgFromEnv, rt as MAX_SEND_WAIT_TIMEOUT_MS, s as extractErrorPayload, sn as fetchCliOrgs, st as buildFollowUpCommand, t as API_BASE_URL_FLAG_DESCRIPTION, tn as refuseOrgSelectionForProfile, u as headerFlagName, un as roleKeyLabel, ut as classifySendError, v as BROWSER_FLAG_DESCRIPTION, vt as formatSendRecordFailureSummary, w as withApproval, wt as sentHistoryWindowStart, x as approveThenRerunHint, xt as sendIdentityFields, y as STEP_UP_HEADER$1, yt as priorRepliesThatWentOut, zt as formatAlreadySentNotice } from "../api-command-CRjIK1QL.js";
2
+ import { $ as parseFromHeader, $n as startAgentClaim, $t as getSendFrom, A as lateMailReread, An as listWakeSchedules, At as downloadEmailAttachmentArchiveMember, B as ensureSearchReportsReplyState, Bn as resendAgentSignupVerification, Bt as getCreditBalance, C as waitOutRetryAfter, Cn as listRepeatingSends, Ct as createWakeSchedule, D as encodeReceivedAtSearchCursor, Dn as listSentEmails, Dt as deleteRoute, E as cursorFromAcceptedRows, En as listSentAttachmentArchiveMembers, Et as deleteMemory, F as AWAITING_VALUES, Ft as getAccessMode, G as queryUsesAwaiting, Gn as searchEmails, Gt as getFunctionTestRunTrace, H as formatRepliesCell, Hn as rotateKey, Ht as getEmail, I as REPLY_STATE_UNSUPPORTED_CODE, In as redeemCreditCode, It as getAccount, Jn as sendEmail, Jt as getMemory, Kn as searchMemories, Kt as getInboxStatus, L as ReplyStateUnsupportedError, Ln as removeDefaultNetworkMember, Lt as getAgentContactPolicy, M as sinceFromFlags, Mn as pullWebhookEvent, N as sleep$3, Nt as downloadSentAttachmentArchiveMember, O as fetchEmailSearchPage, On as listWakeAuthorizations, Ot as deleteWakeAuthorization, P as AWAITING_FLAG_DESCRIPTION, Pt as downloadSentAttachmentByFilename, Q as parseWebhookEvent, Qn as simulateRoute, Qt as getRepeatingSend, R as assertAwaitingFilterRows, Rn as reorderRoutes, S as transientFailure, Sn as listOrganizationPeople, St as createWakeAuthorization, T as collectNewAcceptedEmails, Tn as listRoutes, U as hasReplyState, Un as runWakeSchedule, Ut as getFunction, V as formatAwaitingCell, Vn as revokeCredential, Vt as getDefaultNetworkAgent, W as isAwaitingRejectedError, Wt as getFunctionRouting, X as isEmailReceivedEvent, Xn as setFunctionSecret, Xt as getOutboundStatus, Yn as setFunctionRoute, Yt as getOrgRoutingTopology, Zn as setMemory, Zt as getOwnGrant, _ as describeApiFailure, _n as listFunctionLogs, _t as completeWebhookEvent, a as REPEAT_IDLE_MAX_MINUTES, an as listActivityObjects, ar as updateDefaultNetworkMember, at as automatedRejectedError, b as retryTransient, bn as listKeys, bt as createKey, cn as listAgentNetworks, cr as updateRepeatingSend, d as parseRepeatTick, dn as listDefaultNetworkMembers, dr as updateWakeSchedule, dt as addDefaultNetworkMember, en as getSentEmail, er as startAgentSignup, et as AUTOMATED_FILTER_UNSUPPORTED_CODE, f as preparePresenceAliveEmail, fn as listDomains, fr as verifyAgentClaim, ft as cancelSentEmail, g as WaitDeadlineReached, gn as listEndpoints, gt as cliLogout, hn as listEmails, ht as checkSendability, i as REPEAT_EVERY_MAX_MINUTES, in as leaveOrganization, ir as unsetFunctionRoute, it as assertAutomatedVerdict, j as quoteDslValue, jn as pollCliLogin, jt as downloadEmailAttachmentByFilename, k as filtersFromFlags, kn as listWakeDispatches, kt as deleteWakeSchedule, l as parseInteractionEnvelope, lr as updateRoute, lt as hasAutomatedVerdict, m as repeatStopCommand, mn as listEmailRuleResults, mr as whoami, mt as checkDefaultNetworkContactAdmission, nn as getWakeSchedule, nr as stopRepeatFromEmail, nt as AUTOMATED_VALUES, o as REPEAT_MAX_SENDS_MAX, or as updateFunction, p as prepareSignalEmail, pn as listEmailAttachmentArchiveMembers, pr as verifyAgentSignup, pt as cancelSentEmailByIdempotencyKey, qn as semanticSearch, qt as getKey, rr as testFunction, rt as AutomatedFilterUnsupportedError, s as buildRepeatStopBody, sn as listAgentContacts, sr as updateKey, tn as getThread, tr as startCliLogin, tt as AUTOMATED_FLAG_DESCRIPTION, u as parsePresenceEnvelope, un as listDefaultNetworkAgents, ur as updateWakeAuthorization, ut as isAutomatedRejectedError, vn as listFunctions, vt as createEndpoint, w as SINCE_VALUE_HELP, wn as listRoles, x as retryingOperation, xn as listMailboxAccess, xt as createRoute, y as pauseForTransientFailure, yn as listGroups, yt as createFunction, z as awaitingRejectedError, zn as replyToEmail, zt as getConversation } from "../repeats-Dj_o6c-X-CP0h4alF.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-DTyg-sfS.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-BIOGBT9g.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-DcymzGgB.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-3H-rNH8v.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-CvqdlWuX.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-CRGoKO4A.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-CPxRPbHK.js";
10
+ import { a as backgroundListenStatus, c as runBackgroundListen, d as stopBackgroundListen, f as verifyBackgroundListenTarget, o as backgroundListenToken, r as backgroundListenConfiguration, u as startBackgroundListen } from "../listen-background-DyBYca28.js";
10
11
  import { i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-DWrJMakZ.js";
11
- import { c as startBackgroundListen, i as backgroundListenToken, l as stopBackgroundListen, o as runBackgroundListen, r as backgroundListenStatus, u as verifyBackgroundListenTarget } from "../listen-background-C0F9gNDD.js";
12
12
  import { ORG_SELECTION_ENV } from "./global-org-flag.js";
13
13
  import { n as createConfig, t as createClient } from "../client.gen-6rZvmHfx.js";
14
14
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
@@ -2768,7 +2768,7 @@ const openapiDocument = {
2768
2768
  "format": "email",
2769
2769
  "maxLength": 320
2770
2770
  },
2771
- "description": "Exact case-insensitive delivered recipient mailbox, applied before pagination. Combines with existing filters and cursors; does not search message text."
2771
+ "description": "Exact case-insensitive delivered recipient mailbox, applied before pagination. Combines with existing filters and cursors; does not search message text. It filters only the mail the caller can read, and is never refused for that (200): a mailbox outside the caller's mail access lists nothing, and one the caller reads only in part (for example through an email rule) lists only that part. For a caller limited to some mailboxes, meta.recipient_outside_access or meta.recipient_rules_only says so when the mailbox is neither one of its addresses nor on one of its domains."
2772
2772
  },
2773
2773
  {
2774
2774
  "name": "exclude_fyi",
@@ -2903,12 +2903,16 @@ const openapiDocument = {
2903
2903
  "responses": {
2904
2904
  "200": {
2905
2905
  "description": "Paginated list of emails",
2906
- "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/ListEnvelope" }, {
2906
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2907
2907
  "type": "object",
2908
- "properties": { "data": {
2909
- "type": "array",
2910
- "items": { "$ref": "#/components/schemas/EmailSummary" }
2911
- } }
2908
+ "properties": {
2909
+ "data": {
2910
+ "type": "array",
2911
+ "items": { "$ref": "#/components/schemas/EmailSummary" }
2912
+ },
2913
+ "meta": { "$ref": "#/components/schemas/EmailListMeta" }
2914
+ },
2915
+ "required": ["data", "meta"]
2912
2916
  }] } } },
2913
2917
  "headers": {
2914
2918
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -4762,7 +4766,7 @@ const openapiDocument = {
4762
4766
  "post": {
4763
4767
  "operationId": "createEndpoint",
4764
4768
  "summary": "Create a webhook endpoint",
4765
- "description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing.** A webhook is signed with the organization's webhook secret (`GET /account/webhook-secret`) unless it has its own. A webhook made as a person's own, or by an API key limited to some mailboxes, a third-party app or an agent delegate for itself, is made with its own secret: the response carries it once as `signing_secret`, and `GET /endpoints/{id}/signing-secret` returns it afterwards. Other webhooks, those a person who can read every mailbox makes among them, sign with the organization's secret until given their own. `signed_with` on the endpoint says which secret signs it. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. Creating a pull destination requires a connected-agent credential; it creates a private subscription restricted to inbound email for the assigned address of that credential. Account API keys receive 403 pull_subscription_requires_agent_connection and can instead receive mail with GET /emails?since=<cursor>&wait=30 or an HTTP endpoint. Pulling or completing an account destination created earlier returns the same 403. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.\n\nAgent connection keys (pconn_) may use endpoints.pull:manage instead of endpoints:manage here, for their own pull subscriptions only.",
4769
+ "description": "Creates a new webhook endpoint. A URL can have one endpoint per domain, route targets included (unlike the one default destination per domain), and endpoints with no domain_id count as one domain. A disabled endpoint still holds its URL; a deleted one does not. A create at a URL another endpoint has on the same domain is refused (400). A deleted endpoint's URL can be used again: the create makes a new endpoint, with a new id, and the deleted one stays as it was. Subject to plan limits on the number of active\nendpoints.\n\n**Signing.** A webhook is signed with the organization's webhook secret (`GET /account/webhook-secret`) unless it has its own. A webhook made as a person's own, or by an API key limited to some mailboxes, a third-party app or an agent delegate for itself, is made with its own secret: the response carries it once as `signing_secret`, and `GET /endpoints/{id}/signing-secret` returns it afterwards. Other webhooks, those a person who can read every mailbox makes among them, sign with the organization's secret until given their own. `signed_with` on the endpoint says which secret signs it. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types within their credential scope. Connected-agent credentials automatically create private subscriptions restricted to inbound email for their assigned address. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.\n\nFor local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. Creating a pull destination requires a connected-agent credential; it creates a private subscription restricted to inbound email for the assigned address of that credential. Account API keys receive 403 pull_subscription_requires_agent_connection and can instead receive mail with GET /emails?since=<cursor>&wait=30 or an HTTP endpoint. Pulling or completing an account destination created earlier returns the same 403. Names are isolated per credential; revocation or replacement deletes its subscriptions. Address-scoped events contain parsed mail without raw MIME, signed download links, routing metadata, or sibling envelope recipients. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.\n\nAgent connection keys (pconn_) may use endpoints.pull:manage instead of endpoints:manage here, for their own pull subscriptions only.",
4766
4770
  "tags": ["Endpoints"],
4767
4771
  "requestBody": {
4768
4772
  "required": true,
@@ -4890,7 +4894,7 @@ const openapiDocument = {
4890
4894
  "patch": {
4891
4895
  "operationId": "updateEndpoint",
4892
4896
  "summary": "Update a webhook endpoint",
4893
- "description": "Updates an active webhook endpoint. Changing the URL keeps the endpoint: its id, recipient routes, signing secret, delivery history and any deliveries still owed to it stay with it, and owed deliveries go to the new URL. Another active endpoint at the new URL is refused.\n\n\nPull destinations cannot be assigned a URL, function or recipient-routing domain. Existing endpoint deletion disconnects the destination; creating a new destination does not backfill historical events.",
4897
+ "description": "Updates an active webhook endpoint. Changing the URL keeps the endpoint: its id, recipient routes, signing secret, delivery history and any deliveries still owed to it stay with it, and owed deliveries go to the new URL. A URL can have one endpoint per domain, route targets included (unlike the one default destination per domain), and endpoints with no domain_id count as one domain. A disabled endpoint still holds its URL; a deleted one does not. A change onto a URL another endpoint has on the same domain is refused (400), and a move onto a domain where another endpoint has this URL is refused (409).\n\n\nPull destinations cannot be assigned a URL, function or recipient-routing domain. Existing endpoint deletion disconnects the destination; creating a new destination does not backfill historical events.\n\nGiving a webhook the organization does not own to a different key (owner_key_id), or a key's webhook back to the organization (owner_key_id null), turns it off unless the request also sets enabled: its URL was chosen under its previous owner. It then reads enabled false with disabled_reason owner_changed, and turning it on or giving it a new URL starts it again. A key that carries on for the owning key through rotation (its successor, at any depth up to 16 rotations) is not a different key: the webhook stays as it was, with the same signing secret. That does not hold when the owning key was made from a key a transfer moved or rotated away, or from one that has since been deleted: no successor carries on for it. The answer's warnings name each such change (turned_off_owner_changed, turned_on_new_url), so a client can say so.",
4894
4898
  "tags": ["Endpoints"],
4895
4899
  "requestBody": {
4896
4900
  "required": true,
@@ -4901,7 +4905,7 @@ const openapiDocument = {
4901
4905
  "description": "Updated endpoint",
4902
4906
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
4903
4907
  "type": "object",
4904
- "properties": { "data": { "$ref": "#/components/schemas/Endpoint" } }
4908
+ "properties": { "data": { "$ref": "#/components/schemas/UpdatedEndpoint" } }
4905
4909
  }] } } },
4906
4910
  "headers": {
4907
4911
  "ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
@@ -5086,7 +5090,7 @@ const openapiDocument = {
5086
5090
  "get": {
5087
5091
  "operationId": "getEndpoint",
5088
5092
  "summary": "Get an endpoint",
5089
- "description": "One endpoint, as GET /endpoints shows it, to a caller who would see it there. Any other id, a deleted endpoint's included, answers 404.",
5093
+ "description": "One endpoint, as GET /endpoints shows it, to a caller who would see it there. Any other id, a deleted endpoint's included, answers 404. For a webhook, delivery says whether it is being sent received mail at all, why not, and how much rule-granted mail is held while its owner cannot act.",
5090
5094
  "tags": ["Endpoints"],
5091
5095
  "responses": {
5092
5096
  "200": {
@@ -5499,7 +5503,7 @@ const openapiDocument = {
5499
5503
  "post": {
5500
5504
  "operationId": "testEndpoint",
5501
5505
  "summary": "Send a test webhook",
5502
- "description": "Sends a sample `email.received` event to the endpoint. The request\nincludes SSRF protection (private IP rejection and DNS pinning).\nRate limited to 4 per minute and 30 per hour (non-exempt).\nSuccessful deliveries and verified-domain endpoints are exempt\nfrom the rate limit.\n",
5506
+ "description": "Sends a sample `email.received` event to the endpoint. The request\nincludes SSRF protection (private IP rejection and DNS pinning).\nRate limited to 4 per minute and 30 per hour (non-exempt).\nSuccessful deliveries and verified-domain endpoints are exempt\nfrom the rate limit.\n\n\nThe test is sent whether or not real mail would be. reachable says whether the endpoint answered it with a 2xx; delivery says, apart from that, whether real email.received events would be sent to the endpoint by its own conditions (on, not held, an owner that can receive mail, subscribed to the event), with every reason it would not be. A 200 from the test alone does not mean mail arrives.",
5503
5507
  "tags": ["Endpoints"],
5504
5508
  "responses": {
5505
5509
  "200": {
@@ -5613,7 +5617,7 @@ const openapiDocument = {
5613
5617
  "post": {
5614
5618
  "operationId": "testEndpointRules",
5615
5619
  "summary": "Dry-run endpoint rules against a received email",
5616
- "description": "Evaluates the endpoint's filtering rules against an\nalready-received email WITHOUT delivering anything. The same\nshared matcher the live delivery paths use produces the\nverdict, so the response explains exactly why a webhook fired\nor was suppressed for that message.\n\nWhen delivery would be suppressed, `rule` names the failing\nrule and `reason` carries a human-readable explanation; both\nare null when the message matches. `evaluated` echoes the\nmessage metadata the matcher compared (size, attachments, and\nthe authenticated From identity versus the raw envelope\nsender), so a surprising verdict can be traced to its inputs.\n\nTwo independent gates are surfaced separately:\n`subscribed_to_event` reports the endpoint's event-type\nsubscription (checked before message matching), and\n`rules_valid` reports whether the stored rules blob parsed at\nall. Delivery fails OPEN on an invalid blob (the message is\ndelivered as if unfiltered), so `rules_valid: false` exposes a\nmisconfiguration that is otherwise silent.\n",
5620
+ "description": "Evaluates the endpoint's filtering rules against an\nalready-received email WITHOUT delivering anything. The same\nshared matcher the live delivery paths use produces the\nverdict, so the response explains exactly why a webhook fired\nor was suppressed for that message.\n\nWhen delivery would be suppressed, `rule` names the failing\nrule and `reason` carries a human-readable explanation; both\nare null when the message matches. `evaluated` echoes the\nmessage metadata the matcher compared (size, attachments, and\nthe authenticated From identity versus the raw envelope\nsender), so a surprising verdict can be traced to its inputs.\n\nTwo independent gates are surfaced separately:\n`subscribed_to_event` reports the endpoint's event-type\nsubscription (checked before message matching), and\n`rules_valid` reports whether the stored rules blob parsed at\nall. Delivery fails OPEN on an invalid blob (the message is\ndelivered as if unfiltered), so `rules_valid: false` exposes a\nmisconfiguration that is otherwise silent.\n\n\nwould_deliver also covers the endpoint's own delivery conditions for this email (delivery): it is on, neither it nor the email's route is held, its owner may receive this email, and it subscribes to the event. Which endpoint routing sends the email to is decided separately: POST /routes/simulate answers that. A 503 means those conditions could not be read just now; retry.",
5617
5621
  "tags": ["Endpoints"],
5618
5622
  "requestBody": {
5619
5623
  "required": true,
@@ -5677,6 +5681,19 @@ const openapiDocument = {
5677
5681
  }
5678
5682
  }
5679
5683
  } }
5684
+ },
5685
+ "503": {
5686
+ "description": "Primitive is temporarily unable to process the request",
5687
+ "content": { "application/json": {
5688
+ "schema": { "$ref": "#/components/schemas/ErrorResponse" },
5689
+ "example": {
5690
+ "success": false,
5691
+ "error": {
5692
+ "code": "outbound_capacity_exhausted",
5693
+ "message": "Outbound capacity is temporarily exhausted"
5694
+ }
5695
+ }
5696
+ } }
5680
5697
  }
5681
5698
  },
5682
5699
  "security": [{ "BearerAuth": [] }],
@@ -16229,7 +16246,7 @@ const openapiDocument = {
16229
16246
  "tags": ["Account"],
16230
16247
  "security": [{ "BearerAuth": [] }],
16231
16248
  "summary": "Get current caller identity",
16232
- "description": "Current authenticated identity. member_address is null for machine credentials or an unavailable member address.",
16249
+ "description": "Current authenticated identity. member_address is null for machine credentials or an unavailable member address. email (your own sign-in email) and role_name (your role as the members list labels it) are set only when you are signed in as yourself, in the dashboard or with your own Primitive CLI or app login, and are null for API keys, Functions, connected agents, agent signup logins and other apps acting for you.",
16233
16250
  "parameters": [
16234
16251
  {
16235
16252
  "name": "include",
@@ -16300,6 +16317,14 @@ const openapiDocument = {
16300
16317
  "request_id": { "type": "string" },
16301
16318
  "auth_method": { "type": ["string", "null"] },
16302
16319
  "key_id": { "type": ["string", "null"] },
16320
+ "email": {
16321
+ "description": "Your own sign-in email, when you are signed in as yourself: in the dashboard, or with your own Primitive CLI or app login. Null for API keys, Functions, connected agents, agent signup logins and other apps acting for you.",
16322
+ "type": ["string", "null"]
16323
+ },
16324
+ "role_name": {
16325
+ "description": "Your role as the organization's members list labels it: a template or custom role as \"<name> (template)\" or \"<name> (custom)\", otherwise Owner, Admin, Member or Restricted. Null for the same credentials as email.",
16326
+ "type": ["string", "null"]
16327
+ },
16303
16328
  "grant": {
16304
16329
  "type": ["object", "null"],
16305
16330
  "propertyNames": { "type": "string" },
@@ -16336,7 +16361,9 @@ const openapiDocument = {
16336
16361
  "role",
16337
16362
  "request_id",
16338
16363
  "auth_method",
16339
- "key_id"
16364
+ "key_id",
16365
+ "email",
16366
+ "role_name"
16340
16367
  ]
16341
16368
  }
16342
16369
  }
@@ -21726,16 +21753,28 @@ const openapiDocument = {
21726
21753
  } },
21727
21754
  "/activity/objects": { "get": {
21728
21755
  "operationId": "listActivityObjects",
21729
- "summary": "List what you created",
21730
- "description": "The objects the caller created, newest first, each with where it stands now, including ones since disabled, deleted or revoked where the object keeps a row: Functions, endpoints, Function triggers, pull subscriptions, recipient routes, filters, wake schedules, repeating sends and scheduled sends. An API key, an app or an agent sees what it created itself; a webhook given to an API key lists under that key. A person (a dashboard session, or their own Primitive CLI or app login) sees what they created through any credential of theirs, each item naming it, and also their API keys, personal agents, email rules and rule requests. Each kind is listed only if the caller could list it through that kind's own route; a kind it could not is named in withheld, with no count. Ids, names and states only: never a webhook URL or mail content. Needs activity:read_self.",
21756
+ "summary": "List what you created, or what you were asked",
21757
+ "description": "The objects the caller created, newest first, each with where it stands now, including ones since disabled, deleted or revoked where the object keeps a row: Functions, endpoints, Function triggers, pull subscriptions, recipient routes, filters, wake schedules, repeating sends and scheduled sends. An API key, an app or an agent sees what it created itself; a webhook given to an API key lists under that key. A person (a dashboard session, or their own Primitive CLI or app login) sees what they created through any credential of theirs, each item naming it, and also their API keys, personal agents, email rules and rule requests. Each kind is listed only if the caller could list it through that kind's own route; a kind it could not is named in withheld, with no count. Ids, names and states only: never a webhook URL or mail content. Needs activity:read_self. With relation=asked it lists instead what other members asked of the person, open or answered: the rule requests made to them, under their current membership, each naming the member who asked in created_through. A key, an app or an agent has none, and gets email_rule_request in withheld.",
21731
21758
  "tags": ["Activity"],
21732
21759
  "security": [{ "BearerAuth": [] }],
21733
21760
  "parameters": [
21761
+ {
21762
+ "name": "relation",
21763
+ "in": "query",
21764
+ "required": false,
21765
+ "description": "created (the default) or asked.",
21766
+ "schema": {
21767
+ "default": "created",
21768
+ "description": "created (the default): what you created. asked: what other members asked of you, open or answered: rule requests made to you. asked is for a person's own view only, and kind may then name only email_rule_request.",
21769
+ "type": "string",
21770
+ "enum": ["created", "asked"]
21771
+ }
21772
+ },
21734
21773
  {
21735
21774
  "name": "kind",
21736
21775
  "in": "query",
21737
21776
  "required": false,
21738
- "description": "Only these kinds, comma-separated: function, endpoint, function_trigger, pull_subscription, recipient_route, filter_rule, wake_schedule, repeating_send, scheduled_send, api_key, agent_connection, email_rule, email_rule_request. Defaults to every kind the caller can have created (a person's own kinds only for a person).",
21777
+ "description": "Only these kinds, comma-separated: function, endpoint, function_trigger, pull_subscription, recipient_route, filter_rule, wake_schedule, repeating_send, scheduled_send, api_key, agent_connection, email_rule, email_rule_request. Defaults to every kind the caller can have created (a person's own kinds only for a person). With relation=asked, only email_rule_request, which is also the default.",
21739
21778
  "schema": {
21740
21779
  "type": "string",
21741
21780
  "minLength": 1,
@@ -21782,7 +21821,7 @@ const openapiDocument = {
21782
21821
  ],
21783
21822
  "responses": {
21784
21823
  "200": {
21785
- "description": "List what you created",
21824
+ "description": "List what you created, or what you were asked",
21786
21825
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
21787
21826
  "type": "object",
21788
21827
  "required": ["data"],
@@ -22688,7 +22727,7 @@ const openapiDocument = {
22688
22727
  "delete": {
22689
22728
  "operationId": "deleteEmailRule",
22690
22729
  "summary": "Delete a rule",
22691
- "description": "Deletes a rule with its versions, its decisions and the access it granted. The author, or a caller who reads the whole organization's mail, may delete it; while it is shared with another member, only the author or someone who manages members from the dashboard. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22730
+ "description": "Deletes a rule with its versions, its decisions and the access it granted. A rule that was ever shared with another member keeps the record of its share: the share ends with end_reason rule_deleted, and the member who shared can still list what it handed over, while the rule is no longer shown and reads nothing. The answer lists the shares the delete ended (ended_shares) and the keys, not revoked, that still list the rule, which now read nothing through it: those you may read as key metadata by id, name and prefix (keys), and how many others (other_keys). keys and other_keys are left out when the keys could not be read. The author, or a caller who reads the whole organization's mail, may delete it; while it is shared with another member, only the author or someone who manages members from the dashboard. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
22692
22731
  "tags": ["Email Rules"],
22693
22732
  "security": [{ "BearerAuth": [] }],
22694
22733
  "parameters": [{
@@ -23339,7 +23378,7 @@ const openapiDocument = {
23339
23378
  "post": {
23340
23379
  "operationId": "createEmailRuleRequest",
23341
23380
  "summary": "Ask a member for a rule",
23342
- "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, or by their verified work email if you can list members (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. If you can list members (members:read), the answer also says whether they can accept it under their role (target_can_accept), and if not, why (target_warning); the request is sent either way. To find out before asking, use POST /v1/email-rule-requests/target-check. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23381
+ "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, or by their verified work email if you can list members (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. A message (up to 500 characters) tells them why you are asking; they see it with the request, and it cannot be changed later. You may have at most 5 pending requests to one member and 50 in all. If you can list members (members:read), the answer also says whether they can accept it under their role (target_can_accept), and if not, why (target_warning); the request is sent either way. To find out before asking, use POST /v1/email-rule-requests/target-check. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23343
23382
  "tags": ["Email Rules"],
23344
23383
  "security": [{ "BearerAuth": [] }],
23345
23384
  "requestBody": {
@@ -23356,7 +23395,7 @@ const openapiDocument = {
23356
23395
  }] } } }
23357
23396
  },
23358
23397
  "400": {
23359
- "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.",
23398
+ "description": "validation_error: give exactly one of target_user_id and target_email, a tree that is valid for the column contract, and a message, if any, of 1 to 500 characters; details.reason self_request when the member named is you.",
23360
23399
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23361
23400
  },
23362
23401
  "401": {
@@ -23674,7 +23713,7 @@ const openapiDocument = {
23674
23713
  "get": {
23675
23714
  "operationId": "getEmailRuleRequest",
23676
23715
  "summary": "Read a rule request",
23677
- "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 member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23716
+ "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. If you are the member asked, you can try the rule as proposed before answering: pass the request's tree to POST /v1/email-rules/test to test it on mail you have received. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up. Nothing is stored, and it needs no access beyond testing a rule of your own. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23678
23717
  "tags": ["Email Rules"],
23679
23718
  "security": [{ "BearerAuth": [] }],
23680
23719
  "parameters": [{
@@ -23847,7 +23886,7 @@ const openapiDocument = {
23847
23886
  "/email-rule-requests/{id}/decline": { "post": {
23848
23887
  "operationId": "declineEmailRuleRequest",
23849
23888
  "summary": "Decline a rule request",
23850
- "description": "Declines a pending request made to you. Nothing is written but the decision. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23889
+ "description": "Declines a pending request made to you. Nothing is written but the decision and, if you give one, its reason (reason, up to 500 characters), which the member who asked sees with the request. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
23851
23890
  "tags": ["Email Rules"],
23852
23891
  "security": [{ "BearerAuth": [] }],
23853
23892
  "parameters": [{
@@ -23870,6 +23909,10 @@ const openapiDocument = {
23870
23909
  "maxLength": 255
23871
23910
  }
23872
23911
  }],
23912
+ "requestBody": {
23913
+ "required": false,
23914
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeclineEmailRuleRequestRequest" } } }
23915
+ },
23873
23916
  "responses": {
23874
23917
  "200": {
23875
23918
  "description": "Decline a rule request",
@@ -23885,6 +23928,10 @@ const openapiDocument = {
23885
23928
  "ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
23886
23929
  }
23887
23930
  },
23931
+ "400": {
23932
+ "description": "validation_error: reason is empty or longer than 500 characters.",
23933
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
23934
+ },
23888
23935
  "401": {
23889
23936
  "description": "Invalid or missing API key",
23890
23937
  "content": { "application/json": {
@@ -24043,7 +24090,7 @@ const openapiDocument = {
24043
24090
  "/email-rule-requests/{id}/accept": { "post": {
24044
24091
  "operationId": "acceptEmailRuleRequest",
24045
24092
  "summary": "Accept a rule request",
24046
- "description": "Writes the rule as yours, over your own mailboxes only, turns it on, and shares what it matches from now on with the member who asked. Send no body to accept it as asked, or change it first with english and tree together (and an explanation, or the server writes one). Because this gives your mail to someone else, it is limited to the dashboard and the Primitive CLI: other OAuth apps and agent tokens are refused. Also needs mail:share_own (sharing your own mail through email rules, which only shows it and never sends): a role without it is refused, whether or not it can send. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
24093
+ "description": "Writes the rule as yours, over your own mailboxes only, turns it on, and shares what it matches from now on with the member who asked. Send no body to accept it as asked, or change it first with english and tree together (and an explanation, or the server writes one). Because this gives your mail to someone else, it is limited to the dashboard and the Primitive CLI: other OAuth apps and agent tokens are refused. Before accepting, test it on your mail: pass the request's tree to POST /v1/email-rules/test. A test on mail already received shows only what has arrived. To see what the rule would do with an email that has not, such as one you would never want shared, pass the request's tree to POST /v1/email-rules/test-sample (testEmailRuleOnSample; in the Primitive CLI, primitive email-rules test-email-rule-on-sample --body-file sample.json) with an email you make up. Nothing is stored, and it needs no access beyond testing a rule of your own. Accepting also needs mail:share_own (sharing your own mail through email rules, which only shows it and never sends): a role without it is refused, whether or not it can send. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
24047
24094
  "tags": ["Email Rules"],
24048
24095
  "security": [{ "BearerAuth": [] }],
24049
24096
  "parameters": [{
@@ -32110,7 +32157,7 @@ const openapiDocument = {
32110
32157
  "summary": "List every credential",
32111
32158
  "tags": ["Credentials"],
32112
32159
  "security": [{ "BearerAuth": [] }],
32113
- "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. A key's grant.mail_scope.depth is raw only when it holds mail.raw:read and its scope lists an address or a domain, otherwise content. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
32160
+ "description": "Every credential that can act in the organization: API keys, Function keys, agent connection keys, connected apps (OAuth), agent delegates and, where enabled, CLI logins. Secrets are never returned. A key's grant.mail_scope.depth is raw only when it holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. Filter with kind. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session. The person's role must hold credentials:read. API keys, agent connection keys and agents are refused.",
32114
32161
  "x-primitive-person-only": { "access": "session" },
32115
32162
  "parameters": [{
32116
32163
  "name": "kind",
@@ -32239,7 +32286,7 @@ const openapiDocument = {
32239
32286
  "summary": "Revoke a credential",
32240
32287
  "tags": ["Credentials"],
32241
32288
  "security": [{ "BearerAuth": [] }],
32242
- "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up keeps working and is listed in standing_objects for review. The person's own Primitive CLI login revokes an API key (kind org_key) as the person, with no step-up unless the organization's credential policy sets cli_writes_require_approval (then it takes a keys.create step-up); every other kind (a connected key named as org_key included, 403 grant_forbidden target_not_covered), and every other app even with a step-up, is refused outside the dashboard (403). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
32289
+ "description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked only by a person whose own access covers the key's access (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. What the credential set up or owns is listed in standing_objects for review, and for an API key also what it carries on for the keys it was rotated from. An organization-owned object (org_owned true) keeps working; any other keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to, except that a block filter an app made keeps blocking, within the reach the app had, unless the person revoking chose to stop it. An API key (kind org_key or connected_key) that is already revoked, and whose record is kept, answers 200 with already_revoked true and revoked 0, to a caller who could have revoked it; anyone else gets the answer they would get for a key that does not exist. The person's own Primitive CLI login revokes an API key (kind org_key) as the person, with no step-up unless the organization's credential policy sets cli_writes_require_approval (then it takes a keys.create step-up); every other kind (a connected key named as org_key included, 403 grant_forbidden target_not_covered), and every other app even with a step-up, is refused outside the dashboard (403). Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:manage. API keys, agent connection keys and agents are refused.",
32243
32290
  "x-primitive-person-only": { "access": "session_or_cli" },
32244
32291
  "parameters": [{
32245
32292
  "name": "Primitive-Step-Up",
@@ -33221,7 +33268,7 @@ const openapiDocument = {
33221
33268
  "summary": "List API keys",
33222
33269
  "tags": ["Credentials"],
33223
33270
  "security": [{ "BearerAuth": [] }],
33224
- "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry, suspension and whether each key works now (status). A grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
33271
+ "description": "Key metadata, never a secret: name, prefix, grant, creator, last use, expiry, suspension and whether each key works now (status). Revoked keys are left out unless include_revoked is true, which lists them too with status revoked, revoked_at and revoked_by, under the same access rules as keys that work. A grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
33225
33272
  "x-primitive-person-only": { "access": "session_or_cli" },
33226
33273
  "parameters": [{
33227
33274
  "name": "cursor",
@@ -33232,6 +33279,15 @@ const openapiDocument = {
33232
33279
  "type": "string",
33233
33280
  "maxLength": 200
33234
33281
  }
33282
+ }, {
33283
+ "name": "include_revoked",
33284
+ "in": "query",
33285
+ "required": false,
33286
+ "description": "true to list revoked keys as well, newest first among the rest; false (the default) lists only keys that are not revoked. The cursor does not carry it: send it with every page.",
33287
+ "schema": {
33288
+ "type": "string",
33289
+ "enum": ["true", "false"]
33290
+ }
33235
33291
  }],
33236
33292
  "responses": {
33237
33293
  "200": {
@@ -33344,7 +33400,7 @@ const openapiDocument = {
33344
33400
  "summary": "Rotate an API key",
33345
33401
  "tags": ["Credentials"],
33346
33402
  "security": [{ "BearerAuth": [] }],
33347
- "description": "Mint a successor with a new id and the same name, grant, expiry and conditions, and return its secret once. The old key keeps working for overlap_seconds, then stops: 24 hours when overlap_seconds is omitted, at most 7 days, never past its own expires_at, and at once with 0 (which is how to rotate a key whose secret leaked). previous_key_expires_at says when the old key stops. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner holds is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33403
+ "description": "Mint a successor with a new id and the same name, grant, expiry and conditions, and return its secret once. The old key keeps working for overlap_seconds, then stops: 24 hours when overlap_seconds is omitted, at most 7 days, never past its own expires_at, and at once with 0 (which is how to rotate a key whose secret leaked). previous_key_expires_at says when the old key stops. Rotating does not extend a key: the successor keeps the old expiry unless expires_at gives it a new one. That needs keys:manage (403 forbidden otherwise), is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and someone limited to their own keys can only keep or bring it forward (403 grant_forbidden, details.reason own_key_tighten_only); the old key then never outlives the new one. The person rotating must cover the key's access (403 grant_forbidden, details.reason target_not_covered), and a key an owner holds is rotated by an owner only (403 forbidden, details.reason owner_key_rotation), since rotating hands over the new secret. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33348
33404
  "x-primitive-person-only": {
33349
33405
  "access": "step_up",
33350
33406
  "step_up_purpose": "keys.create",
@@ -33511,7 +33567,7 @@ const openapiDocument = {
33511
33567
  "summary": "Change an API key",
33512
33568
  "tags": ["Credentials"],
33513
33569
  "security": [{ "BearerAuth": [] }],
33514
- "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33570
+ "description": "Rename a key, change its expiry, its grant or its conditions, or suspend it. A later expires_at extends the key, which is how to keep a key working past its expiry (rotating keeps it, unless the rotation gives the new key an expires_at): it is held to the credential policy (400 when past its maximum lifetime, with the latest expiry it allows now in the message), and like any loosening needs the person to be allowed to create a key with the key's access. An expired key's expiry cannot be changed (409 conflict, details.reason key_expired); make a new key instead. 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. It replaces the whole grant: send expected_grant_ref (grant.ref from GET /v1/keys/{id}) to have it refused (409 conflict, details.reason grant_changed) if the grant changed since that read; a grant change sent without it answers grant_unguarded: true. On a key limited to selected resources (grant.resources), a grant change must drop the limits on purpose with a custom grant and grant.resources {}; one that leaves them out is refused (409 conflict, details.reason key_has_resource_limits) and nothing changes. The person's own Primitive CLI login needs no step-up (it acts as the person, held to the same rules) unless the organization's credential policy sets cli_writes_require_approval; the Primitive app's token is refused, even with one. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session, the person's own Primitive CLI login with no step-up (unless the organization's credential policy sets cli_writes_require_approval), or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one any other OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role, or the client, could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.",
33515
33571
  "x-primitive-person-only": {
33516
33572
  "access": "step_up",
33517
33573
  "step_up_purpose": "keys.create",
@@ -33663,7 +33719,7 @@ const openapiDocument = {
33663
33719
  "summary": "Get an API key",
33664
33720
  "tags": ["Credentials"],
33665
33721
  "security": [{ "BearerAuth": [] }],
33666
- "description": "One key's metadata, never its secret, with whether it works now (status). Its grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
33722
+ "description": "One key's metadata, never its secret, with whether it works now (status). A revoked key whose record is kept is answered too, with status revoked, revoked_at and revoked_by, under the same access rules as a key that works: one the caller may not read is 404, revoked or not. Its grant's mail_scope.depth is raw only when the key holds mail.raw:read and its scope lists an address or a domain, otherwise content. A rule the key lists that reads nothing any more (a deleted rule, or a share that ended) is listed under grant.mail_scope.ended_rules, each with its id and reason (rule_deleted or share_ended), and not under rules. The person needs access to the whole organization's mail. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled, with details.feature naming the feature. Contact Primitive to turn them on.\n\nPerson only: a dashboard session or the person's own Primitive CLI token; no other OAuth app. The person's role must hold keys:read. API keys, agent connection keys and agents are refused.",
33667
33723
  "x-primitive-person-only": { "access": "session_or_cli" },
33668
33724
  "parameters": [{
33669
33725
  "name": "id",
@@ -45040,13 +45096,13 @@ const openapiDocument = {
45040
45096
  "type": "boolean"
45041
45097
  },
45042
45098
  "owner_key_id": {
45043
- "description": "The API key that owns this webhook. In endpoint responses it is set only for a caller who may read API key metadata, and is null otherwise and when the organization owns the webhook. After a rotation, the key that carries on for the key first named while it can act.",
45099
+ "description": "The API key that owns this webhook. In endpoint responses it is set only for a caller who may read API key metadata, and is null otherwise and when the organization owns the webhook. After a rotation, the key that carries on for the key first named while it can act. When none can, the key delivery stops on: the key first named while it is suspended and not expired, otherwise the newest key of its rotation that is not revoked, otherwise the newest one (only the key first named when nothing may carry on for it).",
45044
45100
  "type": ["string", "null"],
45045
45101
  "format": "uuid",
45046
45102
  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
45047
45103
  },
45048
45104
  "owner_key_status": {
45049
- "description": "Whether the owning key can act now. In endpoint responses it is set only for a caller who may read API key metadata. Suspended also covers an expired key. A suspended or revoked owner is sent nothing.",
45105
+ "description": "Whether the owning key can act now. In endpoint responses it is set only for a caller who may read API key metadata. It is the state delivery acts on. Suspended also covers an expired key. A suspended or revoked owner is sent nothing. Anyone who may read the endpoint learns that from delivery.reasons, which never names the key.",
45050
45106
  "type": ["string", "null"],
45051
45107
  "enum": [
45052
45108
  "active",
@@ -45055,6 +45111,65 @@ const openapiDocument = {
45055
45111
  null
45056
45112
  ]
45057
45113
  },
45114
+ "disabled_reason": {
45115
+ "description": "Why the platform turned this endpoint off, when it names a reason. owner_changed: it was given to a new owner while its URL stayed, and the URL was chosen under the previous owner; turning it on or giving it a new URL starts it again. owner_credential_revoked: the third-party app or agent delegate grant that owned it was revoked; an owner or admin who turns it back on takes it over. Null while it is on and when it was turned off by hand. An access suspension does not set it (a suspension is decided through its own record). Other values may be added.",
45116
+ "type": ["string", "null"]
45117
+ },
45118
+ "delivery": {
45119
+ "description": "For a webhook (kind http) in GET /endpoints and GET /endpoints/{id}: whether it is being sent received mail, and why not. Shown to anyone who may read the endpoint; it names a state, never a key. Null when it could not be checked just now. Absent from other kinds of endpoint and from create and update answers.",
45120
+ "type": ["object", "null"],
45121
+ "properties": {
45122
+ "delivering": {
45123
+ "type": "boolean",
45124
+ "description": "Whether the webhook is being sent received mail at all now: it is on, it is not held, and its owner can receive mail. Which messages reach it is still decided per message, by routing, rule grants and its own rules."
45125
+ },
45126
+ "reasons": {
45127
+ "type": "array",
45128
+ "items": {
45129
+ "type": "object",
45130
+ "properties": {
45131
+ "code": { "type": "string" },
45132
+ "message": { "type": "string" }
45133
+ },
45134
+ "required": ["code", "message"],
45135
+ "additionalProperties": {}
45136
+ },
45137
+ "description": "Every reason it is not, in the order delivery checks them; empty when it is. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place."
45138
+ },
45139
+ "held_for_owner": {
45140
+ "description": "Rule-granted mail held for this webhook while its owner cannot act; null when none is. Only for a caller with access to every mailbox (held mail can be an earlier owner's), and absent when it could not be read just now.",
45141
+ "type": ["object", "null"],
45142
+ "properties": {
45143
+ "count": {
45144
+ "type": "integer",
45145
+ "minimum": -9007199254740991,
45146
+ "maximum": 9007199254740991,
45147
+ "description": "Rule-granted emails waiting for this webhook because its owner could not act at their last check."
45148
+ },
45149
+ "oldest_queued_at": {
45150
+ "type": "string",
45151
+ "format": "date-time",
45152
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
45153
+ "description": "When the oldest of them was queued."
45154
+ },
45155
+ "expires_at": {
45156
+ "type": "string",
45157
+ "format": "date-time",
45158
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
45159
+ "description": "When the oldest is given up on if its owner still cannot act. Each is checked again periodically, so the count can trail a change of the owner's state."
45160
+ }
45161
+ },
45162
+ "required": [
45163
+ "count",
45164
+ "oldest_queued_at",
45165
+ "expires_at"
45166
+ ],
45167
+ "additionalProperties": {}
45168
+ }
45169
+ },
45170
+ "required": ["delivering", "reasons"],
45171
+ "additionalProperties": {}
45172
+ },
45058
45173
  "signed_with": {
45059
45174
  "description": "Which secret signs deliveries to this webhook: its own (read it from GET /endpoints/{id}/signing-secret), or the organization's webhook secret. Absent for an endpoint that is not a webhook.",
45060
45175
  "type": "string",
@@ -45262,7 +45377,7 @@ const openapiDocument = {
45262
45377
  "description": "For a move to another domain_id whose slot already has an active endpoint: false (the default) answers 409 conflict with details.holder naming it; true retires that endpoint in the same request, after the checks deleting it would need."
45263
45378
  },
45264
45379
  "owner_key_id": {
45265
- "description": "Give this webhook to an API key of the organization. Only a route target can be given to a key; a default endpoint stays the organization's, and takeover is refused with it. null gives a key-owned webhook back to the organization (a caller with access to every mailbox only); it stays a route target.",
45380
+ "description": "Give this webhook to an API key of the organization. Only a route target can be given to a key; a default endpoint stays the organization's, and takeover is refused with it. null gives a key-owned webhook back to the organization (a caller with access to every mailbox only); it stays a route target. Giving a webhook the organization does not own to a different key, or a key's webhook back to the organization, turns it off (enabled false, disabled_reason owner_changed) unless the same request sets enabled: its URL was chosen under its previous owner. A key that carries on for the key that owns it through rotation (its successor, at any depth up to 16 rotations) is not a different key: the webhook stays as it was, with the same signing secret. That does not hold for a webhook whose owning key was made from a key a transfer moved or rotated away, or from one that has since been deleted: no successor carries on for it, so giving the webhook to one is a change of owner. Turning it on, or a new URL, starts it again. A webhook that signs with its own secret keeps signing with one: the same one when it stays with the same key, or was the organization's (giving away one the organization owns needs webhook_secret:read, since the key and its holder can then read its secret), and a new one for any other owner's. The response's warnings say when an update turned the webhook off or on.",
45266
45381
  "type": ["string", "null"],
45267
45382
  "format": "uuid",
45268
45383
  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
@@ -45289,9 +45404,58 @@ const openapiDocument = {
45289
45404
  "signature": {
45290
45405
  "type": "string",
45291
45406
  "description": "The signature header value sent, answered to a caller who may see it: for a webhook signed with the organization's secret, a caller who may read that secret; for one signed with its own, the person who owns it, calling as themselves."
45407
+ },
45408
+ "reachable": {
45409
+ "type": "boolean",
45410
+ "description": "Whether the endpoint answered the test with a 2xx status: the request reached it and its handler accepted it. It says nothing about whether real mail is sent to it: see delivery."
45411
+ },
45412
+ "delivery": {
45413
+ "description": "Whether real email.received events would be sent to this endpoint, judged by the endpoint's own conditions as delivery judges them. Null when that could not be checked just now.",
45414
+ "type": ["object", "null"],
45415
+ "properties": {
45416
+ "event_type": {
45417
+ "type": "string",
45418
+ "description": "The event type checked."
45419
+ },
45420
+ "would_deliver": {
45421
+ "type": "boolean",
45422
+ "description": "Whether this endpoint's own conditions let the event through: it is on, it is not held, its owner can receive it, and it subscribes to the event type. Which endpoint routing or a rule grant sends a message to is decided separately, per message: POST /routes/simulate answers that, and a rule grant can deliver an email outside its owner's mailboxes. A Function endpoint is also judged at delivery on its Function's run state and key."
45423
+ },
45424
+ "reasons": {
45425
+ "type": "array",
45426
+ "items": {
45427
+ "type": "object",
45428
+ "properties": {
45429
+ "code": { "type": "string" },
45430
+ "message": { "type": "string" }
45431
+ },
45432
+ "required": ["code", "message"],
45433
+ "additionalProperties": {}
45434
+ },
45435
+ "description": "Every reason it would not be sent the event, in the order delivery checks them; empty when it would. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place. event_type_not_subscribed: rules.event_types leaves the event out."
45436
+ },
45437
+ "events_paused_until": {
45438
+ "description": "While repeated failures have paused this webhook (its circuit breaker is open): when webhook events other than mail (interaction, sent mail and credential events) are next attempted. Received mail is attempted regardless, so this does not change would_deliver. Null when nothing is paused, and for a Function endpoint.",
45439
+ "type": ["string", "null"],
45440
+ "format": "date-time",
45441
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
45442
+ }
45443
+ },
45444
+ "required": [
45445
+ "event_type",
45446
+ "would_deliver",
45447
+ "reasons",
45448
+ "events_paused_until"
45449
+ ],
45450
+ "additionalProperties": {}
45292
45451
  }
45293
45452
  },
45294
- "required": ["status", "body"]
45453
+ "required": [
45454
+ "status",
45455
+ "body",
45456
+ "reachable",
45457
+ "delivery"
45458
+ ]
45295
45459
  },
45296
45460
  "TestEndpointRulesInput": {
45297
45461
  "type": "object",
@@ -45309,15 +45473,15 @@ const openapiDocument = {
45309
45473
  "properties": {
45310
45474
  "would_deliver": {
45311
45475
  "type": "boolean",
45312
- "description": "Whether the delivery path would deliver this email to the endpoint."
45476
+ "description": "Whether this email would be sent to this endpoint by the endpoint's own conditions: it is on, neither it nor the email's route is held, its owner may receive this email, it subscribes to the event, and its rules match. Which endpoint routing sends the email to is decided separately: POST /routes/simulate answers that. A rule grant can still deliver an email whose mailbox the owner does not read, and a Function endpoint is also judged at delivery on its Function's run state and key."
45313
45477
  },
45314
45478
  "rule": {
45315
- "type": ["string", "null"],
45316
- "description": "Name of the failing rule when delivery would be\nsuppressed; null when the message matches and the\nendpoint is subscribed to the event type.\n"
45479
+ "description": "The first check that stops this email, in the order delivery makes them: disabled, held or an acting_owner_* code (see delivery.reasons), then event_types when the endpoint does not subscribe, then the name of the rule that does not match. Null when it would deliver.",
45480
+ "type": ["string", "null"]
45317
45481
  },
45318
45482
  "reason": {
45319
- "type": ["string", "null"],
45320
- "description": "Human-readable explanation of the failing rule; null when the message matches."
45483
+ "description": "Why, in words. Null when it would deliver.",
45484
+ "type": ["string", "null"]
45321
45485
  },
45322
45486
  "rules_valid": {
45323
45487
  "type": "boolean",
@@ -45364,6 +45528,46 @@ const openapiDocument = {
45364
45528
  "sender_authenticated",
45365
45529
  "sender_trust_basis"
45366
45530
  ]
45531
+ },
45532
+ "delivery": {
45533
+ "description": "The endpoint's own conditions for this email, as delivery judges them, the owner judged on this email's mailbox and time. Null for a pull destination, which nothing pushes to.",
45534
+ "type": ["object", "null"],
45535
+ "properties": {
45536
+ "event_type": {
45537
+ "type": "string",
45538
+ "description": "The event type checked."
45539
+ },
45540
+ "would_deliver": {
45541
+ "type": "boolean",
45542
+ "description": "Whether this endpoint's own conditions let the event through: it is on, it is not held, its owner can receive it, and it subscribes to the event type. Which endpoint routing or a rule grant sends a message to is decided separately, per message: POST /routes/simulate answers that, and a rule grant can deliver an email outside its owner's mailboxes. A Function endpoint is also judged at delivery on its Function's run state and key."
45543
+ },
45544
+ "reasons": {
45545
+ "type": "array",
45546
+ "items": {
45547
+ "type": "object",
45548
+ "properties": {
45549
+ "code": { "type": "string" },
45550
+ "message": { "type": "string" }
45551
+ },
45552
+ "required": ["code", "message"],
45553
+ "additionalProperties": {}
45554
+ },
45555
+ "description": "Every reason it would not be sent the event, in the order delivery checks them; empty when it would. Codes, of which more may be added: disabled: the endpoint is off (disabled_reason on the endpoint says why). held: the endpoint, or the route the email came through, is held pending an owner or admin decision after its creator's access was reduced. acting_owner_revoked, acting_owner_suspended (which covers an expired key) and acting_owner_left: its owner cannot act now. acting_owner_permission: its owner does not read mail. acting_owner_depth: its owner sees headers only. acting_owner_scope: its owner does not read this email's mailbox, or, with no email to judge, reads no received mail at all now. These three name what the owner's access holds, so a caller who may not read API key metadata sees acting_owner_access in their place. event_type_not_subscribed: rules.event_types leaves the event out."
45556
+ },
45557
+ "events_paused_until": {
45558
+ "description": "While repeated failures have paused this webhook (its circuit breaker is open): when webhook events other than mail (interaction, sent mail and credential events) are next attempted. Received mail is attempted regardless, so this does not change would_deliver. Null when nothing is paused, and for a Function endpoint.",
45559
+ "type": ["string", "null"],
45560
+ "format": "date-time",
45561
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
45562
+ }
45563
+ },
45564
+ "required": [
45565
+ "event_type",
45566
+ "would_deliver",
45567
+ "reasons",
45568
+ "events_paused_until"
45569
+ ],
45570
+ "additionalProperties": {}
45367
45571
  }
45368
45572
  },
45369
45573
  "required": [
@@ -45373,7 +45577,8 @@ const openapiDocument = {
45373
45577
  "rules_valid",
45374
45578
  "subscribed_to_event",
45375
45579
  "event_type",
45376
- "evaluated"
45580
+ "evaluated",
45581
+ "delivery"
45377
45582
  ]
45378
45583
  },
45379
45584
  "Filter": {
@@ -46033,10 +46238,14 @@ const openapiDocument = {
46033
46238
  "type": "boolean",
46034
46239
  "description": "Whether this is the latest attempt of its delivery: the one recorded last, where a record of no attempt (a rule skip or a held send, attempt_count 0) is latest only when no attempt was made. A retry is recorded as a new attempt with its own id and attempt_count (not always higher than the one before), so an earlier attempt never changes: follow the latest one, or read attempts on any of them."
46035
46240
  },
46241
+ "retry_scheduled": {
46242
+ "type": ["boolean", "null"],
46243
+ "description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own, which this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, a rule-granted one, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will."
46244
+ },
46036
46245
  "next_attempt_at": {
46037
46246
  "type": ["string", "null"],
46038
46247
  "format": "date-time",
46039
- "description": "When the next automatic retry of this delivery is due. Not reported yet: always null."
46248
+ "description": "When the next automatic retry of this delivery is due. Not reported: always null. Whether one is coming is retry_scheduled."
46040
46249
  }
46041
46250
  },
46042
46251
  "required": [
@@ -46055,6 +46264,7 @@ const openapiDocument = {
46055
46264
  "response_status",
46056
46265
  "response_body",
46057
46266
  "latest_attempt",
46267
+ "retry_scheduled",
46058
46268
  "next_attempt_at"
46059
46269
  ]
46060
46270
  },
@@ -48532,6 +48742,19 @@ const openapiDocument = {
48532
48742
  }
48533
48743
  }
48534
48744
  },
48745
+ "EmailListMeta": { "allOf": [{ "$ref": "#/components/schemas/PaginationMeta" }, {
48746
+ "type": "object",
48747
+ "properties": {
48748
+ "recipient_outside_access": {
48749
+ "type": "boolean",
48750
+ "description": "Present, as true, when your credential is limited to some mailboxes, recipient names one that is neither one of its addresses nor on one of its domains (or the credential reads no received mail at all), and it reads through no email rule: the page is then empty however much mail that mailbox holds. Absent otherwise, and for an address on one of your domains, which may still list nothing."
48751
+ },
48752
+ "recipient_rules_only": {
48753
+ "type": "boolean",
48754
+ "description": "Present, as true, when your credential is limited to some mailboxes, recipient names one that is neither one of its addresses nor on one of its domains, and the credential reads some mail through email rules: the page holds only the mail those rules give it there. Absent otherwise."
48755
+ }
48756
+ }
48757
+ }] },
48535
48758
  "IncludedMessageHeaders": {
48536
48759
  "type": "object",
48537
48760
  "description": "Stored addressing and threading headers. A member is null when absent or withheld.",
@@ -48939,7 +49162,7 @@ const openapiDocument = {
48939
49162
  "properties": {
48940
49163
  "covers": {
48941
49164
  "type": "string",
48942
- "description": "you: what a person created through any credential of theirs (their dashboard session, their own CLI or app login, apps and agent delegates acting for them, their API keys while theirs, their personal agents). this_credential: what the calling key, app or agent created itself."
49165
+ "description": "you: what a person created through any credential of theirs, or with relation=asked what others asked of them (their dashboard session, their own CLI or app login, apps and agent delegates acting for them, their API keys while theirs, their personal agents). this_credential: what the calling key, app or agent created itself."
48943
49166
  },
48944
49167
  "items": {
48945
49168
  "type": "array",
@@ -48971,21 +49194,21 @@ const openapiDocument = {
48971
49194
  "owned_now": {
48972
49195
  "type": "string",
48973
49196
  "enum": ["you", "someone_else"],
48974
- "description": "you: it is still yours: never transferred, or transferred back to you. someone_else: it was transferred and another person holds it now."
49197
+ "description": "you: it is still yours: never transferred, or transferred back to you. someone_else: it was transferred and another person holds it now, or, with relation=asked, another member made it."
48975
49198
  },
48976
49199
  "created_through": {
48977
49200
  "type": "object",
48978
49201
  "properties": {
48979
49202
  "kind": {
48980
49203
  "type": "string",
48981
- "description": "The kind of credential it was created through: org_key, function_key, connected_key, oauth, agent_delegate or member (the person themselves)."
49204
+ "description": "The kind of credential it was created through: org_key, function_key, connected_key, oauth, agent_delegate or member (the person themselves). With relation=asked, member: the member who asked."
48982
49205
  },
48983
49206
  "id": {
48984
- "description": "In a person's view, the person's own user id for member, and an API key's id where they may read their keys (for a person limited to their own keys, only a key they still hold). Null otherwise, and always null in one credential's view, where it is the calling credential itself.",
49207
+ "description": "In a person's view, the person's own user id for member, and an API key's id where they may read their keys (for a person limited to their own keys, only a key they still hold). Null otherwise, and always null in one credential's view, where it is the calling credential itself. With relation=asked, the user id of the member who asked.",
48985
49208
  "type": ["string", "null"]
48986
49209
  },
48987
49210
  "name": {
48988
- "description": "In a person's view, what that credential of theirs is called: an app login by its app, an API key by its name, an agent's key by its address. Null otherwise, and for the person themselves.",
49211
+ "description": "In a person's view, what that credential of theirs is called: an app login by its app, an API key by its name, an agent's key by its address. Null otherwise, and for the person themselves. With relation=asked, the name of the member who asked.",
48989
49212
  "type": ["string", "null"]
48990
49213
  }
48991
49214
  },
@@ -49597,6 +49820,10 @@ const openapiDocument = {
49597
49820
  "format": "date-time"
49598
49821
  },
49599
49822
  "from_address": { "type": ["string", "null"] },
49823
+ "recipient": {
49824
+ "description": "The mailbox this copy of the email was received in, as GET /v1/emails/{id} shows it. Given with received_at, from_address and subject, so absent for not_found.",
49825
+ "type": "string"
49826
+ },
49600
49827
  "subject": { "type": ["string", "null"] },
49601
49828
  "parts": {
49602
49829
  "description": "How each top-level part of the rule decided. Present for matched, near_miss and no_match.",
@@ -50717,9 +50944,67 @@ const openapiDocument = {
50717
50944
  "UpdateEmailRuleResult": { "$ref": "#/components/schemas/EmailRuleWritten" },
50718
50945
  "DeleteEmailRuleResult": {
50719
50946
  "type": "object",
50720
- "properties": { "deleted": { "type": "boolean" } },
50947
+ "properties": {
50948
+ "deleted": { "type": "boolean" },
50949
+ "ended_shares": {
50950
+ "description": "The shares of this rule that the delete ended (end_reason rule_deleted). Each keeps its record.",
50951
+ "type": "array",
50952
+ "items": {
50953
+ "type": "string",
50954
+ "format": "uuid"
50955
+ }
50956
+ },
50957
+ "keys": {
50958
+ "description": "The API keys, not revoked, that still list the rule and that you may read (GET /v1/keys). They now read nothing through it; edit or revoke them. Left out, with other_keys, when the keys could not be read.",
50959
+ "type": "array",
50960
+ "items": {
50961
+ "type": "object",
50962
+ "properties": {
50963
+ "id": {
50964
+ "type": "string",
50965
+ "format": "uuid"
50966
+ },
50967
+ "name": { "type": ["string", "null"] },
50968
+ "key_prefix": { "type": "string" }
50969
+ },
50970
+ "required": [
50971
+ "id",
50972
+ "name",
50973
+ "key_prefix"
50974
+ ]
50975
+ }
50976
+ },
50977
+ "other_keys": {
50978
+ "description": "How many more keys, not revoked, list the rule that you may not read. They are not named.",
50979
+ "type": "integer"
50980
+ }
50981
+ },
50721
50982
  "required": ["deleted"]
50722
50983
  },
50984
+ "EmailRuleWithheldBy": {
50985
+ "type": "object",
50986
+ "properties": {
50987
+ "id": {
50988
+ "type": "string",
50989
+ "format": "uuid"
50990
+ },
50991
+ "name": { "type": "string" },
50992
+ "reason": {
50993
+ "type": "string",
50994
+ "enum": [
50995
+ "matched",
50996
+ "undecided",
50997
+ "outside_scope_stale"
50998
+ ],
50999
+ "description": "undecided: it had not decided the email, which blocks too. outside_scope_stale: cleared as outside its scope, and its author now reads the email."
51000
+ }
51001
+ },
51002
+ "required": [
51003
+ "id",
51004
+ "name",
51005
+ "reason"
51006
+ ]
51007
+ },
50723
51008
  "ListEmailRuleDecisionsResult": {
50724
51009
  "type": "array",
50725
51010
  "items": {
@@ -50761,6 +51046,16 @@ const openapiDocument = {
50761
51046
  "type": "boolean",
50762
51047
  "const": true
50763
51048
  },
51049
+ "withheld_by": {
51050
+ "description": "Set when do-not-share rules kept this match from being shared, so no key or webhook got it through the rule: those you can see, and why.",
51051
+ "type": "array",
51052
+ "items": { "$ref": "#/components/schemas/EmailRuleWithheldBy" }
51053
+ },
51054
+ "withheld_by_hidden": {
51055
+ "type": "integer",
51056
+ "minimum": 0,
51057
+ "description": "With withheld_by: how many more kept it back that you cannot see, or that were deleted since. Those rules are not shown; an admin can see them."
51058
+ },
50764
51059
  "created_at": {
50765
51060
  "type": "string",
50766
51061
  "format": "date-time"
@@ -50795,6 +51090,36 @@ const openapiDocument = {
50795
51090
  "ListEmailRuleResultsResult": {
50796
51091
  "type": "object",
50797
51092
  "properties": {
51093
+ "email": {
51094
+ "description": "The email these results are about, so a saved answer says which one it was.",
51095
+ "type": "object",
51096
+ "properties": {
51097
+ "id": {
51098
+ "type": "string",
51099
+ "format": "uuid"
51100
+ },
51101
+ "from_address": {
51102
+ "description": "The sender address, as GET /v1/emails/{id} shows it.",
51103
+ "type": ["string", "null"]
51104
+ },
51105
+ "recipient": {
51106
+ "type": "string",
51107
+ "description": "The mailbox this copy of the email was received in, as GET /v1/emails/{id} shows it."
51108
+ },
51109
+ "subject": { "type": ["string", "null"] },
51110
+ "received_at": {
51111
+ "type": "string",
51112
+ "format": "date-time"
51113
+ }
51114
+ },
51115
+ "required": [
51116
+ "id",
51117
+ "from_address",
51118
+ "recipient",
51119
+ "subject",
51120
+ "received_at"
51121
+ ]
51122
+ },
50798
51123
  "rules": {
50799
51124
  "type": "array",
50800
51125
  "items": {
@@ -50827,6 +51152,10 @@ const openapiDocument = {
50827
51152
  "description": "The version of the rule that decided."
50828
51153
  },
50829
51154
  "matched": { "type": "boolean" },
51155
+ "near_miss": {
51156
+ "description": "Not matched, but nearly: its questions did not all say yes, or it needs several parts and exactly one failed while at least one part that held is not an exclusion (a top-level not, or is_false). False when detail_withheld is true. Decisions recorded under the earlier definition counted an exclusion too.",
51157
+ "type": "boolean"
51158
+ },
50830
51159
  "parts": {
50831
51160
  "type": "array",
50832
51161
  "items": {
@@ -50873,10 +51202,20 @@ const openapiDocument = {
50873
51202
  "anyOf": [{ "$ref": "#/components/schemas/EmailRuleTree" }, { "type": "null" }]
50874
51203
  },
50875
51204
  "detail_withheld": {
50876
- "description": "Present, as true, on a result for an email whose content cannot be read (withheld, failed to parse or discarded): matched still says what the rule decided, but how it got there was read from that content, so parts and question_answers are empty. Absent otherwise.",
51205
+ "description": "Present, as true, on a result for an email whose content cannot be read (withheld, failed to parse or discarded): matched still says what the rule decided, but how it got there was read from that content, so parts and question_answers are empty and near_miss is false. Absent otherwise.",
50877
51206
  "type": "boolean",
50878
51207
  "const": true
50879
51208
  },
51209
+ "withheld_by": {
51210
+ "description": "Set when do-not-share rules kept this match from being shared, so no key or webhook got it through the rule: those you can see, and why.",
51211
+ "type": "array",
51212
+ "items": { "$ref": "#/components/schemas/EmailRuleWithheldBy" }
51213
+ },
51214
+ "withheld_by_hidden": {
51215
+ "type": "integer",
51216
+ "minimum": 0,
51217
+ "description": "With withheld_by: how many more kept it back that you cannot see, or that were deleted since. Those rules are not shown; an admin can see them."
51218
+ },
50880
51219
  "created_at": {
50881
51220
  "type": "string",
50882
51221
  "format": "date-time"
@@ -51157,6 +51496,12 @@ const openapiDocument = {
51157
51496
  "maxLength": 200,
51158
51497
  "description": "A short name for the rule."
51159
51498
  },
51499
+ "message": {
51500
+ "type": ["string", "null"],
51501
+ "minLength": 1,
51502
+ "maxLength": 500,
51503
+ "description": "A short note to the member asked about why you are asking, up to 500 characters; null or absent for none. They see it with the request; it cannot be changed later."
51504
+ },
51160
51505
  "english": {
51161
51506
  "type": "string",
51162
51507
  "minLength": 1,
@@ -51233,7 +51578,8 @@ const openapiDocument = {
51233
51578
  },
51234
51579
  "warnings": {
51235
51580
  "type": "array",
51236
- "items": { "type": "string" }
51581
+ "items": { "type": "string" },
51582
+ "description": "As the member who asked sent them, normally from their compile. The compiler's note that the rule matches nearly everything, in its own wording, counted that member's recent mail: anyone else reads it naming them."
51237
51583
  },
51238
51584
  "compiled_by": { "type": "string" },
51239
51585
  "vocabulary": { "type": "string" },
@@ -51267,6 +51613,14 @@ const openapiDocument = {
51267
51613
  "format": "uuid",
51268
51614
  "description": "The share accepting created, while it exists."
51269
51615
  },
51616
+ "message": {
51617
+ "description": "What the member asking wrote about why; null when they wrote nothing.",
51618
+ "type": ["string", "null"]
51619
+ },
51620
+ "decline_reason": {
51621
+ "description": "Why the member asked declined it; null unless they gave a reason when declining.",
51622
+ "type": ["string", "null"]
51623
+ },
51270
51624
  "target_can_accept": {
51271
51625
  "description": "Whether the member asked can accept it now under their role. Null unless you can list members and their roles (members:read), or when it could not be read.",
51272
51626
  "type": ["boolean", "null"]
@@ -51382,7 +51736,8 @@ const openapiDocument = {
51382
51736
  },
51383
51737
  "warnings": {
51384
51738
  "type": "array",
51385
- "items": { "type": "string" }
51739
+ "items": { "type": "string" },
51740
+ "description": "As the member who asked sent them, normally from their compile. The compiler's note that the rule matches nearly everything, in its own wording, counted that member's recent mail: anyone else reads it naming them."
51386
51741
  },
51387
51742
  "compiled_by": { "type": "string" },
51388
51743
  "vocabulary": { "type": "string" },
@@ -51415,6 +51770,14 @@ const openapiDocument = {
51415
51770
  "type": ["string", "null"],
51416
51771
  "format": "uuid",
51417
51772
  "description": "The share accepting created, while it exists."
51773
+ },
51774
+ "message": {
51775
+ "description": "What the member asking wrote about why; null when they wrote nothing.",
51776
+ "type": ["string", "null"]
51777
+ },
51778
+ "decline_reason": {
51779
+ "description": "Why the member asked declined it; null unless they gave a reason when declining.",
51780
+ "type": ["string", "null"]
51418
51781
  }
51419
51782
  },
51420
51783
  "required": [
@@ -51445,6 +51808,16 @@ const openapiDocument = {
51445
51808
  "items": { "$ref": "#/components/schemas/EmailRuleRequest" }
51446
51809
  },
51447
51810
  "GetEmailRuleRequestResult": { "$ref": "#/components/schemas/EmailRuleRequest" },
51811
+ "DeclineEmailRuleRequestRequest": {
51812
+ "type": "object",
51813
+ "properties": { "reason": {
51814
+ "type": ["string", "null"],
51815
+ "minLength": 1,
51816
+ "maxLength": 500,
51817
+ "description": "Why you are declining, up to 500 characters; null or absent for none. The member who asked sees it with the request."
51818
+ } },
51819
+ "additionalProperties": false
51820
+ },
51448
51821
  "DeclineEmailRuleRequestResult": { "$ref": "#/components/schemas/EmailRuleRequest" },
51449
51822
  "WithdrawEmailRuleRequestResult": { "$ref": "#/components/schemas/EmailRuleRequest" },
51450
51823
  "DeleteEmailRuleRequestResult": {
@@ -51546,8 +51919,10 @@ const openapiDocument = {
51546
51919
  "removed_by_beneficiary",
51547
51920
  "stopped_by_admin",
51548
51921
  "rule_changed",
51922
+ "rule_deleted",
51549
51923
  null
51550
- ]
51924
+ ],
51925
+ "description": "Why it ended, once it has: stopped_by_granter, removed_by_beneficiary, stopped_by_admin, rule_changed (a new version of the rule was saved) or rule_deleted (the rule was deleted; the share keeps its record, and the rule is no longer shown). Null while it has not."
51551
51926
  },
51552
51927
  "accepted_rule_version": {
51553
51928
  "type": "integer",
@@ -53974,8 +54349,12 @@ const openapiDocument = {
53974
54349
  "type": "boolean",
53975
54350
  "description": "Whether this is the latest attempt of its delivery: the one recorded last, where a record of no attempt (a rule skip or a held send, attempt_count 0) is latest only when no attempt was made. A retry is recorded as a new attempt with its own id and attempt_count (not always higher than the one before), so an earlier attempt never changes: follow the latest one, or read attempts on any of them."
53976
54351
  },
54352
+ "retry_scheduled": {
54353
+ "description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own, which this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, a rule-granted one, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.",
54354
+ "type": ["boolean", "null"]
54355
+ },
53977
54356
  "next_attempt_at": {
53978
- "description": "When the next automatic retry of this delivery is due. Not reported yet: always null.",
54357
+ "description": "When the next automatic retry of this delivery is due. Not reported: always null. Whether one is coming is retry_scheduled.",
53979
54358
  "type": ["string", "null"]
53980
54359
  },
53981
54360
  "attempts": {
@@ -54003,6 +54382,10 @@ const openapiDocument = {
54003
54382
  "response_body": {
54004
54383
  "description": "The first 2 KB of the body the endpoint answered this attempt with, so a handler that answers 200 and ignores a delivery can be seen doing so. It is whatever came back, which may echo what was sent. Null when no answer came back, and cleared when the message's content is discarded or the sent email is deleted. Withheld from a caller who cannot read message content, since an answer can echo what was sent.",
54005
54384
  "type": ["string", "null"]
54385
+ },
54386
+ "retry_scheduled": {
54387
+ "description": "Whether an automatic retry will try this delivery again. A retry is a delivery pass for the email: it sends again to the endpoint the email's routing resolves then (its recorded route, or the current endpoint for its domain), unless the email already reached it, deciding it afresh (its rules, its owner's access, whether it is still on), so true promises a pass, not a delivery to this endpoint. Null on every attempt of a delivery retried or settled on a schedule of its own, which this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, a rule-granted one, and a sent email's or other event's delivery. On the latest attempt that failed, is pending or was skipped in a way still owed, of an email that was not rejected and still has its content, to an endpoint that is still on, takes the event, has an owner the pass would deliver to (a revoked or suspended key, or an owner that may not receive the message, reads false) and has not received the email by another delivery: null while a delivery pass for the email is running (whether another follows is decided when it ends), true when the email has another automatic pass to come (a scheduled retry, the reclaim of a pass that stopped, or the recovery of a first pass that did not finish), and false otherwise. False on every other attempt, including every attempt a later one followed: no automatic retry of the email's delivery will try it again, which does not mean nothing else ever will.",
54388
+ "type": ["boolean", "null"]
54006
54389
  }
54007
54390
  },
54008
54391
  "required": [
@@ -54013,7 +54396,8 @@ const openapiDocument = {
54013
54396
  "duration_ms",
54014
54397
  "created_at",
54015
54398
  "response_status",
54016
- "response_body"
54399
+ "response_body",
54400
+ "retry_scheduled"
54017
54401
  ]
54018
54402
  },
54019
54403
  "description": "Every attempt of this delivery in the order recorded, oldest first, this one included: the same endpoint, and the same event (or, for an attempt recorded without one, the same email). Each carries what the endpoint answered that attempt (response_status, response_body). Records of no attempt (attempt_count 0) are listed where they were recorded, so the last entry is not always the latest attempt (latest_attempt says which is). The newest 100 when there are more."
@@ -54038,6 +54422,7 @@ const openapiDocument = {
54038
54422
  "updated_at",
54039
54423
  "email",
54040
54424
  "latest_attempt",
54425
+ "retry_scheduled",
54041
54426
  "next_attempt_at",
54042
54427
  "attempts"
54043
54428
  ]
@@ -55897,6 +56282,51 @@ const openapiDocument = {
55897
56282
  },
55898
56283
  "required": ["kind", "id"]
55899
56284
  }
56285
+ },
56286
+ "already_revoked": {
56287
+ "description": "true when the API key was already revoked, so nothing changed and revoked is 0. Absent otherwise. GET /v1/keys/{id} says when and by whom, to a caller who may read the key.",
56288
+ "type": "boolean"
56289
+ },
56290
+ "keep_failed": {
56291
+ "description": "Items you chose to keep that could not be taken over after the revocation. Each stopped with it, unless someone else had taken it over in the meantime.",
56292
+ "type": "array",
56293
+ "items": {
56294
+ "type": "object",
56295
+ "properties": {
56296
+ "kind": { "type": "string" },
56297
+ "id": { "type": "string" }
56298
+ },
56299
+ "required": ["kind", "id"]
56300
+ }
56301
+ },
56302
+ "standing_objects": {
56303
+ "description": "What the credential set up or owns, listed for review, and for an API key also what it carries on for the keys it was rotated from. Revoking deletes none of it.",
56304
+ "type": "array",
56305
+ "items": {
56306
+ "type": "object",
56307
+ "properties": {
56308
+ "kind": {
56309
+ "type": "string",
56310
+ "description": "endpoint, function_trigger, pull_subscription, recipient_route, filter_rule or wake_schedule."
56311
+ },
56312
+ "id": { "type": "string" },
56313
+ "label": {
56314
+ "type": "string",
56315
+ "description": "A webhook's URL, a pull subscription's name (or pull when it has none), a route's or filter's pattern, or a wake schedule's command and the address it wakes (command to address). A Function trigger's label is function: look it up by id."
56316
+ },
56317
+ "org_owned": {
56318
+ "type": "boolean",
56319
+ "description": "true: the organization owns it, and it keeps working. false: the credential owns it itself, as with everything a third-party app or agent delegate made, a webhook made with a narrowed API key, or a wake schedule made with a key that does not cover super_admin. It keeps working only while someone can still act for it, such as a key of the same rotation that still works, or whoever it was given to or kept it. Revoking a third-party app or agent delegate turns these off, except that a block filter it made keeps blocking, within the reach the app had, unless the person revoking chose to stop it."
56320
+ }
56321
+ },
56322
+ "required": [
56323
+ "kind",
56324
+ "id",
56325
+ "label",
56326
+ "org_owned"
56327
+ ],
56328
+ "additionalProperties": {}
56329
+ }
55900
56330
  }
55901
56331
  },
55902
56332
  "required": [
@@ -56537,7 +56967,7 @@ const openapiDocument = {
56537
56967
  "type": "string",
56538
56968
  "format": "date-time",
56539
56969
  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
56540
- "description": "When the old key stops working: the time of the rotation plus overlap_seconds (24 hours when it was omitted, now when it was 0), or the key's own expires_at when that comes first."
56970
+ "description": "When the old key stops working: the time of the rotation plus overlap_seconds (24 hours when it was omitted, now when it was 0), or the key's own expires_at, or the expires_at given to the new key, whichever comes first."
56541
56971
  }
56542
56972
  },
56543
56973
  "required": [
@@ -56551,12 +56981,20 @@ const openapiDocument = {
56551
56981
  },
56552
56982
  "Credentials_rotateKey_Input": {
56553
56983
  "type": "object",
56554
- "properties": { "overlap_seconds": {
56555
- "description": "How long the old key keeps working, in seconds, up to 604800 (7 days), and never past its own expires_at. 86400 (24 hours) when omitted. 0 stops the old key at once: use it when rotating because the secret leaked.",
56556
- "type": "integer",
56557
- "minimum": 0,
56558
- "maximum": 604800
56559
- } },
56984
+ "properties": {
56985
+ "overlap_seconds": {
56986
+ "description": "How long the old key keeps working, in seconds, up to 604800 (7 days), and never past its own expires_at. 86400 (24 hours) when omitted. 0 stops the old key at once: use it when rotating because the secret leaked.",
56987
+ "type": "integer",
56988
+ "minimum": 0,
56989
+ "maximum": 604800
56990
+ },
56991
+ "expires_at": {
56992
+ "type": ["string", "null"],
56993
+ "format": "date-time",
56994
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
56995
+ "description": "The new key's expiry. Omitted, it keeps the old key's expiry: rotating alone never extends a key. A time sets it, within the organization's credential policy (its maximum key lifetime counts from now); null makes it never expire, where the policy allows. Needs keys:manage, as changing a key does. The old key stops when the overlap ends, or at this expiry if that comes first."
56996
+ }
56997
+ },
56560
56998
  "additionalProperties": false
56561
56999
  },
56562
57000
  "Credentials_updateKey_Result": {
@@ -56605,6 +57043,11 @@ const openapiDocument = {
56605
57043
  ],
56606
57044
  "additionalProperties": {}
56607
57045
  }
57046
+ },
57047
+ "grant_unguarded": {
57048
+ "description": "Present, as true, when the grant was changed without expected_grant_ref, so the change was not checked against changes made since the key was read. Send grant.ref from GET /v1/keys/{id} as expected_grant_ref to have such a change refused instead (409 conflict, details.reason grant_changed).",
57049
+ "type": "boolean",
57050
+ "const": true
56608
57051
  }
56609
57052
  },
56610
57053
  "required": [
@@ -56625,7 +57068,8 @@ const openapiDocument = {
56625
57068
  "expires_at": {
56626
57069
  "type": ["string", "null"],
56627
57070
  "format": "date-time",
56628
- "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
57071
+ "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
57072
+ "description": "A new expiry. A later time extends the key and an earlier one ends it sooner, within the organization's credential policy (its maximum key lifetime counts from now); null removes the expiry, where the policy allows. A rotated-out key's expiry can only be brought forward, and an expired key's cannot be changed."
56629
57073
  },
56630
57074
  "grant": { "anyOf": [{
56631
57075
  "type": "string",
@@ -56976,12 +57420,25 @@ const openapiDocument = {
56976
57420
  "grant": {
56977
57421
  "type": "object",
56978
57422
  "properties": {
57423
+ "ref": {
57424
+ "description": "The grant this read shows. Send it as expected_grant_ref on PATCH /v1/keys/{id} so the change is refused (409 conflict, details.reason grant_changed) if the grant has changed since this read.",
57425
+ "type": "string"
57426
+ },
56979
57427
  "template": { "type": ["string", "null"] },
56980
57428
  "permissions": {
56981
57429
  "type": ["array", "null"],
56982
57430
  "items": { "type": "string" }
56983
57431
  },
56984
- "mail_scope": {}
57432
+ "mail_scope": {},
57433
+ "resources": {
57434
+ "type": ["object", "null"],
57435
+ "propertyNames": { "type": "string" },
57436
+ "additionalProperties": {
57437
+ "type": "array",
57438
+ "items": { "type": "string" }
57439
+ },
57440
+ "description": "The Functions, domains, endpoints or routes the key is limited to, by type; null when it has no such limits. A grant change on a key with limits must drop them on purpose, with grant.resources {} (otherwise 409 conflict, details.reason key_has_resource_limits)."
57441
+ }
56985
57442
  },
56986
57443
  "required": [
56987
57444
  "template",
@@ -56997,14 +57454,31 @@ const openapiDocument = {
56997
57454
  "expires_at": { "type": ["string", "null"] },
56998
57455
  "suspended": { "type": "boolean" },
56999
57456
  "status": {
57000
- "description": "Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); or inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member). A revoked key is not listed. An endpoint's owner_key_status reports an expired key as suspended.",
57457
+ "description": "Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member); or revoked, which it stays for good and which no unrevoked key ever shows. A revoked key is listed only with include_revoked=true, and GET /v1/keys/{id} answers it. An endpoint's owner_key_status reports an expired key as suspended.",
57001
57458
  "type": "string",
57002
57459
  "enum": [
57003
57460
  "active",
57004
57461
  "suspended",
57005
57462
  "expired",
57006
- "inactive"
57463
+ "inactive",
57464
+ "revoked"
57007
57465
  ]
57466
+ },
57467
+ "revoked_at": {
57468
+ "description": "When the key was revoked (ISO 8601); null for a key that is not revoked.",
57469
+ "type": ["string", "null"]
57470
+ },
57471
+ "revoked_by": {
57472
+ "description": "The user id of the person who revoked the key from their own dashboard session, step-up or Primitive CLI login; null for a key that is not revoked, and for one revoked any other way (by an API key, another app or token, or an automatic process such as secret scanning).",
57473
+ "type": ["string", "null"]
57474
+ },
57475
+ "rotated_from": {
57476
+ "description": "The id of the key this key was rotated from (POST /v1/keys/{id}/rotate made this key in its place); null for a key not made by rotation, when the caller may not read that key, or when its record is gone. A revoked one is still named: GET /v1/keys/{id} answers it.",
57477
+ "type": ["string", "null"]
57478
+ },
57479
+ "rotated_to": {
57480
+ "description": "The id of the newest key rotated from this key that the caller may read; null until it is rotated, or when the caller may read no key rotated from it. A revoked one is still named: GET /v1/keys/{id} answers it.",
57481
+ "type": ["string", "null"]
57008
57482
  }
57009
57483
  },
57010
57484
  "required": [
@@ -57045,12 +57519,25 @@ const openapiDocument = {
57045
57519
  "grant": {
57046
57520
  "type": "object",
57047
57521
  "properties": {
57522
+ "ref": {
57523
+ "description": "The grant this read shows. Send it as expected_grant_ref on PATCH /v1/keys/{id} so the change is refused (409 conflict, details.reason grant_changed) if the grant has changed since this read.",
57524
+ "type": "string"
57525
+ },
57048
57526
  "template": { "type": ["string", "null"] },
57049
57527
  "permissions": {
57050
57528
  "type": ["array", "null"],
57051
57529
  "items": { "type": "string" }
57052
57530
  },
57053
- "mail_scope": {}
57531
+ "mail_scope": {},
57532
+ "resources": {
57533
+ "type": ["object", "null"],
57534
+ "propertyNames": { "type": "string" },
57535
+ "additionalProperties": {
57536
+ "type": "array",
57537
+ "items": { "type": "string" }
57538
+ },
57539
+ "description": "The Functions, domains, endpoints or routes the key is limited to, by type; null when it has no such limits. A grant change on a key with limits must drop them on purpose, with grant.resources {} (otherwise 409 conflict, details.reason key_has_resource_limits)."
57540
+ }
57054
57541
  },
57055
57542
  "required": [
57056
57543
  "template",
@@ -57066,14 +57553,31 @@ const openapiDocument = {
57066
57553
  "expires_at": { "type": ["string", "null"] },
57067
57554
  "suspended": { "type": "boolean" },
57068
57555
  "status": {
57069
- "description": "Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); or inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member). A revoked key is not listed. An endpoint's owner_key_status reports an expired key as suspended.",
57556
+ "description": "Whether the key works now: active; suspended (refused until the suspension is lifted); expired (refused since expires_at); inactive, for an agent connection key whose connection cannot act (not claimed, revoked, its domain not verified, or its owner no longer a member); or revoked, which it stays for good and which no unrevoked key ever shows. A revoked key is listed only with include_revoked=true, and GET /v1/keys/{id} answers it. An endpoint's owner_key_status reports an expired key as suspended.",
57070
57557
  "type": "string",
57071
57558
  "enum": [
57072
57559
  "active",
57073
57560
  "suspended",
57074
57561
  "expired",
57075
- "inactive"
57562
+ "inactive",
57563
+ "revoked"
57076
57564
  ]
57565
+ },
57566
+ "revoked_at": {
57567
+ "description": "When the key was revoked (ISO 8601); null for a key that is not revoked.",
57568
+ "type": ["string", "null"]
57569
+ },
57570
+ "revoked_by": {
57571
+ "description": "The user id of the person who revoked the key from their own dashboard session, step-up or Primitive CLI login; null for a key that is not revoked, and for one revoked any other way (by an API key, another app or token, or an automatic process such as secret scanning).",
57572
+ "type": ["string", "null"]
57573
+ },
57574
+ "rotated_from": {
57575
+ "description": "The id of the key this key was rotated from (POST /v1/keys/{id}/rotate made this key in its place); null for a key not made by rotation, when the caller may not read that key, or when its record is gone. A revoked one is still named: GET /v1/keys/{id} answers it.",
57576
+ "type": ["string", "null"]
57577
+ },
57578
+ "rotated_to": {
57579
+ "description": "The id of the newest key rotated from this key that the caller may read; null until it is rotated, or when the caller may read no key rotated from it. A revoked one is still named: GET /v1/keys/{id} answers it.",
57580
+ "type": ["string", "null"]
57077
57581
  }
57078
57582
  },
57079
57583
  "required": [
@@ -59844,6 +60348,25 @@ const openapiDocument = {
59844
60348
  ],
59845
60349
  "description": "A default wake schedule the install creates for the function: the command it sends, a 5-field cron expression and timezone, and the scheduler sender localpart on the installed address's domain. The owner can change or delete it after install."
59846
60350
  },
60351
+ "EndpointUpdateWarning": {
60352
+ "type": "object",
60353
+ "properties": {
60354
+ "code": { "type": "string" },
60355
+ "message": { "type": "string" }
60356
+ },
60357
+ "required": ["code", "message"],
60358
+ "additionalProperties": {}
60359
+ },
60360
+ "UpdatedEndpoint": { "allOf": [{ "$ref": "#/components/schemas/Endpoint" }, {
60361
+ "type": "object",
60362
+ "required": ["warnings"],
60363
+ "properties": { "warnings": {
60364
+ "default": [],
60365
+ "description": "When this update turned the webhook off or on without the request setting enabled. turned_off_owner_changed: the webhook was given to a new owner, so it is now off (disabled_reason owner_changed) until it is turned on or given a new URL. turned_on_new_url: it was off because its owner changed, and the new URL turned it back on. Empty when the update did neither. Other codes may be added.",
60366
+ "type": "array",
60367
+ "items": { "$ref": "#/components/schemas/EndpointUpdateWarning" }
60368
+ } }
60369
+ }] },
59847
60370
  "EndpointSigningSecretResponse": {
59848
60371
  "type": "object",
59849
60372
  "properties": {
@@ -60078,13 +60601,12 @@ const openapiDocument = {
60078
60601
  },
60079
60602
  "BuiltinRoleKey": {
60080
60603
  "type": "string",
60081
- "description": "The key of a built-in role. `operator` has every permission `admin`\nhas except managing members, changing roles, sending invitations and\nbilling. By default the CLI may be used by `owner`, `admin` and\n`operator`.\n",
60082
60604
  "enum": [
60083
60605
  "owner",
60084
60606
  "admin",
60085
- "operator",
60086
60607
  "member"
60087
- ]
60608
+ ],
60609
+ "description": "The key of a built-in role. Every other role, including the `operator` template, is a template or custom role with a key of its own. A member's `role` (and the `stored_role` a role assignment returns) can also be `restricted`, which is not a built-in role and cannot be assigned: it stands for a template or custom role that does not fully include `member`, or for no role at all."
60088
60610
  }
60089
60611
  },
60090
60612
  "headers": {
@@ -60295,6 +60817,32 @@ function formatActivityRows(covers, items) {
60295
60817
  const line = (cells) => cells.map((cell, i) => i === cells.length - 1 ? cell : cell.padEnd(widths[i] ?? 0)).join(" ");
60296
60818
  return [line(header), ...rows.map(line)].join("\n");
60297
60819
  }
60820
+ /**
60821
+ * One line per request asked of you: its state, when it was asked, its name,
60822
+ * and who asked. `complete` says the listing is the whole of it (every state,
60823
+ * from the first page to the last, nothing withheld), the only time an empty one means
60824
+ * nobody has asked.
60825
+ */
60826
+ function formatAskedRows(items, complete = false) {
60827
+ if (items.length === 0) return complete ? "Nobody has asked you for anything." : "Nothing to show.";
60828
+ const rows = items.map((item) => [
60829
+ item.state,
60830
+ minute(item.created_at),
60831
+ item.name ?? item.id,
60832
+ item.created_through.name ?? item.created_through.id ?? "",
60833
+ item.id
60834
+ ]);
60835
+ const header = [
60836
+ "STATE",
60837
+ "ASKED (UTC)",
60838
+ "NAME",
60839
+ "FROM",
60840
+ "ID"
60841
+ ];
60842
+ const widths = header.map((h, i) => Math.max(h.length, ...rows.map((row) => (row[i] ?? "").length)));
60843
+ const line = (cells) => cells.map((cell, i) => i === cells.length - 1 ? cell : cell.padEnd(widths[i] ?? 0)).join(" ");
60844
+ return [line(header), ...rows.map(line)].join("\n");
60845
+ }
60298
60846
  /** What was left out, and why: no counts, only the kinds and what would show them. */
60299
60847
  function formatWithheld(withheld) {
60300
60848
  if (withheld.length === 0) return null;
@@ -60305,9 +60853,9 @@ function shellWord(value) {
60305
60853
  return SHELL_SAFE.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
60306
60854
  }
60307
60855
  /** The command that continues this listing from `cursor`, with the same filters. */
60308
- function resumeCommand(bin, flags, cursor) {
60856
+ function resumeCommand(bin, flags, cursor, subcommand = "created") {
60309
60857
  return [
60310
- `${bin} activity created`,
60858
+ `${bin} activity ${subcommand}`,
60311
60859
  ...flags.kind ? [`--kind ${shellWord(flags.kind)}`] : [],
60312
60860
  ...flags.state !== "all" ? [`--state ${flags.state}`] : [],
60313
60861
  ...flags.limit !== DEFAULT_LIMIT$3 ? [`--limit ${flags.limit}`] : [],
@@ -60315,6 +60863,60 @@ function resumeCommand(bin, flags, cursor) {
60315
60863
  `--cursor ${shellWord(cursor)}`
60316
60864
  ].join(" ");
60317
60865
  }
60866
+ /** List one page, or every page with --all, of what the caller created or was asked. */
60867
+ async function runActivityList(command, flags, relation) {
60868
+ const auth = await createAuthenticatedCliApiClient({
60869
+ apiKey: flags["api-key"],
60870
+ apiBaseUrl: flags["api-base-url"],
60871
+ configDir: command.config.configDir
60872
+ });
60873
+ const items = [];
60874
+ let covers = "";
60875
+ let withheld = [];
60876
+ let cursor = flags.cursor;
60877
+ for (let page = 0; page < (flags.all ? 50 : 1); page += 1) {
60878
+ const result = await call$1(listActivityObjects({
60879
+ client: auth.apiClient.client,
60880
+ query: {
60881
+ ...relation === "asked" ? { relation } : {},
60882
+ state: flags.state,
60883
+ limit: flags.limit,
60884
+ ...flags.kind ? { kind: flags.kind } : {},
60885
+ ...cursor ? { cursor } : {}
60886
+ }
60887
+ }));
60888
+ if (!result.ok) {
60889
+ if (flags.json) process.stderr.write(`${JSON.stringify(result.payload)}\n`);
60890
+ else writeErrorWithHints(result.payload);
60891
+ surfaceUnauthorizedHint({
60892
+ auth: auth.auth,
60893
+ baseUrlOverridden: auth.baseUrlOverridden,
60894
+ configDir: command.config.configDir,
60895
+ payload: result.payload
60896
+ });
60897
+ process.exitCode = 1;
60898
+ return;
60899
+ }
60900
+ covers = result.data.covers;
60901
+ withheld = result.data.withheld;
60902
+ items.push(...result.data.items);
60903
+ cursor = result.data.next_cursor ?? void 0;
60904
+ if (!cursor) break;
60905
+ }
60906
+ if (flags.json) {
60907
+ command.log(JSON.stringify({
60908
+ covers,
60909
+ items,
60910
+ withheld,
60911
+ next_cursor: cursor ?? null
60912
+ }, null, 2));
60913
+ return;
60914
+ }
60915
+ command.log(relation === "asked" ? formatAskedRows(items, flags.state === "all" && !flags.cursor && !cursor && withheld.length === 0) : formatActivityRows(covers, items));
60916
+ const notShown = formatWithheld(withheld);
60917
+ if (notShown) command.log(`\n${notShown}`);
60918
+ if (cursor) command.log(`\nMore: ${resumeCommand(command.config.bin, flags, cursor, relation)}`);
60919
+ }
60318
60920
  var ActivityCreatedCommand = class ActivityCreatedCommand extends Command {
60319
60921
  static description = `List what you created, newest first, with where each object stands now: Functions, endpoints, pull subscriptions, routes, filters, wake schedules, repeating and scheduled sends, including ones since disabled, deleted or revoked.
60320
60922
 
@@ -60352,56 +60954,44 @@ var ActivityCreatedCommand = class ActivityCreatedCommand extends Command {
60352
60954
  };
60353
60955
  async run() {
60354
60956
  const { flags } = await this.parse(ActivityCreatedCommand);
60355
- const auth = await createAuthenticatedCliApiClient({
60356
- apiKey: flags["api-key"],
60357
- apiBaseUrl: flags["api-base-url"],
60358
- configDir: this.config.configDir
60359
- });
60360
- const items = [];
60361
- let covers = "";
60362
- let withheld = [];
60363
- let cursor = flags.cursor;
60364
- for (let page = 0; page < (flags.all ? 50 : 1); page += 1) {
60365
- const result = await call$1(listActivityObjects({
60366
- client: auth.apiClient.client,
60367
- query: {
60368
- state: flags.state,
60369
- limit: flags.limit,
60370
- ...flags.kind ? { kind: flags.kind } : {},
60371
- ...cursor ? { cursor } : {}
60372
- }
60373
- }));
60374
- if (!result.ok) {
60375
- if (flags.json) process.stderr.write(`${JSON.stringify(result.payload)}\n`);
60376
- else writeErrorWithHints(result.payload);
60377
- surfaceUnauthorizedHint({
60378
- auth: auth.auth,
60379
- baseUrlOverridden: auth.baseUrlOverridden,
60380
- configDir: this.config.configDir,
60381
- payload: result.payload
60382
- });
60383
- process.exitCode = 1;
60384
- return;
60385
- }
60386
- covers = result.data.covers;
60387
- withheld = result.data.withheld;
60388
- items.push(...result.data.items);
60389
- cursor = result.data.next_cursor ?? void 0;
60390
- if (!cursor) break;
60391
- }
60392
- if (flags.json) {
60393
- this.log(JSON.stringify({
60394
- covers,
60395
- items,
60396
- withheld,
60397
- next_cursor: cursor ?? null
60398
- }, null, 2));
60399
- return;
60400
- }
60401
- this.log(formatActivityRows(covers, items));
60402
- const notShown = formatWithheld(withheld);
60403
- if (notShown) this.log(`\n${notShown}`);
60404
- if (cursor) this.log(`\nMore: ${resumeCommand(this.config.bin, flags, cursor)}`);
60957
+ await runActivityList(this, flags, "created");
60958
+ }
60959
+ };
60960
+ var ActivityAskedCommand = class ActivityAskedCommand extends Command {
60961
+ static description = `List what other members asked of you, newest first, open or answered: rule requests made to you for an email rule over your own mail, each with where it stands and who asked.
60962
+
60963
+ Signed in with \`primitive login\`, you see the requests made to you under your current membership. A key, an app or an agent is asked nothing. Read one with \`primitive email-rules get-email-rule-request --id <id>\`, then accept or decline it.`;
60964
+ static summary = "List what other members asked of you";
60965
+ static examples = [
60966
+ "<%= config.bin %> activity asked",
60967
+ "<%= config.bin %> activity asked --state live",
60968
+ "<%= config.bin %> activity asked --all --json"
60969
+ ];
60970
+ static flags = {
60971
+ "api-key": API_KEY_FLAG$3,
60972
+ "api-base-url": API_BASE_URL_FLAG$4,
60973
+ state: Flags.string({
60974
+ description: "all (default), live (still waiting on you), or ended (answered or withdrawn).",
60975
+ options: [
60976
+ "all",
60977
+ "live",
60978
+ "ended"
60979
+ ],
60980
+ default: "all"
60981
+ }),
60982
+ limit: Flags.integer({
60983
+ description: "Requests per page, 1 to 200.",
60984
+ min: 1,
60985
+ max: 200,
60986
+ default: DEFAULT_LIMIT$3
60987
+ }),
60988
+ cursor: Flags.string({ description: "Resume from a next_cursor an earlier page printed." }),
60989
+ all: Flags.boolean({ description: `Follow every page (at most 50).` }),
60990
+ json: Flags.boolean({ description: "Print the answer as JSON." })
60991
+ };
60992
+ async run() {
60993
+ const { flags } = await this.parse(ActivityAskedCommand);
60994
+ await runActivityList(this, flags, "asked");
60405
60995
  }
60406
60996
  };
60407
60997
  // src/oclif/agent-mail-check.ts
@@ -62491,12 +63081,12 @@ var AgentStatusCommand = class AgentStatusCommand extends Command {
62491
63081
  * Authenticated by the agent's own API key (the org is taken from the key).
62492
63082
  */
62493
63083
  var AgentUpgradeCommand = class AgentUpgradeCommand extends Command {
62494
- static description = "Upgrade an emailless agent account to a full developer account by confirming an email. Authenticated by the agent's own API key (PRIMITIVE_API_KEY). Upgrading raises the send cap and grants the developer plan's default features (such as Functions). It does not unlock sending to arbitrary recipients: the account's recipient rules still apply (see `primitive sending permissions`).";
62495
- static summary = "Upgrade an agent account to developer (email confirmation)";
63084
+ static description = "Upgrade an emailless agent account to a full developer account by confirming an email. Authenticated by the agent's own API key (PRIMITIVE_API_KEY). Upgrading raises the send cap and grants the developer plan's default features (such as Functions). It does not unlock sending to arbitrary recipients: the account's recipient rules still apply (see `primitive sending permissions`). It emails a code and asks for it, so it needs a terminal; without one, use `primitive agent claim` and `primitive agent claim-verify`.";
63085
+ static summary = "Upgrade an agent account to developer by email code (needs a terminal)";
62496
63086
  static examples = ["<%= config.bin %> agent upgrade --email you@example.com"];
62497
63087
  static flags = {
62498
- email: Flags.string({ description: "Email to confirm. Prompted if omitted." }),
62499
- code: Flags.string({ description: "Verification code from the email. Prompted if omitted." }),
63088
+ email: Flags.string({ description: "Email to confirm. Prompted if omitted or blank." }),
63089
+ code: Flags.string({ description: "Not accepted; for a code you already have, use `primitive agent claim-verify --verification-code <code>`. This command emails a new code, which replaces any code sent before." }),
62500
63090
  "api-key": Flags.string({
62501
63091
  env: "PRIMITIVE_API_KEY",
62502
63092
  description: "Agent API key (defaults to PRIMITIVE_API_KEY or saved credentials)."
@@ -62505,14 +63095,17 @@ var AgentUpgradeCommand = class AgentUpgradeCommand extends Command {
62505
63095
  };
62506
63096
  async run() {
62507
63097
  const { flags } = await this.parse(AgentUpgradeCommand);
63098
+ const email = flags.email?.trim() || void 0;
63099
+ if (flags.code !== void 0) this.error("Not upgrading: --code cannot be used here, because `primitive agent upgrade` emails a new code, which replaces the one you have. To use the code you have, run `primitive agent claim-verify --verification-code <code>`.", { exit: 2 });
63100
+ if (!(process$1.stdin.isTTY && process$1.stderr.isTTY)) this.error(`Not upgrading: \`primitive agent upgrade\` emails a code and asks for it, so it needs a terminal. Without one, run \`primitive agent claim --email ${email ?? "<address>"}\` to send the code, then \`primitive agent claim-verify --verification-code <code>\`.`, { exit: 2 });
62508
63101
  const { apiClient } = await createAuthenticatedCliApiClient({
62509
63102
  apiKey: flags["api-key"],
62510
63103
  apiBaseUrl: flags["api-base-url"],
62511
63104
  configDir: this.config.configDir
62512
63105
  });
62513
- const email = flags.email ?? await promptRequired$1("Email to confirm: ");
63106
+ const address = email ?? await promptRequired$1("Email to confirm: ");
62514
63107
  const started = await startAgentClaim({
62515
- body: { email },
63108
+ body: { email: address },
62516
63109
  client: apiClient.client,
62517
63110
  responseStyle: "fields"
62518
63111
  });
@@ -62521,8 +63114,8 @@ var AgentUpgradeCommand = class AgentUpgradeCommand extends Command {
62521
63114
  this.exit(1);
62522
63115
  return;
62523
63116
  }
62524
- process$1.stderr.write(`Verification code sent to ${email}.\n`);
62525
- const code = flags.code ?? await promptRequired$1("Verification code: ");
63117
+ process$1.stderr.write(`Verification code sent to ${address}.\n`);
63118
+ const code = await promptRequired$1("Verification code: ");
62526
63119
  const verified = await verifyAgentClaim({
62527
63120
  body: { verification_code: code },
62528
63121
  client: apiClient.client,
@@ -67339,7 +67932,7 @@ async function checkAccount(client) {
67339
67932
  return {
67340
67933
  outcome: {
67341
67934
  status: "ok",
67342
- message: `${account.email ?? "account email not visible to this credential"} (plan: ${account.plan}, id: ${account.id})`
67935
+ message: `account email ${account.email ?? "not visible to this credential"} (plan: ${account.plan}, id: ${account.id})`
67343
67936
  },
67344
67937
  account
67345
67938
  };
@@ -67391,6 +67984,33 @@ async function checkDomains(client) {
67391
67984
  };
67392
67985
  }
67393
67986
  }
67987
+ /**
67988
+ * A background start that gave up, such as a receiver replacing its lost
67989
+ * supervisor, has no terminal to report to. Its saved reason is read
67990
+ * offline here, and only while the receiver is not receiving.
67991
+ */
67992
+ function checkReceiverStart(configDir, profileName) {
67993
+ try {
67994
+ const receiver = savedReceiver(configDir, profileName);
67995
+ if (receiver?.mode !== "native" || !receiver.session) return null;
67996
+ const profile = loadConnectedAgentProfile(configDir, profileName);
67997
+ if (!profile) return null;
67998
+ const listener = backgroundListenStatus({
67999
+ configDir,
68000
+ scope: notificationScope(profile.api_base_url, profile.api_key),
68001
+ threadId: receiver.session
68002
+ });
68003
+ const failure = listener.lastStartFailure;
68004
+ if (!failure) return null;
68005
+ return {
68006
+ status: "warn",
68007
+ message: `This profile's receiver is not receiving, and its last background start failed at ${new Date(failure.at).toISOString()} (${failure.reason}${failure.failureCode ? `, ${failure.failureCode}` : ""})`,
68008
+ hint: `${listener.detail ? `${listener.detail} ` : ""}Inspect it with \`${receiverStatusCommand(configDir, profileName).command}\`.`
68009
+ };
68010
+ } catch {
68011
+ return null;
68012
+ }
68013
+ }
67394
68014
  var DoctorCommand = class DoctorCommand extends Command {
67395
68015
  static description = `Run a one-shot environment health check: Node version, proxy env, CLI auth resolution, /account reachability, and verified-domain status. With PRIMITIVE_AGENT_PROFILE selected, reports saved identity offline and points to whoami and exact listener status instead of account-only checks. Prints actionable hints for each warning or failure.`;
67396
68016
  static summary = "Check the local environment and live API for common problems";
@@ -67435,6 +68055,11 @@ var DoctorCommand = class DoctorCommand extends Command {
67435
68055
  label: "Auth",
67436
68056
  outcome: apiKeyCheck
67437
68057
  });
68058
+ const receiverStart = connected ? checkReceiverStart(this.config.configDir, connected.profileName) : null;
68059
+ if (receiverStart) rows.push({
68060
+ label: "Receiver",
68061
+ outcome: receiverStart
68062
+ });
67438
68063
  if (!connected && apiKeyCheck.status !== "fail") {
67439
68064
  const { apiClient, auth } = await createAuthenticatedCliApiClient({
67440
68065
  apiKey: flags["api-key"],
@@ -68769,7 +69394,11 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
68769
69394
  }
68770
69395
  const rows = envelope?.data ?? [];
68771
69396
  if (rows.length === 0) {
68772
- process.stderr.write(awaiting ? `No inbound emails with awaiting=${awaiting}.\n` : "No inbound emails yet. Send an email to one of your verified domains to populate this list.\n");
69397
+ process.stderr.write(`${emptyEmailListHint({
69398
+ filterFlags: awaiting ? [`--awaiting ${awaiting}`] : [],
69399
+ caller: await readCallerMailFor(auth, apiClient.client),
69400
+ orgHint: "(no results) No inbound emails yet. Send an email to one of your verified domains to populate this list."
69401
+ })}\n`);
68773
69402
  return;
68774
69403
  }
68775
69404
  const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
@@ -69008,7 +69637,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
69008
69637
  await waiter?.close();
69009
69638
  }
69010
69639
  }
69640
+ let reread = null;
69011
69641
  while (!connected && (deadline === null || Date.now() < deadline)) {
69642
+ const polledAt = Date.now();
69012
69643
  let page;
69013
69644
  try {
69014
69645
  page = await fetchEmailSearchPage({
@@ -69044,11 +69675,29 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
69044
69675
  const nextCursor = cursorFromAcceptedRows(page.rows);
69045
69676
  const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
69046
69677
  if (nextCursor) cursor = nextCursor;
69678
+ if (cursor !== null) reread ??= lateMailReread({
69679
+ pollIntervalMs: flags.interval * 1e3,
69680
+ deadline,
69681
+ start: polledAt
69682
+ });
69047
69683
  for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
69048
69684
  printEmail(email);
69049
69685
  if (matched >= flags.number) return;
69050
69686
  }
69051
69687
  if (cursorAdvanced) continue;
69688
+ if (reread?.due()) {
69689
+ const recent = await reread.read({
69690
+ apiClient,
69691
+ filters,
69692
+ since,
69693
+ fetchPage: fetchEmailSearchPage
69694
+ });
69695
+ for (const email of collectNewAcceptedEmails(recent.rows, seenIds)) {
69696
+ printEmail(email);
69697
+ if (matched >= flags.number) return;
69698
+ }
69699
+ if (!await waitOutRetryAfter(recent.failed, { deadline })) break;
69700
+ }
69052
69701
  if (deadline !== null && Date.now() >= deadline) break;
69053
69702
  await sleep$3(flags.interval * 1e3);
69054
69703
  }
@@ -69147,7 +69796,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
69147
69796
  let failures = 0;
69148
69797
  let printed = 0;
69149
69798
  let headerPrinted = false;
69799
+ let reread = null;
69150
69800
  while (deadline === null || Date.now() < deadline) {
69801
+ const polledAt = Date.now();
69151
69802
  let page;
69152
69803
  try {
69153
69804
  page = await fetchEmailSearchPage({
@@ -69183,7 +69834,18 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
69183
69834
  const nextCursor = cursorFromAcceptedRows(page.rows);
69184
69835
  const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
69185
69836
  if (nextCursor) cursor = nextCursor;
69186
- for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
69837
+ if (cursor !== null) reread ??= lateMailReread({
69838
+ pollIntervalMs: flags.interval * 1e3,
69839
+ deadline,
69840
+ start: polledAt
69841
+ });
69842
+ const recent = !cursorAdvanced && reread?.due() ? await reread.read({
69843
+ apiClient,
69844
+ filters,
69845
+ since,
69846
+ fetchPage: fetchEmailSearchPage
69847
+ }) : { rows: [] };
69848
+ for (const email of collectNewAcceptedEmails([...page.rows, ...recent.rows], seenIds)) {
69187
69849
  if (flags.jsonl) this.log(JSON.stringify(email));
69188
69850
  else {
69189
69851
  if (!headerPrinted) {
@@ -69196,6 +69858,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
69196
69858
  if (flags.number && printed >= flags.number) return;
69197
69859
  }
69198
69860
  if (cursorAdvanced) continue;
69861
+ if (!await waitOutRetryAfter(recent.failed, { deadline })) break;
69199
69862
  if (deadline !== null && Date.now() >= deadline) break;
69200
69863
  await sleep$3(flags.interval * 1e3);
69201
69864
  }
@@ -70352,8 +71015,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
70352
71015
  name: "Primitive Team",
70353
71016
  url: "https://primitive.dev"
70354
71017
  };
70355
- const SDK_VERSION_RANGE = "^3.0.0-preview.5";
70356
- const CLI_VERSION_RANGE = "^3.0.0-preview.5";
71018
+ const SDK_VERSION_RANGE = "^3.0.0-preview.6";
71019
+ const CLI_VERSION_RANGE = "^3.0.0-preview.6";
70357
71020
  const ESBUILD_VERSION_RANGE = "^0.27.0";
70358
71021
  function renderHandler() {
70359
71022
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -73102,6 +73765,30 @@ const APPROVAL_FLAGS = {
73102
73765
  description: BROWSER_FLAG_DESCRIPTION
73103
73766
  })
73104
73767
  };
73768
+ /**
73769
+ * A new expiry from --expires-at or --no-expiry: a time with its time zone,
73770
+ * null for none, or undefined when neither was given. The API checks the date
73771
+ * itself; this refuses what is plainly not such a time before sending it.
73772
+ */
73773
+ function expiryFromFlags(flags) {
73774
+ if (flags["no-expiry"]) return {
73775
+ ok: true,
73776
+ expiresAt: null
73777
+ };
73778
+ const at = flags["expires-at"]?.trim();
73779
+ if (at === void 0) return {
73780
+ ok: true,
73781
+ expiresAt: void 0
73782
+ };
73783
+ if (!/^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d+)?)?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)$/.test(at) || Number.isNaN(Date.parse(at))) return {
73784
+ ok: false,
73785
+ message: `--expires-at takes an ISO 8601 time with its time zone, such as 2027-02-08T00:00:00Z, not ${JSON.stringify(at)}.`
73786
+ };
73787
+ return {
73788
+ ok: true,
73789
+ expiresAt: at
73790
+ };
73791
+ }
73105
73792
  function unwrap(data) {
73106
73793
  return data?.data ?? data;
73107
73794
  }
@@ -73145,6 +73832,21 @@ function listFlag(values) {
73145
73832
  function stringList(value) {
73146
73833
  return Array.isArray(value) && value.every((v) => typeof v === "string") ? [...value] : null;
73147
73834
  }
73835
+ /**
73836
+ * The rules a key's mail scope lists that read nothing any more, each with
73837
+ * why: a deleted rule (rule_deleted) or a share that ended (share_ended).
73838
+ */
73839
+ function endedRules(scope) {
73840
+ if (!Array.isArray(scope.ended_rules)) return [];
73841
+ return scope.ended_rules.flatMap((entry) => {
73842
+ if (!entry || typeof entry !== "object") return [];
73843
+ const { id, reason } = entry;
73844
+ return typeof id === "string" ? [{
73845
+ id,
73846
+ reason: typeof reason === "string" ? reason : "ended"
73847
+ }] : [];
73848
+ });
73849
+ }
73148
73850
  /** Which mail a key reaches, in words. */
73149
73851
  function describeMailScope(grant) {
73150
73852
  if (grant.template === "super_admin") return "every mailbox";
@@ -73160,7 +73862,9 @@ function describeMailScope(grant) {
73160
73862
  if (addresses.length > 0) parts.push(`addresses ${addresses.join(", ")}`);
73161
73863
  if (domains.length > 0) parts.push(`domains ${domains.join(", ")}`);
73162
73864
  if (rules.length > 0) parts.push(`rules ${rules.join(", ")}`);
73163
- return parts.length > 0 ? parts.join("; ") : "no mailbox";
73865
+ const ended = endedRules(record);
73866
+ const reaches = parts.length > 0 ? parts.join("; ") : "no mailbox";
73867
+ return ended.length > 0 ? `${reaches}; ended rules ${ended.map((r) => `${r.id} (${r.reason})`).join(", ")}` : reaches;
73164
73868
  }
73165
73869
  /** What a key may do, in words. */
73166
73870
  function describePermissions(grant) {
@@ -73188,6 +73892,10 @@ function formatKeyDetail(key) {
73188
73892
  `Last used: ${key.last_used_at ?? "never"}`,
73189
73893
  `Suspended: ${key.suspended ? "yes" : "no"}`
73190
73894
  ];
73895
+ if (key.status === "revoked") {
73896
+ const by = key.revoked_by ? ` by user ${key.revoked_by}` : "";
73897
+ lines.push(`Revoked: ${key.revoked_at ?? "yes"}${by}`);
73898
+ }
73191
73899
  if (key.agent_address) lines.push(`Agent: ${key.agent_address}`);
73192
73900
  if (key.grant.template) lines.push(`Permissions: ${describePermissions(key.grant)}`);
73193
73901
  else {
@@ -73269,16 +73977,64 @@ function buildCreateGrant(choice) {
73269
73977
  message: `--preset must be one of ${KEY_PRESETS.join(", ")}.`
73270
73978
  };
73271
73979
  }
73272
- /** The mail scope fields a change can send back unchanged. */
73980
+ /**
73981
+ * The mail scope fields a change can restate. `ended_rules` (rules the key
73982
+ * lists that read nothing any more: a deleted rule, or a share that ended) is
73983
+ * read only: a change never sends it back, so the key is saved without them.
73984
+ */
73273
73985
  const RESTATABLE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
73274
73986
  "addresses",
73275
73987
  "domains",
73276
73988
  "rules",
73277
- "depth"
73989
+ "depth",
73990
+ "ended_rules"
73278
73991
  ]);
73992
+ /** Whether a change touches the key's email rules at all. */
73993
+ function changesRules(change) {
73994
+ return change.rules !== void 0 || (change.addRules?.length ?? 0) > 0 || (change.removeRules?.length ?? 0) > 0;
73995
+ }
73996
+ /**
73997
+ * The rules a key lists after `change`: replaced by --rule, or the ones it has
73998
+ * with --add-rule added and --remove-rule taken off. The API takes a key's
73999
+ * whole grant, so a partial change is made here on the grant just read, and
74000
+ * the update is guarded by that grant's ref. Rule ids are compared as the
74001
+ * server stores them, lowercase.
74002
+ */
74003
+ function nextRules(current, change) {
74004
+ if (change.rules) return change.rules;
74005
+ const removing = new Set((change.removeRules ?? []).map((r) => r.toLowerCase()));
74006
+ const kept = current.filter((r) => !removing.has(r.toLowerCase()));
74007
+ const have = new Set(kept.map((r) => r.toLowerCase()));
74008
+ const adding = [];
74009
+ for (const rule of change.addRules ?? []) {
74010
+ const id = rule.toLowerCase();
74011
+ if (have.has(id)) continue;
74012
+ have.add(id);
74013
+ adding.push(id);
74014
+ }
74015
+ return [...kept, ...adding];
74016
+ }
74017
+ /** Whether two lists hold the same values, in any order. */
74018
+ function sameSet(a, b) {
74019
+ const left = new Set(a);
74020
+ const right = new Set(b);
74021
+ return left.size === right.size && [...left].every((v) => right.has(v));
74022
+ }
74023
+ /**
74024
+ * The rules a key's grant lists, or none: the ones it reads through, and the
74025
+ * ones that read nothing any more (`ended_rules`), which a change to the key
74026
+ * takes off.
74027
+ */
74028
+ function keyRules(grant) {
74029
+ const scope = grant.mail_scope;
74030
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)) return [];
74031
+ const record = scope;
74032
+ return [...stringList(record.rules ?? []) ?? [], ...endedRules(record).map((rule) => rule.id)];
74033
+ }
73279
74034
  /**
73280
74035
  * The grant that restates a key's current grant with permissions added or
73281
- * removed, or its email rules replaced, and everything else as it is. Only a
74036
+ * removed, or its email rules replaced, added to or taken off (nextRules), and
74037
+ * everything else as it is. Only a
73282
74038
  * key with a permission list and a mail scope the CLI can restate exactly;
73283
74039
  * anything else is changed in the dashboard.
73284
74040
  */
@@ -73291,10 +74047,14 @@ function buildUpdatedGrant(current, change) {
73291
74047
  ok: false,
73292
74048
  message: `This key's permissions come from its template (${current.template}) and are not changed here.`
73293
74049
  };
74050
+ if (!("resources" in current)) return {
74051
+ ok: false,
74052
+ message: "The server does not say whether this key is limited to selected Functions, domains, endpoints or routes, so the CLI does not restate its grant: that could drop such limits. Change it in the dashboard."
74053
+ };
73294
74054
  const resources = current.resources;
73295
74055
  if (resources !== void 0 && resources !== null && (typeof resources !== "object" || Object.keys(resources).length > 0)) return {
73296
74056
  ok: false,
73297
- message: "This key is limited to selected resources, which the CLI cannot restate. Change it in the dashboard."
74057
+ message: "This key is limited to selected Functions, domains, endpoints or routes, which a change to its grant cannot keep. To change what it can do, create a key with what you need and revoke this one."
73298
74058
  };
73299
74059
  const held = stringList(current.permissions);
73300
74060
  if (!held) return {
@@ -73308,7 +74068,7 @@ function buildUpdatedGrant(current, change) {
73308
74068
  };
73309
74069
  const scope = current.mail_scope;
73310
74070
  if (scope === "org") {
73311
- if (change.rules) return {
74071
+ if (changesRules(change)) return {
73312
74072
  ok: false,
73313
74073
  message: "This key reaches every mailbox, so email rules cannot be set on it. Create a key limited to the rules instead."
73314
74074
  };
@@ -73329,15 +74089,28 @@ function buildUpdatedGrant(current, change) {
73329
74089
  const record = scope;
73330
74090
  const addresses = stringList(record.addresses ?? []);
73331
74091
  const domains = stringList(record.domains ?? []);
73332
- const rules = change.rules ?? stringList(record.rules ?? []);
73333
- if (!addresses || !domains || !rules || Object.keys(record).some((field) => !RESTATABLE_SCOPE_FIELDS.has(field))) return {
74092
+ const heldRules = stringList(record.rules ?? []);
74093
+ const ended = endedRules(record);
74094
+ const readdedEnded = ended.find((rule) => [...change.addRules ?? [], ...change.rules ?? []].some((asked) => asked.toLowerCase() === rule.id.toLowerCase()));
74095
+ if (readdedEnded) return {
73334
74096
  ok: false,
73335
- message: "This key's mail scope has settings the CLI cannot restate. Change it in the dashboard."
74097
+ message: `${readdedEnded.id} reads nothing any more (${readdedEnded.reason}), so it cannot be added back to the key.`
73336
74098
  };
73337
- if (addresses.length === 0 && domains.length === 0 && rules.length === 0) return {
74099
+ const rules = heldRules ? nextRules(heldRules, change) : null;
74100
+ if (!addresses || !domains || !rules || Object.keys(record).some((field) => !RESTATABLE_SCOPE_FIELDS.has(field))) return {
73338
74101
  ok: false,
73339
- message: "The key would reach no mail. Keep at least one address, domain or rule."
74102
+ message: "This key's mail scope has settings the CLI cannot restate. Change it in the dashboard."
73340
74103
  };
74104
+ if (addresses.length === 0 && domains.length === 0 && rules.length === 0) {
74105
+ if ((heldRules?.length ?? 0) === 0 && ended.length > 0) return {
74106
+ ok: false,
74107
+ message: "This key reads no mail any more: its only rules were deleted or their shares ended. Revoke it with `primitive keys revoke <key-id>`."
74108
+ };
74109
+ return {
74110
+ ok: false,
74111
+ message: "The key would reach no mail. Keep at least one address, domain or rule."
74112
+ };
74113
+ }
73341
74114
  return {
73342
74115
  ok: true,
73343
74116
  grant: {
@@ -73552,9 +74325,15 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
73552
74325
  var KeysRotateCommand = class KeysRotateCommand extends Command {
73553
74326
  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.
73554
74327
 
74328
+ The new key keeps the old key's expiry: rotating alone does not extend a key. Pass --expires-at to give the new key another one, within your organization's credential policy, or --no-expiry for none where the policy allows. Either needs keys:manage. The old key never outlives the new one.
74329
+
73555
74330
  The rotation runs directly; if your organization requires approval, the command prints the approval link and takes --step-up-token, as for a new key. Store the new secret right away: it is not shown again and the CLI does not save it.`;
73556
74331
  static summary = "Rotate an API key";
73557
- static examples = ["<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100", "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100 --overlap-seconds 0"];
74332
+ static examples = [
74333
+ "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100",
74334
+ "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100 --overlap-seconds 0",
74335
+ "<%= config.bin %> keys rotate 0f0e0d0c-0b0a-4908-8706-050403020100 --expires-at 2027-02-08T00:00:00Z"
74336
+ ];
73558
74337
  static args = { id: Args.string({
73559
74338
  description: "The key's id.",
73560
74339
  required: true
@@ -73566,13 +74345,26 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
73566
74345
  description: "How long the old key keeps working, in seconds. 0 stops it now.",
73567
74346
  min: 0
73568
74347
  }),
74348
+ "expires-at": Flags.string({
74349
+ description: "Give the new key this expiry (ISO 8601 with a time zone) instead of the old key's.",
74350
+ exclusive: ["no-expiry"]
74351
+ }),
74352
+ "no-expiry": Flags.boolean({
74353
+ description: "Give the new key no expiry, where the policy allows.",
74354
+ exclusive: ["expires-at"]
74355
+ }),
73569
74356
  ...APPROVAL_FLAGS,
73570
74357
  json: Flags.boolean({ description: "Print the new key, its secret included, as JSON." })
73571
74358
  };
73572
74359
  async run() {
73573
74360
  const { args, flags } = await this.parse(KeysRotateCommand);
74361
+ const expiry = expiryFromFlags(flags);
74362
+ if (!expiry.ok) this.error(expiry.message, { exit: 2 });
73574
74363
  const auth = await authenticate(this, flags);
73575
- const body = flags["overlap-seconds"] === void 0 ? {} : { overlap_seconds: flags["overlap-seconds"] };
74364
+ const body = {
74365
+ ...flags["overlap-seconds"] === void 0 ? {} : { overlap_seconds: flags["overlap-seconds"] },
74366
+ ...expiry.expiresAt === void 0 ? {} : { expires_at: expiry.expiresAt }
74367
+ };
73576
74368
  const result = await withApproval((token) => call(rotateKey({
73577
74369
  client: auth.apiClient.client,
73578
74370
  path: { id: args.id },
@@ -73584,22 +74376,25 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
73584
74376
  return;
73585
74377
  }
73586
74378
  const rotated = result.data;
73587
- 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}.`);
74379
+ printSecret(this, Boolean(flags.json), rotated, rotated.key, `Rotated key ${rotated.previous_key_id}. The new key is ${rotated.id}, prefix ${rotated.key_prefix}${rotated.expires_at === void 0 ? "" : `, and expires ${rotated.expires_at ?? "never"}`}. The old key works until ${rotated.previous_key_expires_at}.`);
73588
74380
  }
73589
74381
  };
73590
74382
  var KeysUpdateCommand = class KeysUpdateCommand extends Command {
73591
- static description = `Change an API key's permissions, email rules or name. It keeps its id and secret.
74383
+ static description = `Change an API key's permissions, email rules, name or expiry. It keeps its id and secret.
73592
74384
 
73593
- --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.
74385
+ --add and --remove take permissions (repeatable, or comma-separated). --add-rule and --remove-rule add email rules to the key or take them off, and keep its other rules. --rule replaces every rule the key has with the ones given, so pass all the rules it should keep; it cannot be combined with --add-rule or --remove-rule. --expires-at gives it a new expiry: a later one extends it, within your organization's credential policy, and is how to keep a key working past its expiry (rotating keeps the expiry). --no-expiry removes it where the policy allows. An expired key cannot be extended. Everything else about the key stays as it is, and the output lists any rules a change took off.
73594
74386
 
73595
74387
  The change runs directly; if your organization requires approval, the command prints the approval link and takes --step-up-token, as for a new key. A key never holds more than you do.
73596
74388
 
73597
- With --json it prints the key as it is now, in the shape keys get prints, with changed, warnings and address_warnings alongside, and notice when its permissions or mail changed. If the key cannot be read back after the change, key_error says why and only the change's fields are printed.`;
74389
+ With --json it prints the key as it is now, in the shape keys get prints, with changed, warnings and address_warnings alongside, notice when its permissions or mail changed, rules_removed when a change took rules off, and rules_not_on_key for rules asked to be taken off that it did not have. A permission or rule change that leaves the grant as it is, with no --name, --expires-at or --no-expiry, sends nothing and prints the key with changed: []. If the key cannot be read back after the change, key_error says why and only the change's fields are printed.`;
73598
74390
  static summary = "Change an API key";
73599
74391
  static examples = [
73600
74392
  "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --add mail:send,mail:reply",
73601
74393
  "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --remove mail:send",
73602
- "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f"
74394
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --add-rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f",
74395
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --remove-rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f",
74396
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --rule 3b0f6a1e-6c7a-4f43-9d8e-2a1b5c4d3e2f",
74397
+ "<%= config.bin %> keys update 0f0e0d0c-0b0a-4908-8706-050403020100 --expires-at 2027-02-08T00:00:00Z"
73603
74398
  ];
73604
74399
  static args = { id: Args.string({
73605
74400
  description: "The key's id.",
@@ -73617,24 +74412,58 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
73617
74412
  multiple: true
73618
74413
  }),
73619
74414
  rule: Flags.string({
73620
- description: "An email rule id the key reaches (repeatable). Replaces the key's rules.",
74415
+ description: "An email rule id the key reaches (repeatable, or comma-separated). Replaces every rule the key has: pass all the rules it should keep, or use --add-rule and --remove-rule.",
74416
+ multiple: true,
74417
+ exclusive: ["add-rule", "remove-rule"]
74418
+ }),
74419
+ "add-rule": Flags.string({
74420
+ description: "An email rule id to add to the key (repeatable, or comma-separated). Its other rules stay.",
74421
+ multiple: true
74422
+ }),
74423
+ "remove-rule": Flags.string({
74424
+ description: "An email rule id to take off the key (repeatable, or comma-separated). Its other rules stay.",
73621
74425
  multiple: true
73622
74426
  }),
73623
74427
  name: Flags.string({ description: "A new name for the key." }),
74428
+ "expires-at": Flags.string({
74429
+ description: "A new expiry, ISO 8601 with a time zone. A later one extends the key.",
74430
+ exclusive: ["no-expiry"]
74431
+ }),
74432
+ "no-expiry": Flags.boolean({
74433
+ description: "Remove the key's expiry, where the policy allows.",
74434
+ exclusive: ["expires-at"]
74435
+ }),
73624
74436
  ...APPROVAL_FLAGS,
73625
74437
  json: Flags.boolean({ description: "Print the result as JSON." })
73626
74438
  };
73627
74439
  async run() {
73628
74440
  const { args, flags } = await this.parse(KeysUpdateCommand);
74441
+ const expiry = expiryFromFlags(flags);
74442
+ if (!expiry.ok) this.error(expiry.message, { exit: 2 });
73629
74443
  const add = listFlag(flags.add);
73630
74444
  const remove = listFlag(flags.remove);
73631
74445
  const rules = flags.rule ? listFlag(flags.rule) : void 0;
74446
+ const addRules = listFlag(flags["add-rule"]);
74447
+ const removeRules = listFlag(flags["remove-rule"]);
73632
74448
  const both = add.filter((p) => remove.includes(p));
73633
74449
  if (both.length > 0) this.error(`Both added and removed: ${both.join(", ")}.`, { exit: 2 });
73634
- const changesGrant = add.length > 0 || remove.length > 0 || !!rules;
73635
- if (!changesGrant && flags.name === void 0) this.error("Nothing to change: pass --add, --remove, --rule or --name.", { exit: 2 });
74450
+ const bothRules = addRules.filter((r) => removeRules.some((x) => x.toLowerCase() === r.toLowerCase()));
74451
+ if (bothRules.length > 0) this.error(`Both added and removed: ${bothRules.join(", ")}.`, { exit: 2 });
74452
+ const ruleChange = {
74453
+ ...rules ? { rules } : {},
74454
+ ...addRules.length > 0 ? { addRules } : {},
74455
+ ...removeRules.length > 0 ? { removeRules } : {}
74456
+ };
74457
+ const changesGrant = add.length > 0 || remove.length > 0 || Object.keys(ruleChange).length > 0;
74458
+ if (!changesGrant && flags.name === void 0 && expiry.expiresAt === void 0) this.error("Nothing to change: pass --add, --remove, --add-rule, --remove-rule, --rule, --name, --expires-at or --no-expiry.", { exit: 2 });
74459
+ let rulesBefore = [];
74460
+ let rulesRemoved = [];
74461
+ let rulesNotOnKey = [];
73636
74462
  const auth = await authenticate(this, flags);
73637
- const body = { ...flags.name !== void 0 ? { name: flags.name } : {} };
74463
+ const body = {
74464
+ ...flags.name !== void 0 ? { name: flags.name } : {},
74465
+ ...expiry.expiresAt !== void 0 ? { expires_at: expiry.expiresAt } : {}
74466
+ };
73638
74467
  if (changesGrant) {
73639
74468
  const current = await call(getKey({
73640
74469
  client: auth.apiClient.client,
@@ -73644,15 +74473,36 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
73644
74473
  reportKeysError(this, current, auth, Boolean(flags.json));
73645
74474
  return;
73646
74475
  }
74476
+ if (current.data.status === "revoked") this.error(`Key ${args.id} is revoked, so it cannot be changed.`, { exit: 1 });
74477
+ rulesBefore = keyRules(current.data.grant);
73647
74478
  const next = buildUpdatedGrant(current.data.grant, {
73648
74479
  add,
73649
74480
  remove,
73650
- ...rules ? { rules } : {}
74481
+ ...ruleChange
73651
74482
  });
73652
- if (!next.ok) this.error(next.message, { exit: 2 });
73653
- body.grant = next.grant;
73654
- const ref = current.data.grant.ref;
73655
- if (typeof ref === "string") body.expected_grant_ref = ref;
74483
+ if (!next.ok) this.error(next.message.replace("<key-id>", args.id), { exit: 2 });
74484
+ rulesNotOnKey = removeRules.filter((r) => !rulesBefore.some((k) => k.toLowerCase() === r.toLowerCase()));
74485
+ if (rulesNotOnKey.length > 0 && !flags.json) process.stderr.write(`Not on this key, so nothing to take off: ${rulesNotOnKey.join(", ")}.\n`);
74486
+ const sentRules = stringList(next.grant.rules ?? []) ?? [];
74487
+ rulesRemoved = rulesBefore.filter((r) => !sentRules.some((k) => k.toLowerCase() === r.toLowerCase()));
74488
+ if (!(sameSet(next.permissions, stringList(current.data.grant.permissions) ?? []) && sameSet(sentRules.map((r) => r.toLowerCase()), rulesBefore.map((r) => r.toLowerCase())))) {
74489
+ body.grant = next.grant;
74490
+ const ref = current.data.grant.ref;
74491
+ if (typeof ref === "string") body.expected_grant_ref = ref;
74492
+ else if (!flags.json) process.stderr.write("Note: the server did not say which grant this key holds, so this change is not checked against changes made since it was read.\n");
74493
+ } else if (flags.name === void 0 && expiry.expiresAt === void 0) {
74494
+ if (flags.json) {
74495
+ this.log(JSON.stringify({
74496
+ ...current.data,
74497
+ changed: [],
74498
+ warnings: [],
74499
+ ...rulesNotOnKey.length > 0 ? { rules_not_on_key: rulesNotOnKey } : {}
74500
+ }, null, 2));
74501
+ return;
74502
+ }
74503
+ this.log(`Key ${args.id} already matched; nothing changed.`);
74504
+ return;
74505
+ }
73656
74506
  }
73657
74507
  const result = await withApproval((token) => call(updateKey({
73658
74508
  client: auth.apiClient.client,
@@ -73674,6 +74524,8 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
73674
74524
  ...after?.ok ? after.data : {},
73675
74525
  ...result.data,
73676
74526
  ...grantChanged ? { notice: HELD_REQUEST_NOTICE } : {},
74527
+ ...grantChanged && rulesRemoved.length > 0 ? { rules_removed: rulesRemoved } : {},
74528
+ ...rulesNotOnKey.length > 0 ? { rules_not_on_key: rulesNotOnKey } : {},
73677
74529
  ...after && !after.ok ? { key_error: after.payload } : {}
73678
74530
  }, null, 2));
73679
74531
  return;
@@ -73685,11 +74537,25 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
73685
74537
  this.log(`Permissions: ${describePermissions(after.data.grant)}`);
73686
74538
  this.log(`Mail: ${describeMailScope(after.data.grant)}`);
73687
74539
  }
74540
+ if (grantChanged && rulesRemoved.length > 0) this.log(`Rules taken off: ${rulesRemoved.join(", ")}`);
74541
+ if (changed.includes("expires_at")) this.log(`Expires: ${expiry.expiresAt ?? "never"}`);
73688
74542
  if (grantChanged) process.stderr.write(`${HELD_REQUEST_NOTICE}\n`);
73689
74543
  }
73690
74544
  };
74545
+ /**
74546
+ * What a revoked key set up or owns, split as revocation splits it: what the
74547
+ * organization owns keeps working, and what the key itself owned (its own
74548
+ * webhooks, say) keeps working only while someone can still act for it, such
74549
+ * as a key of the same rotation that still works. Null when there is none.
74550
+ */
74551
+ function standingSummary(standing) {
74552
+ if (!Array.isArray(standing) || standing.length === 0) return null;
74553
+ const keyOwned = standing.filter((item) => item.org_owned === false).length;
74554
+ const kept = standing.length - keyOwned;
74555
+ return `${[...kept > 0 ? [`${kept} item(s) the organization owns keep working.`] : [], ...keyOwned > 0 ? [`${keyOwned} item(s) the key owned itself or carried on for the keys it was rotated from, such as its own webhooks, which keep working only while someone can still act for them, such as a key of the same rotation that still works.`] : []].join(" ")} Review them with --json.`;
74556
+ }
73691
74557
  var KeysRevokeCommand = class KeysRevokeCommand extends Command {
73692
- static description = `Revoke an API key. It stops working at once; what it set up keeps working and is listed for review.
74558
+ static description = `Revoke an API key. It stops working at once. What the organization owns keeps working; what the key itself owns, such as its own webhooks, keeps working only while someone can still act for it, such as a key of the same rotation that still works. What it set up or owns is listed for review.
73693
74559
 
73694
74560
  Asks for confirmation first; pass --yes to skip the prompt in scripts. Revoking runs directly. If your organization requires approval, the command prints the approval link, opens it where it can and waits for the one-time token; in a script, approve and run it again with --step-up-token.`;
73695
74561
  static summary = "Revoke an API key";
@@ -73738,9 +74604,9 @@ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
73738
74604
  this.log(JSON.stringify(result.data, null, 2));
73739
74605
  return;
73740
74606
  }
73741
- this.log(`Revoked key ${args.id}.`);
73742
- const standing = result.data.standing_objects;
73743
- if (Array.isArray(standing) && standing.length > 0) this.log(`It set up ${standing.length} item(s) that keep working. Review them with --json.`);
74607
+ this.log(result.data.already_revoked === true ? `Key ${args.id} was already revoked; nothing changed.` : `Revoked key ${args.id}.`);
74608
+ const summary = standingSummary(result.data.standing_objects);
74609
+ if (summary) this.log(summary);
73744
74610
  }
73745
74611
  };
73746
74612
  const hash = (value) => createHash("sha256").update(value).digest("hex");
@@ -75779,14 +76645,13 @@ var ListenCommand = class ListenCommand extends Command {
75779
76645
  threadId: flags["notify-session"]
75780
76646
  } : void 0;
75781
76647
  const socketPath = target && !flags.status && !flags.stop ? flags["session-socket"] ?? defaultSessionSocket() : void 0;
75782
- const configuration = target ? createHash("sha256").update(JSON.stringify({
75783
- version: this.config.version,
76648
+ const configuration = target ? backgroundListenConfiguration({
75784
76649
  contacts: Boolean(flags.contacts),
75785
76650
  contactRequests: Boolean(flags["contact-requests"]),
75786
- senders: [...senders ?? []].sort(),
76651
+ senders: senders ?? [],
75787
76652
  socketPath,
75788
- events: flags.events?.split(",").map((event) => event.trim()).sort()
75789
- })).digest("hex") : void 0;
76653
+ events: flags.events?.split(",")
76654
+ }) : void 0;
75790
76655
  if (target) verifyBackgroundListenTarget({
75791
76656
  ...target,
75792
76657
  configuration
@@ -76702,7 +77567,7 @@ var LoginCommand$1 = class extends Command {
76702
77567
  process.stderr.write("Logged in as yourself. Switch organizations with `primitive org use <org>`, or pass --org <org> to one command.\n");
76703
77568
  if (login.org) {
76704
77569
  const name = login.org.org_name ? ` (${login.org.org_name})` : "";
76705
- process.stderr.write(`Current organization: ${login.org.org_id}${name}, role ${login.org.role}.\n`);
77570
+ process.stderr.write(`Current organization: ${login.org.org_id}${name}, ${login.org.role ? roleKeyText(login.org.role) : "role unknown"}.\n`);
76706
77571
  } else if (login.org_refusal) process.stderr.write(`The organization chosen in the browser (${login.org_refusal.org_id}) cannot be used from the CLI: ${login.org_refusal.message} [${login.org_refusal.reason}]\nRun \`primitive orgs\` to see where you can, then \`primitive org use <org>\`.\n`);
76707
77572
  const wantedOrg = process.env[ORG_SELECTION_ENV]?.trim();
76708
77573
  const alreadyThere = wantedOrg && login.org && (wantedOrg.toLowerCase() === login.org.org_id.toLowerCase() || wantedOrg.toLowerCase() === (login.org.org_name ?? "").toLowerCase());
@@ -76715,7 +77580,7 @@ var LoginCommand$1 = class extends Command {
76715
77580
  selection: wantedOrg
76716
77581
  });
76717
77582
  const name = org.org_name ? ` (${org.org_name})` : "";
76718
- process.stderr.write(`Switched to organization ${org.org_id}${name}, role ${org.role ?? "unknown"}.\n`);
77583
+ process.stderr.write(`Switched to organization ${org.org_id}${name}, ${org.role ? roleKeyText(org.role) : "role unknown"}.\n`);
76719
77584
  } catch (error) {
76720
77585
  const detail = error instanceof Error ? error.message : String(error);
76721
77586
  const reason = error instanceof CliOrgRefusedError ? ` [${error.reason}]` : "";
@@ -76730,6 +77595,7 @@ const INVALID_VERIFICATION_CODE = "invalid_verification_code";
76730
77595
  const EXPIRED_TOKEN = "expired_token";
76731
77596
  const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
76732
77597
  const SLOW_DOWN = "slow_down";
77598
+ const MANAGED_DOMAIN = "managed_domain";
76733
77599
  const PENDING_SIGNUP_FILE = "signup.json";
76734
77600
  const DEFAULT_SIGNUP_COMMAND_COPY = {
76735
77601
  actionNoun: "signup",
@@ -76741,6 +77607,52 @@ const DEFAULT_SIGNUP_COMMAND_COPY = {
76741
77607
  function cliError$2(message) {
76742
77608
  return new Errors.CLIError(message, { exit: 1 });
76743
77609
  }
77610
+ function usageError$1(message) {
77611
+ return new Errors.CLIError(message, { exit: 2 });
77612
+ }
77613
+ function canPrompt(interactive) {
77614
+ return interactive ?? Boolean(process$1.stdin.isTTY && process$1.stderr.isTTY);
77615
+ }
77616
+ /** The email address given, or undefined when none (or only blanks) was. */
77617
+ function givenEmail(email) {
77618
+ return email?.trim() ? email : void 0;
77619
+ }
77620
+ /**
77621
+ * Refuse an email-code start that would have to prompt when there is no one
77622
+ * to answer. Commands call it before taking the credentials lock, so the
77623
+ * refusal comes before anything is read, written or sent.
77624
+ */
77625
+ function refuseEmailCodeStartWithoutTerminal(params) {
77626
+ if (canPrompt(params.interactive)) return;
77627
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
77628
+ const email = givenEmail(params.email);
77629
+ if (email === void 0) throw usageError$1(`Not starting ${copy.actionNoun}: no terminal to ask for the email address in. Pass the address as an argument, for example \`primitive ${copy.startCommand("<email>")} --accept-terms\`.`);
77630
+ if (!params.flags["accept-terms"]) throw usageError$1(`Not starting ${copy.actionNoun}: no terminal to accept the terms in. To accept Primitive's Terms of Service (https://primitive.dev/terms) and Privacy Policy (https://primitive.dev/privacy), add --accept-terms to the same command, for example \`primitive ${copy.startCommand(email)} --accept-terms\`.`);
77631
+ }
77632
+ /** `signup interactive` asks for everything, so it needs a terminal. */
77633
+ function refuseInteractiveSignupWithoutTerminal(interactive) {
77634
+ if (canPrompt(interactive)) return;
77635
+ throw usageError$1("Not starting signup: `primitive signup interactive` needs a terminal to ask for the email address and code in. Run `primitive signup <email> --accept-terms`, then `primitive signup confirm <email> <code>`.");
77636
+ }
77637
+ function emailDomain(email) {
77638
+ const at = email.lastIndexOf("@");
77639
+ const domain = at === -1 ? "" : email.slice(at + 1).trim().toLowerCase();
77640
+ return domain === "" ? null : domain;
77641
+ }
77642
+ /**
77643
+ * The server sends no emailed code to an address on a domain Primitive
77644
+ * receives mail for. Its refusal is worded for signup; someone logging in
77645
+ * needs to hear which way does sign them in.
77646
+ */
77647
+ function managedDomainLoginError(params) {
77648
+ const domain = emailDomain(params.email);
77649
+ const subject = domain ? `Primitive sends no emailed code to addresses on ${domain}, a domain it receives mail for.` : "Primitive sends no emailed code to this address, which is on a domain it receives mail for.";
77650
+ const run = params.interactive ? `\`primitive ${params.browserLoginCommand}\`` : `\`primitive ${params.browserLoginCommand} --no-browser\` and open the link it prints`;
77651
+ return new Errors.CLIError(`${subject} Use browser login instead: run ${run}.`, {
77652
+ code: MANAGED_DOMAIN,
77653
+ exit: 1
77654
+ });
77655
+ }
76744
77656
  function unwrapData$1(value) {
76745
77657
  return value?.data ?? null;
76746
77658
  }
@@ -76964,7 +77876,7 @@ async function confirmTerms() {
76964
77876
  process$1.stderr.write(" https://primitive.dev/terms\n");
76965
77877
  process$1.stderr.write(" https://primitive.dev/privacy\n");
76966
77878
  const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
76967
- if (answer !== "yes" && answer !== "y") throw cliError$2("You must accept the terms to create an account.");
77879
+ if (answer !== "yes" && answer !== "y") throw cliError$2("You must accept the terms to continue.");
76968
77880
  }
76969
77881
  async function checkExistingCredentials(params) {
76970
77882
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
@@ -76990,7 +77902,7 @@ async function checkExistingCredentials(params) {
76990
77902
  credentialsLockHeld: true
76991
77903
  });
76992
77904
  if (existingStatus.status === "removed_stale") {
76993
- process$1.stderr.write("Continuing with Primitive signup...\n");
77905
+ process$1.stderr.write(`Continuing with Primitive ${copy.actionNoun}...\n`);
76994
77906
  return;
76995
77907
  }
76996
77908
  if (existingStatus.status === "blocked") {
@@ -77032,8 +77944,14 @@ async function startSignup(params) {
77032
77944
  responseStyle: "fields"
77033
77945
  });
77034
77946
  if (started.error) {
77035
- writeErrorWithHints(extractErrorPayload(started.error));
77036
- throw cliError$2("Could not start Primitive agent signup.");
77947
+ const payload = extractErrorPayload(started.error);
77948
+ if (copy.browserLoginCommand && extractErrorCode(payload) === MANAGED_DOMAIN) throw managedDomainLoginError({
77949
+ browserLoginCommand: copy.browserLoginCommand,
77950
+ email: params.email,
77951
+ interactive: canPrompt(params.deps.interactive)
77952
+ });
77953
+ writeErrorWithHints(payload);
77954
+ throw cliError$2(`Could not start Primitive ${copy.actionNoun}.`);
77037
77955
  }
77038
77956
  const startResult = unwrapData$1(started.data);
77039
77957
  if (!startResult) throw cliError$2("Primitive API returned an empty agent signup response.");
@@ -77074,13 +77992,19 @@ async function resendVerificationCode(params) {
77074
77992
  }
77075
77993
  if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
77076
77994
  writeErrorWithHints(payload);
77077
- throw cliError$2("Could not resend Primitive agent signup verification email.");
77995
+ throw cliError$2(`Could not resend the Primitive ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).actionNoun} verification email.`);
77078
77996
  }
77079
77997
  async function runSignupStartWithCredentialLock(params) {
77080
77998
  const { configDir, flags } = params;
77081
77999
  const deps = params.deps ?? {};
78000
+ refuseEmailCodeStartWithoutTerminal({
78001
+ copy: params.copy,
78002
+ email: params.email,
78003
+ flags,
78004
+ interactive: deps.interactive
78005
+ });
77082
78006
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
77083
- const email = params.email ?? await promptRequiredFn("Email: ");
78007
+ const email = givenEmail(params.email) ?? await promptRequiredFn("Email: ");
77084
78008
  await checkExistingCredentials({
77085
78009
  apiBaseUrl: flags["api-base-url"],
77086
78010
  configDir,
@@ -77149,10 +78073,16 @@ async function runSignupConfirmWithCredentialLock(params) {
77149
78073
  }
77150
78074
  const payload = extractErrorPayload(verified.error);
77151
78075
  const code = extractErrorCode(payload);
77152
- if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again, run ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).resendCommand(params.email)}, or run primitive signup status.`);
78076
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
78077
+ if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again, run ${copy.resendCommand(params.email)}, or run primitive signup status.`);
78078
+ if (copy.browserLoginCommand && code === MANAGED_DOMAIN) throw managedDomainLoginError({
78079
+ browserLoginCommand: copy.browserLoginCommand,
78080
+ email: params.email,
78081
+ interactive: canPrompt(deps.interactive)
78082
+ });
77153
78083
  if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
77154
78084
  writeErrorWithHints(payload);
77155
- throw cliError$2("Primitive agent signup failed while verifying the account.");
78085
+ throw cliError$2(`Primitive ${copy.actionNoun} failed while verifying the account.`);
77156
78086
  }
77157
78087
  async function runSignupResendWithCredentialLock(params) {
77158
78088
  const deps = params.deps ?? {};
@@ -77167,11 +78097,12 @@ async function runSignupResendWithCredentialLock(params) {
77167
78097
  configDir: params.configDir,
77168
78098
  email: params.email
77169
78099
  }) : loadPendingAgentSignup(params.configDir, requestConfig.resolvedApiBaseUrl);
77170
- if (!pending) throw cliError$2(`No pending ${copy.actionNoun} found. Run \`primitive signup status\` to inspect pending state, or start one with \`${pendingSignupStartCommand()}\`.`);
78100
+ if (!pending) throw cliError$2(`No pending ${copy.actionNoun} found. Run \`primitive signup status\` to inspect pending state, or start one with \`primitive ${copy.startCommand("<email>")} --accept-terms\`.`);
77171
78101
  const resend = await resendVerificationCode({
77172
78102
  apiBaseUrl: requestConfig.resolvedApiBaseUrl,
77173
78103
  apiClient,
77174
78104
  configDir: params.configDir,
78105
+ copy,
77175
78106
  deps,
77176
78107
  start: pending
77177
78108
  });
@@ -77180,6 +78111,7 @@ async function runSignupResendWithCredentialLock(params) {
77180
78111
  async function runSignupInteractiveWithCredentialLock(params) {
77181
78112
  const { configDir, flags } = params;
77182
78113
  const deps = params.deps ?? {};
78114
+ refuseInteractiveSignupWithoutTerminal(deps.interactive);
77183
78115
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
77184
78116
  await checkExistingCredentials({
77185
78117
  apiBaseUrl: flags["api-base-url"],
@@ -77243,7 +78175,7 @@ async function runSignupInteractiveWithCredentialLock(params) {
77243
78175
  }
77244
78176
  function commonStartFlags() {
77245
78177
  return {
77246
- "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
78178
+ "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy. Required when there is no terminal to ask in." }),
77247
78179
  "api-base-url": Flags.string({
77248
78180
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
77249
78181
  env: "PRIMITIVE_API_BASE_URL",
@@ -77271,6 +78203,10 @@ var SignupCommand = class SignupCommand extends Command {
77271
78203
  static flags = commonStartFlags();
77272
78204
  async run() {
77273
78205
  const { args, flags } = await this.parse(SignupCommand);
78206
+ refuseEmailCodeStartWithoutTerminal({
78207
+ email: args.email,
78208
+ flags
78209
+ });
77274
78210
  let releaseCredentialsLock;
77275
78211
  try {
77276
78212
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
@@ -77399,7 +78335,7 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
77399
78335
  fromFile: flags["code-from-file"],
77400
78336
  fromEnv: flags["code-from-env"]
77401
78337
  });
77402
- if (resolvedCode.kind === "error") throw cliError$2(resolvedCode.message);
78338
+ if (resolvedCode.kind === "error") throw usageError$1(resolvedCode.message);
77403
78339
  let releaseCredentialsLock;
77404
78340
  try {
77405
78341
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
@@ -77486,6 +78422,7 @@ var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
77486
78422
  static flags = commonStartFlags();
77487
78423
  async run() {
77488
78424
  const { flags } = await this.parse(SignupInteractiveCommand);
78425
+ refuseInteractiveSignupWithoutTerminal();
77489
78426
  let releaseCredentialsLock;
77490
78427
  try {
77491
78428
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
@@ -79758,6 +80695,8 @@ var OrgLeaveCommand = class OrgLeaveCommand extends Command {
79758
80695
  };
79759
80696
  async run() {
79760
80697
  const { flags } = await this.parse(OrgLeaveCommand);
80698
+ const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
80699
+ if (!flags.yes && !interactive) this.error("Not leaving: no terminal to confirm in. Pass --yes to leave without confirmation.", { exit: 2 });
79761
80700
  await runWithTiming(flags.time, async () => {
79762
80701
  const { auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
79763
80702
  apiKey: flags["api-key"],
@@ -79772,7 +80711,7 @@ var OrgLeaveCommand = class OrgLeaveCommand extends Command {
79772
80711
  orgLabel,
79773
80712
  yes: flags.yes,
79774
80713
  stepUpToken: flags["step-up-token"],
79775
- interactive: Boolean(process.stdin.isTTY && process.stderr.isTTY),
80714
+ interactive,
79776
80715
  prompt: promptLine,
79777
80716
  write: (text) => process.stderr.write(text)
79778
80717
  });
@@ -79786,7 +80725,7 @@ var OrgLeaveCommand = class OrgLeaveCommand extends Command {
79786
80725
  return;
79787
80726
  case "needs_confirmation":
79788
80727
  process.stderr.write("Not leaving: no terminal to confirm in. Pass --yes to leave without confirmation.\n");
79789
- process.exitCode = 1;
80728
+ process.exitCode = 2;
79790
80729
  return;
79791
80730
  case "error":
79792
80731
  writeErrorWithHints(outcome.payload);
@@ -80062,13 +81001,15 @@ function loadSavedLogin(configDir) {
80062
81001
  /** One line per organization, the current one marked with `*`. */
80063
81002
  function formatOrgRows(rows) {
80064
81003
  if (rows.length === 0) return "You do not belong to any organization.";
80065
- return rows.map((row) => {
81004
+ const lines = rows.map((row) => {
80066
81005
  const marker = row.current ? "*" : " ";
80067
81006
  const name = row.name ?? "(unnamed)";
80068
- const role = row.role ? ` ${row.role}` : "";
81007
+ const role = row.role ? ` ${roleKeyLabel(row.role)}` : "";
80069
81008
  const status = row.available ? "available" : `unavailable: ${row.message ?? row.reason ?? "no CLI access"}${row.reason ? ` [${row.reason}]` : ""}`;
80070
81009
  return `${marker} ${name} (${row.id})${role}, ${status}`;
80071
- }).join("\n");
81010
+ });
81011
+ if (rows.some((row) => row.role === "restricted")) lines.push(`role restricted: no role at all, or ${RESTRICTED_ROLE_NOTE}.`);
81012
+ return lines.join("\n");
80072
81013
  }
80073
81014
  var OrgsCommand = class OrgsCommand extends Command {
80074
81015
  static description = `List the organizations you can switch the CLI to, with your role and whether the CLI may be used there. The current organization is marked with *.
@@ -80174,7 +81115,7 @@ var OrgUseCommand = class OrgUseCommand extends Command {
80174
81115
  }, null, 2));
80175
81116
  return;
80176
81117
  }
80177
- this.log(`Now using ${org.org_name ?? org.org_id} (${org.org_id}), role ${org.role ?? "unknown"}.`);
81118
+ this.log(`Now using ${org.org_name ?? org.org_id} (${org.org_id}), ${org.role ? roleKeyText(org.role) : "role unknown"}.`);
80178
81119
  } catch (error) {
80179
81120
  if (error instanceof CliOrgRefusedError) throw new Errors.CLIError(`Cannot switch to ${args.org}: ${error.message} [${error.reason}]`, {
80180
81121
  code: error.reason,
@@ -81253,7 +82194,7 @@ function formatRoles(list) {
81253
82194
  }
81254
82195
  function formatGroups(list) {
81255
82196
  if (list.groups.length === 0) return "No groups.";
81256
- return list.groups.map((group) => `${group.id} ${group.name} ${group.members.length} members role ${group.role_id ?? "none"}`).join("\n");
82197
+ return list.groups.map((group) => `${group.id} ${group.name} ${group.members.length} members role id ${group.role_id ?? "none"}`).join("\n");
81257
82198
  }
81258
82199
  function formatMailboxAccess(list) {
81259
82200
  if (list.entries.length === 0) return "No mailbox access entries.";
@@ -81289,7 +82230,7 @@ function formatAccessMode(mode) {
81289
82230
  return `Access mode: ${mode.mode} (version ${mode.version}${mode.changed_at ? `, changed ${mode.changed_at}` : ""}).`;
81290
82231
  }
81291
82232
  var RolesListCommand = class RolesListCommand extends Command {
81292
- static description = "List the organization's roles (built-in, template and custom) with their permission counts, the templates that can be assigned, and whether role assignment is available. The built-in roles are owner, admin, operator and member; operator has every permission admin has except managing members, changing roles, sending invitations and billing.";
82233
+ static description = "List the organization's roles (built-in, template and custom) with their permission counts, the templates that can be assigned, and whether role assignment is available. The built-in roles are owner, admin and member. Operator is a template with every permission admin has except managing members, changing roles, managing mailbox delegations, sending invitations and billing.";
81293
82234
  static summary = "List roles";
81294
82235
  static examples = ["<%= config.bin %> roles list", "<%= config.bin %> roles list --json"];
81295
82236
  static flags = SHARED_FLAGS;
@@ -82794,9 +83735,13 @@ var SignalCommand = class SignalCommand extends Command {
82794
83735
  function cliError(message) {
82795
83736
  return new Errors.CLIError(message, { exit: 1 });
82796
83737
  }
83738
+ function usageError(message) {
83739
+ return new Errors.CLIError(message, { exit: 2 });
83740
+ }
82797
83741
  const SIGNIN_OTP_COPY = {
82798
83742
  actionNoun: "sign-in",
82799
83743
  actionGerund: "signing in",
83744
+ browserLoginCommand: "signin browser",
82800
83745
  confirmCommand: (email) => `signin otp confirm ${email} <code>`,
82801
83746
  resendCommand: (email) => `signin otp resend ${email}`,
82802
83747
  startCommand: (email) => `signin otp ${email}`
@@ -82804,6 +83749,7 @@ const SIGNIN_OTP_COPY = {
82804
83749
  const SIGNIN_EMAIL_COPY = {
82805
83750
  actionNoun: "sign-in",
82806
83751
  actionGerund: "signing in",
83752
+ browserLoginCommand: "signin browser",
82807
83753
  confirmCommand: (email) => `signin confirm ${email} <code>`,
82808
83754
  resendCommand: (email) => `signin resend ${email}`,
82809
83755
  startCommand: (email) => `signin ${email}`
@@ -82811,6 +83757,7 @@ const SIGNIN_EMAIL_COPY = {
82811
83757
  const LOGIN_EMAIL_COPY = {
82812
83758
  actionNoun: "login",
82813
83759
  actionGerund: "logging in",
83760
+ browserLoginCommand: "login browser",
82814
83761
  confirmCommand: (email) => `login confirm ${email} <code>`,
82815
83762
  resendCommand: (email) => `login resend ${email}`,
82816
83763
  startCommand: (email) => `login ${email}`
@@ -82818,6 +83765,7 @@ const LOGIN_EMAIL_COPY = {
82818
83765
  const LOGIN_OTP_COPY = {
82819
83766
  actionNoun: "login",
82820
83767
  actionGerund: "logging in",
83768
+ browserLoginCommand: "login browser",
82821
83769
  confirmCommand: (email) => `login otp confirm ${email} <code>`,
82822
83770
  resendCommand: (email) => `login otp resend ${email}`,
82823
83771
  startCommand: (email) => `login otp ${email}`
@@ -82825,10 +83773,12 @@ const LOGIN_OTP_COPY = {
82825
83773
  const OTP_COPY = {
82826
83774
  actionNoun: "email-code auth",
82827
83775
  actionGerund: "authenticating",
83776
+ browserLoginCommand: "login browser",
82828
83777
  confirmCommand: (email) => `otp confirm ${email} <code>`,
82829
83778
  resendCommand: (email) => `otp resend ${email}`,
82830
83779
  startCommand: (email) => `otp ${email}`
82831
83780
  };
83781
+ const EMAIL_CODE_ACCOUNT_NOTE = "The code logs you in to the account that uses the address, or creates a new account when none does. Primitive sends no emailed code to an address on a domain it receives mail for: use browser login instead.";
82832
83782
  function acquireCredentialsLock(configDir) {
82833
83783
  try {
82834
83784
  return acquireCliCredentialsLock(configDir);
@@ -82838,7 +83788,7 @@ function acquireCredentialsLock(configDir) {
82838
83788
  }
82839
83789
  function commonOtpStartFlags() {
82840
83790
  return {
82841
- "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
83791
+ "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy. Required when there is no terminal to ask in." }),
82842
83792
  "api-base-url": Flags.string({
82843
83793
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
82844
83794
  env: "PRIMITIVE_API_BASE_URL",
@@ -82853,13 +83803,15 @@ function commonOtpStartFlags() {
82853
83803
  }
82854
83804
  var SigninCommand = class extends LoginCommand$1 {
82855
83805
  static args = { email: Args.string({
82856
- description: "Email address for email-code sign-in. Omit it to use browser approval.",
83806
+ description: "Email address for email-code auth, which logs in to the account that uses it or creates one when none does. Omit it to use browser approval.",
82857
83807
  required: false
82858
83808
  }) };
82859
- static description = `Sign in or log in to an existing Primitive account and save an org-scoped OAuth session locally.
83809
+ static description = `Sign in to Primitive and save the session locally.
82860
83810
 
82861
- Run \`primitive signin <email> --accept-terms\` for email-code sign-in, then \`primitive signin confirm <email> <code>\`. Run \`primitive signin\` with no email to use browser approval; \`primitive signin browser\` is the explicit browser form. \`primitive login\` supports the same flows with login-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form. For new account creation, use \`primitive signup <email>\`. Browser approval logs you in as yourself where the server supports it, so you can switch organizations with \`primitive org use <org>\` or \`--org <org>\` on any command.`;
82862
- static summary = "Sign in to an existing account";
83811
+ Run \`primitive signin\` with no email to use browser approval; \`primitive signin browser\` is the explicit browser form. Browser approval logs you in as yourself where the server supports it, so you can switch organizations with \`primitive org use <org>\` or \`--org <org>\` on any command.
83812
+
83813
+ Run \`primitive signin <email> --accept-terms\` for email-code sign-in, then \`primitive signin confirm <email> <code>\`. ${EMAIL_CODE_ACCOUNT_NOTE} \`primitive login\` supports the same flows with login-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form; \`primitive signup <email>\` is the same flow, worded for creating an account.`;
83814
+ static summary = "Sign in with browser approval or an emailed code";
82863
83815
  static examples = [
82864
83816
  "<%= config.bin %> signin",
82865
83817
  "<%= config.bin %> signin browser",
@@ -82880,12 +83832,17 @@ Run \`primitive signin <email> --accept-terms\` for email-code sign-in, then \`p
82880
83832
  async run() {
82881
83833
  const commandClass = this.constructor;
82882
83834
  const { args, flags } = await this.parse(commandClass);
82883
- if (args.email && flags.permission?.length) throw cliError("--permission applies to browser login. Run the command without an email to use it.");
83835
+ if (args.email && flags.permission?.length) throw usageError("--permission applies to browser login. Run the command without an email to use it.");
82884
83836
  if (!args.email) {
82885
- if (flags["accept-terms"]) throw cliError(`Email-code auth needs an email address. Run \`primitive ${this.emailCodeCopy().startCommand("<email>")} --accept-terms\`.`);
83837
+ if (flags["accept-terms"]) throw usageError(`Email-code auth needs an email address. Run \`primitive ${this.emailCodeCopy().startCommand("<email>")} --accept-terms\`.`);
82886
83838
  await this.runBrowserLogin(flags, this.retryCommand());
82887
83839
  return;
82888
83840
  }
83841
+ refuseEmailCodeStartWithoutTerminal({
83842
+ copy: this.emailCodeCopy(),
83843
+ email: args.email,
83844
+ flags
83845
+ });
82889
83846
  const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
82890
83847
  try {
82891
83848
  await runSignupStartWithCredentialLock({
@@ -82907,10 +83864,12 @@ Run \`primitive signin <email> --accept-terms\` for email-code sign-in, then \`p
82907
83864
  };
82908
83865
  var LoginCommand = class extends SigninCommand {
82909
83866
  static args = SigninCommand.args;
82910
- static description = `Log in or sign in to an existing Primitive account and save an org-scoped OAuth session locally.
83867
+ static description = `Log in to Primitive and save the session locally.
83868
+
83869
+ Run \`primitive login\` with no email to use browser approval; \`primitive login browser\` is the explicit browser form. Browser approval logs you in as yourself where the server supports it, so you can switch organizations with \`primitive org use <org>\` or \`--org <org>\` on any command.
82911
83870
 
82912
- Run \`primitive login <email> --accept-terms\` for email-code login, then \`primitive login confirm <email> <code>\`. Run \`primitive login\` with no email to use browser approval; \`primitive login browser\` is the explicit browser form. \`primitive signin\` supports the same flows with signin-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form. For new account creation, use \`primitive signup <email>\`. Browser approval logs you in as yourself where the server supports it, so you can switch organizations with \`primitive org use <org>\` or \`--org <org>\` on any command.`;
82913
- static summary = "Log in to an existing account";
83871
+ Run \`primitive login <email> --accept-terms\` for email-code login, then \`primitive login confirm <email> <code>\`. ${EMAIL_CODE_ACCOUNT_NOTE} \`primitive signin\` supports the same flows with signin-shaped commands. \`primitive otp <email>\` is the shortest email-code auth form; \`primitive signup <email>\` is the same flow, worded for creating an account.`;
83872
+ static summary = "Log in with browser approval or an emailed code";
82914
83873
  static examples = [
82915
83874
  "<%= config.bin %> login",
82916
83875
  "<%= config.bin %> login browser",
@@ -82959,13 +83918,18 @@ var SigninOtpCommand = class extends Command {
82959
83918
  description: "Email address to sign in with",
82960
83919
  required: false
82961
83920
  }) };
82962
- static description = "Start email-code sign-in using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally.";
83921
+ static description = `Start email-code sign-in using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally. ${EMAIL_CODE_ACCOUNT_NOTE}`;
82963
83922
  static summary = "Start OTP sign-in";
82964
83923
  static examples = ["<%= config.bin %> signin otp user@example.com --accept-terms", "<%= config.bin %> signin otp confirm user@example.com 123456"];
82965
83924
  static flags = commonOtpStartFlags();
82966
83925
  async run() {
82967
83926
  const commandClass = this.constructor;
82968
83927
  const { args, flags } = await this.parse(commandClass);
83928
+ refuseEmailCodeStartWithoutTerminal({
83929
+ copy: this.emailCodeCopy(),
83930
+ email: args.email,
83931
+ flags
83932
+ });
82969
83933
  const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
82970
83934
  try {
82971
83935
  await runSignupStartWithCredentialLock({
@@ -82984,7 +83948,7 @@ var SigninOtpCommand = class extends Command {
82984
83948
  };
82985
83949
  var LoginOtpCommand = class extends SigninOtpCommand {
82986
83950
  static args = SigninOtpCommand.args;
82987
- static description = "Start email-code login using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally.";
83951
+ static description = `Start email-code login using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally. ${EMAIL_CODE_ACCOUNT_NOTE}`;
82988
83952
  static summary = "Start OTP login";
82989
83953
  static examples = ["<%= config.bin %> login otp user@example.com --accept-terms", "<%= config.bin %> login otp confirm user@example.com 123456"];
82990
83954
  static flags = SigninOtpCommand.flags;
@@ -82994,7 +83958,7 @@ var LoginOtpCommand = class extends SigninOtpCommand {
82994
83958
  };
82995
83959
  var OtpCommand = class extends SigninOtpCommand {
82996
83960
  static args = SigninOtpCommand.args;
82997
- static description = "Start email-code authentication, send a verification code, and save the pending token locally.";
83961
+ static description = `Start email-code authentication, send a verification code, and save the pending token locally. ${EMAIL_CODE_ACCOUNT_NOTE}`;
82998
83962
  static summary = "Start email-code auth";
82999
83963
  static examples = ["<%= config.bin %> otp user@example.com --accept-terms", "<%= config.bin %> otp confirm user@example.com 123456"];
83000
83964
  static flags = SigninOtpCommand.flags;
@@ -84662,7 +85626,7 @@ function formatGrantLines(identity, options = {}) {
84662
85626
  const lines = [];
84663
85627
  const org = options.orgName ? `${options.orgName} (${identity.org_id})` : identity.org_id;
84664
85628
  lines.push(`Organization: ${org}`);
84665
- lines.push(`Role: ${identity.role}`);
85629
+ if (isPersonIdentity(identity)) lines.push(identity.role === "member" ? "Built-in role: member (also shown for a template or custom role that does not fully include member; the Grant line is what this login can do)" : isBuiltInRoleKey(identity.role) ? `Built-in role: ${identity.role}` : `Role key: ${identity.role}`);
84666
85630
  const grant = identity.grant;
84667
85631
  if (grant) lines.push(...formatGrant(grant));
84668
85632
  if (options.userLogin) lines.push("Signed in as yourself. Switch organizations with `primitive org use <org>`.");
@@ -84672,10 +85636,32 @@ function formatPermissionCheck(check) {
84672
85636
  if (check.allow) return `${check.permission}: allowed`;
84673
85637
  return `${check.permission}: not allowed${check.reason ? ` (${check.reason})` : ""}`;
84674
85638
  }
84675
- function formatWhoamiSummary(account, managedInboxDomain, orgName = null) {
85639
+ /** Whether GET /whoami answered for an API key. */
85640
+ function isKeyIdentity(identity) {
85641
+ return Boolean(identity.key_id) || identity.auth_method === "api_key";
85642
+ }
85643
+ /** Whether GET /whoami answered for a person's own session or OAuth login. */
85644
+ const PERSON_AUTH_METHODS = /* @__PURE__ */ new Set([
85645
+ "oauth",
85646
+ "session",
85647
+ "dashboard_bridge"
85648
+ ]);
85649
+ function isPersonIdentity(identity) {
85650
+ return !isKeyIdentity(identity) && typeof identity.auth_method === "string" && PERSON_AUTH_METHODS.has(identity.auth_method);
85651
+ }
85652
+ /** What the summary's first line says about who is calling. */
85653
+ function identityLine(identity) {
85654
+ if (!identity || identity.member_address === void 0) return "Authenticated";
85655
+ if (identity.member_address) return `Authenticated as ${identity.member_address.address} (your member address)`;
85656
+ if (isKeyIdentity(identity)) return "Authenticated (API keys have no member address)";
85657
+ if (!isPersonIdentity(identity)) return "Authenticated (this credential has no member address)";
85658
+ return `Authenticated (you have no member address in this organization${identity.member_address_suggestion ? `; \`primitive account provision-member-address --address ${identity.member_address_suggestion}\` sets one` : ""})`;
85659
+ }
85660
+ function formatWhoamiSummary(account, managedInboxDomain, orgName = null, identity = null) {
84676
85661
  const lines = [
84677
- account.email ? `Authenticated as ${account.email}` : "Authenticated (this credential may not see the account email)",
85662
+ identityLine(identity),
84678
85663
  orgName ? `Organization: ${orgName} (${account.id})` : `Organization id: ${account.id}`,
85664
+ ...account.email ? [`Account email: ${account.email} (the email the account was opened with)`] : [],
84679
85665
  `Plan: ${account.plan}`
84680
85666
  ];
84681
85667
  if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
@@ -84726,7 +85712,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
84726
85712
  hidden: true
84727
85713
  }),
84728
85714
  json: Flags.boolean({ description: "Print the account JSON response, or the saved connected identity marked verification: offline. Never prints credentials." }),
84729
- grant: Flags.boolean({ description: "With --json, add `grant`: the organization, role and grant of this credential. The summary always shows them when the server reports them." }),
85715
+ grant: Flags.boolean({ description: "With --json, add `grant`: the organization, role and grant of this credential. The summary shows them when the server reports them; only a person's own login has a role line." }),
84730
85716
  check: Flags.string({ description: "Ask whether this credential may use a permission, such as mail:send. Prints the decision and exits 1 when it is not allowed. Nothing is enforced by asking." }),
84731
85717
  resource: Flags.string({
84732
85718
  description: "With --check, the resource to check against, as function:<id>, domain:<id>, endpoint:<id> or route:<id>.",
@@ -84855,7 +85841,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
84855
85841
  }, null, 2));
84856
85842
  return;
84857
85843
  }
84858
- const summary = [formatWhoamiSummary(account, managedInboxDomain, orgName)];
85844
+ const summary = [formatWhoamiSummary(account, managedInboxDomain, orgName, identity)];
84859
85845
  if (identity?.grant) summary.push(...formatGrantLines(identity, {
84860
85846
  orgName: auth.credentials?.org_name ?? null,
84861
85847
  userLogin: auth.credentials?.auth_method === "user_grant"
@@ -85318,6 +86304,7 @@ const COMMANDS = {
85318
86304
  orgs: OrgsCommand,
85319
86305
  "org:use": OrgUseCommand,
85320
86306
  "cli:list-cli-orgs": OrgsCommand,
86307
+ "activity:asked": ActivityAskedCommand,
85321
86308
  "activity:created": ActivityCreatedCommand,
85322
86309
  "keys:list": KeysListCommand,
85323
86310
  "keys:get": KeysGetCommand,