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.
@@ -1,18 +1,19 @@
1
- import { B as parseConnectedAgentProfile, F as agentProfileName, G as PrimitiveApiClient, H as profileAlreadyConnectedMessage, I as agentProfilesDirectory, L as connectedAgentIdentity, M as AGENT_PROFILE_ENV, N as AgentConnectionSetupError, P as agentProfileDirectory, R as connectedApiBaseUrl, V as parseOwnerMemberAddress, W as saveConnectedAgentProfile, x as loadCliCredentials, z as loadConnectedAgentProfile } from "./cli-config-B80p0pLk.js";
2
- import { $t as listDomains, At as getAgentContactPolicy, Dn as sendEmail, Ft as getEmail, Jt as listAgentContacts, Kn as __exportAll, Kt as inviteAgentConnection, Xt as listContacts, cn as listSentEmails, gn as putContact, hn as putAgentContactPolicy, jt as getContact, mn as putAgentContact, qt as listAgentConnections, vt as deleteAgentContact, wn as searchEmails, yt as deleteContact } from "./repeats-Dj_o6c-X-LyCkKYjp.js";
3
- import { Pt as refreshStoredCliCredentials, gt as defaultConnectionName, mt as cliInvocation, xt as detectAgentRuntime } from "./api-command-rlS3eTlZ.js";
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 { _ as installConnectSkill, a as NotificationRetryError, b as readBundledConnectSkill, d as defaultSessionSocket, f as NativeSessionDisconnectedError, l as SESSION_UUID, t as notificationScope, u as connectNativeSession } from "./notify-session-CbY4FYUt.js";
6
- import { l as stopBackgroundListen, r as backgroundListenStatus } from "./listen-background-C0F9gNDD.js";
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 { o as scopedChatSenderTrust } from "./scoped-chat-B0thCwME.js";
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 (typeof network.member_policy_required !== "boolean") throw unavailable();
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
- if (!(await managedDomains(new PrimitiveApiClient({
5594
+ const domains = await managedDomains(new PrimitiveApiClient({
5583
5595
  apiKey: credentials.access_token,
5584
5596
  apiBaseUrl,
5585
5597
  fetch: params.fetch
5586
- }), apiBaseUrl)).length) throw refuse("No managed domain is available for enrollment.");
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-LyCkKYjp.js";
2
- import { B as wakeInteractionSentence, T as wakeReadCommand, w as skillFirstLine } from "./api-command-rlS3eTlZ.js";
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(delivery.body), delivery.event_type);
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 { installConnectSkill as _, NotificationRetryError as a, readBundledConnectSkill as b, readConversationStatusContent as c, defaultSessionSocket as d, NativeSessionDisconnectedError as f, connectSkillTarget as g, NotificationOutcomeUnknownError as h, notificationReceiptPage as i, SESSION_UUID as l, NativeSessionNotLoadedError as m, notificationSenders as n, isRoutineNotificationContent as o, NativeSessionError as p, openSessionNotifications as r, notificationPartReader as s, notificationScope as t, connectNativeSession as u, installedConnectSkillFile as v, installedVersion as y };
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 { Ut as getSentEmail } from "../repeats-Dj_o6c-X-LyCkKYjp.js";
2
- import { Mt as createAuthenticatedCliApiClient, b as readThreadContext } from "../api-command-rlS3eTlZ.js";
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-DCPsJBdz.js";
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-B0thCwME.js";
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.cc?.length ?? 0) > 0 || (parsed.bcc?.length ?? 0) > 0) return false;
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 };