switchroom 0.12.14 → 0.12.15

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.
@@ -21409,7 +21409,7 @@ function stripWireFields(entry) {
21409
21409
  files: entry.files
21410
21410
  };
21411
21411
  }
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;
21412
+ var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
21413
21413
  var init_protocol = __esm(() => {
21414
21414
  init_zod();
21415
21415
  MAX_FRAME_BYTES = 64 * 1024;
@@ -21537,6 +21537,15 @@ var init_protocol = __esm(() => {
21537
21537
  granted_by_user_id: exports_external.number().int(),
21538
21538
  ttl_ms: exports_external.number().int().positive().nullable().optional()
21539
21539
  });
21540
+ ApprovalConsumeRecordRequestSchema = exports_external.object({
21541
+ v: exports_external.literal(1),
21542
+ op: exports_external.literal("approval_consume_record"),
21543
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
21544
+ decision: ApprovalDecisionModeSchema,
21545
+ approver_set: exports_external.array(exports_external.string()),
21546
+ granted_by_user_id: exports_external.number().int(),
21547
+ ttl_ms: exports_external.number().int().positive().nullable().optional()
21548
+ });
21540
21549
  RequestSchema = exports_external.discriminatedUnion("op", [
21541
21550
  GetRequestSchema,
21542
21551
  PutRequestSchema,
@@ -21552,7 +21561,8 @@ var init_protocol = __esm(() => {
21552
21561
  ApprovalConsumeRequestSchema,
21553
21562
  ApprovalRevokeRequestSchema,
21554
21563
  ApprovalListRequestSchema,
21555
- ApprovalRecordRequestSchema
21564
+ ApprovalRecordRequestSchema,
21565
+ ApprovalConsumeRecordRequestSchema
21556
21566
  ]);
21557
21567
  VaultEntrySchema = exports_external.union([
21558
21568
  exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
@@ -21674,6 +21684,15 @@ var init_protocol = __esm(() => {
21674
21684
  ok: exports_external.literal(true),
21675
21685
  decision_id: exports_external.string()
21676
21686
  });
21687
+ OkApprovalConsumeRecordResponseSchema = exports_external.object({
21688
+ ok: exports_external.literal(true),
21689
+ consumed: exports_external.boolean(),
21690
+ decision_id: exports_external.string().optional(),
21691
+ agent_unit: exports_external.string().optional(),
21692
+ scope: exports_external.string().optional(),
21693
+ action: exports_external.string().optional(),
21694
+ why: exports_external.string().nullable().optional()
21695
+ });
21677
21696
  ErrorResponseSchema = exports_external.object({
21678
21697
  ok: exports_external.literal(false),
21679
21698
  code: ErrorCode,
@@ -21695,6 +21714,7 @@ var init_protocol = __esm(() => {
21695
21714
  OkApprovalRevokeResponseSchema,
21696
21715
  OkApprovalListResponseSchema,
21697
21716
  OkApprovalRecordResponseSchema,
21717
+ OkApprovalConsumeRecordResponseSchema,
21698
21718
  ErrorResponseSchema
21699
21719
  ]);
21700
21720
  });
@@ -28282,12 +28302,240 @@ function runHostdChecks(config, deps = {}) {
28282
28302
  var HOSTD_CONTAINER = "switchroom-hostd", HOSTD_DRIFT_HOURS = 2;
28283
28303
  var init_doctor_hostd = () => {};
28284
28304
 
28305
+ // src/cli/doctor-secret-access.ts
28306
+ import {
28307
+ accessSync,
28308
+ constants as fsConstants4,
28309
+ existsSync as existsSync46,
28310
+ realpathSync as realpathSync4,
28311
+ statSync as statSync20
28312
+ } from "node:fs";
28313
+ import { userInfo, homedir as homedir20 } from "node:os";
28314
+ import { join as join38 } from "node:path";
28315
+ function resolveVaultPath2(config) {
28316
+ return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
28317
+ }
28318
+ function defaultStatVault(path4) {
28319
+ if (!existsSync46(path4)) {
28320
+ return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
28321
+ }
28322
+ let real = path4;
28323
+ try {
28324
+ real = realpathSync4(path4);
28325
+ } catch {}
28326
+ let uid = -1;
28327
+ let mode = 0;
28328
+ try {
28329
+ const s = statSync20(real);
28330
+ uid = s.uid;
28331
+ mode = s.mode & 511;
28332
+ } catch {
28333
+ return { exists: true, readable: false, uid, mode, realPath: real };
28334
+ }
28335
+ let readable = false;
28336
+ try {
28337
+ accessSync(real, fsConstants4.R_OK);
28338
+ readable = true;
28339
+ } catch {
28340
+ readable = false;
28341
+ }
28342
+ return { exists: true, readable, uid, mode, realPath: real };
28343
+ }
28344
+ function collectVaultRefs2(value, out) {
28345
+ if (typeof value === "string") {
28346
+ if (value.startsWith("vault:")) {
28347
+ const key = value.slice("vault:".length).split("#")[0].trim();
28348
+ if (key)
28349
+ out.add(key);
28350
+ }
28351
+ return;
28352
+ }
28353
+ if (Array.isArray(value)) {
28354
+ for (const v of value)
28355
+ collectVaultRefs2(v, out);
28356
+ return;
28357
+ }
28358
+ if (value && typeof value === "object") {
28359
+ for (const v of Object.values(value)) {
28360
+ collectVaultRefs2(v, out);
28361
+ }
28362
+ }
28363
+ }
28364
+ function collectNeeds(resolved) {
28365
+ const cronKeys = new Set;
28366
+ for (const entry of resolved.schedule ?? []) {
28367
+ for (const s of entry.secrets ?? [])
28368
+ cronKeys.add(s);
28369
+ }
28370
+ const refKeys = new Set;
28371
+ collectVaultRefs2(resolved, refKeys);
28372
+ return { needed: new Set([...cronKeys, ...refKeys]), cronKeys };
28373
+ }
28374
+ function keyGap(key, isCron, exists, acl, scope) {
28375
+ const isGoogleSlot = key.startsWith("google:");
28376
+ if (!isGoogleSlot && !exists)
28377
+ return `'${key}' missing from the vault`;
28378
+ if (isCron && !acl.allow) {
28379
+ return `'${key}' (cron) \u2014 no static ACL grants read (${acl.reason})`;
28380
+ }
28381
+ if (!scope.allow) {
28382
+ return `'${key}' \u2014 per-key scope denies read (${scope.reason})`;
28383
+ }
28384
+ return null;
28385
+ }
28386
+ async function defaultPreflight(socketPath, agent, keys) {
28387
+ const r = await rpcRaw({ v: 1, op: "preflight_access", agent, keys }, { socket: socketPath, timeoutMs: 5000 });
28388
+ if (r.kind === "unreachable")
28389
+ return { kind: "unreachable", msg: r.msg };
28390
+ const resp = r.resp;
28391
+ if (resp.ok === true && resp.op === "preflight_access") {
28392
+ return {
28393
+ kind: "ok",
28394
+ results: resp.results
28395
+ };
28396
+ }
28397
+ if (resp.ok === false && resp.code === "LOCKED")
28398
+ return { kind: "locked" };
28399
+ return {
28400
+ kind: "unreachable",
28401
+ msg: resp.ok === false ? `broker error ${resp.code}: ${resp.msg}` : "unexpected broker response"
28402
+ };
28403
+ }
28404
+ async function runSecretAccessChecks(config, deps = {}) {
28405
+ const results = [];
28406
+ const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
28407
+ const statVault = deps.statVault ?? defaultStatVault;
28408
+ const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
28409
+ let selfUser = deps.selfUser;
28410
+ if (selfUser === undefined) {
28411
+ try {
28412
+ selfUser = userInfo().username;
28413
+ } catch {
28414
+ selfUser = "<you>";
28415
+ }
28416
+ }
28417
+ const vf = statVault(vaultPath);
28418
+ if (!vf.exists) {
28419
+ results.push({
28420
+ name: "vault: operator readable",
28421
+ status: "ok",
28422
+ detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
28423
+ });
28424
+ } else if (!vf.readable) {
28425
+ results.push({
28426
+ name: "vault: operator readable",
28427
+ status: "fail",
28428
+ detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
28429
+ fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
28430
+ });
28431
+ } else {
28432
+ results.push({
28433
+ name: "vault: operator readable",
28434
+ status: "ok",
28435
+ detail: `operator can read ${vf.realPath}`
28436
+ });
28437
+ }
28438
+ const pushAgentResult = (name, total, gaps) => {
28439
+ results.push(gaps.length === 0 ? {
28440
+ name: `secret access: ${name}`,
28441
+ status: "ok",
28442
+ detail: `${total} secret(s): all present + ACL ok`
28443
+ } : {
28444
+ name: `secret access: ${name}`,
28445
+ status: "fail",
28446
+ detail: `${gaps.length}/${total} unreachable \u2014 ${gaps.join("; ")}`,
28447
+ fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
28448
+ });
28449
+ };
28450
+ const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
28451
+ if (!passphrase) {
28452
+ const sock = deps.brokerOperatorSocket ?? join38(homedir20(), ".switchroom", "broker-operator", "sock");
28453
+ const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
28454
+ for (const name of Object.keys(config.agents ?? {})) {
28455
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28456
+ const { needed, cronKeys } = collectNeeds(resolved);
28457
+ if (needed.size === 0) {
28458
+ results.push({
28459
+ name: `secret access: ${name}`,
28460
+ status: "ok",
28461
+ detail: "no declared vault secrets"
28462
+ });
28463
+ continue;
28464
+ }
28465
+ const out = await preflight(name, [...needed].sort());
28466
+ if (out.kind === "unreachable" || out.kind === "locked") {
28467
+ results.push({
28468
+ name: "agent secret access",
28469
+ status: "skip",
28470
+ 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`,
28471
+ fix: "Ensure the broker operator socket (~/.switchroom/broker-operator/sock) is bound, or export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
28472
+ });
28473
+ return results;
28474
+ }
28475
+ const byKey = new Map(out.results.map((r) => [r.key, r]));
28476
+ const gaps = [];
28477
+ for (const key of [...needed].sort()) {
28478
+ const r = byKey.get(key);
28479
+ if (r === undefined) {
28480
+ gaps.push(`'${key}' \u2014 broker returned no result`);
28481
+ continue;
28482
+ }
28483
+ 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 });
28484
+ if (g)
28485
+ gaps.push(g);
28486
+ }
28487
+ pushAgentResult(name, needed.size, gaps);
28488
+ }
28489
+ return results;
28490
+ }
28491
+ let entries;
28492
+ try {
28493
+ entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
28494
+ } catch (err) {
28495
+ results.push({
28496
+ name: "agent secret access",
28497
+ status: vf.readable ? "fail" : "warn",
28498
+ detail: `cannot open the vault: ${err.message}`,
28499
+ fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
28500
+ });
28501
+ return results;
28502
+ }
28503
+ for (const name of Object.keys(config.agents ?? {})) {
28504
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28505
+ const { needed, cronKeys } = collectNeeds(resolved);
28506
+ if (needed.size === 0) {
28507
+ results.push({
28508
+ name: `secret access: ${name}`,
28509
+ status: "ok",
28510
+ detail: "no declared vault secrets"
28511
+ });
28512
+ continue;
28513
+ }
28514
+ const gaps = [];
28515
+ for (const key of [...needed].sort()) {
28516
+ const g = keyGap(key, cronKeys.has(key), key in entries, checkAclByAgent(config, name, key), checkEntryScope(entries[key]?.scope, name));
28517
+ if (g)
28518
+ gaps.push(g);
28519
+ }
28520
+ pushAgentResult(name, needed.size, gaps);
28521
+ }
28522
+ return results;
28523
+ }
28524
+ var init_doctor_secret_access = __esm(() => {
28525
+ init_paths();
28526
+ init_merge();
28527
+ init_vault();
28528
+ init_acl();
28529
+ init_client();
28530
+ });
28531
+
28285
28532
  // src/cli/doctor-drive.ts
28286
28533
  import {
28287
28534
  existsSync as realExistsSync,
28288
28535
  readFileSync as realReadFileSync
28289
28536
  } from "node:fs";
28290
- import { join as join38, resolve as resolve28 } from "node:path";
28537
+ import { join as join39, resolve as resolve28 } from "node:path";
28538
+ import { homedir as homedir21 } from "node:os";
28291
28539
  function resolveDeps(config, deps) {
28292
28540
  let agentsDir = deps.agentsDir;
28293
28541
  if (agentsDir === undefined) {
@@ -28410,8 +28658,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
28410
28658
  });
28411
28659
  continue;
28412
28660
  }
28413
- const mcpPath = join38(agentDir, ".mcp.json");
28414
- const claudeJsonPath = join38(agentDir, ".claude", ".claude.json");
28661
+ const mcpPath = join39(agentDir, ".mcp.json");
28662
+ const claudeJsonPath = join39(agentDir, ".claude", ".claude.json");
28415
28663
  const mcpRead = readJson(d, mcpPath);
28416
28664
  const trustRead = readJson(d, claudeJsonPath);
28417
28665
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -28495,16 +28743,78 @@ function runDriveChecks(config, deps = {}) {
28495
28743
  const results = [];
28496
28744
  const matrix = checkConfigMatrix(config);
28497
28745
  results.push(...matrix);
28498
- const driveAgents = Object.keys(config.agents ?? {}).filter((name) => {
28746
+ const driveAgents = computeDriveAgents(config);
28747
+ results.push(...checkOAuthClient(config, driveAgents.length > 0));
28748
+ results.push(...checkScaffoldWiring(config, driveAgents, d));
28749
+ return results;
28750
+ }
28751
+ function computeDriveAgents(config) {
28752
+ const accounts = config.google_accounts;
28753
+ return Object.keys(config.agents ?? {}).filter((name) => {
28499
28754
  const acct = agentAccount(config, name);
28500
28755
  return !!acct && !!accounts?.[acct] && (accounts[acct].enabled_for ?? []).includes(name);
28501
28756
  });
28502
- results.push(...checkOAuthClient(config, driveAgents.length > 0));
28503
- results.push(...checkScaffoldWiring(config, driveAgents, d));
28757
+ }
28758
+ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
28759
+ const driveAgents = computeDriveAgents(config);
28760
+ if (driveAgents.length === 0)
28761
+ return [];
28762
+ const gw = config.google_workspace;
28763
+ const idKey = vaultRefKey(gw?.google_client_id);
28764
+ const secretKey = vaultRefKey(gw?.google_client_secret);
28765
+ const vaultKeys = [idKey, secretKey].filter((k) => k !== null);
28766
+ if (vaultKeys.length === 0) {
28767
+ return [
28768
+ {
28769
+ name: "drive: OAuth client broker-reachable",
28770
+ status: "ok",
28771
+ detail: "client credential is a literal (not a vault: ref) \u2014 the launcher reads it from config; no broker grant needed"
28772
+ }
28773
+ ];
28774
+ }
28775
+ const sock = deps.brokerOperatorSocket ?? join39(homedir21(), ".switchroom", "broker-operator", "sock");
28776
+ const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
28777
+ const results = [];
28778
+ for (const agent of driveAgents) {
28779
+ const outcome = await preflight(agent, vaultKeys);
28780
+ if (outcome.kind === "locked") {
28781
+ results.push({
28782
+ name: `drive: ${agent} client-cred broker-reachable`,
28783
+ status: "skip",
28784
+ detail: "vault-broker is locked \u2014 cannot verify; the fleet's launchers/crons are dead while locked anyway (not a Drive-specific fault)"
28785
+ });
28786
+ continue;
28787
+ }
28788
+ if (outcome.kind === "unreachable") {
28789
+ results.push({
28790
+ name: `drive: ${agent} client-cred broker-reachable`,
28791
+ status: "skip",
28792
+ detail: `broker operator socket unreachable (${outcome.msg}) \u2014 cannot verify; not a false fail`
28793
+ });
28794
+ continue;
28795
+ }
28796
+ const denied = outcome.results.filter((r) => !r.acl_ok);
28797
+ if (denied.length === 0) {
28798
+ results.push({
28799
+ name: `drive: ${agent} client-cred broker-reachable`,
28800
+ status: "ok",
28801
+ detail: `vault-broker will serve the OAuth client credential (${vaultKeys.join(", ")}) to '${agent}'`
28802
+ });
28803
+ continue;
28804
+ }
28805
+ const d0 = denied[0];
28806
+ results.push({
28807
+ name: `drive: ${agent} client-cred broker-reachable`,
28808
+ status: "fail",
28809
+ detail: `vault-broker DENIES '${agent}' the OAuth client key '${d0.key}' (${d0.acl_reason ?? "no ACL grant"}) \u2014 the gdrive MCP launcher exits before spawning and Drive silently never works for this agent`,
28810
+ fix: `confirm '${agent}' is in google_accounts[<account>].enabled_for[] and has agents.${agent}.google_workspace.account set (v0.12.14+ broker grants the client credential off that gate automatically); then ensure the broker is running the v0.12.14+ image`
28811
+ });
28812
+ }
28504
28813
  return results;
28505
28814
  }
28506
28815
  var init_doctor_drive = __esm(() => {
28507
28816
  init_loader();
28817
+ init_doctor_secret_access();
28508
28818
  });
28509
28819
 
28510
28820
  // src/cli/doctor-credentials-migration.ts
@@ -28513,11 +28823,11 @@ import {
28513
28823
  readdirSync as realReaddirSync,
28514
28824
  statSync as realStatSync
28515
28825
  } from "node:fs";
28516
- import { homedir as homedir20 } from "node:os";
28517
- import { join as join39 } from "node:path";
28826
+ import { homedir as homedir22 } from "node:os";
28827
+ import { join as join40 } from "node:path";
28518
28828
  function runCredentialsMigrationChecks(config, deps = {}) {
28519
- const credDir = deps.credentialsDir ?? join39(homedir20(), ".switchroom", "credentials");
28520
- const existsSync46 = deps.existsSync ?? ((p) => realExistsSync2(p));
28829
+ const credDir = deps.credentialsDir ?? join40(homedir22(), ".switchroom", "credentials");
28830
+ const existsSync47 = deps.existsSync ?? ((p) => realExistsSync2(p));
28521
28831
  const readdirSync17 = deps.readdirSync ?? ((p) => realReaddirSync(p));
28522
28832
  const isDirectory = deps.isDirectory ?? ((p) => {
28523
28833
  try {
@@ -28526,7 +28836,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28526
28836
  return false;
28527
28837
  }
28528
28838
  });
28529
- if (!existsSync46(credDir))
28839
+ if (!existsSync47(credDir))
28530
28840
  return [];
28531
28841
  const agentNames = new Set(Object.keys(config.agents ?? {}));
28532
28842
  let entries;
@@ -28544,7 +28854,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28544
28854
  const flat = [];
28545
28855
  const perAgentDirs = [];
28546
28856
  for (const e of entries) {
28547
- const full = join39(credDir, e);
28857
+ const full = join40(credDir, e);
28548
28858
  if (isDirectory(full) && agentNames.has(e)) {
28549
28859
  perAgentDirs.push(e);
28550
28860
  } else {
@@ -28571,233 +28881,6 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28571
28881
  }
28572
28882
  var init_doctor_credentials_migration = () => {};
28573
28883
 
28574
- // src/cli/doctor-secret-access.ts
28575
- import {
28576
- accessSync,
28577
- constants as fsConstants4,
28578
- existsSync as existsSync46,
28579
- realpathSync as realpathSync4,
28580
- statSync as statSync20
28581
- } from "node:fs";
28582
- import { userInfo, homedir as homedir21 } from "node:os";
28583
- import { join as join40 } from "node:path";
28584
- function resolveVaultPath2(config) {
28585
- return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
28586
- }
28587
- function defaultStatVault(path4) {
28588
- if (!existsSync46(path4)) {
28589
- return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
28590
- }
28591
- let real = path4;
28592
- try {
28593
- real = realpathSync4(path4);
28594
- } catch {}
28595
- let uid = -1;
28596
- let mode = 0;
28597
- try {
28598
- const s = statSync20(real);
28599
- uid = s.uid;
28600
- mode = s.mode & 511;
28601
- } catch {
28602
- return { exists: true, readable: false, uid, mode, realPath: real };
28603
- }
28604
- let readable = false;
28605
- try {
28606
- accessSync(real, fsConstants4.R_OK);
28607
- readable = true;
28608
- } catch {
28609
- readable = false;
28610
- }
28611
- return { exists: true, readable, uid, mode, realPath: real };
28612
- }
28613
- function collectVaultRefs2(value, out) {
28614
- if (typeof value === "string") {
28615
- if (value.startsWith("vault:")) {
28616
- const key = value.slice("vault:".length).split("#")[0].trim();
28617
- if (key)
28618
- out.add(key);
28619
- }
28620
- return;
28621
- }
28622
- if (Array.isArray(value)) {
28623
- for (const v of value)
28624
- collectVaultRefs2(v, out);
28625
- return;
28626
- }
28627
- if (value && typeof value === "object") {
28628
- for (const v of Object.values(value)) {
28629
- collectVaultRefs2(v, out);
28630
- }
28631
- }
28632
- }
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 = {}) {
28674
- const results = [];
28675
- const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
28676
- const statVault = deps.statVault ?? defaultStatVault;
28677
- const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
28678
- let selfUser = deps.selfUser;
28679
- if (selfUser === undefined) {
28680
- try {
28681
- selfUser = userInfo().username;
28682
- } catch {
28683
- selfUser = "<you>";
28684
- }
28685
- }
28686
- const vf = statVault(vaultPath);
28687
- if (!vf.exists) {
28688
- results.push({
28689
- name: "vault: operator readable",
28690
- status: "ok",
28691
- detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
28692
- });
28693
- } else if (!vf.readable) {
28694
- results.push({
28695
- name: "vault: operator readable",
28696
- status: "fail",
28697
- detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
28698
- fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
28699
- });
28700
- } else {
28701
- results.push({
28702
- name: "vault: operator readable",
28703
- status: "ok",
28704
- detail: `operator can read ${vf.realPath}`
28705
- });
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
- };
28719
- const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
28720
- if (!passphrase) {
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
- }
28758
- return results;
28759
- }
28760
- let entries;
28761
- try {
28762
- entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
28763
- } catch (err) {
28764
- results.push({
28765
- name: "agent secret access",
28766
- status: vf.readable ? "fail" : "warn",
28767
- detail: `cannot open the vault: ${err.message}`,
28768
- fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
28769
- });
28770
- return results;
28771
- }
28772
- for (const name of Object.keys(config.agents ?? {})) {
28773
- const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28774
- const { needed, cronKeys } = collectNeeds(resolved);
28775
- if (needed.size === 0) {
28776
- results.push({
28777
- name: `secret access: ${name}`,
28778
- status: "ok",
28779
- detail: "no declared vault secrets"
28780
- });
28781
- continue;
28782
- }
28783
- const gaps = [];
28784
- for (const key of [...needed].sort()) {
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);
28788
- }
28789
- pushAgentResult(name, needed.size, gaps);
28790
- }
28791
- return results;
28792
- }
28793
- var init_doctor_secret_access = __esm(() => {
28794
- init_paths();
28795
- init_merge();
28796
- init_vault();
28797
- init_acl();
28798
- init_client();
28799
- });
28800
-
28801
28884
  // src/cli/doctor-inlined-secrets.ts
28802
28885
  import { readFileSync as fsReadFileSync } from "node:fs";
28803
28886
  function isSecretShapedKey(key) {
@@ -28894,7 +28977,7 @@ var init_doctor_inlined_secrets = __esm(() => {
28894
28977
 
28895
28978
  // src/cli/doctor-audit-integrity.ts
28896
28979
  import { readFileSync as fsReadFileSync2 } from "node:fs";
28897
- import { homedir as homedir22 } from "node:os";
28980
+ import { homedir as homedir23 } from "node:os";
28898
28981
  import { join as join41 } from "node:path";
28899
28982
  function rootWrittenLogs(home2) {
28900
28983
  return [
@@ -28906,7 +28989,7 @@ function rootWrittenLogs(home2) {
28906
28989
  ];
28907
28990
  }
28908
28991
  function runAuditIntegrityChecks(deps = {}) {
28909
- const home2 = deps.homeDir ?? homedir22();
28992
+ const home2 = deps.homeDir ?? homedir23();
28910
28993
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
28911
28994
  const results = [];
28912
28995
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -29183,13 +29266,13 @@ var init_client4 = __esm(() => {
29183
29266
 
29184
29267
  // src/cli/doctor-agent-smoke.ts
29185
29268
  import { existsSync as existsSync47 } from "node:fs";
29186
- import { homedir as homedir23 } from "node:os";
29269
+ import { homedir as homedir24 } from "node:os";
29187
29270
  import { join as join42 } from "node:path";
29188
29271
  import { randomUUID as randomUUID4 } from "node:crypto";
29189
29272
  async function runAgentSmokeChecks(config, deps = {}) {
29190
29273
  if (deps.fast)
29191
29274
  return [];
29192
- const home2 = deps.homeDir ?? homedir23();
29275
+ const home2 = deps.homeDir ?? homedir24();
29193
29276
  const sock = deps.operatorSockPath ?? join42(home2, ".switchroom", "hostd", "operator", "sock");
29194
29277
  if (!deps.hostdRequestImpl && !existsSync47(sock)) {
29195
29278
  return [
@@ -30809,7 +30892,13 @@ function registerDoctorCommand(program3) {
30809
30892
  title: "Agent liveness (in-agent via hostd)",
30810
30893
  results: await runAgentSmokeChecks(config, { fast: opts.fast })
30811
30894
  },
30812
- { title: "Google Drive", results: runDriveChecks(config) },
30895
+ {
30896
+ title: "Google Drive",
30897
+ results: [
30898
+ ...runDriveChecks(config),
30899
+ ...await runDriveBrokerReachabilityChecks(config)
30900
+ ]
30901
+ },
30813
30902
  { title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
30814
30903
  ];
30815
30904
  const cwd = process.cwd();
@@ -47153,8 +47242,8 @@ var {
47153
47242
  } = import__.default;
47154
47243
 
47155
47244
  // src/build-info.ts
47156
- var VERSION = "0.12.14";
47157
- var COMMIT_SHA = "db6d87d6";
47245
+ var VERSION = "0.12.15";
47246
+ var COMMIT_SHA = "dc508a92";
47158
47247
 
47159
47248
  // src/cli/agent.ts
47160
47249
  init_source();
@@ -69125,8 +69214,8 @@ init_lifecycle();
69125
69214
  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";
69126
69215
  import { spawnSync as spawnSync8 } from "node:child_process";
69127
69216
  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");
69217
+ import { homedir as homedir25 } from "node:os";
69218
+ var DEFAULT_COMPOSE_PATH = join44(homedir25(), ".switchroom", "compose", "docker-compose.yml");
69130
69219
  function runningFromSwitchroomCheckout(scriptPath) {
69131
69220
  let dir = dirname13(scriptPath);
69132
69221
  for (let i = 0;i < 12; i++) {
@@ -69263,7 +69352,7 @@ function planUpdate(opts) {
69263
69352
  return;
69264
69353
  }
69265
69354
  const source = resolve30(import.meta.dirname, "../../skills");
69266
- const dest = join44(homedir24(), ".switchroom", "skills", "_bundled");
69355
+ const dest = join44(homedir25(), ".switchroom", "skills", "_bundled");
69267
69356
  if (!existsSync49(source)) {
69268
69357
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
69269
69358
  `);
@@ -70666,7 +70755,7 @@ function relTime(deltaMs) {
70666
70755
  // src/cli/deps.ts
70667
70756
  init_source();
70668
70757
  import { existsSync as existsSync54 } from "node:fs";
70669
- import { homedir as homedir27 } from "node:os";
70758
+ import { homedir as homedir28 } from "node:os";
70670
70759
  import { join as join49, resolve as resolve34 } from "node:path";
70671
70760
 
70672
70761
  // src/deps/python.ts
@@ -70679,7 +70768,7 @@ import {
70679
70768
  writeFileSync as writeFileSync26
70680
70769
  } from "node:fs";
70681
70770
  import { dirname as dirname15, join as join47 } from "node:path";
70682
- import { homedir as homedir25 } from "node:os";
70771
+ import { homedir as homedir26 } from "node:os";
70683
70772
  import { execFileSync as execFileSync14 } from "node:child_process";
70684
70773
 
70685
70774
  class PythonEnvError extends Error {
@@ -70691,7 +70780,7 @@ class PythonEnvError extends Error {
70691
70780
  }
70692
70781
  }
70693
70782
  function defaultPythonCacheRoot() {
70694
- return join47(homedir25(), ".switchroom", "deps", "python");
70783
+ return join47(homedir26(), ".switchroom", "deps", "python");
70695
70784
  }
70696
70785
  function hashFile(path4) {
70697
70786
  return createHash9("sha256").update(readFileSync48(path4)).digest("hex");
@@ -70767,7 +70856,7 @@ import {
70767
70856
  writeFileSync as writeFileSync27
70768
70857
  } from "node:fs";
70769
70858
  import { dirname as dirname16, join as join48 } from "node:path";
70770
- import { homedir as homedir26 } from "node:os";
70859
+ import { homedir as homedir27 } from "node:os";
70771
70860
  import { execFileSync as execFileSync15 } from "node:child_process";
70772
70861
 
70773
70862
  class NodeEnvError extends Error {
@@ -70790,7 +70879,7 @@ var LOCKFILES_FOR = {
70790
70879
  npm: ["package-lock.json"]
70791
70880
  };
70792
70881
  function defaultNodeCacheRoot() {
70793
- return join48(homedir26(), ".switchroom", "deps", "node");
70882
+ return join48(homedir27(), ".switchroom", "deps", "node");
70794
70883
  }
70795
70884
  function hashDepInputs(packageJsonPath) {
70796
70885
  const sourceDir = dirname16(packageJsonPath);
@@ -70874,7 +70963,7 @@ function ensureNodeEnv(opts) {
70874
70963
 
70875
70964
  // src/cli/deps.ts
70876
70965
  function builtinSkillsRoot() {
70877
- return resolve34(homedir27(), ".switchroom/skills/_bundled");
70966
+ return resolve34(homedir28(), ".switchroom/skills/_bundled");
70878
70967
  }
70879
70968
  function registerDepsCommand(program3) {
70880
70969
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -72184,7 +72273,7 @@ init_source();
72184
72273
  import { execFileSync as execFileSync16 } from "node:child_process";
72185
72274
  import { closeSync as closeSync11, mkdirSync as mkdirSync32, openSync as openSync11, existsSync as existsSync59, unlinkSync as unlinkSync12 } from "node:fs";
72186
72275
  import { join as join53, resolve as resolve39 } from "node:path";
72187
- import { homedir as homedir29 } from "node:os";
72276
+ import { homedir as homedir30 } from "node:os";
72188
72277
  import { randomBytes as randomBytes11 } from "node:crypto";
72189
72278
 
72190
72279
  // src/worktree/registry.ts
@@ -72198,9 +72287,9 @@ import {
72198
72287
  renameSync as renameSync11
72199
72288
  } from "node:fs";
72200
72289
  import { join as join52, resolve as resolve38 } from "node:path";
72201
- import { homedir as homedir28 } from "node:os";
72290
+ import { homedir as homedir29 } from "node:os";
72202
72291
  function registryDir() {
72203
- return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir28(), ".switchroom", "worktrees"));
72292
+ return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir29(), ".switchroom", "worktrees"));
72204
72293
  }
72205
72294
  function recordPath(id) {
72206
72295
  return join52(registryDir(), `${id}.json`);
@@ -72281,7 +72370,7 @@ function acquireRepoLock(repoPath) {
72281
72370
  }
72282
72371
  var DEFAULT_CONCURRENCY = 5;
72283
72372
  function worktreesBaseDir() {
72284
- return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir29(), ".switchroom", "worktree-checkouts"));
72373
+ return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir30(), ".switchroom", "worktree-checkouts"));
72285
72374
  }
72286
72375
  function shortId() {
72287
72376
  return randomBytes11(4).toString("hex");
@@ -72303,7 +72392,7 @@ function resolveRepoPath(repo, codeRepos) {
72303
72392
  }
72304
72393
  function expandHome(p) {
72305
72394
  if (p.startsWith("~/"))
72306
- return join53(homedir29(), p.slice(2));
72395
+ return join53(homedir30(), p.slice(2));
72307
72396
  return p;
72308
72397
  }
72309
72398
  async function claimWorktree(input, codeRepos) {
@@ -73258,7 +73347,7 @@ agents:
73258
73347
  // src/cli/apply.ts
73259
73348
  init_resolver();
73260
73349
  import { dirname as dirname19, join as join58, resolve as resolve41 } from "node:path";
73261
- import { homedir as homedir31 } from "node:os";
73350
+ import { homedir as homedir32 } from "node:os";
73262
73351
  import { execFileSync as execFileSync19 } from "node:child_process";
73263
73352
  init_vault();
73264
73353
  init_loader();
@@ -73559,7 +73648,7 @@ var EMBEDDED_EXAMPLES = {
73559
73648
  switchroom: switchroom_default,
73560
73649
  minimal: minimal_default
73561
73650
  };
73562
- var DEFAULT_COMPOSE_PATH2 = join58(homedir31(), ".switchroom", "compose", "docker-compose.yml");
73651
+ var DEFAULT_COMPOSE_PATH2 = join58(homedir32(), ".switchroom", "compose", "docker-compose.yml");
73563
73652
  var COMPOSE_PROJECT2 = "switchroom";
73564
73653
  function resolveVaultBindMountDir(homeDir, ctx) {
73565
73654
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
@@ -73595,7 +73684,7 @@ function hasVaultRefs(value) {
73595
73684
  return false;
73596
73685
  }
73597
73686
  async function ensureHostMountSources(config) {
73598
- const home2 = homedir31();
73687
+ const home2 = homedir32();
73599
73688
  const dirs = [
73600
73689
  join58(home2, ".switchroom", "approvals"),
73601
73690
  join58(home2, ".switchroom", "scheduler"),
@@ -73691,7 +73780,7 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
73691
73780
  `));
73692
73781
  }
73693
73782
  }
73694
- function writeInstallTypeCache(homeDir = homedir31()) {
73783
+ function writeInstallTypeCache(homeDir = homedir32()) {
73695
73784
  const ctx = detectInstallType();
73696
73785
  const dir = join58(homeDir, ".switchroom");
73697
73786
  const out = join58(dir, "install-type.json");
@@ -73801,7 +73890,7 @@ Applying switchroom config...
73801
73890
  }
73802
73891
  const vaultPathConfigured = config.vault?.path;
73803
73892
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
73804
- const migrationResult = migrateVaultLayout(homedir31(), {
73893
+ const migrationResult = migrateVaultLayout(homedir32(), {
73805
73894
  customVaultPath
73806
73895
  });
73807
73896
  switch (migrationResult.kind) {
@@ -73827,7 +73916,7 @@ Applying switchroom config...
73827
73916
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
73828
73917
  process.exit(4);
73829
73918
  }
73830
- const postMigrationInspect = inspectVaultLayout(homedir31());
73919
+ const postMigrationInspect = inspectVaultLayout(homedir32());
73831
73920
  const acceptable = [
73832
73921
  "no-vault",
73833
73922
  "already-migrated",
@@ -73842,7 +73931,7 @@ Applying switchroom config...
73842
73931
  `));
73843
73932
  process.exit(5);
73844
73933
  }
73845
- const vaultDir = resolveVaultBindMountDir(homedir31(), {
73934
+ const vaultDir = resolveVaultBindMountDir(homedir32(), {
73846
73935
  migrationKind: migrationResult.kind,
73847
73936
  customVaultPath
73848
73937
  });
@@ -73872,7 +73961,7 @@ Applying switchroom config...
73872
73961
  imageTag: composeImageTag,
73873
73962
  buildMode: options.buildLocal ? "local" : "pull",
73874
73963
  buildContext: options.buildContext,
73875
- homeDir: homedir31(),
73964
+ homeDir: homedir32(),
73876
73965
  switchroomConfigPath,
73877
73966
  operatorUid
73878
73967
  });
@@ -73892,7 +73981,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
73892
73981
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
73893
73982
  `));
73894
73983
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
73895
- const restored = restoreOperatorOwnership(homedir31(), operatorUid);
73984
+ const restored = restoreOperatorOwnership(homedir32(), operatorUid);
73896
73985
  if (restored.length > 0) {
73897
73986
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
73898
73987
  `));
@@ -74143,7 +74232,7 @@ function runRedactStdin() {
74143
74232
  // src/cli/status-ask.ts
74144
74233
  import { readFileSync as readFileSync54, existsSync as existsSync66, readdirSync as readdirSync24 } from "node:fs";
74145
74234
  import { join as join59 } from "node:path";
74146
- import { homedir as homedir32 } from "node:os";
74235
+ import { homedir as homedir33 } from "node:os";
74147
74236
 
74148
74237
  // src/status-ask/report.ts
74149
74238
  function parseJsonl(content) {
@@ -74478,7 +74567,7 @@ function resolveSources(explicitPath) {
74478
74567
  const config = loadConfig();
74479
74568
  agentsDir = resolveAgentsDir(config);
74480
74569
  } catch {
74481
- agentsDir = join59(homedir32(), ".switchroom", "agents");
74570
+ agentsDir = join59(homedir33(), ".switchroom", "agents");
74482
74571
  }
74483
74572
  if (!existsSync66(agentsDir))
74484
74573
  return [];
@@ -74513,14 +74602,14 @@ function inferAgentFromPath(p) {
74513
74602
  // src/cli/agent-config.ts
74514
74603
  init_helpers();
74515
74604
  import { join as join60 } from "node:path";
74516
- import { homedir as homedir33 } from "node:os";
74605
+ import { homedir as homedir34 } from "node:os";
74517
74606
  import {
74518
74607
  existsSync as existsSync67,
74519
74608
  mkdirSync as mkdirSync36,
74520
74609
  appendFileSync as appendFileSync3,
74521
74610
  readFileSync as readFileSync55
74522
74611
  } from "node:fs";
74523
- var AUDIT_ROOT = join60(homedir33(), ".switchroom", "audit");
74612
+ var AUDIT_ROOT = join60(homedir34(), ".switchroom", "audit");
74524
74613
  function auditPathFor(agent) {
74525
74614
  return join60(AUDIT_ROOT, agent, "agent-config.jsonl");
74526
74615
  }
@@ -76041,7 +76130,7 @@ function registerMigrateCommand(program3) {
76041
76130
  init_source();
76042
76131
  init_helpers();
76043
76132
  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";
76044
- import { homedir as homedir34 } from "node:os";
76133
+ import { homedir as homedir35 } from "node:os";
76045
76134
  import { join as join65 } from "node:path";
76046
76135
  import { spawnSync as spawnSync11 } from "node:child_process";
76047
76136
  init_audit_reader();
@@ -76133,7 +76222,7 @@ networks:
76133
76222
  `;
76134
76223
  }
76135
76224
  function hostdDir() {
76136
- return join65(homedir34(), ".switchroom", "hostd");
76225
+ return join65(homedir35(), ".switchroom", "hostd");
76137
76226
  }
76138
76227
  function hostdComposePath() {
76139
76228
  return join65(hostdDir(), "docker-compose.yml");
@@ -76175,7 +76264,7 @@ async function doInstall(opts, program3) {
76175
76264
  const composePath = hostdComposePath();
76176
76265
  mkdirSync39(dir, { recursive: true });
76177
76266
  const yaml = renderHostdComposeFile({
76178
- hostHome: homedir34(),
76267
+ hostHome: homedir35(),
76179
76268
  imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
76180
76269
  operatorUid: resolveOperatorUid()
76181
76270
  });