primcli 3.0.0-preview.6 → 3.0.0-preview.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{api-command-CRjIK1QL.js → api-command-TUwewvqZ.js} +2840 -444
- package/dist/{json-output-DWrJMakZ.js → json-output-ch756TXV.js} +8 -1
- package/dist/{machine-session-3H-rNH8v.js → machine-session-DNnNhhFr.js} +5 -5
- package/dist/{notify-session-DTyg-sfS.js → notify-session-CeIflLIX.js} +3 -3
- package/dist/oclif/auto-signal-worker.js +3 -3
- package/dist/oclif/auto-signals.js +1 -1
- package/dist/oclif/command-not-found.js +1 -1
- package/dist/oclif/index.js +1709 -381
- package/dist/oclif/proxy-auto-detect.js +1 -1
- package/dist/oclif/receiver-heal.js +2 -2
- package/dist/{repeats-Dj_o6c-X-CP0h4alF.js → repeats-Dj_o6c-X-DNbrLjEF.js} +30 -12
- package/dist/{scoped-chat-CvqdlWuX.js → scoped-chat-Dd3P8Q-m.js} +1 -1
- package/dist/{signal-command-CPxRPbHK.js → signal-command-DZQemE9I.js} +4 -4
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { $ as
|
|
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-
|
|
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-
|
|
1
|
+
import { $ as readEmailInteraction, A as explainPendingMailNotFound, At as cliInvocation, Bt as formatAlreadySentNotice, C as promptLine$2, Ct as sendOutcomeExitCode, D as formatBrowserLoginInstructions, Dt as successfulSendOutcome, E as canOfferCopyHotkey, Et as serializeErrorPayload, F as latestOwnOutbound, Ft as loadSkillLine, G as clearConsumedPendingMail, Gt as classificationFactLines, H as wakeRecipientField, Ht as emptyEmailListHint, I as readThreadContext, It as currentMailSessionKey, J as readPendingMail, Jt as noFactsMessage, K as clearReadPendingMail, Kt as classificationOf, L as sentInThread, Lt as detectAgentRuntime, M as describeWake, N as displayAddress, Nt as generatedAgentName, O as listenForCopyHotkey, Ot as thrownErrorExitCode, P as formatMailWakeLine, Pt as identitySuggestions, Q as interactionNextActions, Qt as ConnectedAgentKeyRequiredError, R as serverMuted, Rt as detectRuntimeSession, S as approveThenRerunHint, St as sendIdentityFields, T as withApproval, Tt as sentHistoryWindowStart, U as wakeRelationship, Ut as mailReach, V as wakeReadCommand, Vt as withFlagSuggestion, W as claimPendingMail, Wt as readCallerMailFor, X as removePendingMail, Xt as ATTACHMENT_WAIT_MAX_MS, Y as recordPendingMail, Yt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, Z as interactionHumanLine, Zt as withAttachmentWait, _ as takesStepUp, _n as setKnownCommands, _t as formatPriorRepliesWarning, a as createOperationCommand, an as selectedOrgFromEnv, at as SEND_OUTCOME_HELP, b as STEP_UP_HEADER$1, bt as priorRepliesThatWentOut, c as formatErrorPayload, cn as credentialsFromUserLogin, ct as buildFollowUpCommand, d as isGrantRefusal, dn as revokeUserLogin, dt as classifySendError, en as createAuthenticatedCliApiClient, et as replyExpectation, f as parameterFlagName, fn as roleKeyLabel, ft as deriveSendIdempotencyKey, g as surfaceUnauthorizedHint, gn as sanitizeForTerminal, gt as formatPriorRepliesCheckSkipped, h as runWithTiming, hn as routeWarningLines, ht as formatDeletedEarlierSendNotice, i as TIME_FLAG_DESCRIPTION, in as resolveCliApiRequestConfig, it as MAX_SEND_WAIT_TIMEOUT_MS, j as bareAddress, jt as connectNextSteps, k as openBrowser, l as hasControlCharacter$1, ln as fetchCliOrgs, lt as buildThrownSendFailureEnvelope, m as refusalDetails, mn as switchCurrentOrg, mt as followUpCommandPrefix, n as API_ERROR_CODES, nn as refreshStoredCliCredentials, o as extractErrorCode, on as CliOrgRefusedError, ot as apiResultHttpStatus, p as readTextFileFlag, pn as roleKeyText, pt as excludeSignalReplies, q as pendingMailPath, qt as isEmailId, r as OPERATION_PATH_FLAGS, rn as refuseOrgSelectionForProfile, rt as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, s as extractErrorPayload, sn as RESTRICTED_ROLE_NOTE, st as assertValidIdempotencyKey, t as API_BASE_URL_FLAG_DESCRIPTION, tn as createCliApiClient, tt as replyInteractionWarning, u as headerFlagName, un as isBuiltInRoleKey, ut as checkPriorReplies, v as writeErrorWithHints, vn as operationManifest, vt as formatSendFailureSummary, w as terminalApprovalIo, wt as sendWaitClampNotice, x as STEP_UP_TOKEN_FLAG_DESCRIPTION, xt as reportSendCommandResult, y as BROWSER_FLAG_DESCRIPTION, yt as formatSendRecordFailureSummary, z as serverRelationship, zt as runtimeSessionVariableList } from "../api-command-TUwewvqZ.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-DNbrLjEF.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-CeIflLIX.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-
|
|
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-
|
|
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-DNnNhhFr.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-Dd3P8Q-m.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-
|
|
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-DZQemE9I.js";
|
|
10
|
+
import { a as thrownErrorBody, i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-ch756TXV.js";
|
|
10
11
|
import { a as backgroundListenStatus, c as runBackgroundListen, d as stopBackgroundListen, f as verifyBackgroundListenTarget, o as backgroundListenToken, r as backgroundListenConfiguration, u as startBackgroundListen } from "../listen-background-DyBYca28.js";
|
|
11
|
-
import { i as runStreamingJson, n as guardJsonOutputCommands, r as jsonOutputRequested, t as JSON_OUTPUT_FLAG } from "../json-output-DWrJMakZ.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";
|
|
@@ -5317,7 +5317,7 @@ const openapiDocument = {
|
|
|
5317
5317
|
} }
|
|
5318
5318
|
},
|
|
5319
5319
|
"503": {
|
|
5320
|
-
"description": "Local receiving is unavailable",
|
|
5320
|
+
"description": "Local receiving is unavailable (pull_unavailable), or the subscription was too busy to claim from in time (service_unavailable): nothing was claimed, so retry the pull after Retry-After",
|
|
5321
5321
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
5322
5322
|
}
|
|
5323
5323
|
},
|
|
@@ -21978,7 +21978,7 @@ const openapiDocument = {
|
|
|
21978
21978
|
"/email-rules/compile": { "post": {
|
|
21979
21979
|
"operationId": "compileEmailRule",
|
|
21980
21980
|
"summary": "Compile a rule description",
|
|
21981
|
-
"description": "Turns a plain-English description of which received emails to match into a rule tree over the published column contract, with a plain-English explanation of what it matches, the assumptions made, and warnings for anything the columns cannot express. Where the columns fall short the rule may carry up to
|
|
21981
|
+
"description": "Turns a plain-English description of which received emails to match into a rule tree over the published column contract, with a plain-English explanation of what it matches, the assumptions made, and warnings for anything the columns cannot express. Where the columns fall short the rule may carry up to five yes/no questions, asked about an email only after its conditions match. Nothing is saved. A rule with no questions has its conditions counted, read only, over the caller's recent classified mail (what a test of it covers by default): breadth is that count, and a warning says when it matches nearly all of it. The SQL the rule renders to is returned only when include_sql is true. kind do_not_share compiles a do-not-share rule for recall: conditions only as narrow as the description says, the judgement of what it describes put into questions, and with nothing a column decides for certain, conditions that hold for every email. 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.",
|
|
21982
21982
|
"tags": ["Email Rules"],
|
|
21983
21983
|
"security": [{ "BearerAuth": [] }],
|
|
21984
21984
|
"requestBody": {
|
|
@@ -22107,7 +22107,7 @@ const openapiDocument = {
|
|
|
22107
22107
|
}
|
|
22108
22108
|
},
|
|
22109
22109
|
"400": {
|
|
22110
|
-
"description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids (with the expected emails), 1 to 100 expected emails none in both lists, and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule whose current version no longer renders answers details.reason version_not_renderable; email_ids that with its saved expectations name over 200 emails, too_many_emails.",
|
|
22110
|
+
"description": "validation_error: give exactly one of tree and rule_id, at most 200 email_ids (with the expected emails), 1 to 100 expected emails none in both lists (or only expect.samples, up to 20), and a tree that is valid for the column contract. evaluation_scope, scope_selection and kind go only with tree. A saved rule whose current version no longer renders answers details.reason version_not_renderable; email_ids that with its saved expectations name over 200 emails, too_many_emails.",
|
|
22111
22111
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22112
22112
|
},
|
|
22113
22113
|
"401": {
|
|
@@ -22178,6 +22178,111 @@ const openapiDocument = {
|
|
|
22178
22178
|
}
|
|
22179
22179
|
}]
|
|
22180
22180
|
} },
|
|
22181
|
+
"/email-rules/look-alikes": { "post": {
|
|
22182
|
+
"operationId": "generateLookAlikes",
|
|
22183
|
+
"summary": "Write look-alike emails a rule should not match",
|
|
22184
|
+
"description": "Writes made-up emails that come close to what a grant rule describes but are private (a personal medical bill, pay for named people, a personal purchase, personal banking or tax, or another near case), and reports what the rule decides for each, as test-sample would. failed means the rule matched it, so it would be shared. Nothing is stored and no mail is read. Keep the ones it must never match with add_expectation_samples, or for a request with the accept's expectation_samples. Limited to 20 an hour per member. 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.",
|
|
22185
|
+
"tags": ["Email Rules"],
|
|
22186
|
+
"security": [{ "BearerAuth": [] }],
|
|
22187
|
+
"requestBody": {
|
|
22188
|
+
"required": true,
|
|
22189
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerateLookAlikesRequest" } } }
|
|
22190
|
+
},
|
|
22191
|
+
"responses": {
|
|
22192
|
+
"200": {
|
|
22193
|
+
"description": "Write look-alike emails a rule should not match",
|
|
22194
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
22195
|
+
"type": "object",
|
|
22196
|
+
"required": ["data"],
|
|
22197
|
+
"properties": { "data": { "$ref": "#/components/schemas/GenerateLookAlikesResult" } }
|
|
22198
|
+
}] } } },
|
|
22199
|
+
"headers": {
|
|
22200
|
+
"ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit" },
|
|
22201
|
+
"ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining" },
|
|
22202
|
+
"ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
|
|
22203
|
+
"ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy" }
|
|
22204
|
+
}
|
|
22205
|
+
},
|
|
22206
|
+
"400": {
|
|
22207
|
+
"description": "validation_error: give exactly one of tree, rule_id and request_id, and a tree valid for the column contract; details.reason do_not_share for a do-not-share rule, no_address when no sign-in address was found and no to given, version_not_renderable for a saved version or a request's rule that no longer fits.",
|
|
22208
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22209
|
+
},
|
|
22210
|
+
"401": {
|
|
22211
|
+
"description": "Invalid or missing API key",
|
|
22212
|
+
"content": { "application/json": {
|
|
22213
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
22214
|
+
"example": {
|
|
22215
|
+
"success": false,
|
|
22216
|
+
"error": {
|
|
22217
|
+
"code": "unauthorized",
|
|
22218
|
+
"message": "Invalid or missing API key"
|
|
22219
|
+
}
|
|
22220
|
+
}
|
|
22221
|
+
} },
|
|
22222
|
+
"headers": {
|
|
22223
|
+
"ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-unauthenticated" },
|
|
22224
|
+
"ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-unauthenticated" },
|
|
22225
|
+
"ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset-unauthenticated" },
|
|
22226
|
+
"ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-unauthenticated" }
|
|
22227
|
+
}
|
|
22228
|
+
},
|
|
22229
|
+
"403": {
|
|
22230
|
+
"description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_scope_not_readable: scope_selection names something you cannot read.",
|
|
22231
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22232
|
+
},
|
|
22233
|
+
"404": {
|
|
22234
|
+
"description": "not_found: rule_id names no rule you can see (details.reason rule_not_found), or request_id no rule request you can see (request_not_found).",
|
|
22235
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22236
|
+
},
|
|
22237
|
+
"409": {
|
|
22238
|
+
"description": "email_rule_author_unresolvable: the rule cannot run as its author (for an unsaved tree or a request, you). email_rule_request_not_pending: request_id names a request already decided.",
|
|
22239
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22240
|
+
},
|
|
22241
|
+
"422": {
|
|
22242
|
+
"description": "email_rule_look_alikes_not_written: the model declined, ran out of room or time, or wrote none that fit (details.reason refused, truncated, deadline or invalid).",
|
|
22243
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22244
|
+
},
|
|
22245
|
+
"429": {
|
|
22246
|
+
"description": "Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.",
|
|
22247
|
+
"headers": {
|
|
22248
|
+
"Retry-After": {
|
|
22249
|
+
"schema": { "type": "integer" },
|
|
22250
|
+
"description": "Seconds to wait before retrying"
|
|
22251
|
+
},
|
|
22252
|
+
"ratelimit-limit": { "$ref": "#/components/headers/ratelimit-limit-429" },
|
|
22253
|
+
"ratelimit-remaining": { "$ref": "#/components/headers/ratelimit-remaining-429" },
|
|
22254
|
+
"ratelimit-reset": { "$ref": "#/components/headers/ratelimit-reset" },
|
|
22255
|
+
"ratelimit-policy": { "$ref": "#/components/headers/ratelimit-policy-429" }
|
|
22256
|
+
},
|
|
22257
|
+
"content": { "application/json": {
|
|
22258
|
+
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
22259
|
+
"example": {
|
|
22260
|
+
"success": false,
|
|
22261
|
+
"error": {
|
|
22262
|
+
"code": "rate_limit_exceeded",
|
|
22263
|
+
"message": "Rate limit exceeded"
|
|
22264
|
+
}
|
|
22265
|
+
}
|
|
22266
|
+
} }
|
|
22267
|
+
},
|
|
22268
|
+
"503": {
|
|
22269
|
+
"description": "email_rule_compiler_unavailable: the model, or the classifier that decides each, is not available or could not be reached.",
|
|
22270
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22271
|
+
}
|
|
22272
|
+
},
|
|
22273
|
+
"x-primitive-permission": ["mail:read"],
|
|
22274
|
+
"parameters": [{
|
|
22275
|
+
"name": "Idempotency-Key",
|
|
22276
|
+
"in": "header",
|
|
22277
|
+
"required": false,
|
|
22278
|
+
"description": "Optional, and not used to deduplicate: this operation stores no result by the key, so a retry with the same key runs the request again. Operations that need a credential refuse a key that is empty, longer than 255 characters or not printable ASCII with 400 `validation_error` and echo a valid one in the Idempotency-Key response header; operations that need none ignore it.",
|
|
22279
|
+
"schema": {
|
|
22280
|
+
"type": "string",
|
|
22281
|
+
"minLength": 1,
|
|
22282
|
+
"maxLength": 255
|
|
22283
|
+
}
|
|
22284
|
+
}]
|
|
22285
|
+
} },
|
|
22181
22286
|
"/email-rules/test-sample": { "post": {
|
|
22182
22287
|
"operationId": "testEmailRuleOnSample",
|
|
22183
22288
|
"summary": "Test a rule against a made-up email",
|
|
@@ -22279,7 +22384,7 @@ const openapiDocument = {
|
|
|
22279
22384
|
"post": {
|
|
22280
22385
|
"operationId": "createEmailRule",
|
|
22281
22386
|
"summary": "Create a rule",
|
|
22282
|
-
"description": "Saves a rule as version 1 from a compile response or a tree you wrote, checked against the column contract; without an explanation the server writes one. It starts in observe (what it matches is recorded, nothing granted or blocked) unless state is draft (not evaluated). You are its author, and it never reaches mail you cannot read. evaluation_scope is own_mailboxes (your own mail) unless you name author (all you can read) or selected; scope_selection may name only what you can read. The answer carries the scope as stored. expectations saves emails it must match and must not; the answer judges them (expectations_check). kind do_not_share blocks sharing: it runs before every other rule, and while active no rule shares an email it matches or cannot decide; a key cannot list it (details.reason rule_blocks_sharing). 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.",
|
|
22387
|
+
"description": "Saves a rule as version 1 from a compile response or a tree you wrote, checked against the column contract; without an explanation the server writes one. It starts in observe (what it matches is recorded, nothing granted or blocked) unless state is draft (not evaluated), or active for a do_not_share rule. You are its author, and it never reaches mail you cannot read. evaluation_scope is own_mailboxes (your own mail) unless you name author (all you can read) or selected; scope_selection may name only what you can read. The answer carries the scope as stored. expectations saves emails it must match and must not; the answer judges them (expectations_check). kind do_not_share blocks sharing: it runs before every other rule, and while active no rule shares an email it matches or cannot decide; a key cannot list it (details.reason rule_blocks_sharing). 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.",
|
|
22283
22388
|
"tags": ["Email Rules"],
|
|
22284
22389
|
"security": [{ "BearerAuth": [] }],
|
|
22285
22390
|
"requestBody": {
|
|
@@ -22375,7 +22480,7 @@ const openapiDocument = {
|
|
|
22375
22480
|
"get": {
|
|
22376
22481
|
"operationId": "listEmailRules",
|
|
22377
22482
|
"summary": "List rules",
|
|
22378
|
-
"description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule). With matched_email_id, only the rules that matched that email. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
|
|
22483
|
+
"description": "The rules the caller can see, newest first: their own, or every member's rules for a caller who reads the whole organization's mail. Each carries its scope (evaluation_scope, and scope_selection for a selected rule) and current_version, but not the version's text: read that under current from GET /email-rules/{id}. With matched_email_id, only the rules that matched that email. 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.",
|
|
22379
22484
|
"tags": ["Email Rules"],
|
|
22380
22485
|
"security": [{ "BearerAuth": [] }],
|
|
22381
22486
|
"parameters": [
|
|
@@ -22498,7 +22603,7 @@ const openapiDocument = {
|
|
|
22498
22603
|
"get": {
|
|
22499
22604
|
"operationId": "getEmailRule",
|
|
22500
22605
|
"summary": "Read a rule",
|
|
22501
|
-
"description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule), its current version and its saved expectations. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
|
|
22606
|
+
"description": "A rule, its scope (evaluation_scope, and scope_selection for a selected rule), its current version and its saved expectations. The current version's text (english, tree, explanation, assumptions, warnings) is under current, and current_version is its number. What the scope comes to for its author now is GET /email-rules/{id}/scope. The SQL the version renders to is included only when include_sql is true. Needs mail:read, a member's own credential with their full access (not an API key, agent key or narrowed token), and the email rules feature.",
|
|
22502
22607
|
"tags": ["Email Rules"],
|
|
22503
22608
|
"security": [{ "BearerAuth": [] }],
|
|
22504
22609
|
"parameters": [{
|
|
@@ -22613,7 +22718,7 @@ const openapiDocument = {
|
|
|
22613
22718
|
"patch": {
|
|
22614
22719
|
"operationId": "updateEmailRule",
|
|
22615
22720
|
"summary": "Change a rule",
|
|
22616
|
-
"description": "Renames the rule, saves a new version (english and tree together, with an explanation or one the server writes; earlier versions stay), changes its state (draft is off; observe; active), whose mail it runs over (evaluation_scope, with scope_selection when selected) or its saved expectations (each saved version and new set is judged in expectations_check). Anyone but its author who sees it may only disable it (state draft), and while it is shared only if they manage members from the dashboard. Without evaluation_scope the scope stays; a scope change saves a new version with the same conditions, and what was granted stays. A shared rule keeps its scope; a kind never changes. Send if_version, the current_version you read, so a write never replaces a version saved since. 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.",
|
|
22721
|
+
"description": "Renames the rule, saves a new version (english and tree together, with an explanation or one the server writes; earlier versions stay) or an earlier one again (restore_version), changes its state (draft is off; observe; active), whose mail it runs over (evaluation_scope, with scope_selection when selected) or its saved expectations (each saved version and new set is judged in expectations_check). Anyone but its author who sees it may only disable it (state draft), and while it is shared only if they manage members from the dashboard. Without evaluation_scope the scope stays; a scope change saves a new version with the same conditions, and what was granted stays. A shared rule keeps its scope; a kind never changes. A saved version takes effect at once (an active rule stays active); affects counts what it reaches. Send if_version, the current_version you read, so a write never replaces a version saved since, or if_revision to refuse it after any update. Without if_expectations_pass a version is saved even when its expectations_check fails. 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.",
|
|
22617
22722
|
"tags": ["Email Rules"],
|
|
22618
22723
|
"security": [{ "BearerAuth": [] }],
|
|
22619
22724
|
"parameters": [{
|
|
@@ -22646,17 +22751,8 @@ const openapiDocument = {
|
|
|
22646
22751
|
}
|
|
22647
22752
|
},
|
|
22648
22753
|
"400": {
|
|
22649
|
-
"description": "
|
|
22650
|
-
"content": { "application/json": {
|
|
22651
|
-
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
22652
|
-
"example": {
|
|
22653
|
-
"success": false,
|
|
22654
|
-
"error": {
|
|
22655
|
-
"code": "validation_error",
|
|
22656
|
-
"message": "Invalid domain format"
|
|
22657
|
-
}
|
|
22658
|
-
}
|
|
22659
|
-
} }
|
|
22754
|
+
"description": "validation_error: invalid body or tree (details.errors), too large (too_large), a version that no longer renders (version_not_renderable), restore_version names the current version (already_current), or over 20 made-up emails (too_many_expectation_samples). no_expectations: if_expectations_pass with no expectations to judge; nothing was saved.",
|
|
22755
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22660
22756
|
},
|
|
22661
22757
|
"401": {
|
|
22662
22758
|
"description": "Invalid or missing API key",
|
|
@@ -22678,24 +22774,15 @@ const openapiDocument = {
|
|
|
22678
22774
|
}
|
|
22679
22775
|
},
|
|
22680
22776
|
"403": {
|
|
22681
|
-
"description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden
|
|
22777
|
+
"description": "feature_not_enabled: the email rules feature is not enabled for this organization yet. Permission refusals answer forbidden or grant_forbidden instead. email_rule_author_only: only the rule's author may make this change. forbidden (rule_shared): the rule is shared, so only its author or a member manager can turn it off. email_rule_scope_not_readable: scope_selection names something you cannot read. grant_forbidden (mail_depth): if_expectations_pass needs content access to mail when it judges emails by id.",
|
|
22682
22778
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22683
22779
|
},
|
|
22684
22780
|
"404": {
|
|
22685
|
-
"description": "
|
|
22686
|
-
"content": { "application/json": {
|
|
22687
|
-
"schema": { "$ref": "#/components/schemas/ErrorResponse" },
|
|
22688
|
-
"example": {
|
|
22689
|
-
"success": false,
|
|
22690
|
-
"error": {
|
|
22691
|
-
"code": "not_found",
|
|
22692
|
-
"message": "Resource not found"
|
|
22693
|
-
}
|
|
22694
|
-
}
|
|
22695
|
-
} }
|
|
22781
|
+
"description": "not_found: no such rule you can see, or restore_version names no version of it (details.reason version_not_found).",
|
|
22782
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22696
22783
|
},
|
|
22697
22784
|
"409": {
|
|
22698
|
-
"description": "email_rule_conflict: the rule changed
|
|
22785
|
+
"description": "email_rule_conflict: the rule changed (details.reason version_moved with current_version, revision_moved with current_revision, expectations_moved or rule_changed); read it again and retry. email_rule_scope_locked: the rule is shared. email_rule_author_unresolvable: the rule could not run as you over the chosen scope. expectations_failed or expectations_undecided (if_expectations_pass): nothing was saved; details carries current_version, state and expectations_check.",
|
|
22699
22786
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22700
22787
|
},
|
|
22701
22788
|
"429": {
|
|
@@ -23713,7 +23800,7 @@ const openapiDocument = {
|
|
|
23713
23800
|
"get": {
|
|
23714
23801
|
"operationId": "getEmailRuleRequest",
|
|
23715
23802
|
"summary": "Read a rule request",
|
|
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.
|
|
23803
|
+
"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, or have look-alikes written for it (POST /v1/email-rules/look-alikes with request_id; primitive email-rules generate-look-alikes). Neither stores anything, and neither needs access beyond testing a rule of your own; the accept keeps any you send as expectation_samples on the rule it writes. 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.",
|
|
23717
23804
|
"tags": ["Email Rules"],
|
|
23718
23805
|
"security": [{ "BearerAuth": [] }],
|
|
23719
23806
|
"parameters": [{
|
|
@@ -24090,7 +24177,7 @@ const openapiDocument = {
|
|
|
24090
24177
|
"/email-rule-requests/{id}/accept": { "post": {
|
|
24091
24178
|
"operationId": "acceptEmailRuleRequest",
|
|
24092
24179
|
"summary": "Accept a rule request",
|
|
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.
|
|
24180
|
+
"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: each email in full with its attachments, but never raw. It does not go back through mail already received. 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, or have look-alikes written for it (POST /v1/email-rules/look-alikes with request_id; primitive email-rules generate-look-alikes). Neither stores anything, and neither needs access beyond testing a rule of your own; the accept keeps any you send as expectation_samples on the rule it writes. Made-up emails sent in expectation_samples are kept on the rule this writes, where GET /v1/email-rules/{id} lists them. 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.",
|
|
24094
24181
|
"tags": ["Email Rules"],
|
|
24095
24182
|
"security": [{ "BearerAuth": [] }],
|
|
24096
24183
|
"parameters": [{
|
|
@@ -24127,7 +24214,7 @@ const openapiDocument = {
|
|
|
24127
24214
|
}] } } }
|
|
24128
24215
|
},
|
|
24129
24216
|
"400": {
|
|
24130
|
-
"description": "validation_error: a changed rule needs english and tree together, and a tree that is valid for the column contract. The rule as asked is checked the same way, and must be changed first if it no longer passes.",
|
|
24217
|
+
"description": "validation_error: a changed rule needs english and tree together, and a tree that is valid for the column contract. The rule as asked is checked the same way, and must be changed first if it no longer passes. Made-up emails in expectation_samples are checked as on a rule write (at most 20, each within the sample bounds). Nothing is accepted.",
|
|
24131
24218
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
24132
24219
|
},
|
|
24133
24220
|
"401": {
|
|
@@ -32286,7 +32373,7 @@ const openapiDocument = {
|
|
|
32286
32373
|
"summary": "Revoke a credential",
|
|
32287
32374
|
"tags": ["Credentials"],
|
|
32288
32375
|
"security": [{ "BearerAuth": [] }],
|
|
32289
|
-
"description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked
|
|
32376
|
+
"description": "Revoke one credential by kind and id. A Function key is rotated, never revoked (409 rotate_instead). An API key is revoked by the person who holds it, whatever it reaches, or 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. Revoking an API key also revokes the keys it replaced in its rotation that are still in their overlap and that you may revoke (predecessors_revoked_count; predecessors_left counts the rest), so its webhooks stop at once; the keys that replaced it carry its work on. Revoking a key already revoked does the same for those keys. 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, each saying whether anything still acts for it (still_acting, with the key in acting_key_id) and, for a webhook, whether it is still delivering. 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, to a caller who could have revoked it, and revoked counts only the keys it replaced that were revoked now; 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.",
|
|
32290
32377
|
"x-primitive-person-only": { "access": "session_or_cli" },
|
|
32291
32378
|
"parameters": [{
|
|
32292
32379
|
"name": "Primitive-Step-Up",
|
|
@@ -33400,7 +33487,7 @@ const openapiDocument = {
|
|
|
33400
33487
|
"summary": "Rotate an API key",
|
|
33401
33488
|
"tags": ["Credentials"],
|
|
33402
33489
|
"security": [{ "BearerAuth": [] }],
|
|
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.",
|
|
33490
|
+
"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. Revoking the successor during the overlap also revokes the old key when you may revoke that one too; the revoke's predecessors_left counts any left working. 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.",
|
|
33404
33491
|
"x-primitive-person-only": {
|
|
33405
33492
|
"access": "step_up",
|
|
33406
33493
|
"step_up_purpose": "keys.create",
|
|
@@ -33567,7 +33654,7 @@ const openapiDocument = {
|
|
|
33567
33654
|
"summary": "Change an API key",
|
|
33568
33655
|
"tags": ["Credentials"],
|
|
33569
33656
|
"security": [{ "BearerAuth": [] }],
|
|
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.",
|
|
33657
|
+
"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. The one exception is suspending a key (suspended true with no other change; expected_grant_ref may accompany it), which the person who holds it may do whatever the key reaches, calling as themself rather than through another app; lifting a suspension is a change like any other. 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.",
|
|
33571
33658
|
"x-primitive-person-only": {
|
|
33572
33659
|
"access": "step_up",
|
|
33573
33660
|
"step_up_purpose": "keys.create",
|
|
@@ -37383,7 +37470,7 @@ const openapiDocument = {
|
|
|
37383
37470
|
"/access-suspensions/{id}/decision": { "post": {
|
|
37384
37471
|
"operationId": "decideSuspension",
|
|
37385
37472
|
"summary": "Decide a suspension",
|
|
37386
|
-
"description": "Keep, narrow or revoke one suspended or flagged item. Keeping it, and revoking an API key
|
|
37473
|
+
"description": "Keep, narrow or revoke one suspended or flagged item. Keeping it needs the decider to cover it, and so does revoking an API key unless the decider holds it and decides as themself rather than through another app (revoking an uncovered key someone else holds is refused with `grant_forbidden`, `target_not_covered`). Keeping an API key rotated (`rotate: true`, other than the shared Function key) also revokes its older secrets still in a rotation overlap, and needs keys:manage on both the credential and the person: without it, or if the decider lost members:manage meanwhile, it is refused with `grant_forbidden` (`keys_manage_required` or `actor_access_changed`) and nothing is rotated. A rule share paused by a role change (`email_rule_share`) takes keep, which leaves it paused, or revoke, which ends the share and needs confirm: true (the person it was shared with loses the emails already shared; without it, 409 `confirmation_required`, as for revoking a filter). Neither of the share's two people may keep it (`grant_forbidden`, `share_decision_forbidden`). Access a person gave on their own mailbox (`mailbox_access`) takes keep or revoke, and both need delegations:manage; revoke removes only what that person gave on that mailbox. Nobody the access reaches, nor the person who gave it, may keep it (`grant_forbidden`, `mailbox_access_decision_forbidden`). Person only: a dashboard session, or the person's own OAuth or CLI token with a session step-up of purpose `suspensions.decide` (the person confirms at the step_up_url a 403 step_up_required answer carries, which names this client, then send the token in the Primitive-Step-Up header; the token works only with the client it was confirmed for). Needs members:manage. API keys are refused.",
|
|
37387
37474
|
"tags": ["Roles and members"],
|
|
37388
37475
|
"security": [{ "BearerAuth": [] }],
|
|
37389
37476
|
"parameters": [
|
|
@@ -39307,6 +39394,7 @@ const openapiDocument = {
|
|
|
39307
39394
|
"email_rule_author_unresolvable",
|
|
39308
39395
|
"email_rule_compiler_unavailable",
|
|
39309
39396
|
"email_rule_conflict",
|
|
39397
|
+
"email_rule_look_alikes_not_written",
|
|
39310
39398
|
"email_rule_not_compiled",
|
|
39311
39399
|
"email_rule_request_limit",
|
|
39312
39400
|
"email_rule_request_not_pending",
|
|
@@ -39320,6 +39408,8 @@ const openapiDocument = {
|
|
|
39320
39408
|
"email_rule_share_limit",
|
|
39321
39409
|
"embedding_provider_unavailable",
|
|
39322
39410
|
"entitlement",
|
|
39411
|
+
"expectations_failed",
|
|
39412
|
+
"expectations_undecided",
|
|
39323
39413
|
"expired",
|
|
39324
39414
|
"feature_not_enabled",
|
|
39325
39415
|
"fresh_send_not_authorized",
|
|
@@ -39394,6 +39484,7 @@ const openapiDocument = {
|
|
|
39394
39484
|
"network_view_denied",
|
|
39395
39485
|
"no_code",
|
|
39396
39486
|
"no_endpoint",
|
|
39487
|
+
"no_expectations",
|
|
39397
39488
|
"no_mail_read",
|
|
39398
39489
|
"not_a_template_install",
|
|
39399
39490
|
"not_available",
|
|
@@ -39445,6 +39536,7 @@ const openapiDocument = {
|
|
|
39445
39536
|
"semantic_search_not_enabled",
|
|
39446
39537
|
"send_failed",
|
|
39447
39538
|
"sender_not_authorized",
|
|
39539
|
+
"sending_ip_warmup_cap",
|
|
39448
39540
|
"sent_email_tail_unavailable",
|
|
39449
39541
|
"sent_mail_search_unavailable",
|
|
39450
39542
|
"shopping_binding_conflict",
|
|
@@ -46240,12 +46332,12 @@ const openapiDocument = {
|
|
|
46240
46332
|
},
|
|
46241
46333
|
"retry_scheduled": {
|
|
46242
46334
|
"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
|
|
46335
|
+
"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. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, 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
46336
|
},
|
|
46245
46337
|
"next_attempt_at": {
|
|
46246
46338
|
"type": ["string", "null"],
|
|
46247
46339
|
"format": "date-time",
|
|
46248
|
-
"description": "When the next automatic
|
|
46340
|
+
"description": "When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled)."
|
|
46249
46341
|
}
|
|
46250
46342
|
},
|
|
46251
46343
|
"required": [
|
|
@@ -49452,6 +49544,22 @@ const openapiDocument = {
|
|
|
49452
49544
|
"questions"
|
|
49453
49545
|
]
|
|
49454
49546
|
},
|
|
49547
|
+
"EmailRuleBreadth": {
|
|
49548
|
+
"type": "object",
|
|
49549
|
+
"properties": {
|
|
49550
|
+
"checked": {
|
|
49551
|
+
"type": "integer",
|
|
49552
|
+
"minimum": 0,
|
|
49553
|
+
"description": "Classified emails the conditions were decided for: of the newest 200 from the last 30 days in your own mailboxes (for a grant rule, those you may share), the ones you can still read."
|
|
49554
|
+
},
|
|
49555
|
+
"matched": {
|
|
49556
|
+
"type": "integer",
|
|
49557
|
+
"minimum": 0,
|
|
49558
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
49559
|
+
}
|
|
49560
|
+
},
|
|
49561
|
+
"required": ["checked", "matched"]
|
|
49562
|
+
},
|
|
49455
49563
|
"CompileEmailRuleRequest": {
|
|
49456
49564
|
"type": "object",
|
|
49457
49565
|
"properties": {
|
|
@@ -49480,7 +49588,7 @@ const openapiDocument = {
|
|
|
49480
49588
|
"tree": { "$ref": "#/components/schemas/EmailRuleTree" },
|
|
49481
49589
|
"explanation": {
|
|
49482
49590
|
"type": "string",
|
|
49483
|
-
"description": "What the rule matches, in plain English."
|
|
49591
|
+
"description": "What the rule matches, in plain English: only which emails, not what it grants or shares, which follows from its state, its scope and the keys, webhooks and people it reaches. The compiler leaves out a sentence that plainly says what the rule hands over."
|
|
49484
49592
|
},
|
|
49485
49593
|
"assumptions": {
|
|
49486
49594
|
"type": "array",
|
|
@@ -49490,12 +49598,16 @@ const openapiDocument = {
|
|
|
49490
49598
|
"warnings": {
|
|
49491
49599
|
"type": "array",
|
|
49492
49600
|
"items": { "type": "string" },
|
|
49493
|
-
"description": "Parts of the description the column contract cannot express, and how the rule handles them.
|
|
49601
|
+
"description": "Parts of the description the column contract cannot express, and how the rule handles them. It also has notes on reading automated, from_mailing_list or sender_relationship, a sender address the description does not give, and a grant worded as a prohibition. When the rule has no questions and its conditions hold for all of the caller's recent classified mail (what a test of it covers by default), or for at least 90% of at least 20 emails, the first warning says so. That count is not made for a caller whose access shows headers only, or who could not run a test of the rule, and may also be skipped when the compile ran long or the count fails."
|
|
49494
49602
|
},
|
|
49495
49603
|
"compiled_by": {
|
|
49496
49604
|
"type": "string",
|
|
49497
49605
|
"description": "An opaque name for the version of the rule compiler that produced the rule, such as rule-compiler-v1. Send it back as compiled_by when you save the rule."
|
|
49498
49606
|
},
|
|
49607
|
+
"breadth": {
|
|
49608
|
+
"description": "How many of your recent emails its conditions hold for, counted over your own mailboxes whatever scope you save it with, warning or not. Null when not counted in full: a rule with questions, headers-only access, no test of it possible as you, or a count that failed or ran out of time. Writes ignore it, so a compile answer (without include_sql) can be sent back unchanged.",
|
|
49609
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
49610
|
+
},
|
|
49499
49611
|
"sql": {
|
|
49500
49612
|
"description": "Present only when include_sql was true.",
|
|
49501
49613
|
"type": "object",
|
|
@@ -49547,6 +49659,67 @@ const openapiDocument = {
|
|
|
49547
49659
|
"message"
|
|
49548
49660
|
]
|
|
49549
49661
|
},
|
|
49662
|
+
"EmailRuleSampleJudgment": {
|
|
49663
|
+
"type": "object",
|
|
49664
|
+
"properties": {
|
|
49665
|
+
"index": {
|
|
49666
|
+
"type": "integer",
|
|
49667
|
+
"minimum": 0
|
|
49668
|
+
},
|
|
49669
|
+
"sample_id": {
|
|
49670
|
+
"type": ["string", "null"],
|
|
49671
|
+
"format": "uuid"
|
|
49672
|
+
},
|
|
49673
|
+
"label": { "type": ["string", "null"] },
|
|
49674
|
+
"expected": {
|
|
49675
|
+
"type": "string",
|
|
49676
|
+
"enum": ["match", "no_match"]
|
|
49677
|
+
},
|
|
49678
|
+
"outcome": {
|
|
49679
|
+
"type": "string",
|
|
49680
|
+
"enum": [
|
|
49681
|
+
"matched",
|
|
49682
|
+
"near_miss",
|
|
49683
|
+
"no_match",
|
|
49684
|
+
"not_decided"
|
|
49685
|
+
]
|
|
49686
|
+
},
|
|
49687
|
+
"status": {
|
|
49688
|
+
"type": "string",
|
|
49689
|
+
"enum": [
|
|
49690
|
+
"passed",
|
|
49691
|
+
"failed",
|
|
49692
|
+
"undecided"
|
|
49693
|
+
]
|
|
49694
|
+
},
|
|
49695
|
+
"question_answers": {
|
|
49696
|
+
"type": "array",
|
|
49697
|
+
"items": {
|
|
49698
|
+
"type": "object",
|
|
49699
|
+
"properties": {
|
|
49700
|
+
"question": { "type": "string" },
|
|
49701
|
+
"answer": { "type": ["boolean", "null"] },
|
|
49702
|
+
"probability": { "type": ["number", "null"] },
|
|
49703
|
+
"error_class": { "type": "string" }
|
|
49704
|
+
},
|
|
49705
|
+
"required": [
|
|
49706
|
+
"question",
|
|
49707
|
+
"answer",
|
|
49708
|
+
"probability"
|
|
49709
|
+
]
|
|
49710
|
+
}
|
|
49711
|
+
}
|
|
49712
|
+
},
|
|
49713
|
+
"required": [
|
|
49714
|
+
"index",
|
|
49715
|
+
"sample_id",
|
|
49716
|
+
"label",
|
|
49717
|
+
"expected",
|
|
49718
|
+
"outcome",
|
|
49719
|
+
"status",
|
|
49720
|
+
"question_answers"
|
|
49721
|
+
]
|
|
49722
|
+
},
|
|
49550
49723
|
"EmailRuleTreeInput": {
|
|
49551
49724
|
"type": "object",
|
|
49552
49725
|
"properties": {
|
|
@@ -49604,6 +49777,107 @@ const openapiDocument = {
|
|
|
49604
49777
|
"additionalProperties": false,
|
|
49605
49778
|
"description": "For a selected rule: inboxes, and domains (each address at them the author reads); each one you can read."
|
|
49606
49779
|
},
|
|
49780
|
+
"EmailRuleExpectationSampleInput": {
|
|
49781
|
+
"type": "object",
|
|
49782
|
+
"properties": {
|
|
49783
|
+
"expected": {
|
|
49784
|
+
"type": "string",
|
|
49785
|
+
"enum": ["match", "no_match"]
|
|
49786
|
+
},
|
|
49787
|
+
"label": {
|
|
49788
|
+
"type": ["string", "null"],
|
|
49789
|
+
"minLength": 1,
|
|
49790
|
+
"maxLength": 200,
|
|
49791
|
+
"pattern": "^[^\\u0000]*$",
|
|
49792
|
+
"description": "What the email stands for, such as a personal medical bill."
|
|
49793
|
+
},
|
|
49794
|
+
"email": { "$ref": "#/components/schemas/EmailRuleSavedSampleEmail" },
|
|
49795
|
+
"id": {
|
|
49796
|
+
"type": "string",
|
|
49797
|
+
"format": "uuid"
|
|
49798
|
+
},
|
|
49799
|
+
"created_at": { "type": "string" }
|
|
49800
|
+
},
|
|
49801
|
+
"required": ["expected", "email"],
|
|
49802
|
+
"additionalProperties": false
|
|
49803
|
+
},
|
|
49804
|
+
"EmailRuleSavedSampleEmail": {
|
|
49805
|
+
"type": "object",
|
|
49806
|
+
"properties": {
|
|
49807
|
+
"from": {
|
|
49808
|
+
"type": "object",
|
|
49809
|
+
"properties": {
|
|
49810
|
+
"address": {
|
|
49811
|
+
"type": "string",
|
|
49812
|
+
"maxLength": 254,
|
|
49813
|
+
"format": "email",
|
|
49814
|
+
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
49815
|
+
},
|
|
49816
|
+
"name": {
|
|
49817
|
+
"type": "string",
|
|
49818
|
+
"maxLength": 200,
|
|
49819
|
+
"pattern": "^[^\\u0000]*$"
|
|
49820
|
+
}
|
|
49821
|
+
},
|
|
49822
|
+
"required": ["address"]
|
|
49823
|
+
},
|
|
49824
|
+
"to": {
|
|
49825
|
+
"minItems": 1,
|
|
49826
|
+
"maxItems": 10,
|
|
49827
|
+
"type": "array",
|
|
49828
|
+
"items": {
|
|
49829
|
+
"type": "string",
|
|
49830
|
+
"maxLength": 254,
|
|
49831
|
+
"format": "email",
|
|
49832
|
+
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
49833
|
+
}
|
|
49834
|
+
},
|
|
49835
|
+
"cc": {
|
|
49836
|
+
"maxItems": 10,
|
|
49837
|
+
"type": "array",
|
|
49838
|
+
"items": {
|
|
49839
|
+
"type": "string",
|
|
49840
|
+
"maxLength": 254,
|
|
49841
|
+
"format": "email",
|
|
49842
|
+
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
49843
|
+
}
|
|
49844
|
+
},
|
|
49845
|
+
"subject": {
|
|
49846
|
+
"type": "string",
|
|
49847
|
+
"maxLength": 998,
|
|
49848
|
+
"pattern": "^[^\\u0000]*$"
|
|
49849
|
+
},
|
|
49850
|
+
"text": {
|
|
49851
|
+
"type": "string",
|
|
49852
|
+
"minLength": 1,
|
|
49853
|
+
"maxLength": 4e3,
|
|
49854
|
+
"pattern": "^[^\\u0000]*$"
|
|
49855
|
+
},
|
|
49856
|
+
"attachment_types": {
|
|
49857
|
+
"maxItems": 10,
|
|
49858
|
+
"type": "array",
|
|
49859
|
+
"items": {
|
|
49860
|
+
"type": "string",
|
|
49861
|
+
"minLength": 1,
|
|
49862
|
+
"maxLength": 255,
|
|
49863
|
+
"pattern": "^[^\\u0000]*$"
|
|
49864
|
+
}
|
|
49865
|
+
},
|
|
49866
|
+
"automated": { "type": "boolean" },
|
|
49867
|
+
"from_mailing_list": { "type": "boolean" },
|
|
49868
|
+
"spam_score": {
|
|
49869
|
+
"type": "number",
|
|
49870
|
+
"minimum": -1e3,
|
|
49871
|
+
"maximum": 1e3
|
|
49872
|
+
}
|
|
49873
|
+
},
|
|
49874
|
+
"required": [
|
|
49875
|
+
"from",
|
|
49876
|
+
"to",
|
|
49877
|
+
"text"
|
|
49878
|
+
],
|
|
49879
|
+
"description": "A made-up email, as POST /email-rules/test-sample takes it."
|
|
49880
|
+
},
|
|
49607
49881
|
"TestEmailRuleRequest": {
|
|
49608
49882
|
"type": "object",
|
|
49609
49883
|
"properties": {
|
|
@@ -49663,10 +49937,15 @@ const openapiDocument = {
|
|
|
49663
49937
|
"type": "string",
|
|
49664
49938
|
"format": "uuid"
|
|
49665
49939
|
}
|
|
49940
|
+
},
|
|
49941
|
+
"samples": {
|
|
49942
|
+
"maxItems": 20,
|
|
49943
|
+
"type": "array",
|
|
49944
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
|
|
49666
49945
|
}
|
|
49667
49946
|
},
|
|
49668
49947
|
"additionalProperties": false,
|
|
49669
|
-
"description": "Emails the rule must match and must not
|
|
49948
|
+
"description": "Emails the rule must match and must not (at most 100), always tested whatever their age, and made-up emails (samples, at most 20). Given, it replaces a saved rule's own."
|
|
49670
49949
|
}
|
|
49671
49950
|
},
|
|
49672
49951
|
"additionalProperties": false
|
|
@@ -50023,7 +50302,7 @@ const openapiDocument = {
|
|
|
50023
50302
|
"items": { "$ref": "#/components/schemas/EmailRuleScopeWarning" }
|
|
50024
50303
|
},
|
|
50025
50304
|
"expectations": {
|
|
50026
|
-
"description": "Present when expect was given, or for a saved rule with saved expectations.",
|
|
50305
|
+
"description": "Present when expect was given, or for a saved rule with saved expectations or made-up emails.",
|
|
50027
50306
|
"type": "object",
|
|
50028
50307
|
"properties": {
|
|
50029
50308
|
"passed": {
|
|
@@ -50093,6 +50372,11 @@ const openapiDocument = {
|
|
|
50093
50372
|
]
|
|
50094
50373
|
},
|
|
50095
50374
|
"description": "match first, then no_match, each in the order given (saved expectations by email id)."
|
|
50375
|
+
},
|
|
50376
|
+
"samples": {
|
|
50377
|
+
"description": "Made-up emails judged (expect.samples, or a saved rule's), in order; counts include them.",
|
|
50378
|
+
"type": "array",
|
|
50379
|
+
"items": { "$ref": "#/components/schemas/EmailRuleSampleJudgment" }
|
|
50096
50380
|
}
|
|
50097
50381
|
},
|
|
50098
50382
|
"required": [
|
|
@@ -50111,6 +50395,244 @@ const openapiDocument = {
|
|
|
50111
50395
|
"results"
|
|
50112
50396
|
]
|
|
50113
50397
|
},
|
|
50398
|
+
"EmailRuleLookAlike": {
|
|
50399
|
+
"type": "object",
|
|
50400
|
+
"properties": {
|
|
50401
|
+
"kind": {
|
|
50402
|
+
"type": "string",
|
|
50403
|
+
"enum": [
|
|
50404
|
+
"medical",
|
|
50405
|
+
"payroll",
|
|
50406
|
+
"personal_purchase",
|
|
50407
|
+
"bank_tax",
|
|
50408
|
+
"other"
|
|
50409
|
+
]
|
|
50410
|
+
},
|
|
50411
|
+
"why": {
|
|
50412
|
+
"type": "string",
|
|
50413
|
+
"description": "Why the rule should not match it."
|
|
50414
|
+
},
|
|
50415
|
+
"email": {
|
|
50416
|
+
"type": "object",
|
|
50417
|
+
"properties": {
|
|
50418
|
+
"from": {
|
|
50419
|
+
"type": "object",
|
|
50420
|
+
"properties": {
|
|
50421
|
+
"address": { "type": "string" },
|
|
50422
|
+
"name": { "type": "string" }
|
|
50423
|
+
},
|
|
50424
|
+
"required": ["address"]
|
|
50425
|
+
},
|
|
50426
|
+
"to": {
|
|
50427
|
+
"type": "array",
|
|
50428
|
+
"items": { "type": "string" }
|
|
50429
|
+
},
|
|
50430
|
+
"cc": {
|
|
50431
|
+
"type": "array",
|
|
50432
|
+
"items": { "type": "string" }
|
|
50433
|
+
},
|
|
50434
|
+
"subject": { "type": "string" },
|
|
50435
|
+
"text": { "type": "string" },
|
|
50436
|
+
"attachment_types": {
|
|
50437
|
+
"type": "array",
|
|
50438
|
+
"items": { "type": "string" }
|
|
50439
|
+
},
|
|
50440
|
+
"automated": { "type": "boolean" },
|
|
50441
|
+
"from_mailing_list": { "type": "boolean" }
|
|
50442
|
+
},
|
|
50443
|
+
"required": [
|
|
50444
|
+
"from",
|
|
50445
|
+
"to",
|
|
50446
|
+
"cc",
|
|
50447
|
+
"subject",
|
|
50448
|
+
"text",
|
|
50449
|
+
"attachment_types",
|
|
50450
|
+
"automated",
|
|
50451
|
+
"from_mailing_list"
|
|
50452
|
+
],
|
|
50453
|
+
"description": "A made-up email, as test-sample takes it."
|
|
50454
|
+
},
|
|
50455
|
+
"outcome": {
|
|
50456
|
+
"type": "string",
|
|
50457
|
+
"enum": [
|
|
50458
|
+
"matched",
|
|
50459
|
+
"near_miss",
|
|
50460
|
+
"no_match",
|
|
50461
|
+
"not_decided"
|
|
50462
|
+
]
|
|
50463
|
+
},
|
|
50464
|
+
"status": {
|
|
50465
|
+
"type": "string",
|
|
50466
|
+
"enum": [
|
|
50467
|
+
"passed",
|
|
50468
|
+
"failed",
|
|
50469
|
+
"undecided"
|
|
50470
|
+
],
|
|
50471
|
+
"description": "failed: the rule matched it, so it would be shared. undecided: the rule reads a spam score, which it cannot carry, whatever it decided; or not matched on something it could not decide (not classified, a fact or answer missing, or not reached in time)."
|
|
50472
|
+
},
|
|
50473
|
+
"question_answers": {
|
|
50474
|
+
"type": "array",
|
|
50475
|
+
"items": {
|
|
50476
|
+
"type": "object",
|
|
50477
|
+
"properties": {
|
|
50478
|
+
"question": { "type": "string" },
|
|
50479
|
+
"answer": { "type": ["boolean", "null"] },
|
|
50480
|
+
"probability": { "type": ["number", "null"] },
|
|
50481
|
+
"error_class": { "type": "string" }
|
|
50482
|
+
},
|
|
50483
|
+
"required": [
|
|
50484
|
+
"question",
|
|
50485
|
+
"answer",
|
|
50486
|
+
"probability"
|
|
50487
|
+
]
|
|
50488
|
+
}
|
|
50489
|
+
}
|
|
50490
|
+
},
|
|
50491
|
+
"required": [
|
|
50492
|
+
"kind",
|
|
50493
|
+
"why",
|
|
50494
|
+
"email",
|
|
50495
|
+
"outcome",
|
|
50496
|
+
"status",
|
|
50497
|
+
"question_answers"
|
|
50498
|
+
]
|
|
50499
|
+
},
|
|
50500
|
+
"GenerateLookAlikesRequest": {
|
|
50501
|
+
"type": "object",
|
|
50502
|
+
"properties": {
|
|
50503
|
+
"tree": {
|
|
50504
|
+
"description": "An unsaved rule tree, as POST /email-rules/compile returned it or written by hand. Give tree or rule_id, not both.",
|
|
50505
|
+
"$ref": "#/components/schemas/EmailRuleTreeInput"
|
|
50506
|
+
},
|
|
50507
|
+
"english": {
|
|
50508
|
+
"description": "What the tree is meant to match; without it, the tree is described in words.",
|
|
50509
|
+
"type": "string",
|
|
50510
|
+
"minLength": 1,
|
|
50511
|
+
"maxLength": 2e3
|
|
50512
|
+
},
|
|
50513
|
+
"evaluation_scope": {
|
|
50514
|
+
"type": "string",
|
|
50515
|
+
"enum": [
|
|
50516
|
+
"author",
|
|
50517
|
+
"own_mailboxes",
|
|
50518
|
+
"selected"
|
|
50519
|
+
],
|
|
50520
|
+
"description": "own_mailboxes (the default): mail to your own addresses and agents (for a rule that shares, those mail:share_own lets you share). author: all you can read, inboxes added later included. selected: what scope_selection names."
|
|
50521
|
+
},
|
|
50522
|
+
"scope_selection": { "$ref": "#/components/schemas/EmailRuleScopeSelection" },
|
|
50523
|
+
"rule_id": {
|
|
50524
|
+
"description": "A saved rule you can see: its current version and words.",
|
|
50525
|
+
"type": "string",
|
|
50526
|
+
"format": "uuid"
|
|
50527
|
+
},
|
|
50528
|
+
"request_id": {
|
|
50529
|
+
"description": "A rule request you can see: its rule as asked.",
|
|
50530
|
+
"type": "string",
|
|
50531
|
+
"format": "uuid"
|
|
50532
|
+
},
|
|
50533
|
+
"kind": {
|
|
50534
|
+
"description": "For an unsaved tree: do_not_share tests it as a rule that blocks sharing, so an email it cannot decide counts as matched. A saved rule uses its own.",
|
|
50535
|
+
"type": "string",
|
|
50536
|
+
"enum": ["grant", "do_not_share"]
|
|
50537
|
+
},
|
|
50538
|
+
"count": {
|
|
50539
|
+
"description": "How many, 1 to 8; 6 by default.",
|
|
50540
|
+
"type": "integer",
|
|
50541
|
+
"minimum": 1,
|
|
50542
|
+
"maximum": 8
|
|
50543
|
+
},
|
|
50544
|
+
"kinds": {
|
|
50545
|
+
"description": "Which kinds, taken in turn; all by default, the private ones first.",
|
|
50546
|
+
"minItems": 1,
|
|
50547
|
+
"maxItems": 5,
|
|
50548
|
+
"type": "array",
|
|
50549
|
+
"items": {
|
|
50550
|
+
"type": "string",
|
|
50551
|
+
"enum": [
|
|
50552
|
+
"medical",
|
|
50553
|
+
"payroll",
|
|
50554
|
+
"personal_purchase",
|
|
50555
|
+
"bank_tax",
|
|
50556
|
+
"other"
|
|
50557
|
+
]
|
|
50558
|
+
}
|
|
50559
|
+
},
|
|
50560
|
+
"to": {
|
|
50561
|
+
"description": "The mailbox they arrive in; by default the rule author's (a request's target's) sign-in address when you may see it, else yours.",
|
|
50562
|
+
"type": "string",
|
|
50563
|
+
"maxLength": 254,
|
|
50564
|
+
"format": "email",
|
|
50565
|
+
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
50566
|
+
}
|
|
50567
|
+
},
|
|
50568
|
+
"additionalProperties": false
|
|
50569
|
+
},
|
|
50570
|
+
"GenerateLookAlikesResult": {
|
|
50571
|
+
"type": "object",
|
|
50572
|
+
"properties": {
|
|
50573
|
+
"rule": {
|
|
50574
|
+
"type": "object",
|
|
50575
|
+
"properties": {
|
|
50576
|
+
"id": {
|
|
50577
|
+
"type": ["string", "null"],
|
|
50578
|
+
"format": "uuid"
|
|
50579
|
+
},
|
|
50580
|
+
"version": {
|
|
50581
|
+
"type": ["integer", "null"],
|
|
50582
|
+
"exclusiveMinimum": 0
|
|
50583
|
+
},
|
|
50584
|
+
"vocabulary": { "type": "string" }
|
|
50585
|
+
},
|
|
50586
|
+
"required": [
|
|
50587
|
+
"id",
|
|
50588
|
+
"version",
|
|
50589
|
+
"vocabulary"
|
|
50590
|
+
]
|
|
50591
|
+
},
|
|
50592
|
+
"look_alikes": {
|
|
50593
|
+
"type": "array",
|
|
50594
|
+
"items": { "$ref": "#/components/schemas/EmailRuleLookAlike" }
|
|
50595
|
+
},
|
|
50596
|
+
"counts": {
|
|
50597
|
+
"type": "object",
|
|
50598
|
+
"properties": {
|
|
50599
|
+
"passed": {
|
|
50600
|
+
"type": "integer",
|
|
50601
|
+
"minimum": 0
|
|
50602
|
+
},
|
|
50603
|
+
"failed": {
|
|
50604
|
+
"type": "integer",
|
|
50605
|
+
"minimum": 0
|
|
50606
|
+
},
|
|
50607
|
+
"undecided": {
|
|
50608
|
+
"type": "integer",
|
|
50609
|
+
"minimum": 0
|
|
50610
|
+
},
|
|
50611
|
+
"dropped": {
|
|
50612
|
+
"type": "integer",
|
|
50613
|
+
"minimum": 0,
|
|
50614
|
+
"description": "Asked for but not returned: not written, outside the sample bounds, or of a kind not asked for."
|
|
50615
|
+
}
|
|
50616
|
+
},
|
|
50617
|
+
"required": [
|
|
50618
|
+
"passed",
|
|
50619
|
+
"failed",
|
|
50620
|
+
"undecided",
|
|
50621
|
+
"dropped"
|
|
50622
|
+
]
|
|
50623
|
+
},
|
|
50624
|
+
"note": {
|
|
50625
|
+
"type": "string",
|
|
50626
|
+
"description": "That the emails are made up and were never sent or stored."
|
|
50627
|
+
}
|
|
50628
|
+
},
|
|
50629
|
+
"required": [
|
|
50630
|
+
"rule",
|
|
50631
|
+
"look_alikes",
|
|
50632
|
+
"counts",
|
|
50633
|
+
"note"
|
|
50634
|
+
]
|
|
50635
|
+
},
|
|
50114
50636
|
"TestEmailRuleOnSampleRequest": {
|
|
50115
50637
|
"type": "object",
|
|
50116
50638
|
"properties": {
|
|
@@ -50402,6 +50924,10 @@ const openapiDocument = {
|
|
|
50402
50924
|
"type": "string",
|
|
50403
50925
|
"enum": ["grant", "do_not_share"]
|
|
50404
50926
|
},
|
|
50927
|
+
"protection_note": {
|
|
50928
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
50929
|
+
"type": "string"
|
|
50930
|
+
},
|
|
50405
50931
|
"evaluation_scope": {
|
|
50406
50932
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
50407
50933
|
"type": "string",
|
|
@@ -50419,10 +50945,19 @@ const openapiDocument = {
|
|
|
50419
50945
|
"type": ["integer", "null"],
|
|
50420
50946
|
"exclusiveMinimum": 0
|
|
50421
50947
|
},
|
|
50948
|
+
"revision": {
|
|
50949
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
50950
|
+
"type": "integer",
|
|
50951
|
+
"exclusiveMinimum": 0
|
|
50952
|
+
},
|
|
50422
50953
|
"author_user_id": {
|
|
50423
50954
|
"type": "string",
|
|
50424
50955
|
"format": "uuid"
|
|
50425
50956
|
},
|
|
50957
|
+
"author": {
|
|
50958
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
50959
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
50960
|
+
},
|
|
50426
50961
|
"is_author": {
|
|
50427
50962
|
"type": "boolean",
|
|
50428
50963
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -50435,14 +50970,22 @@ const openapiDocument = {
|
|
|
50435
50970
|
"type": "string",
|
|
50436
50971
|
"format": "date-time"
|
|
50437
50972
|
},
|
|
50438
|
-
"current": {
|
|
50973
|
+
"current": {
|
|
50974
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
50975
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
50976
|
+
},
|
|
50439
50977
|
"scope_warnings": {
|
|
50440
50978
|
"description": "Names in scope_selection that match nothing this organization receives mail at (a typo, or an inbox not yet in use). Present only when there are some.",
|
|
50441
50979
|
"type": "array",
|
|
50442
50980
|
"items": { "$ref": "#/components/schemas/EmailRuleScopeWarning" }
|
|
50443
50981
|
},
|
|
50982
|
+
"expectation_samples": {
|
|
50983
|
+
"description": "Its saved made-up emails, when this write set or added some.",
|
|
50984
|
+
"type": "array",
|
|
50985
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSample" }
|
|
50986
|
+
},
|
|
50444
50987
|
"expectations_check": {
|
|
50445
|
-
"description": "After a write that saved a version or changed its expectations, if it has some:
|
|
50988
|
+
"description": "After a write that saved a version or changed its expectations or made-up emails, if it has some: the emails by id judged on that version from stored classifications without asking its questions, and any made-up emails listed as undecided. Undecided: one that needs answers not yet kept for that version, is unclassified, skipped or not reached, or that its author does not read. Absent when it could not be judged (the rule changed again, its author cannot be resolved, your access shows headers only, it could not finish in time, or the check failed), and after a write that changed only made-up emails on a rule with emails by id. Test by rule_id for the rest. With if_expectations_pass, the judgment made before the save, which passed.",
|
|
50446
50989
|
"type": "object",
|
|
50447
50990
|
"properties": {
|
|
50448
50991
|
"version": {
|
|
@@ -50454,7 +50997,7 @@ const openapiDocument = {
|
|
|
50454
50997
|
"type": ["boolean", "null"]
|
|
50455
50998
|
},
|
|
50456
50999
|
"decided": {
|
|
50457
|
-
"description": "Whether every expectation was judged (undecided
|
|
51000
|
+
"description": "Whether every expectation was judged (undecided and undecided_samples are empty). Absent from a server that predates it.",
|
|
50458
51001
|
"type": "boolean"
|
|
50459
51002
|
},
|
|
50460
51003
|
"failed": {
|
|
@@ -50470,6 +51013,26 @@ const openapiDocument = {
|
|
|
50470
51013
|
"type": "string",
|
|
50471
51014
|
"format": "uuid"
|
|
50472
51015
|
}
|
|
51016
|
+
},
|
|
51017
|
+
"failed_samples": {
|
|
51018
|
+
"description": "Made-up emails that failed, by index in the set judged (saved oldest first, then added; or as sent).",
|
|
51019
|
+
"type": "array",
|
|
51020
|
+
"items": {
|
|
51021
|
+
"type": "integer",
|
|
51022
|
+
"minimum": 0
|
|
51023
|
+
}
|
|
51024
|
+
},
|
|
51025
|
+
"undecided_samples": {
|
|
51026
|
+
"description": "Made-up emails not decided, by index. Only a write sent with if_expectations_pass that saves a version or activates the rule judges them.",
|
|
51027
|
+
"type": "array",
|
|
51028
|
+
"items": {
|
|
51029
|
+
"type": "integer",
|
|
51030
|
+
"minimum": 0
|
|
51031
|
+
}
|
|
51032
|
+
},
|
|
51033
|
+
"note": {
|
|
51034
|
+
"description": "On an update that saved a version that did not pass, or a do_not_share rule created active: that it was saved anyway, is current, and whether it is live.",
|
|
51035
|
+
"type": "string"
|
|
50473
51036
|
}
|
|
50474
51037
|
},
|
|
50475
51038
|
"required": [
|
|
@@ -50478,6 +51041,41 @@ const openapiDocument = {
|
|
|
50478
51041
|
"failed",
|
|
50479
51042
|
"undecided"
|
|
50480
51043
|
]
|
|
51044
|
+
},
|
|
51045
|
+
"restored_from": {
|
|
51046
|
+
"description": "When this write saved an earlier version again (restore_version): which, and that the scope is not part of a version.",
|
|
51047
|
+
"type": "object",
|
|
51048
|
+
"properties": {
|
|
51049
|
+
"version": {
|
|
51050
|
+
"type": "integer",
|
|
51051
|
+
"exclusiveMinimum": 0
|
|
51052
|
+
},
|
|
51053
|
+
"note": { "type": "string" }
|
|
51054
|
+
},
|
|
51055
|
+
"required": ["version", "note"]
|
|
51056
|
+
},
|
|
51057
|
+
"affects": {
|
|
51058
|
+
"description": "After an update that saved a version: API keys listing the rule, the webhooks they own, and shares this write ended. The rule keeps its state. Absent if not read.",
|
|
51059
|
+
"type": "object",
|
|
51060
|
+
"properties": {
|
|
51061
|
+
"keys": {
|
|
51062
|
+
"type": "integer",
|
|
51063
|
+
"minimum": 0
|
|
51064
|
+
},
|
|
51065
|
+
"endpoints": {
|
|
51066
|
+
"type": "integer",
|
|
51067
|
+
"minimum": 0
|
|
51068
|
+
},
|
|
51069
|
+
"shares_ended": {
|
|
51070
|
+
"type": "integer",
|
|
51071
|
+
"minimum": 0
|
|
51072
|
+
}
|
|
51073
|
+
},
|
|
51074
|
+
"required": [
|
|
51075
|
+
"keys",
|
|
51076
|
+
"endpoints",
|
|
51077
|
+
"shares_ended"
|
|
51078
|
+
]
|
|
50481
51079
|
}
|
|
50482
51080
|
},
|
|
50483
51081
|
"required": [
|
|
@@ -50506,6 +51104,28 @@ const openapiDocument = {
|
|
|
50506
51104
|
},
|
|
50507
51105
|
"required": ["addresses", "domains"]
|
|
50508
51106
|
},
|
|
51107
|
+
"EmailRuleAuthor": {
|
|
51108
|
+
"type": "object",
|
|
51109
|
+
"properties": {
|
|
51110
|
+
"user_id": {
|
|
51111
|
+
"type": "string",
|
|
51112
|
+
"format": "uuid"
|
|
51113
|
+
},
|
|
51114
|
+
"name": {
|
|
51115
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
51116
|
+
"type": ["string", "null"]
|
|
51117
|
+
},
|
|
51118
|
+
"email": {
|
|
51119
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
51120
|
+
"type": ["string", "null"]
|
|
51121
|
+
}
|
|
51122
|
+
},
|
|
51123
|
+
"required": [
|
|
51124
|
+
"user_id",
|
|
51125
|
+
"name",
|
|
51126
|
+
"email"
|
|
51127
|
+
]
|
|
51128
|
+
},
|
|
50509
51129
|
"EmailRuleVersion": {
|
|
50510
51130
|
"type": "object",
|
|
50511
51131
|
"properties": {
|
|
@@ -50562,6 +51182,32 @@ const openapiDocument = {
|
|
|
50562
51182
|
"created_at"
|
|
50563
51183
|
]
|
|
50564
51184
|
},
|
|
51185
|
+
"EmailRuleExpectationSample": {
|
|
51186
|
+
"type": "object",
|
|
51187
|
+
"properties": {
|
|
51188
|
+
"id": {
|
|
51189
|
+
"type": "string",
|
|
51190
|
+
"format": "uuid"
|
|
51191
|
+
},
|
|
51192
|
+
"expected": {
|
|
51193
|
+
"type": "string",
|
|
51194
|
+
"enum": ["match", "no_match"]
|
|
51195
|
+
},
|
|
51196
|
+
"label": { "type": ["string", "null"] },
|
|
51197
|
+
"email": { "$ref": "#/components/schemas/EmailRuleSavedSampleEmail" },
|
|
51198
|
+
"created_at": {
|
|
51199
|
+
"type": "string",
|
|
51200
|
+
"format": "date-time"
|
|
51201
|
+
}
|
|
51202
|
+
},
|
|
51203
|
+
"required": [
|
|
51204
|
+
"id",
|
|
51205
|
+
"expected",
|
|
51206
|
+
"label",
|
|
51207
|
+
"email",
|
|
51208
|
+
"created_at"
|
|
51209
|
+
]
|
|
51210
|
+
},
|
|
50565
51211
|
"CreateEmailRuleRequest": {
|
|
50566
51212
|
"type": "object",
|
|
50567
51213
|
"properties": {
|
|
@@ -50577,9 +51223,13 @@ const openapiDocument = {
|
|
|
50577
51223
|
"enum": ["grant", "do_not_share"]
|
|
50578
51224
|
},
|
|
50579
51225
|
"state": {
|
|
50580
|
-
"description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated.",
|
|
51226
|
+
"description": "observe (the default) records what the rule would match and grants or blocks nothing; draft is not evaluated; active only for a do_not_share rule.",
|
|
50581
51227
|
"type": "string",
|
|
50582
|
-
"enum": [
|
|
51228
|
+
"enum": [
|
|
51229
|
+
"draft",
|
|
51230
|
+
"observe",
|
|
51231
|
+
"active"
|
|
51232
|
+
]
|
|
50583
51233
|
},
|
|
50584
51234
|
"evaluation_scope": {
|
|
50585
51235
|
"type": "string",
|
|
@@ -50614,6 +51264,13 @@ const openapiDocument = {
|
|
|
50614
51264
|
"additionalProperties": false,
|
|
50615
51265
|
"description": "Emails it must match and must not (at most 100): a test by rule_id judges them unless expect is given, and each version it saves is checked against them."
|
|
50616
51266
|
},
|
|
51267
|
+
"expectation_samples": {
|
|
51268
|
+
"description": "Made-up emails it must match or not (at most 20).",
|
|
51269
|
+
"minItems": 1,
|
|
51270
|
+
"maxItems": 20,
|
|
51271
|
+
"type": "array",
|
|
51272
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
|
|
51273
|
+
},
|
|
50617
51274
|
"english": {
|
|
50618
51275
|
"type": "string",
|
|
50619
51276
|
"minLength": 1,
|
|
@@ -50655,6 +51312,10 @@ const openapiDocument = {
|
|
|
50655
51312
|
"type": "string",
|
|
50656
51313
|
"minLength": 1,
|
|
50657
51314
|
"maxLength": 64
|
|
51315
|
+
},
|
|
51316
|
+
"breadth": {
|
|
51317
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
51318
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
50658
51319
|
}
|
|
50659
51320
|
},
|
|
50660
51321
|
"required": [
|
|
@@ -50686,6 +51347,10 @@ const openapiDocument = {
|
|
|
50686
51347
|
"type": "string",
|
|
50687
51348
|
"enum": ["grant", "do_not_share"]
|
|
50688
51349
|
},
|
|
51350
|
+
"protection_note": {
|
|
51351
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
51352
|
+
"type": "string"
|
|
51353
|
+
},
|
|
50689
51354
|
"evaluation_scope": {
|
|
50690
51355
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
50691
51356
|
"type": "string",
|
|
@@ -50703,10 +51368,19 @@ const openapiDocument = {
|
|
|
50703
51368
|
"type": ["integer", "null"],
|
|
50704
51369
|
"exclusiveMinimum": 0
|
|
50705
51370
|
},
|
|
51371
|
+
"revision": {
|
|
51372
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
51373
|
+
"type": "integer",
|
|
51374
|
+
"exclusiveMinimum": 0
|
|
51375
|
+
},
|
|
50706
51376
|
"author_user_id": {
|
|
50707
51377
|
"type": "string",
|
|
50708
51378
|
"format": "uuid"
|
|
50709
51379
|
},
|
|
51380
|
+
"author": {
|
|
51381
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
51382
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
51383
|
+
},
|
|
50710
51384
|
"is_author": {
|
|
50711
51385
|
"type": "boolean",
|
|
50712
51386
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -50771,6 +51445,10 @@ const openapiDocument = {
|
|
|
50771
51445
|
"type": "string",
|
|
50772
51446
|
"enum": ["grant", "do_not_share"]
|
|
50773
51447
|
},
|
|
51448
|
+
"protection_note": {
|
|
51449
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
51450
|
+
"type": "string"
|
|
51451
|
+
},
|
|
50774
51452
|
"evaluation_scope": {
|
|
50775
51453
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
50776
51454
|
"type": "string",
|
|
@@ -50788,10 +51466,19 @@ const openapiDocument = {
|
|
|
50788
51466
|
"type": ["integer", "null"],
|
|
50789
51467
|
"exclusiveMinimum": 0
|
|
50790
51468
|
},
|
|
51469
|
+
"revision": {
|
|
51470
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
51471
|
+
"type": "integer",
|
|
51472
|
+
"exclusiveMinimum": 0
|
|
51473
|
+
},
|
|
50791
51474
|
"author_user_id": {
|
|
50792
51475
|
"type": "string",
|
|
50793
51476
|
"format": "uuid"
|
|
50794
51477
|
},
|
|
51478
|
+
"author": {
|
|
51479
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
51480
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
51481
|
+
},
|
|
50795
51482
|
"is_author": {
|
|
50796
51483
|
"type": "boolean",
|
|
50797
51484
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -50804,7 +51491,10 @@ const openapiDocument = {
|
|
|
50804
51491
|
"type": "string",
|
|
50805
51492
|
"format": "date-time"
|
|
50806
51493
|
},
|
|
50807
|
-
"current": {
|
|
51494
|
+
"current": {
|
|
51495
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
51496
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
51497
|
+
},
|
|
50808
51498
|
"expectations": {
|
|
50809
51499
|
"description": "Its saved expectations, each list by email id.",
|
|
50810
51500
|
"type": "object",
|
|
@@ -50825,6 +51515,11 @@ const openapiDocument = {
|
|
|
50825
51515
|
}
|
|
50826
51516
|
},
|
|
50827
51517
|
"required": ["match", "no_match"]
|
|
51518
|
+
},
|
|
51519
|
+
"expectation_samples": {
|
|
51520
|
+
"description": "Its saved made-up emails, oldest first.",
|
|
51521
|
+
"type": "array",
|
|
51522
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSample" }
|
|
50828
51523
|
}
|
|
50829
51524
|
},
|
|
50830
51525
|
"required": [
|
|
@@ -50900,6 +51595,10 @@ const openapiDocument = {
|
|
|
50900
51595
|
"minLength": 1,
|
|
50901
51596
|
"maxLength": 64
|
|
50902
51597
|
},
|
|
51598
|
+
"breadth": {
|
|
51599
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
51600
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
51601
|
+
},
|
|
50903
51602
|
"evaluation_scope": {
|
|
50904
51603
|
"type": "string",
|
|
50905
51604
|
"enum": [
|
|
@@ -50911,7 +51610,7 @@ const openapiDocument = {
|
|
|
50911
51610
|
},
|
|
50912
51611
|
"scope_selection": { "$ref": "#/components/schemas/EmailRuleScopeSelection" },
|
|
50913
51612
|
"if_version": {
|
|
50914
|
-
"description": "The current_version you read
|
|
51613
|
+
"description": "The current_version you read: if it has moved, nothing is saved (409 email_rule_conflict, version_moved). It guards versions only; if_revision guards every update.",
|
|
50915
51614
|
"type": "integer",
|
|
50916
51615
|
"minimum": 1
|
|
50917
51616
|
},
|
|
@@ -50937,6 +51636,35 @@ const openapiDocument = {
|
|
|
50937
51636
|
},
|
|
50938
51637
|
"additionalProperties": false,
|
|
50939
51638
|
"description": "Replaces its saved expectations (author only); null removes them."
|
|
51639
|
+
},
|
|
51640
|
+
"expectation_samples": {
|
|
51641
|
+
"maxItems": 20,
|
|
51642
|
+
"type": ["array", "null"],
|
|
51643
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" },
|
|
51644
|
+
"description": "Replaces its saved made-up emails (author only); null or [] removes them."
|
|
51645
|
+
},
|
|
51646
|
+
"add_expectation_samples": {
|
|
51647
|
+
"description": "Adds made-up emails to those saved (author only), up to 20 in all.",
|
|
51648
|
+
"minItems": 1,
|
|
51649
|
+
"maxItems": 20,
|
|
51650
|
+
"type": "array",
|
|
51651
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
|
|
51652
|
+
},
|
|
51653
|
+
"if_revision": {
|
|
51654
|
+
"description": "The revision you read: if the rule was updated since, nothing is saved (409 email_rule_conflict, revision_moved).",
|
|
51655
|
+
"type": "integer",
|
|
51656
|
+
"minimum": 1,
|
|
51657
|
+
"maximum": 2147483647
|
|
51658
|
+
},
|
|
51659
|
+
"if_expectations_pass": {
|
|
51660
|
+
"description": "true: judge the rule as this write leaves it (new tree, restored version, new scope, or the version it activates) against the expectations it leaves, as POST /email-rules/test would, before saving; it can take as long as a test. Failed: 409 expectations_failed; undecided: 409 expectations_undecided (both with details.expectations_check); none to judge: 400 no_expectations. Refused writes save nothing; a pass saves unless the rule moved meanwhile (409 email_rule_conflict). Only writes that save a version or activate the rule are judged.",
|
|
51661
|
+
"type": "boolean"
|
|
51662
|
+
},
|
|
51663
|
+
"restore_version": {
|
|
51664
|
+
"description": "Save version n again as the next version (author only): its english, tree, explanation, assumptions, warnings and compiled_by. A version carries no scope: the rule keeps its own unless evaluation_scope is sent. 404 version_not_found; 400 already_current or version_not_renderable. Not with a version's own fields.",
|
|
51665
|
+
"type": "integer",
|
|
51666
|
+
"minimum": 1,
|
|
51667
|
+
"maximum": 2147483647
|
|
50940
51668
|
}
|
|
50941
51669
|
},
|
|
50942
51670
|
"additionalProperties": false
|
|
@@ -51543,6 +52271,10 @@ const openapiDocument = {
|
|
|
51543
52271
|
"type": "string",
|
|
51544
52272
|
"minLength": 1,
|
|
51545
52273
|
"maxLength": 64
|
|
52274
|
+
},
|
|
52275
|
+
"breadth": {
|
|
52276
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
52277
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
51546
52278
|
}
|
|
51547
52279
|
},
|
|
51548
52280
|
"required": [
|
|
@@ -51621,6 +52353,14 @@ const openapiDocument = {
|
|
|
51621
52353
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
51622
52354
|
"type": ["string", "null"]
|
|
51623
52355
|
},
|
|
52356
|
+
"edited_on_accept": {
|
|
52357
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
52358
|
+
"type": ["boolean", "null"]
|
|
52359
|
+
},
|
|
52360
|
+
"notice": {
|
|
52361
|
+
"description": "For the member asked, while pending: what accepting would share. For the member who asked, once accepted with changes: that the request is not what is shared. Else null.",
|
|
52362
|
+
"type": ["string", "null"]
|
|
52363
|
+
},
|
|
51624
52364
|
"target_can_accept": {
|
|
51625
52365
|
"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.",
|
|
51626
52366
|
"type": ["boolean", "null"]
|
|
@@ -51778,6 +52518,14 @@ const openapiDocument = {
|
|
|
51778
52518
|
"decline_reason": {
|
|
51779
52519
|
"description": "Why the member asked declined it; null unless they gave a reason when declining.",
|
|
51780
52520
|
"type": ["string", "null"]
|
|
52521
|
+
},
|
|
52522
|
+
"edited_on_accept": {
|
|
52523
|
+
"description": "Once accepted: whether the rule was changed first, as on the share. Null until then, or when unreadable.",
|
|
52524
|
+
"type": ["boolean", "null"]
|
|
52525
|
+
},
|
|
52526
|
+
"notice": {
|
|
52527
|
+
"description": "For the member asked, while pending: what accepting would share. For the member who asked, once accepted with changes: that the request is not what is shared. Else null.",
|
|
52528
|
+
"type": ["string", "null"]
|
|
51781
52529
|
}
|
|
51782
52530
|
},
|
|
51783
52531
|
"required": [
|
|
@@ -51846,6 +52594,10 @@ const openapiDocument = {
|
|
|
51846
52594
|
"type": "string",
|
|
51847
52595
|
"enum": ["grant", "do_not_share"]
|
|
51848
52596
|
},
|
|
52597
|
+
"protection_note": {
|
|
52598
|
+
"description": "On a do_not_share rule only: it protects nothing yet (not active), or a classifier fact narrows it.",
|
|
52599
|
+
"type": "string"
|
|
52600
|
+
},
|
|
51849
52601
|
"evaluation_scope": {
|
|
51850
52602
|
"description": "Whose mail it runs over, as stored (GET /email-rules/{id}/scope: what that is now).",
|
|
51851
52603
|
"type": "string",
|
|
@@ -51863,10 +52615,19 @@ const openapiDocument = {
|
|
|
51863
52615
|
"type": ["integer", "null"],
|
|
51864
52616
|
"exclusiveMinimum": 0
|
|
51865
52617
|
},
|
|
52618
|
+
"revision": {
|
|
52619
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
52620
|
+
"type": "integer",
|
|
52621
|
+
"exclusiveMinimum": 0
|
|
52622
|
+
},
|
|
51866
52623
|
"author_user_id": {
|
|
51867
52624
|
"type": "string",
|
|
51868
52625
|
"format": "uuid"
|
|
51869
52626
|
},
|
|
52627
|
+
"author": {
|
|
52628
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
52629
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
52630
|
+
},
|
|
51870
52631
|
"is_author": {
|
|
51871
52632
|
"type": "boolean",
|
|
51872
52633
|
"description": "Whether you are its author, in your current membership. Only its author edits it; others who see it may only disable or delete it (while shared, only if they manage members)."
|
|
@@ -51879,7 +52640,10 @@ const openapiDocument = {
|
|
|
51879
52640
|
"type": "string",
|
|
51880
52641
|
"format": "date-time"
|
|
51881
52642
|
},
|
|
51882
|
-
"current": {
|
|
52643
|
+
"current": {
|
|
52644
|
+
"description": "Its current version: its text (english, tree, explanation, assumptions, warnings) and who compiled it. current_version is its number. GET /email-rules lists rules without it.",
|
|
52645
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
52646
|
+
}
|
|
51883
52647
|
},
|
|
51884
52648
|
"required": [
|
|
51885
52649
|
"id",
|
|
@@ -52102,6 +52866,17 @@ const openapiDocument = {
|
|
|
52102
52866
|
"type": "string",
|
|
52103
52867
|
"minLength": 1,
|
|
52104
52868
|
"maxLength": 64
|
|
52869
|
+
},
|
|
52870
|
+
"breadth": {
|
|
52871
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
52872
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
52873
|
+
},
|
|
52874
|
+
"expectation_samples": {
|
|
52875
|
+
"description": "Made-up emails the rule must match or not, such as look-alikes it must never match, kept on the rule this accept writes.",
|
|
52876
|
+
"minItems": 1,
|
|
52877
|
+
"maxItems": 20,
|
|
52878
|
+
"type": "array",
|
|
52879
|
+
"items": { "$ref": "#/components/schemas/EmailRuleExpectationSampleInput" }
|
|
52105
52880
|
}
|
|
52106
52881
|
},
|
|
52107
52882
|
"additionalProperties": false
|
|
@@ -52111,7 +52886,11 @@ const openapiDocument = {
|
|
|
52111
52886
|
"properties": {
|
|
52112
52887
|
"request": { "$ref": "#/components/schemas/EmailRuleRequest" },
|
|
52113
52888
|
"rule": { "$ref": "#/components/schemas/EmailRuleWithVersion" },
|
|
52114
|
-
"share": { "$ref": "#/components/schemas/EmailRuleShare" }
|
|
52889
|
+
"share": { "$ref": "#/components/schemas/EmailRuleShare" },
|
|
52890
|
+
"notice": {
|
|
52891
|
+
"description": "What is now shared, in words.",
|
|
52892
|
+
"type": "string"
|
|
52893
|
+
}
|
|
52115
52894
|
},
|
|
52116
52895
|
"required": [
|
|
52117
52896
|
"request",
|
|
@@ -54350,11 +55129,11 @@ const openapiDocument = {
|
|
|
54350
55129
|
"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."
|
|
54351
55130
|
},
|
|
54352
55131
|
"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
|
|
55132
|
+
"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. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, 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
55133
|
"type": ["boolean", "null"]
|
|
54355
55134
|
},
|
|
54356
55135
|
"next_attempt_at": {
|
|
54357
|
-
"description": "When the next automatic
|
|
55136
|
+
"description": "When the next automatic attempt of a rule-granted delivery is due: set on its latest attempt while retry_scheduled is true. The attempt runs at the first retry pass at or after this time (one runs every few minutes), so a time already past means it is due now. Null otherwise, and on every delivery that is not rule-granted, whose retries are passes for the email with no time reported (whether one is coming is retry_scheduled).",
|
|
54358
55137
|
"type": ["string", "null"]
|
|
54359
55138
|
},
|
|
54360
55139
|
"attempts": {
|
|
@@ -54384,7 +55163,7 @@ const openapiDocument = {
|
|
|
54384
55163
|
"type": ["string", "null"]
|
|
54385
55164
|
},
|
|
54386
55165
|
"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
|
|
55166
|
+
"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. A rule-granted delivery is retried on a schedule of its own, to the same endpoint, deciding it afresh: on its latest attempt true while another attempt is to come (the delivery's next_attempt_at says when); null while an attempt is under way (read it again shortly), while it waits on an owner who cannot receive mail or on a held endpoint (checked again hourly, and sent once that clears), when it is past its 7-day limit, or when that could not be read just now; and false otherwise, including once the endpoint is off or already has the email. Null on every attempt of a delivery retried or settled on a schedule of its own that this does not report: a held one (its release settles it, including the attempts it makes itself), a pull attempt, 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
55167
|
"type": ["boolean", "null"]
|
|
54389
55168
|
}
|
|
54390
55169
|
},
|
|
@@ -56284,7 +57063,7 @@ const openapiDocument = {
|
|
|
56284
57063
|
}
|
|
56285
57064
|
},
|
|
56286
57065
|
"already_revoked": {
|
|
56287
|
-
"description": "true when the API key was already revoked
|
|
57066
|
+
"description": "true when the API key was already revoked. revoked then counts only the keys it replaced that were revoked now. Absent otherwise. GET /v1/keys/{id} says when and by whom, to a caller who may read the key.",
|
|
56288
57067
|
"type": "boolean"
|
|
56289
57068
|
},
|
|
56290
57069
|
"keep_failed": {
|
|
@@ -56317,6 +57096,18 @@ const openapiDocument = {
|
|
|
56317
57096
|
"org_owned": {
|
|
56318
57097
|
"type": "boolean",
|
|
56319
57098
|
"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."
|
|
57099
|
+
},
|
|
57100
|
+
"still_acting": {
|
|
57101
|
+
"description": "For an API key: whether anything still acts for it after this revoke (the organization, a key of the same rotation that still works, or whoever took it over). Null when that could not be read.",
|
|
57102
|
+
"type": ["boolean", "null"]
|
|
57103
|
+
},
|
|
57104
|
+
"acting_key_id": {
|
|
57105
|
+
"description": "For an API key: the key that still acts for it, shown to a caller who may read the organization's API keys. Null otherwise, and when no key does.",
|
|
57106
|
+
"type": ["string", "null"]
|
|
57107
|
+
},
|
|
57108
|
+
"delivering": {
|
|
57109
|
+
"description": "For an API key's webhook: whether it is still sent received mail, as GET /v1/endpoints/{id} delivery.delivering says. Null when that could not be read.",
|
|
57110
|
+
"type": ["boolean", "null"]
|
|
56320
57111
|
}
|
|
56321
57112
|
},
|
|
56322
57113
|
"required": [
|
|
@@ -56327,6 +57118,28 @@ const openapiDocument = {
|
|
|
56327
57118
|
],
|
|
56328
57119
|
"additionalProperties": {}
|
|
56329
57120
|
}
|
|
57121
|
+
},
|
|
57122
|
+
"predecessors_revoked_count": {
|
|
57123
|
+
"description": "For an API key: how many keys it replaced in its rotation, still in their overlap, were revoked with it. Keys that replaced it never are.",
|
|
57124
|
+
"type": "integer",
|
|
57125
|
+
"minimum": -9007199254740991,
|
|
57126
|
+
"maximum": 9007199254740991
|
|
57127
|
+
},
|
|
57128
|
+
"predecessors_left": {
|
|
57129
|
+
"description": "For an API key: how many such keys were left working because you may not revoke them. They work until their overlap ends.",
|
|
57130
|
+
"type": "integer",
|
|
57131
|
+
"minimum": -9007199254740991,
|
|
57132
|
+
"maximum": 9007199254740991
|
|
57133
|
+
},
|
|
57134
|
+
"predecessors_revoked": {
|
|
57135
|
+
"description": "The ids of the keys counted in predecessors_revoked_count, to a caller who may read the organization's API keys.",
|
|
57136
|
+
"type": "array",
|
|
57137
|
+
"items": { "type": "string" }
|
|
57138
|
+
},
|
|
57139
|
+
"predecessors_not_revoked": {
|
|
57140
|
+
"description": "The ids of the keys counted in predecessors_left, to a caller who may read the organization's API keys.",
|
|
57141
|
+
"type": "array",
|
|
57142
|
+
"items": { "type": "string" }
|
|
56330
57143
|
}
|
|
56331
57144
|
},
|
|
56332
57145
|
"required": [
|
|
@@ -65178,7 +65991,9 @@ function unauthorizedRefusal(errorPayload, authFailureContext) {
|
|
|
65178
65991
|
* Anything else, including an organization-wide key, an agent_delegate and
|
|
65179
65992
|
* a person with no member address they may send from, gets no default: the
|
|
65180
65993
|
* command refuses, lists what the credential may send from, and asks for
|
|
65181
|
-
* --from.
|
|
65994
|
+
* --from. A credential whose grant has no mail:send is refused by that
|
|
65995
|
+
* permission instead (MissingSendPermissionError), with no --from hint. The
|
|
65996
|
+
* From is never an address guessed from the organization's
|
|
65182
65997
|
* domains, such as agent@ at the first verified one. A failed answer from an
|
|
65183
65998
|
* API that has it refuses rather than fall back.
|
|
65184
65999
|
*/
|
|
@@ -65236,6 +66051,57 @@ const SEND_FROM_SOURCES = {
|
|
|
65236
66051
|
member_address: "member_address",
|
|
65237
66052
|
granted_address: "granted_address"
|
|
65238
66053
|
};
|
|
66054
|
+
/**
|
|
66055
|
+
* The refusal of a send by a credential whose grant has no mail:send, in the
|
|
66056
|
+
* shape the send route itself refuses it with (code grant_forbidden, details
|
|
66057
|
+
* naming the permission), so a script can branch on it. It names what the
|
|
66058
|
+
* credential lacks first, then what would fix it, never --from.
|
|
66059
|
+
*/
|
|
66060
|
+
var MissingSendPermissionError = class extends Errors.CLIError {
|
|
66061
|
+
details = {
|
|
66062
|
+
permission: "mail:send",
|
|
66063
|
+
reason: "permission_missing"
|
|
66064
|
+
};
|
|
66065
|
+
constructor(missing) {
|
|
66066
|
+
super(missingSendPermissionMessage(missing), {
|
|
66067
|
+
code: "grant_forbidden",
|
|
66068
|
+
exit: 1
|
|
66069
|
+
});
|
|
66070
|
+
}
|
|
66071
|
+
};
|
|
66072
|
+
function missingSendPermissionMessage(missing) {
|
|
66073
|
+
const parts = [`${KEY_CREDENTIAL_KINDS.has(missing.credentialKind ?? "") ? "This key" : "This credential"} has no mail:send, so it cannot send new mail from any address; --from does not change that.`];
|
|
66074
|
+
if (missing.replies) parts.push("It can still reply to mail it received.");
|
|
66075
|
+
parts.push(missing.credentialKind === "org_key" && missing.keyId ? `To send, add the permission to the key (a member who manages keys runs \`primitive keys update ${missing.keyId} --add mail:send\`), or use a credential that holds mail:send.` : PERSON_CREDENTIAL_KINDS.has(missing.credentialKind ?? "") ? "To send, ask an organization admin for a role that includes mail:send." : "To send, use a credential that holds mail:send.");
|
|
66076
|
+
return parts.join(" ");
|
|
66077
|
+
}
|
|
66078
|
+
/**
|
|
66079
|
+
* The credential's own grant when it has no mail:send, outright or over a
|
|
66080
|
+
* reach of its own (as the API decides whether a caller may send), or null
|
|
66081
|
+
* when it has it or the grant cannot be read: then nothing is claimed.
|
|
66082
|
+
*/
|
|
66083
|
+
async function missingSendPermission(apiClient) {
|
|
66084
|
+
let own;
|
|
66085
|
+
try {
|
|
66086
|
+
own = await getOwnGrant({
|
|
66087
|
+
client: apiClient.client,
|
|
66088
|
+
responseStyle: "fields"
|
|
66089
|
+
});
|
|
66090
|
+
} catch {
|
|
66091
|
+
return null;
|
|
66092
|
+
}
|
|
66093
|
+
if (!own || own.error) return null;
|
|
66094
|
+
const data = own.data?.data;
|
|
66095
|
+
const grant = data?.grant;
|
|
66096
|
+
if (!grant || !Array.isArray(grant.permissions)) return null;
|
|
66097
|
+
const holds = (permission) => grant.permissions.includes(permission) || grant.scoped_permissions !== null && typeof grant.scoped_permissions === "object" && Object.hasOwn(grant.scoped_permissions, permission);
|
|
66098
|
+
if (holds("mail:send")) return null;
|
|
66099
|
+
return {
|
|
66100
|
+
credentialKind: typeof grant.credential_kind === "string" ? grant.credential_kind : null,
|
|
66101
|
+
keyId: typeof data?.key_id === "string" ? data.key_id : null,
|
|
66102
|
+
replies: holds("mail:reply")
|
|
66103
|
+
};
|
|
66104
|
+
}
|
|
65239
66105
|
function errorDetail(error) {
|
|
65240
66106
|
return error instanceof Error ? error.message : String(error);
|
|
65241
66107
|
}
|
|
@@ -65292,6 +66158,10 @@ async function sendFromAnswer(apiClient, authFailureContext, purpose) {
|
|
|
65292
66158
|
const addresses = data.addresses.map((entry) => entry && typeof entry === "object" ? entry.address : void 0).filter((address) => typeof address === "string");
|
|
65293
66159
|
const domains = Array.isArray(data.domains) ? data.domains.filter((domain) => typeof domain === "string") : [];
|
|
65294
66160
|
if (purpose === "reply" && addresses.length === 0 && domains.length === 0) return void 0;
|
|
66161
|
+
if (addresses.length === 0 && domains.length === 0) {
|
|
66162
|
+
const missing = await missingSendPermission(apiClient);
|
|
66163
|
+
if (missing) throw new MissingSendPermissionError(missing);
|
|
66164
|
+
}
|
|
65295
66165
|
const parts = [typeof data.reason === "string" && data.reason.trim() ? data.reason.trim().replace(/\.?$/, ".") : "This credential has no single address to send from, so --from has no default."];
|
|
65296
66166
|
if (addresses.length > 0) parts.push(`Addresses you may send from: ${addresses.join(", ")}.`);
|
|
65297
66167
|
if (domains.length > 0) parts.push(`${addresses.length > 0 ? "Also any" : "Any"} address at: ${domains.join(", ")}.`);
|
|
@@ -66106,7 +66976,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
66106
66976
|
env: "PRIMITIVE_API_BASE_URL",
|
|
66107
66977
|
hidden: true
|
|
66108
66978
|
}),
|
|
66109
|
-
from: Flags.string({ description: "Sender address. Defaults to the selected connected profile's pinned address; on your own login, to your member address when you may send from it; with a key whose grant names exactly one address, to that address. Otherwise the command asks for --from. A connected profile rejects another sender." }),
|
|
66979
|
+
from: Flags.string({ description: "Sender address. Defaults to the selected connected profile's pinned address; on your own login, to your member address when you may send from it; with a key whose grant names exactly one address, to that address. Otherwise the command asks for --from, except that a credential without mail:send is refused by that permission. A connected profile rejects another sender." }),
|
|
66110
66980
|
subject: Flags.string({
|
|
66111
66981
|
description: "Advanced email transport override. Usually omit; chat threading does not depend on the subject.",
|
|
66112
66982
|
hidden: true
|
|
@@ -66186,7 +67056,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
66186
67056
|
}
|
|
66187
67057
|
const outcome = phase === "sending" || error instanceof UncertainChatSendError ? "uncertain" : "not_sent";
|
|
66188
67058
|
if (flags.json) this.log(JSON.stringify(buildChatSendFailureEnvelope({
|
|
66189
|
-
error:
|
|
67059
|
+
error: thrownErrorBody(error),
|
|
66190
67060
|
exitCode: outcome === "not_sent" ? thrownErrorExitCode(error) : void 0,
|
|
66191
67061
|
idempotencyKey: this.chatProgress.idempotencyKey,
|
|
66192
67062
|
noun,
|
|
@@ -66976,15 +67846,12 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
66976
67846
|
try {
|
|
66977
67847
|
await this.runReply(args, flags);
|
|
66978
67848
|
} catch (error) {
|
|
66979
|
-
if (flags.json && !this.delegatedToChat) {
|
|
66980
|
-
|
|
66981
|
-
|
|
66982
|
-
|
|
66983
|
-
|
|
66984
|
-
|
|
66985
|
-
outcome: "not_sent"
|
|
66986
|
-
}), null, 2));
|
|
66987
|
-
}
|
|
67849
|
+
if (flags.json && !this.delegatedToChat) this.log(JSON.stringify(buildChatSendFailureEnvelope({
|
|
67850
|
+
error: thrownErrorBody(error),
|
|
67851
|
+
exitCode: thrownErrorExitCode(error),
|
|
67852
|
+
noun: "Reply",
|
|
67853
|
+
outcome: "not_sent"
|
|
67854
|
+
}), null, 2));
|
|
66988
67855
|
throw error;
|
|
66989
67856
|
}
|
|
66990
67857
|
}
|
|
@@ -68435,7 +69302,9 @@ const ID_DESCRIPTION = "The id of the received email (from `primitive emails lis
|
|
|
68435
69302
|
/**
|
|
68436
69303
|
* `primitive emails facts <id>`: what the classifier read from one received
|
|
68437
69304
|
* email, every fact as email rule conditions read it, from the email's rule
|
|
68438
|
-
* results (GET /emails/{id}/rule-results).
|
|
69305
|
+
* results (GET /emails/{id}/rule-results). A credential that may not read
|
|
69306
|
+
* rule results gets the facts as written, from the email read
|
|
69307
|
+
* (GET /emails/{id}, `understanding`).
|
|
68439
69308
|
*/
|
|
68440
69309
|
var EmailsFactsCommand = class EmailsFactsCommand extends Command {
|
|
68441
69310
|
static summary = "Show what the classifier read from an email";
|
|
@@ -68443,7 +69312,7 @@ var EmailsFactsCommand = class EmailsFactsCommand extends Command {
|
|
|
68443
69312
|
|
|
68444
69313
|
Use it to see why an email rule did or did not match, alongside \`primitive email-rules list-email-rule-results --email-id <id>\`, which this reads. An email with no classification (not classified yet, never to be, or content that cannot be read) prints a message saying so on stderr and nothing on stdout. With --json, prints the classification object, or null when there is none.
|
|
68445
69314
|
|
|
68446
|
-
Needs the email rules feature
|
|
69315
|
+
Needs the email rules feature. Rule results are read with a member's own credential with their full access. An API key, agent key or narrowed token cannot read them, so for one of those the command reads the facts from the email itself instead (\`understanding.facts\` of \`primitive emails get <id> --json\`), as the classifier wrote them, and says so on stderr (in \`warnings\` with --json). Those differ from what rule conditions read in two ways: conditions read a multi fact's options lowercased, and read a fact the classifier left out as null, where \`understanding.facts\` leaves it out.`;
|
|
68447
69316
|
static examples = ["<%= config.bin %> emails facts 3f0e1c2a-8b7d-4c6e-9a1b-2d3e4f5a6b7c", "<%= config.bin %> emails facts --id 3f0e1c2a-8b7d-4c6e-9a1b-2d3e4f5a6b7c --json"];
|
|
68448
69317
|
static args = { id: Args.string({
|
|
68449
69318
|
description: ID_DESCRIPTION,
|
|
@@ -68451,7 +69320,7 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68451
69320
|
}) };
|
|
68452
69321
|
static flags = {
|
|
68453
69322
|
id: Flags.string({ description: ID_DESCRIPTION }),
|
|
68454
|
-
json: Flags.boolean({ description: "Print the classification object as one JSON document on stdout (null when the email has none), and nothing on stderr. A failure prints the error with exit_code." }),
|
|
69323
|
+
json: Flags.boolean({ description: "Print the classification object as one JSON document on stdout (null when the email has none), and nothing on stderr. When the facts come from the email itself, the note saying so is in its warnings. A failure prints the error with exit_code." }),
|
|
68455
69324
|
"api-key": Flags.string({
|
|
68456
69325
|
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
68457
69326
|
env: "PRIMITIVE_API_KEY"
|
|
@@ -68484,6 +69353,14 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68484
69353
|
});
|
|
68485
69354
|
if (result.error) {
|
|
68486
69355
|
const payload = extractErrorPayload(result.error);
|
|
69356
|
+
if (refusesRuleResultsToCredential(payload)) {
|
|
69357
|
+
await this.printFactsFromEmail(id, Boolean(flags.json), {
|
|
69358
|
+
apiClient,
|
|
69359
|
+
auth,
|
|
69360
|
+
baseUrlOverridden
|
|
69361
|
+
});
|
|
69362
|
+
return;
|
|
69363
|
+
}
|
|
68487
69364
|
writeErrorWithHints(payload, result.response);
|
|
68488
69365
|
surfaceUnauthorizedHint({
|
|
68489
69366
|
auth,
|
|
@@ -68508,7 +69385,80 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68508
69385
|
this.log(lines.join("\n"));
|
|
68509
69386
|
});
|
|
68510
69387
|
}
|
|
69388
|
+
/**
|
|
69389
|
+
* The facts as `emails get` reads them, for a credential that may not read
|
|
69390
|
+
* rule results: the email's understanding, when it is ready.
|
|
69391
|
+
*/
|
|
69392
|
+
async printFactsFromEmail(id, json, client) {
|
|
69393
|
+
const read = await getEmail({
|
|
69394
|
+
client: client.apiClient.client,
|
|
69395
|
+
path: { id },
|
|
69396
|
+
responseStyle: "fields"
|
|
69397
|
+
});
|
|
69398
|
+
if (read.error) {
|
|
69399
|
+
const payload = extractErrorPayload(read.error);
|
|
69400
|
+
process.stderr.write("This credential cannot read email rule results, and reading the facts from the email itself failed:\n");
|
|
69401
|
+
writeErrorWithHints(payload, read.response);
|
|
69402
|
+
surfaceUnauthorizedHint({
|
|
69403
|
+
auth: client.auth,
|
|
69404
|
+
baseUrlOverridden: client.baseUrlOverridden,
|
|
69405
|
+
configDir: this.config.configDir,
|
|
69406
|
+
payload
|
|
69407
|
+
});
|
|
69408
|
+
process.exitCode = 1;
|
|
69409
|
+
return;
|
|
69410
|
+
}
|
|
69411
|
+
const understanding = read.data?.data?.understanding;
|
|
69412
|
+
const classification = classificationFromUnderstanding(understanding);
|
|
69413
|
+
if (classification) process.stderr.write(`${understandingNote(id)}\n`);
|
|
69414
|
+
if (json) {
|
|
69415
|
+
this.log(JSON.stringify(classification, null, 2));
|
|
69416
|
+
return;
|
|
69417
|
+
}
|
|
69418
|
+
const lines = classification ? classificationFactLines(classification) : [];
|
|
69419
|
+
if (lines.length === 0) {
|
|
69420
|
+
process.stderr.write(`${classification ? "This email's classification has no facts." : noUnderstandingMessage(understanding)}\n`);
|
|
69421
|
+
return;
|
|
69422
|
+
}
|
|
69423
|
+
this.log(lines.join("\n"));
|
|
69424
|
+
}
|
|
68511
69425
|
};
|
|
69426
|
+
/** The refusals of rule results to a credential that is not a member's own, unnarrowed one. */
|
|
69427
|
+
const NOT_A_RULE_READER = /* @__PURE__ */ new Set(["rule_author_credential", "narrowed_credential"]);
|
|
69428
|
+
/** Whether rule results were refused only because of the kind of credential. */
|
|
69429
|
+
function refusesRuleResultsToCredential(payload) {
|
|
69430
|
+
if (extractErrorCode(payload) !== "forbidden") return false;
|
|
69431
|
+
const { reason } = refusalDetails(payload);
|
|
69432
|
+
return typeof reason === "string" && NOT_A_RULE_READER.has(reason);
|
|
69433
|
+
}
|
|
69434
|
+
/**
|
|
69435
|
+
* The email's `understanding` as the classification rule results show
|
|
69436
|
+
* (id, vocabulary, classified_at, facts), when it is ready; otherwise null.
|
|
69437
|
+
*/
|
|
69438
|
+
function classificationFromUnderstanding(understanding) {
|
|
69439
|
+
if (!isRecord$1(understanding) || understanding.status !== "ready") return null;
|
|
69440
|
+
return {
|
|
69441
|
+
id: understanding.classification_id ?? null,
|
|
69442
|
+
vocabulary: understanding.vocabulary ?? null,
|
|
69443
|
+
classified_at: understanding.classified_at ?? null,
|
|
69444
|
+
facts: isRecord$1(understanding.facts) ? understanding.facts : {}
|
|
69445
|
+
};
|
|
69446
|
+
}
|
|
69447
|
+
/** Where facts read from the email came from, and how they differ. */
|
|
69448
|
+
function understandingNote(id) {
|
|
69449
|
+
return `This credential cannot read email rule results, so these facts come from the email itself (\`primitive emails get ${id} --json\`, understanding.facts), as the classifier wrote them. Rule conditions read a multi fact's options lowercased, and a fact left out here reads null to them.`;
|
|
69450
|
+
}
|
|
69451
|
+
/** Why an email read gave no facts to show. */
|
|
69452
|
+
function noUnderstandingMessage(understanding) {
|
|
69453
|
+
const status = isRecord$1(understanding) ? understanding.status : void 0;
|
|
69454
|
+
if (status === "pending") return "No classification for this email yet: it has not been classified. Recent mail can take a while, so try again later.";
|
|
69455
|
+
if (status === "unavailable") return "No classification to show for this email: it was not classified in time, its content was discarded first, or its facts are too large to include.";
|
|
69456
|
+
if (typeof status === "string") return `No classification to show: the email's understanding is ${status}.`;
|
|
69457
|
+
return "No classification to show: the email read carries no facts. Its content may be withheld, discarded or failed to parse, this credential may not read message content, or the facts could not be read just now, so try again.";
|
|
69458
|
+
}
|
|
69459
|
+
function isRecord$1(value) {
|
|
69460
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
69461
|
+
}
|
|
68512
69462
|
// src/oclif/repeat-message.ts
|
|
68513
69463
|
const UUID$5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
68514
69464
|
/** The server-verified repeat marker on an email read, if any. */
|
|
@@ -71015,8 +71965,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
71015
71965
|
name: "Primitive Team",
|
|
71016
71966
|
url: "https://primitive.dev"
|
|
71017
71967
|
};
|
|
71018
|
-
const SDK_VERSION_RANGE = "^3.0.0-preview.
|
|
71019
|
-
const CLI_VERSION_RANGE = "^3.0.0-preview.
|
|
71968
|
+
const SDK_VERSION_RANGE = "^3.0.0-preview.8";
|
|
71969
|
+
const CLI_VERSION_RANGE = "^3.0.0-preview.8";
|
|
71020
71970
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
71021
71971
|
function renderHandler() {
|
|
71022
71972
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -72848,7 +73798,8 @@ const INBOX_NEXT_EXIT_CODE_HELP = `Exit codes:
|
|
|
72848
73798
|
const SCAN_PAGE_SIZE = 100;
|
|
72849
73799
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 300;
|
|
72850
73800
|
const MAX_LONG_POLL_SECONDS$1 = 30;
|
|
72851
|
-
const
|
|
73801
|
+
const TIME_CURSOR_ID = "00000000-0000-4000-8000-000000000000";
|
|
73802
|
+
const EPOCH_CURSOR = `1970-01-01T00:00:00.000000Z|${TIME_CURSOR_ID}`;
|
|
72852
73803
|
/** An API call failed; carries the error payload for printing. */
|
|
72853
73804
|
var InboxNextApiError = class extends Error {
|
|
72854
73805
|
surface;
|
|
@@ -73090,18 +74041,43 @@ async function countAutomatedAwaiting(params) {
|
|
|
73090
74041
|
capped: page.totalCapped
|
|
73091
74042
|
};
|
|
73092
74043
|
}
|
|
74044
|
+
/** Pages of the forward tail `baselineCursor` reads to catch up, at most. */
|
|
74045
|
+
const BASELINE_CATCH_UP_PAGES = 5;
|
|
73093
74046
|
/**
|
|
73094
|
-
* The wake-up cursor for --wait: the
|
|
73095
|
-
*
|
|
73096
|
-
*
|
|
73097
|
-
*
|
|
73098
|
-
*
|
|
74047
|
+
* The wake-up cursor for --wait (and for `primitive wait`): the end of the
|
|
74048
|
+
* forward tail, the position of the email made visible most recently, read
|
|
74049
|
+
* BEFORE the first state check. Anything that becomes visible after it is
|
|
74050
|
+
* returned by the long-poll, so there is no gap between "checked, nothing
|
|
74051
|
+
* awaits" and "started waiting". Taken from the server's answers alone, with
|
|
74052
|
+
* no client clock involved.
|
|
74053
|
+
*
|
|
74054
|
+
* The tail orders and cursors on when an email became visible, which the
|
|
74055
|
+
* API stamps when it accepts the email: after the created_at a list row
|
|
74056
|
+
* carries, and to the microsecond where that created_at is truncated to the
|
|
74057
|
+
* millisecond. So a cursor built from the newest row's created_at sits
|
|
74058
|
+
* before that row on the tail, and the tail would hand it back, with any
|
|
74059
|
+
* older email made visible after it, as new mail. From that cursor the tail
|
|
74060
|
+
* is read forward without waiting until it is caught up, and the position it
|
|
74061
|
+
* gives is the baseline. The read stops after BASELINE_CATCH_UP_PAGES pages;
|
|
74062
|
+
* mail past that would still be handed back, which needs more than that many
|
|
74063
|
+
* pages of mail made visible after the newest row's created_at.
|
|
73099
74064
|
*/
|
|
73100
74065
|
async function baselineCursor(params) {
|
|
73101
|
-
const
|
|
74066
|
+
const api = params.api ?? DEFAULT_API;
|
|
74067
|
+
const newest = (await listPage(api, params.apiClient, { limit: 1 })).rows[0];
|
|
73102
74068
|
const createdAt = str$1(newest?.created_at);
|
|
73103
74069
|
const id = str$1(newest?.id);
|
|
73104
|
-
|
|
74070
|
+
let cursor = createdAt && id ? `${createdAt}|${id}` : EPOCH_CURSOR;
|
|
74071
|
+
for (let read = 0; read < BASELINE_CATCH_UP_PAGES; read += 1) {
|
|
74072
|
+
const tail = await listPage(api, params.apiClient, {
|
|
74073
|
+
limit: SCAN_PAGE_SIZE,
|
|
74074
|
+
since: cursor
|
|
74075
|
+
});
|
|
74076
|
+
if (tail.rows.length === 0 || !tail.cursor) break;
|
|
74077
|
+
cursor = tail.cursor;
|
|
74078
|
+
if (tail.rows.length < SCAN_PAGE_SIZE) break;
|
|
74079
|
+
}
|
|
74080
|
+
return cursor;
|
|
73105
74081
|
}
|
|
73106
74082
|
/**
|
|
73107
74083
|
* Hold one long-poll on the forward tail. Returns the advanced cursor
|
|
@@ -73743,6 +74719,498 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
|
|
|
73743
74719
|
});
|
|
73744
74720
|
}
|
|
73745
74721
|
};
|
|
74722
|
+
// src/oclif/commands/whoami.ts
|
|
74723
|
+
/**
|
|
74724
|
+
* `GET /whoami` with its grant query parameters. A server without grants
|
|
74725
|
+
* ignores them and answers without `grant` or `check`.
|
|
74726
|
+
*/
|
|
74727
|
+
async function fetchCallerIdentity(client, query, options = {}) {
|
|
74728
|
+
const result = await client.get({
|
|
74729
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
74730
|
+
...options.signal ? { signal: options.signal } : {},
|
|
74731
|
+
query,
|
|
74732
|
+
responseStyle: "fields",
|
|
74733
|
+
security: [{
|
|
74734
|
+
scheme: "bearer",
|
|
74735
|
+
type: "http"
|
|
74736
|
+
}],
|
|
74737
|
+
url: "/whoami"
|
|
74738
|
+
});
|
|
74739
|
+
if (result.error) return {
|
|
74740
|
+
identity: null,
|
|
74741
|
+
error: result.error
|
|
74742
|
+
};
|
|
74743
|
+
return {
|
|
74744
|
+
identity: result.data?.data ?? null,
|
|
74745
|
+
error: null
|
|
74746
|
+
};
|
|
74747
|
+
}
|
|
74748
|
+
/** How many rule ids the Grant line names before it says how many more. */
|
|
74749
|
+
const GRANT_LINE_RULES = 3;
|
|
74750
|
+
/** Rule ids for the Grant line: the first few, then how many more. */
|
|
74751
|
+
function ruleIdList(rules) {
|
|
74752
|
+
const shown = rules.slice(0, GRANT_LINE_RULES);
|
|
74753
|
+
const rest = rules.length - shown.length;
|
|
74754
|
+
const all = rest > 0 ? [...shown, `${rest} more`] : shown;
|
|
74755
|
+
return all.length <= 1 ? all[0] ?? "" : `${all.slice(0, -1).join(", ")} and ${all[all.length - 1]}`;
|
|
74756
|
+
}
|
|
74757
|
+
/**
|
|
74758
|
+
* The mail a grant reaches, for the Grant line. A key cannot read email
|
|
74759
|
+
* rules, so a scope that lists them names them by id. A rule grants mail it
|
|
74760
|
+
* matches as the mail arrives, while the rule is active, and the key reads it
|
|
74761
|
+
* only while the rule stays active.
|
|
74762
|
+
*/
|
|
74763
|
+
function grantScopeText(scope) {
|
|
74764
|
+
if (scope === void 0) return "all mail in the organization";
|
|
74765
|
+
const reach = mailReach(scope);
|
|
74766
|
+
switch (reach.kind) {
|
|
74767
|
+
case "org": return "all mail in the organization";
|
|
74768
|
+
case "unreadable": return "a mail scope the server could not read, so no mail";
|
|
74769
|
+
case "none": return "no mail through its grant, which names no mailbox, domain or email rule";
|
|
74770
|
+
case "rules_only":
|
|
74771
|
+
case "mailboxes": {
|
|
74772
|
+
if (reach.rules.length === 0) return "a narrowed mail scope";
|
|
74773
|
+
const granted = reach.rules.length === 1 ? `mail that email rule ${ruleIdList(reach.rules)} granted, while it stays active` : `mail that email rules ${ruleIdList(reach.rules)} granted, while each stays active`;
|
|
74774
|
+
const more = reach.rules.length > GRANT_LINE_RULES ? " (`primitive whoami --json --grant` lists every rule)" : "";
|
|
74775
|
+
return reach.kind === "mailboxes" ? `a narrowed mail scope, plus ${granted}${more}` : `${granted}${more}`;
|
|
74776
|
+
}
|
|
74777
|
+
default: return "a narrowed mail scope";
|
|
74778
|
+
}
|
|
74779
|
+
}
|
|
74780
|
+
function formatGrant(grant, keyId = null) {
|
|
74781
|
+
const what = grant.template ? `template ${grant.template}` : `${grant.grant_type ?? "grant"} with ${grant.permissions.length} permissions`;
|
|
74782
|
+
const credential = keyId ? `${grant.credential_kind}, key id ${keyId}` : grant.credential_kind;
|
|
74783
|
+
const lines = [`Grant: ${what}, ${grantScopeText(grant.mail_scope)} (${credential})`];
|
|
74784
|
+
if (grant.expires_at) lines.push(`Credential expires: ${grant.expires_at}`);
|
|
74785
|
+
return lines;
|
|
74786
|
+
}
|
|
74787
|
+
/** Lines describing the caller's organization, role and grant. */
|
|
74788
|
+
function formatGrantLines(identity, options = {}) {
|
|
74789
|
+
const lines = [];
|
|
74790
|
+
const org = options.orgName ? `${options.orgName} (${identity.org_id})` : identity.org_id;
|
|
74791
|
+
lines.push(`Organization: ${org}`);
|
|
74792
|
+
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}`);
|
|
74793
|
+
const grant = identity.grant;
|
|
74794
|
+
if (grant) lines.push(...formatGrant(grant, identity.key_id));
|
|
74795
|
+
if (options.userLogin) lines.push("Signed in as yourself. Switch organizations with `primitive org use <org>`.");
|
|
74796
|
+
return lines;
|
|
74797
|
+
}
|
|
74798
|
+
function formatPermissionCheck(check) {
|
|
74799
|
+
if (check.allow) return `${check.permission}: allowed`;
|
|
74800
|
+
return `${check.permission}: not allowed${check.reason ? ` (${check.reason})` : ""}`;
|
|
74801
|
+
}
|
|
74802
|
+
/** Whether GET /whoami answered for an API key. */
|
|
74803
|
+
function isKeyIdentity(identity) {
|
|
74804
|
+
return Boolean(identity.key_id) || identity.auth_method === "api_key";
|
|
74805
|
+
}
|
|
74806
|
+
/** Whether GET /whoami answered for a person's own session or OAuth login. */
|
|
74807
|
+
const PERSON_AUTH_METHODS = /* @__PURE__ */ new Set([
|
|
74808
|
+
"oauth",
|
|
74809
|
+
"session",
|
|
74810
|
+
"dashboard_bridge"
|
|
74811
|
+
]);
|
|
74812
|
+
function isPersonIdentity(identity) {
|
|
74813
|
+
return !isKeyIdentity(identity) && typeof identity.auth_method === "string" && PERSON_AUTH_METHODS.has(identity.auth_method);
|
|
74814
|
+
}
|
|
74815
|
+
/** What the summary's first line says about who is calling. */
|
|
74816
|
+
function identityLine(identity) {
|
|
74817
|
+
if (!identity || identity.member_address === void 0) return "Authenticated";
|
|
74818
|
+
if (identity.member_address) return `Authenticated as ${identity.member_address.address} (your member address)`;
|
|
74819
|
+
if (isKeyIdentity(identity)) return "Authenticated (API keys have no member address)";
|
|
74820
|
+
if (!isPersonIdentity(identity)) return "Authenticated (this credential has no member address)";
|
|
74821
|
+
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` : ""})`;
|
|
74822
|
+
}
|
|
74823
|
+
function formatWhoamiSummary(account, managedInboxDomain, orgName = null, identity = null) {
|
|
74824
|
+
const lines = [
|
|
74825
|
+
identityLine(identity),
|
|
74826
|
+
orgName ? `Organization: ${orgName} (${account.id})` : `Organization id: ${account.id}`,
|
|
74827
|
+
...account.email ? [`Account email: ${account.email} (the email the account was opened with)`] : [],
|
|
74828
|
+
`Plan: ${account.plan}`
|
|
74829
|
+
];
|
|
74830
|
+
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
74831
|
+
return lines.join("\n");
|
|
74832
|
+
}
|
|
74833
|
+
/** `GET /whoami/grant`, which any credential may ask about itself. */
|
|
74834
|
+
async function fetchOwnGrant(client) {
|
|
74835
|
+
try {
|
|
74836
|
+
const result = await getOwnGrant({
|
|
74837
|
+
client,
|
|
74838
|
+
responseStyle: "fields"
|
|
74839
|
+
});
|
|
74840
|
+
if (result.error) return null;
|
|
74841
|
+
return result.data?.data ?? null;
|
|
74842
|
+
} catch {
|
|
74843
|
+
return null;
|
|
74844
|
+
}
|
|
74845
|
+
}
|
|
74846
|
+
/** The summary for a valid credential whose grant cannot read the account. */
|
|
74847
|
+
function formatOwnGrantSummary(own, orgName) {
|
|
74848
|
+
return [
|
|
74849
|
+
"Authenticated. This credential cannot read the account (it lacks org:read).",
|
|
74850
|
+
orgName ? `Organization: ${orgName} (${own.org_id})` : `Organization id: ${own.org_id}`,
|
|
74851
|
+
...own.grant ? formatGrant(own.grant, own.key_id) : []
|
|
74852
|
+
].join("\n");
|
|
74853
|
+
}
|
|
74854
|
+
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
74855
|
+
static description = `Print the account currently authenticated by saved OAuth credentials or an explicit API key. For account credentials this is a live credentials smoke test. Use account whoami for the authenticated caller, their assigned member address and an address suggestion; account provision-member-address --address <email> saves your chosen personal address.
|
|
74856
|
+
|
|
74857
|
+
With PRIMITIVE_AGENT_PROFILE selected, print the saved connected-agent identity offline, with no API call. The first line is the agent's email address alone; --json adds address, profile and source (session_hook when the selected profile is this Claude Code session's own agent exported by the session start hook, env otherwise). This does not verify live authentication, app pairing, or listener readiness. Inspect the receiver separately: the output names the command for the profile's receiver mode (agent connect --status for Claude hook and poll receivers, listen --status --notify-session for a native listener).
|
|
74858
|
+
|
|
74859
|
+
The default output is a concise human summary. Pass --json for the full /account response or the explicit offline connected-agent identity envelope.`;
|
|
74860
|
+
static summary = "Print the live account or saved connected-agent identity";
|
|
74861
|
+
static examples = [
|
|
74862
|
+
"<%= config.bin %> whoami",
|
|
74863
|
+
"<%= config.bin %> whoami --api-key prim_...",
|
|
74864
|
+
"<%= config.bin %> whoami --json | jq .id",
|
|
74865
|
+
"PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> whoami --json"
|
|
74866
|
+
];
|
|
74867
|
+
static flags = {
|
|
74868
|
+
"api-key": Flags.string({
|
|
74869
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
74870
|
+
env: "PRIMITIVE_API_KEY"
|
|
74871
|
+
}),
|
|
74872
|
+
"api-base-url": Flags.string({
|
|
74873
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
74874
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
74875
|
+
hidden: true
|
|
74876
|
+
}),
|
|
74877
|
+
json: Flags.boolean({ description: "Print the account JSON response, or the saved connected identity marked verification: offline. Never prints credentials." }),
|
|
74878
|
+
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." }),
|
|
74879
|
+
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." }),
|
|
74880
|
+
resource: Flags.string({
|
|
74881
|
+
description: "With --check, the resource to check against, as function:<id>, domain:<id>, endpoint:<id> or route:<id>.",
|
|
74882
|
+
dependsOn: ["check"]
|
|
74883
|
+
}),
|
|
74884
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
74885
|
+
};
|
|
74886
|
+
async run() {
|
|
74887
|
+
const { flags } = await this.parse(WhoamiCommand);
|
|
74888
|
+
await runWithTiming(flags.time, async () => {
|
|
74889
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
74890
|
+
apiKey: flags["api-key"],
|
|
74891
|
+
apiBaseUrl: flags["api-base-url"],
|
|
74892
|
+
configDir: this.config.configDir
|
|
74893
|
+
});
|
|
74894
|
+
if (auth.connectedAgent && flags.check !== void 0) throw new Errors.CLIError("--check needs a live account credential. A selected connected-agent profile is shown offline; rerun with `PRIMITIVE_AGENT_PROFILE=` in front of the command to check the member login.", { exit: 1 });
|
|
74895
|
+
if (auth.connectedAgent) {
|
|
74896
|
+
const identity = auth.connectedAgent;
|
|
74897
|
+
const statusCommand = `${cliInvocation(process.argv[1])} agent connect --profile ${identity.profileName} --status --json`;
|
|
74898
|
+
const receiver = receiverStatusCommand(this.config.configDir, identity.profileName);
|
|
74899
|
+
const guidance = `Saved identity only. Live authentication, app pairing, and receiving readiness are not verified. Inspect the ${receiver.mode === "external" ? "Claude hook receiver" : receiver.mode === "poll" ? "poll receiver" : "receiver"} with \`${receiver.command}\`.`;
|
|
74900
|
+
const source = agentIdentitySource(identity);
|
|
74901
|
+
const selectedBy = source === "session_hook" ? `this Claude Code session's start hook (PRIMITIVE_AGENT_PROFILE=${identity.profileName})` : `PRIMITIVE_AGENT_PROFILE=${identity.profileName}`;
|
|
74902
|
+
this.log(flags.json ? JSON.stringify({
|
|
74903
|
+
address: identity.agentAddress,
|
|
74904
|
+
profile: identity.profileName,
|
|
74905
|
+
source,
|
|
74906
|
+
auth_method: "agent_connection",
|
|
74907
|
+
verification: "offline",
|
|
74908
|
+
identity,
|
|
74909
|
+
status_command: statusCommand,
|
|
74910
|
+
guidance
|
|
74911
|
+
}, null, 2) : `${identity.agentAddress}\nSelected by: ${selectedBy}. Prefix a command with \`PRIMITIVE_AGENT_PROFILE=\` to run it as the member login instead.\nSaved connected-agent profile: ${identity.profileName}\nAgent address: ${identity.agentAddress}\nOwner personal address (send reports here): ${identity.ownerMemberAddress ?? "unknown; reply to the member who wrote to you"}\nControl address (setup and presence only): ${identity.ownerAddress}\nAPI origin: ${identity.apiBaseUrl}\n${guidance}\nSaved profile status: ${statusCommand}`);
|
|
74912
|
+
return;
|
|
74913
|
+
}
|
|
74914
|
+
if (isConnectedChatCredential(auth.apiKey)) throw new Errors.CLIError("This connection credential cannot inspect an account. Select its saved profile with PRIMITIVE_AGENT_PROFILE to inspect the connected identity offline.", { exit: 1 });
|
|
74915
|
+
if (flags.check !== void 0) {
|
|
74916
|
+
const check = flags.check.trim();
|
|
74917
|
+
if (!/^[a-z][a-z0-9_.]*:[a-z][a-z0-9_]*$/.test(check)) throw new Errors.CLIError(`--check ${JSON.stringify(flags.check)} is not a permission. Permissions look like \`mail:send\`.`, { exit: 2 });
|
|
74918
|
+
const { identity, error } = await fetchCallerIdentity(apiClient.client, {
|
|
74919
|
+
check,
|
|
74920
|
+
...flags.resource ? { resource: flags.resource.trim() } : {}
|
|
74921
|
+
});
|
|
74922
|
+
if (error) {
|
|
74923
|
+
const errorPayload = extractErrorPayload(error);
|
|
74924
|
+
writeErrorWithHints(errorPayload);
|
|
74925
|
+
surfaceUnauthorizedHint({
|
|
74926
|
+
auth,
|
|
74927
|
+
baseUrlOverridden,
|
|
74928
|
+
configDir: this.config.configDir,
|
|
74929
|
+
payload: errorPayload
|
|
74930
|
+
});
|
|
74931
|
+
process.exitCode = 1;
|
|
74932
|
+
return;
|
|
74933
|
+
}
|
|
74934
|
+
const decision = identity?.check;
|
|
74935
|
+
if (!decision) throw new Errors.CLIError("This Primitive server does not answer permission checks.", { exit: 1 });
|
|
74936
|
+
this.log(flags.json ? JSON.stringify(decision, null, 2) : formatPermissionCheck(decision));
|
|
74937
|
+
if (!decision.allow) process.exitCode = 1;
|
|
74938
|
+
return;
|
|
74939
|
+
}
|
|
74940
|
+
const result = await getAccount({
|
|
74941
|
+
client: apiClient.client,
|
|
74942
|
+
responseStyle: "fields"
|
|
74943
|
+
});
|
|
74944
|
+
if (result.error) {
|
|
74945
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
74946
|
+
const own = isGrantRefusal(errorPayload) ? await fetchOwnGrant(apiClient.client) : null;
|
|
74947
|
+
if (own) {
|
|
74948
|
+
const orgName = auth.credentials?.org_id === own.org_id ? auth.credentials.org_name ?? null : null;
|
|
74949
|
+
this.log(flags.json ? JSON.stringify({
|
|
74950
|
+
account: null,
|
|
74951
|
+
...own,
|
|
74952
|
+
org_name: orgName
|
|
74953
|
+
}, null, 2) : formatOwnGrantSummary(own, orgName));
|
|
74954
|
+
return;
|
|
74955
|
+
}
|
|
74956
|
+
writeErrorWithHints(errorPayload);
|
|
74957
|
+
surfaceUnauthorizedHint({
|
|
74958
|
+
auth,
|
|
74959
|
+
baseUrlOverridden,
|
|
74960
|
+
configDir: this.config.configDir,
|
|
74961
|
+
payload: errorPayload
|
|
74962
|
+
});
|
|
74963
|
+
process.exitCode = 1;
|
|
74964
|
+
return;
|
|
74965
|
+
}
|
|
74966
|
+
const account = result.data?.data;
|
|
74967
|
+
if (!account) {
|
|
74968
|
+
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
74969
|
+
throw new Errors.CLIError("unexpected empty response");
|
|
74970
|
+
}
|
|
74971
|
+
let managedInboxDomain = null;
|
|
74972
|
+
try {
|
|
74973
|
+
const domainsResult = await listDomains({
|
|
74974
|
+
client: apiClient.client,
|
|
74975
|
+
responseStyle: "fields"
|
|
74976
|
+
});
|
|
74977
|
+
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
74978
|
+
} catch {}
|
|
74979
|
+
const orgName = auth.credentials?.org_id === account.id ? auth.credentials.org_name ?? null : null;
|
|
74980
|
+
const session = guardedSession(void 0, process.env);
|
|
74981
|
+
let registration = null;
|
|
74982
|
+
try {
|
|
74983
|
+
registration = session ? failedSessionRegistration(this.config.configDir, session) : null;
|
|
74984
|
+
} catch {
|
|
74985
|
+
registration = null;
|
|
74986
|
+
}
|
|
74987
|
+
let identity = null;
|
|
74988
|
+
if (!flags.json || flags.grant) try {
|
|
74989
|
+
identity = (await fetchCallerIdentity(apiClient.client, { include: "grant" })).identity;
|
|
74990
|
+
} catch {
|
|
74991
|
+
identity = null;
|
|
74992
|
+
}
|
|
74993
|
+
if (flags.json) {
|
|
74994
|
+
this.log(JSON.stringify({
|
|
74995
|
+
...account,
|
|
74996
|
+
managed_inbox_domain: managedInboxDomain,
|
|
74997
|
+
org_name: orgName,
|
|
74998
|
+
...registration ? { session_registration: registration } : {},
|
|
74999
|
+
...flags.grant ? {
|
|
75000
|
+
grant: identity?.grant ?? null,
|
|
75001
|
+
org_id: identity?.org_id ?? null,
|
|
75002
|
+
role: identity?.role ?? null
|
|
75003
|
+
} : {}
|
|
75004
|
+
}, null, 2));
|
|
75005
|
+
return;
|
|
75006
|
+
}
|
|
75007
|
+
const summary = [formatWhoamiSummary(account, managedInboxDomain, orgName, identity)];
|
|
75008
|
+
if (identity?.grant) summary.push(...formatGrantLines(identity, {
|
|
75009
|
+
orgName: auth.credentials?.org_name ?? null,
|
|
75010
|
+
userLogin: auth.credentials?.auth_method === "user_grant"
|
|
75011
|
+
}).filter((line) => !line.startsWith("Organization:")));
|
|
75012
|
+
this.log(summary.join("\n"));
|
|
75013
|
+
if (registration) this.log(registrationSummaryLine(registration));
|
|
75014
|
+
});
|
|
75015
|
+
}
|
|
75016
|
+
};
|
|
75017
|
+
// src/oclif/key-permissions.ts
|
|
75018
|
+
const KEY_PERMISSION_AREAS = [
|
|
75019
|
+
{
|
|
75020
|
+
label: "Mail",
|
|
75021
|
+
permissions: [
|
|
75022
|
+
"mail:read",
|
|
75023
|
+
"changes:read",
|
|
75024
|
+
"mail.search:semantic",
|
|
75025
|
+
"mail:send",
|
|
75026
|
+
"mail:send_batch",
|
|
75027
|
+
"mail:reply",
|
|
75028
|
+
"mail:signal",
|
|
75029
|
+
"mail.state:write",
|
|
75030
|
+
"mail:delete",
|
|
75031
|
+
"mail.compat:use"
|
|
75032
|
+
]
|
|
75033
|
+
},
|
|
75034
|
+
{
|
|
75035
|
+
label: "Raw mail",
|
|
75036
|
+
permissions: ["mail.raw:read"]
|
|
75037
|
+
},
|
|
75038
|
+
{
|
|
75039
|
+
label: "Scheduled and repeating sends",
|
|
75040
|
+
permissions: ["sent:manage", "sent:cancel"]
|
|
75041
|
+
},
|
|
75042
|
+
{
|
|
75043
|
+
label: "Thread mutes",
|
|
75044
|
+
permissions: ["threads:mute"]
|
|
75045
|
+
},
|
|
75046
|
+
{
|
|
75047
|
+
label: "App sync state",
|
|
75048
|
+
permissions: ["mail_sync:manage"]
|
|
75049
|
+
},
|
|
75050
|
+
{
|
|
75051
|
+
label: "Endpoints and webhooks",
|
|
75052
|
+
permissions: [
|
|
75053
|
+
"webhooks:read",
|
|
75054
|
+
"endpoints:manage",
|
|
75055
|
+
"endpoints.pull:manage",
|
|
75056
|
+
"webhooks:replay"
|
|
75057
|
+
]
|
|
75058
|
+
},
|
|
75059
|
+
{
|
|
75060
|
+
label: "Recipient routes",
|
|
75061
|
+
permissions: ["routes:manage"]
|
|
75062
|
+
},
|
|
75063
|
+
{
|
|
75064
|
+
label: "Filters",
|
|
75065
|
+
permissions: ["filters:manage"]
|
|
75066
|
+
},
|
|
75067
|
+
{
|
|
75068
|
+
label: "Domains",
|
|
75069
|
+
permissions: ["domains:read", "domains:manage"]
|
|
75070
|
+
},
|
|
75071
|
+
{
|
|
75072
|
+
label: "Account",
|
|
75073
|
+
permissions: [
|
|
75074
|
+
"org:read",
|
|
75075
|
+
"account:manage",
|
|
75076
|
+
"account:claim",
|
|
75077
|
+
"credentials:revoke_self"
|
|
75078
|
+
]
|
|
75079
|
+
},
|
|
75080
|
+
{
|
|
75081
|
+
label: "Own activity",
|
|
75082
|
+
permissions: ["activity:read_self"]
|
|
75083
|
+
},
|
|
75084
|
+
{
|
|
75085
|
+
label: "Webhook signing secret",
|
|
75086
|
+
permissions: ["webhook_secret:read", "webhook_secret:manage"]
|
|
75087
|
+
},
|
|
75088
|
+
{
|
|
75089
|
+
label: "Functions",
|
|
75090
|
+
permissions: [
|
|
75091
|
+
"functions:read",
|
|
75092
|
+
"functions:manage",
|
|
75093
|
+
"functions:deploy",
|
|
75094
|
+
"github:manage",
|
|
75095
|
+
"templates:install"
|
|
75096
|
+
]
|
|
75097
|
+
},
|
|
75098
|
+
{
|
|
75099
|
+
label: "Secrets",
|
|
75100
|
+
permissions: ["secrets:manage"]
|
|
75101
|
+
},
|
|
75102
|
+
{
|
|
75103
|
+
label: "Contacts",
|
|
75104
|
+
permissions: [
|
|
75105
|
+
"contacts:read",
|
|
75106
|
+
"contacts:write",
|
|
75107
|
+
"contacts:delete",
|
|
75108
|
+
"agent_contacts:manage"
|
|
75109
|
+
]
|
|
75110
|
+
},
|
|
75111
|
+
{
|
|
75112
|
+
label: "Address notes",
|
|
75113
|
+
permissions: [
|
|
75114
|
+
"notes:read",
|
|
75115
|
+
"notes:write",
|
|
75116
|
+
"notes:delete"
|
|
75117
|
+
]
|
|
75118
|
+
},
|
|
75119
|
+
{
|
|
75120
|
+
label: "Memories",
|
|
75121
|
+
permissions: ["memories:read", "memories:write"]
|
|
75122
|
+
},
|
|
75123
|
+
{
|
|
75124
|
+
label: "Payloads",
|
|
75125
|
+
permissions: ["payloads:read", "payloads:write"]
|
|
75126
|
+
},
|
|
75127
|
+
{
|
|
75128
|
+
label: "Interactions",
|
|
75129
|
+
permissions: [
|
|
75130
|
+
"interactions:read",
|
|
75131
|
+
"interactions:send",
|
|
75132
|
+
"interactions:advance"
|
|
75133
|
+
]
|
|
75134
|
+
},
|
|
75135
|
+
{
|
|
75136
|
+
label: "Wake",
|
|
75137
|
+
permissions: ["wake:manage"]
|
|
75138
|
+
},
|
|
75139
|
+
{
|
|
75140
|
+
label: "Credits",
|
|
75141
|
+
permissions: ["credits:read", "credits:redeem"]
|
|
75142
|
+
},
|
|
75143
|
+
{
|
|
75144
|
+
label: "Push devices",
|
|
75145
|
+
permissions: ["push_devices:manage"]
|
|
75146
|
+
}
|
|
75147
|
+
];
|
|
75148
|
+
/** Held only by a key that reaches every mailbox (--all-mailboxes). */
|
|
75149
|
+
const EVERY_MAILBOX_KEY_PERMISSIONS = ["members:read"];
|
|
75150
|
+
/** The permissions a key can hold, by area, for --help-full. */
|
|
75151
|
+
function keyPermissionsHelp() {
|
|
75152
|
+
return `Permissions a key can hold, by area, as this CLI version lists them (the API decides, and a key never holds more than you do): ${KEY_PERMISSION_AREAS.map((area) => `${area.label}: ${area.permissions.join(", ")}.`).join(" ")} Only a key that reaches every mailbox (--all-mailboxes) can also hold ${EVERY_MAILBOX_KEY_PERMISSIONS.join(", ")}.`;
|
|
75153
|
+
}
|
|
75154
|
+
/** How long the refusal hint waits on the permission check. */
|
|
75155
|
+
const CHECK_TIMEOUT_MS = 5e3;
|
|
75156
|
+
/**
|
|
75157
|
+
* The permission a refusal says cannot be granted to a key, or null for any
|
|
75158
|
+
* other refusal.
|
|
75159
|
+
*/
|
|
75160
|
+
function ungrantablePermission(payload) {
|
|
75161
|
+
const error = extractErrorPayload(payload);
|
|
75162
|
+
if (extractErrorCode(error) !== "validation_error") return null;
|
|
75163
|
+
const message = error && typeof error === "object" ? error.message : void 0;
|
|
75164
|
+
if (typeof message !== "string") return null;
|
|
75165
|
+
return /^(.+) cannot be granted to a key$/.exec(message.trim())?.[1] ?? null;
|
|
75166
|
+
}
|
|
75167
|
+
/**
|
|
75168
|
+
* Whether the API knows `name` as a permission at all: GET /whoami?check
|
|
75169
|
+
* answers a check for any permission it knows and refuses any other name
|
|
75170
|
+
* with "unknown permission". Null when the answer says neither, or does not
|
|
75171
|
+
* come in time: the hint never holds the command up for longer than that.
|
|
75172
|
+
*/
|
|
75173
|
+
async function isKnownPermission(client, name) {
|
|
75174
|
+
let timer;
|
|
75175
|
+
const late = new Promise((resolve) => {
|
|
75176
|
+
timer = setTimeout(() => resolve(null), CHECK_TIMEOUT_MS);
|
|
75177
|
+
});
|
|
75178
|
+
try {
|
|
75179
|
+
return await Promise.race([checkPermission(client, name), late]);
|
|
75180
|
+
} finally {
|
|
75181
|
+
clearTimeout(timer);
|
|
75182
|
+
}
|
|
75183
|
+
}
|
|
75184
|
+
async function checkPermission(client, name) {
|
|
75185
|
+
try {
|
|
75186
|
+
const { identity, error } = await fetchCallerIdentity(client, { check: name }, {
|
|
75187
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
75188
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
|
|
75189
|
+
});
|
|
75190
|
+
if (error) {
|
|
75191
|
+
const payload = extractErrorPayload(error);
|
|
75192
|
+
const message = payload && typeof payload === "object" ? payload.message : void 0;
|
|
75193
|
+
return extractErrorCode(payload) === "validation_error" && typeof message === "string" && message.startsWith("unknown permission") ? false : null;
|
|
75194
|
+
}
|
|
75195
|
+
return identity?.check ? true : null;
|
|
75196
|
+
} catch {
|
|
75197
|
+
return null;
|
|
75198
|
+
}
|
|
75199
|
+
}
|
|
75200
|
+
/**
|
|
75201
|
+
* The hint after "<name> cannot be granted to a key": whether the name is
|
|
75202
|
+
* not a permission at all or is one a key cannot hold, then what a key can
|
|
75203
|
+
* hold, as this CLI lists it. `asked` is the permissions the command asked
|
|
75204
|
+
* for, when the request also carried others (an update sends the key's own
|
|
75205
|
+
* permissions too), so a refusal of one the key already holds says so.
|
|
75206
|
+
*/
|
|
75207
|
+
async function ungrantablePermissionHint(client, name, asked) {
|
|
75208
|
+
const known = await isKnownPermission(client, name);
|
|
75209
|
+
const what = known === false ? `${name} is not a permission.` : known === true ? `${name} is a permission, but an API key cannot hold it.` : `${name} is not a permission an API key can hold.`;
|
|
75210
|
+
if (asked && !asked.includes(name)) return `Hint: ${what} The key already holds it, so this change cannot keep it: take it off with --remove ${name}.`;
|
|
75211
|
+
if (KEY_PERMISSION_AREAS.some((area) => area.permissions.includes(name)) || EVERY_MAILBOX_KEY_PERMISSIONS.includes(name)) return `Hint: ${what} This CLI's list of the permissions a key can hold still names it, so the list is older than the API's: update the CLI.`;
|
|
75212
|
+
return `Hint: ${what} As this CLI version lists them, a key can hold ${KEY_PERMISSION_AREAS.flatMap((area) => area.permissions).join(", ")}, and, only when it reaches every mailbox, ${EVERY_MAILBOX_KEY_PERMISSIONS.join(", ")}. \`primitive keys create --help-full\` lists them by area.`;
|
|
75213
|
+
}
|
|
73746
75214
|
// src/oclif/commands/keys.ts
|
|
73747
75215
|
const STORE_SECRET_NOTICE = "Store this key now. It is shown only once, and the CLI does not save it.";
|
|
73748
75216
|
const HELD_REQUEST_NOTICE = "A request already held open with this key, such as a long poll or a wait, ends once because the key's permissions changed. Run it again to use the new permissions.";
|
|
@@ -73873,11 +75341,12 @@ function describePermissions(grant) {
|
|
|
73873
75341
|
const permissions = grant.permissions ?? [];
|
|
73874
75342
|
return permissions.length > 0 ? permissions.join(", ") : "none";
|
|
73875
75343
|
}
|
|
73876
|
-
function formatKeyRows(rows) {
|
|
73877
|
-
if (rows.length === 0) return "No API keys.";
|
|
75344
|
+
function formatKeyRows(rows, options = {}) {
|
|
75345
|
+
if (rows.length === 0) return options.includeRevoked ? "No API keys, revoked or not." : "No API keys. Revoked keys are left out; pass --include-revoked to list them too.";
|
|
73878
75346
|
return rows.map((row) => {
|
|
73879
|
-
const suspended = row.suspended ? " [suspended]" : "";
|
|
73880
|
-
|
|
75347
|
+
const suspended = row.suspended && row.status !== "revoked" ? " [suspended]" : "";
|
|
75348
|
+
const revoked = row.status === "revoked" ? ` [revoked${row.revoked_at ? ` ${row.revoked_at}` : ""}]` : "";
|
|
75349
|
+
return `${row.id} ${row.key_prefix} ${row.name ?? "(unnamed)"} ${describePermissions(row.grant)} mail: ${describeMailScope(row.grant)} expires ${row.expires_at ?? "never"} last used ${row.last_used_at ?? "never"}${suspended}${revoked}`;
|
|
73881
75350
|
}).join("\n");
|
|
73882
75351
|
}
|
|
73883
75352
|
function formatKeyDetail(key) {
|
|
@@ -74131,9 +75600,11 @@ async function authenticate(command, flags) {
|
|
|
74131
75600
|
});
|
|
74132
75601
|
}
|
|
74133
75602
|
/** Print a refusal and set a failing exit code. */
|
|
74134
|
-
function reportKeysError(command, outcome, auth, json) {
|
|
75603
|
+
async function reportKeysError(command, outcome, auth, json, askedPermissions) {
|
|
74135
75604
|
if (json) process.stderr.write(`${JSON.stringify(outcome.payload)}\n`);
|
|
74136
75605
|
else writeErrorWithHints(outcome.payload);
|
|
75606
|
+
const ungrantable = ungrantablePermission(outcome.payload);
|
|
75607
|
+
if (ungrantable) process.stderr.write(`${await ungrantablePermissionHint(auth.apiClient.client, ungrantable, askedPermissions)}\n`);
|
|
74137
75608
|
if (outcome.stepUpUrl) process.stderr.write(approveThenRerunHint(outcome.stepUpUrl));
|
|
74138
75609
|
surfaceUnauthorizedHint({
|
|
74139
75610
|
auth: auth.auth,
|
|
@@ -74149,26 +75620,37 @@ function writeWarnings(warnings) {
|
|
|
74149
75620
|
var KeysListCommand = class KeysListCommand extends Command {
|
|
74150
75621
|
static description = `List the organization's API keys with their permissions, mail scope, expiry and last use. Only metadata is shown, never a secret.
|
|
74151
75622
|
|
|
74152
|
-
You see your own keys, and every key if your role may read them all, as in the dashboard
|
|
75623
|
+
You see your own keys, and every key if your role may read them all, as in the dashboard.
|
|
75624
|
+
|
|
75625
|
+
Revoked keys are left out unless you pass --include-revoked, which lists them too, each marked [revoked] with when it was revoked. With --json, a revoked key has status "revoked", revoked_at and revoked_by. That confirms a revocation without having noted the key ids beforehand.`;
|
|
74153
75626
|
static summary = "List API keys";
|
|
74154
|
-
static examples = [
|
|
75627
|
+
static examples = [
|
|
75628
|
+
"<%= config.bin %> keys list",
|
|
75629
|
+
"<%= config.bin %> keys list --json",
|
|
75630
|
+
"<%= config.bin %> keys list --include-revoked"
|
|
75631
|
+
];
|
|
74155
75632
|
static flags = {
|
|
74156
75633
|
"api-key": API_KEY_FLAG$1,
|
|
74157
75634
|
"api-base-url": API_BASE_URL_FLAG$2,
|
|
75635
|
+
"include-revoked": Flags.boolean({ description: "List revoked keys too, each marked [revoked] with when it was revoked." }),
|
|
74158
75636
|
json: Flags.boolean({ description: "Print the keys as JSON." })
|
|
74159
75637
|
};
|
|
74160
75638
|
async run() {
|
|
74161
75639
|
const { flags } = await this.parse(KeysListCommand);
|
|
75640
|
+
const includeRevoked = flags["include-revoked"] === true;
|
|
74162
75641
|
const auth = await authenticate(this, flags);
|
|
74163
75642
|
const items = [];
|
|
74164
75643
|
let cursor;
|
|
74165
75644
|
for (let page = 0; page < LIST_PAGE_LIMIT; page += 1) {
|
|
74166
75645
|
const result = await call(listKeys({
|
|
74167
75646
|
client: auth.apiClient.client,
|
|
74168
|
-
|
|
75647
|
+
query: {
|
|
75648
|
+
cursor,
|
|
75649
|
+
include_revoked: includeRevoked ? "true" : void 0
|
|
75650
|
+
}
|
|
74169
75651
|
}));
|
|
74170
75652
|
if (!result.ok) {
|
|
74171
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75653
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74172
75654
|
return;
|
|
74173
75655
|
}
|
|
74174
75656
|
items.push(...result.data.items);
|
|
@@ -74179,7 +75661,7 @@ var KeysListCommand = class KeysListCommand extends Command {
|
|
|
74179
75661
|
this.log(JSON.stringify(items, null, 2));
|
|
74180
75662
|
return;
|
|
74181
75663
|
}
|
|
74182
|
-
this.log(formatKeyRows(items));
|
|
75664
|
+
this.log(formatKeyRows(items, { includeRevoked }));
|
|
74183
75665
|
}
|
|
74184
75666
|
};
|
|
74185
75667
|
var KeysGetCommand = class KeysGetCommand extends Command {
|
|
@@ -74203,7 +75685,7 @@ var KeysGetCommand = class KeysGetCommand extends Command {
|
|
|
74203
75685
|
path: { id: args.id }
|
|
74204
75686
|
}));
|
|
74205
75687
|
if (!result.ok) {
|
|
74206
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75688
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74207
75689
|
return;
|
|
74208
75690
|
}
|
|
74209
75691
|
this.log(flags.json ? JSON.stringify(result.data, null, 2) : formatKeyDetail(result.data));
|
|
@@ -74229,7 +75711,9 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74229
75711
|
|
|
74230
75712
|
The secret is printed once, on its own line on stdout (in the JSON with --json). Store it right away: it is not shown again and the CLI does not save it.
|
|
74231
75713
|
|
|
74232
|
-
A key limited to email rules (--rule, repeatable) reaches only the mail those rules match. Give it mail:read with --permission, or use --preset own_addresses_read_only
|
|
75714
|
+
A key limited to email rules (--rule, repeatable) reaches only the mail those rules match. Give it mail:read with --permission, or use --preset own_addresses_read_only.
|
|
75715
|
+
|
|
75716
|
+
${keyPermissionsHelp()}`;
|
|
74233
75717
|
static summary = "Create an API key";
|
|
74234
75718
|
static examples = [
|
|
74235
75719
|
"<%= config.bin %> keys create --name reader --preset read_only",
|
|
@@ -74246,7 +75730,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74246
75730
|
options: [...KEY_PRESETS]
|
|
74247
75731
|
}),
|
|
74248
75732
|
permission: Flags.string({
|
|
74249
|
-
description: "A permission the key holds (repeatable, or comma-separated), for a custom key.",
|
|
75733
|
+
description: "A permission the key holds (repeatable, or comma-separated), for a custom key. See --help-full for the permissions a key can hold.",
|
|
74250
75734
|
multiple: true
|
|
74251
75735
|
}),
|
|
74252
75736
|
address: Flags.string({
|
|
@@ -74301,7 +75785,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74301
75785
|
}
|
|
74302
75786
|
})), approvalIo(flags));
|
|
74303
75787
|
if (!result.ok) {
|
|
74304
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75788
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74305
75789
|
return;
|
|
74306
75790
|
}
|
|
74307
75791
|
const key = result.data;
|
|
@@ -74372,7 +75856,7 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
|
|
|
74372
75856
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74373
75857
|
})), approvalIo(flags));
|
|
74374
75858
|
if (!result.ok) {
|
|
74375
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75859
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74376
75860
|
return;
|
|
74377
75861
|
}
|
|
74378
75862
|
const rotated = result.data;
|
|
@@ -74382,7 +75866,7 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
|
|
|
74382
75866
|
var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
74383
75867
|
static description = `Change an API key's permissions, email rules, name or expiry. It keeps its id and secret.
|
|
74384
75868
|
|
|
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.
|
|
75869
|
+
--add and --remove take permissions (repeatable, or comma-separated); \`primitive keys create --help-full\` lists the permissions a key can hold. --add-rule and --remove-rule add email rules to the key or take them off, and keep its other rules. --rule replaces every rule the key has with the ones given, so pass all the rules it should keep; it cannot be combined with --add-rule or --remove-rule. --expires-at gives it a new expiry: a later one extends it, within your organization's credential policy, and is how to keep a key working past its expiry (rotating keeps the expiry). --no-expiry removes it where the policy allows. An expired key cannot be extended. Everything else about the key stays as it is, and the output lists any rules a change took off.
|
|
74386
75870
|
|
|
74387
75871
|
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.
|
|
74388
75872
|
|
|
@@ -74412,7 +75896,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74412
75896
|
multiple: true
|
|
74413
75897
|
}),
|
|
74414
75898
|
rule: Flags.string({
|
|
74415
|
-
description: "
|
|
75899
|
+
description: "Replaces the key's rules with these email rule ids. Pass every rule it should keep (repeatable, or comma-separated), or use --add-rule and --remove-rule, which keep its other rules.",
|
|
74416
75900
|
multiple: true,
|
|
74417
75901
|
exclusive: ["add-rule", "remove-rule"]
|
|
74418
75902
|
}),
|
|
@@ -74470,7 +75954,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74470
75954
|
path: { id: args.id }
|
|
74471
75955
|
}));
|
|
74472
75956
|
if (!current.ok) {
|
|
74473
|
-
reportKeysError(this, current, auth, Boolean(flags.json));
|
|
75957
|
+
await reportKeysError(this, current, auth, Boolean(flags.json));
|
|
74474
75958
|
return;
|
|
74475
75959
|
}
|
|
74476
75960
|
if (current.data.status === "revoked") this.error(`Key ${args.id} is revoked, so it cannot be changed.`, { exit: 1 });
|
|
@@ -74511,7 +75995,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74511
75995
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74512
75996
|
})), approvalIo(flags));
|
|
74513
75997
|
if (!result.ok) {
|
|
74514
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75998
|
+
await reportKeysError(this, result, auth, Boolean(flags.json), add);
|
|
74515
75999
|
return;
|
|
74516
76000
|
}
|
|
74517
76001
|
const grantChanged = result.data.changed.includes("grant");
|
|
@@ -74554,8 +76038,82 @@ function standingSummary(standing) {
|
|
|
74554
76038
|
const kept = standing.length - keyOwned;
|
|
74555
76039
|
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
76040
|
}
|
|
76041
|
+
/** How many standing objects the summary lists one by one. */
|
|
76042
|
+
const STANDING_LIST_LIMIT = 10;
|
|
76043
|
+
const STANDING_KIND_LABELS = {
|
|
76044
|
+
endpoint: "webhook",
|
|
76045
|
+
function_trigger: "Function trigger",
|
|
76046
|
+
pull_subscription: "pull subscription",
|
|
76047
|
+
recipient_route: "route",
|
|
76048
|
+
filter_rule: "filter",
|
|
76049
|
+
wake_schedule: "wake schedule"
|
|
76050
|
+
};
|
|
76051
|
+
/** Whether a revoke answer says, per item, what still acts for it or delivers. */
|
|
76052
|
+
function reportsCascade(standing) {
|
|
76053
|
+
return standing.some((item) => "still_acting" in item || "delivering" in item);
|
|
76054
|
+
}
|
|
76055
|
+
/**
|
|
76056
|
+
* A standing object's label. A Function trigger's label is only "function" and
|
|
76057
|
+
* an unnamed pull subscription's only "pull", so those carry their id; a
|
|
76058
|
+
* webhook's URL loses its query and fragment, which can hold a token.
|
|
76059
|
+
*/
|
|
76060
|
+
function standingLabel(item) {
|
|
76061
|
+
const id = typeof item.id === "string" ? item.id : String(item.id);
|
|
76062
|
+
const label = typeof item.label === "string" && item.label ? item.label : id;
|
|
76063
|
+
if (item.kind === "function_trigger") return id;
|
|
76064
|
+
if (item.kind === "pull_subscription") return label === id || label === "pull" ? id : `${label} (${id})`;
|
|
76065
|
+
if (item.kind === "endpoint") {
|
|
76066
|
+
const cut = label.search(/[?#]/);
|
|
76067
|
+
return cut >= 0 ? `${label.slice(0, cut)}?...` : label;
|
|
76068
|
+
}
|
|
76069
|
+
return label;
|
|
76070
|
+
}
|
|
76071
|
+
/** One standing object, with who still acts for it and whether it delivers. */
|
|
76072
|
+
function standingLine(item, leftWorking) {
|
|
76073
|
+
const kind = typeof item.kind === "string" ? STANDING_KIND_LABELS[item.kind] ?? item.kind : "item";
|
|
76074
|
+
let acting;
|
|
76075
|
+
if (item.org_owned !== false) acting = "the organization owns it, so revoking does not stop it";
|
|
76076
|
+
else if (item.still_acting === true) {
|
|
76077
|
+
const by = typeof item.acting_key_id === "string" && item.acting_key_id ? item.acting_key_id : null;
|
|
76078
|
+
acting = !by ? "owned by the revoked key; someone still acts for it, so it keeps working while they do" : leftWorking.has(by) ? `owned by the revoked key; key ${by} still acts for it until that key's overlap ends` : `owned by the revoked key; key ${by} still acts for it, so it keeps working while that key does`;
|
|
76079
|
+
} else if (item.still_acting === false) acting = "owned by the revoked key; nothing acts for it, so it does not work";
|
|
76080
|
+
else if (item.still_acting === null) acting = "owned by the revoked key; whether anything still acts for it could not be read, and it works only while something does";
|
|
76081
|
+
else acting = "owned by the revoked key; it keeps working only while someone can still act for it";
|
|
76082
|
+
const delivering = item.delivering === true ? "; still delivering received mail" : item.delivering === false ? "; not delivering received mail" : item.delivering === null ? "; whether it still delivers could not be read" : "";
|
|
76083
|
+
return ` ${kind} ${standingLabel(item)}: ${acting}${delivering}.`;
|
|
76084
|
+
}
|
|
76085
|
+
/**
|
|
76086
|
+
* What a revoke did beyond the key itself, from the answer: the keys it
|
|
76087
|
+
* replaced that were revoked with it or left working, and, for each item the
|
|
76088
|
+
* key set up or owns, whether anything still acts for it and whether it still
|
|
76089
|
+
* delivers. Lines, empty when the answer has none of these. An answer from a
|
|
76090
|
+
* server that does not report them per item falls back to standingSummary.
|
|
76091
|
+
*/
|
|
76092
|
+
function revokeCascadeLines(data) {
|
|
76093
|
+
if (!data || typeof data !== "object") return [];
|
|
76094
|
+
const answer = data;
|
|
76095
|
+
const lines = [];
|
|
76096
|
+
const ids = (value) => Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
|
|
76097
|
+
const named = (list) => list.length > 0 ? `: ${list.join(", ")}` : "";
|
|
76098
|
+
const revoked = answer.predecessors_revoked_count;
|
|
76099
|
+
if (typeof revoked === "number" && revoked > 0) lines.push(`Also revoked ${revoked === 1 ? "1 key" : `${revoked} keys`} it replaced, still in ${revoked === 1 ? "its" : "their"} rotation overlap${named(ids(answer.predecessors_revoked))}.`);
|
|
76100
|
+
const left = answer.predecessors_left;
|
|
76101
|
+
if (typeof left === "number" && left > 0) lines.push(`${left === 1 ? "1 key" : `${left} keys`} it replaced ${left === 1 ? "is" : "are"} left working until ${left === 1 ? "its" : "their"} overlap ends, because you may not revoke ${left === 1 ? "it" : "them"}${named(ids(answer.predecessors_not_revoked))}; what ${left === 1 ? "it acts" : "they act"} for keeps working until then.`);
|
|
76102
|
+
const leftWorking = new Set(ids(answer.predecessors_not_revoked));
|
|
76103
|
+
const standing = Array.isArray(answer.standing_objects) ? answer.standing_objects.filter((item) => item !== null && typeof item === "object") : [];
|
|
76104
|
+
if (standing.length > 0 && reportsCascade(standing)) {
|
|
76105
|
+
lines.push("What it set up, owns or carried on for the keys it replaced (revoking deletes none of it):");
|
|
76106
|
+
for (const item of standing.slice(0, STANDING_LIST_LIMIT)) lines.push(standingLine(item, leftWorking));
|
|
76107
|
+
const more = standing.length - STANDING_LIST_LIMIT;
|
|
76108
|
+
if (more > 0) lines.push(` ${more} more; --json lists them all.`);
|
|
76109
|
+
} else {
|
|
76110
|
+
const summary = standingSummary(answer.standing_objects);
|
|
76111
|
+
if (summary) lines.push(summary);
|
|
76112
|
+
}
|
|
76113
|
+
return lines;
|
|
76114
|
+
}
|
|
74557
76115
|
var KeysRevokeCommand = class KeysRevokeCommand extends Command {
|
|
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.
|
|
76116
|
+
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, each with whether anything still acts for it and, for a webhook, whether it still delivers received mail. Keys it replaced that are still in their rotation overlap are revoked with it, except any you may not revoke; the output counts both, and names them and the key acting for each item when you may read the organization's keys.
|
|
74559
76117
|
|
|
74560
76118
|
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.`;
|
|
74561
76119
|
static summary = "Revoke an API key";
|
|
@@ -74597,16 +76155,17 @@ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
|
|
|
74597
76155
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74598
76156
|
})), approvalIo(flags));
|
|
74599
76157
|
if (!result.ok) {
|
|
74600
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
76158
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74601
76159
|
return;
|
|
74602
76160
|
}
|
|
74603
76161
|
if (flags.json) {
|
|
74604
76162
|
this.log(JSON.stringify(result.data, null, 2));
|
|
74605
76163
|
return;
|
|
74606
76164
|
}
|
|
74607
|
-
|
|
74608
|
-
const
|
|
74609
|
-
|
|
76165
|
+
const replaced = result.data.predecessors_revoked_count;
|
|
76166
|
+
const alsoRevoked = typeof replaced === "number" && replaced > 0;
|
|
76167
|
+
this.log(result.data.already_revoked === true ? alsoRevoked ? `Key ${args.id} was already revoked.` : `Key ${args.id} was already revoked; nothing changed.` : `Revoked key ${args.id}.`);
|
|
76168
|
+
for (const line of revokeCascadeLines(result.data)) this.log(line);
|
|
74610
76169
|
}
|
|
74611
76170
|
};
|
|
74612
76171
|
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
@@ -83161,7 +84720,9 @@ var SendCommand = class SendCommand extends Command {
|
|
|
83161
84720
|
\`primitive account whoami\` reports) when you may send from it; a key
|
|
83162
84721
|
whose grant names exactly one address sends from that address. Otherwise
|
|
83163
84722
|
there is no default: the command lists the addresses you may send from
|
|
83164
|
-
and asks for --from.
|
|
84723
|
+
and asks for --from. A credential without mail:send is refused with
|
|
84724
|
+
grant_forbidden naming mail:send instead, since no --from would help.
|
|
84725
|
+
Without --json, stderr names the From it used.
|
|
83165
84726
|
--subject defaults to the first line of the body when omitted.
|
|
83166
84727
|
--attachment attaches a file; repeat it to attach multiple files.
|
|
83167
84728
|
--header adds a custom header such as "X-Campaign-Id: spring-sale";
|
|
@@ -83216,7 +84777,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
83216
84777
|
description: "Recipient address (e.g. alice@example.com).",
|
|
83217
84778
|
required: true
|
|
83218
84779
|
}),
|
|
83219
|
-
from: Flags.string({ description: "Sender address. Without it: a connected agent's own address, your member address on your own login, or a key's one granted address; otherwise the command asks for --from." }),
|
|
84780
|
+
from: Flags.string({ description: "Sender address. Without it: a connected agent's own address, your member address on your own login, or a key's one granted address; otherwise the command asks for --from, except that a credential without mail:send is refused by that permission." }),
|
|
83220
84781
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
83221
84782
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
83222
84783
|
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. This does not attach the file; use --attachment for file attachments. Mutually exclusive with --body and --body-stdin." }),
|
|
@@ -84625,6 +86186,11 @@ const WAIT_EXIT_CODES = {
|
|
|
84625
86186
|
const MAX_LONG_POLL_SECONDS = 30;
|
|
84626
86187
|
const TAIL_PAGE_SIZE = 100;
|
|
84627
86188
|
const EXISTING_PAGE_SIZE = 50;
|
|
86189
|
+
/**
|
|
86190
|
+
* Pages of mail made visible since the start that are read before the
|
|
86191
|
+
* long-poll; any more are walked by the long-poll itself.
|
|
86192
|
+
*/
|
|
86193
|
+
const SINCE_START_PAGES = 5;
|
|
84628
86194
|
const PRINTED_BODY_CHARS = 1200;
|
|
84629
86195
|
const JSON_TEXT_CHARS = 4e3;
|
|
84630
86196
|
/**
|
|
@@ -84765,9 +86331,20 @@ async function firstMatch(api, apiClient, rows, filters, seen) {
|
|
|
84765
86331
|
}
|
|
84766
86332
|
return null;
|
|
84767
86333
|
}
|
|
84768
|
-
|
|
84769
|
-
|
|
84770
|
-
|
|
86334
|
+
/**
|
|
86335
|
+
* The forward-tail cursor for an instant, on the client clock: the tail
|
|
86336
|
+
* returns what became visible after it.
|
|
86337
|
+
*/
|
|
86338
|
+
function tailCursorAt(at) {
|
|
86339
|
+
return `${new Date(at).toISOString().replace(/Z$/, "000Z")}|${TIME_CURSOR_ID}`;
|
|
86340
|
+
}
|
|
86341
|
+
/**
|
|
86342
|
+
* Whether tail cursor `a` sorts before `b`. A cursor's time is microseconds
|
|
86343
|
+
* from the tail and milliseconds from a list row, so it is padded first.
|
|
86344
|
+
*/
|
|
86345
|
+
function cursorBefore(a, b) {
|
|
86346
|
+
const pad = (cursor) => cursor.replace(/(\.\d{3})Z\|/, "$1000Z|");
|
|
86347
|
+
return pad(a) < pad(b);
|
|
84771
86348
|
}
|
|
84772
86349
|
/**
|
|
84773
86350
|
* Block until an email matching `filters` arrives (or, with
|
|
@@ -84795,11 +86372,21 @@ async function tailForEmail(api, params) {
|
|
|
84795
86372
|
apiClient,
|
|
84796
86373
|
api
|
|
84797
86374
|
});
|
|
84798
|
-
|
|
84799
|
-
|
|
84800
|
-
|
|
86375
|
+
let fromStart = tailCursorAt(params.startedAt);
|
|
86376
|
+
let caughtUp = false;
|
|
86377
|
+
for (let read = 0; read < SINCE_START_PAGES && !caughtUp; read += 1) {
|
|
86378
|
+
const page = await listRows(api, apiClient, {
|
|
86379
|
+
limit: TAIL_PAGE_SIZE,
|
|
86380
|
+
since: fromStart
|
|
86381
|
+
});
|
|
86382
|
+
const early = await firstMatch(api, apiClient, page.rows, filters, seen);
|
|
86383
|
+
if (early) return early;
|
|
86384
|
+
if (page.rows.length < TAIL_PAGE_SIZE || !page.cursor) caughtUp = true;
|
|
86385
|
+
else fromStart = page.cursor;
|
|
86386
|
+
}
|
|
86387
|
+
if (!caughtUp && cursorBefore(fromStart, since)) since = fromStart;
|
|
84801
86388
|
if (params.includeExisting) {
|
|
84802
|
-
const found = await firstMatch(api, apiClient,
|
|
86389
|
+
const found = await firstMatch(api, apiClient, (await listRows(api, apiClient, { limit: EXISTING_PAGE_SIZE })).rows, filters, seen);
|
|
84803
86390
|
if (found) return found;
|
|
84804
86391
|
}
|
|
84805
86392
|
for (;;) {
|
|
@@ -85592,265 +87179,6 @@ var WakeSchedulesUpdateCommand = class WakeSchedulesUpdateCommand extends Comman
|
|
|
85592
87179
|
});
|
|
85593
87180
|
}
|
|
85594
87181
|
};
|
|
85595
|
-
// src/oclif/commands/whoami.ts
|
|
85596
|
-
/**
|
|
85597
|
-
* `GET /whoami` with its grant query parameters. A server without grants
|
|
85598
|
-
* ignores them and answers without `grant` or `check`.
|
|
85599
|
-
*/
|
|
85600
|
-
async function fetchCallerIdentity(client, query) {
|
|
85601
|
-
const result = await client.get({
|
|
85602
|
-
query,
|
|
85603
|
-
responseStyle: "fields",
|
|
85604
|
-
security: [{
|
|
85605
|
-
scheme: "bearer",
|
|
85606
|
-
type: "http"
|
|
85607
|
-
}],
|
|
85608
|
-
url: "/whoami"
|
|
85609
|
-
});
|
|
85610
|
-
if (result.error) return {
|
|
85611
|
-
identity: null,
|
|
85612
|
-
error: result.error
|
|
85613
|
-
};
|
|
85614
|
-
return {
|
|
85615
|
-
identity: result.data?.data ?? null,
|
|
85616
|
-
error: null
|
|
85617
|
-
};
|
|
85618
|
-
}
|
|
85619
|
-
function formatGrant(grant) {
|
|
85620
|
-
const lines = [`Grant: ${grant.template ? `template ${grant.template}` : `${grant.grant_type ?? "grant"} with ${grant.permissions.length} permissions`}, ${grant.mail_scope === "org" || grant.mail_scope === void 0 ? "all mail in the organization" : "a narrowed mail scope"} (${grant.credential_kind})`];
|
|
85621
|
-
if (grant.expires_at) lines.push(`Credential expires: ${grant.expires_at}`);
|
|
85622
|
-
return lines;
|
|
85623
|
-
}
|
|
85624
|
-
/** Lines describing the caller's organization, role and grant. */
|
|
85625
|
-
function formatGrantLines(identity, options = {}) {
|
|
85626
|
-
const lines = [];
|
|
85627
|
-
const org = options.orgName ? `${options.orgName} (${identity.org_id})` : identity.org_id;
|
|
85628
|
-
lines.push(`Organization: ${org}`);
|
|
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}`);
|
|
85630
|
-
const grant = identity.grant;
|
|
85631
|
-
if (grant) lines.push(...formatGrant(grant));
|
|
85632
|
-
if (options.userLogin) lines.push("Signed in as yourself. Switch organizations with `primitive org use <org>`.");
|
|
85633
|
-
return lines;
|
|
85634
|
-
}
|
|
85635
|
-
function formatPermissionCheck(check) {
|
|
85636
|
-
if (check.allow) return `${check.permission}: allowed`;
|
|
85637
|
-
return `${check.permission}: not allowed${check.reason ? ` (${check.reason})` : ""}`;
|
|
85638
|
-
}
|
|
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) {
|
|
85661
|
-
const lines = [
|
|
85662
|
-
identityLine(identity),
|
|
85663
|
-
orgName ? `Organization: ${orgName} (${account.id})` : `Organization id: ${account.id}`,
|
|
85664
|
-
...account.email ? [`Account email: ${account.email} (the email the account was opened with)`] : [],
|
|
85665
|
-
`Plan: ${account.plan}`
|
|
85666
|
-
];
|
|
85667
|
-
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
85668
|
-
return lines.join("\n");
|
|
85669
|
-
}
|
|
85670
|
-
/** `GET /whoami/grant`, which any credential may ask about itself. */
|
|
85671
|
-
async function fetchOwnGrant(client) {
|
|
85672
|
-
try {
|
|
85673
|
-
const result = await getOwnGrant({
|
|
85674
|
-
client,
|
|
85675
|
-
responseStyle: "fields"
|
|
85676
|
-
});
|
|
85677
|
-
if (result.error) return null;
|
|
85678
|
-
return result.data?.data ?? null;
|
|
85679
|
-
} catch {
|
|
85680
|
-
return null;
|
|
85681
|
-
}
|
|
85682
|
-
}
|
|
85683
|
-
/** The summary for a valid credential whose grant cannot read the account. */
|
|
85684
|
-
function formatOwnGrantSummary(own, orgName) {
|
|
85685
|
-
return [
|
|
85686
|
-
"Authenticated. This credential cannot read the account (it lacks org:read).",
|
|
85687
|
-
orgName ? `Organization: ${orgName} (${own.org_id})` : `Organization id: ${own.org_id}`,
|
|
85688
|
-
...own.grant ? formatGrant(own.grant) : []
|
|
85689
|
-
].join("\n");
|
|
85690
|
-
}
|
|
85691
|
-
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
85692
|
-
static description = `Print the account currently authenticated by saved OAuth credentials or an explicit API key. For account credentials this is a live credentials smoke test. Use account whoami for the authenticated caller, their assigned member address and an address suggestion; account provision-member-address --address <email> saves your chosen personal address.
|
|
85693
|
-
|
|
85694
|
-
With PRIMITIVE_AGENT_PROFILE selected, print the saved connected-agent identity offline, with no API call. The first line is the agent's email address alone; --json adds address, profile and source (session_hook when the selected profile is this Claude Code session's own agent exported by the session start hook, env otherwise). This does not verify live authentication, app pairing, or listener readiness. Inspect the receiver separately: the output names the command for the profile's receiver mode (agent connect --status for Claude hook and poll receivers, listen --status --notify-session for a native listener).
|
|
85695
|
-
|
|
85696
|
-
The default output is a concise human summary. Pass --json for the full /account response or the explicit offline connected-agent identity envelope.`;
|
|
85697
|
-
static summary = "Print the live account or saved connected-agent identity";
|
|
85698
|
-
static examples = [
|
|
85699
|
-
"<%= config.bin %> whoami",
|
|
85700
|
-
"<%= config.bin %> whoami --api-key prim_...",
|
|
85701
|
-
"<%= config.bin %> whoami --json | jq .id",
|
|
85702
|
-
"PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> whoami --json"
|
|
85703
|
-
];
|
|
85704
|
-
static flags = {
|
|
85705
|
-
"api-key": Flags.string({
|
|
85706
|
-
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
85707
|
-
env: "PRIMITIVE_API_KEY"
|
|
85708
|
-
}),
|
|
85709
|
-
"api-base-url": Flags.string({
|
|
85710
|
-
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
85711
|
-
env: "PRIMITIVE_API_BASE_URL",
|
|
85712
|
-
hidden: true
|
|
85713
|
-
}),
|
|
85714
|
-
json: Flags.boolean({ description: "Print the account JSON response, or the saved connected identity marked verification: offline. Never prints credentials." }),
|
|
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." }),
|
|
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." }),
|
|
85717
|
-
resource: Flags.string({
|
|
85718
|
-
description: "With --check, the resource to check against, as function:<id>, domain:<id>, endpoint:<id> or route:<id>.",
|
|
85719
|
-
dependsOn: ["check"]
|
|
85720
|
-
}),
|
|
85721
|
-
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
85722
|
-
};
|
|
85723
|
-
async run() {
|
|
85724
|
-
const { flags } = await this.parse(WhoamiCommand);
|
|
85725
|
-
await runWithTiming(flags.time, async () => {
|
|
85726
|
-
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
85727
|
-
apiKey: flags["api-key"],
|
|
85728
|
-
apiBaseUrl: flags["api-base-url"],
|
|
85729
|
-
configDir: this.config.configDir
|
|
85730
|
-
});
|
|
85731
|
-
if (auth.connectedAgent && flags.check !== void 0) throw new Errors.CLIError("--check needs a live account credential. A selected connected-agent profile is shown offline; rerun with `PRIMITIVE_AGENT_PROFILE=` in front of the command to check the member login.", { exit: 1 });
|
|
85732
|
-
if (auth.connectedAgent) {
|
|
85733
|
-
const identity = auth.connectedAgent;
|
|
85734
|
-
const statusCommand = `${cliInvocation(process.argv[1])} agent connect --profile ${identity.profileName} --status --json`;
|
|
85735
|
-
const receiver = receiverStatusCommand(this.config.configDir, identity.profileName);
|
|
85736
|
-
const guidance = `Saved identity only. Live authentication, app pairing, and receiving readiness are not verified. Inspect the ${receiver.mode === "external" ? "Claude hook receiver" : receiver.mode === "poll" ? "poll receiver" : "receiver"} with \`${receiver.command}\`.`;
|
|
85737
|
-
const source = agentIdentitySource(identity);
|
|
85738
|
-
const selectedBy = source === "session_hook" ? `this Claude Code session's start hook (PRIMITIVE_AGENT_PROFILE=${identity.profileName})` : `PRIMITIVE_AGENT_PROFILE=${identity.profileName}`;
|
|
85739
|
-
this.log(flags.json ? JSON.stringify({
|
|
85740
|
-
address: identity.agentAddress,
|
|
85741
|
-
profile: identity.profileName,
|
|
85742
|
-
source,
|
|
85743
|
-
auth_method: "agent_connection",
|
|
85744
|
-
verification: "offline",
|
|
85745
|
-
identity,
|
|
85746
|
-
status_command: statusCommand,
|
|
85747
|
-
guidance
|
|
85748
|
-
}, null, 2) : `${identity.agentAddress}\nSelected by: ${selectedBy}. Prefix a command with \`PRIMITIVE_AGENT_PROFILE=\` to run it as the member login instead.\nSaved connected-agent profile: ${identity.profileName}\nAgent address: ${identity.agentAddress}\nOwner personal address (send reports here): ${identity.ownerMemberAddress ?? "unknown; reply to the member who wrote to you"}\nControl address (setup and presence only): ${identity.ownerAddress}\nAPI origin: ${identity.apiBaseUrl}\n${guidance}\nSaved profile status: ${statusCommand}`);
|
|
85749
|
-
return;
|
|
85750
|
-
}
|
|
85751
|
-
if (isConnectedChatCredential(auth.apiKey)) throw new Errors.CLIError("This connection credential cannot inspect an account. Select its saved profile with PRIMITIVE_AGENT_PROFILE to inspect the connected identity offline.", { exit: 1 });
|
|
85752
|
-
if (flags.check !== void 0) {
|
|
85753
|
-
const check = flags.check.trim();
|
|
85754
|
-
if (!/^[a-z][a-z0-9_.]*:[a-z][a-z0-9_]*$/.test(check)) throw new Errors.CLIError(`--check ${JSON.stringify(flags.check)} is not a permission. Permissions look like \`mail:send\`.`, { exit: 2 });
|
|
85755
|
-
const { identity, error } = await fetchCallerIdentity(apiClient.client, {
|
|
85756
|
-
check,
|
|
85757
|
-
...flags.resource ? { resource: flags.resource.trim() } : {}
|
|
85758
|
-
});
|
|
85759
|
-
if (error) {
|
|
85760
|
-
const errorPayload = extractErrorPayload(error);
|
|
85761
|
-
writeErrorWithHints(errorPayload);
|
|
85762
|
-
surfaceUnauthorizedHint({
|
|
85763
|
-
auth,
|
|
85764
|
-
baseUrlOverridden,
|
|
85765
|
-
configDir: this.config.configDir,
|
|
85766
|
-
payload: errorPayload
|
|
85767
|
-
});
|
|
85768
|
-
process.exitCode = 1;
|
|
85769
|
-
return;
|
|
85770
|
-
}
|
|
85771
|
-
const decision = identity?.check;
|
|
85772
|
-
if (!decision) throw new Errors.CLIError("This Primitive server does not answer permission checks.", { exit: 1 });
|
|
85773
|
-
this.log(flags.json ? JSON.stringify(decision, null, 2) : formatPermissionCheck(decision));
|
|
85774
|
-
if (!decision.allow) process.exitCode = 1;
|
|
85775
|
-
return;
|
|
85776
|
-
}
|
|
85777
|
-
const result = await getAccount({
|
|
85778
|
-
client: apiClient.client,
|
|
85779
|
-
responseStyle: "fields"
|
|
85780
|
-
});
|
|
85781
|
-
if (result.error) {
|
|
85782
|
-
const errorPayload = extractErrorPayload(result.error);
|
|
85783
|
-
const own = isGrantRefusal(errorPayload) ? await fetchOwnGrant(apiClient.client) : null;
|
|
85784
|
-
if (own) {
|
|
85785
|
-
const orgName = auth.credentials?.org_id === own.org_id ? auth.credentials.org_name ?? null : null;
|
|
85786
|
-
this.log(flags.json ? JSON.stringify({
|
|
85787
|
-
account: null,
|
|
85788
|
-
...own,
|
|
85789
|
-
org_name: orgName
|
|
85790
|
-
}, null, 2) : formatOwnGrantSummary(own, orgName));
|
|
85791
|
-
return;
|
|
85792
|
-
}
|
|
85793
|
-
writeErrorWithHints(errorPayload);
|
|
85794
|
-
surfaceUnauthorizedHint({
|
|
85795
|
-
auth,
|
|
85796
|
-
baseUrlOverridden,
|
|
85797
|
-
configDir: this.config.configDir,
|
|
85798
|
-
payload: errorPayload
|
|
85799
|
-
});
|
|
85800
|
-
process.exitCode = 1;
|
|
85801
|
-
return;
|
|
85802
|
-
}
|
|
85803
|
-
const account = result.data?.data;
|
|
85804
|
-
if (!account) {
|
|
85805
|
-
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
85806
|
-
throw new Errors.CLIError("unexpected empty response");
|
|
85807
|
-
}
|
|
85808
|
-
let managedInboxDomain = null;
|
|
85809
|
-
try {
|
|
85810
|
-
const domainsResult = await listDomains({
|
|
85811
|
-
client: apiClient.client,
|
|
85812
|
-
responseStyle: "fields"
|
|
85813
|
-
});
|
|
85814
|
-
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
85815
|
-
} catch {}
|
|
85816
|
-
const orgName = auth.credentials?.org_id === account.id ? auth.credentials.org_name ?? null : null;
|
|
85817
|
-
const session = guardedSession(void 0, process.env);
|
|
85818
|
-
let registration = null;
|
|
85819
|
-
try {
|
|
85820
|
-
registration = session ? failedSessionRegistration(this.config.configDir, session) : null;
|
|
85821
|
-
} catch {
|
|
85822
|
-
registration = null;
|
|
85823
|
-
}
|
|
85824
|
-
let identity = null;
|
|
85825
|
-
if (!flags.json || flags.grant) try {
|
|
85826
|
-
identity = (await fetchCallerIdentity(apiClient.client, { include: "grant" })).identity;
|
|
85827
|
-
} catch {
|
|
85828
|
-
identity = null;
|
|
85829
|
-
}
|
|
85830
|
-
if (flags.json) {
|
|
85831
|
-
this.log(JSON.stringify({
|
|
85832
|
-
...account,
|
|
85833
|
-
managed_inbox_domain: managedInboxDomain,
|
|
85834
|
-
org_name: orgName,
|
|
85835
|
-
...registration ? { session_registration: registration } : {},
|
|
85836
|
-
...flags.grant ? {
|
|
85837
|
-
grant: identity?.grant ?? null,
|
|
85838
|
-
org_id: identity?.org_id ?? null,
|
|
85839
|
-
role: identity?.role ?? null
|
|
85840
|
-
} : {}
|
|
85841
|
-
}, null, 2));
|
|
85842
|
-
return;
|
|
85843
|
-
}
|
|
85844
|
-
const summary = [formatWhoamiSummary(account, managedInboxDomain, orgName, identity)];
|
|
85845
|
-
if (identity?.grant) summary.push(...formatGrantLines(identity, {
|
|
85846
|
-
orgName: auth.credentials?.org_name ?? null,
|
|
85847
|
-
userLogin: auth.credentials?.auth_method === "user_grant"
|
|
85848
|
-
}).filter((line) => !line.startsWith("Organization:")));
|
|
85849
|
-
this.log(summary.join("\n"));
|
|
85850
|
-
if (registration) this.log(registrationSummaryLine(registration));
|
|
85851
|
-
});
|
|
85852
|
-
}
|
|
85853
|
-
};
|
|
85854
87182
|
// src/oclif/fish-completion.ts
|
|
85855
87183
|
function fishEscape(value) {
|
|
85856
87184
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|