primcli 1.75.0 → 3.0.0-preview.10
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 +131 -68
- package/bin/claude-pending-mail.mjs +1 -1
- package/bin/run.js +14 -0
- package/dist/api-command-D0KHkqhA.js +56084 -0
- package/dist/{cli-config-B80p0pLk.js → cli-config-Ms_0vNjq.js} +59 -16
- package/dist/{json-output-BzSBR-KX.js → json-output-ch756TXV.js} +14 -1
- package/dist/{listen-background-C0F9gNDD.js → listen-background-DyBYca28.js} +323 -36
- package/dist/{machine-session-Cq_nD5eF.js → machine-session-CI8tnGzz.js} +27 -14
- package/dist/{notify-session-CbY4FYUt.js → notify-session-DN8tSfAi.js} +335 -5
- package/dist/oclif/auto-signal-worker.js +3 -3
- package/dist/oclif/auto-signals.js +2 -2
- package/dist/oclif/command-not-found.js +62 -0
- package/dist/oclif/global-org-flag.js +76 -0
- package/dist/oclif/index.js +61145 -36959
- package/dist/oclif/listen-supervisor.js +1 -1
- package/dist/oclif/proxy-auto-detect.js +1 -1
- package/dist/oclif/receiver-heal.js +5 -4
- package/dist/oclif/root-signup-hint.js +17 -17
- 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-CDaE-kdZ.js} +3981 -930
- package/dist/{scoped-chat-B0thCwME.js → scoped-chat-C4TDfZND.js} +1 -1
- package/dist/{signal-command-DCPsJBdz.js → signal-command-f5QeK45s.js} +9 -8
- package/man/primitive.1 +50 -0
- package/package.json +68 -11
- package/dist/api-command-rlS3eTlZ.js +0 -2938
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { B as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 { Dn as listSentEmails, Fn as putContact, Gn as searchEmails, Ht as getEmail, Jn as sendEmail, Lt as getAgentContactPolicy, Nn as putAgentContact, Pn as putAgentContactPolicy, Rt as getContact, Tt as deleteContact, fn as listDomains, hr as __exportAll, ln as listContacts, on as listAgentConnections, rn as inviteAgentConnection, sn as listAgentContacts, wt as deleteAgentContact } from "./repeats-Dj_o6c-X-CDaE-kdZ.js";
|
|
3
|
+
import { At as cliInvocation, Lt as detectAgentRuntime, Mt as defaultConnectionName, nn as refreshStoredCliCredentials, rn as refuseOrgSelectionForProfile } from "./api-command-D0KHkqhA.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 {
|
|
6
|
-
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-DN8tSfAi.js";
|
|
6
|
+
import { a as backgroundListenStatus, d as stopBackgroundListen } from "./listen-background-DyBYca28.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-C4TDfZND.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";
|
|
12
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
13
14
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
+
import { homedir } from "node:os";
|
|
16
17
|
import { createConnection } from "node:net";
|
|
17
18
|
// src/oclif/owner-member-address.ts
|
|
18
19
|
const REFRESH_TIMEOUT_MS = 5e3;
|
|
@@ -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 {
|
|
2
|
-
import { B as
|
|
1
|
+
import { Mt as downloadEmailAttachmentPart, Q as parseWebhookEvent, X as isEmailReceivedEvent, Z as isTrustedSender, c as classifySignalContent } from "./repeats-Dj_o6c-X-CDaE-kdZ.js";
|
|
2
|
+
import { B as skillFirstLine, V as wakeReadCommand, nt as wakeInteractionSentence } from "./api-command-D0KHkqhA.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";
|
|
8
|
+
import { isDeepStrictEqual } from "node:util";
|
|
7
9
|
import { homedir } from "node:os";
|
|
8
10
|
import { lstat, realpath } from "node:fs/promises";
|
|
9
|
-
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 {
|
|
1
|
+
import { en as getSentEmail } from "../repeats-Dj_o6c-X-CDaE-kdZ.js";
|
|
2
|
+
import { I as readThreadContext, en as createAuthenticatedCliApiClient } from "../api-command-D0KHkqhA.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-f5QeK45s.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-C4TDfZND.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;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { r as jsonOutputRequested } from "../json-output-ch756TXV.js";
|
|
2
|
+
// src/oclif/command-not-found.ts
|
|
3
|
+
/**
|
|
4
|
+
* What to say for an id no command has. oclif joins every word before the
|
|
5
|
+
* first flag into the id while no command along the way takes arguments, so
|
|
6
|
+
* `emails list foo` arrives as `emails:list:foo`: when a leading part is a
|
|
7
|
+
* real command, the rest was an argument it does not take.
|
|
8
|
+
*/
|
|
9
|
+
function commandNotFoundMessage(id, lookup) {
|
|
10
|
+
const parts = id.split(":");
|
|
11
|
+
const typed = `primitive ${parts.join(" ")}`;
|
|
12
|
+
for (let end = parts.length - 1; end > 0; end -= 1) {
|
|
13
|
+
const known = parts.slice(0, end).join(":");
|
|
14
|
+
const command = lookup.find(known);
|
|
15
|
+
const topic = lookup.hasSubcommands(known);
|
|
16
|
+
if (!command && !topic) continue;
|
|
17
|
+
const shown = `primitive ${parts.slice(0, end).join(" ")}`;
|
|
18
|
+
const rest = parts.slice(end).join(" ");
|
|
19
|
+
if (topic || !command) return {
|
|
20
|
+
code: "command_not_found",
|
|
21
|
+
message: `Unknown command "${typed}". Run "${shown} --help" to see the commands under it.`
|
|
22
|
+
};
|
|
23
|
+
if (command.takesArguments) return {
|
|
24
|
+
code: "command_not_found",
|
|
25
|
+
message: `Unknown command "${parts.join(":")}". To pass "${rest}" to "${shown}", run "${shown} ${rest}".`
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
code: "unexpected_argument",
|
|
29
|
+
message: `Unexpected argument "${rest}" for "${shown}", which takes no arguments. Run "${shown} --help" to see the flags it takes.`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
code: "command_not_found",
|
|
34
|
+
message: `Unknown command "${typed}". Run "primitive --help" to see the commands.`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Say what went wrong in plain words, and under --json as one JSON document
|
|
39
|
+
* on stdout like every other failure, instead of oclif's "command <id> not
|
|
40
|
+
* found" naming a command nobody typed.
|
|
41
|
+
*/
|
|
42
|
+
const hook = async (options) => {
|
|
43
|
+
const { config } = options;
|
|
44
|
+
const { code, message } = commandNotFoundMessage(options.id, {
|
|
45
|
+
find: (id) => {
|
|
46
|
+
const command = config.findCommand(id);
|
|
47
|
+
return command ? { takesArguments: Object.keys(command.args ?? {}).length > 0 } : void 0;
|
|
48
|
+
},
|
|
49
|
+
hasSubcommands: (id) => config.commandIDs.some((other) => other.startsWith(`${id}:`))
|
|
50
|
+
});
|
|
51
|
+
if (jsonOutputRequested([...process.argv.slice(2), ...options.argv ?? []])) process.stdout.write(`${JSON.stringify({
|
|
52
|
+
error: {
|
|
53
|
+
code,
|
|
54
|
+
message
|
|
55
|
+
},
|
|
56
|
+
exit_code: 2
|
|
57
|
+
}, null, 2)}\n`);
|
|
58
|
+
else process.stderr.write(` › Error: ${message}\n`);
|
|
59
|
+
process.exitCode = 2;
|
|
60
|
+
return message;
|
|
61
|
+
};
|
|
62
|
+
export { commandNotFoundMessage, hook as default };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/oclif/global-org-flag.ts
|
|
2
|
+
/**
|
|
3
|
+
* The global `--org <org>` flag. bin/run.js takes it out of argv before oclif
|
|
4
|
+
* parses the command, so every command accepts it, and passes it on as
|
|
5
|
+
* PRIMITIVE_CLI_ORG. Only a saved `primitive login` made as yourself can switch
|
|
6
|
+
* organizations; every other credential refuses a different organization.
|
|
7
|
+
*
|
|
8
|
+
* No oclif import here: this runs before oclif loads.
|
|
9
|
+
*/
|
|
10
|
+
/** Selects the organization for one command. */
|
|
11
|
+
const ORG_SELECTION_ENV = "PRIMITIVE_CLI_ORG";
|
|
12
|
+
/** Split `--org <value>` and `--org=<value>` out of argv, before any `--`. */
|
|
13
|
+
function extractGlobalOrgFlag(argv) {
|
|
14
|
+
const separator = argv.indexOf("--");
|
|
15
|
+
const head = separator === -1 ? [...argv] : argv.slice(0, separator);
|
|
16
|
+
const tail = separator === -1 ? [] : argv.slice(separator);
|
|
17
|
+
const command = head[0] ?? "";
|
|
18
|
+
if (command === "memories" || command.startsWith("memories:")) return {
|
|
19
|
+
ok: true,
|
|
20
|
+
argv: [...argv],
|
|
21
|
+
org: null
|
|
22
|
+
};
|
|
23
|
+
const kept = [];
|
|
24
|
+
let org = null;
|
|
25
|
+
for (let index = 0; index < head.length; index += 1) {
|
|
26
|
+
const arg = head[index];
|
|
27
|
+
if (arg === "--org") {
|
|
28
|
+
const value = head[index + 1];
|
|
29
|
+
if (value === void 0 || value.startsWith("-") || !value.trim()) return {
|
|
30
|
+
ok: false,
|
|
31
|
+
message: "--org needs an organization id or name, for example `--org acme`."
|
|
32
|
+
};
|
|
33
|
+
org = value.trim();
|
|
34
|
+
index += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (arg.startsWith("--org=")) {
|
|
38
|
+
const value = arg.slice(6).trim();
|
|
39
|
+
if (!value) return {
|
|
40
|
+
ok: false,
|
|
41
|
+
message: "--org needs an organization id or name, for example `--org=acme`."
|
|
42
|
+
};
|
|
43
|
+
org = value;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
kept.push(arg);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
argv: [...kept, ...tail],
|
|
51
|
+
org
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Apply the flag to this process: strip it from `process.argv` and set
|
|
56
|
+
* PRIMITIVE_CLI_ORG. Returns an error message for a malformed flag.
|
|
57
|
+
*/
|
|
58
|
+
function applyGlobalOrgFlag(processArgv = process.argv, env = process.env) {
|
|
59
|
+
const parsed = extractGlobalOrgFlag(processArgv.slice(2));
|
|
60
|
+
if (!parsed.ok) return parsed.message;
|
|
61
|
+
if (parsed.org !== null) {
|
|
62
|
+
processArgv.splice(2, processArgv.length - 2, ...parsed.argv);
|
|
63
|
+
env[ORG_SELECTION_ENV] = parsed.org;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `env` without an organization selection, for a child that acts as a
|
|
69
|
+
* connected agent profile: the profile acts in its own organization.
|
|
70
|
+
*/
|
|
71
|
+
function withoutOrgSelection(env) {
|
|
72
|
+
const next = { ...env };
|
|
73
|
+
delete next[ORG_SELECTION_ENV];
|
|
74
|
+
return next;
|
|
75
|
+
}
|
|
76
|
+
export { ORG_SELECTION_ENV, applyGlobalOrgFlag, extractGlobalOrgFlag, withoutOrgSelection };
|