primcli 1.74.1 → 3.0.0-preview.1

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.
Files changed (31) hide show
  1. package/README.md +165 -72
  2. package/bin/claude-pending-mail.mjs +1 -1
  3. package/bin/run.js +14 -0
  4. package/dist/{api-command-fKP3NYa7.js → api-command-ctHBqTL5.js} +825 -95
  5. package/dist/{cli-config-C0Y_Ppol.js → cli-config-Ms_0vNjq.js} +66 -28
  6. package/dist/{client.gen-CaaMP86o.js → client.gen-6rZvmHfx.js} +7 -14
  7. package/dist/{json-output-Ce10AsSb.js → json-output-BzSBR-KX.js} +1 -2
  8. package/dist/{listen-background-cD6n8VYO.js → listen-background-C0F9gNDD.js} +2 -3
  9. package/dist/{machine-session-B6V5-grn.js → machine-session-Cp_eQ9t_.js} +164 -99
  10. package/dist/{notify-session-DJAb9SGm.js → notify-session-BN0JTPZ3.js} +341 -31
  11. package/dist/oclif/auto-signal-worker.js +5 -6
  12. package/dist/oclif/auto-signals.js +4 -5
  13. package/dist/oclif/global-org-flag.js +76 -0
  14. package/dist/oclif/help.js +127 -0
  15. package/dist/oclif/index.js +93239 -31954
  16. package/dist/oclif/listen-supervisor.js +1 -1
  17. package/dist/oclif/proxy-auto-detect.js +2 -3
  18. package/dist/oclif/receiver-heal.js +6 -6
  19. package/dist/oclif/root-signup-hint.js +3 -4
  20. package/dist/receiver.d.ts +95 -0
  21. package/dist/receiver.js +569 -0
  22. package/dist/{repeats-BL7ZC-_6-B_ESsWTH.js → repeats-Dj_o6c-X-Bf9G0xds.js} +4617 -1744
  23. package/dist/{scoped-chat-DAiOb622.js → scoped-chat-DZ42V3jq.js} +3 -5
  24. package/dist/{shared-mail-files-CZfm7E2k.js → shared-mail-files-BAQSGxxz.js} +2 -4
  25. package/dist/{shared-mail-watch-DyQB0MK0.js → shared-mail-watch-BAPMsFNv.js} +2 -3
  26. package/dist/{signal-command-Cte4mAL3.js → signal-command-CRUMUCNa.js} +19 -27
  27. package/dist/skills/primitive-connect/references/connect.md +3 -1
  28. package/dist/skills/primitive-connect/references/contacts.md +9 -4
  29. package/dist/skills/primitive-connect.json +4 -4
  30. package/man/primitive.1 +44 -3
  31. package/package.json +65 -15
@@ -1,11 +1,603 @@
1
- import { $ as AutomatedFilterUnsupportedError, Cn as sdk_gen_exports, F as assertAwaitingFilterRows, Ft as getEmail, G as WEBHOOK_EVENT_TYPES, I as awaitingRejectedError, L as ensureSearchReportsReplyState, P as ReplyStateUnsupportedError, U as replyStateSurfaceForOperation, V as isAwaitingRejectedError, W as SENT_EMAIL_EVENT_TYPES, Wt as getThread, et as assertAutomatedVerdict, it as expectedAutomatedVerdicts, nn as listEndpoints, nt as automatedSurfaceForOperation, ot as isAutomatedRejectedError, rt as ensureSearchReportsAutomated, tt as automatedRejectedError, v as parseRetryAfter } from "./repeats-BL7ZC-_6-B_ESsWTH.js";
2
- import { C as resolveCliAuth, G as PrimitiveApiClient, I as agentProfilesDirectory, M as AGENT_PROFILE_ENV, P as agentProfileDirectory, S as normalizeApiBaseUrl, T as saveSignupCredentials, U as requireDefaultLoginProfile, _ as deleteCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, i as loadCliConfig, m as cliAccessTokenExpiresAt, p as acquireCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname, z as loadConnectedAgentProfile } from "./cli-config-C0Y_Ppol.js";
3
- import { d as readMailJson, h as writeMailJson, m as withMailLock, p as syncMailDirectory, u as privateMailPermissions } from "./shared-mail-files-CZfm7E2k.js";
1
+ import { $ as AutomatedFilterUnsupportedError, An as sdk_gen_exports, F as assertAwaitingFilterRows, G as WEBHOOK_EVENT_TYPES, I as awaitingRejectedError, It as getEmail, Kt as getThread, L as ensureSearchReportsReplyState, P as ReplyStateUnsupportedError, U as replyStateSurfaceForOperation, V as isAwaitingRejectedError, W as SENT_EMAIL_EVENT_TYPES, et as assertAutomatedVerdict, it as expectedAutomatedVerdicts, nt as automatedSurfaceForOperation, on as listEndpoints, ot as isAutomatedRejectedError, rt as ensureSearchReportsAutomated, tt as automatedRejectedError, v as parseRetryAfter } from "./repeats-Dj_o6c-X-Bf9G0xds.js";
2
+ import { C as saveCliCredentials, F as agentProfilesDirectory, H as requireDefaultLoginProfile, N as agentProfileDirectory, R as loadConnectedAgentProfile, S as resolveCliAuth, W as PrimitiveApiClient, _ as deleteCliCredentials, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, i as loadCliConfig, j as AGENT_PROFILE_ENV, m as cliAccessTokenExpiresAt, p as acquireCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "./cli-config-Ms_0vNjq.js";
3
+ import "./oclif/global-org-flag.js";
4
+ import { d as readMailJson, h as writeMailJson, m as withMailLock, p as syncMailDirectory, u as privateMailPermissions } from "./shared-mail-files-BAQSGxxz.js";
4
5
  import { Command, Errors, Flags } from "@oclif/core";
5
6
  import { existsSync, lstatSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
6
7
  import { createHash } from "node:crypto";
7
8
  import { join } from "node:path";
8
- //#region src/oclif/api-client.ts
9
+ // src/oclif/user-grant.ts
10
+ /**
11
+ * CLI user logins: a login as the person, not bound to one organization,
12
+ * that switches between the person's organizations without logging in again.
13
+ *
14
+ * The saved login holds the person's identity token (`prim_uat_`, short
15
+ * lived) and refresh token (`prim_urt_`). Neither calls the API: the
16
+ * identity token only lists organizations (`GET /v1/cli/orgs`) and is
17
+ * exchanged (RFC 8693, at `/oauth/token`) for a short-lived access token in
18
+ * one organization. That per-org token is what every other command sends.
19
+ * It cannot be refreshed, so the CLI exchanges again when it expires or
20
+ * when the API answers 401.
21
+ *
22
+ * Saved logins made by older CLIs, and logins against servers without this
23
+ * feature, keep their org-bound OAuth format (`auth_method: "oauth"`) and
24
+ * never reach this module.
25
+ */
26
+ const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
27
+ const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
28
+ /** Exchange or refresh this long before a token's stated expiry. */
29
+ const TOKEN_SKEW_MS = 6e4;
30
+ /** Cached tokens for organizations other than the current one. */
31
+ const MAX_CACHED_ORG_TOKENS = 20;
32
+ const USER_LOGIN_EXPIRED_MESSAGE = "Saved Primitive CLI login expired or was revoked. Run `primitive login` to sign in again.";
33
+ const NO_CURRENT_ORG_MESSAGE = "No organization is selected for this login. Run `primitive orgs` to see where you can use the CLI, then `primitive org use <org>`.";
34
+ const REFUSAL_FALLBACK_MESSAGES = {
35
+ not_a_member: "You are not a member of this organization.",
36
+ cli_access_disabled_by_org: "This organization does not allow CLI access.",
37
+ role_insufficient: "Your role in this organization does not allow CLI access. Ask an owner or admin to allow CLI access for members.",
38
+ cli_access_revoked: "Your CLI access to this organization was revoked. Run `primitive login` again to sign in afresh.",
39
+ grant_not_available: "The requested grant is not available for CLI logins on this server."
40
+ };
41
+ /** A refusal from the exchange or the org list, with the server's reason. */
42
+ var CliOrgRefusedError = class extends Errors.CLIError {
43
+ reason;
44
+ orgId;
45
+ constructor(params) {
46
+ super(params.message, {
47
+ code: params.reason,
48
+ exit: 1
49
+ });
50
+ this.reason = params.reason;
51
+ this.orgId = params.orgId;
52
+ }
53
+ };
54
+ function isRecord(value) {
55
+ return value !== null && typeof value === "object" && !Array.isArray(value);
56
+ }
57
+ function apiOrigin(apiBaseUrl) {
58
+ const url = new URL(apiBaseUrl);
59
+ url.search = "";
60
+ url.hash = "";
61
+ return url;
62
+ }
63
+ function oauthEndpoint(apiBaseUrl, path) {
64
+ const url = apiOrigin(apiBaseUrl);
65
+ url.pathname = path;
66
+ return url.toString();
67
+ }
68
+ function cliOrgsEndpoint(apiBaseUrl) {
69
+ const url = apiOrigin(apiBaseUrl);
70
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}/cli/orgs`;
71
+ return url.toString();
72
+ }
73
+ function expiringSoon(expiresAt, now) {
74
+ const at = Date.parse(expiresAt);
75
+ return !Number.isFinite(at) || at <= now() + TOKEN_SKEW_MS;
76
+ }
77
+ function isUuid(value) {
78
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
79
+ }
80
+ function isOrgAccessToken(value) {
81
+ return isRecord(value) && typeof value.access_token === "string" && value.access_token.length > 0 && value.token_type === "Bearer" && typeof value.expires_in === "number" && value.expires_in > 0 && typeof value.org_id === "string" && (value.org_name === null || typeof value.org_name === "string") && typeof value.oauth_grant_id === "string";
82
+ }
83
+ function orgTokenFromResponse(token, now = Date.now) {
84
+ return {
85
+ access_token: token.access_token,
86
+ expires_at: cliAccessTokenExpiresAt(token.expires_in, now),
87
+ oauth_grant_id: token.oauth_grant_id,
88
+ org_id: token.org_id,
89
+ org_name: token.org_name ?? null,
90
+ role: typeof token.role === "string" ? token.role : null
91
+ };
92
+ }
93
+ /**
94
+ * The org-bound view every other command reads: the per-org token in the
95
+ * standard fields, the person's grant kept alongside.
96
+ */
97
+ function credentialsForOrg(credentials, org) {
98
+ return {
99
+ ...credentials,
100
+ access_token: org.access_token,
101
+ expires_at: org.expires_at,
102
+ oauth_grant_id: org.oauth_grant_id,
103
+ org_id: org.org_id,
104
+ org_name: org.org_name
105
+ };
106
+ }
107
+ function requireUserGrant(credentials) {
108
+ if (credentials.auth_method !== "user_grant" || !credentials.user_grant) throw new Errors.CLIError("This saved login is bound to one organization. Run `primitive logout`, then `primitive login`, to sign in as yourself and switch between organizations.", { exit: 1 });
109
+ return credentials.user_grant;
110
+ }
111
+ async function postTokenEndpoint(params) {
112
+ const fetchImpl = params.fetch ?? fetch;
113
+ let response;
114
+ try {
115
+ response = await fetchImpl(oauthEndpoint(params.apiBaseUrl, params.path ?? "/oauth/token"), {
116
+ body: new URLSearchParams(params.body),
117
+ headers: {
118
+ ...params.headers ?? {},
119
+ "content-type": "application/x-www-form-urlencoded"
120
+ },
121
+ method: "POST"
122
+ });
123
+ } catch (error) {
124
+ const detail = error instanceof Error ? error.message : String(error);
125
+ throw new Errors.CLIError(`Could not reach the Primitive OAuth token endpoint: ${detail}`, { exit: 1 });
126
+ }
127
+ const payload = await response.json().catch(() => null);
128
+ if (response.ok && isRecord(payload)) return {
129
+ ok: true,
130
+ payload
131
+ };
132
+ if (response.ok && params.allowEmptySuccess) return {
133
+ ok: true,
134
+ payload: {}
135
+ };
136
+ const record = isRecord(payload) ? payload : {};
137
+ return {
138
+ ok: false,
139
+ status: response.status,
140
+ error: typeof record.error === "string" ? record.error : null,
141
+ description: typeof record.error_description === "string" ? record.error_description : null,
142
+ reason: typeof record.reason === "string" ? record.reason : null
143
+ };
144
+ }
145
+ /**
146
+ * Refresh the identity token when it is about to expire. The refresh token
147
+ * rotates on every use, so the new pair is saved before it is used.
148
+ * `invalid_grant` means the login is gone: the saved login is removed.
149
+ */
150
+ async function freshIdentity(params) {
151
+ const grant = requireUserGrant(params.credentials);
152
+ if (!params.force && !expiringSoon(grant.expires_at, params.now)) return params.credentials;
153
+ const result = await postTokenEndpoint({
154
+ apiBaseUrl: params.apiBaseUrl,
155
+ body: {
156
+ client_id: params.credentials.oauth_client_id,
157
+ grant_type: "refresh_token",
158
+ refresh_token: params.credentials.refresh_token
159
+ },
160
+ fetch: params.fetch,
161
+ headers: params.headers
162
+ });
163
+ if (!result.ok) {
164
+ if (result.error === "invalid_grant") {
165
+ deleteCliCredentials(params.configDir);
166
+ throw new Errors.CLIError(USER_LOGIN_EXPIRED_MESSAGE, { exit: 1 });
167
+ }
168
+ throw new Errors.CLIError(`Could not refresh the saved Primitive CLI login${result.description ? `: ${result.description}` : "."}`, { exit: 1 });
169
+ }
170
+ const payload = result.payload;
171
+ if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number" || payload.expires_in <= 0) throw new Errors.CLIError("Primitive OAuth token endpoint returned an unexpected refresh response.", { exit: 1 });
172
+ const next = {
173
+ ...params.credentials,
174
+ refresh_token: payload.refresh_token,
175
+ user_grant: {
176
+ ...grant,
177
+ access_token: payload.access_token,
178
+ expires_at: cliAccessTokenExpiresAt(payload.expires_in, params.now)
179
+ }
180
+ };
181
+ saveCliCredentials(params.configDir, next);
182
+ return next;
183
+ }
184
+ function refusalMessage(reason, description) {
185
+ return description ?? (reason ? REFUSAL_FALLBACK_MESSAGES[reason] : void 0) ?? "Primitive refused access to this organization.";
186
+ }
187
+ /** One exchange of the identity token for an access token in `orgId`. */
188
+ async function exchangeForOrg(params) {
189
+ const body = {
190
+ client_id: params.clientId,
191
+ grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
192
+ org: params.orgId,
193
+ subject_token: params.identityToken,
194
+ subject_token_type: ACCESS_TOKEN_TYPE
195
+ };
196
+ if (params.requestedGrant) body.requested_grant = params.requestedGrant;
197
+ const result = await postTokenEndpoint({
198
+ apiBaseUrl: params.apiBaseUrl,
199
+ body,
200
+ fetch: params.fetch,
201
+ headers: params.headers
202
+ });
203
+ if (!result.ok) return result;
204
+ if (!isOrgAccessToken(result.payload)) throw new Errors.CLIError("Primitive OAuth token endpoint returned an unexpected token exchange response.", { exit: 1 });
205
+ return {
206
+ ok: true,
207
+ token: result.payload
208
+ };
209
+ }
210
+ /**
211
+ * Exchange for `orgId`, refreshing the identity token first when needed and
212
+ * once more if the exchange says it expired. Saves the result: as the
213
+ * current organization when `makeCurrent`, otherwise in the per-org cache.
214
+ * Must run while the credentials lock is held.
215
+ */
216
+ async function exchangeAndSave(params) {
217
+ let credentials = await freshIdentity(params);
218
+ let attempt = 0;
219
+ for (;;) {
220
+ const grant = requireUserGrant(credentials);
221
+ const result = await exchangeForOrg({
222
+ apiBaseUrl: params.apiBaseUrl,
223
+ clientId: credentials.oauth_client_id,
224
+ fetch: params.fetch,
225
+ headers: params.headers,
226
+ identityToken: grant.access_token,
227
+ orgId: params.orgId,
228
+ requestedGrant: grant.requested_grant ?? null
229
+ });
230
+ if (result.ok) {
231
+ const org = orgTokenFromResponse(result.token, params.now);
232
+ const saved = saveOrgToken(credentials, org, params.makeCurrent);
233
+ saveCliCredentials(params.configDir, saved);
234
+ return {
235
+ credentials: saved,
236
+ org
237
+ };
238
+ }
239
+ if (result.error === "invalid_grant" && attempt === 0) {
240
+ attempt += 1;
241
+ credentials = await freshIdentity({
242
+ ...params,
243
+ credentials,
244
+ force: true
245
+ });
246
+ continue;
247
+ }
248
+ if (result.error === "invalid_grant") {
249
+ deleteCliCredentials(params.configDir);
250
+ throw new Errors.CLIError(USER_LOGIN_EXPIRED_MESSAGE, { exit: 1 });
251
+ }
252
+ if (result.error === "unsupported_grant_type") throw new Errors.CLIError("This Primitive server does not support switching organizations for CLI logins. Run `primitive logout`, then `primitive login`, to sign in to one organization.", { exit: 1 });
253
+ if (result.reason) throw new CliOrgRefusedError({
254
+ message: refusalMessage(result.reason, result.description),
255
+ orgId: params.orgId,
256
+ reason: result.reason
257
+ });
258
+ throw new Errors.CLIError(`Could not get an access token for organization ${params.orgId}${result.description ? `: ${result.description}` : "."}`, { exit: 1 });
259
+ }
260
+ }
261
+ function saveOrgToken(credentials, org, makeCurrent) {
262
+ const grant = requireUserGrant(credentials);
263
+ const cache = { ...grant.org_tokens };
264
+ if (makeCurrent) {
265
+ if (credentials.org_id && credentials.access_token) cache[credentials.org_id] = {
266
+ access_token: credentials.access_token,
267
+ expires_at: credentials.expires_at,
268
+ oauth_grant_id: credentials.oauth_grant_id,
269
+ org_id: credentials.org_id,
270
+ org_name: credentials.org_name,
271
+ role: grant.org_role ?? null
272
+ };
273
+ delete cache[org.org_id];
274
+ return {
275
+ ...credentialsForOrg(credentials, org),
276
+ user_grant: {
277
+ ...grant,
278
+ org_role: org.role,
279
+ org_tokens: pruneOrgTokens(cache)
280
+ }
281
+ };
282
+ }
283
+ if (org.org_id === credentials.org_id) return {
284
+ ...credentialsForOrg(credentials, org),
285
+ user_grant: {
286
+ ...grant,
287
+ org_role: org.role
288
+ }
289
+ };
290
+ cache[org.org_id] = org;
291
+ return {
292
+ ...credentials,
293
+ user_grant: {
294
+ ...grant,
295
+ org_tokens: pruneOrgTokens(cache)
296
+ }
297
+ };
298
+ }
299
+ function pruneOrgTokens(cache) {
300
+ const entries = Object.entries(cache).sort(([, left], [, right]) => Date.parse(right.expires_at) - Date.parse(left.expires_at));
301
+ return Object.fromEntries(entries.slice(0, MAX_CACHED_ORG_TOKENS));
302
+ }
303
+ /** The organizations the person can switch to. */
304
+ async function fetchCliOrgs(params) {
305
+ const now = params.now ?? Date.now;
306
+ let credentials = params.credentials;
307
+ requireUserGrant(credentials);
308
+ const grant = credentials.user_grant;
309
+ if (expiringSoon(grant.expires_at, now)) credentials = await withLock(params, () => {
310
+ const current = loadCliCredentials(params.configDir) ?? credentials;
311
+ return freshIdentity({
312
+ ...params,
313
+ credentials: current,
314
+ now
315
+ });
316
+ });
317
+ const request = async (token) => (params.fetch ?? fetch)(cliOrgsEndpoint(params.apiBaseUrl), {
318
+ headers: {
319
+ ...params.headers ?? {},
320
+ authorization: `Bearer ${token}`
321
+ },
322
+ method: "GET"
323
+ });
324
+ let response = await request(credentials.user_grant.access_token);
325
+ if (response.status === 401) {
326
+ credentials = await withLock(params, () => {
327
+ const current = loadCliCredentials(params.configDir) ?? credentials;
328
+ return freshIdentity({
329
+ ...params,
330
+ credentials: current,
331
+ force: true,
332
+ now
333
+ });
334
+ });
335
+ response = await request(credentials.user_grant.access_token);
336
+ }
337
+ const payload = await response.json().catch(() => null);
338
+ if (!response.ok) {
339
+ const error = isRecord(payload) && isRecord(payload.error) ? payload.error : {};
340
+ if (response.status === 404 && error.code === "feature_disabled") throw new Errors.CLIError("This Primitive server does not support switching organizations for CLI logins.", { exit: 1 });
341
+ throw new Errors.CLIError(`Could not list organizations${typeof error.message === "string" ? `: ${error.message}` : ` (HTTP ${response.status}).`}`, { exit: 1 });
342
+ }
343
+ const data = isRecord(payload) ? payload.data : null;
344
+ if (!isRecord(data) || !Array.isArray(data.orgs)) throw new Errors.CLIError("Primitive API returned an unexpected organization list.", { exit: 1 });
345
+ return data;
346
+ }
347
+ /** How long a command waits for another CLI process to finish with the saved login. */
348
+ const LOCK_WAIT_MS = 15e3;
349
+ const LOCK_RETRY_MS = 100;
350
+ function isLockBusy(error) {
351
+ return error instanceof Error && error.message.startsWith("Another Primitive CLI credential operation is already in progress");
352
+ }
353
+ /**
354
+ * Run `fn` holding the credentials lock. Exchanges happen every few minutes
355
+ * and from parallel commands, so a busy lock is waited for (briefly) rather
356
+ * than refused; callers re-read the saved login once they hold it.
357
+ */
358
+ async function withLock(params, fn) {
359
+ let release;
360
+ if (!params.credentialsLockHeld) {
361
+ const deadline = Date.now() + LOCK_WAIT_MS;
362
+ for (;;) try {
363
+ release = acquireCliCredentialsLock(params.configDir);
364
+ break;
365
+ } catch (error) {
366
+ if (isLockBusy(error) && Date.now() < deadline) {
367
+ await new Promise((done) => setTimeout(done, LOCK_RETRY_MS));
368
+ continue;
369
+ }
370
+ if (isLockBusy(error)) throw new Errors.CLIError("Another Primitive CLI command is still updating the saved login. Retry in a moment.", { exit: 1 });
371
+ const detail = error instanceof Error ? error.message : String(error);
372
+ throw new Errors.CLIError(detail, { exit: 1 });
373
+ }
374
+ }
375
+ try {
376
+ return await fn();
377
+ } finally {
378
+ release?.();
379
+ }
380
+ }
381
+ /**
382
+ * Match `selection` (an organization id, or a name) against the person's
383
+ * organizations. Ids pass through without a lookup.
384
+ */
385
+ function matchOrg(selection, orgs) {
386
+ const wanted = selection.trim();
387
+ const byId = orgs.find((org) => org.id.toLowerCase() === wanted.toLowerCase());
388
+ if (byId) return byId;
389
+ const byName = orgs.filter((org) => (org.name ?? "").trim().toLowerCase() === wanted.toLowerCase());
390
+ if (byName.length === 1) return byName[0];
391
+ if (byName.length > 1) throw new Errors.CLIError(`More than one organization is named ${JSON.stringify(wanted)}. Use its id: ${byName.map((org) => org.id).join(", ")}.`, { exit: 1 });
392
+ throw new Errors.CLIError(`No organization matches ${JSON.stringify(wanted)}. Run \`primitive orgs\` to list yours.`, { exit: 1 });
393
+ }
394
+ /** Resolve an `--org` value to an organization id, listing orgs only for a name. */
395
+ async function resolveOrgSelection(params) {
396
+ const selection = params.selection.trim();
397
+ if (isUuid(selection)) return selection.toLowerCase();
398
+ requireUserGrant(params.credentials);
399
+ return matchOrg(selection, (await fetchCliOrgs(params)).orgs).id;
400
+ }
401
+ /**
402
+ * The org-bound credentials for this command: the current organization, or
403
+ * `orgSelection` (from `--org`) when given. Exchanges when the token for
404
+ * that organization is missing or about to expire.
405
+ */
406
+ async function ensureUserGrantSession(params) {
407
+ const now = params.now ?? Date.now;
408
+ requireUserGrant(params.credentials);
409
+ const raw = params.orgSelection?.trim() || null;
410
+ const selection = raw === null ? null : await resolveOrgSelection({
411
+ ...params,
412
+ credentials: params.credentials,
413
+ now,
414
+ selection: raw
415
+ });
416
+ const fast = usableOrgToken(params.credentials, selection, now);
417
+ if (fast && !params.forceExchange) return fast;
418
+ return withLock(params, async () => {
419
+ const current = loadCliCredentials(params.configDir);
420
+ if (!current) throw new Errors.CLIError(USER_LOGIN_EXPIRED_MESSAGE, { exit: 1 });
421
+ requireUserGrant(current);
422
+ const orgId = selection ?? (current.org_id || null);
423
+ if (!orgId) throw new Errors.CLIError(NO_CURRENT_ORG_MESSAGE, { exit: 1 });
424
+ const saved = usableOrgToken(current, orgId, now);
425
+ if (saved && (!params.forceExchange || params.staleToken !== void 0 && saved.access_token !== params.staleToken)) return saved;
426
+ const { credentials, org } = await exchangeAndSave({
427
+ apiBaseUrl: params.apiBaseUrl,
428
+ configDir: params.configDir,
429
+ credentials: current,
430
+ fetch: params.fetch,
431
+ headers: params.headers,
432
+ makeCurrent: false,
433
+ now,
434
+ orgId
435
+ });
436
+ return credentialsForOrg(credentials, org);
437
+ });
438
+ }
439
+ function usableOrgToken(credentials, selection, now) {
440
+ const grant = credentials.user_grant;
441
+ if (!grant) return null;
442
+ const wanted = selection?.toLowerCase() ?? null;
443
+ if (wanted === null || wanted === credentials.org_id.toLowerCase()) {
444
+ if (!credentials.org_id || !credentials.access_token) return null;
445
+ return expiringSoon(credentials.expires_at, now) ? null : credentials;
446
+ }
447
+ const cached = Object.values(grant.org_tokens).find((token) => token.org_id.toLowerCase() === wanted);
448
+ if (!cached || expiringSoon(cached.expires_at, now)) return null;
449
+ return credentialsForOrg(credentials, cached);
450
+ }
451
+ /** `primitive org use`: exchange for `selection` and make it the current organization. */
452
+ async function switchCurrentOrg(params) {
453
+ const now = params.now ?? Date.now;
454
+ return withLock(params, async () => {
455
+ const current = loadCliCredentials(params.configDir);
456
+ if (!current) throw new Errors.CLIError("Not logged in. Run `primitive login` first.", { exit: 1 });
457
+ requireUserGrant(current);
458
+ const orgId = await resolveOrgSelection({
459
+ ...params,
460
+ credentials: current,
461
+ credentialsLockHeld: true,
462
+ now
463
+ });
464
+ const latest = loadCliCredentials(params.configDir);
465
+ if (!latest) throw new Errors.CLIError(USER_LOGIN_EXPIRED_MESSAGE, { exit: 1 });
466
+ return exchangeAndSave({
467
+ apiBaseUrl: params.apiBaseUrl,
468
+ configDir: params.configDir,
469
+ credentials: latest,
470
+ fetch: params.fetch,
471
+ headers: params.headers,
472
+ makeCurrent: true,
473
+ now,
474
+ orgId
475
+ });
476
+ });
477
+ }
478
+ /**
479
+ * Wrap `fetch` so a 401 on a per-org token is answered by one fresh
480
+ * exchange and one retry, as the server asks. If the exchange is refused,
481
+ * the caller sees a 403 carrying the refusal's reason as its error code.
482
+ */
483
+ function reexchangingFetch(params) {
484
+ const base = (input, init) => (params.base ?? globalThis.fetch)(input, init);
485
+ let pending = null;
486
+ const wrapped = async (input, init) => {
487
+ const request = new Request(input, init);
488
+ const sent = /^Bearer\s+(\S+)$/i.exec(request.headers.get("authorization") ?? "")?.[1];
489
+ const retry = request.clone();
490
+ const response = await base(request);
491
+ if (response.status !== 401 || !sent) return response;
492
+ if (params.token.current === sent) {
493
+ pending ??= params.reexchange(sent).finally(() => {
494
+ pending = null;
495
+ });
496
+ const outcome = await pending;
497
+ if (!outcome.ok) return Response.json({
498
+ success: false,
499
+ error: {
500
+ code: outcome.code,
501
+ message: outcome.message
502
+ }
503
+ }, { status: outcome.status });
504
+ params.token.current = outcome.token;
505
+ }
506
+ retry.headers.set("authorization", `Bearer ${params.token.current}`);
507
+ return base(retry);
508
+ };
509
+ return wrapped;
510
+ }
511
+ /** The reexchange callback for `reexchangingFetch`, for a saved user login. */
512
+ function savedLoginReexchange(params) {
513
+ return async (staleToken) => {
514
+ try {
515
+ const current = loadCliCredentials(params.configDir);
516
+ if (!current?.user_grant) return {
517
+ ok: false,
518
+ status: 401,
519
+ code: "unauthorized",
520
+ message: USER_LOGIN_EXPIRED_MESSAGE
521
+ };
522
+ return {
523
+ ok: true,
524
+ token: (await ensureUserGrantSession({
525
+ apiBaseUrl: params.apiBaseUrl,
526
+ configDir: params.configDir,
527
+ credentials: current,
528
+ fetch: params.fetch,
529
+ forceExchange: true,
530
+ headers: params.headers,
531
+ now: params.now,
532
+ orgSelection: params.orgId,
533
+ staleToken
534
+ })).access_token
535
+ };
536
+ } catch (error) {
537
+ if (error instanceof CliOrgRefusedError) return {
538
+ ok: false,
539
+ status: 403,
540
+ code: error.reason,
541
+ message: error.message
542
+ };
543
+ return {
544
+ ok: false,
545
+ status: 401,
546
+ code: "unauthorized",
547
+ message: error instanceof Error ? error.message : String(error)
548
+ };
549
+ }
550
+ };
551
+ }
552
+ /** Sign the person out of every organization by revoking the user login. */
553
+ async function revokeUserLogin(params) {
554
+ const result = await postTokenEndpoint({
555
+ apiBaseUrl: params.apiBaseUrl,
556
+ body: {
557
+ client_id: params.credentials.oauth_client_id,
558
+ token: params.credentials.refresh_token,
559
+ token_type_hint: "refresh_token"
560
+ },
561
+ fetch: params.fetch,
562
+ headers: params.headers,
563
+ path: "/oauth/revoke",
564
+ allowEmptySuccess: true
565
+ });
566
+ if (result.ok) return { ok: true };
567
+ return {
568
+ ok: false,
569
+ status: result.status,
570
+ message: result.description ?? result.error ?? `The server answered HTTP ${result.status}.`
571
+ };
572
+ }
573
+ /** Build the saved credentials for a user login from the login poll. */
574
+ function credentialsFromUserLogin(params) {
575
+ const now = params.now ?? Date.now;
576
+ const org = isOrgAccessToken(params.login.org) ? orgTokenFromResponse(params.login.org, now) : null;
577
+ return {
578
+ access_token: org?.access_token ?? "",
579
+ api_base_url: params.apiBaseUrl,
580
+ auth_method: "user_grant",
581
+ created_at: new Date(now()).toISOString(),
582
+ expires_at: org?.expires_at ?? (/* @__PURE__ */ new Date(0)).toISOString(),
583
+ oauth_client_id: params.login.oauth_client_id,
584
+ oauth_grant_id: org?.oauth_grant_id ?? "",
585
+ org_id: org?.org_id ?? "",
586
+ org_name: org?.org_name ?? null,
587
+ refresh_token: params.login.refresh_token,
588
+ token_type: "Bearer",
589
+ user_grant: {
590
+ access_token: params.login.access_token,
591
+ expires_at: cliAccessTokenExpiresAt(params.login.expires_in, now),
592
+ org_role: org?.role ?? null,
593
+ org_tokens: {},
594
+ requested_grant: params.requestedGrant ?? null,
595
+ user_grant_id: params.login.user_grant_id,
596
+ user_id: params.login.user_id
597
+ }
598
+ };
599
+ }
600
+ // src/oclif/api-client.ts
9
601
  const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
10
602
  const OAUTH_REFRESH_SKEW_MS = 6e4;
11
603
  const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
@@ -99,8 +691,47 @@ function oauthErrorDescription(value) {
99
691
  const raw = value.error_description;
100
692
  return typeof raw === "string" && raw.trim() ? raw : null;
101
693
  }
694
+ /** The organization the global `--org` flag (or PRIMITIVE_CLI_ORG) selects, if any. */
695
+ function selectedOrgFromEnv(env = process.env) {
696
+ return env["PRIMITIVE_CLI_ORG"]?.trim() || null;
697
+ }
698
+ /**
699
+ * Refuse `--org` before a command acts with a connected agent profile's own
700
+ * key: the profile belongs to one organization and cannot act in another.
701
+ */
702
+ function refuseOrgSelectionForProfile(profileName, env = process.env) {
703
+ const selection = selectedOrgFromEnv(env);
704
+ if (!selection) return;
705
+ const who = profileName ? `Connected agent profile ${profileName}` : "A connected agent credential";
706
+ throw new Errors.CLIError(`--org (PRIMITIVE_CLI_ORG=${selection}) selects an organization for a saved \`primitive login\`. ${who} belongs to one organization; drop --org, or unset PRIMITIVE_CLI_ORG.`, { exit: 1 });
707
+ }
708
+ function orgMatches(credentials, selection) {
709
+ const wanted = selection.toLowerCase();
710
+ return credentials.org_id.toLowerCase() === wanted || (credentials.org_name ?? "").trim().toLowerCase() === wanted;
711
+ }
712
+ /**
713
+ * Refuse `--org` for an org-bound login unless it names that login's own
714
+ * organization: an org-bound login cannot act anywhere else.
715
+ */
716
+ function assertOrgBoundSelection(credentials, selection) {
717
+ if (!selection || orgMatches(credentials, selection)) return;
718
+ const org = credentials.org_name ? `${credentials.org_name} (${credentials.org_id})` : credentials.org_id;
719
+ throw new Errors.CLIError(`This saved login is bound to organization ${org}, so --org ${selection} cannot be used with it. Run \`primitive logout\`, then \`primitive login\`, to sign in as yourself and switch between organizations.`, { exit: 1 });
720
+ }
102
721
  async function refreshStoredCliCredentials(params) {
103
722
  const now = params.now ?? Date.now;
723
+ const orgSelection = params.orgSelection === void 0 ? selectedOrgFromEnv() : params.orgSelection;
724
+ if (params.credentials.auth_method === "user_grant") return ensureUserGrantSession({
725
+ apiBaseUrl: params.apiBaseUrl,
726
+ configDir: params.configDir,
727
+ credentials: params.credentials,
728
+ credentialsLockHeld: params.credentialsLockHeld,
729
+ fetch: params.fetch,
730
+ headers: params.headers,
731
+ now,
732
+ orgSelection
733
+ });
734
+ assertOrgBoundSelection(params.credentials, orgSelection);
104
735
  if (!shouldRefresh(params.credentials, now)) return params.credentials;
105
736
  let releaseLock;
106
737
  try {
@@ -112,6 +743,16 @@ async function refreshStoredCliCredentials(params) {
112
743
  }
113
744
  const current = loadCliCredentials(params.configDir);
114
745
  if (!current) throw new Errors.CLIError("Saved Primitive CLI OAuth session is no longer available. Run `primitive signin` to authenticate again.", { exit: 1 });
746
+ if (current.auth_method === "user_grant") return ensureUserGrantSession({
747
+ apiBaseUrl: params.apiBaseUrl,
748
+ configDir: params.configDir,
749
+ credentials: current,
750
+ credentialsLockHeld: true,
751
+ fetch: params.fetch,
752
+ headers: params.headers,
753
+ now,
754
+ orgSelection
755
+ });
115
756
  if (!shouldRefresh(current, now)) return current;
116
757
  const fetchImpl = params.fetch ?? fetch;
117
758
  const body = new URLSearchParams({
@@ -172,6 +813,9 @@ async function createAuthenticatedCliApiClient(params) {
172
813
  if (hint) process.stderr.write(`${hint}\n`);
173
814
  }
174
815
  if (params.requireConnectedAgentKey && (auth.source === "stored" || !auth.apiKey?.startsWith("pconn_"))) throw new ConnectedAgentKeyRequiredError("A connected agent credential is required.");
816
+ const orgSelection = selectedOrgFromEnv(params.env ?? process.env);
817
+ if (orgSelection && auth.source !== "stored") throw new Errors.CLIError(`--org (PRIMITIVE_CLI_ORG=${orgSelection}) selects an organization for a saved \`primitive login\`. ${auth.source === "connected-profile" ? "A connected agent profile" : auth.source === "flag-or-env" ? "An API key" : "This command's credential"} belongs to one organization; drop --org, or unset PRIMITIVE_CLI_ORG.`, { exit: 1 });
818
+ let userLoginToken = null;
175
819
  if (auth.source === "stored" && auth.credentials) {
176
820
  const refreshed = await refreshStoredCliCredentials({
177
821
  apiBaseUrl: auth.apiBaseUrl,
@@ -180,17 +824,33 @@ async function createAuthenticatedCliApiClient(params) {
180
824
  credentialsLockHeld: params.credentialsLockHeld,
181
825
  fetch: params.fetch,
182
826
  headers: requestConfig.headers,
183
- now: params.now
827
+ now: params.now,
828
+ orgSelection
184
829
  });
185
830
  auth = {
186
831
  ...auth,
187
832
  apiKey: refreshed.access_token,
188
833
  credentials: refreshed
189
834
  };
835
+ if (refreshed.auth_method === "user_grant") userLoginToken = { current: refreshed.access_token };
190
836
  }
837
+ const tokenHolder = userLoginToken;
191
838
  return {
192
839
  apiClient: new PrimitiveApiClient({
193
- apiKey: auth.apiKey,
840
+ ...tokenHolder && auth.credentials ? {
841
+ auth: (security) => security.type === "http" && security.scheme === "bearer" ? tokenHolder.current : void 0,
842
+ fetch: reexchangingFetch({
843
+ reexchange: savedLoginReexchange({
844
+ apiBaseUrl: auth.apiBaseUrl,
845
+ configDir: params.configDir,
846
+ fetch: params.fetch,
847
+ headers: requestConfig.headers,
848
+ now: params.now,
849
+ orgId: auth.credentials.org_id
850
+ }),
851
+ token: tokenHolder
852
+ })
853
+ } : { apiKey: auth.apiKey },
194
854
  apiBaseUrl: auth.apiBaseUrl,
195
855
  headers: auth.connectedAgent ? void 0 : requestConfig.headers
196
856
  }),
@@ -199,8 +859,7 @@ async function createAuthenticatedCliApiClient(params) {
199
859
  requestConfig
200
860
  };
201
861
  }
202
- //#endregion
203
- //#region src/oclif/attachment-wait.ts
862
+ // src/oclif/attachment-wait.ts
204
863
  /** Generated download commands that wait for attachment content by default. */
205
864
  const ATTACHMENT_WAIT_OPERATIONS = /* @__PURE__ */ new Set([
206
865
  "downloadAttachments",
@@ -255,8 +914,7 @@ async function withAttachmentWait(call, options) {
255
914
  }
256
915
  return result;
257
916
  }
258
- //#endregion
259
- //#region src/oclif/endpoint-event-types.ts
917
+ // src/oclif/endpoint-event-types.ts
260
918
  const EVENT_TYPES_FLAG_OPERATIONS = /* @__PURE__ */ new Set(["createEndpoint", "updateEndpoint"]);
261
919
  const EVENT_TYPES_FLAG_DESCRIPTION = "Event types this endpoint subscribes to, comma separated or repeated (sets rules.event_types). Omitting the subscription means every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them; sent_email.* expands to all four. Keep email.received in the list if the endpoint also handles inbound mail. On update, the endpoint's other rules are kept.";
262
920
  const SENT_EMAIL_WILDCARD = "sent_email.*";
@@ -328,8 +986,7 @@ function withEventTypes(body, eventTypes, baseRules = {}) {
328
986
  function bodyHasRules(body) {
329
987
  return isPlainObject(body) && body.rules !== void 0;
330
988
  }
331
- //#endregion
332
- //#region src/oclif/endpoints-test-redirect.ts
989
+ // src/oclif/endpoints-test-redirect.ts
333
990
  async function detectFunctionEndpoint(endpointId, listEndpoints) {
334
991
  let response;
335
992
  try {
@@ -371,8 +1028,7 @@ async function maybeWriteFunctionEndpointRedirect(inputs) {
371
1028
  inputs.writeStderr(`${formatFunctionEndpointRedirect(match)}\n`);
372
1029
  return match;
373
1030
  }
374
- //#endregion
375
- //#region src/oclif/flag-suggestions.ts
1031
+ // src/oclif/flag-suggestions.ts
376
1032
  /**
377
1033
  * "Did you mean" hints for unknown flags. oclif reports an unknown flag as
378
1034
  * `Nonexistent flag: --query` and prints the full help; this adds the
@@ -437,8 +1093,7 @@ function withFlagSuggestion(error, valid) {
437
1093
  ].join("\n");
438
1094
  return error;
439
1095
  }
440
- //#endregion
441
- //#region src/oclif/from-verified-filter.ts
1096
+ // src/oclif/from-verified-filter.ts
442
1097
  /**
443
1098
  * The `from_verified` filter on GET /emails and GET /emails/search: the
444
1099
  * `from_verified=true|false` parameter (`--from-verified`) and the
@@ -494,8 +1149,7 @@ function assertFromVerifiedRows(rows, expectation) {
494
1149
  }).length;
495
1150
  if (wrong > 0) throw new FromVerifiedFilterUnsupportedError(`${expectation.surface} returned ${wrong} of ${rows.length} email${rows.length === 1 ? "" : "s"} without the requested \`from_verified\` value.`);
496
1151
  }
497
- //#endregion
498
- //#region src/oclif/idempotent-replay-banner.ts
1152
+ // src/oclif/idempotent-replay-banner.ts
499
1153
  /**
500
1154
  * If `data` carries `idempotent_replay: true`, write a stderr notice
501
1155
  * that nothing new was sent and why. Otherwise no-op.
@@ -509,7 +1163,7 @@ function isReplay(data) {
509
1163
  if (data === null || typeof data !== "object") return false;
510
1164
  return data.idempotent_replay === true;
511
1165
  }
512
- function nonEmptyString(value) {
1166
+ function nonEmptyString$1(value) {
513
1167
  return typeof value === "string" && value ? value : null;
514
1168
  }
515
1169
  /**
@@ -529,8 +1183,8 @@ function readReplayDetail(sent, keyOrigin) {
529
1183
  else return null;
530
1184
  return {
531
1185
  kind,
532
- originalId: nonEmptyString(object.original_sent_email_id) ?? nonEmptyString(sent.id),
533
- originalCreatedAt: nonEmptyString(object.original_created_at)
1186
+ originalId: nonEmptyString$1(object.original_sent_email_id) ?? nonEmptyString$1(sent.id),
1187
+ originalCreatedAt: nonEmptyString$1(object.original_created_at)
534
1188
  };
535
1189
  }
536
1190
  /** "12s ago", "4m ago", "3h ago", "9d ago"; null when the time is unusable. */
@@ -547,8 +1201,8 @@ function formatReplayAge(createdAt, now) {
547
1201
  return `${Math.round(hours / 24)}d ago`;
548
1202
  }
549
1203
  function statusSuffix(sent) {
550
- const status = nonEmptyString(sent.status);
551
- const deliveryStatus = nonEmptyString(sent.delivery_status);
1204
+ const status = nonEmptyString$1(sent.status);
1205
+ const deliveryStatus = nonEmptyString$1(sent.delivery_status);
552
1206
  const details = [];
553
1207
  if (status) details.push(`status ${status}`);
554
1208
  if (deliveryStatus && deliveryStatus !== status) details.push(`delivery status ${deliveryStatus}`);
@@ -564,7 +1218,7 @@ function formatSpecificNotice(sent, detail, options) {
564
1218
  switch (detail.kind) {
565
1219
  case "auto_content": return `Not sent: identical to ${id}${when}${state}.${advise ? ` Pass ${keyHint} to send again.` : ""}`;
566
1220
  case "explicit": {
567
- const key = nonEmptyString(sent.client_idempotency_key);
1221
+ const key = nonEmptyString$1(sent.client_idempotency_key);
568
1222
  return `Not sent: ${key ? `idempotency key ${key}` : "this idempotency key"} was already used for ${id}${when}${state}. A key you pass never expires.${advise ? " Use a different key only for a different message." : ""}`;
569
1223
  }
570
1224
  case "function_trigger": return `Not sent: this Function already sent this message as ${id}${when}${state}, for the same email or event that invoked it. A Function that runs again for one trigger sends once.`;
@@ -593,18 +1247,66 @@ function formatAlreadySentNotice(sent, options = {}) {
593
1247
  if (deliveryStatus && deliveryStatus !== status) details.push(`delivery status ${deliveryStatus}`);
594
1248
  return `Already sent: this exact message went out earlier${details.length > 0 ? ` (${details.join(", ")})` : ""}. Nothing new was sent.`;
595
1249
  }
596
- //#endregion
597
- //#region src/oclif/mail-session.ts
1250
+ // src/oclif/runtime-session.ts
1251
+ /**
1252
+ * The variables each runtime sets to its exact session ID, first match wins.
1253
+ * The loaded Codex thread is the receiving identity; the process session ID
1254
+ * is only a fallback when no thread ID is exposed.
1255
+ */
1256
+ const RUNTIME_SESSION_VARIABLES = {
1257
+ claude: ["CLAUDE_CODE_SESSION_ID"],
1258
+ codex: ["CODEX_THREAD_ID", "CODEX_SESSION_ID"]
1259
+ };
1260
+ const UUID$3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1261
+ /** Each runtime whose environment names a session UUID. Never guessed from history or cwd. */
1262
+ function runtimeSessions(env) {
1263
+ return Object.keys(RUNTIME_SESSION_VARIABLES).flatMap((runtime) => {
1264
+ for (const variable of RUNTIME_SESSION_VARIABLES[runtime]) {
1265
+ const value = env[variable]?.trim().toLowerCase();
1266
+ if (!value) continue;
1267
+ return UUID$3.test(value) ? [{
1268
+ runtime,
1269
+ session: value,
1270
+ variable
1271
+ }] : [];
1272
+ }
1273
+ return [];
1274
+ });
1275
+ }
1276
+ /** The one session the runtime environment names, if exactly one runtime names one. */
1277
+ function detectRuntimeSession(env = process.env) {
1278
+ const candidates = runtimeSessions(env);
1279
+ const [only] = candidates;
1280
+ if (!only) return { kind: "none" };
1281
+ return candidates.length === 1 ? {
1282
+ kind: "detected",
1283
+ ...only
1284
+ } : {
1285
+ kind: "ambiguous",
1286
+ candidates
1287
+ };
1288
+ }
1289
+ /**
1290
+ * The runtime whose tool environment names this exact session. A session ID
1291
+ * that matches neither runtime's variable, or both, has no detectable runtime.
1292
+ */
1293
+ function detectAgentRuntime(session, env = process.env) {
1294
+ const wanted = session.trim().toLowerCase();
1295
+ const matches = runtimeSessions(env).filter((candidate) => candidate.session === wanted);
1296
+ return matches.length === 1 ? matches[0]?.runtime ?? null : null;
1297
+ }
1298
+ /** Every variable a runtime may name its session in, for messages. */
1299
+ function runtimeSessionVariableList() {
1300
+ const names = Object.values(RUNTIME_SESSION_VARIABLES).flat();
1301
+ return `${names.slice(0, -1).join(", ")} or ${names.at(-1)}`;
1302
+ }
1303
+ // src/oclif/mail-session.ts
598
1304
  /** Runtime-provided identity only. Never guess a session from history or cwd. */
599
1305
  function currentMailSessionKey(env = process.env) {
600
- const codex = env.CODEX_THREAD_ID ?? env.CODEX_SESSION_ID;
601
- const claude = env.CLAUDE_CODE_SESSION_ID;
602
- if (codex && claude) return null;
603
- const value = codex ?? claude;
604
- return value && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(value) ? `${codex ? "codex" : "claude"}:${value.toLowerCase()}` : null;
605
- }
606
- //#endregion
607
- //#region src/oclif/agent-identity-suggestions.ts
1306
+ const detection = detectRuntimeSession(env);
1307
+ return detection.kind === "detected" ? `${detection.runtime}:${detection.session}` : null;
1308
+ }
1309
+ // src/oclif/agent-identity-suggestions.ts
608
1310
  /** The connection name enrollment uses when none is given. */
609
1311
  const DEFAULT_ENROLL_NAME = "Coding agent";
610
1312
  /** npx runs the CLI from its cache; print follow-up commands the same way. */
@@ -687,8 +1389,7 @@ function identitySuggestions(params) {
687
1389
  suggestions
688
1390
  };
689
1391
  }
690
- //#endregion
691
- //#region src/oclif/send-outcome.ts
1392
+ // src/oclif/send-outcome.ts
692
1393
  const SEND_OUTCOME_EXIT_CODES = {
693
1394
  replied: 0,
694
1395
  sent: 0,
@@ -1156,8 +1857,7 @@ function thrownErrorExitCode(error) {
1156
1857
  const exit = error?.oclif?.exit;
1157
1858
  return typeof exit === "number" ? exit : 1;
1158
1859
  }
1159
- //#endregion
1160
- //#region src/oclif/interaction-actions.ts
1860
+ // src/oclif/interaction-actions.ts
1161
1861
  const HINTS = /* @__PURE__ */ new Set([
1162
1862
  "status",
1163
1863
  "card",
@@ -1173,7 +1873,6 @@ function record$1(value) {
1173
1873
  function categoryOf(hint, kind, fyi, candidate) {
1174
1874
  if (hint === "pending") return candidate ? "pending" : "ordinary";
1175
1875
  if (hint === "card") switch (kind) {
1176
- case "x402.payment/1": return "payment";
1177
1876
  case "primitive.contact/1": return "contact";
1178
1877
  case "repeat.tick/1": return "repeat";
1179
1878
  case "repeat.stop/1": return "repeat_stopped";
@@ -1188,7 +1887,6 @@ function replyExpectation(category) {
1188
1887
  case "fyi":
1189
1888
  case "signal":
1190
1889
  case "repeat_stopped": return "no_reply";
1191
- case "payment":
1192
1890
  case "contact": return "answer_with_command";
1193
1891
  case "repeat": return "repeat";
1194
1892
  case "unsupported": return "unsupported";
@@ -1240,19 +1938,6 @@ function interactionNextActions(interaction, emailId, options = {}) {
1240
1938
  const id = emailId.toLowerCase();
1241
1939
  switch (interaction?.category ?? "ordinary") {
1242
1940
  case "ordinary": return [replyAction(bin, id)];
1243
- case "payment": return [buildFollowUpCommand("inspect_payment", "Show the payment request's terms before paying", [
1244
- bin,
1245
- "payments",
1246
- "challenge-from-email",
1247
- "--id",
1248
- id
1249
- ]), buildFollowUpCommand("pay", "Pay the payment request (signs with your wallet key and sends the payment)", [
1250
- bin,
1251
- "payments",
1252
- "pay-email",
1253
- "--in-reply-to",
1254
- id
1255
- ])];
1256
1941
  case "contact": return [buildFollowUpCommand("accept_contact", "Accept the contact request, if the owner's policy allows it", [
1257
1942
  bin,
1258
1943
  "contacts",
@@ -1295,11 +1980,6 @@ function interactionHumanLine(interaction, actions) {
1295
1980
  case "fyi": return "Informational (fyi): no reply needed, not even another fyi.";
1296
1981
  case "signal": return `Status signal (${kind}): no reply needed.`;
1297
1982
  case "repeat_stopped": return `Notice that a repeating message stopped (${kind}): no reply needed.`;
1298
- case "payment": {
1299
- const inspect = command("inspect_payment");
1300
- const pay = command("pay");
1301
- return inspect && pay ? `Payment interaction (${kind}). If it requests payment, review it with ${inspect} and pay with ${pay}. A plain reply does not pay or decline it.` : null;
1302
- }
1303
1983
  case "contact": {
1304
1984
  const accept = command("accept_contact");
1305
1985
  return accept ? `Contact interaction (${kind}). If it is a contact request, accept it under the owner's policy with ${accept}. A plain reply does not accept it.` : null;
@@ -1374,12 +2054,6 @@ function replyInteractionWarning(detail, emailId) {
1374
2054
  const expectation = replyExpectation(interaction.category);
1375
2055
  if (expectation === "reply" || expectation === "repeat") return null;
1376
2056
  switch (interaction.category) {
1377
- case "payment": return {
1378
- ...base,
1379
- code: "interaction_not_answered",
1380
- message: `Warning: email ${id} is a payment interaction (${label}). A plain reply does not pay or decline it; sending anyway. To pay: ${commandFor("pay")}`,
1381
- expected
1382
- };
1383
2057
  case "contact": return {
1384
2058
  ...base,
1385
2059
  code: "interaction_not_answered",
@@ -1413,8 +2087,7 @@ function replyInteractionWarning(detail, emailId) {
1413
2087
  default: return null;
1414
2088
  }
1415
2089
  }
1416
- //#endregion
1417
- //#region src/oclif/pending-mail.ts
2090
+ // src/oclif/pending-mail.ts
1418
2091
  const RELATIONSHIPS = [
1419
2092
  "owner",
1420
2093
  "member",
@@ -1771,8 +2444,7 @@ function pendingMailProfiles(configDir, session, emailId) {
1771
2444
  }
1772
2445
  }).sort();
1773
2446
  }
1774
- //#endregion
1775
- //#region src/oclif/wake-context.ts
2447
+ // src/oclif/wake-context.ts
1776
2448
  /**
1777
2449
  * Mail from a verified owner, member or connected peer agent is mail the
1778
2450
  * agent may act on, so its wake starts with the line that loads the rules
@@ -2009,8 +2681,7 @@ function formatMailWakeLine(input) {
2009
2681
  const skillFirst = skillFirstLine(input.relation ?? context?.relationship, input.skillFile);
2010
2682
  return `${skillFirst ? `${skillFirst}\n` : ""}Primitive mail arrived: ${input.emailId}${input.recipient}${metadata}. Read with ${wakeReadCommand(input.emailId, input.profileName)}.${interaction} ${wakeAuthority(input.relation)}\n`;
2011
2683
  }
2012
- //#endregion
2013
- //#region src/oclif/pending-mail-miss.ts
2684
+ // src/oclif/pending-mail-miss.ts
2014
2685
  /** At most this many other profiles are named in one hint line. */
2015
2686
  const HINT_PROFILE_LIMIT = 3;
2016
2687
  function currentProfile(configDir) {
@@ -2064,7 +2735,7 @@ function savedConnectedProfiles(configDir, exclude) {
2064
2735
  */
2065
2736
  async function otherProfiles(configDir, session, current) {
2066
2737
  if (session) try {
2067
- const { connectedProfilesForSession } = await import("./machine-session-B6V5-grn.js").then((n) => n.s);
2738
+ const { connectedProfilesForSession } = await import("./machine-session-Cp_eQ9t_.js").then((n) => n.s);
2068
2739
  return {
2069
2740
  inSession: true,
2070
2741
  profiles: connectedProfilesForSession(configDir, session, current)
@@ -2138,8 +2809,7 @@ async function explainPendingMailNotFound(configDir, emailId, write = (line) =>
2138
2809
  if (outcome.kind === "dropped") write(`Dropped the pending notice for ${id} from profile ${current.profile} after ${outcome.reads} consecutive not_found reads; the email is no longer visible to this profile.\n`);
2139
2810
  } catch {}
2140
2811
  }
2141
- //#endregion
2142
- //#region src/oclif/sent-list-labels.ts
2812
+ // src/oclif/sent-list-labels.ts
2143
2813
  /**
2144
2814
  * `sent list` under a connected agent profile returns this address's own
2145
2815
  * sends and also delivered sends from other addresses in the organization
@@ -2172,12 +2842,13 @@ function sentListOtherSendersNote(rows, self) {
2172
2842
  const named = senders.length > 3 ? `${senders.slice(0, 3).join(", ")} and ${senders.length - 3} more` : senders.join(", ");
2173
2843
  return `${count} of ${rows.length} rows were sent by another address in this organization (${named}) to ${own}; they are mail ${own} received, not its own sends. Rows with from_address ${own} are this address's sends; pass --from ${own} to list only those.`;
2174
2844
  }
2175
- //#endregion
2176
- //#region src/oclif/api-command.ts
2845
+ // src/oclif/api-command.ts
2177
2846
  const API_ERROR_CODES = {
2178
2847
  accessDenied: "access_denied",
2179
2848
  authorizationPending: "authorization_pending",
2180
2849
  expiredToken: "expired_token",
2850
+ forbidden: "forbidden",
2851
+ grantForbidden: "grant_forbidden",
2181
2852
  invalidDeviceCode: "invalid_device_code",
2182
2853
  notFound: "not_found",
2183
2854
  slowDown: "slow_down",
@@ -2455,6 +3126,47 @@ function rateLimitHint(response) {
2455
3126
  const delay = parseRetryAfter(response?.headers.get("retry-after"));
2456
3127
  return delay === void 0 ? "Hint: this request was rate limited. Wait before retrying: the API request limits use a 60 second window, and send limits can take longer to reset." : `Hint: this request was rate limited. Retry in ${Math.max(1, Math.ceil(delay / 1e3))} s.`;
2457
3128
  }
3129
+ /**
3130
+ * True when the API refused because of the credential's grant or role. The
3131
+ * credential itself was accepted, so this is not an authentication failure.
3132
+ */
3133
+ function isGrantRefusal(payload) {
3134
+ const code = extractErrorCode(payload);
3135
+ return code === API_ERROR_CODES.grantForbidden || code === API_ERROR_CODES.forbidden;
3136
+ }
3137
+ /** `details` of a refusal, whether the payload is the error or its envelope. */
3138
+ function refusalDetails(payload) {
3139
+ const outer = payload && typeof payload === "object" ? payload : {};
3140
+ const inner = outer.error && typeof outer.error === "object" ? outer.error : {};
3141
+ const details = outer.details ?? inner.details;
3142
+ return details && typeof details === "object" ? details : {};
3143
+ }
3144
+ function nonEmptyString(value) {
3145
+ return typeof value === "string" && value ? value : null;
3146
+ }
3147
+ /**
3148
+ * The hint for a `403 grant_forbidden`: which permission the credential's
3149
+ * grant lacks, and how to check a credential before using it.
3150
+ */
3151
+ function grantForbiddenHint(payload) {
3152
+ const record = refusalDetails(payload);
3153
+ const permission = nonEmptyString(record.permission);
3154
+ const reason = nonEmptyString(record.reason);
3155
+ const missing = nonEmptyString(record.missing);
3156
+ return `Hint: ${permission ? `This credential's grant does not include ${missing ?? permission}${reason ? ` (${reason})` : ""}.` : "This credential's grant does not allow this operation."} Use a credential whose grant includes it, or ask an owner or admin to widen it.${permission ? ` Check a credential with \`primitive whoami --check ${missing ?? permission}\`.` : ""}`;
3157
+ }
3158
+ /**
3159
+ * The hint for a `403 forbidden` that names a permission, or null when it does
3160
+ * not (a dashboard-only route, say, explains itself in its message). Unlike
3161
+ * `grant_forbidden`, this refusal can come from a route closed to keys and the
3162
+ * CLI altogether, so it never promises that a wider credential opens it.
3163
+ */
3164
+ function forbiddenHint(payload) {
3165
+ const record = refusalDetails(payload);
3166
+ const permission = nonEmptyString(record.permission);
3167
+ if (!permission || record.reason !== "permission_missing") return null;
3168
+ return `Hint: This operation needs ${permission}, which this credential does not have. Some operations stay closed to keys and the CLI even with it. Check a credential with \`primitive whoami --check ${permission}\`.`;
3169
+ }
2458
3170
  function writeErrorWithHints(payload, response) {
2459
3171
  process.stderr.write(`${formatErrorPayload(payload)}\n`);
2460
3172
  const code = extractErrorCode(payload);
@@ -2463,6 +3175,15 @@ function writeErrorWithHints(payload, response) {
2463
3175
  return;
2464
3176
  }
2465
3177
  if (!code) return;
3178
+ if (code === "grant_forbidden") {
3179
+ process.stderr.write(`${grantForbiddenHint(payload)}\n`);
3180
+ return;
3181
+ }
3182
+ if (code === API_ERROR_CODES.forbidden) {
3183
+ const hint = forbiddenHint(payload);
3184
+ if (hint) process.stderr.write(`${hint}\n`);
3185
+ return;
3186
+ }
2466
3187
  if (code in ERROR_CODE_HINTS) {
2467
3188
  const hint = ERROR_CODE_HINTS[code];
2468
3189
  process.stderr.write(`${hint}\n`);
@@ -2646,14 +3367,25 @@ function operationJsonDocument(envelope, summary = []) {
2646
3367
  if (summary.length > 0) document.summary = summary.join("\n");
2647
3368
  return document;
2648
3369
  }
2649
- function isIncompleteDomainVerification(operation, envelope) {
2650
- if (operation.sdkName !== "verifyDomain") return false;
2651
- const data = envelope?.data;
2652
- if (!data || typeof data !== "object") return false;
2653
- return data.verified === false;
2654
- }
2655
- function writeIncompleteDomainVerificationHint() {
2656
- process.stderr.write("Domain verification is incomplete. Add or fix the DNS records shown above, or run `primitive domains zone-file --id <domain-id>` to download the complete zone file, then retry `primitive domains verify --id <domain-id>`.\n");
3370
+ const SETTLED_DNS_RECORD_STATUSES = /* @__PURE__ */ new Set(["found", "legacy"]);
3371
+ /**
3372
+ * The hint for a failed domain verification: the API answers 400
3373
+ * `dns_verification_failed` with every expected record, each with its
3374
+ * `status`, in `error.details.records`. Lists the records that still need
3375
+ * work, then how to finish.
3376
+ */
3377
+ function domainVerificationFailedHint(payload) {
3378
+ const body = payload !== null && typeof payload === "object" ? payload : {};
3379
+ const records = body.details?.records ?? body.error?.details?.records;
3380
+ const lines = (Array.isArray(records) ? records : []).filter((record) => record !== null && typeof record === "object" && !SETTLED_DNS_RECORD_STATUSES.has(String(record.status))).map((record) => {
3381
+ const name = record.fqdn ?? record.name ?? "?";
3382
+ return ` ${String(record.type ?? "?")} ${String(name)} ${String(record.value ?? "")} (${String(record.status ?? "unknown")})`;
3383
+ });
3384
+ return [
3385
+ "Domain verification is incomplete.",
3386
+ ...lines.length > 0 ? ["Records to add or fix:", ...lines] : [],
3387
+ "Add or fix those DNS records, or run `primitive domains zone-file --id <domain-id>` to download the complete zone file, then retry `primitive domains verify --id <domain-id>`."
3388
+ ].join("\n");
2657
3389
  }
2658
3390
  const OPERATION_HINTS = {
2659
3391
  addDomain: "Tip: after this returns a domain id, run `primitive domains zone-file --id <domain-id> --output <domain>.zone` when the user wants an importable DNS zone file.",
@@ -2680,11 +3412,14 @@ const OPERATION_EXTRA_BODY_FLAGS = { addDomain: { "mail-relay": {
2680
3412
  } } };
2681
3413
  const MAIL_RELAY_NOT_ENABLED_MESSAGE = "Mail relay is not enabled for this account, so the domain was not added. Retry without --mail-relay to point the domain's MX at Primitive directly, or ask Primitive to enable mail relay for your account.";
2682
3414
  const MAIL_RELAY_UNAVAILABLE_MESSAGE = "No mail relay is available on this API server right now, so the domain was not added. Retry later, or retry without --mail-relay to point the domain's MX at Primitive directly.";
2683
- const OPERATION_ERROR_HINTS = { addDomain: (code, body) => {
2684
- if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
2685
- const mailRelay = body !== null && typeof body === "object" && body.mail_relay === true;
2686
- if (code === "feature_disabled" && mailRelay) return MAIL_RELAY_NOT_ENABLED_MESSAGE;
2687
- } };
3415
+ const OPERATION_ERROR_HINTS = {
3416
+ verifyDomain: (code, _body, payload) => code === "dns_verification_failed" ? domainVerificationFailedHint(payload) : void 0,
3417
+ addDomain: (code, body) => {
3418
+ if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
3419
+ const mailRelay = body !== null && typeof body === "object" && body.mail_relay === true;
3420
+ if (code === "feature_disabled" && mailRelay) return MAIL_RELAY_NOT_ENABLED_MESSAGE;
3421
+ }
3422
+ };
2688
3423
  const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, apiBaseUrl, writeStderr }) => {
2689
3424
  const data = envelope?.data;
2690
3425
  if (!data?.access_token || !data?.refresh_token) return;
@@ -2776,7 +3511,7 @@ function createOperationCommand(operation) {
2776
3511
  if (result.error) {
2777
3512
  const errorPayload = extractErrorPayload(result.error);
2778
3513
  writeErrorWithHints(errorPayload, result.response);
2779
- const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body);
3514
+ const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body, errorPayload);
2780
3515
  if (operationErrorHint) process.stderr.write(`${operationErrorHint}\n`);
2781
3516
  if (replyStateSurface && isAwaitingRejectedError(errorPayload)) process.stderr.write(`${awaitingRejectedError(replyStateSurface).message}\n`);
2782
3517
  if (automatedSurface && isAutomatedRejectedError(errorPayload)) process.stderr.write(`${automatedRejectedError(automatedSurface).message}\n`);
@@ -2880,10 +3615,6 @@ function createOperationCommand(operation) {
2880
3615
  process.stderr.write(`Warning: ${operation.sdkName} succeeded but its post-success hook threw (${detail}). The response below is still valid; act on it manually.\n`);
2881
3616
  }
2882
3617
  this.log(JSON.stringify(jsonOutput && withEnvelope ? operationJsonDocument(envelope, summary) : operationOutputPayload(envelope, withEnvelope), null, 2));
2883
- if (isIncompleteDomainVerification(operation, envelope)) {
2884
- writeIncompleteDomainVerificationHint();
2885
- process.exitCode = 1;
2886
- }
2887
3618
  });
2888
3619
  }
2889
3620
  }
@@ -2900,5 +3631,4 @@ const EMPTY_RESULT_HINTS = {
2900
3631
  function canonicalizeCliReferences(description) {
2901
3632
  return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
2902
3633
  }
2903
- //#endregion
2904
- export { formatDeletedEarlierSendNotice as $, clearReadPendingMail as A, createCliApiClient as At, wakeInteractionSentence as B, serverRelationship as C, withFlagSuggestion as Ct, wakeRelationship as D, ConnectedAgentKeyRequiredError as Dt, wakeRecipientField as E, withAttachmentWait as Et, interactionHumanLine as F, assertValidIdempotencyKey as G, MAX_SEND_WAIT_TIMEOUT_MS as H, interactionNextActions as I, checkPriorReplies as J, buildFollowUpCommand as K, readEmailInteraction as L, readPendingMail as M, resolveCliApiRequestConfig as Mt, recordPendingMail as N, claimPendingMail as O, SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE as Ot, removePendingMail as P, followUpCommandPrefix as Q, replyExpectation as R, serverMuted as S, writeIdempotentReplayBannerIfReplay as St, wakeReadCommand as T, ATTACHMENT_WAIT_MAX_MS as Tt, SEND_OUTCOME_HELP as U, IDEMPOTENCY_KEY_FLAG_DESCRIPTION as V, apiResultHttpStatus as W, deriveSendIdempotencyKey as X, classifySendError as Y, excludeSignalReplies as Z, displayAddress as _, generatedAgentName as _t, extractErrorCode as a, reportSendCommandResult as at, readThreadContext as b, currentMailSessionKey as bt, hasControlCharacter as c, sendWaitClampNotice as ct, runWithTiming as d, successfulSendOutcome as dt, formatPriorRepliesCheckSkipped as et, surfaceUnauthorizedHint as f, thrownErrorExitCode as ft, describeWake as g, defaultConnectionName as gt, bareAddress$1 as h, connectNextSteps as ht, createOperationCommand as i, priorRepliesThatWentOut as it, pendingMailPath as j, refreshStoredCliCredentials as jt, clearConsumedPendingMail as k, createAuthenticatedCliApiClient as kt, headerFlagName as l, sentHistoryWindowStart as lt, explainPendingMailNotFound as m, cliInvocation as mt, API_ERROR_CODES as n, formatSendFailureSummary as nt, extractErrorPayload as o, sendIdentityFields as ot, writeErrorWithHints as p, DEFAULT_ENROLL_NAME as pt, buildThrownSendFailureEnvelope as q, TIME_FLAG_DESCRIPTION as r, formatSendRecordFailureSummary as rt, formatErrorPayload as s, sendOutcomeExitCode as st, API_BASE_URL_FLAG_DESCRIPTION as t, formatPriorRepliesWarning as tt, readTextFileFlag as u, serializeErrorPayload as ut, formatMailWakeLine as v, identitySuggestions as vt, skillFirstLine as w, ATTACHMENT_WAIT_FLAG_DESCRIPTION as wt, sentInThread as x, formatAlreadySentNotice as xt, latestOwnOutbound as y, loadSkillLine as yt, replyInteractionWarning as z };
3634
+ export { followUpCommandPrefix as $, clearConsumedPendingMail as A, ConnectedAgentKeyRequiredError as At, replyInteractionWarning as B, fetchCliOrgs as Bt, serverMuted as C, detectRuntimeSession as Ct, wakeRecipientField as D, ATTACHMENT_WAIT_FLAG_DESCRIPTION as Dt, wakeReadCommand as E, withFlagSuggestion as Et, removePendingMail as F, refuseOrgSelectionForProfile as Ft, apiResultHttpStatus as G, IDEMPOTENCY_KEY_FLAG_DESCRIPTION as H, switchCurrentOrg as Ht, interactionHumanLine as I, resolveCliApiRequestConfig as It, buildThrownSendFailureEnvelope as J, assertValidIdempotencyKey as K, interactionNextActions as L, selectedOrgFromEnv as Lt, pendingMailPath as M, createAuthenticatedCliApiClient as Mt, readPendingMail as N, createCliApiClient as Nt, wakeRelationship as O, ATTACHMENT_WAIT_MAX_MS as Ot, recordPendingMail as P, refreshStoredCliCredentials as Pt, excludeSignalReplies as Q, readEmailInteraction as R, CliOrgRefusedError as Rt, sentInThread as S, detectAgentRuntime as St, skillFirstLine as T, formatAlreadySentNotice as Tt, MAX_SEND_WAIT_TIMEOUT_MS as U, wakeInteractionSentence as V, revokeUserLogin as Vt, SEND_OUTCOME_HELP as W, classifySendError as X, checkPriorReplies as Y, deriveSendIdempotencyKey as Z, describeWake as _, defaultConnectionName as _t, extractErrorCode as a, priorRepliesThatWentOut as at, latestOwnOutbound as b, loadSkillLine as bt, hasControlCharacter as c, sendOutcomeExitCode as ct, readTextFileFlag as d, serializeErrorPayload as dt, formatDeletedEarlierSendNotice as et, runWithTiming as f, successfulSendOutcome as ft, bareAddress$1 as g, connectNextSteps as gt, explainPendingMailNotFound as h, cliInvocation as ht, createOperationCommand as i, formatSendRecordFailureSummary as it, clearReadPendingMail as j, SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE as jt, claimPendingMail as k, withAttachmentWait as kt, headerFlagName as l, sendWaitClampNotice as lt, writeErrorWithHints as m, DEFAULT_ENROLL_NAME as mt, API_ERROR_CODES as n, formatPriorRepliesWarning as nt, extractErrorPayload as o, reportSendCommandResult as ot, surfaceUnauthorizedHint as p, thrownErrorExitCode as pt, buildFollowUpCommand as q, TIME_FLAG_DESCRIPTION as r, formatSendFailureSummary as rt, formatErrorPayload as s, sendIdentityFields as st, API_BASE_URL_FLAG_DESCRIPTION as t, formatPriorRepliesCheckSkipped as tt, isGrantRefusal as u, sentHistoryWindowStart as ut, displayAddress as v, generatedAgentName as vt, serverRelationship as w, runtimeSessionVariableList as wt, readThreadContext as x, currentMailSessionKey as xt, formatMailWakeLine as y, identitySuggestions as yt, replyExpectation as z, credentialsFromUserLogin as zt };