primcli 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/oclif/index.js +217 -41
  2. package/package.json +1 -1
@@ -13069,6 +13069,38 @@ const openapiDocument = {
13069
13069
  "attachments_download_available": {
13070
13070
  "type": "boolean",
13071
13071
  "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
13072
+ },
13073
+ "to_addresses": {
13074
+ "type": ["array", "null"],
13075
+ "items": { "type": "string" },
13076
+ "description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
13077
+ },
13078
+ "cc": {
13079
+ "type": ["array", "null"],
13080
+ "items": { "type": "string" },
13081
+ "description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
13082
+ },
13083
+ "bcc": {
13084
+ "type": ["array", "null"],
13085
+ "items": { "type": "string" },
13086
+ "description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
13087
+ },
13088
+ "reply_to": {
13089
+ "type": ["array", "null"],
13090
+ "items": { "type": "string" },
13091
+ "description": "Normalized Reply-To address list. Null when the send had none.\n"
13092
+ },
13093
+ "tags": {
13094
+ "type": ["array", "null"],
13095
+ "description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
13096
+ "items": {
13097
+ "type": "object",
13098
+ "properties": {
13099
+ "name": { "type": "string" },
13100
+ "value": { "type": "string" }
13101
+ },
13102
+ "required": ["name", "value"]
13103
+ }
13072
13104
  }
13073
13105
  }
13074
13106
  }],
@@ -31319,6 +31351,38 @@ const operationManifest = [
31319
31351
  "attachments_download_available": {
31320
31352
  "type": "boolean",
31321
31353
  "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
31354
+ },
31355
+ "to_addresses": {
31356
+ "type": ["array", "null"],
31357
+ "items": { "type": "string" },
31358
+ "description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
31359
+ },
31360
+ "cc": {
31361
+ "type": ["array", "null"],
31362
+ "items": { "type": "string" },
31363
+ "description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
31364
+ },
31365
+ "bcc": {
31366
+ "type": ["array", "null"],
31367
+ "items": { "type": "string" },
31368
+ "description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
31369
+ },
31370
+ "reply_to": {
31371
+ "type": ["array", "null"],
31372
+ "items": { "type": "string" },
31373
+ "description": "Normalized Reply-To address list. Null when the send had none.\n"
31374
+ },
31375
+ "tags": {
31376
+ "type": ["array", "null"],
31377
+ "description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
31378
+ "items": {
31379
+ "type": "object",
31380
+ "properties": {
31381
+ "name": { "type": "string" },
31382
+ "value": { "type": "string" }
31383
+ },
31384
+ "required": ["name", "value"]
31385
+ }
31322
31386
  }
31323
31387
  }
31324
31388
  }],
@@ -32022,6 +32086,38 @@ const operationManifest = [
32022
32086
  "attachments_download_available": {
32023
32087
  "type": "boolean",
32024
32088
  "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
32089
+ },
32090
+ "to_addresses": {
32091
+ "type": ["array", "null"],
32092
+ "items": { "type": "string" },
32093
+ "description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
32094
+ },
32095
+ "cc": {
32096
+ "type": ["array", "null"],
32097
+ "items": { "type": "string" },
32098
+ "description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
32099
+ },
32100
+ "bcc": {
32101
+ "type": ["array", "null"],
32102
+ "items": { "type": "string" },
32103
+ "description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
32104
+ },
32105
+ "reply_to": {
32106
+ "type": ["array", "null"],
32107
+ "items": { "type": "string" },
32108
+ "description": "Normalized Reply-To address list. Null when the send had none.\n"
32109
+ },
32110
+ "tags": {
32111
+ "type": ["array", "null"],
32112
+ "description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
32113
+ "items": {
32114
+ "type": "object",
32115
+ "properties": {
32116
+ "name": { "type": "string" },
32117
+ "value": { "type": "string" }
32118
+ },
32119
+ "required": ["name", "value"]
32120
+ }
32025
32121
  }
32026
32122
  }
32027
32123
  }],
@@ -33009,6 +33105,38 @@ const operationManifest = [
33009
33105
  "attachments_download_available": {
33010
33106
  "type": "boolean",
33011
33107
  "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
33108
+ },
33109
+ "to_addresses": {
33110
+ "type": ["array", "null"],
33111
+ "items": { "type": "string" },
33112
+ "description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
33113
+ },
33114
+ "cc": {
33115
+ "type": ["array", "null"],
33116
+ "items": { "type": "string" },
33117
+ "description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
33118
+ },
33119
+ "bcc": {
33120
+ "type": ["array", "null"],
33121
+ "items": { "type": "string" },
33122
+ "description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
33123
+ },
33124
+ "reply_to": {
33125
+ "type": ["array", "null"],
33126
+ "items": { "type": "string" },
33127
+ "description": "Normalized Reply-To address list. Null when the send had none.\n"
33128
+ },
33129
+ "tags": {
33130
+ "type": ["array", "null"],
33131
+ "description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
33132
+ "items": {
33133
+ "type": "object",
33134
+ "properties": {
33135
+ "name": { "type": "string" },
33136
+ "value": { "type": "string" }
33137
+ },
33138
+ "required": ["name", "value"]
33139
+ }
33012
33140
  }
33013
33141
  }
33014
33142
  }],
@@ -35849,7 +35977,13 @@ function boundKey(item) {
35849
35977
  * session a second address. A profile that still has any receive hook for
35850
35978
  * the session, or the only profile bound to a session, is restored as usual.
35851
35979
  */
35852
- function heldSessionProfiles(hooks, bound, restore) {
35980
+ /**
35981
+ * For each session, the connected profiles it receives as through the
35982
+ * per-session receive hooks in `hooks`, keyed like `boundKey`. A hook for a
35983
+ * disconnected or removed profile is stale (machine doctor removes it), so
35984
+ * the session does not receive through it.
35985
+ */
35986
+ function receivingProfilesBySession(hooks, always) {
35853
35987
  const receiving = /* @__PURE__ */ new Map();
35854
35988
  for (const event of SESSION_EVENTS) {
35855
35989
  const entries = hooks[event];
@@ -35859,18 +35993,42 @@ function heldSessionProfiles(hooks, bound, restore) {
35859
35993
  for (const hook of entry.hooks) {
35860
35994
  const parsed = parseSessionHook(hook);
35861
35995
  if (!parsed?.profile || !parsed.session) continue;
35862
- if (!existsSync(parsed.configDir) || !profileStillConnected(parsed.configDir, parsed.profile)) continue;
35863
35996
  const session = parsed.session.toLowerCase();
35864
- const profiles = receiving.get(session) ?? /* @__PURE__ */ new Set();
35865
- profiles.add([
35997
+ const key = [
35866
35998
  resolve(parsed.configDir),
35867
35999
  parsed.profile,
35868
36000
  session
35869
- ].join("\0"));
36001
+ ].join("\0");
36002
+ if (key !== always && (!existsSync(parsed.configDir) || !profileStillConnected(parsed.configDir, parsed.profile))) continue;
36003
+ const profiles = receiving.get(session) ?? /* @__PURE__ */ new Set();
36004
+ profiles.add(key);
35870
36005
  receiving.set(session, profiles);
35871
36006
  }
35872
36007
  }
35873
36008
  }
36009
+ return receiving;
36010
+ }
36011
+ /**
36012
+ * The other connected profiles a session already receives as, when `target`
36013
+ * itself has no receive hook for it. Adding `target`'s hooks then would give
36014
+ * the session a second address; an empty list means it would not (the
36015
+ * session receives as nobody else, or `target` already has hooks there and
36016
+ * only needs them repaired). Uses the same rule as machine doctor, so an
36017
+ * automatic install never binds a profile doctor would leave alone.
36018
+ */
36019
+ function otherSessionReceivers(hooks, target) {
36020
+ const session = target.session.toLowerCase();
36021
+ const own = [
36022
+ resolve(target.configDir),
36023
+ target.profile,
36024
+ session
36025
+ ].join("\0");
36026
+ const present = receivingProfilesBySession(hooks, own).get(session) ?? /* @__PURE__ */ new Set();
36027
+ if (present.has(own)) return [];
36028
+ return [...present].map((other) => other.split("\0")[1] ?? "").sort();
36029
+ }
36030
+ function heldSessionProfiles(hooks, bound, restore) {
36031
+ const receiving = receivingProfilesBySession(hooks);
35874
36032
  const held = /* @__PURE__ */ new Map();
35875
36033
  for (const item of bound) {
35876
36034
  const key = boundKey(item);
@@ -36272,11 +36430,18 @@ function installClaudeWakeHook(options) {
36272
36430
  const agentAddress = mailAddress(options.agentAddress);
36273
36431
  if (!SESSION_UUID$1.test(options.sessionId)) return "unavailable";
36274
36432
  const sessionId = options.sessionId.toLowerCase();
36275
- return editClaudeSettings(resolve((options.env ?? process.env).CLAUDE_CONFIG_DIR || join(homedir(), ".claude")), (settings) => {
36433
+ const claudeDir = resolve((options.env ?? process.env).CLAUDE_CONFIG_DIR || join(homedir(), ".claude"));
36434
+ let held = false;
36435
+ if (!editClaudeSettings(claudeDir, (settings) => {
36276
36436
  const hooks = settings.hooks ?? {};
36277
36437
  if (!record$3(hooks)) throw new Error("Invalid hook settings");
36278
36438
  for (const event of WAKE_EVENTS) if (!Array.isArray(hooks[event] ?? [])) throw new Error(`Invalid ${event} hooks`);
36279
36439
  if (!Array.isArray(hooks[PENDING_EVENT] ?? [])) throw new Error(`Invalid ${PENDING_EVENT} hooks`);
36440
+ held = options.yieldToOtherProfiles === true && otherSessionReceivers(hooks, {
36441
+ configDir,
36442
+ profile: profileName,
36443
+ session: sessionId
36444
+ }).length > 0;
36280
36445
  const hook = {
36281
36446
  type: "command",
36282
36447
  command: process.execPath,
@@ -36320,19 +36485,26 @@ function installClaudeWakeHook(options) {
36320
36485
  }
36321
36486
  };
36322
36487
  const nextHooks = { ...hooks };
36323
- for (const event of WAKE_EVENTS) nextHooks[event] = [...(hooks[event] ?? []).flatMap((entry) => {
36324
- if (!record$3(entry) || !Array.isArray(entry.hooks)) return [entry];
36325
- const siblings = entry.hooks.filter((candidate) => !isOwnHook(candidate));
36326
- if (siblings.length === entry.hooks.length) return [entry];
36327
- return siblings.length ? [{
36328
- ...entry,
36329
- hooks: siblings
36330
- }] : [];
36331
- }), event === "SessionStart" ? {
36332
- matcher: "resume",
36333
- hooks: [hook]
36334
- } : { hooks: [hook] }];
36335
- nextHooks[PENDING_EVENT] = [...(hooks[PENDING_EVENT] ?? []).flatMap((entry) => {
36488
+ for (const event of WAKE_EVENTS) {
36489
+ const entries = hooks[event];
36490
+ const retained = (entries ?? []).flatMap((entry) => {
36491
+ if (!record$3(entry) || !Array.isArray(entry.hooks)) return [entry];
36492
+ const siblings = entry.hooks.filter((candidate) => !isOwnHook(candidate));
36493
+ if (siblings.length === entry.hooks.length) return [entry];
36494
+ return siblings.length ? [{
36495
+ ...entry,
36496
+ hooks: siblings
36497
+ }] : [];
36498
+ });
36499
+ if (held) {
36500
+ if (entries) nextHooks[event] = retained;
36501
+ } else nextHooks[event] = [...retained, event === "SessionStart" ? {
36502
+ matcher: "resume",
36503
+ hooks: [hook]
36504
+ } : { hooks: [hook] }];
36505
+ }
36506
+ const pendingEntries = hooks[PENDING_EVENT];
36507
+ const pendingRetained = (pendingEntries ?? []).flatMap((entry) => {
36336
36508
  if (!record$3(entry) || !Array.isArray(entry.hooks)) return [entry];
36337
36509
  const siblings = entry.hooks.filter((candidate) => !isOwnPendingHook(candidate));
36338
36510
  if (siblings.length === entry.hooks.length) return [entry];
@@ -36340,12 +36512,17 @@ function installClaudeWakeHook(options) {
36340
36512
  ...entry,
36341
36513
  hooks: siblings
36342
36514
  }] : [];
36343
- }), { hooks: [pendingHook] }];
36515
+ });
36516
+ if (held) {
36517
+ if (pendingEntries) nextHooks[PENDING_EVENT] = pendingRetained;
36518
+ } else nextHooks[PENDING_EVENT] = [...pendingRetained, { hooks: [pendingHook] }];
36519
+ if (held && JSON.stringify(nextHooks) === JSON.stringify(hooks)) return null;
36344
36520
  return {
36345
36521
  ...settings,
36346
36522
  hooks: nextHooks
36347
36523
  };
36348
- }) ? "installed_unverified" : "unavailable";
36524
+ })) return "unavailable";
36525
+ return held ? "held_for_other_profile" : "installed_unverified";
36349
36526
  } catch {
36350
36527
  return "unavailable";
36351
36528
  }
@@ -39356,6 +39533,7 @@ function withDefined(defaults, overrides) {
39356
39533
  ...Object.fromEntries(Object.entries(overrides ?? {}).filter(([, value]) => value !== void 0))
39357
39534
  };
39358
39535
  }
39536
+ const HELD_DETAIL = "This session already receives as another connected profile, so this profile's receive hooks were not added. Run `primitive machine doctor --fix --profile <profile>` to bind it deliberately.";
39359
39537
  const ENDED_DETAIL = "This session ended, so its agent was disconnected. A new address is never created for the same session.";
39360
39538
  const ENDED_PENDING_DETAIL = "This session ended, but disconnecting its agent has not been confirmed yet; `primitive machine doctor --fix` retries it.";
39361
39539
  /**
@@ -39474,14 +39652,19 @@ async function registerSession(options) {
39474
39652
  agentInfo: seeded
39475
39653
  } : current);
39476
39654
  };
39477
- const installHook = (profile) => runtime !== "claude" || receiverMode(agentProfileDirectory(options.configDir, profileName)) !== "external" || deps.installClaudeHook({
39478
- cliPath: options.cliPath,
39479
- configDir: options.configDir,
39480
- profileName,
39481
- agentAddress: profile.agent_address,
39482
- sessionId,
39483
- env
39484
- }) === "installed_unverified";
39655
+ const claudeHook = (agentAddress) => {
39656
+ const outcome = deps.installClaudeHook({
39657
+ cliPath: options.cliPath,
39658
+ configDir: options.configDir,
39659
+ profileName,
39660
+ agentAddress,
39661
+ sessionId,
39662
+ env,
39663
+ yieldToOtherProfiles: true
39664
+ });
39665
+ return outcome === "installed_unverified" ? "installed" : outcome === "held_for_other_profile" ? "held" : "failed";
39666
+ };
39667
+ const installHook = (profile) => runtime !== "claude" || receiverMode(agentProfileDirectory(options.configDir, profileName)) !== "external" ? "installed" : claudeHook(profile.agent_address);
39485
39668
  const existing = loadConnectedAgentProfile(options.configDir, profileName);
39486
39669
  if (existing) {
39487
39670
  record = await updateSessionRecord(options.configDir, sessionId, (current) => current ?? newRecord("existing", existing.agent_address));
@@ -39515,7 +39698,7 @@ async function registerSession(options) {
39515
39698
  ...withAddress,
39516
39699
  receiving: RECEIVING[runtime],
39517
39700
  status: "already_registered",
39518
- detail: hooked ? "This session is already connected." : "This session is already connected, but its receive hook could not be installed. Run `primitive machine doctor`."
39701
+ detail: hooked === "installed" ? "This session is already connected." : hooked === "held" ? HELD_DETAIL : "This session is already connected, but its receive hook could not be installed. Run `primitive machine doctor`."
39519
39702
  };
39520
39703
  }
39521
39704
  } else if (record?.address && record.createdBy === "session-register" && !enrollmentAddress(options.configDir, profileName).exists || record?.createdBy === "existing") return {
@@ -39576,15 +39759,8 @@ async function registerSession(options) {
39576
39759
  const address = result.identity.agentAddress;
39577
39760
  const submitted = verificationReplySubmitted(result.verification.state);
39578
39761
  const status = result.connection.status;
39579
- let hooked = true;
39580
- if (runtime === "claude" && submitted && status !== "owner_inactive") hooked = deps.installClaudeHook({
39581
- cliPath: options.cliPath,
39582
- configDir: options.configDir,
39583
- profileName,
39584
- agentAddress: address,
39585
- sessionId,
39586
- env
39587
- }) === "installed_unverified";
39762
+ let hooked = "installed";
39763
+ if (runtime === "claude" && submitted && status !== "owner_inactive") hooked = claudeHook(address);
39588
39764
  if (status === "connected") await finishAgentInfo();
39589
39765
  const withAddress = {
39590
39766
  ...known,
@@ -39594,7 +39770,7 @@ async function registerSession(options) {
39594
39770
  if (status === "connected") return {
39595
39771
  ...withAddress,
39596
39772
  status: "registered",
39597
- detail: hooked ? `Connected as ${address}.` : `Connected as ${address}, but the receive hook could not be installed. Run \`primitive machine doctor\`.`
39773
+ detail: hooked === "installed" ? `Connected as ${address}.` : hooked === "held" ? `Connected as ${address}. ${HELD_DETAIL}` : `Connected as ${address}, but the receive hook could not be installed. Run \`primitive machine doctor\`.`
39598
39774
  };
39599
39775
  if (status === "revoked" || status === "owner_inactive") return {
39600
39776
  ...withAddress,
@@ -47338,7 +47514,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
47338
47514
  url: "https://primitive.dev"
47339
47515
  };
47340
47516
  const SDK_VERSION_RANGE = "^1.34.0";
47341
- const CLI_VERSION_RANGE = "^1.44.0";
47517
+ const CLI_VERSION_RANGE = "^1.45.0";
47342
47518
  const ESBUILD_VERSION_RANGE = "^0.27.0";
47343
47519
  function renderHandler() {
47344
47520
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.44.0",
3
+ "version": "1.45.0",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,