switchroom 0.12.13 → 0.12.14

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.
@@ -20645,18 +20645,6 @@ function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
20645
20645
  }
20646
20646
  };
20647
20647
  }
20648
- function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
20649
- if (!agentGoogleAccount)
20650
- return false;
20651
- const account = agentGoogleAccount.trim().toLowerCase();
20652
- if (account.length === 0)
20653
- return false;
20654
- const acctEntry = googleAccounts?.[account];
20655
- if (!acctEntry)
20656
- return false;
20657
- const enabledFor = acctEntry.enabled_for ?? [];
20658
- return enabledFor.includes(agentName);
20659
- }
20660
20648
  function getBuiltinDefaultMcpEntries() {
20661
20649
  const playwright = getPlaywrightMcpSettingsEntry();
20662
20650
  return [
@@ -20689,6 +20677,48 @@ var init_scaffold_integration = __esm(() => {
20689
20677
  init_hindsight();
20690
20678
  });
20691
20679
 
20680
+ // src/config/google-workspace-acl.ts
20681
+ function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
20682
+ if (!agentGoogleAccount)
20683
+ return false;
20684
+ const account = agentGoogleAccount.trim().toLowerCase();
20685
+ if (account.length === 0)
20686
+ return false;
20687
+ const acctEntry = googleAccounts?.[account];
20688
+ if (!acctEntry)
20689
+ return false;
20690
+ const enabledFor = acctEntry.enabled_for ?? [];
20691
+ return enabledFor.includes(agentName);
20692
+ }
20693
+ function vaultRefKey(value) {
20694
+ if (typeof value !== "string" || !value.startsWith("vault:"))
20695
+ return null;
20696
+ const key = value.slice("vault:".length).split("#")[0];
20697
+ return key.length > 0 ? key : null;
20698
+ }
20699
+ function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
20700
+ if (!agentName || !key)
20701
+ return false;
20702
+ const agentConfig = config.agents?.[agentName];
20703
+ if (!agentConfig)
20704
+ return false;
20705
+ if (agentConfig.mcp_servers?.["gdrive"] === false) {
20706
+ return false;
20707
+ }
20708
+ const account = agentConfig.google_workspace?.account;
20709
+ if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
20710
+ return false;
20711
+ }
20712
+ const gw = config.google_workspace;
20713
+ if (!gw)
20714
+ return false;
20715
+ for (const ref of [gw.google_client_id, gw.google_client_secret]) {
20716
+ if (vaultRefKey(ref) === key)
20717
+ return true;
20718
+ }
20719
+ return false;
20720
+ }
20721
+
20692
20722
  // src/agents/reconcile-default-skills.ts
20693
20723
  import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readlinkSync as readlinkSync2, rmSync, symlinkSync } from "node:fs";
20694
20724
  import { homedir as homedir2 } from "node:os";
@@ -21379,7 +21409,7 @@ function stripWireFields(entry) {
21379
21409
  files: entry.files
21380
21410
  };
21381
21411
  }
21382
- var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
21412
+ var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
21383
21413
  var init_protocol = __esm(() => {
21384
21414
  init_zod();
21385
21415
  MAX_FRAME_BYTES = 64 * 1024;
@@ -21438,6 +21468,24 @@ var init_protocol = __esm(() => {
21438
21468
  v: exports_external.literal(1),
21439
21469
  op: exports_external.literal("lock")
21440
21470
  });
21471
+ PreflightAccessRequestSchema = exports_external.object({
21472
+ v: exports_external.literal(1),
21473
+ op: exports_external.literal("preflight_access"),
21474
+ agent: exports_external.string().min(1),
21475
+ keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
21476
+ });
21477
+ OkPreflightAccessResponseSchema = exports_external.object({
21478
+ ok: exports_external.literal(true),
21479
+ op: exports_external.literal("preflight_access"),
21480
+ results: exports_external.array(exports_external.object({
21481
+ key: exports_external.string(),
21482
+ exists: exports_external.boolean(),
21483
+ acl_ok: exports_external.boolean(),
21484
+ acl_reason: exports_external.string().optional(),
21485
+ scope_ok: exports_external.boolean(),
21486
+ scope_reason: exports_external.string().optional()
21487
+ }))
21488
+ });
21441
21489
  ApprovalRequestRequestSchema = exports_external.object({
21442
21490
  v: exports_external.literal(1),
21443
21491
  op: exports_external.literal("approval_request"),
@@ -21495,6 +21543,7 @@ var init_protocol = __esm(() => {
21495
21543
  ListRequestSchema,
21496
21544
  StatusRequestSchema,
21497
21545
  LockRequestSchema,
21546
+ PreflightAccessRequestSchema,
21498
21547
  MintGrantRequestSchema,
21499
21548
  ListGrantsRequestSchema,
21500
21549
  RevokeGrantRequestSchema,
@@ -21635,6 +21684,7 @@ var init_protocol = __esm(() => {
21635
21684
  OkKeysResponseSchema,
21636
21685
  OkStatusResponseSchema,
21637
21686
  OkLockResponseSchema,
21687
+ OkPreflightAccessResponseSchema,
21638
21688
  OkPutResponseSchema,
21639
21689
  OkMintGrantResponseSchema,
21640
21690
  OkListGrantsResponseSchema,
@@ -27169,6 +27219,9 @@ function checkAclByAgent(config, agentName, key) {
27169
27219
  if (googleSlot !== null) {
27170
27220
  return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
27171
27221
  }
27222
+ if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
27223
+ return { allow: true };
27224
+ }
27172
27225
  const agentBot = agentConfig.bot_token;
27173
27226
  const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
27174
27227
  if (typeof botRef === "string" && botRef.startsWith("vault:")) {
@@ -27226,6 +27279,7 @@ function checkGoogleAccountAcl(config, agentName, account, key) {
27226
27279
  }
27227
27280
  return { allow: true };
27228
27281
  }
27282
+ var init_acl = () => {};
27229
27283
 
27230
27284
  // src/util/audit-hashchain.ts
27231
27285
  import { createHash as createHash6 } from "node:crypto";
@@ -28525,7 +28579,8 @@ import {
28525
28579
  realpathSync as realpathSync4,
28526
28580
  statSync as statSync20
28527
28581
  } from "node:fs";
28528
- import { userInfo } from "node:os";
28582
+ import { userInfo, homedir as homedir21 } from "node:os";
28583
+ import { join as join40 } from "node:path";
28529
28584
  function resolveVaultPath2(config) {
28530
28585
  return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
28531
28586
  }
@@ -28575,7 +28630,47 @@ function collectVaultRefs2(value, out) {
28575
28630
  }
28576
28631
  }
28577
28632
  }
28578
- function runSecretAccessChecks(config, deps = {}) {
28633
+ function collectNeeds(resolved) {
28634
+ const cronKeys = new Set;
28635
+ for (const entry of resolved.schedule ?? []) {
28636
+ for (const s of entry.secrets ?? [])
28637
+ cronKeys.add(s);
28638
+ }
28639
+ const refKeys = new Set;
28640
+ collectVaultRefs2(resolved, refKeys);
28641
+ return { needed: new Set([...cronKeys, ...refKeys]), cronKeys };
28642
+ }
28643
+ function keyGap(key, isCron, exists, acl, scope) {
28644
+ const isGoogleSlot = key.startsWith("google:");
28645
+ if (!isGoogleSlot && !exists)
28646
+ return `'${key}' missing from the vault`;
28647
+ if (isCron && !acl.allow) {
28648
+ return `'${key}' (cron) \u2014 no static ACL grants read (${acl.reason})`;
28649
+ }
28650
+ if (!scope.allow) {
28651
+ return `'${key}' \u2014 per-key scope denies read (${scope.reason})`;
28652
+ }
28653
+ return null;
28654
+ }
28655
+ async function defaultPreflight(socketPath, agent, keys) {
28656
+ const r = await rpcRaw({ v: 1, op: "preflight_access", agent, keys }, { socket: socketPath, timeoutMs: 5000 });
28657
+ if (r.kind === "unreachable")
28658
+ return { kind: "unreachable", msg: r.msg };
28659
+ const resp = r.resp;
28660
+ if (resp.ok === true && resp.op === "preflight_access") {
28661
+ return {
28662
+ kind: "ok",
28663
+ results: resp.results
28664
+ };
28665
+ }
28666
+ if (resp.ok === false && resp.code === "LOCKED")
28667
+ return { kind: "locked" };
28668
+ return {
28669
+ kind: "unreachable",
28670
+ msg: resp.ok === false ? `broker error ${resp.code}: ${resp.msg}` : "unexpected broker response"
28671
+ };
28672
+ }
28673
+ async function runSecretAccessChecks(config, deps = {}) {
28579
28674
  const results = [];
28580
28675
  const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
28581
28676
  const statVault = deps.statVault ?? defaultStatVault;
@@ -28609,14 +28704,57 @@ function runSecretAccessChecks(config, deps = {}) {
28609
28704
  detail: `operator can read ${vf.realPath}`
28610
28705
  });
28611
28706
  }
28707
+ const pushAgentResult = (name, total, gaps) => {
28708
+ results.push(gaps.length === 0 ? {
28709
+ name: `secret access: ${name}`,
28710
+ status: "ok",
28711
+ detail: `${total} secret(s): all present + ACL ok`
28712
+ } : {
28713
+ name: `secret access: ${name}`,
28714
+ status: "fail",
28715
+ detail: `${gaps.length}/${total} unreachable \u2014 ${gaps.join("; ")}`,
28716
+ fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
28717
+ });
28718
+ };
28612
28719
  const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
28613
28720
  if (!passphrase) {
28614
- results.push({
28615
- name: "agent secret access",
28616
- status: "skip",
28617
- detail: "SWITCHROOM_VAULT_PASSPHRASE not set \u2014 cannot enumerate vault keys/ACLs " + "to verify per-agent secret access",
28618
- fix: "Export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
28619
- });
28721
+ const sock = deps.brokerOperatorSocket ?? join40(homedir21(), ".switchroom", "broker-operator", "sock");
28722
+ const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
28723
+ for (const name of Object.keys(config.agents ?? {})) {
28724
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28725
+ const { needed, cronKeys } = collectNeeds(resolved);
28726
+ if (needed.size === 0) {
28727
+ results.push({
28728
+ name: `secret access: ${name}`,
28729
+ status: "ok",
28730
+ detail: "no declared vault secrets"
28731
+ });
28732
+ continue;
28733
+ }
28734
+ const out = await preflight(name, [...needed].sort());
28735
+ if (out.kind === "unreachable" || out.kind === "locked") {
28736
+ results.push({
28737
+ name: "agent secret access",
28738
+ status: "skip",
28739
+ detail: out.kind === "locked" ? "vault-broker is locked \u2014 cron-secret existence/ACL unverified (re-run after it unlocks; it auto-unlocks on boot)" : `vault-broker operator socket unreachable (${out.msg}) and SWITCHROOM_VAULT_PASSPHRASE unset \u2014 cron-secret existence/ACL unverified`,
28740
+ fix: "Ensure the broker operator socket (~/.switchroom/broker-operator/sock) is bound, or export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
28741
+ });
28742
+ return results;
28743
+ }
28744
+ const byKey = new Map(out.results.map((r) => [r.key, r]));
28745
+ const gaps = [];
28746
+ for (const key of [...needed].sort()) {
28747
+ const r = byKey.get(key);
28748
+ if (r === undefined) {
28749
+ gaps.push(`'${key}' \u2014 broker returned no result`);
28750
+ continue;
28751
+ }
28752
+ const g = keyGap(key, cronKeys.has(key), r.exists, { allow: r.acl_ok, reason: r.acl_reason }, { allow: r.scope_ok, reason: r.scope_reason });
28753
+ if (g)
28754
+ gaps.push(g);
28755
+ }
28756
+ pushAgentResult(name, needed.size, gaps);
28757
+ }
28620
28758
  return results;
28621
28759
  }
28622
28760
  let entries;
@@ -28631,17 +28769,9 @@ function runSecretAccessChecks(config, deps = {}) {
28631
28769
  });
28632
28770
  return results;
28633
28771
  }
28634
- const agents = Object.keys(config.agents ?? {});
28635
- for (const name of agents) {
28772
+ for (const name of Object.keys(config.agents ?? {})) {
28636
28773
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28637
- const cronKeys = new Set;
28638
- for (const entry of resolved.schedule ?? []) {
28639
- for (const s of entry.secrets ?? [])
28640
- cronKeys.add(s);
28641
- }
28642
- const refKeys = new Set;
28643
- collectVaultRefs2(resolved, refKeys);
28644
- const needed = new Set([...cronKeys, ...refKeys]);
28774
+ const { needed, cronKeys } = collectNeeds(resolved);
28645
28775
  if (needed.size === 0) {
28646
28776
  results.push({
28647
28777
  name: `secret access: ${name}`,
@@ -28652,37 +28782,11 @@ function runSecretAccessChecks(config, deps = {}) {
28652
28782
  }
28653
28783
  const gaps = [];
28654
28784
  for (const key of [...needed].sort()) {
28655
- const isGoogleSlot = key.startsWith("google:");
28656
- if (!isGoogleSlot && !(key in entries)) {
28657
- gaps.push(`'${key}' missing from the vault`);
28658
- continue;
28659
- }
28660
- const byScope = checkEntryScope(entries[key]?.scope, name);
28661
- if (cronKeys.has(key)) {
28662
- const byAgent = checkAclByAgent(config, name, key);
28663
- if (!byAgent.allow) {
28664
- gaps.push(`'${key}' (cron) \u2014 no static ACL grants read (${byAgent.reason})`);
28665
- continue;
28666
- }
28667
- }
28668
- if (!byScope.allow) {
28669
- gaps.push(`'${key}' \u2014 per-key scope denies read (${byScope.reason})`);
28670
- }
28671
- }
28672
- if (gaps.length === 0) {
28673
- results.push({
28674
- name: `secret access: ${name}`,
28675
- status: "ok",
28676
- detail: `${needed.size} secret(s): all present + ACL ok`
28677
- });
28678
- } else {
28679
- results.push({
28680
- name: `secret access: ${name}`,
28681
- status: "fail",
28682
- detail: `${gaps.length}/${needed.size} unreachable \u2014 ${gaps.join("; ")}`,
28683
- fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
28684
- });
28785
+ const g = keyGap(key, cronKeys.has(key), key in entries, checkAclByAgent(config, name, key), checkEntryScope(entries[key]?.scope, name));
28786
+ if (g)
28787
+ gaps.push(g);
28685
28788
  }
28789
+ pushAgentResult(name, needed.size, gaps);
28686
28790
  }
28687
28791
  return results;
28688
28792
  }
@@ -28690,6 +28794,8 @@ var init_doctor_secret_access = __esm(() => {
28690
28794
  init_paths();
28691
28795
  init_merge();
28692
28796
  init_vault();
28797
+ init_acl();
28798
+ init_client();
28693
28799
  });
28694
28800
 
28695
28801
  // src/cli/doctor-inlined-secrets.ts
@@ -28788,19 +28894,19 @@ var init_doctor_inlined_secrets = __esm(() => {
28788
28894
 
28789
28895
  // src/cli/doctor-audit-integrity.ts
28790
28896
  import { readFileSync as fsReadFileSync2 } from "node:fs";
28791
- import { homedir as homedir21 } from "node:os";
28792
- import { join as join40 } from "node:path";
28897
+ import { homedir as homedir22 } from "node:os";
28898
+ import { join as join41 } from "node:path";
28793
28899
  function rootWrittenLogs(home2) {
28794
28900
  return [
28795
- { label: "vault-broker", path: join40(home2, ".switchroom", "vault-audit.log") },
28901
+ { label: "vault-broker", path: join41(home2, ".switchroom", "vault-audit.log") },
28796
28902
  {
28797
28903
  label: "hostd",
28798
- path: join40(home2, ".switchroom", "host-control-audit.log")
28904
+ path: join41(home2, ".switchroom", "host-control-audit.log")
28799
28905
  }
28800
28906
  ];
28801
28907
  }
28802
28908
  function runAuditIntegrityChecks(deps = {}) {
28803
- const home2 = deps.homeDir ?? homedir21();
28909
+ const home2 = deps.homeDir ?? homedir22();
28804
28910
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
28805
28911
  const results = [];
28806
28912
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -29077,14 +29183,14 @@ var init_client4 = __esm(() => {
29077
29183
 
29078
29184
  // src/cli/doctor-agent-smoke.ts
29079
29185
  import { existsSync as existsSync47 } from "node:fs";
29080
- import { homedir as homedir22 } from "node:os";
29081
- import { join as join41 } from "node:path";
29186
+ import { homedir as homedir23 } from "node:os";
29187
+ import { join as join42 } from "node:path";
29082
29188
  import { randomUUID as randomUUID4 } from "node:crypto";
29083
29189
  async function runAgentSmokeChecks(config, deps = {}) {
29084
29190
  if (deps.fast)
29085
29191
  return [];
29086
- const home2 = deps.homeDir ?? homedir22();
29087
- const sock = deps.operatorSockPath ?? join41(home2, ".switchroom", "hostd", "operator", "sock");
29192
+ const home2 = deps.homeDir ?? homedir23();
29193
+ const sock = deps.operatorSockPath ?? join42(home2, ".switchroom", "hostd", "operator", "sock");
29088
29194
  if (!deps.hostdRequestImpl && !existsSync47(sock)) {
29089
29195
  return [
29090
29196
  {
@@ -29211,16 +29317,16 @@ import {
29211
29317
  readdirSync as readdirSync17,
29212
29318
  statSync as statSync21
29213
29319
  } from "node:fs";
29214
- import { dirname as dirname12, join as join42, resolve as resolve29 } from "node:path";
29320
+ import { dirname as dirname12, join as join43, resolve as resolve29 } from "node:path";
29215
29321
  import { createPublicKey, createPrivateKey } from "node:crypto";
29216
29322
  function findInNvm(bin) {
29217
- const nvmRoot = join42(process.env.HOME ?? "", ".nvm", "versions", "node");
29323
+ const nvmRoot = join43(process.env.HOME ?? "", ".nvm", "versions", "node");
29218
29324
  if (!existsSync48(nvmRoot))
29219
29325
  return null;
29220
29326
  try {
29221
29327
  const versions = readdirSync17(nvmRoot).sort().reverse();
29222
29328
  for (const v of versions) {
29223
- const candidate = join42(nvmRoot, v, "bin", bin);
29329
+ const candidate = join43(nvmRoot, v, "bin", bin);
29224
29330
  try {
29225
29331
  const s = statSync21(candidate);
29226
29332
  if (s.isFile() || s.isSymbolicLink()) {
@@ -29385,7 +29491,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
29385
29491
  if (envBrowsersPath && envBrowsersPath.length > 0) {
29386
29492
  cacheLocations.push(envBrowsersPath);
29387
29493
  }
29388
- cacheLocations.push(join42(homeDir, ".cache", "ms-playwright"));
29494
+ cacheLocations.push(join43(homeDir, ".cache", "ms-playwright"));
29389
29495
  for (const cacheDir of cacheLocations) {
29390
29496
  if (!existsSync48(cacheDir))
29391
29497
  continue;
@@ -29393,10 +29499,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
29393
29499
  const entries = readdirSync17(cacheDir).filter((e) => e.startsWith("chromium"));
29394
29500
  for (const entry of entries) {
29395
29501
  const candidates2 = [
29396
- join42(cacheDir, entry, "chrome-linux64", "chrome"),
29397
- join42(cacheDir, entry, "chrome-linux", "chrome"),
29398
- join42(cacheDir, entry, "chrome-linux64", "headless_shell"),
29399
- join42(cacheDir, entry, "chrome-linux", "headless_shell")
29502
+ join43(cacheDir, entry, "chrome-linux64", "chrome"),
29503
+ join43(cacheDir, entry, "chrome-linux", "chrome"),
29504
+ join43(cacheDir, entry, "chrome-linux64", "headless_shell"),
29505
+ join43(cacheDir, entry, "chrome-linux", "headless_shell")
29400
29506
  ];
29401
29507
  for (const path4 of candidates2) {
29402
29508
  if (existsSync48(path4))
@@ -29479,7 +29585,7 @@ function checkConfig(config, configPath) {
29479
29585
  function checkLegacyState() {
29480
29586
  const results = [];
29481
29587
  const h = process.env.HOME ?? "/root";
29482
- const clerkDir = join42(h, LEGACY_STATE_DIR);
29588
+ const clerkDir = join43(h, LEGACY_STATE_DIR);
29483
29589
  const clerkPresent = existsSync48(clerkDir);
29484
29590
  results.push({
29485
29591
  name: "legacy ~/.clerk state",
@@ -29489,7 +29595,7 @@ function checkLegacyState() {
29489
29595
  fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
29490
29596
  } : {}
29491
29597
  });
29492
- const legacySock = join42(h, ".switchroom", "vault-broker.sock");
29598
+ const legacySock = join43(h, ".switchroom", "vault-broker.sock");
29493
29599
  let sockStat = null;
29494
29600
  try {
29495
29601
  sockStat = lstatSync5(legacySock);
@@ -29693,7 +29799,7 @@ async function checkHindsight(config) {
29693
29799
  }
29694
29800
  function checkPendingRetainsQueue(dir) {
29695
29801
  const home2 = process.env.HOME ?? "";
29696
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join42(home2, ".hindsight", "pending-retains");
29802
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join43(home2, ".hindsight", "pending-retains");
29697
29803
  if (!existsSync48(pendingDir)) {
29698
29804
  return {
29699
29805
  name: "pending-retains queue",
@@ -29824,7 +29930,7 @@ async function checkTelegram(config) {
29824
29930
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
29825
29931
  if (plugin !== "switchroom")
29826
29932
  continue;
29827
- const envPath = join42(agentsDir, name, "telegram", ".env");
29933
+ const envPath = join43(agentsDir, name, "telegram", ".env");
29828
29934
  const read = tryReadHostFile(envPath);
29829
29935
  if (read.kind === "eacces") {
29830
29936
  results.push({
@@ -29907,7 +30013,7 @@ function checkStartShStale(agentName, startShPath) {
29907
30013
  }
29908
30014
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
29909
30015
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
29910
- const path4 = join42(agentDir, "home", ".switchroom");
30016
+ const path4 = join43(agentDir, "home", ".switchroom");
29911
30017
  let stats;
29912
30018
  try {
29913
30019
  stats = lstatSync5(path4);
@@ -29944,7 +30050,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
29944
30050
  }
29945
30051
  function checkRepoHygiene(repoRoot) {
29946
30052
  const results = [];
29947
- const exportDir = join42(repoRoot, "clerk-export");
30053
+ const exportDir = join43(repoRoot, "clerk-export");
29948
30054
  if (existsSync48(exportDir)) {
29949
30055
  results.push({
29950
30056
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -29953,7 +30059,7 @@ function checkRepoHygiene(repoRoot) {
29953
30059
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
29954
30060
  });
29955
30061
  }
29956
- const knownTarball = join42(repoRoot, "clerk-export-with-secrets.tar.gz");
30062
+ const knownTarball = join43(repoRoot, "clerk-export-with-secrets.tar.gz");
29957
30063
  if (existsSync48(knownTarball)) {
29958
30064
  results.push({
29959
30065
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -29971,7 +30077,7 @@ function checkRepoHygiene(repoRoot) {
29971
30077
  results.push({
29972
30078
  name: `repo hygiene: ${name} on disk (#1072)`,
29973
30079
  status: "warn",
29974
- detail: `${join42(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
30080
+ detail: `${join43(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
29975
30081
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
29976
30082
  });
29977
30083
  }
@@ -29994,9 +30100,9 @@ function checkRepoHygiene(repoRoot) {
29994
30100
  }
29995
30101
  function isSwitchroomCheckout(dir) {
29996
30102
  try {
29997
- if (!existsSync48(join42(dir, ".git")))
30103
+ if (!existsSync48(join43(dir, ".git")))
29998
30104
  return false;
29999
- const pkgPath = join42(dir, "package.json");
30105
+ const pkgPath = join43(dir, "package.json");
30000
30106
  if (!existsSync48(pkgPath))
30001
30107
  return false;
30002
30108
  const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
@@ -30033,7 +30139,7 @@ function checkAgents(config, configPath) {
30033
30139
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
30034
30140
  });
30035
30141
  }
30036
- results.push(checkStartShStale(name, join42(agentDir, "start.sh")));
30142
+ results.push(checkStartShStale(name, join43(agentDir, "start.sh")));
30037
30143
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
30038
30144
  const status = statuses[name];
30039
30145
  const active = status?.active ?? "unknown";
@@ -30110,7 +30216,7 @@ function checkAgents(config, configPath) {
30110
30216
  }
30111
30217
  }
30112
30218
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
30113
- const mcpJsonPath = join42(agentDir, ".mcp.json");
30219
+ const mcpJsonPath = join43(agentDir, ".mcp.json");
30114
30220
  if (!existsSync48(mcpJsonPath)) {
30115
30221
  results.push({
30116
30222
  name: `${name}: .mcp.json`,
@@ -30411,7 +30517,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
30411
30517
  };
30412
30518
  }
30413
30519
  const credDir = dirname12(envPath);
30414
- const authScript = join42(credDir, "claude-auth.py");
30520
+ const authScript = join43(credDir, "claude-auth.py");
30415
30521
  if (!existsSync48(authScript)) {
30416
30522
  return {
30417
30523
  name: "mff: auth flow",
@@ -30690,7 +30796,7 @@ function registerDoctorCommand(program3) {
30690
30796
  },
30691
30797
  { title: "Legacy State", results: checkLegacyState() },
30692
30798
  { title: "Vault", results: checkVault(config) },
30693
- { title: "Vault access", results: runSecretAccessChecks(config) },
30799
+ { title: "Vault access", results: await runSecretAccessChecks(config) },
30694
30800
  { title: "Memory (Hindsight)", results: await checkHindsight(config) },
30695
30801
  { title: "Telegram", results: await checkTelegram(config) },
30696
30802
  { title: "Agents", results: checkAgents(config, configPath) },
@@ -47047,8 +47153,8 @@ var {
47047
47153
  } = import__.default;
47048
47154
 
47049
47155
  // src/build-info.ts
47050
- var VERSION = "0.12.13";
47051
- var COMMIT_SHA = "fc96015d";
47156
+ var VERSION = "0.12.14";
47157
+ var COMMIT_SHA = "db6d87d6";
47052
47158
 
47053
47159
  // src/cli/agent.ts
47054
47160
  init_source();
@@ -55869,6 +55975,7 @@ var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
55869
55975
 
55870
55976
  // src/vault/broker/server.ts
55871
55977
  init_peercred();
55978
+ init_acl();
55872
55979
  init_protocol();
55873
55980
 
55874
55981
  // src/vault/broker/audit-log.ts
@@ -58199,7 +58306,9 @@ class VaultBroker {
58199
58306
  this._writePidFile();
58200
58307
  this._sdNotify(`READY=1
58201
58308
  `);
58202
- this._tryAutoUnlock();
58309
+ if (this.testOpts._testSecrets === undefined) {
58310
+ this._tryAutoUnlock();
58311
+ }
58203
58312
  if (process.platform !== "linux") {
58204
58313
  process.stderr.write(`[vault-broker] WARNING: running on ${process.platform} with ` + `SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 \u2014 peercred ACL is disabled. ` + `Access control is socket file mode 0600 ONLY. Do not use this configuration for production secrets.
58205
58314
  `);
@@ -58510,6 +58619,49 @@ class VaultBroker {
58510
58619
  socket.write(encodeResponse({ ok: true, locked: true }));
58511
58620
  return;
58512
58621
  }
58622
+ if (req.op === "preflight_access") {
58623
+ if (!isOperator) {
58624
+ writeAudit({
58625
+ ts: new Date().toISOString(),
58626
+ op: "preflight_access",
58627
+ caller: auditCaller,
58628
+ pid: auditPid,
58629
+ cgroup: auditCgroup,
58630
+ result: "denied:operator-only"
58631
+ });
58632
+ socket.write(encodeResponse(errorResponse("DENIED", "preflight_access is operator-only")));
58633
+ return;
58634
+ }
58635
+ if (this.secrets === null) {
58636
+ socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
58637
+ return;
58638
+ }
58639
+ const pfSecrets = this.secrets;
58640
+ const pfConfig = this.config;
58641
+ const results = req.keys.map((key) => {
58642
+ const exists = Object.prototype.hasOwnProperty.call(pfSecrets, key);
58643
+ const acl = pfConfig !== null ? checkAclByAgent(pfConfig, req.agent, key) : { allow: false, reason: "broker has no config loaded" };
58644
+ const scope = checkEntryScope(pfSecrets[key]?.scope, req.agent);
58645
+ return {
58646
+ key,
58647
+ exists,
58648
+ acl_ok: acl.allow,
58649
+ ...acl.allow ? {} : { acl_reason: acl.reason },
58650
+ scope_ok: scope.allow,
58651
+ ...scope.allow ? {} : { scope_reason: scope.reason }
58652
+ };
58653
+ });
58654
+ writeAudit({
58655
+ ts: new Date().toISOString(),
58656
+ op: "preflight_access",
58657
+ caller: auditCaller,
58658
+ pid: auditPid,
58659
+ cgroup: auditCgroup,
58660
+ result: `allowed:agent=${req.agent},keys=${req.keys.length}`
58661
+ });
58662
+ socket.write(encodeResponse({ ok: true, op: "preflight_access", results }));
58663
+ return;
58664
+ }
58513
58665
  if (req.op === "list") {
58514
58666
  if (this.secrets === null) {
58515
58667
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
@@ -68972,15 +69124,15 @@ init_loader();
68972
69124
  init_lifecycle();
68973
69125
  import { cpSync as cpSync2, existsSync as existsSync49, mkdirSync as mkdirSync27, readFileSync as readFileSync45, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync22 } from "node:fs";
68974
69126
  import { spawnSync as spawnSync8 } from "node:child_process";
68975
- import { join as join43, dirname as dirname13, resolve as resolve30 } from "node:path";
68976
- import { homedir as homedir23 } from "node:os";
68977
- var DEFAULT_COMPOSE_PATH = join43(homedir23(), ".switchroom", "compose", "docker-compose.yml");
69127
+ import { join as join44, dirname as dirname13, resolve as resolve30 } from "node:path";
69128
+ import { homedir as homedir24 } from "node:os";
69129
+ var DEFAULT_COMPOSE_PATH = join44(homedir24(), ".switchroom", "compose", "docker-compose.yml");
68978
69130
  function runningFromSwitchroomCheckout(scriptPath) {
68979
69131
  let dir = dirname13(scriptPath);
68980
69132
  for (let i = 0;i < 12; i++) {
68981
- if (existsSync49(join43(dir, ".git"))) {
69133
+ if (existsSync49(join44(dir, ".git"))) {
68982
69134
  try {
68983
- const pkg = JSON.parse(readFileSync45(join43(dir, "package.json"), "utf-8"));
69135
+ const pkg = JSON.parse(readFileSync45(join44(dir, "package.json"), "utf-8"));
68984
69136
  if (pkg.name === "switchroom")
68985
69137
  return true;
68986
69138
  } catch {}
@@ -69111,7 +69263,7 @@ function planUpdate(opts) {
69111
69263
  return;
69112
69264
  }
69113
69265
  const source = resolve30(import.meta.dirname, "../../skills");
69114
- const dest = join43(homedir23(), ".switchroom", "skills", "_bundled");
69266
+ const dest = join44(homedir24(), ".switchroom", "skills", "_bundled");
69115
69267
  if (!existsSync49(source)) {
69116
69268
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
69117
69269
  `);
@@ -69225,7 +69377,7 @@ function defaultStatusProbe(composePath) {
69225
69377
  } catch {}
69226
69378
  let dir = dirname13(scriptPath);
69227
69379
  for (let i = 0;i < 8; i++) {
69228
- const pkgPath = join43(dir, "package.json");
69380
+ const pkgPath = join44(dir, "package.json");
69229
69381
  if (existsSync49(pkgPath)) {
69230
69382
  try {
69231
69383
  const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
@@ -69445,7 +69597,7 @@ init_helpers();
69445
69597
  init_lifecycle();
69446
69598
  import { execSync as execSync4 } from "node:child_process";
69447
69599
  import { existsSync as existsSync50, readFileSync as readFileSync46 } from "node:fs";
69448
- import { dirname as dirname14, join as join44 } from "node:path";
69600
+ import { dirname as dirname14, join as join45 } from "node:path";
69449
69601
  function getClaudeCodeVersion() {
69450
69602
  try {
69451
69603
  const out = execSync4("claude --version 2>/dev/null", {
@@ -69495,11 +69647,11 @@ function formatUptime3(timestamp) {
69495
69647
  function locateSwitchroomInstallDir() {
69496
69648
  let dir = import.meta.dirname;
69497
69649
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
69498
- const pkgPath = join44(dir, "package.json");
69650
+ const pkgPath = join45(dir, "package.json");
69499
69651
  if (existsSync50(pkgPath)) {
69500
69652
  try {
69501
69653
  const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
69502
- if (pkg.name === "switchroom" && existsSync50(join44(dir, ".git"))) {
69654
+ if (pkg.name === "switchroom" && existsSync50(join45(dir, ".git"))) {
69503
69655
  return dir;
69504
69656
  }
69505
69657
  } catch {}
@@ -69729,7 +69881,7 @@ import {
69729
69881
  writeFileSync as writeFileSync25,
69730
69882
  writeSync as writeSync6
69731
69883
  } from "node:fs";
69732
- import { join as join45 } from "node:path";
69884
+ import { join as join46 } from "node:path";
69733
69885
  import { randomBytes as randomBytes10 } from "node:crypto";
69734
69886
  import { execSync as execSync5 } from "node:child_process";
69735
69887
 
@@ -70049,7 +70201,7 @@ function redactedMarker(ruleId) {
70049
70201
  var ISSUES_FILE = "issues.jsonl";
70050
70202
  var ISSUES_LOCK = "issues.lock";
70051
70203
  function readAll(stateDir) {
70052
- const path4 = join45(stateDir, ISSUES_FILE);
70204
+ const path4 = join46(stateDir, ISSUES_FILE);
70053
70205
  if (!existsSync51(path4))
70054
70206
  return [];
70055
70207
  let raw;
@@ -70127,7 +70279,7 @@ function record(stateDir, input, nowFn = Date.now) {
70127
70279
  });
70128
70280
  }
70129
70281
  function resolve33(stateDir, fingerprint, nowFn = Date.now) {
70130
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70282
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70131
70283
  return 0;
70132
70284
  return withLock(stateDir, () => {
70133
70285
  const all = readAll(stateDir);
@@ -70145,7 +70297,7 @@ function resolve33(stateDir, fingerprint, nowFn = Date.now) {
70145
70297
  });
70146
70298
  }
70147
70299
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
70148
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70300
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70149
70301
  return 0;
70150
70302
  return withLock(stateDir, () => {
70151
70303
  const all = readAll(stateDir);
@@ -70163,7 +70315,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
70163
70315
  });
70164
70316
  }
70165
70317
  function prune(stateDir, opts = {}) {
70166
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70318
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70167
70319
  return 0;
70168
70320
  return withLock(stateDir, () => {
70169
70321
  const all = readAll(stateDir);
@@ -70196,7 +70348,7 @@ function ensureDir(stateDir) {
70196
70348
  mkdirSync28(stateDir, { recursive: true });
70197
70349
  }
70198
70350
  function writeAll(stateDir, events) {
70199
- const path4 = join45(stateDir, ISSUES_FILE);
70351
+ const path4 = join46(stateDir, ISSUES_FILE);
70200
70352
  sweepOrphanTmpFiles(stateDir);
70201
70353
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes10(4).toString("hex")}`;
70202
70354
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -70218,7 +70370,7 @@ function sweepOrphanTmpFiles(stateDir) {
70218
70370
  for (const entry of entries) {
70219
70371
  if (!entry.startsWith(TMP_PREFIX))
70220
70372
  continue;
70221
- const tmpPath = join45(stateDir, entry);
70373
+ const tmpPath = join46(stateDir, entry);
70222
70374
  try {
70223
70375
  const stat = statSync23(tmpPath);
70224
70376
  if (stat.mtimeMs < cutoff) {
@@ -70230,7 +70382,7 @@ function sweepOrphanTmpFiles(stateDir) {
70230
70382
  var LOCK_RETRY_MS = 25;
70231
70383
  var LOCK_TIMEOUT_MS = 1e4;
70232
70384
  function withLock(stateDir, fn) {
70233
- const lockPath = join45(stateDir, ISSUES_LOCK);
70385
+ const lockPath = join46(stateDir, ISSUES_LOCK);
70234
70386
  const startedAt = Date.now();
70235
70387
  let fd = null;
70236
70388
  while (fd === null) {
@@ -70514,8 +70666,8 @@ function relTime(deltaMs) {
70514
70666
  // src/cli/deps.ts
70515
70667
  init_source();
70516
70668
  import { existsSync as existsSync54 } from "node:fs";
70517
- import { homedir as homedir26 } from "node:os";
70518
- import { join as join48, resolve as resolve34 } from "node:path";
70669
+ import { homedir as homedir27 } from "node:os";
70670
+ import { join as join49, resolve as resolve34 } from "node:path";
70519
70671
 
70520
70672
  // src/deps/python.ts
70521
70673
  import { createHash as createHash9 } from "node:crypto";
@@ -70526,8 +70678,8 @@ import {
70526
70678
  rmSync as rmSync13,
70527
70679
  writeFileSync as writeFileSync26
70528
70680
  } from "node:fs";
70529
- import { dirname as dirname15, join as join46 } from "node:path";
70530
- import { homedir as homedir24 } from "node:os";
70681
+ import { dirname as dirname15, join as join47 } from "node:path";
70682
+ import { homedir as homedir25 } from "node:os";
70531
70683
  import { execFileSync as execFileSync14 } from "node:child_process";
70532
70684
 
70533
70685
  class PythonEnvError extends Error {
@@ -70539,7 +70691,7 @@ class PythonEnvError extends Error {
70539
70691
  }
70540
70692
  }
70541
70693
  function defaultPythonCacheRoot() {
70542
- return join46(homedir24(), ".switchroom", "deps", "python");
70694
+ return join47(homedir25(), ".switchroom", "deps", "python");
70543
70695
  }
70544
70696
  function hashFile(path4) {
70545
70697
  return createHash9("sha256").update(readFileSync48(path4)).digest("hex");
@@ -70551,11 +70703,11 @@ function ensurePythonEnv(opts) {
70551
70703
  if (!existsSync52(requirementsPath)) {
70552
70704
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
70553
70705
  }
70554
- const venvDir = join46(cacheRoot, skillName);
70555
- const stampPath = join46(venvDir, ".requirements.sha256");
70556
- const binDir = join46(venvDir, "bin");
70557
- const pythonBin = join46(binDir, "python");
70558
- const pipBin = join46(binDir, "pip");
70706
+ const venvDir = join47(cacheRoot, skillName);
70707
+ const stampPath = join47(venvDir, ".requirements.sha256");
70708
+ const binDir = join47(venvDir, "bin");
70709
+ const pythonBin = join47(binDir, "python");
70710
+ const pipBin = join47(binDir, "pip");
70559
70711
  const targetHash = hashFile(requirementsPath);
70560
70712
  if (!force && existsSync52(stampPath) && existsSync52(pythonBin)) {
70561
70713
  const existingHash = readFileSync48(stampPath, "utf8").trim();
@@ -70614,8 +70766,8 @@ import {
70614
70766
  rmSync as rmSync14,
70615
70767
  writeFileSync as writeFileSync27
70616
70768
  } from "node:fs";
70617
- import { dirname as dirname16, join as join47 } from "node:path";
70618
- import { homedir as homedir25 } from "node:os";
70769
+ import { dirname as dirname16, join as join48 } from "node:path";
70770
+ import { homedir as homedir26 } from "node:os";
70619
70771
  import { execFileSync as execFileSync15 } from "node:child_process";
70620
70772
 
70621
70773
  class NodeEnvError extends Error {
@@ -70638,7 +70790,7 @@ var LOCKFILES_FOR = {
70638
70790
  npm: ["package-lock.json"]
70639
70791
  };
70640
70792
  function defaultNodeCacheRoot() {
70641
- return join47(homedir25(), ".switchroom", "deps", "node");
70793
+ return join48(homedir26(), ".switchroom", "deps", "node");
70642
70794
  }
70643
70795
  function hashDepInputs(packageJsonPath) {
70644
70796
  const sourceDir = dirname16(packageJsonPath);
@@ -70647,7 +70799,7 @@ function hashDepInputs(packageJsonPath) {
70647
70799
  `);
70648
70800
  hasher.update(readFileSync49(packageJsonPath));
70649
70801
  for (const lockName of ALL_LOCKFILES) {
70650
- const lockPath = join47(sourceDir, lockName);
70802
+ const lockPath = join48(sourceDir, lockName);
70651
70803
  if (existsSync53(lockPath)) {
70652
70804
  hasher.update(`
70653
70805
  `);
@@ -70667,10 +70819,10 @@ function ensureNodeEnv(opts) {
70667
70819
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
70668
70820
  }
70669
70821
  const sourceDir = dirname16(packageJsonPath);
70670
- const envDir = join47(cacheRoot, skillName);
70671
- const stampPath = join47(envDir, ".package.sha256");
70672
- const nodeModulesDir = join47(envDir, "node_modules");
70673
- const binDir = join47(nodeModulesDir, ".bin");
70822
+ const envDir = join48(cacheRoot, skillName);
70823
+ const stampPath = join48(envDir, ".package.sha256");
70824
+ const nodeModulesDir = join48(envDir, "node_modules");
70825
+ const binDir = join48(nodeModulesDir, ".bin");
70674
70826
  const targetHash = hashDepInputs(packageJsonPath);
70675
70827
  if (!force && existsSync53(stampPath) && existsSync53(nodeModulesDir)) {
70676
70828
  const existingHash = readFileSync49(stampPath, "utf8").trim();
@@ -70688,12 +70840,12 @@ function ensureNodeEnv(opts) {
70688
70840
  rmSync14(envDir, { recursive: true, force: true });
70689
70841
  }
70690
70842
  mkdirSync30(envDir, { recursive: true });
70691
- copyFileSync9(packageJsonPath, join47(envDir, "package.json"));
70843
+ copyFileSync9(packageJsonPath, join48(envDir, "package.json"));
70692
70844
  let copiedLockfile = false;
70693
70845
  for (const lockName of LOCKFILES_FOR[installer]) {
70694
- const lockPath = join47(sourceDir, lockName);
70846
+ const lockPath = join48(sourceDir, lockName);
70695
70847
  if (existsSync53(lockPath)) {
70696
- copyFileSync9(lockPath, join47(envDir, lockName));
70848
+ copyFileSync9(lockPath, join48(envDir, lockName));
70697
70849
  copiedLockfile = true;
70698
70850
  }
70699
70851
  }
@@ -70722,7 +70874,7 @@ function ensureNodeEnv(opts) {
70722
70874
 
70723
70875
  // src/cli/deps.ts
70724
70876
  function builtinSkillsRoot() {
70725
- return resolve34(homedir26(), ".switchroom/skills/_bundled");
70877
+ return resolve34(homedir27(), ".switchroom/skills/_bundled");
70726
70878
  }
70727
70879
  function registerDepsCommand(program3) {
70728
70880
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -70732,13 +70884,13 @@ function registerDepsCommand(program3) {
70732
70884
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
70733
70885
  process.exit(1);
70734
70886
  }
70735
- const skillDir = join48(skillsRoot, skill);
70887
+ const skillDir = join49(skillsRoot, skill);
70736
70888
  if (!existsSync54(skillDir)) {
70737
70889
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
70738
70890
  process.exit(1);
70739
70891
  }
70740
- const requirementsPath = join48(skillDir, "requirements.txt");
70741
- const packageJsonPath = join48(skillDir, "package.json");
70892
+ const requirementsPath = join49(skillDir, "requirements.txt");
70893
+ const packageJsonPath = join49(skillDir, "package.json");
70742
70894
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync54(requirementsPath));
70743
70895
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync54(packageJsonPath));
70744
70896
  let did = 0;
@@ -71693,7 +71845,7 @@ init_helpers();
71693
71845
  init_loader();
71694
71846
  init_merge();
71695
71847
  import { copyFileSync as copyFileSync10, existsSync as existsSync56, readFileSync as readFileSync50, writeFileSync as writeFileSync28 } from "node:fs";
71696
- import { join as join49, resolve as resolve36 } from "node:path";
71848
+ import { join as join50, resolve as resolve36 } from "node:path";
71697
71849
  init_schema();
71698
71850
  function resolveSoulTargetOrExit(program3, agentName) {
71699
71851
  const config = getConfig(program3);
@@ -71717,7 +71869,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
71717
71869
  profileName,
71718
71870
  profilePath,
71719
71871
  workspaceDir,
71720
- soulPath: join49(workspaceDir, "SOUL.md"),
71872
+ soulPath: join50(workspaceDir, "SOUL.md"),
71721
71873
  soul: merged.soul
71722
71874
  };
71723
71875
  }
@@ -71784,7 +71936,7 @@ function registerSoulCommand(program3) {
71784
71936
  init_helpers();
71785
71937
  init_loader();
71786
71938
  import { existsSync as existsSync57, readFileSync as readFileSync51, readdirSync as readdirSync19, statSync as statSync24 } from "node:fs";
71787
- import { resolve as resolve37, join as join50 } from "node:path";
71939
+ import { resolve as resolve37, join as join51 } from "node:path";
71788
71940
  import { createHash as createHash11 } from "node:crypto";
71789
71941
  init_merge();
71790
71942
  init_hindsight();
@@ -71798,7 +71950,7 @@ function sha256(content) {
71798
71950
  return createHash11("sha256").update(content).digest("hex").slice(0, 16);
71799
71951
  }
71800
71952
  function findLatestTranscriptJsonl(claudeConfigDir) {
71801
- const projectsDir = join50(claudeConfigDir, "projects");
71953
+ const projectsDir = join51(claudeConfigDir, "projects");
71802
71954
  if (!existsSync57(projectsDir))
71803
71955
  return;
71804
71956
  try {
@@ -71807,8 +71959,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
71807
71959
  for (const entry of entries) {
71808
71960
  if (!entry.isDirectory())
71809
71961
  continue;
71810
- const projectPath = join50(projectsDir, entry.name);
71811
- const transcriptPath = join50(projectPath, "transcript.jsonl");
71962
+ const projectPath = join51(projectsDir, entry.name);
71963
+ const transcriptPath = join51(projectPath, "transcript.jsonl");
71812
71964
  if (!existsSync57(transcriptPath))
71813
71965
  continue;
71814
71966
  const stat3 = statSync24(transcriptPath);
@@ -71877,11 +72029,11 @@ function registerDebugCommand(program3) {
71877
72029
  process.exit(1);
71878
72030
  }
71879
72031
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
71880
- const claudeConfigDir = join50(agentDir, ".claude");
71881
- const claudeMdPath = join50(agentDir, "CLAUDE.md");
71882
- const soulMdPath = join50(agentDir, "SOUL.md");
71883
- const workspaceSoulMdPath = join50(workspaceDir, "SOUL.md");
71884
- const handoffPath = join50(agentDir, ".handoff.md");
72032
+ const claudeConfigDir = join51(agentDir, ".claude");
72033
+ const claudeMdPath = join51(agentDir, "CLAUDE.md");
72034
+ const soulMdPath = join51(agentDir, "SOUL.md");
72035
+ const workspaceSoulMdPath = join51(workspaceDir, "SOUL.md");
72036
+ const handoffPath = join51(agentDir, ".handoff.md");
71885
72037
  const lastN = parseInt(opts.last, 10);
71886
72038
  if (isNaN(lastN) || lastN < 1) {
71887
72039
  console.error("--last must be a positive integer");
@@ -72031,8 +72183,8 @@ init_source();
72031
72183
  // src/worktree/claim.ts
72032
72184
  import { execFileSync as execFileSync16 } from "node:child_process";
72033
72185
  import { closeSync as closeSync11, mkdirSync as mkdirSync32, openSync as openSync11, existsSync as existsSync59, unlinkSync as unlinkSync12 } from "node:fs";
72034
- import { join as join52, resolve as resolve39 } from "node:path";
72035
- import { homedir as homedir28 } from "node:os";
72186
+ import { join as join53, resolve as resolve39 } from "node:path";
72187
+ import { homedir as homedir29 } from "node:os";
72036
72188
  import { randomBytes as randomBytes11 } from "node:crypto";
72037
72189
 
72038
72190
  // src/worktree/registry.ts
@@ -72045,13 +72197,13 @@ import {
72045
72197
  existsSync as existsSync58,
72046
72198
  renameSync as renameSync11
72047
72199
  } from "node:fs";
72048
- import { join as join51, resolve as resolve38 } from "node:path";
72049
- import { homedir as homedir27 } from "node:os";
72200
+ import { join as join52, resolve as resolve38 } from "node:path";
72201
+ import { homedir as homedir28 } from "node:os";
72050
72202
  function registryDir() {
72051
- return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join51(homedir27(), ".switchroom", "worktrees"));
72203
+ return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir28(), ".switchroom", "worktrees"));
72052
72204
  }
72053
72205
  function recordPath(id) {
72054
- return join51(registryDir(), `${id}.json`);
72206
+ return join52(registryDir(), `${id}.json`);
72055
72207
  }
72056
72208
  function ensureDir2() {
72057
72209
  mkdirSync31(registryDir(), { recursive: true });
@@ -72102,7 +72254,7 @@ function acquireRepoLock(repoPath) {
72102
72254
  const lockDir = registryDir();
72103
72255
  mkdirSync32(lockDir, { recursive: true });
72104
72256
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
72105
- const lockPath = join52(lockDir, `.lock-${lockName}`);
72257
+ const lockPath = join53(lockDir, `.lock-${lockName}`);
72106
72258
  const deadline = Date.now() + 5000;
72107
72259
  let fd = null;
72108
72260
  while (fd === null) {
@@ -72129,7 +72281,7 @@ function acquireRepoLock(repoPath) {
72129
72281
  }
72130
72282
  var DEFAULT_CONCURRENCY = 5;
72131
72283
  function worktreesBaseDir() {
72132
- return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join52(homedir28(), ".switchroom", "worktree-checkouts"));
72284
+ return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir29(), ".switchroom", "worktree-checkouts"));
72133
72285
  }
72134
72286
  function shortId() {
72135
72287
  return randomBytes11(4).toString("hex");
@@ -72151,7 +72303,7 @@ function resolveRepoPath(repo, codeRepos) {
72151
72303
  }
72152
72304
  function expandHome(p) {
72153
72305
  if (p.startsWith("~/"))
72154
- return join52(homedir28(), p.slice(2));
72306
+ return join53(homedir29(), p.slice(2));
72155
72307
  return p;
72156
72308
  }
72157
72309
  async function claimWorktree(input, codeRepos) {
@@ -72179,7 +72331,7 @@ async function claimWorktree(input, codeRepos) {
72179
72331
  branch = `task/${taskSuffix}-${id}`;
72180
72332
  const baseDir = worktreesBaseDir();
72181
72333
  mkdirSync32(baseDir, { recursive: true });
72182
- worktreePath = join52(baseDir, `${id}-${taskSuffix}`);
72334
+ worktreePath = join53(baseDir, `${id}-${taskSuffix}`);
72183
72335
  const now = new Date().toISOString();
72184
72336
  const record2 = {
72185
72337
  id,
@@ -72434,7 +72586,7 @@ import {
72434
72586
  rmSync as rmSync15,
72435
72587
  writeFileSync as writeFileSync30
72436
72588
  } from "node:fs";
72437
- import { join as join53 } from "node:path";
72589
+ import { join as join54 } from "node:path";
72438
72590
  function encodeCredentialsFilename(email) {
72439
72591
  const SAFE = new Set([
72440
72592
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -72598,16 +72750,16 @@ function resolveCredentialsDir(env2) {
72598
72750
  if (explicit && explicit.length > 0)
72599
72751
  return explicit;
72600
72752
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
72601
- return join53(stateBase, "google-workspace-mcp", "credentials");
72753
+ return join54(stateBase, "google-workspace-mcp", "credentials");
72602
72754
  }
72603
72755
  function writeSeedFile(dir, email, seed) {
72604
72756
  mkdirSync33(dir, { recursive: true, mode: 448 });
72605
72757
  chmodSync9(dir, 448);
72606
72758
  for (const name of readdirSync21(dir)) {
72607
- rmSync15(join53(dir, name), { force: true, recursive: true });
72759
+ rmSync15(join54(dir, name), { force: true, recursive: true });
72608
72760
  }
72609
72761
  const filename = encodeCredentialsFilename(email);
72610
- const filePath = join53(dir, filename);
72762
+ const filePath = join54(dir, filename);
72611
72763
  writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
72612
72764
  chmodSync9(filePath, 384);
72613
72765
  return filePath;
@@ -73105,8 +73257,8 @@ agents:
73105
73257
 
73106
73258
  // src/cli/apply.ts
73107
73259
  init_resolver();
73108
- import { dirname as dirname19, join as join57, resolve as resolve41 } from "node:path";
73109
- import { homedir as homedir30 } from "node:os";
73260
+ import { dirname as dirname19, join as join58, resolve as resolve41 } from "node:path";
73261
+ import { homedir as homedir31 } from "node:os";
73110
73262
  import { execFileSync as execFileSync19 } from "node:child_process";
73111
73263
  init_vault();
73112
73264
  init_loader();
@@ -73114,7 +73266,7 @@ init_loader();
73114
73266
 
73115
73267
  // src/cli/update-prompt-hook.ts
73116
73268
  import { existsSync as existsSync62, readFileSync as readFileSync53, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync34 } from "node:fs";
73117
- import { join as join54 } from "node:path";
73269
+ import { join as join55 } from "node:path";
73118
73270
  var HOOK_FILENAME = "update-card-on-prompt.sh";
73119
73271
  function updatePromptHookScript() {
73120
73272
  return `#!/bin/bash
@@ -73180,9 +73332,9 @@ exit 0
73180
73332
  `;
73181
73333
  }
73182
73334
  function installUpdatePromptHook(agentDir) {
73183
- const hooksDir = join54(agentDir, ".claude", "hooks");
73335
+ const hooksDir = join55(agentDir, ".claude", "hooks");
73184
73336
  mkdirSync34(hooksDir, { recursive: true });
73185
- const scriptPath = join54(hooksDir, HOOK_FILENAME);
73337
+ const scriptPath = join55(hooksDir, HOOK_FILENAME);
73186
73338
  const desired = updatePromptHookScript();
73187
73339
  let installed = false;
73188
73340
  const existing = existsSync62(scriptPath) ? readFileSync53(scriptPath, "utf-8") : "";
@@ -73195,7 +73347,7 @@ function installUpdatePromptHook(agentDir) {
73195
73347
  chmodSync10(scriptPath, 493);
73196
73348
  } catch {}
73197
73349
  }
73198
- const settingsPath = join54(agentDir, ".claude", "settings.json");
73350
+ const settingsPath = join55(agentDir, ".claude", "settings.json");
73199
73351
  if (!existsSync62(settingsPath)) {
73200
73352
  return { scriptPath, settingsPath, installed };
73201
73353
  }
@@ -73321,7 +73473,7 @@ import {
73321
73473
  realpathSync as realpathSync6,
73322
73474
  statSync as statSync25
73323
73475
  } from "node:fs";
73324
- import { join as join56 } from "node:path";
73476
+ import { join as join57 } from "node:path";
73325
73477
  function resolveOperatorUid() {
73326
73478
  const sudoUid = process.env.SUDO_UID;
73327
73479
  if (sudoUid !== undefined) {
@@ -73337,14 +73489,14 @@ function resolveOperatorUid() {
73337
73489
  return;
73338
73490
  }
73339
73491
  function operatorOwnedPaths(home2) {
73340
- const root = join56(home2, ".switchroom");
73492
+ const root = join57(home2, ".switchroom");
73341
73493
  return [
73342
- join56(root, "vault"),
73343
- join56(root, "vault-auto-unlock"),
73344
- join56(root, "vault-audit.log"),
73345
- join56(root, "host-control-audit.log"),
73346
- join56(root, "accounts"),
73347
- join56(root, "compose")
73494
+ join57(root, "vault"),
73495
+ join57(root, "vault-auto-unlock"),
73496
+ join57(root, "vault-audit.log"),
73497
+ join57(root, "host-control-audit.log"),
73498
+ join57(root, "accounts"),
73499
+ join57(root, "compose")
73348
73500
  ];
73349
73501
  }
73350
73502
  function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
@@ -73393,7 +73545,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
73393
73545
  } catch {}
73394
73546
  if (isDir(target)) {
73395
73547
  for (const entry of readdir2(target)) {
73396
- visit(join56(target, entry));
73548
+ visit(join57(target, entry));
73397
73549
  }
73398
73550
  }
73399
73551
  };
@@ -73407,14 +73559,14 @@ var EMBEDDED_EXAMPLES = {
73407
73559
  switchroom: switchroom_default,
73408
73560
  minimal: minimal_default
73409
73561
  };
73410
- var DEFAULT_COMPOSE_PATH2 = join57(homedir30(), ".switchroom", "compose", "docker-compose.yml");
73562
+ var DEFAULT_COMPOSE_PATH2 = join58(homedir31(), ".switchroom", "compose", "docker-compose.yml");
73411
73563
  var COMPOSE_PROJECT2 = "switchroom";
73412
73564
  function resolveVaultBindMountDir(homeDir, ctx) {
73413
73565
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
73414
73566
  if (isCustomPath && ctx.customVaultPath) {
73415
73567
  return dirname19(ctx.customVaultPath);
73416
73568
  }
73417
- return join57(homeDir, ".switchroom", "vault");
73569
+ return join58(homeDir, ".switchroom", "vault");
73418
73570
  }
73419
73571
  function inspectVaultBindMountDir(vaultDir) {
73420
73572
  if (!existsSync65(vaultDir))
@@ -73443,31 +73595,31 @@ function hasVaultRefs(value) {
73443
73595
  return false;
73444
73596
  }
73445
73597
  async function ensureHostMountSources(config) {
73446
- const home2 = homedir30();
73598
+ const home2 = homedir31();
73447
73599
  const dirs = [
73448
- join57(home2, ".switchroom", "approvals"),
73449
- join57(home2, ".switchroom", "scheduler"),
73450
- join57(home2, ".switchroom", "logs"),
73451
- join57(home2, ".switchroom", "compose"),
73452
- join57(home2, ".switchroom", "broker-operator")
73600
+ join58(home2, ".switchroom", "approvals"),
73601
+ join58(home2, ".switchroom", "scheduler"),
73602
+ join58(home2, ".switchroom", "logs"),
73603
+ join58(home2, ".switchroom", "compose"),
73604
+ join58(home2, ".switchroom", "broker-operator")
73453
73605
  ];
73454
73606
  for (const name of Object.keys(config.agents)) {
73455
- dirs.push(join57(home2, ".switchroom", "agents", name));
73456
- dirs.push(join57(home2, ".switchroom", "logs", name));
73457
- dirs.push(join57(home2, ".claude", "projects", name));
73607
+ dirs.push(join58(home2, ".switchroom", "agents", name));
73608
+ dirs.push(join58(home2, ".switchroom", "logs", name));
73609
+ dirs.push(join58(home2, ".claude", "projects", name));
73458
73610
  }
73459
73611
  for (const dir of dirs) {
73460
73612
  await mkdir(dir, { recursive: true });
73461
73613
  }
73462
- const autoUnlockPath = join57(home2, ".switchroom", "vault-auto-unlock");
73614
+ const autoUnlockPath = join58(home2, ".switchroom", "vault-auto-unlock");
73463
73615
  if (!existsSync65(autoUnlockPath)) {
73464
73616
  writeFileSync32(autoUnlockPath, "", { mode: 384 });
73465
73617
  }
73466
- const auditLogPath = join57(home2, ".switchroom", "vault-audit.log");
73618
+ const auditLogPath = join58(home2, ".switchroom", "vault-audit.log");
73467
73619
  if (!existsSync65(auditLogPath)) {
73468
73620
  writeFileSync32(auditLogPath, "", { mode: 420 });
73469
73621
  }
73470
- const hostdAuditLogPath = join57(home2, ".switchroom", "host-control-audit.log");
73622
+ const hostdAuditLogPath = join58(home2, ".switchroom", "host-control-audit.log");
73471
73623
  if (!existsSync65(hostdAuditLogPath)) {
73472
73624
  writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
73473
73625
  }
@@ -73539,10 +73691,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
73539
73691
  `));
73540
73692
  }
73541
73693
  }
73542
- function writeInstallTypeCache(homeDir = homedir30()) {
73694
+ function writeInstallTypeCache(homeDir = homedir31()) {
73543
73695
  const ctx = detectInstallType();
73544
- const dir = join57(homeDir, ".switchroom");
73545
- const out = join57(dir, "install-type.json");
73696
+ const dir = join58(homeDir, ".switchroom");
73697
+ const out = join58(dir, "install-type.json");
73546
73698
  const tmp = `${out}.tmp`;
73547
73699
  mkdirSync35(dir, { recursive: true });
73548
73700
  const payload = {
@@ -73591,14 +73743,14 @@ Applying switchroom config...
73591
73743
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
73592
73744
  `));
73593
73745
  try {
73594
- installUpdatePromptHook(join57(agentsDir, name));
73746
+ installUpdatePromptHook(join58(agentsDir, name));
73595
73747
  } catch (hookErr) {
73596
73748
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
73597
73749
  `));
73598
73750
  }
73599
73751
  try {
73600
73752
  const uid = allocateAgentUid(name);
73601
- alignAgentUid(name, join57(agentsDir, name), uid, {
73753
+ alignAgentUid(name, join58(agentsDir, name), uid, {
73602
73754
  confirm: !options.nonInteractive,
73603
73755
  writeOut
73604
73756
  });
@@ -73635,7 +73787,7 @@ Applying switchroom config...
73635
73787
  for (const name of agentNames) {
73636
73788
  try {
73637
73789
  const uid = allocateAgentUid(name);
73638
- alignAgentUid(name, join57(agentsDir, name), uid, {
73790
+ alignAgentUid(name, join58(agentsDir, name), uid, {
73639
73791
  confirm: !options.nonInteractive,
73640
73792
  writeOut
73641
73793
  });
@@ -73649,7 +73801,7 @@ Applying switchroom config...
73649
73801
  }
73650
73802
  const vaultPathConfigured = config.vault?.path;
73651
73803
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
73652
- const migrationResult = migrateVaultLayout(homedir30(), {
73804
+ const migrationResult = migrateVaultLayout(homedir31(), {
73653
73805
  customVaultPath
73654
73806
  });
73655
73807
  switch (migrationResult.kind) {
@@ -73675,7 +73827,7 @@ Applying switchroom config...
73675
73827
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
73676
73828
  process.exit(4);
73677
73829
  }
73678
- const postMigrationInspect = inspectVaultLayout(homedir30());
73830
+ const postMigrationInspect = inspectVaultLayout(homedir31());
73679
73831
  const acceptable = [
73680
73832
  "no-vault",
73681
73833
  "already-migrated",
@@ -73690,7 +73842,7 @@ Applying switchroom config...
73690
73842
  `));
73691
73843
  process.exit(5);
73692
73844
  }
73693
- const vaultDir = resolveVaultBindMountDir(homedir30(), {
73845
+ const vaultDir = resolveVaultBindMountDir(homedir31(), {
73694
73846
  migrationKind: migrationResult.kind,
73695
73847
  customVaultPath
73696
73848
  });
@@ -73720,7 +73872,7 @@ Applying switchroom config...
73720
73872
  imageTag: composeImageTag,
73721
73873
  buildMode: options.buildLocal ? "local" : "pull",
73722
73874
  buildContext: options.buildContext,
73723
- homeDir: homedir30(),
73875
+ homeDir: homedir31(),
73724
73876
  switchroomConfigPath,
73725
73877
  operatorUid
73726
73878
  });
@@ -73740,7 +73892,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
73740
73892
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
73741
73893
  `));
73742
73894
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
73743
- const restored = restoreOperatorOwnership(homedir30(), operatorUid);
73895
+ const restored = restoreOperatorOwnership(homedir31(), operatorUid);
73744
73896
  if (restored.length > 0) {
73745
73897
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
73746
73898
  `));
@@ -73810,7 +73962,7 @@ function findUnwritableAgentDirs(config, opts) {
73810
73962
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
73811
73963
  const unwritable = [];
73812
73964
  for (const name of targets) {
73813
- const startSh = join57(agentsDir, name, "start.sh");
73965
+ const startSh = join58(agentsDir, name, "start.sh");
73814
73966
  if (!existsSync65(startSh))
73815
73967
  continue;
73816
73968
  try {
@@ -73990,8 +74142,8 @@ function runRedactStdin() {
73990
74142
 
73991
74143
  // src/cli/status-ask.ts
73992
74144
  import { readFileSync as readFileSync54, existsSync as existsSync66, readdirSync as readdirSync24 } from "node:fs";
73993
- import { join as join58 } from "node:path";
73994
- import { homedir as homedir31 } from "node:os";
74145
+ import { join as join59 } from "node:path";
74146
+ import { homedir as homedir32 } from "node:os";
73995
74147
 
73996
74148
  // src/status-ask/report.ts
73997
74149
  function parseJsonl(content) {
@@ -74326,7 +74478,7 @@ function resolveSources(explicitPath) {
74326
74478
  const config = loadConfig();
74327
74479
  agentsDir = resolveAgentsDir(config);
74328
74480
  } catch {
74329
- agentsDir = join58(homedir31(), ".switchroom", "agents");
74481
+ agentsDir = join59(homedir32(), ".switchroom", "agents");
74330
74482
  }
74331
74483
  if (!existsSync66(agentsDir))
74332
74484
  return [];
@@ -74338,7 +74490,7 @@ function resolveSources(explicitPath) {
74338
74490
  return [];
74339
74491
  }
74340
74492
  for (const name of entries) {
74341
- const path8 = join58(agentsDir, name, "runtime-metrics.jsonl");
74493
+ const path8 = join59(agentsDir, name, "runtime-metrics.jsonl");
74342
74494
  if (existsSync66(path8)) {
74343
74495
  sources.push({ path: path8, agent: name });
74344
74496
  }
@@ -74360,17 +74512,17 @@ function inferAgentFromPath(p) {
74360
74512
 
74361
74513
  // src/cli/agent-config.ts
74362
74514
  init_helpers();
74363
- import { join as join59 } from "node:path";
74364
- import { homedir as homedir32 } from "node:os";
74515
+ import { join as join60 } from "node:path";
74516
+ import { homedir as homedir33 } from "node:os";
74365
74517
  import {
74366
74518
  existsSync as existsSync67,
74367
74519
  mkdirSync as mkdirSync36,
74368
74520
  appendFileSync as appendFileSync3,
74369
74521
  readFileSync as readFileSync55
74370
74522
  } from "node:fs";
74371
- var AUDIT_ROOT = join59(homedir32(), ".switchroom", "audit");
74523
+ var AUDIT_ROOT = join60(homedir33(), ".switchroom", "audit");
74372
74524
  function auditPathFor(agent) {
74373
- return join59(AUDIT_ROOT, agent, "agent-config.jsonl");
74525
+ return join60(AUDIT_ROOT, agent, "agent-config.jsonl");
74374
74526
  }
74375
74527
  function appendAudit(agent, cmd, args, exit, opts = {}) {
74376
74528
  const row = {
@@ -74628,21 +74780,21 @@ import {
74628
74780
  unlinkSync as unlinkSync13,
74629
74781
  writeSync as writeSync7
74630
74782
  } from "node:fs";
74631
- import { join as join60, resolve as resolve42 } from "node:path";
74783
+ import { join as join61, resolve as resolve42 } from "node:path";
74632
74784
  var STAGING_SUBDIR = ".staging";
74633
74785
  function overlayPathsFor(agent, opts = {}) {
74634
74786
  const base = opts.root ? resolve42(opts.root, agent) : resolve42(resolveDualPath(`~/.switchroom/agents/${agent}`));
74635
- const scheduleDir = join60(base, "schedule.d");
74636
- const scheduleStagingDir = join60(scheduleDir, STAGING_SUBDIR);
74637
- const skillsDir = join60(base, "skills.d");
74638
- const skillsStagingDir = join60(skillsDir, STAGING_SUBDIR);
74787
+ const scheduleDir = join61(base, "schedule.d");
74788
+ const scheduleStagingDir = join61(scheduleDir, STAGING_SUBDIR);
74789
+ const skillsDir = join61(base, "skills.d");
74790
+ const skillsStagingDir = join61(skillsDir, STAGING_SUBDIR);
74639
74791
  return {
74640
74792
  agentRoot: base,
74641
74793
  scheduleDir,
74642
74794
  scheduleStagingDir,
74643
74795
  skillsDir,
74644
74796
  skillsStagingDir,
74645
- lockPath: join60(base, ".lock"),
74797
+ lockPath: join61(base, ".lock"),
74646
74798
  stagingDir: scheduleStagingDir
74647
74799
  };
74648
74800
  }
@@ -74696,8 +74848,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
74696
74848
  const paths = overlayPathsFor(agent, opts);
74697
74849
  return withAgentLock(paths, () => {
74698
74850
  ensureDirs(paths);
74699
- const stagingPath = join60(paths.scheduleStagingDir, `${slug}.yaml`);
74700
- const finalPath = join60(paths.scheduleDir, `${slug}.yaml`);
74851
+ const stagingPath = join61(paths.scheduleStagingDir, `${slug}.yaml`);
74852
+ const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
74701
74853
  const fd = openSync12(stagingPath, "w", 384);
74702
74854
  try {
74703
74855
  writeSync7(fd, yamlText);
@@ -74713,8 +74865,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
74713
74865
  const paths = overlayPathsFor(agent, opts);
74714
74866
  return withAgentLock(paths, () => {
74715
74867
  ensureSkillsDirs(paths);
74716
- const stagingPath = join60(paths.skillsStagingDir, `${slug}.yaml`);
74717
- const finalPath = join60(paths.skillsDir, `${slug}.yaml`);
74868
+ const stagingPath = join61(paths.skillsStagingDir, `${slug}.yaml`);
74869
+ const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
74718
74870
  const fd = openSync12(stagingPath, "w", 384);
74719
74871
  try {
74720
74872
  writeSync7(fd, yamlText);
@@ -74729,7 +74881,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
74729
74881
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
74730
74882
  const paths = overlayPathsFor(agent, opts);
74731
74883
  return withAgentLock(paths, () => {
74732
- const finalPath = join60(paths.skillsDir, `${slug}.yaml`);
74884
+ const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
74733
74885
  if (!existsSync68(finalPath))
74734
74886
  return false;
74735
74887
  unlinkSync13(finalPath);
@@ -74744,7 +74896,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
74744
74896
  for (const name of readdirSync25(paths.skillsDir)) {
74745
74897
  if (!/\.ya?ml$/i.test(name))
74746
74898
  continue;
74747
- const full = join60(paths.skillsDir, name);
74899
+ const full = join61(paths.skillsDir, name);
74748
74900
  try {
74749
74901
  const raw = readFileSync56(full, "utf-8");
74750
74902
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -74756,7 +74908,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
74756
74908
  function deleteOverlayEntry(agent, slug, opts = {}) {
74757
74909
  const paths = overlayPathsFor(agent, opts);
74758
74910
  return withAgentLock(paths, () => {
74759
- const finalPath = join60(paths.scheduleDir, `${slug}.yaml`);
74911
+ const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
74760
74912
  if (!existsSync68(finalPath))
74761
74913
  return false;
74762
74914
  unlinkSync13(finalPath);
@@ -74771,7 +74923,7 @@ function listOverlayEntries(agent, opts = {}) {
74771
74923
  for (const name of readdirSync25(paths.scheduleDir)) {
74772
74924
  if (!/\.ya?ml$/i.test(name))
74773
74925
  continue;
74774
- const full = join60(paths.scheduleDir, name);
74926
+ const full = join61(paths.scheduleDir, name);
74775
74927
  try {
74776
74928
  const raw = readFileSync56(full, "utf-8");
74777
74929
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -74927,12 +75079,12 @@ import {
74927
75079
  writeFileSync as writeFileSync33,
74928
75080
  writeSync as writeSync8
74929
75081
  } from "node:fs";
74930
- import { join as join61 } from "node:path";
75082
+ import { join as join62 } from "node:path";
74931
75083
  import { randomBytes as randomBytes12 } from "node:crypto";
74932
75084
  var STAGE_ID_PREFIX = "cap_";
74933
75085
  function pendingDir(agent, opts = {}) {
74934
75086
  const paths = overlayPathsFor(agent, opts);
74935
- return join61(paths.scheduleDir, ".pending");
75087
+ return join62(paths.scheduleDir, ".pending");
74936
75088
  }
74937
75089
  function ensurePendingDir(agent, opts = {}) {
74938
75090
  const dir = pendingDir(agent, opts);
@@ -74945,8 +75097,8 @@ function newStageId() {
74945
75097
  function stagePendingScheduleEntry(opts) {
74946
75098
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
74947
75099
  const stageId = opts.stageId ?? newStageId();
74948
- const yamlPath = join61(dir, `${stageId}.yaml`);
74949
- const metaPath = join61(dir, `${stageId}.meta.json`);
75100
+ const yamlPath = join62(dir, `${stageId}.yaml`);
75101
+ const metaPath = join62(dir, `${stageId}.meta.json`);
74950
75102
  const meta = {
74951
75103
  v: 1,
74952
75104
  stage_id: stageId,
@@ -74980,8 +75132,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
74980
75132
  if (!name.endsWith(".meta.json"))
74981
75133
  continue;
74982
75134
  const stageId = name.slice(0, -".meta.json".length);
74983
- const metaPath = join61(dir, name);
74984
- const yamlPath = join61(dir, `${stageId}.yaml`);
75135
+ const metaPath = join62(dir, name);
75136
+ const yamlPath = join62(dir, `${stageId}.yaml`);
74985
75137
  if (!existsSync69(yamlPath))
74986
75138
  continue;
74987
75139
  try {
@@ -75000,7 +75152,7 @@ function commitPendingScheduleEntry(opts) {
75000
75152
  return { committed: false, reason: "not_found" };
75001
75153
  const slug = match.meta.entry.name ?? match.stageId;
75002
75154
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
75003
- const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
75155
+ const finalPath = join62(paths.scheduleDir, `${slug}.yaml`);
75004
75156
  if (existsSync69(finalPath)) {
75005
75157
  return { committed: false, reason: "slug_collision" };
75006
75158
  }
@@ -75453,7 +75605,7 @@ var import_yaml15 = __toESM(require_dist(), 1);
75453
75605
  import { existsSync as existsSync71 } from "node:fs";
75454
75606
  init_reconcile_default_skills();
75455
75607
  var import_yaml16 = __toESM(require_dist(), 1);
75456
- import { join as join62 } from "node:path";
75608
+ import { join as join63 } from "node:path";
75457
75609
  var MAX_SKILLS_PER_AGENT = 20;
75458
75610
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
75459
75611
  function exitCodeFor2(code) {
@@ -75528,7 +75680,7 @@ function skillInstall(opts) {
75528
75680
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
75529
75681
  }
75530
75682
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
75531
- const skillPath = join62(poolDir, skillName);
75683
+ const skillPath = join63(poolDir, skillName);
75532
75684
  if (!existsSync71(skillPath)) {
75533
75685
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
75534
75686
  }
@@ -75721,14 +75873,14 @@ import {
75721
75873
  unlinkSync as unlinkSync15
75722
75874
  } from "node:fs";
75723
75875
  import { createHash as createHash12 } from "node:crypto";
75724
- import { join as join63 } from "node:path";
75876
+ import { join as join64 } from "node:path";
75725
75877
  function planCronUnitRenames(agentsDir, agents) {
75726
75878
  const plans = [];
75727
75879
  for (const [agentName, agentConfig] of Object.entries(agents)) {
75728
75880
  const schedule = agentConfig.schedule ?? [];
75729
75881
  if (schedule.length === 0)
75730
75882
  continue;
75731
- const telegramDir = join63(agentsDir, agentName, "telegram");
75883
+ const telegramDir = join64(agentsDir, agentName, "telegram");
75732
75884
  if (!existsSync73(telegramDir))
75733
75885
  continue;
75734
75886
  let entries;
@@ -75750,8 +75902,8 @@ function planCronUnitRenames(agentsDir, agents) {
75750
75902
  continue;
75751
75903
  plans.push({
75752
75904
  agent: agentName,
75753
- from: join63(telegramDir, file),
75754
- to: join63(telegramDir, canonical),
75905
+ from: join64(telegramDir, file),
75906
+ to: join64(telegramDir, canonical),
75755
75907
  scheduleIdx: idx,
75756
75908
  entry
75757
75909
  });
@@ -75889,8 +76041,8 @@ function registerMigrateCommand(program3) {
75889
76041
  init_source();
75890
76042
  init_helpers();
75891
76043
  import { existsSync as existsSync74, mkdirSync as mkdirSync39, readdirSync as readdirSync28, readFileSync as readFileSync61, writeFileSync as writeFileSync34, statSync as statSync28, copyFileSync as copyFileSync12 } from "node:fs";
75892
- import { homedir as homedir33 } from "node:os";
75893
- import { join as join64 } from "node:path";
76044
+ import { homedir as homedir34 } from "node:os";
76045
+ import { join as join65 } from "node:path";
75894
76046
  import { spawnSync as spawnSync11 } from "node:child_process";
75895
76047
  init_audit_reader();
75896
76048
  var DEFAULT_IMAGE_TAG = "latest";
@@ -75981,10 +76133,10 @@ networks:
75981
76133
  `;
75982
76134
  }
75983
76135
  function hostdDir() {
75984
- return join64(homedir33(), ".switchroom", "hostd");
76136
+ return join65(homedir34(), ".switchroom", "hostd");
75985
76137
  }
75986
76138
  function hostdComposePath() {
75987
- return join64(hostdDir(), "docker-compose.yml");
76139
+ return join65(hostdDir(), "docker-compose.yml");
75988
76140
  }
75989
76141
  function backupExistingCompose() {
75990
76142
  const p = hostdComposePath();
@@ -76023,7 +76175,7 @@ async function doInstall(opts, program3) {
76023
76175
  const composePath = hostdComposePath();
76024
76176
  mkdirSync39(dir, { recursive: true });
76025
76177
  const yaml = renderHostdComposeFile({
76026
- hostHome: homedir33(),
76178
+ hostHome: homedir34(),
76027
76179
  imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
76028
76180
  operatorUid: resolveOperatorUid()
76029
76181
  });
@@ -76092,7 +76244,7 @@ function doStatus() {
76092
76244
  for (const name of readdirSync28(dir)) {
76093
76245
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
76094
76246
  continue;
76095
- const sockPath = join64(dir, name, "sock");
76247
+ const sockPath = join65(dir, name, "sock");
76096
76248
  if (existsSync74(sockPath)) {
76097
76249
  const st = statSync28(sockPath);
76098
76250
  if ((st.mode & 61440) === 49152) {