primcli 1.75.0 → 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.
- package/README.md +116 -63
- package/bin/claude-pending-mail.mjs +1 -1
- package/bin/run.js +14 -0
- package/dist/{api-command-rlS3eTlZ.js → api-command-ctHBqTL5.js} +752 -56
- package/dist/{cli-config-B80p0pLk.js → cli-config-Ms_0vNjq.js} +59 -16
- package/dist/{machine-session-Cq_nD5eF.js → machine-session-Cp_eQ9t_.js} +25 -12
- package/dist/{notify-session-CbY4FYUt.js → notify-session-BN0JTPZ3.js} +334 -4
- package/dist/oclif/auto-signal-worker.js +3 -3
- package/dist/oclif/auto-signals.js +2 -2
- package/dist/oclif/global-org-flag.js +76 -0
- package/dist/oclif/index.js +112940 -51725
- package/dist/oclif/receiver-heal.js +3 -3
- package/dist/oclif/root-signup-hint.js +2 -2
- package/dist/receiver.d.ts +95 -0
- package/dist/receiver.js +569 -0
- package/dist/{repeats-Dj_o6c-X-LyCkKYjp.js → repeats-Dj_o6c-X-Bf9G0xds.js} +4916 -2382
- package/dist/{scoped-chat-B0thCwME.js → scoped-chat-DZ42V3jq.js} +1 -1
- package/dist/{signal-command-DCPsJBdz.js → signal-command-CRUMUCNa.js} +9 -8
- package/man/primitive.1 +34 -0
- package/package.json +53 -8
|
@@ -391,6 +391,7 @@ var LegacyApiKeyCredentialFormatError = class extends Error {
|
|
|
391
391
|
};
|
|
392
392
|
function parseCredentials(raw) {
|
|
393
393
|
if (!isRecord$1(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
394
|
+
if (raw.auth_method === "user_grant") return parseUserGrantCredentials(raw);
|
|
394
395
|
if (raw.auth_method !== "oauth") {
|
|
395
396
|
if (typeof raw.api_key === "string" || typeof raw.key_id === "string" || typeof raw.base_url === "string") throw new LegacyApiKeyCredentialFormatError();
|
|
396
397
|
throw new Error(`Stored Primitive CLI credentials are malformed: auth_method must be oauth. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
@@ -412,6 +413,63 @@ function parseCredentials(raw) {
|
|
|
412
413
|
created_at: requireString(raw, "created_at")
|
|
413
414
|
};
|
|
414
415
|
}
|
|
416
|
+
function malformed(detail) {
|
|
417
|
+
return /* @__PURE__ */ new Error(`Stored Primitive CLI credentials are malformed: ${detail}. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
418
|
+
}
|
|
419
|
+
function requireText(value, key, options = {}) {
|
|
420
|
+
const raw = value[key];
|
|
421
|
+
if (typeof raw !== "string" || !options.allowEmpty && raw.trim().length === 0) throw malformed(`${key} must be a non-empty string`);
|
|
422
|
+
return raw;
|
|
423
|
+
}
|
|
424
|
+
function nullableText(value, key) {
|
|
425
|
+
const raw = value[key];
|
|
426
|
+
if (raw === void 0 || raw === null) return null;
|
|
427
|
+
if (typeof raw !== "string") throw malformed(`${key} must be a string or null`);
|
|
428
|
+
return raw;
|
|
429
|
+
}
|
|
430
|
+
function parseOrgToken(raw) {
|
|
431
|
+
if (!isRecord$1(raw)) throw malformed("user_grant.org_tokens entries must be objects");
|
|
432
|
+
return {
|
|
433
|
+
access_token: requireText(raw, "access_token"),
|
|
434
|
+
expires_at: requireText(raw, "expires_at"),
|
|
435
|
+
oauth_grant_id: requireText(raw, "oauth_grant_id"),
|
|
436
|
+
org_id: requireText(raw, "org_id"),
|
|
437
|
+
org_name: nullableText(raw, "org_name"),
|
|
438
|
+
role: nullableText(raw, "role")
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function parseUserGrantCredentials(raw) {
|
|
442
|
+
const grantRaw = raw.user_grant;
|
|
443
|
+
if (!isRecord$1(grantRaw)) throw malformed("user_grant must be an object");
|
|
444
|
+
if (raw.token_type !== "Bearer") throw malformed("token_type must be Bearer");
|
|
445
|
+
const tokensRaw = grantRaw.org_tokens ?? {};
|
|
446
|
+
if (!isRecord$1(tokensRaw)) throw malformed("user_grant.org_tokens must be an object");
|
|
447
|
+
const orgTokens = {};
|
|
448
|
+
for (const [orgId, token] of Object.entries(tokensRaw)) orgTokens[orgId] = parseOrgToken(token);
|
|
449
|
+
const orgId = requireText(raw, "org_id", { allowEmpty: true });
|
|
450
|
+
return {
|
|
451
|
+
access_token: requireText(raw, "access_token", { allowEmpty: true }),
|
|
452
|
+
api_base_url: readStoredApiBaseUrl(raw),
|
|
453
|
+
auth_method: "user_grant",
|
|
454
|
+
created_at: requireText(raw, "created_at"),
|
|
455
|
+
expires_at: requireText(raw, "expires_at"),
|
|
456
|
+
oauth_client_id: requireText(raw, "oauth_client_id"),
|
|
457
|
+
oauth_grant_id: requireText(raw, "oauth_grant_id", { allowEmpty: true }),
|
|
458
|
+
org_id: orgId,
|
|
459
|
+
org_name: nullableText(raw, "org_name"),
|
|
460
|
+
refresh_token: requireText(raw, "refresh_token"),
|
|
461
|
+
token_type: "Bearer",
|
|
462
|
+
user_grant: {
|
|
463
|
+
access_token: requireText(grantRaw, "access_token"),
|
|
464
|
+
expires_at: requireText(grantRaw, "expires_at"),
|
|
465
|
+
org_role: nullableText(grantRaw, "org_role"),
|
|
466
|
+
org_tokens: orgTokens,
|
|
467
|
+
requested_grant: nullableText(grantRaw, "requested_grant"),
|
|
468
|
+
user_grant_id: requireText(grantRaw, "user_grant_id"),
|
|
469
|
+
user_id: requireText(grantRaw, "user_id")
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
415
473
|
function credentialsPath(configDir) {
|
|
416
474
|
return join(configDir, CREDENTIALS_FILE);
|
|
417
475
|
}
|
|
@@ -674,21 +732,6 @@ function detectPrimitiveKeyEnvMisname(env = process.env) {
|
|
|
674
732
|
if ((primitiveKey?.length ?? 0) > 0 && (primitiveApiKey?.length ?? 0) === 0) return "PRIMITIVE_KEY is set but the CLI reads PRIMITIVE_API_KEY. Rename your env var, or re-run with PRIMITIVE_API_KEY=$PRIMITIVE_KEY.";
|
|
675
733
|
return null;
|
|
676
734
|
}
|
|
677
|
-
/**
|
|
678
|
-
* True when a usable OAuth login session is saved on disk. Used by `pay-email`
|
|
679
|
-
* to decide whether it can safely ignore an env-only `PRIMITIVE_API_KEY` in
|
|
680
|
-
* favor of the payer's logged-in account (which owns the inbound challenge
|
|
681
|
-
* inbox). Returns false when nothing is stored or the stored credential is
|
|
682
|
-
* unreadable/legacy, so the caller falls back to whatever key did resolve
|
|
683
|
-
* rather than silently dropping the user's only auth.
|
|
684
|
-
*/
|
|
685
|
-
function hasStoredCliLogin(configDir) {
|
|
686
|
-
try {
|
|
687
|
-
return loadCliCredentials(configDir) !== null;
|
|
688
|
-
} catch {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
735
|
function resolveCliAuth(params) {
|
|
693
736
|
const apiKey = params.apiKey?.trim();
|
|
694
737
|
const apiBaseUrl = normalizeApiBaseUrl(params.apiBaseUrl);
|
|
@@ -903,4 +946,4 @@ function redactCliEnvironment(environment) {
|
|
|
903
946
|
...headers ? { headers } : {}
|
|
904
947
|
};
|
|
905
948
|
}
|
|
906
|
-
export {
|
|
949
|
+
export { AGENT_ADDRESS_ENV as A, parseOwnerMemberAddress as B, saveCliCredentials as C, loadActiveChatState as D, deleteChatState as E, agentProfilesDirectory as F, requireDefaultLoginProfile as H, connectedAgentIdentity as I, connectedApiBaseUrl as L, AgentConnectionSetupError as M, agentProfileDirectory as N, loadChatConversationByLocalId as O, agentProfileName as P, loadConnectedAgentProfile as R, resolveCliAuth as S, chatStatePath as T, saveConnectedAgentProfile as U, profileAlreadyConnectedMessage as V, PrimitiveApiClient as W, deleteCliCredentials as _, normalizeCliEnvironmentName as a, loadCliCredentials as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, AGENT_PROFILE_ENV as j, saveActiveChatState as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveSignupCredentials as w, normalizeApiBaseUrl as x, detectPrimitiveKeyEnvMisname as y, parseConnectedAgentProfile as z };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { B as
|
|
2
|
-
import { $
|
|
3
|
-
import { Pt as refreshStoredCliCredentials,
|
|
1
|
+
import { B as parseOwnerMemberAddress, F as agentProfilesDirectory, I as connectedAgentIdentity, L as connectedApiBaseUrl, M as AgentConnectionSetupError, N as agentProfileDirectory, P as agentProfileName, R as loadConnectedAgentProfile, U as saveConnectedAgentProfile, V as profileAlreadyConnectedMessage, W as PrimitiveApiClient, b as loadCliCredentials, j as AGENT_PROFILE_ENV, z as parseConnectedAgentProfile } from "./cli-config-Ms_0vNjq.js";
|
|
2
|
+
import { $n as __exportAll, $t as listContacts, Cn as putContact, It as getEmail, Jt as inviteAgentConnection, Mt as getContact, Pn as sendEmail, Sn as putAgentContactPolicy, Xt as listAgentConnections, Zt as listAgentContacts, hn as listSentEmails, jn as searchEmails, jt as getAgentContactPolicy, rn as listDomains, vt as deleteAgentContact, xn as putAgentContact, yt as deleteContact } from "./repeats-Dj_o6c-X-Bf9G0xds.js";
|
|
3
|
+
import { Ft as refuseOrgSelectionForProfile, Pt as refreshStoredCliCredentials, St as detectAgentRuntime, _t as defaultConnectionName, ht as cliInvocation } from "./api-command-ctHBqTL5.js";
|
|
4
4
|
import { _ as acquireListenLock, b as listenLockHeld, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, o as mailObject, p as syncMailDirectory, r as mailAddress, s as mailString } from "./shared-mail-files-BAQSGxxz.js";
|
|
5
|
-
import {
|
|
5
|
+
import { S as readBundledConnectSkill, a as NotificationRetryError, d as SESSION_UUID, f as connectNativeSession, m as NativeSessionDisconnectedError, p as defaultSessionSocket, t as notificationScope, y as installConnectSkill } from "./notify-session-BN0JTPZ3.js";
|
|
6
6
|
import { l as stopBackgroundListen, r as backgroundListenStatus } from "./listen-background-C0F9gNDD.js";
|
|
7
7
|
import { t as readSharedMailOwner } from "./shared-mail-watch-BAPMsFNv.js";
|
|
8
|
-
import {
|
|
8
|
+
import { withoutOrgSelection } from "./oclif/global-org-flag.js";
|
|
9
|
+
import { o as scopedChatSenderTrust } from "./scoped-chat-DZ42V3jq.js";
|
|
9
10
|
import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
12
|
import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -3009,6 +3010,7 @@ async function revoke(profile, fetcher) {
|
|
|
3009
3010
|
/** Revoke one selected connected credential; never alter OAuth login or mail evidence. */
|
|
3010
3011
|
async function disconnectAgent(params) {
|
|
3011
3012
|
const profileName = agentProfileName(params.profileName);
|
|
3013
|
+
refuseOrgSelectionForProfile(profileName);
|
|
3012
3014
|
if (!loadConnectedAgentProfile(params.configDir, profileName)) throw new AgentDisconnectError("This agent profile is not configured. Nothing was changed.");
|
|
3013
3015
|
const directory = agentProfileDirectory(params.configDir, profileName);
|
|
3014
3016
|
let releaseSetup;
|
|
@@ -3448,6 +3450,11 @@ var ContactPolicyReadRetryError = class extends NotificationRetryError {
|
|
|
3448
3450
|
super("Contact notification policy is temporarily unavailable. Mail remains pending while permissions are refreshed.");
|
|
3449
3451
|
}
|
|
3450
3452
|
};
|
|
3453
|
+
/** A network decision is trusted only when every field agrees with the others. */
|
|
3454
|
+
function consistentNetworkAdmission(decision) {
|
|
3455
|
+
const relation = decision.sender_relation;
|
|
3456
|
+
return typeof decision.allowed === "boolean" && typeof decision.pending === "boolean" && typeof decision.member_policy_required === "boolean" && (relation === void 0 || decision.member_policy_required && (relation === "owner" || relation === "member")) && !(decision.member_policy_required && decision.allowed && !relation) && !(decision.pending && (decision.allowed || decision.allowed_since !== null)) && (decision.allowed ? typeof decision.allowed_since === "string" : decision.allowed_since === null);
|
|
3457
|
+
}
|
|
3451
3458
|
const unavailable = () => new ListenStateError("Contact notification preferences are unavailable or invalid. No contact notification was submitted; restart after access is restored.");
|
|
3452
3459
|
const changed = () => new NotificationRetryError("Contact notification permission changed or expired before dispatch.");
|
|
3453
3460
|
/** Read one connected address's policy. Partial pages never authorize delivery. */
|
|
@@ -3561,10 +3568,8 @@ function createNotificationContactPolicy(options) {
|
|
|
3561
3568
|
if (!emailId) throw unavailable();
|
|
3562
3569
|
const network = await networkReader(emailId, sender, signal);
|
|
3563
3570
|
signal.throwIfAborted();
|
|
3564
|
-
if (
|
|
3565
|
-
if (network.sender_relation !== void 0 && (!network.member_policy_required || !["owner", "member"].includes(network.sender_relation))) throw unavailable();
|
|
3571
|
+
if (!consistentNetworkAdmission(network)) throw unavailable();
|
|
3566
3572
|
if (network.member_policy_required) {
|
|
3567
|
-
if (network.allowed && !network.sender_relation) throw unavailable();
|
|
3568
3573
|
if (network.pending) throw new NetworkAdmissionPendingError();
|
|
3569
3574
|
if (!network.allowed) return null;
|
|
3570
3575
|
if (decision.kind === "silent" && decision.source !== "default") return null;
|
|
@@ -4192,11 +4197,11 @@ function defaults$1() {
|
|
|
4192
4197
|
"--contacts",
|
|
4193
4198
|
...contactRequests ? ["--contact-requests"] : []
|
|
4194
4199
|
], {
|
|
4195
|
-
env: {
|
|
4200
|
+
env: withoutOrgSelection({
|
|
4196
4201
|
...process.env,
|
|
4197
4202
|
[AGENT_PROFILE_ENV]: profile,
|
|
4198
4203
|
PRIMITIVE_CONFIG_DIR: configDir
|
|
4199
|
-
},
|
|
4204
|
+
}),
|
|
4200
4205
|
timeout: 35e3,
|
|
4201
4206
|
maxBuffer: 65536
|
|
4202
4207
|
});
|
|
@@ -5058,11 +5063,18 @@ function assertNativeSessionIdentity(session, env) {
|
|
|
5058
5063
|
if (env.CODEX_THREAD_ID && env.CODEX_THREAD_ID !== session) throw refuse("The requested session differs from this Codex thread. No address was created.");
|
|
5059
5064
|
if (!env.CODEX_THREAD_ID && env.CODEX_SESSION_ID && env.CODEX_SESSION_ID !== session) throw refuse("The requested session differs from this Codex session. No address was created.");
|
|
5060
5065
|
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Preflight for enrollment: the managed domains an address could land on.
|
|
5068
|
+
* Null when the member's grant may not list domains; creating a connection
|
|
5069
|
+
* needs only agent_connections:manage, and the server picks the address and
|
|
5070
|
+
* refuses with connection_domain_unavailable when none is usable.
|
|
5071
|
+
*/
|
|
5061
5072
|
async function managedDomains(client, apiBaseUrl) {
|
|
5062
5073
|
const response = await listDomains({
|
|
5063
5074
|
client: client.client,
|
|
5064
5075
|
responseStyle: "fields"
|
|
5065
5076
|
});
|
|
5077
|
+
if (response.response?.status === 403) return null;
|
|
5066
5078
|
const rows = response.data?.data;
|
|
5067
5079
|
if (response.error || !Array.isArray(rows)) throw refuse("Could not list the owner's managed domains. No address was created.");
|
|
5068
5080
|
const suffix = apiBaseUrl === "https://api.primitive-staging-1.com/v1" ? ".primitive-staging.email" : ".primitive.email";
|
|
@@ -5579,11 +5591,12 @@ async function enrollAgent(params) {
|
|
|
5579
5591
|
if (state?.continueAttempted) throw refuse(stuckEnrollmentStep(resolvedEnrollment(state).address));
|
|
5580
5592
|
if (!state) {
|
|
5581
5593
|
if (params.continueSetup) throw refuse("There is no saved enrollment to continue. Start enrollment normally first.");
|
|
5582
|
-
|
|
5594
|
+
const domains = await managedDomains(new PrimitiveApiClient({
|
|
5583
5595
|
apiKey: credentials.access_token,
|
|
5584
5596
|
apiBaseUrl,
|
|
5585
5597
|
fetch: params.fetch
|
|
5586
|
-
}), apiBaseUrl)
|
|
5598
|
+
}), apiBaseUrl);
|
|
5599
|
+
if (domains && !domains.length) throw refuse("No managed domain is available for enrollment.");
|
|
5587
5600
|
state = {
|
|
5588
5601
|
version: 2,
|
|
5589
5602
|
session: params.session,
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { Et as downloadEmailAttachmentPart, J as parseWebhookEvent, K as isEmailReceivedEvent, c as classifySignalContent, q as isTrustedSender } from "./repeats-Dj_o6c-X-
|
|
2
|
-
import {
|
|
1
|
+
import { Et as downloadEmailAttachmentPart, J as parseWebhookEvent, K as isEmailReceivedEvent, c as classifySignalContent, q as isTrustedSender } from "./repeats-Dj_o6c-X-Bf9G0xds.js";
|
|
2
|
+
import { E as wakeReadCommand, T as skillFirstLine, V as wakeInteractionSentence } from "./api-command-ctHBqTL5.js";
|
|
3
3
|
import { _ as acquireListenLock, g as ListenStateError, y as listenIdentity } from "./shared-mail-files-BAQSGxxz.js";
|
|
4
4
|
import { closeSync, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, opendirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createHash, randomUUID, scryptSync } from "node:crypto";
|
|
6
6
|
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { lstat, realpath } from "node:fs/promises";
|
|
9
10
|
import { isDeepStrictEqual } from "node:util";
|
|
10
11
|
import WebSocket from "ws";
|
|
12
|
+
import { request } from "node:http";
|
|
13
|
+
import { request as request$1 } from "node:https";
|
|
14
|
+
import { performance } from "node:perf_hooks";
|
|
11
15
|
// src/oclif/connect-skill.ts
|
|
12
16
|
const CONNECT_SKILL_NAME = "primitive-connect";
|
|
13
17
|
function connectSkillTarget(options) {
|
|
@@ -564,6 +568,331 @@ async function connectNativeSession(options) {
|
|
|
564
568
|
}
|
|
565
569
|
};
|
|
566
570
|
}
|
|
571
|
+
// src/oclif/listen-handlers.ts
|
|
572
|
+
const HANDLER_TIMEOUT_MS = 3e4;
|
|
573
|
+
const MAX_RESPONSE_BYTES = 1048576;
|
|
574
|
+
const FORWARDED_HEADERS = /* @__PURE__ */ new Set([
|
|
575
|
+
"content-type",
|
|
576
|
+
"user-agent",
|
|
577
|
+
"primitive-signature",
|
|
578
|
+
"mymx-signature",
|
|
579
|
+
"x-webhook-event",
|
|
580
|
+
"x-webhook-id",
|
|
581
|
+
"x-primitive-webhook-delivery-id",
|
|
582
|
+
"x-primitive-email-id",
|
|
583
|
+
"x-primitive-endpoint-id"
|
|
584
|
+
]);
|
|
585
|
+
function normalizeForwardTarget(target) {
|
|
586
|
+
if (/^\d+$/.test(target)) return `http://localhost:${target}`;
|
|
587
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/.test(target)) return `http://${target}`;
|
|
588
|
+
return target;
|
|
589
|
+
}
|
|
590
|
+
function validateListenHandlerOptions(options) {
|
|
591
|
+
if (options.exec !== void 0 && process.platform === "win32") throw new Error("--exec requires a POSIX shell and process groups. On Windows, use --forward-to with a local HTTP handler.");
|
|
592
|
+
if (options.exec !== void 0 && options.forwardTo !== void 0) throw new Error("Choose either --exec or --forward-to.");
|
|
593
|
+
if (options.exec !== void 0 && (!options.exec.trim() || options.exec.includes("\0"))) throw new Error("--exec requires a nonempty shell command.");
|
|
594
|
+
if (options.forwardTo !== void 0) {
|
|
595
|
+
let url;
|
|
596
|
+
try {
|
|
597
|
+
url = new URL(normalizeForwardTarget(options.forwardTo));
|
|
598
|
+
} catch {
|
|
599
|
+
throw new Error("--forward-to requires an HTTP or HTTPS URL.");
|
|
600
|
+
}
|
|
601
|
+
if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password || url.hash) throw new Error("--forward-to requires HTTP or HTTPS without userinfo or a fragment.");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** User hooks retain their configuration, but not the CLI's API credentials. */
|
|
605
|
+
function hookEnvironment(delivery) {
|
|
606
|
+
const env = { ...process.env };
|
|
607
|
+
for (const name of Object.keys(env)) if (/^PRIMITIVE_(?:(?:API_|OAUTH_|AUTH_)?(?:KEY|TOKEN|ACCESS_TOKEN|REFRESH_TOKEN)|API_HEADERS|LEASE_TOKEN)$/i.test(name)) delete env[name];
|
|
608
|
+
return {
|
|
609
|
+
...env,
|
|
610
|
+
PRIMITIVE_EVENT_ID: delivery.event_id,
|
|
611
|
+
PRIMITIVE_DELIVERY_ID: delivery.delivery_id,
|
|
612
|
+
PRIMITIVE_EVENT_TYPE: delivery.event_type
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
async function writeChunk(output, chunk, signal) {
|
|
616
|
+
signal.throwIfAborted();
|
|
617
|
+
let onError = () => {};
|
|
618
|
+
let onAbort = () => {};
|
|
619
|
+
let onDrain = () => {};
|
|
620
|
+
let onClose = () => {};
|
|
621
|
+
try {
|
|
622
|
+
await new Promise((resolve, reject) => {
|
|
623
|
+
let callbackDone = false;
|
|
624
|
+
let returned = false;
|
|
625
|
+
let accepted = false;
|
|
626
|
+
let drained = false;
|
|
627
|
+
const finish = () => {
|
|
628
|
+
if (returned && callbackDone && (accepted || drained)) resolve();
|
|
629
|
+
};
|
|
630
|
+
onError = reject;
|
|
631
|
+
onAbort = () => reject(signal.reason);
|
|
632
|
+
onClose = () => reject(/* @__PURE__ */ new Error("Output closed before accepting the event."));
|
|
633
|
+
onDrain = () => {
|
|
634
|
+
drained = true;
|
|
635
|
+
finish();
|
|
636
|
+
};
|
|
637
|
+
output.on("error", onError);
|
|
638
|
+
output.on("close", onClose);
|
|
639
|
+
output.on("drain", onDrain);
|
|
640
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
641
|
+
accepted = output.write(chunk, (error) => {
|
|
642
|
+
if (error) reject(error);
|
|
643
|
+
else {
|
|
644
|
+
callbackDone = true;
|
|
645
|
+
finish();
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
returned = true;
|
|
649
|
+
finish();
|
|
650
|
+
});
|
|
651
|
+
} finally {
|
|
652
|
+
output.off("error", onError);
|
|
653
|
+
output.off("close", onClose);
|
|
654
|
+
output.off("drain", onDrain);
|
|
655
|
+
signal.removeEventListener("abort", onAbort);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function stopProcessGroup(child) {
|
|
659
|
+
if (!child.pid) return;
|
|
660
|
+
try {
|
|
661
|
+
process.kill(-child.pid, "SIGKILL");
|
|
662
|
+
} catch {}
|
|
663
|
+
}
|
|
664
|
+
var ChildCleanupError = class extends Error {};
|
|
665
|
+
async function reapChild(child, closed) {
|
|
666
|
+
stopProcessGroup(child);
|
|
667
|
+
child.stdin?.destroy();
|
|
668
|
+
child.stdout?.destroy();
|
|
669
|
+
child.stderr?.destroy();
|
|
670
|
+
let timer;
|
|
671
|
+
try {
|
|
672
|
+
await Promise.race([closed, new Promise((_resolve, reject) => {
|
|
673
|
+
timer = setTimeout(() => reject(new ChildCleanupError("The hook did not close after termination. Its delivery remains uncompleted.")), 1e3);
|
|
674
|
+
})]);
|
|
675
|
+
} finally {
|
|
676
|
+
clearTimeout(timer);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function execute(command, delivery, stderr, signal) {
|
|
680
|
+
signal.throwIfAborted();
|
|
681
|
+
const child = spawn(command, {
|
|
682
|
+
shell: true,
|
|
683
|
+
detached: true,
|
|
684
|
+
env: hookEnvironment(delivery),
|
|
685
|
+
stdio: [
|
|
686
|
+
"pipe",
|
|
687
|
+
"pipe",
|
|
688
|
+
"pipe"
|
|
689
|
+
],
|
|
690
|
+
windowsHide: true
|
|
691
|
+
});
|
|
692
|
+
const closed = new Promise((resolve) => child.once("close", () => resolve()));
|
|
693
|
+
const stop = () => stopProcessGroup(child);
|
|
694
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
695
|
+
const consume = async (stream) => {
|
|
696
|
+
for await (const chunk of stream) await writeChunk(stderr, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), signal);
|
|
697
|
+
};
|
|
698
|
+
child.once("exit", stop);
|
|
699
|
+
try {
|
|
700
|
+
const exited = new Promise((resolve, reject) => {
|
|
701
|
+
child.once("error", reject);
|
|
702
|
+
child.once("close", (code) => resolve(code));
|
|
703
|
+
});
|
|
704
|
+
const stdin = writeChunk(child.stdin, Buffer.from(delivery.body, "utf8"), signal).then(() => child.stdin.end());
|
|
705
|
+
const [code] = await Promise.all([
|
|
706
|
+
exited,
|
|
707
|
+
stdin,
|
|
708
|
+
consume(child.stdout),
|
|
709
|
+
consume(child.stderr)
|
|
710
|
+
]);
|
|
711
|
+
signal.throwIfAborted();
|
|
712
|
+
return code;
|
|
713
|
+
} finally {
|
|
714
|
+
try {
|
|
715
|
+
await reapChild(child, closed);
|
|
716
|
+
} finally {
|
|
717
|
+
signal.removeEventListener("abort", stop);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function structuredErrorCode(body) {
|
|
722
|
+
try {
|
|
723
|
+
const value = JSON.parse(body);
|
|
724
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
725
|
+
const object = value;
|
|
726
|
+
const nested = typeof object.error === "object" && object.error !== null ? object.error.code : void 0;
|
|
727
|
+
const code = typeof nested === "string" ? nested : object.code;
|
|
728
|
+
return typeof code === "string" && /^[a-z][a-z0-9_]{0,63}$/.test(code) ? code : void 0;
|
|
729
|
+
} catch {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
var ForwardFailure = class extends Error {
|
|
734
|
+
statusCode;
|
|
735
|
+
transportError;
|
|
736
|
+
constructor(statusCode, transportError) {
|
|
737
|
+
super("Webhook transport did not complete.");
|
|
738
|
+
this.statusCode = statusCode;
|
|
739
|
+
this.transportError = transportError;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
async function forward(target, delivery, signal) {
|
|
743
|
+
signal.throwIfAborted();
|
|
744
|
+
const url = new URL(normalizeForwardTarget(target));
|
|
745
|
+
const body = Buffer.from(delivery.body, "utf8");
|
|
746
|
+
const headers = {};
|
|
747
|
+
for (const [name, value] of Object.entries(delivery.headers)) if (FORWARDED_HEADERS.has(name.toLowerCase())) headers[name.toLowerCase()] = value;
|
|
748
|
+
headers["content-length"] = String(body.length);
|
|
749
|
+
return new Promise((resolve, reject) => {
|
|
750
|
+
let observedStatus = null;
|
|
751
|
+
let settled = false;
|
|
752
|
+
const fail = (kind = observedStatus === null ? "network" : "io") => {
|
|
753
|
+
if (settled) return;
|
|
754
|
+
settled = true;
|
|
755
|
+
reject(new ForwardFailure(observedStatus, kind));
|
|
756
|
+
};
|
|
757
|
+
const request$2 = (url.protocol === "https:" ? request$1 : request)(url, {
|
|
758
|
+
method: "POST",
|
|
759
|
+
headers,
|
|
760
|
+
signal,
|
|
761
|
+
rejectUnauthorized: true
|
|
762
|
+
}, (response) => {
|
|
763
|
+
observedStatus = response.statusCode ?? null;
|
|
764
|
+
const chunks = [];
|
|
765
|
+
let size = 0;
|
|
766
|
+
response.on("error", () => fail());
|
|
767
|
+
response.on("aborted", () => fail());
|
|
768
|
+
response.on("data", (chunk) => {
|
|
769
|
+
size += chunk.length;
|
|
770
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
771
|
+
fail("response_too_large");
|
|
772
|
+
request$2.destroy();
|
|
773
|
+
} else chunks.push(chunk);
|
|
774
|
+
});
|
|
775
|
+
response.on("end", () => {
|
|
776
|
+
if (settled) return;
|
|
777
|
+
if (!response.complete || size > MAX_RESPONSE_BYTES) {
|
|
778
|
+
fail(size > MAX_RESPONSE_BYTES ? "response_too_large" : "io");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
settled = true;
|
|
782
|
+
const status = response.statusCode ?? 0;
|
|
783
|
+
const succeeded = status >= 200 && status < 300;
|
|
784
|
+
resolve({
|
|
785
|
+
status,
|
|
786
|
+
confirmed: succeeded && (response.headers["x-primitive-confirmed"] === "true" || response.headers["x-mymx-confirmed"] === "true"),
|
|
787
|
+
errorCode: succeeded ? void 0 : status >= 300 && status < 400 ? "handler_redirect" : structuredErrorCode(Buffer.concat(chunks).toString("utf8")) ?? (status >= 400 && status < 500 ? "handler_4xx" : status >= 500 ? "handler_5xx" : "unknown_error")
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
request$2.on("error", () => fail());
|
|
792
|
+
request$2.end(body);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function jsonLine(body) {
|
|
796
|
+
try {
|
|
797
|
+
return JSON.stringify(JSON.parse(body));
|
|
798
|
+
} catch {
|
|
799
|
+
throw new Error("Webhook body is not valid JSON.");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* The API withholds an event's body from a credential that cannot read the
|
|
804
|
+
* mail it carries. Such an event is never handed on as if it were the event:
|
|
805
|
+
* listening stops with an explanation and the delivery stays uncompleted.
|
|
806
|
+
*/
|
|
807
|
+
function readableDelivery(delivery) {
|
|
808
|
+
if (delivery.body === null) throw new ListenStateError("The API withheld this event's content from the listening credential; the delivery remains uncompleted. Listen with a credential that can read this mail.");
|
|
809
|
+
return {
|
|
810
|
+
...delivery,
|
|
811
|
+
body: delivery.body
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function createListenHandler(options = {}) {
|
|
815
|
+
validateListenHandlerOptions(options);
|
|
816
|
+
return async (received, signal) => {
|
|
817
|
+
signal.throwIfAborted();
|
|
818
|
+
const delivery = readableDelivery(received);
|
|
819
|
+
const start = performance.now();
|
|
820
|
+
const work = new AbortController();
|
|
821
|
+
let timedOut = false;
|
|
822
|
+
const cancel = () => work.abort(signal.reason);
|
|
823
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
824
|
+
const timer = setTimeout(() => {
|
|
825
|
+
timedOut = true;
|
|
826
|
+
work.abort(/* @__PURE__ */ new Error("Event handler timed out."));
|
|
827
|
+
}, HANDLER_TIMEOUT_MS);
|
|
828
|
+
const duration = () => Math.min(HANDLER_TIMEOUT_MS, Math.max(0, Math.round(performance.now() - start)));
|
|
829
|
+
try {
|
|
830
|
+
if (options.exec !== void 0) {
|
|
831
|
+
const code = await execute(options.exec, delivery, options.stderr ?? process.stderr, work.signal);
|
|
832
|
+
signal.throwIfAborted();
|
|
833
|
+
return {
|
|
834
|
+
succeeded: code === 0,
|
|
835
|
+
outcome: {
|
|
836
|
+
mode: "exec",
|
|
837
|
+
exit_code: code,
|
|
838
|
+
duration_ms: duration()
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
if (options.forwardTo !== void 0) {
|
|
843
|
+
const response = await forward(options.forwardTo, delivery, work.signal);
|
|
844
|
+
signal.throwIfAborted();
|
|
845
|
+
return {
|
|
846
|
+
succeeded: response.status >= 200 && response.status < 300,
|
|
847
|
+
outcome: {
|
|
848
|
+
mode: "http",
|
|
849
|
+
status_code: response.status,
|
|
850
|
+
confirmed: response.confirmed,
|
|
851
|
+
duration_ms: duration(),
|
|
852
|
+
...response.errorCode ? { error_code: response.errorCode } : {}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
await writeChunk(options.stdout ?? process.stdout, Buffer.from(`${jsonLine(delivery.body)}\n`, "utf8"), work.signal);
|
|
857
|
+
signal.throwIfAborted();
|
|
858
|
+
return {
|
|
859
|
+
succeeded: true,
|
|
860
|
+
outcome: {
|
|
861
|
+
mode: "stdout",
|
|
862
|
+
write_succeeded: true,
|
|
863
|
+
duration_ms: duration()
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
} catch (error) {
|
|
867
|
+
signal.throwIfAborted();
|
|
868
|
+
if (error instanceof ChildCleanupError) throw error;
|
|
869
|
+
if (options.exec !== void 0) return {
|
|
870
|
+
succeeded: false,
|
|
871
|
+
outcome: {
|
|
872
|
+
mode: "exec",
|
|
873
|
+
exit_code: null,
|
|
874
|
+
transport_error: timedOut ? "timeout" : "io",
|
|
875
|
+
duration_ms: duration()
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
if (options.forwardTo !== void 0) return {
|
|
879
|
+
succeeded: false,
|
|
880
|
+
outcome: {
|
|
881
|
+
mode: "http",
|
|
882
|
+
status_code: error instanceof ForwardFailure ? error.statusCode : null,
|
|
883
|
+
confirmed: false,
|
|
884
|
+
transport_error: timedOut ? "timeout" : error instanceof ForwardFailure ? error.transportError : "network",
|
|
885
|
+
duration_ms: duration()
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
throw error;
|
|
889
|
+
} finally {
|
|
890
|
+
clearTimeout(timer);
|
|
891
|
+
signal.removeEventListener("abort", cancel);
|
|
892
|
+
work.abort();
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
567
896
|
// src/oclif/notify-session-content.ts
|
|
568
897
|
var NotificationRetryError = class extends ListenStateError {};
|
|
569
898
|
function notificationPartReader(client) {
|
|
@@ -951,9 +1280,10 @@ async function openSessionNotifications(options) {
|
|
|
951
1280
|
}
|
|
952
1281
|
});
|
|
953
1282
|
if (delivery.event_type !== "email.received") throw new ListenStateError("Notification mode received an unrelated event. It remains uncompleted; use an email.received-only subscription.");
|
|
1283
|
+
const { body } = readableDelivery(delivery);
|
|
954
1284
|
let event;
|
|
955
1285
|
try {
|
|
956
|
-
event = parseWebhookEvent(JSON.parse(
|
|
1286
|
+
event = parseWebhookEvent(JSON.parse(body), delivery.event_type);
|
|
957
1287
|
} catch {
|
|
958
1288
|
throw new ListenStateError("Invalid email event; the delivery remains uncompleted.");
|
|
959
1289
|
}
|
|
@@ -1114,4 +1444,4 @@ async function openSessionNotifications(options) {
|
|
|
1114
1444
|
}
|
|
1115
1445
|
var ContactNoticeSuppressed = class extends Error {};
|
|
1116
1446
|
var ContactNoticeDeferred = class extends Error {};
|
|
1117
|
-
export {
|
|
1447
|
+
export { readBundledConnectSkill as S, NotificationOutcomeUnknownError as _, NotificationRetryError as a, installedConnectSkillFile as b, readConversationStatusContent as c, SESSION_UUID as d, connectNativeSession as f, NativeSessionNotLoadedError as g, NativeSessionError as h, notificationReceiptPage as i, createListenHandler as l, NativeSessionDisconnectedError as m, notificationSenders as n, isRoutineNotificationContent as o, defaultSessionSocket as p, openSessionNotifications as r, notificationPartReader as s, notificationScope as t, readableDelivery as u, connectSkillTarget as v, installedVersion as x, installConnectSkill as y };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Mt as createAuthenticatedCliApiClient,
|
|
1
|
+
import { Gt as getSentEmail } from "../repeats-Dj_o6c-X-Bf9G0xds.js";
|
|
2
|
+
import { Mt as createAuthenticatedCliApiClient, x as readThreadContext } from "../api-command-ctHBqTL5.js";
|
|
3
3
|
import { AUTO_SIGNAL_KIND_ENV, AUTO_WORKING_RENEW_MS, autoSignalsDisabled, clearWorkingSending, markWorkingSending, readAutoClaim, readWorkingLease, recordRenewer, stopWorkingLease, writeWorkingLease } from "./auto-signals.js";
|
|
4
4
|
import { _ as acquireListenLock } from "../shared-mail-files-BAQSGxxz.js";
|
|
5
|
-
import { t as sendSignal } from "../signal-command-
|
|
5
|
+
import { t as sendSignal } from "../signal-command-CRUMUCNa.js";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
// src/oclif/auto-signal-worker.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as isPlainChatReply, o as scopedChatSenderTrust } from "../scoped-chat-
|
|
1
|
+
import { n as isPlainChatReply, o as scopedChatSenderTrust } from "../scoped-chat-DZ42V3jq.js";
|
|
2
2
|
import { d as readMailJson, h as writeMailJson, i as mailId, l as privateMailDirectory, r as mailAddress } from "../shared-mail-files-BAQSGxxz.js";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -63,7 +63,7 @@ function autoSignalEligible(detail, identity, admission) {
|
|
|
63
63
|
if (!detail.message_id) return false;
|
|
64
64
|
const parsed = detail.parsed;
|
|
65
65
|
const to = parsed?.to_addresses;
|
|
66
|
-
if (!Array.isArray(to) || to.length !== 1 || lower(to[0]?.address) !== agent || (parsed
|
|
66
|
+
if (!Array.isArray(to) || to.length !== 1 || lower(to[0]?.address) !== agent || (parsed?.cc?.length ?? 0) > 0 || (parsed?.bcc?.length ?? 0) > 0) return false;
|
|
67
67
|
return isPlainChatReply(detail);
|
|
68
68
|
} catch {
|
|
69
69
|
return false;
|