primcli 3.0.0-preview.6 → 3.0.0-preview.7
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-BXYpylbG.js} +832 -358
- package/dist/{json-output-DWrJMakZ.js → json-output-ch756TXV.js} +8 -1
- package/dist/{machine-session-3H-rNH8v.js → machine-session-YQWxPePp.js} +5 -5
- package/dist/{notify-session-DTyg-sfS.js → notify-session-Bm59oy4j.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 +942 -359
- 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-C6tOKAqk.js} +5 -5
- package/dist/{scoped-chat-CvqdlWuX.js → scoped-chat-GItY2PvL.js} +1 -1
- package/dist/{signal-command-CPxRPbHK.js → signal-command-CvEDx8qQ.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-BXYpylbG.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-C6tOKAqk.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-Bm59oy4j.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-YQWxPePp.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-GItY2PvL.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-CvEDx8qQ.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";
|
|
@@ -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 three 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),
|
|
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 three 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": {
|
|
@@ -22375,7 +22375,7 @@ const openapiDocument = {
|
|
|
22375
22375
|
"get": {
|
|
22376
22376
|
"operationId": "listEmailRules",
|
|
22377
22377
|
"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.",
|
|
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) 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
22379
|
"tags": ["Email Rules"],
|
|
22380
22380
|
"security": [{ "BearerAuth": [] }],
|
|
22381
22381
|
"parameters": [
|
|
@@ -22498,7 +22498,7 @@ const openapiDocument = {
|
|
|
22498
22498
|
"get": {
|
|
22499
22499
|
"operationId": "getEmailRule",
|
|
22500
22500
|
"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.",
|
|
22501
|
+
"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
22502
|
"tags": ["Email Rules"],
|
|
22503
22503
|
"security": [{ "BearerAuth": [] }],
|
|
22504
22504
|
"parameters": [{
|
|
@@ -22613,7 +22613,7 @@ const openapiDocument = {
|
|
|
22613
22613
|
"patch": {
|
|
22614
22614
|
"operationId": "updateEmailRule",
|
|
22615
22615
|
"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.",
|
|
22616
|
+
"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
22617
|
"tags": ["Email Rules"],
|
|
22618
22618
|
"security": [{ "BearerAuth": [] }],
|
|
22619
22619
|
"parameters": [{
|
|
@@ -22646,17 +22646,8 @@ const openapiDocument = {
|
|
|
22646
22646
|
}
|
|
22647
22647
|
},
|
|
22648
22648
|
"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
|
-
} }
|
|
22649
|
+
"description": "validation_error: invalid body or tree (details.errors), too large (too_large), a version that no longer renders (version_not_renderable), or restore_version names the current version (already_current). no_expectations: if_expectations_pass with no expectations to judge; nothing was saved.",
|
|
22650
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22660
22651
|
},
|
|
22661
22652
|
"401": {
|
|
22662
22653
|
"description": "Invalid or missing API key",
|
|
@@ -22678,24 +22669,15 @@ const openapiDocument = {
|
|
|
22678
22669
|
}
|
|
22679
22670
|
},
|
|
22680
22671
|
"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
|
|
22672
|
+
"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.",
|
|
22682
22673
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22683
22674
|
},
|
|
22684
22675
|
"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
|
-
} }
|
|
22676
|
+
"description": "not_found: no such rule you can see, or restore_version names no version of it (details.reason version_not_found).",
|
|
22677
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22696
22678
|
},
|
|
22697
22679
|
"409": {
|
|
22698
|
-
"description": "email_rule_conflict: the rule changed
|
|
22680
|
+
"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
22681
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
22700
22682
|
},
|
|
22701
22683
|
"429": {
|
|
@@ -39320,6 +39302,8 @@ const openapiDocument = {
|
|
|
39320
39302
|
"email_rule_share_limit",
|
|
39321
39303
|
"embedding_provider_unavailable",
|
|
39322
39304
|
"entitlement",
|
|
39305
|
+
"expectations_failed",
|
|
39306
|
+
"expectations_undecided",
|
|
39323
39307
|
"expired",
|
|
39324
39308
|
"feature_not_enabled",
|
|
39325
39309
|
"fresh_send_not_authorized",
|
|
@@ -39394,6 +39378,7 @@ const openapiDocument = {
|
|
|
39394
39378
|
"network_view_denied",
|
|
39395
39379
|
"no_code",
|
|
39396
39380
|
"no_endpoint",
|
|
39381
|
+
"no_expectations",
|
|
39397
39382
|
"no_mail_read",
|
|
39398
39383
|
"not_a_template_install",
|
|
39399
39384
|
"not_available",
|
|
@@ -46240,12 +46225,12 @@ const openapiDocument = {
|
|
|
46240
46225
|
},
|
|
46241
46226
|
"retry_scheduled": {
|
|
46242
46227
|
"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
|
|
46228
|
+
"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
46229
|
},
|
|
46245
46230
|
"next_attempt_at": {
|
|
46246
46231
|
"type": ["string", "null"],
|
|
46247
46232
|
"format": "date-time",
|
|
46248
|
-
"description": "When the next automatic
|
|
46233
|
+
"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
46234
|
}
|
|
46250
46235
|
},
|
|
46251
46236
|
"required": [
|
|
@@ -49452,6 +49437,22 @@ const openapiDocument = {
|
|
|
49452
49437
|
"questions"
|
|
49453
49438
|
]
|
|
49454
49439
|
},
|
|
49440
|
+
"EmailRuleBreadth": {
|
|
49441
|
+
"type": "object",
|
|
49442
|
+
"properties": {
|
|
49443
|
+
"checked": {
|
|
49444
|
+
"type": "integer",
|
|
49445
|
+
"minimum": 0,
|
|
49446
|
+
"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."
|
|
49447
|
+
},
|
|
49448
|
+
"matched": {
|
|
49449
|
+
"type": "integer",
|
|
49450
|
+
"minimum": 0,
|
|
49451
|
+
"description": "Of those, how many they hold for (for a do-not-share rule, one they cannot decide counts)."
|
|
49452
|
+
}
|
|
49453
|
+
},
|
|
49454
|
+
"required": ["checked", "matched"]
|
|
49455
|
+
},
|
|
49455
49456
|
"CompileEmailRuleRequest": {
|
|
49456
49457
|
"type": "object",
|
|
49457
49458
|
"properties": {
|
|
@@ -49480,7 +49481,7 @@ const openapiDocument = {
|
|
|
49480
49481
|
"tree": { "$ref": "#/components/schemas/EmailRuleTree" },
|
|
49481
49482
|
"explanation": {
|
|
49482
49483
|
"type": "string",
|
|
49483
|
-
"description": "What the rule matches, in plain English."
|
|
49484
|
+
"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
49485
|
},
|
|
49485
49486
|
"assumptions": {
|
|
49486
49487
|
"type": "array",
|
|
@@ -49496,6 +49497,10 @@ const openapiDocument = {
|
|
|
49496
49497
|
"type": "string",
|
|
49497
49498
|
"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
49499
|
},
|
|
49500
|
+
"breadth": {
|
|
49501
|
+
"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.",
|
|
49502
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
49503
|
+
},
|
|
49499
49504
|
"sql": {
|
|
49500
49505
|
"description": "Present only when include_sql was true.",
|
|
49501
49506
|
"type": "object",
|
|
@@ -50419,10 +50424,19 @@ const openapiDocument = {
|
|
|
50419
50424
|
"type": ["integer", "null"],
|
|
50420
50425
|
"exclusiveMinimum": 0
|
|
50421
50426
|
},
|
|
50427
|
+
"revision": {
|
|
50428
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
50429
|
+
"type": "integer",
|
|
50430
|
+
"exclusiveMinimum": 0
|
|
50431
|
+
},
|
|
50422
50432
|
"author_user_id": {
|
|
50423
50433
|
"type": "string",
|
|
50424
50434
|
"format": "uuid"
|
|
50425
50435
|
},
|
|
50436
|
+
"author": {
|
|
50437
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
50438
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
50439
|
+
},
|
|
50426
50440
|
"is_author": {
|
|
50427
50441
|
"type": "boolean",
|
|
50428
50442
|
"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 +50449,17 @@ const openapiDocument = {
|
|
|
50435
50449
|
"type": "string",
|
|
50436
50450
|
"format": "date-time"
|
|
50437
50451
|
},
|
|
50438
|
-
"current": {
|
|
50452
|
+
"current": {
|
|
50453
|
+
"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.",
|
|
50454
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
50455
|
+
},
|
|
50439
50456
|
"scope_warnings": {
|
|
50440
50457
|
"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
50458
|
"type": "array",
|
|
50442
50459
|
"items": { "$ref": "#/components/schemas/EmailRuleScopeWarning" }
|
|
50443
50460
|
},
|
|
50444
50461
|
"expectations_check": {
|
|
50445
|
-
"description": "After a write that saved a version or changed its expectations, if it has some: them, judged on that version from stored classifications without asking its questions. 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). Test by rule_id for the rest.",
|
|
50462
|
+
"description": "After a write that saved a version or changed its expectations, if it has some: them, judged on that version from stored classifications without asking its questions. 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). Test by rule_id for the rest. With if_expectations_pass, the judgment made before the save, which passed.",
|
|
50446
50463
|
"type": "object",
|
|
50447
50464
|
"properties": {
|
|
50448
50465
|
"version": {
|
|
@@ -50470,6 +50487,10 @@ const openapiDocument = {
|
|
|
50470
50487
|
"type": "string",
|
|
50471
50488
|
"format": "uuid"
|
|
50472
50489
|
}
|
|
50490
|
+
},
|
|
50491
|
+
"note": {
|
|
50492
|
+
"description": "On an update that saved a version that did not pass: that it was saved anyway, is current, and whether it is live.",
|
|
50493
|
+
"type": "string"
|
|
50473
50494
|
}
|
|
50474
50495
|
},
|
|
50475
50496
|
"required": [
|
|
@@ -50478,6 +50499,41 @@ const openapiDocument = {
|
|
|
50478
50499
|
"failed",
|
|
50479
50500
|
"undecided"
|
|
50480
50501
|
]
|
|
50502
|
+
},
|
|
50503
|
+
"restored_from": {
|
|
50504
|
+
"description": "When this write saved an earlier version again (restore_version): which, and that the scope is not part of a version.",
|
|
50505
|
+
"type": "object",
|
|
50506
|
+
"properties": {
|
|
50507
|
+
"version": {
|
|
50508
|
+
"type": "integer",
|
|
50509
|
+
"exclusiveMinimum": 0
|
|
50510
|
+
},
|
|
50511
|
+
"note": { "type": "string" }
|
|
50512
|
+
},
|
|
50513
|
+
"required": ["version", "note"]
|
|
50514
|
+
},
|
|
50515
|
+
"affects": {
|
|
50516
|
+
"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.",
|
|
50517
|
+
"type": "object",
|
|
50518
|
+
"properties": {
|
|
50519
|
+
"keys": {
|
|
50520
|
+
"type": "integer",
|
|
50521
|
+
"minimum": 0
|
|
50522
|
+
},
|
|
50523
|
+
"endpoints": {
|
|
50524
|
+
"type": "integer",
|
|
50525
|
+
"minimum": 0
|
|
50526
|
+
},
|
|
50527
|
+
"shares_ended": {
|
|
50528
|
+
"type": "integer",
|
|
50529
|
+
"minimum": 0
|
|
50530
|
+
}
|
|
50531
|
+
},
|
|
50532
|
+
"required": [
|
|
50533
|
+
"keys",
|
|
50534
|
+
"endpoints",
|
|
50535
|
+
"shares_ended"
|
|
50536
|
+
]
|
|
50481
50537
|
}
|
|
50482
50538
|
},
|
|
50483
50539
|
"required": [
|
|
@@ -50506,6 +50562,28 @@ const openapiDocument = {
|
|
|
50506
50562
|
},
|
|
50507
50563
|
"required": ["addresses", "domains"]
|
|
50508
50564
|
},
|
|
50565
|
+
"EmailRuleAuthor": {
|
|
50566
|
+
"type": "object",
|
|
50567
|
+
"properties": {
|
|
50568
|
+
"user_id": {
|
|
50569
|
+
"type": "string",
|
|
50570
|
+
"format": "uuid"
|
|
50571
|
+
},
|
|
50572
|
+
"name": {
|
|
50573
|
+
"description": "Their name; null unless you can list members or this is you, and when they have none set.",
|
|
50574
|
+
"type": ["string", "null"]
|
|
50575
|
+
},
|
|
50576
|
+
"email": {
|
|
50577
|
+
"description": "Sign-in email; null unless you can list member emails or this is you.",
|
|
50578
|
+
"type": ["string", "null"]
|
|
50579
|
+
}
|
|
50580
|
+
},
|
|
50581
|
+
"required": [
|
|
50582
|
+
"user_id",
|
|
50583
|
+
"name",
|
|
50584
|
+
"email"
|
|
50585
|
+
]
|
|
50586
|
+
},
|
|
50509
50587
|
"EmailRuleVersion": {
|
|
50510
50588
|
"type": "object",
|
|
50511
50589
|
"properties": {
|
|
@@ -50655,6 +50733,10 @@ const openapiDocument = {
|
|
|
50655
50733
|
"type": "string",
|
|
50656
50734
|
"minLength": 1,
|
|
50657
50735
|
"maxLength": 64
|
|
50736
|
+
},
|
|
50737
|
+
"breadth": {
|
|
50738
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
50739
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
50658
50740
|
}
|
|
50659
50741
|
},
|
|
50660
50742
|
"required": [
|
|
@@ -50703,10 +50785,19 @@ const openapiDocument = {
|
|
|
50703
50785
|
"type": ["integer", "null"],
|
|
50704
50786
|
"exclusiveMinimum": 0
|
|
50705
50787
|
},
|
|
50788
|
+
"revision": {
|
|
50789
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
50790
|
+
"type": "integer",
|
|
50791
|
+
"exclusiveMinimum": 0
|
|
50792
|
+
},
|
|
50706
50793
|
"author_user_id": {
|
|
50707
50794
|
"type": "string",
|
|
50708
50795
|
"format": "uuid"
|
|
50709
50796
|
},
|
|
50797
|
+
"author": {
|
|
50798
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
50799
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
50800
|
+
},
|
|
50710
50801
|
"is_author": {
|
|
50711
50802
|
"type": "boolean",
|
|
50712
50803
|
"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)."
|
|
@@ -50788,10 +50879,19 @@ const openapiDocument = {
|
|
|
50788
50879
|
"type": ["integer", "null"],
|
|
50789
50880
|
"exclusiveMinimum": 0
|
|
50790
50881
|
},
|
|
50882
|
+
"revision": {
|
|
50883
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
50884
|
+
"type": "integer",
|
|
50885
|
+
"exclusiveMinimum": 0
|
|
50886
|
+
},
|
|
50791
50887
|
"author_user_id": {
|
|
50792
50888
|
"type": "string",
|
|
50793
50889
|
"format": "uuid"
|
|
50794
50890
|
},
|
|
50891
|
+
"author": {
|
|
50892
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
50893
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
50894
|
+
},
|
|
50795
50895
|
"is_author": {
|
|
50796
50896
|
"type": "boolean",
|
|
50797
50897
|
"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 +50904,10 @@ const openapiDocument = {
|
|
|
50804
50904
|
"type": "string",
|
|
50805
50905
|
"format": "date-time"
|
|
50806
50906
|
},
|
|
50807
|
-
"current": {
|
|
50907
|
+
"current": {
|
|
50908
|
+
"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.",
|
|
50909
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
50910
|
+
},
|
|
50808
50911
|
"expectations": {
|
|
50809
50912
|
"description": "Its saved expectations, each list by email id.",
|
|
50810
50913
|
"type": "object",
|
|
@@ -50900,6 +51003,10 @@ const openapiDocument = {
|
|
|
50900
51003
|
"minLength": 1,
|
|
50901
51004
|
"maxLength": 64
|
|
50902
51005
|
},
|
|
51006
|
+
"breadth": {
|
|
51007
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
51008
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
51009
|
+
},
|
|
50903
51010
|
"evaluation_scope": {
|
|
50904
51011
|
"type": "string",
|
|
50905
51012
|
"enum": [
|
|
@@ -50911,7 +51018,7 @@ const openapiDocument = {
|
|
|
50911
51018
|
},
|
|
50912
51019
|
"scope_selection": { "$ref": "#/components/schemas/EmailRuleScopeSelection" },
|
|
50913
51020
|
"if_version": {
|
|
50914
|
-
"description": "The current_version you read
|
|
51021
|
+
"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
51022
|
"type": "integer",
|
|
50916
51023
|
"minimum": 1
|
|
50917
51024
|
},
|
|
@@ -50937,6 +51044,22 @@ const openapiDocument = {
|
|
|
50937
51044
|
},
|
|
50938
51045
|
"additionalProperties": false,
|
|
50939
51046
|
"description": "Replaces its saved expectations (author only); null removes them."
|
|
51047
|
+
},
|
|
51048
|
+
"if_revision": {
|
|
51049
|
+
"description": "The revision you read: if the rule was updated since, nothing is saved (409 email_rule_conflict, revision_moved).",
|
|
51050
|
+
"type": "integer",
|
|
51051
|
+
"minimum": 1,
|
|
51052
|
+
"maximum": 2147483647
|
|
51053
|
+
},
|
|
51054
|
+
"if_expectations_pass": {
|
|
51055
|
+
"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.",
|
|
51056
|
+
"type": "boolean"
|
|
51057
|
+
},
|
|
51058
|
+
"restore_version": {
|
|
51059
|
+
"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.",
|
|
51060
|
+
"type": "integer",
|
|
51061
|
+
"minimum": 1,
|
|
51062
|
+
"maximum": 2147483647
|
|
50940
51063
|
}
|
|
50941
51064
|
},
|
|
50942
51065
|
"additionalProperties": false
|
|
@@ -51543,6 +51666,10 @@ const openapiDocument = {
|
|
|
51543
51666
|
"type": "string",
|
|
51544
51667
|
"minLength": 1,
|
|
51545
51668
|
"maxLength": 64
|
|
51669
|
+
},
|
|
51670
|
+
"breadth": {
|
|
51671
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
51672
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
51546
51673
|
}
|
|
51547
51674
|
},
|
|
51548
51675
|
"required": [
|
|
@@ -51863,10 +51990,19 @@ const openapiDocument = {
|
|
|
51863
51990
|
"type": ["integer", "null"],
|
|
51864
51991
|
"exclusiveMinimum": 0
|
|
51865
51992
|
},
|
|
51993
|
+
"revision": {
|
|
51994
|
+
"description": "Moves on by one with every update. Send it as if_revision.",
|
|
51995
|
+
"type": "integer",
|
|
51996
|
+
"exclusiveMinimum": 0
|
|
51997
|
+
},
|
|
51866
51998
|
"author_user_id": {
|
|
51867
51999
|
"type": "string",
|
|
51868
52000
|
"format": "uuid"
|
|
51869
52001
|
},
|
|
52002
|
+
"author": {
|
|
52003
|
+
"description": "Who wrote it: author_user_id with their name and sign-in email.",
|
|
52004
|
+
"$ref": "#/components/schemas/EmailRuleAuthor"
|
|
52005
|
+
},
|
|
51870
52006
|
"is_author": {
|
|
51871
52007
|
"type": "boolean",
|
|
51872
52008
|
"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 +52015,10 @@ const openapiDocument = {
|
|
|
51879
52015
|
"type": "string",
|
|
51880
52016
|
"format": "date-time"
|
|
51881
52017
|
},
|
|
51882
|
-
"current": {
|
|
52018
|
+
"current": {
|
|
52019
|
+
"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.",
|
|
52020
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleVersion" }, { "type": "null" }]
|
|
52021
|
+
}
|
|
51883
52022
|
},
|
|
51884
52023
|
"required": [
|
|
51885
52024
|
"id",
|
|
@@ -52102,6 +52241,10 @@ const openapiDocument = {
|
|
|
52102
52241
|
"type": "string",
|
|
52103
52242
|
"minLength": 1,
|
|
52104
52243
|
"maxLength": 64
|
|
52244
|
+
},
|
|
52245
|
+
"breadth": {
|
|
52246
|
+
"description": "Ignored (the compile answer's breadth).",
|
|
52247
|
+
"anyOf": [{ "$ref": "#/components/schemas/EmailRuleBreadth" }, { "type": "null" }]
|
|
52105
52248
|
}
|
|
52106
52249
|
},
|
|
52107
52250
|
"additionalProperties": false
|
|
@@ -54350,11 +54493,11 @@ const openapiDocument = {
|
|
|
54350
54493
|
"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
54494
|
},
|
|
54352
54495
|
"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
|
|
54496
|
+
"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
54497
|
"type": ["boolean", "null"]
|
|
54355
54498
|
},
|
|
54356
54499
|
"next_attempt_at": {
|
|
54357
|
-
"description": "When the next automatic
|
|
54500
|
+
"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
54501
|
"type": ["string", "null"]
|
|
54359
54502
|
},
|
|
54360
54503
|
"attempts": {
|
|
@@ -54384,7 +54527,7 @@ const openapiDocument = {
|
|
|
54384
54527
|
"type": ["string", "null"]
|
|
54385
54528
|
},
|
|
54386
54529
|
"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
|
|
54530
|
+
"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
54531
|
"type": ["boolean", "null"]
|
|
54389
54532
|
}
|
|
54390
54533
|
},
|
|
@@ -65178,7 +65321,9 @@ function unauthorizedRefusal(errorPayload, authFailureContext) {
|
|
|
65178
65321
|
* Anything else, including an organization-wide key, an agent_delegate and
|
|
65179
65322
|
* a person with no member address they may send from, gets no default: the
|
|
65180
65323
|
* command refuses, lists what the credential may send from, and asks for
|
|
65181
|
-
* --from.
|
|
65324
|
+
* --from. A credential whose grant has no mail:send is refused by that
|
|
65325
|
+
* permission instead (MissingSendPermissionError), with no --from hint. The
|
|
65326
|
+
* From is never an address guessed from the organization's
|
|
65182
65327
|
* domains, such as agent@ at the first verified one. A failed answer from an
|
|
65183
65328
|
* API that has it refuses rather than fall back.
|
|
65184
65329
|
*/
|
|
@@ -65236,6 +65381,57 @@ const SEND_FROM_SOURCES = {
|
|
|
65236
65381
|
member_address: "member_address",
|
|
65237
65382
|
granted_address: "granted_address"
|
|
65238
65383
|
};
|
|
65384
|
+
/**
|
|
65385
|
+
* The refusal of a send by a credential whose grant has no mail:send, in the
|
|
65386
|
+
* shape the send route itself refuses it with (code grant_forbidden, details
|
|
65387
|
+
* naming the permission), so a script can branch on it. It names what the
|
|
65388
|
+
* credential lacks first, then what would fix it, never --from.
|
|
65389
|
+
*/
|
|
65390
|
+
var MissingSendPermissionError = class extends Errors.CLIError {
|
|
65391
|
+
details = {
|
|
65392
|
+
permission: "mail:send",
|
|
65393
|
+
reason: "permission_missing"
|
|
65394
|
+
};
|
|
65395
|
+
constructor(missing) {
|
|
65396
|
+
super(missingSendPermissionMessage(missing), {
|
|
65397
|
+
code: "grant_forbidden",
|
|
65398
|
+
exit: 1
|
|
65399
|
+
});
|
|
65400
|
+
}
|
|
65401
|
+
};
|
|
65402
|
+
function missingSendPermissionMessage(missing) {
|
|
65403
|
+
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.`];
|
|
65404
|
+
if (missing.replies) parts.push("It can still reply to mail it received.");
|
|
65405
|
+
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.");
|
|
65406
|
+
return parts.join(" ");
|
|
65407
|
+
}
|
|
65408
|
+
/**
|
|
65409
|
+
* The credential's own grant when it has no mail:send, outright or over a
|
|
65410
|
+
* reach of its own (as the API decides whether a caller may send), or null
|
|
65411
|
+
* when it has it or the grant cannot be read: then nothing is claimed.
|
|
65412
|
+
*/
|
|
65413
|
+
async function missingSendPermission(apiClient) {
|
|
65414
|
+
let own;
|
|
65415
|
+
try {
|
|
65416
|
+
own = await getOwnGrant({
|
|
65417
|
+
client: apiClient.client,
|
|
65418
|
+
responseStyle: "fields"
|
|
65419
|
+
});
|
|
65420
|
+
} catch {
|
|
65421
|
+
return null;
|
|
65422
|
+
}
|
|
65423
|
+
if (!own || own.error) return null;
|
|
65424
|
+
const data = own.data?.data;
|
|
65425
|
+
const grant = data?.grant;
|
|
65426
|
+
if (!grant || !Array.isArray(grant.permissions)) return null;
|
|
65427
|
+
const holds = (permission) => grant.permissions.includes(permission) || grant.scoped_permissions !== null && typeof grant.scoped_permissions === "object" && Object.hasOwn(grant.scoped_permissions, permission);
|
|
65428
|
+
if (holds("mail:send")) return null;
|
|
65429
|
+
return {
|
|
65430
|
+
credentialKind: typeof grant.credential_kind === "string" ? grant.credential_kind : null,
|
|
65431
|
+
keyId: typeof data?.key_id === "string" ? data.key_id : null,
|
|
65432
|
+
replies: holds("mail:reply")
|
|
65433
|
+
};
|
|
65434
|
+
}
|
|
65239
65435
|
function errorDetail(error) {
|
|
65240
65436
|
return error instanceof Error ? error.message : String(error);
|
|
65241
65437
|
}
|
|
@@ -65292,6 +65488,10 @@ async function sendFromAnswer(apiClient, authFailureContext, purpose) {
|
|
|
65292
65488
|
const addresses = data.addresses.map((entry) => entry && typeof entry === "object" ? entry.address : void 0).filter((address) => typeof address === "string");
|
|
65293
65489
|
const domains = Array.isArray(data.domains) ? data.domains.filter((domain) => typeof domain === "string") : [];
|
|
65294
65490
|
if (purpose === "reply" && addresses.length === 0 && domains.length === 0) return void 0;
|
|
65491
|
+
if (addresses.length === 0 && domains.length === 0) {
|
|
65492
|
+
const missing = await missingSendPermission(apiClient);
|
|
65493
|
+
if (missing) throw new MissingSendPermissionError(missing);
|
|
65494
|
+
}
|
|
65295
65495
|
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
65496
|
if (addresses.length > 0) parts.push(`Addresses you may send from: ${addresses.join(", ")}.`);
|
|
65297
65497
|
if (domains.length > 0) parts.push(`${addresses.length > 0 ? "Also any" : "Any"} address at: ${domains.join(", ")}.`);
|
|
@@ -66106,7 +66306,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
66106
66306
|
env: "PRIMITIVE_API_BASE_URL",
|
|
66107
66307
|
hidden: true
|
|
66108
66308
|
}),
|
|
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." }),
|
|
66309
|
+
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
66310
|
subject: Flags.string({
|
|
66111
66311
|
description: "Advanced email transport override. Usually omit; chat threading does not depend on the subject.",
|
|
66112
66312
|
hidden: true
|
|
@@ -66186,7 +66386,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
66186
66386
|
}
|
|
66187
66387
|
const outcome = phase === "sending" || error instanceof UncertainChatSendError ? "uncertain" : "not_sent";
|
|
66188
66388
|
if (flags.json) this.log(JSON.stringify(buildChatSendFailureEnvelope({
|
|
66189
|
-
error:
|
|
66389
|
+
error: thrownErrorBody(error),
|
|
66190
66390
|
exitCode: outcome === "not_sent" ? thrownErrorExitCode(error) : void 0,
|
|
66191
66391
|
idempotencyKey: this.chatProgress.idempotencyKey,
|
|
66192
66392
|
noun,
|
|
@@ -66976,15 +67176,12 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
66976
67176
|
try {
|
|
66977
67177
|
await this.runReply(args, flags);
|
|
66978
67178
|
} catch (error) {
|
|
66979
|
-
if (flags.json && !this.delegatedToChat) {
|
|
66980
|
-
|
|
66981
|
-
|
|
66982
|
-
|
|
66983
|
-
|
|
66984
|
-
|
|
66985
|
-
outcome: "not_sent"
|
|
66986
|
-
}), null, 2));
|
|
66987
|
-
}
|
|
67179
|
+
if (flags.json && !this.delegatedToChat) this.log(JSON.stringify(buildChatSendFailureEnvelope({
|
|
67180
|
+
error: thrownErrorBody(error),
|
|
67181
|
+
exitCode: thrownErrorExitCode(error),
|
|
67182
|
+
noun: "Reply",
|
|
67183
|
+
outcome: "not_sent"
|
|
67184
|
+
}), null, 2));
|
|
66988
67185
|
throw error;
|
|
66989
67186
|
}
|
|
66990
67187
|
}
|
|
@@ -68435,7 +68632,9 @@ const ID_DESCRIPTION = "The id of the received email (from `primitive emails lis
|
|
|
68435
68632
|
/**
|
|
68436
68633
|
* `primitive emails facts <id>`: what the classifier read from one received
|
|
68437
68634
|
* email, every fact as email rule conditions read it, from the email's rule
|
|
68438
|
-
* results (GET /emails/{id}/rule-results).
|
|
68635
|
+
* results (GET /emails/{id}/rule-results). A credential that may not read
|
|
68636
|
+
* rule results gets the facts as written, from the email read
|
|
68637
|
+
* (GET /emails/{id}, `understanding`).
|
|
68439
68638
|
*/
|
|
68440
68639
|
var EmailsFactsCommand = class EmailsFactsCommand extends Command {
|
|
68441
68640
|
static summary = "Show what the classifier read from an email";
|
|
@@ -68443,7 +68642,7 @@ var EmailsFactsCommand = class EmailsFactsCommand extends Command {
|
|
|
68443
68642
|
|
|
68444
68643
|
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
68644
|
|
|
68446
|
-
Needs the email rules feature
|
|
68645
|
+
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
68646
|
static examples = ["<%= config.bin %> emails facts 3f0e1c2a-8b7d-4c6e-9a1b-2d3e4f5a6b7c", "<%= config.bin %> emails facts --id 3f0e1c2a-8b7d-4c6e-9a1b-2d3e4f5a6b7c --json"];
|
|
68448
68647
|
static args = { id: Args.string({
|
|
68449
68648
|
description: ID_DESCRIPTION,
|
|
@@ -68451,7 +68650,7 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68451
68650
|
}) };
|
|
68452
68651
|
static flags = {
|
|
68453
68652
|
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." }),
|
|
68653
|
+
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
68654
|
"api-key": Flags.string({
|
|
68456
68655
|
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
68457
68656
|
env: "PRIMITIVE_API_KEY"
|
|
@@ -68484,6 +68683,14 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68484
68683
|
});
|
|
68485
68684
|
if (result.error) {
|
|
68486
68685
|
const payload = extractErrorPayload(result.error);
|
|
68686
|
+
if (refusesRuleResultsToCredential(payload)) {
|
|
68687
|
+
await this.printFactsFromEmail(id, Boolean(flags.json), {
|
|
68688
|
+
apiClient,
|
|
68689
|
+
auth,
|
|
68690
|
+
baseUrlOverridden
|
|
68691
|
+
});
|
|
68692
|
+
return;
|
|
68693
|
+
}
|
|
68487
68694
|
writeErrorWithHints(payload, result.response);
|
|
68488
68695
|
surfaceUnauthorizedHint({
|
|
68489
68696
|
auth,
|
|
@@ -68508,7 +68715,80 @@ Needs the email rules feature, and a member's own credential with their full acc
|
|
|
68508
68715
|
this.log(lines.join("\n"));
|
|
68509
68716
|
});
|
|
68510
68717
|
}
|
|
68718
|
+
/**
|
|
68719
|
+
* The facts as `emails get` reads them, for a credential that may not read
|
|
68720
|
+
* rule results: the email's understanding, when it is ready.
|
|
68721
|
+
*/
|
|
68722
|
+
async printFactsFromEmail(id, json, client) {
|
|
68723
|
+
const read = await getEmail({
|
|
68724
|
+
client: client.apiClient.client,
|
|
68725
|
+
path: { id },
|
|
68726
|
+
responseStyle: "fields"
|
|
68727
|
+
});
|
|
68728
|
+
if (read.error) {
|
|
68729
|
+
const payload = extractErrorPayload(read.error);
|
|
68730
|
+
process.stderr.write("This credential cannot read email rule results, and reading the facts from the email itself failed:\n");
|
|
68731
|
+
writeErrorWithHints(payload, read.response);
|
|
68732
|
+
surfaceUnauthorizedHint({
|
|
68733
|
+
auth: client.auth,
|
|
68734
|
+
baseUrlOverridden: client.baseUrlOverridden,
|
|
68735
|
+
configDir: this.config.configDir,
|
|
68736
|
+
payload
|
|
68737
|
+
});
|
|
68738
|
+
process.exitCode = 1;
|
|
68739
|
+
return;
|
|
68740
|
+
}
|
|
68741
|
+
const understanding = read.data?.data?.understanding;
|
|
68742
|
+
const classification = classificationFromUnderstanding(understanding);
|
|
68743
|
+
if (classification) process.stderr.write(`${understandingNote(id)}\n`);
|
|
68744
|
+
if (json) {
|
|
68745
|
+
this.log(JSON.stringify(classification, null, 2));
|
|
68746
|
+
return;
|
|
68747
|
+
}
|
|
68748
|
+
const lines = classification ? classificationFactLines(classification) : [];
|
|
68749
|
+
if (lines.length === 0) {
|
|
68750
|
+
process.stderr.write(`${classification ? "This email's classification has no facts." : noUnderstandingMessage(understanding)}\n`);
|
|
68751
|
+
return;
|
|
68752
|
+
}
|
|
68753
|
+
this.log(lines.join("\n"));
|
|
68754
|
+
}
|
|
68511
68755
|
};
|
|
68756
|
+
/** The refusals of rule results to a credential that is not a member's own, unnarrowed one. */
|
|
68757
|
+
const NOT_A_RULE_READER = /* @__PURE__ */ new Set(["rule_author_credential", "narrowed_credential"]);
|
|
68758
|
+
/** Whether rule results were refused only because of the kind of credential. */
|
|
68759
|
+
function refusesRuleResultsToCredential(payload) {
|
|
68760
|
+
if (extractErrorCode(payload) !== "forbidden") return false;
|
|
68761
|
+
const { reason } = refusalDetails(payload);
|
|
68762
|
+
return typeof reason === "string" && NOT_A_RULE_READER.has(reason);
|
|
68763
|
+
}
|
|
68764
|
+
/**
|
|
68765
|
+
* The email's `understanding` as the classification rule results show
|
|
68766
|
+
* (id, vocabulary, classified_at, facts), when it is ready; otherwise null.
|
|
68767
|
+
*/
|
|
68768
|
+
function classificationFromUnderstanding(understanding) {
|
|
68769
|
+
if (!isRecord$1(understanding) || understanding.status !== "ready") return null;
|
|
68770
|
+
return {
|
|
68771
|
+
id: understanding.classification_id ?? null,
|
|
68772
|
+
vocabulary: understanding.vocabulary ?? null,
|
|
68773
|
+
classified_at: understanding.classified_at ?? null,
|
|
68774
|
+
facts: isRecord$1(understanding.facts) ? understanding.facts : {}
|
|
68775
|
+
};
|
|
68776
|
+
}
|
|
68777
|
+
/** Where facts read from the email came from, and how they differ. */
|
|
68778
|
+
function understandingNote(id) {
|
|
68779
|
+
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.`;
|
|
68780
|
+
}
|
|
68781
|
+
/** Why an email read gave no facts to show. */
|
|
68782
|
+
function noUnderstandingMessage(understanding) {
|
|
68783
|
+
const status = isRecord$1(understanding) ? understanding.status : void 0;
|
|
68784
|
+
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.";
|
|
68785
|
+
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.";
|
|
68786
|
+
if (typeof status === "string") return `No classification to show: the email's understanding is ${status}.`;
|
|
68787
|
+
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.";
|
|
68788
|
+
}
|
|
68789
|
+
function isRecord$1(value) {
|
|
68790
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
68791
|
+
}
|
|
68512
68792
|
// src/oclif/repeat-message.ts
|
|
68513
68793
|
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
68794
|
/** The server-verified repeat marker on an email read, if any. */
|
|
@@ -71015,8 +71295,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
71015
71295
|
name: "Primitive Team",
|
|
71016
71296
|
url: "https://primitive.dev"
|
|
71017
71297
|
};
|
|
71018
|
-
const SDK_VERSION_RANGE = "^3.0.0-preview.
|
|
71019
|
-
const CLI_VERSION_RANGE = "^3.0.0-preview.
|
|
71298
|
+
const SDK_VERSION_RANGE = "^3.0.0-preview.7";
|
|
71299
|
+
const CLI_VERSION_RANGE = "^3.0.0-preview.7";
|
|
71020
71300
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
71021
71301
|
function renderHandler() {
|
|
71022
71302
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -72848,7 +73128,8 @@ const INBOX_NEXT_EXIT_CODE_HELP = `Exit codes:
|
|
|
72848
73128
|
const SCAN_PAGE_SIZE = 100;
|
|
72849
73129
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 300;
|
|
72850
73130
|
const MAX_LONG_POLL_SECONDS$1 = 30;
|
|
72851
|
-
const
|
|
73131
|
+
const TIME_CURSOR_ID = "00000000-0000-4000-8000-000000000000";
|
|
73132
|
+
const EPOCH_CURSOR = `1970-01-01T00:00:00.000000Z|${TIME_CURSOR_ID}`;
|
|
72852
73133
|
/** An API call failed; carries the error payload for printing. */
|
|
72853
73134
|
var InboxNextApiError = class extends Error {
|
|
72854
73135
|
surface;
|
|
@@ -73090,18 +73371,43 @@ async function countAutomatedAwaiting(params) {
|
|
|
73090
73371
|
capped: page.totalCapped
|
|
73091
73372
|
};
|
|
73092
73373
|
}
|
|
73374
|
+
/** Pages of the forward tail `baselineCursor` reads to catch up, at most. */
|
|
73375
|
+
const BASELINE_CATCH_UP_PAGES = 5;
|
|
73093
73376
|
/**
|
|
73094
|
-
* The wake-up cursor for --wait: the
|
|
73095
|
-
*
|
|
73096
|
-
*
|
|
73097
|
-
*
|
|
73098
|
-
*
|
|
73377
|
+
* The wake-up cursor for --wait (and for `primitive wait`): the end of the
|
|
73378
|
+
* forward tail, the position of the email made visible most recently, read
|
|
73379
|
+
* BEFORE the first state check. Anything that becomes visible after it is
|
|
73380
|
+
* returned by the long-poll, so there is no gap between "checked, nothing
|
|
73381
|
+
* awaits" and "started waiting". Taken from the server's answers alone, with
|
|
73382
|
+
* no client clock involved.
|
|
73383
|
+
*
|
|
73384
|
+
* The tail orders and cursors on when an email became visible, which the
|
|
73385
|
+
* API stamps when it accepts the email: after the created_at a list row
|
|
73386
|
+
* carries, and to the microsecond where that created_at is truncated to the
|
|
73387
|
+
* millisecond. So a cursor built from the newest row's created_at sits
|
|
73388
|
+
* before that row on the tail, and the tail would hand it back, with any
|
|
73389
|
+
* older email made visible after it, as new mail. From that cursor the tail
|
|
73390
|
+
* is read forward without waiting until it is caught up, and the position it
|
|
73391
|
+
* gives is the baseline. The read stops after BASELINE_CATCH_UP_PAGES pages;
|
|
73392
|
+
* mail past that would still be handed back, which needs more than that many
|
|
73393
|
+
* pages of mail made visible after the newest row's created_at.
|
|
73099
73394
|
*/
|
|
73100
73395
|
async function baselineCursor(params) {
|
|
73101
|
-
const
|
|
73396
|
+
const api = params.api ?? DEFAULT_API;
|
|
73397
|
+
const newest = (await listPage(api, params.apiClient, { limit: 1 })).rows[0];
|
|
73102
73398
|
const createdAt = str$1(newest?.created_at);
|
|
73103
73399
|
const id = str$1(newest?.id);
|
|
73104
|
-
|
|
73400
|
+
let cursor = createdAt && id ? `${createdAt}|${id}` : EPOCH_CURSOR;
|
|
73401
|
+
for (let read = 0; read < BASELINE_CATCH_UP_PAGES; read += 1) {
|
|
73402
|
+
const tail = await listPage(api, params.apiClient, {
|
|
73403
|
+
limit: SCAN_PAGE_SIZE,
|
|
73404
|
+
since: cursor
|
|
73405
|
+
});
|
|
73406
|
+
if (tail.rows.length === 0 || !tail.cursor) break;
|
|
73407
|
+
cursor = tail.cursor;
|
|
73408
|
+
if (tail.rows.length < SCAN_PAGE_SIZE) break;
|
|
73409
|
+
}
|
|
73410
|
+
return cursor;
|
|
73105
73411
|
}
|
|
73106
73412
|
/**
|
|
73107
73413
|
* Hold one long-poll on the forward tail. Returns the advanced cursor
|
|
@@ -73743,6 +74049,498 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
|
|
|
73743
74049
|
});
|
|
73744
74050
|
}
|
|
73745
74051
|
};
|
|
74052
|
+
// src/oclif/commands/whoami.ts
|
|
74053
|
+
/**
|
|
74054
|
+
* `GET /whoami` with its grant query parameters. A server without grants
|
|
74055
|
+
* ignores them and answers without `grant` or `check`.
|
|
74056
|
+
*/
|
|
74057
|
+
async function fetchCallerIdentity(client, query, options = {}) {
|
|
74058
|
+
const result = await client.get({
|
|
74059
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
74060
|
+
...options.signal ? { signal: options.signal } : {},
|
|
74061
|
+
query,
|
|
74062
|
+
responseStyle: "fields",
|
|
74063
|
+
security: [{
|
|
74064
|
+
scheme: "bearer",
|
|
74065
|
+
type: "http"
|
|
74066
|
+
}],
|
|
74067
|
+
url: "/whoami"
|
|
74068
|
+
});
|
|
74069
|
+
if (result.error) return {
|
|
74070
|
+
identity: null,
|
|
74071
|
+
error: result.error
|
|
74072
|
+
};
|
|
74073
|
+
return {
|
|
74074
|
+
identity: result.data?.data ?? null,
|
|
74075
|
+
error: null
|
|
74076
|
+
};
|
|
74077
|
+
}
|
|
74078
|
+
/** How many rule ids the Grant line names before it says how many more. */
|
|
74079
|
+
const GRANT_LINE_RULES = 3;
|
|
74080
|
+
/** Rule ids for the Grant line: the first few, then how many more. */
|
|
74081
|
+
function ruleIdList(rules) {
|
|
74082
|
+
const shown = rules.slice(0, GRANT_LINE_RULES);
|
|
74083
|
+
const rest = rules.length - shown.length;
|
|
74084
|
+
const all = rest > 0 ? [...shown, `${rest} more`] : shown;
|
|
74085
|
+
return all.length <= 1 ? all[0] ?? "" : `${all.slice(0, -1).join(", ")} and ${all[all.length - 1]}`;
|
|
74086
|
+
}
|
|
74087
|
+
/**
|
|
74088
|
+
* The mail a grant reaches, for the Grant line. A key cannot read email
|
|
74089
|
+
* rules, so a scope that lists them names them by id. A rule grants mail it
|
|
74090
|
+
* matches as the mail arrives, while the rule is active, and the key reads it
|
|
74091
|
+
* only while the rule stays active.
|
|
74092
|
+
*/
|
|
74093
|
+
function grantScopeText(scope) {
|
|
74094
|
+
if (scope === void 0) return "all mail in the organization";
|
|
74095
|
+
const reach = mailReach(scope);
|
|
74096
|
+
switch (reach.kind) {
|
|
74097
|
+
case "org": return "all mail in the organization";
|
|
74098
|
+
case "unreadable": return "a mail scope the server could not read, so no mail";
|
|
74099
|
+
case "none": return "no mail through its grant, which names no mailbox, domain or email rule";
|
|
74100
|
+
case "rules_only":
|
|
74101
|
+
case "mailboxes": {
|
|
74102
|
+
if (reach.rules.length === 0) return "a narrowed mail scope";
|
|
74103
|
+
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`;
|
|
74104
|
+
const more = reach.rules.length > GRANT_LINE_RULES ? " (`primitive whoami --json --grant` lists every rule)" : "";
|
|
74105
|
+
return reach.kind === "mailboxes" ? `a narrowed mail scope, plus ${granted}${more}` : `${granted}${more}`;
|
|
74106
|
+
}
|
|
74107
|
+
default: return "a narrowed mail scope";
|
|
74108
|
+
}
|
|
74109
|
+
}
|
|
74110
|
+
function formatGrant(grant, keyId = null) {
|
|
74111
|
+
const what = grant.template ? `template ${grant.template}` : `${grant.grant_type ?? "grant"} with ${grant.permissions.length} permissions`;
|
|
74112
|
+
const credential = keyId ? `${grant.credential_kind}, key id ${keyId}` : grant.credential_kind;
|
|
74113
|
+
const lines = [`Grant: ${what}, ${grantScopeText(grant.mail_scope)} (${credential})`];
|
|
74114
|
+
if (grant.expires_at) lines.push(`Credential expires: ${grant.expires_at}`);
|
|
74115
|
+
return lines;
|
|
74116
|
+
}
|
|
74117
|
+
/** Lines describing the caller's organization, role and grant. */
|
|
74118
|
+
function formatGrantLines(identity, options = {}) {
|
|
74119
|
+
const lines = [];
|
|
74120
|
+
const org = options.orgName ? `${options.orgName} (${identity.org_id})` : identity.org_id;
|
|
74121
|
+
lines.push(`Organization: ${org}`);
|
|
74122
|
+
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}`);
|
|
74123
|
+
const grant = identity.grant;
|
|
74124
|
+
if (grant) lines.push(...formatGrant(grant, identity.key_id));
|
|
74125
|
+
if (options.userLogin) lines.push("Signed in as yourself. Switch organizations with `primitive org use <org>`.");
|
|
74126
|
+
return lines;
|
|
74127
|
+
}
|
|
74128
|
+
function formatPermissionCheck(check) {
|
|
74129
|
+
if (check.allow) return `${check.permission}: allowed`;
|
|
74130
|
+
return `${check.permission}: not allowed${check.reason ? ` (${check.reason})` : ""}`;
|
|
74131
|
+
}
|
|
74132
|
+
/** Whether GET /whoami answered for an API key. */
|
|
74133
|
+
function isKeyIdentity(identity) {
|
|
74134
|
+
return Boolean(identity.key_id) || identity.auth_method === "api_key";
|
|
74135
|
+
}
|
|
74136
|
+
/** Whether GET /whoami answered for a person's own session or OAuth login. */
|
|
74137
|
+
const PERSON_AUTH_METHODS = /* @__PURE__ */ new Set([
|
|
74138
|
+
"oauth",
|
|
74139
|
+
"session",
|
|
74140
|
+
"dashboard_bridge"
|
|
74141
|
+
]);
|
|
74142
|
+
function isPersonIdentity(identity) {
|
|
74143
|
+
return !isKeyIdentity(identity) && typeof identity.auth_method === "string" && PERSON_AUTH_METHODS.has(identity.auth_method);
|
|
74144
|
+
}
|
|
74145
|
+
/** What the summary's first line says about who is calling. */
|
|
74146
|
+
function identityLine(identity) {
|
|
74147
|
+
if (!identity || identity.member_address === void 0) return "Authenticated";
|
|
74148
|
+
if (identity.member_address) return `Authenticated as ${identity.member_address.address} (your member address)`;
|
|
74149
|
+
if (isKeyIdentity(identity)) return "Authenticated (API keys have no member address)";
|
|
74150
|
+
if (!isPersonIdentity(identity)) return "Authenticated (this credential has no member address)";
|
|
74151
|
+
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` : ""})`;
|
|
74152
|
+
}
|
|
74153
|
+
function formatWhoamiSummary(account, managedInboxDomain, orgName = null, identity = null) {
|
|
74154
|
+
const lines = [
|
|
74155
|
+
identityLine(identity),
|
|
74156
|
+
orgName ? `Organization: ${orgName} (${account.id})` : `Organization id: ${account.id}`,
|
|
74157
|
+
...account.email ? [`Account email: ${account.email} (the email the account was opened with)`] : [],
|
|
74158
|
+
`Plan: ${account.plan}`
|
|
74159
|
+
];
|
|
74160
|
+
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
74161
|
+
return lines.join("\n");
|
|
74162
|
+
}
|
|
74163
|
+
/** `GET /whoami/grant`, which any credential may ask about itself. */
|
|
74164
|
+
async function fetchOwnGrant(client) {
|
|
74165
|
+
try {
|
|
74166
|
+
const result = await getOwnGrant({
|
|
74167
|
+
client,
|
|
74168
|
+
responseStyle: "fields"
|
|
74169
|
+
});
|
|
74170
|
+
if (result.error) return null;
|
|
74171
|
+
return result.data?.data ?? null;
|
|
74172
|
+
} catch {
|
|
74173
|
+
return null;
|
|
74174
|
+
}
|
|
74175
|
+
}
|
|
74176
|
+
/** The summary for a valid credential whose grant cannot read the account. */
|
|
74177
|
+
function formatOwnGrantSummary(own, orgName) {
|
|
74178
|
+
return [
|
|
74179
|
+
"Authenticated. This credential cannot read the account (it lacks org:read).",
|
|
74180
|
+
orgName ? `Organization: ${orgName} (${own.org_id})` : `Organization id: ${own.org_id}`,
|
|
74181
|
+
...own.grant ? formatGrant(own.grant, own.key_id) : []
|
|
74182
|
+
].join("\n");
|
|
74183
|
+
}
|
|
74184
|
+
var WhoamiCommand = class WhoamiCommand extends Command {
|
|
74185
|
+
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.
|
|
74186
|
+
|
|
74187
|
+
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).
|
|
74188
|
+
|
|
74189
|
+
The default output is a concise human summary. Pass --json for the full /account response or the explicit offline connected-agent identity envelope.`;
|
|
74190
|
+
static summary = "Print the live account or saved connected-agent identity";
|
|
74191
|
+
static examples = [
|
|
74192
|
+
"<%= config.bin %> whoami",
|
|
74193
|
+
"<%= config.bin %> whoami --api-key prim_...",
|
|
74194
|
+
"<%= config.bin %> whoami --json | jq .id",
|
|
74195
|
+
"PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> whoami --json"
|
|
74196
|
+
];
|
|
74197
|
+
static flags = {
|
|
74198
|
+
"api-key": Flags.string({
|
|
74199
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
74200
|
+
env: "PRIMITIVE_API_KEY"
|
|
74201
|
+
}),
|
|
74202
|
+
"api-base-url": Flags.string({
|
|
74203
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
74204
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
74205
|
+
hidden: true
|
|
74206
|
+
}),
|
|
74207
|
+
json: Flags.boolean({ description: "Print the account JSON response, or the saved connected identity marked verification: offline. Never prints credentials." }),
|
|
74208
|
+
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." }),
|
|
74209
|
+
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." }),
|
|
74210
|
+
resource: Flags.string({
|
|
74211
|
+
description: "With --check, the resource to check against, as function:<id>, domain:<id>, endpoint:<id> or route:<id>.",
|
|
74212
|
+
dependsOn: ["check"]
|
|
74213
|
+
}),
|
|
74214
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
74215
|
+
};
|
|
74216
|
+
async run() {
|
|
74217
|
+
const { flags } = await this.parse(WhoamiCommand);
|
|
74218
|
+
await runWithTiming(flags.time, async () => {
|
|
74219
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
74220
|
+
apiKey: flags["api-key"],
|
|
74221
|
+
apiBaseUrl: flags["api-base-url"],
|
|
74222
|
+
configDir: this.config.configDir
|
|
74223
|
+
});
|
|
74224
|
+
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 });
|
|
74225
|
+
if (auth.connectedAgent) {
|
|
74226
|
+
const identity = auth.connectedAgent;
|
|
74227
|
+
const statusCommand = `${cliInvocation(process.argv[1])} agent connect --profile ${identity.profileName} --status --json`;
|
|
74228
|
+
const receiver = receiverStatusCommand(this.config.configDir, identity.profileName);
|
|
74229
|
+
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}\`.`;
|
|
74230
|
+
const source = agentIdentitySource(identity);
|
|
74231
|
+
const selectedBy = source === "session_hook" ? `this Claude Code session's start hook (PRIMITIVE_AGENT_PROFILE=${identity.profileName})` : `PRIMITIVE_AGENT_PROFILE=${identity.profileName}`;
|
|
74232
|
+
this.log(flags.json ? JSON.stringify({
|
|
74233
|
+
address: identity.agentAddress,
|
|
74234
|
+
profile: identity.profileName,
|
|
74235
|
+
source,
|
|
74236
|
+
auth_method: "agent_connection",
|
|
74237
|
+
verification: "offline",
|
|
74238
|
+
identity,
|
|
74239
|
+
status_command: statusCommand,
|
|
74240
|
+
guidance
|
|
74241
|
+
}, 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}`);
|
|
74242
|
+
return;
|
|
74243
|
+
}
|
|
74244
|
+
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 });
|
|
74245
|
+
if (flags.check !== void 0) {
|
|
74246
|
+
const check = flags.check.trim();
|
|
74247
|
+
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 });
|
|
74248
|
+
const { identity, error } = await fetchCallerIdentity(apiClient.client, {
|
|
74249
|
+
check,
|
|
74250
|
+
...flags.resource ? { resource: flags.resource.trim() } : {}
|
|
74251
|
+
});
|
|
74252
|
+
if (error) {
|
|
74253
|
+
const errorPayload = extractErrorPayload(error);
|
|
74254
|
+
writeErrorWithHints(errorPayload);
|
|
74255
|
+
surfaceUnauthorizedHint({
|
|
74256
|
+
auth,
|
|
74257
|
+
baseUrlOverridden,
|
|
74258
|
+
configDir: this.config.configDir,
|
|
74259
|
+
payload: errorPayload
|
|
74260
|
+
});
|
|
74261
|
+
process.exitCode = 1;
|
|
74262
|
+
return;
|
|
74263
|
+
}
|
|
74264
|
+
const decision = identity?.check;
|
|
74265
|
+
if (!decision) throw new Errors.CLIError("This Primitive server does not answer permission checks.", { exit: 1 });
|
|
74266
|
+
this.log(flags.json ? JSON.stringify(decision, null, 2) : formatPermissionCheck(decision));
|
|
74267
|
+
if (!decision.allow) process.exitCode = 1;
|
|
74268
|
+
return;
|
|
74269
|
+
}
|
|
74270
|
+
const result = await getAccount({
|
|
74271
|
+
client: apiClient.client,
|
|
74272
|
+
responseStyle: "fields"
|
|
74273
|
+
});
|
|
74274
|
+
if (result.error) {
|
|
74275
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
74276
|
+
const own = isGrantRefusal(errorPayload) ? await fetchOwnGrant(apiClient.client) : null;
|
|
74277
|
+
if (own) {
|
|
74278
|
+
const orgName = auth.credentials?.org_id === own.org_id ? auth.credentials.org_name ?? null : null;
|
|
74279
|
+
this.log(flags.json ? JSON.stringify({
|
|
74280
|
+
account: null,
|
|
74281
|
+
...own,
|
|
74282
|
+
org_name: orgName
|
|
74283
|
+
}, null, 2) : formatOwnGrantSummary(own, orgName));
|
|
74284
|
+
return;
|
|
74285
|
+
}
|
|
74286
|
+
writeErrorWithHints(errorPayload);
|
|
74287
|
+
surfaceUnauthorizedHint({
|
|
74288
|
+
auth,
|
|
74289
|
+
baseUrlOverridden,
|
|
74290
|
+
configDir: this.config.configDir,
|
|
74291
|
+
payload: errorPayload
|
|
74292
|
+
});
|
|
74293
|
+
process.exitCode = 1;
|
|
74294
|
+
return;
|
|
74295
|
+
}
|
|
74296
|
+
const account = result.data?.data;
|
|
74297
|
+
if (!account) {
|
|
74298
|
+
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
74299
|
+
throw new Errors.CLIError("unexpected empty response");
|
|
74300
|
+
}
|
|
74301
|
+
let managedInboxDomain = null;
|
|
74302
|
+
try {
|
|
74303
|
+
const domainsResult = await listDomains({
|
|
74304
|
+
client: apiClient.client,
|
|
74305
|
+
responseStyle: "fields"
|
|
74306
|
+
});
|
|
74307
|
+
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
74308
|
+
} catch {}
|
|
74309
|
+
const orgName = auth.credentials?.org_id === account.id ? auth.credentials.org_name ?? null : null;
|
|
74310
|
+
const session = guardedSession(void 0, process.env);
|
|
74311
|
+
let registration = null;
|
|
74312
|
+
try {
|
|
74313
|
+
registration = session ? failedSessionRegistration(this.config.configDir, session) : null;
|
|
74314
|
+
} catch {
|
|
74315
|
+
registration = null;
|
|
74316
|
+
}
|
|
74317
|
+
let identity = null;
|
|
74318
|
+
if (!flags.json || flags.grant) try {
|
|
74319
|
+
identity = (await fetchCallerIdentity(apiClient.client, { include: "grant" })).identity;
|
|
74320
|
+
} catch {
|
|
74321
|
+
identity = null;
|
|
74322
|
+
}
|
|
74323
|
+
if (flags.json) {
|
|
74324
|
+
this.log(JSON.stringify({
|
|
74325
|
+
...account,
|
|
74326
|
+
managed_inbox_domain: managedInboxDomain,
|
|
74327
|
+
org_name: orgName,
|
|
74328
|
+
...registration ? { session_registration: registration } : {},
|
|
74329
|
+
...flags.grant ? {
|
|
74330
|
+
grant: identity?.grant ?? null,
|
|
74331
|
+
org_id: identity?.org_id ?? null,
|
|
74332
|
+
role: identity?.role ?? null
|
|
74333
|
+
} : {}
|
|
74334
|
+
}, null, 2));
|
|
74335
|
+
return;
|
|
74336
|
+
}
|
|
74337
|
+
const summary = [formatWhoamiSummary(account, managedInboxDomain, orgName, identity)];
|
|
74338
|
+
if (identity?.grant) summary.push(...formatGrantLines(identity, {
|
|
74339
|
+
orgName: auth.credentials?.org_name ?? null,
|
|
74340
|
+
userLogin: auth.credentials?.auth_method === "user_grant"
|
|
74341
|
+
}).filter((line) => !line.startsWith("Organization:")));
|
|
74342
|
+
this.log(summary.join("\n"));
|
|
74343
|
+
if (registration) this.log(registrationSummaryLine(registration));
|
|
74344
|
+
});
|
|
74345
|
+
}
|
|
74346
|
+
};
|
|
74347
|
+
// src/oclif/key-permissions.ts
|
|
74348
|
+
const KEY_PERMISSION_AREAS = [
|
|
74349
|
+
{
|
|
74350
|
+
label: "Mail",
|
|
74351
|
+
permissions: [
|
|
74352
|
+
"mail:read",
|
|
74353
|
+
"changes:read",
|
|
74354
|
+
"mail.search:semantic",
|
|
74355
|
+
"mail:send",
|
|
74356
|
+
"mail:send_batch",
|
|
74357
|
+
"mail:reply",
|
|
74358
|
+
"mail:signal",
|
|
74359
|
+
"mail.state:write",
|
|
74360
|
+
"mail:delete",
|
|
74361
|
+
"mail.compat:use"
|
|
74362
|
+
]
|
|
74363
|
+
},
|
|
74364
|
+
{
|
|
74365
|
+
label: "Raw mail",
|
|
74366
|
+
permissions: ["mail.raw:read"]
|
|
74367
|
+
},
|
|
74368
|
+
{
|
|
74369
|
+
label: "Scheduled and repeating sends",
|
|
74370
|
+
permissions: ["sent:manage", "sent:cancel"]
|
|
74371
|
+
},
|
|
74372
|
+
{
|
|
74373
|
+
label: "Thread mutes",
|
|
74374
|
+
permissions: ["threads:mute"]
|
|
74375
|
+
},
|
|
74376
|
+
{
|
|
74377
|
+
label: "App sync state",
|
|
74378
|
+
permissions: ["mail_sync:manage"]
|
|
74379
|
+
},
|
|
74380
|
+
{
|
|
74381
|
+
label: "Endpoints and webhooks",
|
|
74382
|
+
permissions: [
|
|
74383
|
+
"webhooks:read",
|
|
74384
|
+
"endpoints:manage",
|
|
74385
|
+
"endpoints.pull:manage",
|
|
74386
|
+
"webhooks:replay"
|
|
74387
|
+
]
|
|
74388
|
+
},
|
|
74389
|
+
{
|
|
74390
|
+
label: "Recipient routes",
|
|
74391
|
+
permissions: ["routes:manage"]
|
|
74392
|
+
},
|
|
74393
|
+
{
|
|
74394
|
+
label: "Filters",
|
|
74395
|
+
permissions: ["filters:manage"]
|
|
74396
|
+
},
|
|
74397
|
+
{
|
|
74398
|
+
label: "Domains",
|
|
74399
|
+
permissions: ["domains:read", "domains:manage"]
|
|
74400
|
+
},
|
|
74401
|
+
{
|
|
74402
|
+
label: "Account",
|
|
74403
|
+
permissions: [
|
|
74404
|
+
"org:read",
|
|
74405
|
+
"account:manage",
|
|
74406
|
+
"account:claim",
|
|
74407
|
+
"credentials:revoke_self"
|
|
74408
|
+
]
|
|
74409
|
+
},
|
|
74410
|
+
{
|
|
74411
|
+
label: "Own activity",
|
|
74412
|
+
permissions: ["activity:read_self"]
|
|
74413
|
+
},
|
|
74414
|
+
{
|
|
74415
|
+
label: "Webhook signing secret",
|
|
74416
|
+
permissions: ["webhook_secret:read", "webhook_secret:manage"]
|
|
74417
|
+
},
|
|
74418
|
+
{
|
|
74419
|
+
label: "Functions",
|
|
74420
|
+
permissions: [
|
|
74421
|
+
"functions:read",
|
|
74422
|
+
"functions:manage",
|
|
74423
|
+
"functions:deploy",
|
|
74424
|
+
"github:manage",
|
|
74425
|
+
"templates:install"
|
|
74426
|
+
]
|
|
74427
|
+
},
|
|
74428
|
+
{
|
|
74429
|
+
label: "Secrets",
|
|
74430
|
+
permissions: ["secrets:manage"]
|
|
74431
|
+
},
|
|
74432
|
+
{
|
|
74433
|
+
label: "Contacts",
|
|
74434
|
+
permissions: [
|
|
74435
|
+
"contacts:read",
|
|
74436
|
+
"contacts:write",
|
|
74437
|
+
"contacts:delete",
|
|
74438
|
+
"agent_contacts:manage"
|
|
74439
|
+
]
|
|
74440
|
+
},
|
|
74441
|
+
{
|
|
74442
|
+
label: "Address notes",
|
|
74443
|
+
permissions: [
|
|
74444
|
+
"notes:read",
|
|
74445
|
+
"notes:write",
|
|
74446
|
+
"notes:delete"
|
|
74447
|
+
]
|
|
74448
|
+
},
|
|
74449
|
+
{
|
|
74450
|
+
label: "Memories",
|
|
74451
|
+
permissions: ["memories:read", "memories:write"]
|
|
74452
|
+
},
|
|
74453
|
+
{
|
|
74454
|
+
label: "Payloads",
|
|
74455
|
+
permissions: ["payloads:read", "payloads:write"]
|
|
74456
|
+
},
|
|
74457
|
+
{
|
|
74458
|
+
label: "Interactions",
|
|
74459
|
+
permissions: [
|
|
74460
|
+
"interactions:read",
|
|
74461
|
+
"interactions:send",
|
|
74462
|
+
"interactions:advance"
|
|
74463
|
+
]
|
|
74464
|
+
},
|
|
74465
|
+
{
|
|
74466
|
+
label: "Wake",
|
|
74467
|
+
permissions: ["wake:manage"]
|
|
74468
|
+
},
|
|
74469
|
+
{
|
|
74470
|
+
label: "Credits",
|
|
74471
|
+
permissions: ["credits:read", "credits:redeem"]
|
|
74472
|
+
},
|
|
74473
|
+
{
|
|
74474
|
+
label: "Push devices",
|
|
74475
|
+
permissions: ["push_devices:manage"]
|
|
74476
|
+
}
|
|
74477
|
+
];
|
|
74478
|
+
/** Held only by a key that reaches every mailbox (--all-mailboxes). */
|
|
74479
|
+
const EVERY_MAILBOX_KEY_PERMISSIONS = ["members:read"];
|
|
74480
|
+
/** The permissions a key can hold, by area, for --help-full. */
|
|
74481
|
+
function keyPermissionsHelp() {
|
|
74482
|
+
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(", ")}.`;
|
|
74483
|
+
}
|
|
74484
|
+
/** How long the refusal hint waits on the permission check. */
|
|
74485
|
+
const CHECK_TIMEOUT_MS = 5e3;
|
|
74486
|
+
/**
|
|
74487
|
+
* The permission a refusal says cannot be granted to a key, or null for any
|
|
74488
|
+
* other refusal.
|
|
74489
|
+
*/
|
|
74490
|
+
function ungrantablePermission(payload) {
|
|
74491
|
+
const error = extractErrorPayload(payload);
|
|
74492
|
+
if (extractErrorCode(error) !== "validation_error") return null;
|
|
74493
|
+
const message = error && typeof error === "object" ? error.message : void 0;
|
|
74494
|
+
if (typeof message !== "string") return null;
|
|
74495
|
+
return /^(.+) cannot be granted to a key$/.exec(message.trim())?.[1] ?? null;
|
|
74496
|
+
}
|
|
74497
|
+
/**
|
|
74498
|
+
* Whether the API knows `name` as a permission at all: GET /whoami?check
|
|
74499
|
+
* answers a check for any permission it knows and refuses any other name
|
|
74500
|
+
* with "unknown permission". Null when the answer says neither, or does not
|
|
74501
|
+
* come in time: the hint never holds the command up for longer than that.
|
|
74502
|
+
*/
|
|
74503
|
+
async function isKnownPermission(client, name) {
|
|
74504
|
+
let timer;
|
|
74505
|
+
const late = new Promise((resolve) => {
|
|
74506
|
+
timer = setTimeout(() => resolve(null), CHECK_TIMEOUT_MS);
|
|
74507
|
+
});
|
|
74508
|
+
try {
|
|
74509
|
+
return await Promise.race([checkPermission(client, name), late]);
|
|
74510
|
+
} finally {
|
|
74511
|
+
clearTimeout(timer);
|
|
74512
|
+
}
|
|
74513
|
+
}
|
|
74514
|
+
async function checkPermission(client, name) {
|
|
74515
|
+
try {
|
|
74516
|
+
const { identity, error } = await fetchCallerIdentity(client, { check: name }, {
|
|
74517
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
74518
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
|
|
74519
|
+
});
|
|
74520
|
+
if (error) {
|
|
74521
|
+
const payload = extractErrorPayload(error);
|
|
74522
|
+
const message = payload && typeof payload === "object" ? payload.message : void 0;
|
|
74523
|
+
return extractErrorCode(payload) === "validation_error" && typeof message === "string" && message.startsWith("unknown permission") ? false : null;
|
|
74524
|
+
}
|
|
74525
|
+
return identity?.check ? true : null;
|
|
74526
|
+
} catch {
|
|
74527
|
+
return null;
|
|
74528
|
+
}
|
|
74529
|
+
}
|
|
74530
|
+
/**
|
|
74531
|
+
* The hint after "<name> cannot be granted to a key": whether the name is
|
|
74532
|
+
* not a permission at all or is one a key cannot hold, then what a key can
|
|
74533
|
+
* hold, as this CLI lists it. `asked` is the permissions the command asked
|
|
74534
|
+
* for, when the request also carried others (an update sends the key's own
|
|
74535
|
+
* permissions too), so a refusal of one the key already holds says so.
|
|
74536
|
+
*/
|
|
74537
|
+
async function ungrantablePermissionHint(client, name, asked) {
|
|
74538
|
+
const known = await isKnownPermission(client, name);
|
|
74539
|
+
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.`;
|
|
74540
|
+
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}.`;
|
|
74541
|
+
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.`;
|
|
74542
|
+
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.`;
|
|
74543
|
+
}
|
|
73746
74544
|
// src/oclif/commands/keys.ts
|
|
73747
74545
|
const STORE_SECRET_NOTICE = "Store this key now. It is shown only once, and the CLI does not save it.";
|
|
73748
74546
|
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 +74671,12 @@ function describePermissions(grant) {
|
|
|
73873
74671
|
const permissions = grant.permissions ?? [];
|
|
73874
74672
|
return permissions.length > 0 ? permissions.join(", ") : "none";
|
|
73875
74673
|
}
|
|
73876
|
-
function formatKeyRows(rows) {
|
|
73877
|
-
if (rows.length === 0) return "No API keys.";
|
|
74674
|
+
function formatKeyRows(rows, options = {}) {
|
|
74675
|
+
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
74676
|
return rows.map((row) => {
|
|
73879
|
-
const suspended = row.suspended ? " [suspended]" : "";
|
|
73880
|
-
|
|
74677
|
+
const suspended = row.suspended && row.status !== "revoked" ? " [suspended]" : "";
|
|
74678
|
+
const revoked = row.status === "revoked" ? ` [revoked${row.revoked_at ? ` ${row.revoked_at}` : ""}]` : "";
|
|
74679
|
+
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
74680
|
}).join("\n");
|
|
73882
74681
|
}
|
|
73883
74682
|
function formatKeyDetail(key) {
|
|
@@ -74131,9 +74930,11 @@ async function authenticate(command, flags) {
|
|
|
74131
74930
|
});
|
|
74132
74931
|
}
|
|
74133
74932
|
/** Print a refusal and set a failing exit code. */
|
|
74134
|
-
function reportKeysError(command, outcome, auth, json) {
|
|
74933
|
+
async function reportKeysError(command, outcome, auth, json, askedPermissions) {
|
|
74135
74934
|
if (json) process.stderr.write(`${JSON.stringify(outcome.payload)}\n`);
|
|
74136
74935
|
else writeErrorWithHints(outcome.payload);
|
|
74936
|
+
const ungrantable = ungrantablePermission(outcome.payload);
|
|
74937
|
+
if (ungrantable) process.stderr.write(`${await ungrantablePermissionHint(auth.apiClient.client, ungrantable, askedPermissions)}\n`);
|
|
74137
74938
|
if (outcome.stepUpUrl) process.stderr.write(approveThenRerunHint(outcome.stepUpUrl));
|
|
74138
74939
|
surfaceUnauthorizedHint({
|
|
74139
74940
|
auth: auth.auth,
|
|
@@ -74149,26 +74950,37 @@ function writeWarnings(warnings) {
|
|
|
74149
74950
|
var KeysListCommand = class KeysListCommand extends Command {
|
|
74150
74951
|
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
74952
|
|
|
74152
|
-
You see your own keys, and every key if your role may read them all, as in the dashboard
|
|
74953
|
+
You see your own keys, and every key if your role may read them all, as in the dashboard.
|
|
74954
|
+
|
|
74955
|
+
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
74956
|
static summary = "List API keys";
|
|
74154
|
-
static examples = [
|
|
74957
|
+
static examples = [
|
|
74958
|
+
"<%= config.bin %> keys list",
|
|
74959
|
+
"<%= config.bin %> keys list --json",
|
|
74960
|
+
"<%= config.bin %> keys list --include-revoked"
|
|
74961
|
+
];
|
|
74155
74962
|
static flags = {
|
|
74156
74963
|
"api-key": API_KEY_FLAG$1,
|
|
74157
74964
|
"api-base-url": API_BASE_URL_FLAG$2,
|
|
74965
|
+
"include-revoked": Flags.boolean({ description: "List revoked keys too, each marked [revoked] with when it was revoked." }),
|
|
74158
74966
|
json: Flags.boolean({ description: "Print the keys as JSON." })
|
|
74159
74967
|
};
|
|
74160
74968
|
async run() {
|
|
74161
74969
|
const { flags } = await this.parse(KeysListCommand);
|
|
74970
|
+
const includeRevoked = flags["include-revoked"] === true;
|
|
74162
74971
|
const auth = await authenticate(this, flags);
|
|
74163
74972
|
const items = [];
|
|
74164
74973
|
let cursor;
|
|
74165
74974
|
for (let page = 0; page < LIST_PAGE_LIMIT; page += 1) {
|
|
74166
74975
|
const result = await call(listKeys({
|
|
74167
74976
|
client: auth.apiClient.client,
|
|
74168
|
-
|
|
74977
|
+
query: {
|
|
74978
|
+
cursor,
|
|
74979
|
+
include_revoked: includeRevoked ? "true" : void 0
|
|
74980
|
+
}
|
|
74169
74981
|
}));
|
|
74170
74982
|
if (!result.ok) {
|
|
74171
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74983
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74172
74984
|
return;
|
|
74173
74985
|
}
|
|
74174
74986
|
items.push(...result.data.items);
|
|
@@ -74179,7 +74991,7 @@ var KeysListCommand = class KeysListCommand extends Command {
|
|
|
74179
74991
|
this.log(JSON.stringify(items, null, 2));
|
|
74180
74992
|
return;
|
|
74181
74993
|
}
|
|
74182
|
-
this.log(formatKeyRows(items));
|
|
74994
|
+
this.log(formatKeyRows(items, { includeRevoked }));
|
|
74183
74995
|
}
|
|
74184
74996
|
};
|
|
74185
74997
|
var KeysGetCommand = class KeysGetCommand extends Command {
|
|
@@ -74203,7 +75015,7 @@ var KeysGetCommand = class KeysGetCommand extends Command {
|
|
|
74203
75015
|
path: { id: args.id }
|
|
74204
75016
|
}));
|
|
74205
75017
|
if (!result.ok) {
|
|
74206
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75018
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74207
75019
|
return;
|
|
74208
75020
|
}
|
|
74209
75021
|
this.log(flags.json ? JSON.stringify(result.data, null, 2) : formatKeyDetail(result.data));
|
|
@@ -74229,7 +75041,9 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74229
75041
|
|
|
74230
75042
|
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
75043
|
|
|
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
|
|
75044
|
+
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.
|
|
75045
|
+
|
|
75046
|
+
${keyPermissionsHelp()}`;
|
|
74233
75047
|
static summary = "Create an API key";
|
|
74234
75048
|
static examples = [
|
|
74235
75049
|
"<%= config.bin %> keys create --name reader --preset read_only",
|
|
@@ -74246,7 +75060,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74246
75060
|
options: [...KEY_PRESETS]
|
|
74247
75061
|
}),
|
|
74248
75062
|
permission: Flags.string({
|
|
74249
|
-
description: "A permission the key holds (repeatable, or comma-separated), for a custom key.",
|
|
75063
|
+
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
75064
|
multiple: true
|
|
74251
75065
|
}),
|
|
74252
75066
|
address: Flags.string({
|
|
@@ -74301,7 +75115,7 @@ var KeysCreateCommand = class KeysCreateCommand extends Command {
|
|
|
74301
75115
|
}
|
|
74302
75116
|
})), approvalIo(flags));
|
|
74303
75117
|
if (!result.ok) {
|
|
74304
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75118
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74305
75119
|
return;
|
|
74306
75120
|
}
|
|
74307
75121
|
const key = result.data;
|
|
@@ -74372,7 +75186,7 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
|
|
|
74372
75186
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74373
75187
|
})), approvalIo(flags));
|
|
74374
75188
|
if (!result.ok) {
|
|
74375
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75189
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74376
75190
|
return;
|
|
74377
75191
|
}
|
|
74378
75192
|
const rotated = result.data;
|
|
@@ -74382,7 +75196,7 @@ var KeysRotateCommand = class KeysRotateCommand extends Command {
|
|
|
74382
75196
|
var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
74383
75197
|
static description = `Change an API key's permissions, email rules, name or expiry. It keeps its id and secret.
|
|
74384
75198
|
|
|
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.
|
|
75199
|
+
--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
75200
|
|
|
74387
75201
|
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
75202
|
|
|
@@ -74412,7 +75226,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74412
75226
|
multiple: true
|
|
74413
75227
|
}),
|
|
74414
75228
|
rule: Flags.string({
|
|
74415
|
-
description: "
|
|
75229
|
+
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
75230
|
multiple: true,
|
|
74417
75231
|
exclusive: ["add-rule", "remove-rule"]
|
|
74418
75232
|
}),
|
|
@@ -74470,7 +75284,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74470
75284
|
path: { id: args.id }
|
|
74471
75285
|
}));
|
|
74472
75286
|
if (!current.ok) {
|
|
74473
|
-
reportKeysError(this, current, auth, Boolean(flags.json));
|
|
75287
|
+
await reportKeysError(this, current, auth, Boolean(flags.json));
|
|
74474
75288
|
return;
|
|
74475
75289
|
}
|
|
74476
75290
|
if (current.data.status === "revoked") this.error(`Key ${args.id} is revoked, so it cannot be changed.`, { exit: 1 });
|
|
@@ -74511,7 +75325,7 @@ var KeysUpdateCommand = class KeysUpdateCommand extends Command {
|
|
|
74511
75325
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74512
75326
|
})), approvalIo(flags));
|
|
74513
75327
|
if (!result.ok) {
|
|
74514
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75328
|
+
await reportKeysError(this, result, auth, Boolean(flags.json), add);
|
|
74515
75329
|
return;
|
|
74516
75330
|
}
|
|
74517
75331
|
const grantChanged = result.data.changed.includes("grant");
|
|
@@ -74597,7 +75411,7 @@ var KeysRevokeCommand = class KeysRevokeCommand extends Command {
|
|
|
74597
75411
|
...token ? { headers: { [STEP_UP_HEADER$1]: token } } : {}
|
|
74598
75412
|
})), approvalIo(flags));
|
|
74599
75413
|
if (!result.ok) {
|
|
74600
|
-
reportKeysError(this, result, auth, Boolean(flags.json));
|
|
75414
|
+
await reportKeysError(this, result, auth, Boolean(flags.json));
|
|
74601
75415
|
return;
|
|
74602
75416
|
}
|
|
74603
75417
|
if (flags.json) {
|
|
@@ -83161,7 +83975,9 @@ var SendCommand = class SendCommand extends Command {
|
|
|
83161
83975
|
\`primitive account whoami\` reports) when you may send from it; a key
|
|
83162
83976
|
whose grant names exactly one address sends from that address. Otherwise
|
|
83163
83977
|
there is no default: the command lists the addresses you may send from
|
|
83164
|
-
and asks for --from.
|
|
83978
|
+
and asks for --from. A credential without mail:send is refused with
|
|
83979
|
+
grant_forbidden naming mail:send instead, since no --from would help.
|
|
83980
|
+
Without --json, stderr names the From it used.
|
|
83165
83981
|
--subject defaults to the first line of the body when omitted.
|
|
83166
83982
|
--attachment attaches a file; repeat it to attach multiple files.
|
|
83167
83983
|
--header adds a custom header such as "X-Campaign-Id: spring-sale";
|
|
@@ -83216,7 +84032,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
83216
84032
|
description: "Recipient address (e.g. alice@example.com).",
|
|
83217
84033
|
required: true
|
|
83218
84034
|
}),
|
|
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." }),
|
|
84035
|
+
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
84036
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
83221
84037
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
83222
84038
|
"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 +85441,11 @@ const WAIT_EXIT_CODES = {
|
|
|
84625
85441
|
const MAX_LONG_POLL_SECONDS = 30;
|
|
84626
85442
|
const TAIL_PAGE_SIZE = 100;
|
|
84627
85443
|
const EXISTING_PAGE_SIZE = 50;
|
|
85444
|
+
/**
|
|
85445
|
+
* Pages of mail made visible since the start that are read before the
|
|
85446
|
+
* long-poll; any more are walked by the long-poll itself.
|
|
85447
|
+
*/
|
|
85448
|
+
const SINCE_START_PAGES = 5;
|
|
84628
85449
|
const PRINTED_BODY_CHARS = 1200;
|
|
84629
85450
|
const JSON_TEXT_CHARS = 4e3;
|
|
84630
85451
|
/**
|
|
@@ -84765,9 +85586,20 @@ async function firstMatch(api, apiClient, rows, filters, seen) {
|
|
|
84765
85586
|
}
|
|
84766
85587
|
return null;
|
|
84767
85588
|
}
|
|
84768
|
-
|
|
84769
|
-
|
|
84770
|
-
|
|
85589
|
+
/**
|
|
85590
|
+
* The forward-tail cursor for an instant, on the client clock: the tail
|
|
85591
|
+
* returns what became visible after it.
|
|
85592
|
+
*/
|
|
85593
|
+
function tailCursorAt(at) {
|
|
85594
|
+
return `${new Date(at).toISOString().replace(/Z$/, "000Z")}|${TIME_CURSOR_ID}`;
|
|
85595
|
+
}
|
|
85596
|
+
/**
|
|
85597
|
+
* Whether tail cursor `a` sorts before `b`. A cursor's time is microseconds
|
|
85598
|
+
* from the tail and milliseconds from a list row, so it is padded first.
|
|
85599
|
+
*/
|
|
85600
|
+
function cursorBefore(a, b) {
|
|
85601
|
+
const pad = (cursor) => cursor.replace(/(\.\d{3})Z\|/, "$1000Z|");
|
|
85602
|
+
return pad(a) < pad(b);
|
|
84771
85603
|
}
|
|
84772
85604
|
/**
|
|
84773
85605
|
* Block until an email matching `filters` arrives (or, with
|
|
@@ -84795,11 +85627,21 @@ async function tailForEmail(api, params) {
|
|
|
84795
85627
|
apiClient,
|
|
84796
85628
|
api
|
|
84797
85629
|
});
|
|
84798
|
-
|
|
84799
|
-
|
|
84800
|
-
|
|
85630
|
+
let fromStart = tailCursorAt(params.startedAt);
|
|
85631
|
+
let caughtUp = false;
|
|
85632
|
+
for (let read = 0; read < SINCE_START_PAGES && !caughtUp; read += 1) {
|
|
85633
|
+
const page = await listRows(api, apiClient, {
|
|
85634
|
+
limit: TAIL_PAGE_SIZE,
|
|
85635
|
+
since: fromStart
|
|
85636
|
+
});
|
|
85637
|
+
const early = await firstMatch(api, apiClient, page.rows, filters, seen);
|
|
85638
|
+
if (early) return early;
|
|
85639
|
+
if (page.rows.length < TAIL_PAGE_SIZE || !page.cursor) caughtUp = true;
|
|
85640
|
+
else fromStart = page.cursor;
|
|
85641
|
+
}
|
|
85642
|
+
if (!caughtUp && cursorBefore(fromStart, since)) since = fromStart;
|
|
84801
85643
|
if (params.includeExisting) {
|
|
84802
|
-
const found = await firstMatch(api, apiClient,
|
|
85644
|
+
const found = await firstMatch(api, apiClient, (await listRows(api, apiClient, { limit: EXISTING_PAGE_SIZE })).rows, filters, seen);
|
|
84803
85645
|
if (found) return found;
|
|
84804
85646
|
}
|
|
84805
85647
|
for (;;) {
|
|
@@ -85592,265 +86434,6 @@ var WakeSchedulesUpdateCommand = class WakeSchedulesUpdateCommand extends Comman
|
|
|
85592
86434
|
});
|
|
85593
86435
|
}
|
|
85594
86436
|
};
|
|
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
86437
|
// src/oclif/fish-completion.ts
|
|
85855
86438
|
function fishEscape(value) {
|
|
85856
86439
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|