switchroom 0.13.31 → 0.13.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/switchroom.js
CHANGED
|
@@ -22983,6 +22983,7 @@ function generateCompose(opts) {
|
|
|
22983
22983
|
lines.push(` - ${homePrefix}/.switchroom/vault:/state/vault:rw`);
|
|
22984
22984
|
lines.push(` - ${homePrefix}/.switchroom/vault-auto-unlock:/state/vault-auto-unlock:ro`);
|
|
22985
22985
|
lines.push(` - ${homePrefix}/.switchroom/vault-audit.log:/root/.switchroom/vault-audit.log`);
|
|
22986
|
+
lines.push(` - ${homePrefix}/.switchroom/vault-grants.db:/root/.switchroom/vault-grants.db`);
|
|
22986
22987
|
lines.push(` - /etc/machine-id:/etc/machine-id:ro`);
|
|
22987
22988
|
lines.push(``);
|
|
22988
22989
|
lines.push(` approval-kernel:`);
|
|
@@ -29399,6 +29400,220 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
29399
29400
|
init_client4();
|
|
29400
29401
|
});
|
|
29401
29402
|
|
|
29403
|
+
// src/cli/doctor-vault-broker-durability.ts
|
|
29404
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
29405
|
+
import { existsSync as existsSync50, statSync as statSync22 } from "node:fs";
|
|
29406
|
+
import { homedir as homedir27 } from "node:os";
|
|
29407
|
+
import { join as join46 } from "node:path";
|
|
29408
|
+
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
29409
|
+
const statHost = opts?.statHost ?? defaultStatHost;
|
|
29410
|
+
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
29411
|
+
const host = statHost(hostPath);
|
|
29412
|
+
if (host === null)
|
|
29413
|
+
return { kind: "host-missing", hostPath };
|
|
29414
|
+
const broker = statBroker(brokerContainerPath);
|
|
29415
|
+
if (broker.kind !== "ok-with-stat")
|
|
29416
|
+
return broker;
|
|
29417
|
+
if (String(host.ino) === broker.ino && host.size === broker.size) {
|
|
29418
|
+
return { kind: "ok" };
|
|
29419
|
+
}
|
|
29420
|
+
return {
|
|
29421
|
+
kind: "mismatch",
|
|
29422
|
+
hostInode: String(host.ino),
|
|
29423
|
+
brokerInode: broker.ino,
|
|
29424
|
+
hostSize: host.size,
|
|
29425
|
+
brokerSize: broker.size
|
|
29426
|
+
};
|
|
29427
|
+
}
|
|
29428
|
+
function defaultStatHost(p) {
|
|
29429
|
+
if (!existsSync50(p))
|
|
29430
|
+
return null;
|
|
29431
|
+
try {
|
|
29432
|
+
const s = statSync22(p, { bigint: true });
|
|
29433
|
+
return { ino: s.ino, size: Number(s.size) };
|
|
29434
|
+
} catch {
|
|
29435
|
+
return null;
|
|
29436
|
+
}
|
|
29437
|
+
}
|
|
29438
|
+
function defaultStatBroker(p) {
|
|
29439
|
+
const r = spawnDockerStat(p);
|
|
29440
|
+
if (r.error || r.status === null)
|
|
29441
|
+
return { kind: "broker-unreachable" };
|
|
29442
|
+
if (r.status !== 0) {
|
|
29443
|
+
if (r.status >= 125)
|
|
29444
|
+
return { kind: "broker-unreachable" };
|
|
29445
|
+
return {
|
|
29446
|
+
kind: "broker-stat-failed",
|
|
29447
|
+
msg: r.stderr?.trim() || `exit ${r.status}`
|
|
29448
|
+
};
|
|
29449
|
+
}
|
|
29450
|
+
const out = r.stdout.trim();
|
|
29451
|
+
const [inoStr, sizeStr] = out.split(/\s+/);
|
|
29452
|
+
const size = Number(sizeStr);
|
|
29453
|
+
if (!inoStr || !Number.isFinite(size)) {
|
|
29454
|
+
return {
|
|
29455
|
+
kind: "broker-stat-failed",
|
|
29456
|
+
msg: `unparseable stat output: ${out}`
|
|
29457
|
+
};
|
|
29458
|
+
}
|
|
29459
|
+
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
29460
|
+
}
|
|
29461
|
+
function spawnDockerStat(p) {
|
|
29462
|
+
try {
|
|
29463
|
+
const stdout = execFileSync14("docker", ["exec", "switchroom-vault-broker", "stat", "-c", "%i %s", p], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
|
|
29464
|
+
return { status: 0, stdout, stderr: "", error: null };
|
|
29465
|
+
} catch (err) {
|
|
29466
|
+
const e = err;
|
|
29467
|
+
return {
|
|
29468
|
+
status: typeof e.status === "number" ? e.status : null,
|
|
29469
|
+
stdout: e.stdout ?? "",
|
|
29470
|
+
stderr: e.stderr ?? "",
|
|
29471
|
+
error: e.status === undefined ? new Error(e.message ?? "spawn failed") : null
|
|
29472
|
+
};
|
|
29473
|
+
}
|
|
29474
|
+
}
|
|
29475
|
+
function formatBindMountResult(name, hostPath, brokerContainerPath, result) {
|
|
29476
|
+
if (result.kind === "ok") {
|
|
29477
|
+
return {
|
|
29478
|
+
name,
|
|
29479
|
+
status: "ok",
|
|
29480
|
+
detail: `${hostPath} == ${brokerContainerPath} (same inode)`
|
|
29481
|
+
};
|
|
29482
|
+
}
|
|
29483
|
+
if (result.kind === "host-missing") {
|
|
29484
|
+
return {
|
|
29485
|
+
name,
|
|
29486
|
+
status: "warn",
|
|
29487
|
+
detail: `host file ${hostPath} missing \u2014 pre-created by \`switchroom apply\` on greenfield`,
|
|
29488
|
+
fix: "Run `switchroom apply` to pre-create the host file at the correct mode"
|
|
29489
|
+
};
|
|
29490
|
+
}
|
|
29491
|
+
if (result.kind === "broker-unreachable") {
|
|
29492
|
+
return {
|
|
29493
|
+
name,
|
|
29494
|
+
status: "skip",
|
|
29495
|
+
detail: "vault-broker container unreachable \u2014 bind mount unverified"
|
|
29496
|
+
};
|
|
29497
|
+
}
|
|
29498
|
+
if (result.kind === "broker-stat-failed") {
|
|
29499
|
+
return {
|
|
29500
|
+
name,
|
|
29501
|
+
status: "warn",
|
|
29502
|
+
detail: `broker stat failed: ${result.msg}`
|
|
29503
|
+
};
|
|
29504
|
+
}
|
|
29505
|
+
return {
|
|
29506
|
+
name,
|
|
29507
|
+
status: "fail",
|
|
29508
|
+
detail: `inode mismatch \u2014 bind mount is NOT wiring host to broker. ` + `host inode=${result.hostInode} size=${result.hostSize}; ` + `broker inode=${result.brokerInode} size=${result.brokerSize}. ` + `The broker is operating on an ephemeral container-local file; ` + `data written there evaporates on container recreate.`,
|
|
29509
|
+
fix: "Run `switchroom apply` to regenerate compose with the correct " + "bind mount, then `docker compose -p switchroom up -d vault-broker` " + "to recreate the broker container."
|
|
29510
|
+
};
|
|
29511
|
+
}
|
|
29512
|
+
function probeBrokerUnlocked(opts) {
|
|
29513
|
+
const status = (opts?.statusProbe ?? defaultBrokerStatusProbe)();
|
|
29514
|
+
if (status === null) {
|
|
29515
|
+
return {
|
|
29516
|
+
name: "vault-broker unlocked (state)",
|
|
29517
|
+
status: "skip",
|
|
29518
|
+
detail: "vault-broker container unreachable"
|
|
29519
|
+
};
|
|
29520
|
+
}
|
|
29521
|
+
if (!status.unlocked) {
|
|
29522
|
+
return {
|
|
29523
|
+
name: "vault-broker unlocked (state)",
|
|
29524
|
+
status: "fail",
|
|
29525
|
+
detail: `broker reports locked despite config \u2014 auto-unlock failed silently. ` + `Common causes: \`/etc/machine-id\` mount missing or differs from the ` + `host the blob was sealed on; vault-auto-unlock blob corrupted; ` + `vault passphrase was rotated without re-running ` + `\`switchroom vault broker enable-auto-unlock\`.`,
|
|
29526
|
+
fix: "Re-run `switchroom vault broker enable-auto-unlock` on the host " + "to re-seal the blob against the current machine-id + passphrase. " + "Then restart the broker (`docker compose -p switchroom restart vault-broker`)."
|
|
29527
|
+
};
|
|
29528
|
+
}
|
|
29529
|
+
return {
|
|
29530
|
+
name: "vault-broker unlocked (state)",
|
|
29531
|
+
status: "ok",
|
|
29532
|
+
detail: `${status.keyCount} key(s) loaded`
|
|
29533
|
+
};
|
|
29534
|
+
}
|
|
29535
|
+
function defaultBrokerStatusProbe() {
|
|
29536
|
+
try {
|
|
29537
|
+
const out = execFileSync14("switchroom", ["vault", "broker", "status"], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
|
|
29538
|
+
const parsed = JSON.parse(out.trim());
|
|
29539
|
+
if (!parsed.running)
|
|
29540
|
+
return null;
|
|
29541
|
+
return { unlocked: parsed.unlocked, keyCount: parsed.keyCount };
|
|
29542
|
+
} catch {
|
|
29543
|
+
return null;
|
|
29544
|
+
}
|
|
29545
|
+
}
|
|
29546
|
+
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
29547
|
+
const home2 = homedir27();
|
|
29548
|
+
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
29549
|
+
return [
|
|
29550
|
+
probeBrokerUnlocked(opts?.statusProbe),
|
|
29551
|
+
probeAutoUnlockBlob(home2),
|
|
29552
|
+
probeMachineIdMount(),
|
|
29553
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
29554
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
29555
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log"))
|
|
29556
|
+
];
|
|
29557
|
+
}
|
|
29558
|
+
function probeAutoUnlockBlob(home2) {
|
|
29559
|
+
const blobPath = join46(home2, ".switchroom", "vault-auto-unlock");
|
|
29560
|
+
if (!existsSync50(blobPath)) {
|
|
29561
|
+
return {
|
|
29562
|
+
name: "vault-broker: auto-unlock blob",
|
|
29563
|
+
status: "warn",
|
|
29564
|
+
detail: `${blobPath} not present \u2014 broker will fall back to interactive unlock`,
|
|
29565
|
+
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
29566
|
+
};
|
|
29567
|
+
}
|
|
29568
|
+
const sz = statSync22(blobPath).size;
|
|
29569
|
+
if (sz === 0) {
|
|
29570
|
+
return {
|
|
29571
|
+
name: "vault-broker: auto-unlock blob",
|
|
29572
|
+
status: "warn",
|
|
29573
|
+
detail: `${blobPath} is 0 bytes (placeholder) \u2014 broker will fall back to interactive unlock`,
|
|
29574
|
+
fix: "Run `switchroom vault broker enable-auto-unlock` to actually seal the blob"
|
|
29575
|
+
};
|
|
29576
|
+
}
|
|
29577
|
+
return {
|
|
29578
|
+
name: "vault-broker: auto-unlock blob",
|
|
29579
|
+
status: "ok",
|
|
29580
|
+
detail: `${blobPath} present (${sz} bytes, machine-bound)`
|
|
29581
|
+
};
|
|
29582
|
+
}
|
|
29583
|
+
function probeMachineIdMount() {
|
|
29584
|
+
const hostExists = existsSync50("/etc/machine-id");
|
|
29585
|
+
if (!hostExists) {
|
|
29586
|
+
return {
|
|
29587
|
+
name: "vault-broker: machine-id passthrough",
|
|
29588
|
+
status: "fail",
|
|
29589
|
+
detail: "/etc/machine-id missing on host \u2014 auto-unlock key derivation impossible",
|
|
29590
|
+
fix: "Generate a machine-id (`systemd-machine-id-setup`) and re-seal the auto-unlock blob"
|
|
29591
|
+
};
|
|
29592
|
+
}
|
|
29593
|
+
const r = spawnDockerStat("/etc/machine-id");
|
|
29594
|
+
if (r.error || r.status === null || r.status >= 125) {
|
|
29595
|
+
return {
|
|
29596
|
+
name: "vault-broker: machine-id passthrough",
|
|
29597
|
+
status: "skip",
|
|
29598
|
+
detail: "vault-broker container unreachable"
|
|
29599
|
+
};
|
|
29600
|
+
}
|
|
29601
|
+
if (r.status !== 0) {
|
|
29602
|
+
return {
|
|
29603
|
+
name: "vault-broker: machine-id passthrough",
|
|
29604
|
+
status: "fail",
|
|
29605
|
+
detail: "broker container has no /etc/machine-id \u2014 compose `/etc/machine-id:/etc/machine-id:ro` mount is missing",
|
|
29606
|
+
fix: "Run `switchroom apply` to regenerate compose with the machine-id passthrough"
|
|
29607
|
+
};
|
|
29608
|
+
}
|
|
29609
|
+
return {
|
|
29610
|
+
name: "vault-broker: machine-id passthrough",
|
|
29611
|
+
status: "ok",
|
|
29612
|
+
detail: "broker reads the host machine-id"
|
|
29613
|
+
};
|
|
29614
|
+
}
|
|
29615
|
+
var init_doctor_vault_broker_durability = () => {};
|
|
29616
|
+
|
|
29402
29617
|
// src/cli/doctor.ts
|
|
29403
29618
|
var exports_doctor = {};
|
|
29404
29619
|
__export(exports_doctor, {
|
|
@@ -29444,25 +29659,25 @@ import { execSync as execSync3, spawnSync as spawnSync7 } from "node:child_proce
|
|
|
29444
29659
|
import {
|
|
29445
29660
|
accessSync as accessSync2,
|
|
29446
29661
|
constants as fsConstants5,
|
|
29447
|
-
existsSync as
|
|
29662
|
+
existsSync as existsSync51,
|
|
29448
29663
|
lstatSync as lstatSync5,
|
|
29449
29664
|
mkdirSync as mkdirSync27,
|
|
29450
29665
|
readFileSync as readFileSync45,
|
|
29451
29666
|
readdirSync as readdirSync19,
|
|
29452
|
-
statSync as
|
|
29667
|
+
statSync as statSync23
|
|
29453
29668
|
} from "node:fs";
|
|
29454
|
-
import { dirname as dirname12, join as
|
|
29669
|
+
import { dirname as dirname12, join as join47, resolve as resolve30 } from "node:path";
|
|
29455
29670
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
29456
29671
|
function findInNvm(bin) {
|
|
29457
|
-
const nvmRoot =
|
|
29458
|
-
if (!
|
|
29672
|
+
const nvmRoot = join47(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
29673
|
+
if (!existsSync51(nvmRoot))
|
|
29459
29674
|
return null;
|
|
29460
29675
|
try {
|
|
29461
29676
|
const versions = readdirSync19(nvmRoot).sort().reverse();
|
|
29462
29677
|
for (const v of versions) {
|
|
29463
|
-
const candidate =
|
|
29678
|
+
const candidate = join47(nvmRoot, v, "bin", bin);
|
|
29464
29679
|
try {
|
|
29465
|
-
const s =
|
|
29680
|
+
const s = statSync23(candidate);
|
|
29466
29681
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
29467
29682
|
return candidate;
|
|
29468
29683
|
}
|
|
@@ -29625,21 +29840,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29625
29840
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
29626
29841
|
cacheLocations.push(envBrowsersPath);
|
|
29627
29842
|
}
|
|
29628
|
-
cacheLocations.push(
|
|
29843
|
+
cacheLocations.push(join47(homeDir, ".cache", "ms-playwright"));
|
|
29629
29844
|
for (const cacheDir of cacheLocations) {
|
|
29630
|
-
if (!
|
|
29845
|
+
if (!existsSync51(cacheDir))
|
|
29631
29846
|
continue;
|
|
29632
29847
|
try {
|
|
29633
29848
|
const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
29634
29849
|
for (const entry of entries) {
|
|
29635
29850
|
const candidates2 = [
|
|
29636
|
-
|
|
29637
|
-
|
|
29638
|
-
|
|
29639
|
-
|
|
29851
|
+
join47(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
29852
|
+
join47(cacheDir, entry, "chrome-linux", "chrome"),
|
|
29853
|
+
join47(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
29854
|
+
join47(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
29640
29855
|
];
|
|
29641
29856
|
for (const path4 of candidates2) {
|
|
29642
|
-
if (
|
|
29857
|
+
if (existsSync51(path4))
|
|
29643
29858
|
return path4;
|
|
29644
29859
|
}
|
|
29645
29860
|
}
|
|
@@ -29719,8 +29934,8 @@ function checkConfig(config, configPath) {
|
|
|
29719
29934
|
function checkLegacyState() {
|
|
29720
29935
|
const results = [];
|
|
29721
29936
|
const h = process.env.HOME ?? "/root";
|
|
29722
|
-
const clerkDir =
|
|
29723
|
-
const clerkPresent =
|
|
29937
|
+
const clerkDir = join47(h, LEGACY_STATE_DIR);
|
|
29938
|
+
const clerkPresent = existsSync51(clerkDir);
|
|
29724
29939
|
results.push({
|
|
29725
29940
|
name: "legacy ~/.clerk state",
|
|
29726
29941
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -29729,7 +29944,7 @@ function checkLegacyState() {
|
|
|
29729
29944
|
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."
|
|
29730
29945
|
} : {}
|
|
29731
29946
|
});
|
|
29732
|
-
const legacySock =
|
|
29947
|
+
const legacySock = join47(h, ".switchroom", "vault-broker.sock");
|
|
29733
29948
|
let sockStat = null;
|
|
29734
29949
|
try {
|
|
29735
29950
|
sockStat = lstatSync5(legacySock);
|
|
@@ -29850,7 +30065,7 @@ function checkVault(config) {
|
|
|
29850
30065
|
detail: "Approval auth: passphrase (two-factor)"
|
|
29851
30066
|
};
|
|
29852
30067
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
29853
|
-
if (!
|
|
30068
|
+
if (!existsSync51(vaultPath)) {
|
|
29854
30069
|
return [
|
|
29855
30070
|
postureResult,
|
|
29856
30071
|
{
|
|
@@ -30025,8 +30240,8 @@ async function checkHindsight(config) {
|
|
|
30025
30240
|
}
|
|
30026
30241
|
function checkPendingRetainsQueue(dir) {
|
|
30027
30242
|
const home2 = process.env.HOME ?? "";
|
|
30028
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
30029
|
-
if (!
|
|
30243
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join47(home2, ".hindsight", "pending-retains");
|
|
30244
|
+
if (!existsSync51(pendingDir)) {
|
|
30030
30245
|
return {
|
|
30031
30246
|
name: "pending-retains queue",
|
|
30032
30247
|
status: "ok",
|
|
@@ -30097,7 +30312,7 @@ function tryReadHostFile(path4) {
|
|
|
30097
30312
|
}
|
|
30098
30313
|
}
|
|
30099
30314
|
function parseEnvFile(path4) {
|
|
30100
|
-
if (!
|
|
30315
|
+
if (!existsSync51(path4))
|
|
30101
30316
|
return {};
|
|
30102
30317
|
let content;
|
|
30103
30318
|
try {
|
|
@@ -30156,7 +30371,7 @@ async function checkTelegram(config) {
|
|
|
30156
30371
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
30157
30372
|
if (plugin !== "switchroom")
|
|
30158
30373
|
continue;
|
|
30159
|
-
const envPath =
|
|
30374
|
+
const envPath = join47(agentsDir, name, "telegram", ".env");
|
|
30160
30375
|
const read = tryReadHostFile(envPath);
|
|
30161
30376
|
if (read.kind === "eacces") {
|
|
30162
30377
|
results.push({
|
|
@@ -30208,7 +30423,7 @@ async function checkTelegram(config) {
|
|
|
30208
30423
|
}
|
|
30209
30424
|
function checkStartShStale(agentName, startShPath) {
|
|
30210
30425
|
const label = `${agentName}: start.sh scheduler block`;
|
|
30211
|
-
if (!
|
|
30426
|
+
if (!existsSync51(startShPath)) {
|
|
30212
30427
|
return {
|
|
30213
30428
|
name: label,
|
|
30214
30429
|
status: "warn",
|
|
@@ -30239,7 +30454,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
30239
30454
|
}
|
|
30240
30455
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
30241
30456
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
30242
|
-
const path4 =
|
|
30457
|
+
const path4 = join47(agentDir, "home", ".switchroom");
|
|
30243
30458
|
let stats;
|
|
30244
30459
|
try {
|
|
30245
30460
|
stats = lstatSync5(path4);
|
|
@@ -30276,8 +30491,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
30276
30491
|
}
|
|
30277
30492
|
function checkRepoHygiene(repoRoot) {
|
|
30278
30493
|
const results = [];
|
|
30279
|
-
const exportDir =
|
|
30280
|
-
if (
|
|
30494
|
+
const exportDir = join47(repoRoot, "clerk-export");
|
|
30495
|
+
if (existsSync51(exportDir)) {
|
|
30281
30496
|
results.push({
|
|
30282
30497
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
30283
30498
|
status: "warn",
|
|
@@ -30285,8 +30500,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30285
30500
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
30286
30501
|
});
|
|
30287
30502
|
}
|
|
30288
|
-
const knownTarball =
|
|
30289
|
-
if (
|
|
30503
|
+
const knownTarball = join47(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
30504
|
+
if (existsSync51(knownTarball)) {
|
|
30290
30505
|
results.push({
|
|
30291
30506
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
30292
30507
|
status: "warn",
|
|
@@ -30303,7 +30518,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30303
30518
|
results.push({
|
|
30304
30519
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
30305
30520
|
status: "warn",
|
|
30306
|
-
detail: `${
|
|
30521
|
+
detail: `${join47(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
30307
30522
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
30308
30523
|
});
|
|
30309
30524
|
}
|
|
@@ -30326,10 +30541,10 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30326
30541
|
}
|
|
30327
30542
|
function isSwitchroomCheckout(dir) {
|
|
30328
30543
|
try {
|
|
30329
|
-
if (!
|
|
30544
|
+
if (!existsSync51(join47(dir, ".git")))
|
|
30330
30545
|
return false;
|
|
30331
|
-
const pkgPath =
|
|
30332
|
-
if (!
|
|
30546
|
+
const pkgPath = join47(dir, "package.json");
|
|
30547
|
+
if (!existsSync51(pkgPath))
|
|
30333
30548
|
return false;
|
|
30334
30549
|
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
30335
30550
|
return pkg.name === "switchroom";
|
|
@@ -30344,7 +30559,7 @@ function checkAgents(config, configPath) {
|
|
|
30344
30559
|
const authStatuses = getAllAuthStatuses(config);
|
|
30345
30560
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
30346
30561
|
const agentDir = resolve30(agentsDir, name);
|
|
30347
|
-
if (!
|
|
30562
|
+
if (!existsSync51(agentDir)) {
|
|
30348
30563
|
results.push({
|
|
30349
30564
|
name: `${name}: scaffold`,
|
|
30350
30565
|
status: "fail",
|
|
@@ -30365,7 +30580,7 @@ function checkAgents(config, configPath) {
|
|
|
30365
30580
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
30366
30581
|
});
|
|
30367
30582
|
}
|
|
30368
|
-
results.push(checkStartShStale(name,
|
|
30583
|
+
results.push(checkStartShStale(name, join47(agentDir, "start.sh")));
|
|
30369
30584
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
30370
30585
|
const status = statuses[name];
|
|
30371
30586
|
const active = status?.active ?? "unknown";
|
|
@@ -30442,8 +30657,8 @@ function checkAgents(config, configPath) {
|
|
|
30442
30657
|
}
|
|
30443
30658
|
}
|
|
30444
30659
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
30445
|
-
const mcpJsonPath =
|
|
30446
|
-
if (!
|
|
30660
|
+
const mcpJsonPath = join47(agentDir, ".mcp.json");
|
|
30661
|
+
if (!existsSync51(mcpJsonPath)) {
|
|
30447
30662
|
results.push({
|
|
30448
30663
|
name: `${name}: .mcp.json`,
|
|
30449
30664
|
status: "fail",
|
|
@@ -30528,7 +30743,7 @@ function mffEnvPath(config) {
|
|
|
30528
30743
|
return agent ? resolve30(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve30(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
30529
30744
|
}
|
|
30530
30745
|
function mffEnvState(envPath) {
|
|
30531
|
-
if (!
|
|
30746
|
+
if (!existsSync51(envPath))
|
|
30532
30747
|
return "absent";
|
|
30533
30748
|
try {
|
|
30534
30749
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -30546,7 +30761,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
30546
30761
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
30547
30762
|
};
|
|
30548
30763
|
}
|
|
30549
|
-
if (!
|
|
30764
|
+
if (!existsSync51(vaultPath)) {
|
|
30550
30765
|
return {
|
|
30551
30766
|
name: "mff: vault key present",
|
|
30552
30767
|
status: "fail",
|
|
@@ -30599,7 +30814,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
30599
30814
|
}
|
|
30600
30815
|
}
|
|
30601
30816
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
30602
|
-
if (!passphrase || !
|
|
30817
|
+
if (!passphrase || !existsSync51(vaultPath)) {
|
|
30603
30818
|
return {
|
|
30604
30819
|
name: "mff: vault key format",
|
|
30605
30820
|
status: "warn",
|
|
@@ -30743,8 +30958,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
30743
30958
|
};
|
|
30744
30959
|
}
|
|
30745
30960
|
const credDir = dirname12(envPath);
|
|
30746
|
-
const authScript =
|
|
30747
|
-
if (!
|
|
30961
|
+
const authScript = join47(credDir, "claude-auth.py");
|
|
30962
|
+
if (!existsSync51(authScript)) {
|
|
30748
30963
|
return {
|
|
30749
30964
|
name: "mff: auth flow",
|
|
30750
30965
|
status: "warn",
|
|
@@ -31022,6 +31237,10 @@ function registerDoctorCommand(program3) {
|
|
|
31022
31237
|
},
|
|
31023
31238
|
{ title: "Legacy State", results: checkLegacyState() },
|
|
31024
31239
|
{ title: "Vault", results: checkVault(config) },
|
|
31240
|
+
{
|
|
31241
|
+
title: "Vault-broker durability",
|
|
31242
|
+
results: runVaultBrokerDurabilityChecks(config)
|
|
31243
|
+
},
|
|
31025
31244
|
{ title: "Vault access", results: await runSecretAccessChecks(config) },
|
|
31026
31245
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
31027
31246
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
@@ -31105,6 +31324,7 @@ var init_doctor = __esm(() => {
|
|
|
31105
31324
|
init_doctor_inlined_secrets();
|
|
31106
31325
|
init_doctor_audit_integrity();
|
|
31107
31326
|
init_doctor_agent_smoke();
|
|
31327
|
+
init_doctor_vault_broker_durability();
|
|
31108
31328
|
MANIFEST_WARN_ONLY = new Set([
|
|
31109
31329
|
"@playwright/mcp",
|
|
31110
31330
|
"hindsight.backend",
|
|
@@ -47033,7 +47253,7 @@ __export(exports_server2, {
|
|
|
47033
47253
|
TOOLS: () => TOOLS2
|
|
47034
47254
|
});
|
|
47035
47255
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47036
|
-
import { existsSync as
|
|
47256
|
+
import { existsSync as existsSync75, readFileSync as readFileSync60 } from "node:fs";
|
|
47037
47257
|
function selfSocketPath() {
|
|
47038
47258
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
47039
47259
|
}
|
|
@@ -47048,7 +47268,7 @@ async function dispatchTool2(name, args) {
|
|
|
47048
47268
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
47049
47269
|
}
|
|
47050
47270
|
const sockPath = selfSocketPath();
|
|
47051
|
-
if (!
|
|
47271
|
+
if (!existsSync75(sockPath)) {
|
|
47052
47272
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
47053
47273
|
}
|
|
47054
47274
|
let req;
|
|
@@ -47192,13 +47412,13 @@ function resolveAuditLogPath() {
|
|
|
47192
47412
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
47193
47413
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
47194
47414
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
47195
|
-
if (
|
|
47415
|
+
if (existsSync75(bindMounted))
|
|
47196
47416
|
return bindMounted;
|
|
47197
47417
|
return defaultAuditLogPath2();
|
|
47198
47418
|
}
|
|
47199
47419
|
function getLastUpdateApplyStatus() {
|
|
47200
47420
|
const path8 = resolveAuditLogPath();
|
|
47201
|
-
if (!
|
|
47421
|
+
if (!existsSync75(path8)) {
|
|
47202
47422
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
47203
47423
|
}
|
|
47204
47424
|
let raw;
|
|
@@ -47436,8 +47656,8 @@ var {
|
|
|
47436
47656
|
} = import__.default;
|
|
47437
47657
|
|
|
47438
47658
|
// src/build-info.ts
|
|
47439
|
-
var VERSION = "0.13.
|
|
47440
|
-
var COMMIT_SHA = "
|
|
47659
|
+
var VERSION = "0.13.33";
|
|
47660
|
+
var COMMIT_SHA = "a8018071";
|
|
47441
47661
|
|
|
47442
47662
|
// src/cli/agent.ts
|
|
47443
47663
|
init_source();
|
|
@@ -69810,17 +70030,17 @@ init_doctor();
|
|
|
69810
70030
|
init_source();
|
|
69811
70031
|
init_loader();
|
|
69812
70032
|
init_lifecycle();
|
|
69813
|
-
import { cpSync as cpSync2, existsSync as
|
|
70033
|
+
import { cpSync as cpSync2, existsSync as existsSync52, mkdirSync as mkdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
|
|
69814
70034
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
69815
|
-
import { join as
|
|
69816
|
-
import { homedir as
|
|
69817
|
-
var DEFAULT_COMPOSE_PATH =
|
|
70035
|
+
import { join as join48, dirname as dirname13, resolve as resolve31 } from "node:path";
|
|
70036
|
+
import { homedir as homedir28 } from "node:os";
|
|
70037
|
+
var DEFAULT_COMPOSE_PATH = join48(homedir28(), ".switchroom", "compose", "docker-compose.yml");
|
|
69818
70038
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
69819
70039
|
let dir = dirname13(scriptPath);
|
|
69820
70040
|
for (let i = 0;i < 12; i++) {
|
|
69821
|
-
if (
|
|
70041
|
+
if (existsSync52(join48(dir, ".git"))) {
|
|
69822
70042
|
try {
|
|
69823
|
-
const pkg = JSON.parse(readFileSync46(
|
|
70043
|
+
const pkg = JSON.parse(readFileSync46(join48(dir, "package.json"), "utf-8"));
|
|
69824
70044
|
if (pkg.name === "switchroom")
|
|
69825
70045
|
return true;
|
|
69826
70046
|
} catch {}
|
|
@@ -69872,7 +70092,7 @@ function planUpdate(opts) {
|
|
|
69872
70092
|
steps.push({
|
|
69873
70093
|
name: "pull-images",
|
|
69874
70094
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
69875
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
70095
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync52(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
69876
70096
|
run: () => {
|
|
69877
70097
|
const r = runner("docker", [
|
|
69878
70098
|
"compose",
|
|
@@ -69951,14 +70171,14 @@ function planUpdate(opts) {
|
|
|
69951
70171
|
return;
|
|
69952
70172
|
}
|
|
69953
70173
|
const source = resolve31(import.meta.dirname, "../../skills");
|
|
69954
|
-
const dest =
|
|
69955
|
-
if (!
|
|
70174
|
+
const dest = join48(homedir28(), ".switchroom", "skills", "_bundled");
|
|
70175
|
+
if (!existsSync52(source)) {
|
|
69956
70176
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69957
70177
|
`);
|
|
69958
70178
|
return;
|
|
69959
70179
|
}
|
|
69960
70180
|
try {
|
|
69961
|
-
if (
|
|
70181
|
+
if (existsSync52(dest)) {
|
|
69962
70182
|
rmSync12(dest, { recursive: true, force: true });
|
|
69963
70183
|
}
|
|
69964
70184
|
mkdirSync28(dirname13(dest), { recursive: true });
|
|
@@ -70061,12 +70281,12 @@ function defaultStatusProbe(composePath) {
|
|
|
70061
70281
|
} catch {}
|
|
70062
70282
|
if (scriptPath) {
|
|
70063
70283
|
try {
|
|
70064
|
-
cliBuiltAt = new Date(
|
|
70284
|
+
cliBuiltAt = new Date(statSync24(scriptPath).mtimeMs).toISOString();
|
|
70065
70285
|
} catch {}
|
|
70066
70286
|
let dir = dirname13(scriptPath);
|
|
70067
70287
|
for (let i = 0;i < 8; i++) {
|
|
70068
|
-
const pkgPath =
|
|
70069
|
-
if (
|
|
70288
|
+
const pkgPath = join48(dir, "package.json");
|
|
70289
|
+
if (existsSync52(pkgPath)) {
|
|
70070
70290
|
try {
|
|
70071
70291
|
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
70072
70292
|
if (typeof pkg.version === "string")
|
|
@@ -70089,7 +70309,7 @@ function defaultStatusProbe(composePath) {
|
|
|
70089
70309
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
70090
70310
|
}
|
|
70091
70311
|
const services = [];
|
|
70092
|
-
if (!
|
|
70312
|
+
if (!existsSync52(composePath)) {
|
|
70093
70313
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
70094
70314
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
70095
70315
|
}
|
|
@@ -70284,8 +70504,8 @@ init_source();
|
|
|
70284
70504
|
init_helpers();
|
|
70285
70505
|
init_lifecycle();
|
|
70286
70506
|
import { execSync as execSync4 } from "node:child_process";
|
|
70287
|
-
import { existsSync as
|
|
70288
|
-
import { dirname as dirname14, join as
|
|
70507
|
+
import { existsSync as existsSync53, readFileSync as readFileSync47 } from "node:fs";
|
|
70508
|
+
import { dirname as dirname14, join as join49 } from "node:path";
|
|
70289
70509
|
function getClaudeCodeVersion() {
|
|
70290
70510
|
try {
|
|
70291
70511
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -70335,11 +70555,11 @@ function formatUptime3(timestamp) {
|
|
|
70335
70555
|
function locateSwitchroomInstallDir() {
|
|
70336
70556
|
let dir = import.meta.dirname;
|
|
70337
70557
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
70338
|
-
const pkgPath =
|
|
70339
|
-
if (
|
|
70558
|
+
const pkgPath = join49(dir, "package.json");
|
|
70559
|
+
if (existsSync53(pkgPath)) {
|
|
70340
70560
|
try {
|
|
70341
70561
|
const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
|
|
70342
|
-
if (pkg.name === "switchroom" &&
|
|
70562
|
+
if (pkg.name === "switchroom" && existsSync53(join49(dir, ".git"))) {
|
|
70343
70563
|
return dir;
|
|
70344
70564
|
}
|
|
70345
70565
|
} catch {}
|
|
@@ -70554,18 +70774,18 @@ function registerHandoffCommand(program3) {
|
|
|
70554
70774
|
// src/issues/store.ts
|
|
70555
70775
|
import {
|
|
70556
70776
|
closeSync as closeSync11,
|
|
70557
|
-
existsSync as
|
|
70777
|
+
existsSync as existsSync54,
|
|
70558
70778
|
mkdirSync as mkdirSync29,
|
|
70559
70779
|
openSync as openSync11,
|
|
70560
70780
|
readdirSync as readdirSync20,
|
|
70561
70781
|
readFileSync as readFileSync48,
|
|
70562
70782
|
renameSync as renameSync11,
|
|
70563
|
-
statSync as
|
|
70783
|
+
statSync as statSync25,
|
|
70564
70784
|
unlinkSync as unlinkSync11,
|
|
70565
70785
|
writeFileSync as writeFileSync25,
|
|
70566
70786
|
writeSync as writeSync7
|
|
70567
70787
|
} from "node:fs";
|
|
70568
|
-
import { join as
|
|
70788
|
+
import { join as join50 } from "node:path";
|
|
70569
70789
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
70570
70790
|
import { execSync as execSync5 } from "node:child_process";
|
|
70571
70791
|
|
|
@@ -70885,8 +71105,8 @@ function redactedMarker(ruleId) {
|
|
|
70885
71105
|
var ISSUES_FILE = "issues.jsonl";
|
|
70886
71106
|
var ISSUES_LOCK = "issues.lock";
|
|
70887
71107
|
function readAll(stateDir) {
|
|
70888
|
-
const path4 =
|
|
70889
|
-
if (!
|
|
71108
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71109
|
+
if (!existsSync54(path4))
|
|
70890
71110
|
return [];
|
|
70891
71111
|
let raw;
|
|
70892
71112
|
try {
|
|
@@ -70963,7 +71183,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
70963
71183
|
});
|
|
70964
71184
|
}
|
|
70965
71185
|
function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
70966
|
-
if (!
|
|
71186
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70967
71187
|
return 0;
|
|
70968
71188
|
return withLock(stateDir, () => {
|
|
70969
71189
|
const all = readAll(stateDir);
|
|
@@ -70981,7 +71201,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
70981
71201
|
});
|
|
70982
71202
|
}
|
|
70983
71203
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
70984
|
-
if (!
|
|
71204
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70985
71205
|
return 0;
|
|
70986
71206
|
return withLock(stateDir, () => {
|
|
70987
71207
|
const all = readAll(stateDir);
|
|
@@ -70999,7 +71219,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
70999
71219
|
});
|
|
71000
71220
|
}
|
|
71001
71221
|
function prune(stateDir, opts = {}) {
|
|
71002
|
-
if (!
|
|
71222
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
71003
71223
|
return 0;
|
|
71004
71224
|
return withLock(stateDir, () => {
|
|
71005
71225
|
const all = readAll(stateDir);
|
|
@@ -71032,7 +71252,7 @@ function ensureDir(stateDir) {
|
|
|
71032
71252
|
mkdirSync29(stateDir, { recursive: true });
|
|
71033
71253
|
}
|
|
71034
71254
|
function writeAll(stateDir, events) {
|
|
71035
|
-
const path4 =
|
|
71255
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71036
71256
|
sweepOrphanTmpFiles(stateDir);
|
|
71037
71257
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
|
|
71038
71258
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -71054,9 +71274,9 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71054
71274
|
for (const entry of entries) {
|
|
71055
71275
|
if (!entry.startsWith(TMP_PREFIX))
|
|
71056
71276
|
continue;
|
|
71057
|
-
const tmpPath =
|
|
71277
|
+
const tmpPath = join50(stateDir, entry);
|
|
71058
71278
|
try {
|
|
71059
|
-
const stat =
|
|
71279
|
+
const stat = statSync25(tmpPath);
|
|
71060
71280
|
if (stat.mtimeMs < cutoff) {
|
|
71061
71281
|
unlinkSync11(tmpPath);
|
|
71062
71282
|
}
|
|
@@ -71066,7 +71286,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71066
71286
|
var LOCK_RETRY_MS = 25;
|
|
71067
71287
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
71068
71288
|
function withLock(stateDir, fn) {
|
|
71069
|
-
const lockPath =
|
|
71289
|
+
const lockPath = join50(stateDir, ISSUES_LOCK);
|
|
71070
71290
|
const startedAt = Date.now();
|
|
71071
71291
|
let fd = null;
|
|
71072
71292
|
while (fd === null) {
|
|
@@ -71349,22 +71569,22 @@ function relTime(deltaMs) {
|
|
|
71349
71569
|
|
|
71350
71570
|
// src/cli/deps.ts
|
|
71351
71571
|
init_source();
|
|
71352
|
-
import { existsSync as
|
|
71353
|
-
import { homedir as
|
|
71354
|
-
import { join as
|
|
71572
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
71573
|
+
import { homedir as homedir31 } from "node:os";
|
|
71574
|
+
import { join as join53, resolve as resolve35 } from "node:path";
|
|
71355
71575
|
|
|
71356
71576
|
// src/deps/python.ts
|
|
71357
71577
|
import { createHash as createHash10 } from "node:crypto";
|
|
71358
71578
|
import {
|
|
71359
|
-
existsSync as
|
|
71579
|
+
existsSync as existsSync55,
|
|
71360
71580
|
mkdirSync as mkdirSync30,
|
|
71361
71581
|
readFileSync as readFileSync49,
|
|
71362
71582
|
rmSync as rmSync13,
|
|
71363
71583
|
writeFileSync as writeFileSync26
|
|
71364
71584
|
} from "node:fs";
|
|
71365
|
-
import { dirname as dirname15, join as
|
|
71366
|
-
import { homedir as
|
|
71367
|
-
import { execFileSync as
|
|
71585
|
+
import { dirname as dirname15, join as join51 } from "node:path";
|
|
71586
|
+
import { homedir as homedir29 } from "node:os";
|
|
71587
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
71368
71588
|
|
|
71369
71589
|
class PythonEnvError extends Error {
|
|
71370
71590
|
stderr;
|
|
@@ -71375,7 +71595,7 @@ class PythonEnvError extends Error {
|
|
|
71375
71595
|
}
|
|
71376
71596
|
}
|
|
71377
71597
|
function defaultPythonCacheRoot() {
|
|
71378
|
-
return
|
|
71598
|
+
return join51(homedir29(), ".switchroom", "deps", "python");
|
|
71379
71599
|
}
|
|
71380
71600
|
function hashFile(path4) {
|
|
71381
71601
|
return createHash10("sha256").update(readFileSync49(path4)).digest("hex");
|
|
@@ -71384,16 +71604,16 @@ function ensurePythonEnv(opts) {
|
|
|
71384
71604
|
const { skillName, requirementsPath, force = false } = opts;
|
|
71385
71605
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
71386
71606
|
const hostPython = opts.pythonBin ?? "python3";
|
|
71387
|
-
if (!
|
|
71607
|
+
if (!existsSync55(requirementsPath)) {
|
|
71388
71608
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
71389
71609
|
}
|
|
71390
|
-
const venvDir =
|
|
71391
|
-
const stampPath =
|
|
71392
|
-
const binDir =
|
|
71393
|
-
const pythonBin =
|
|
71394
|
-
const pipBin =
|
|
71610
|
+
const venvDir = join51(cacheRoot, skillName);
|
|
71611
|
+
const stampPath = join51(venvDir, ".requirements.sha256");
|
|
71612
|
+
const binDir = join51(venvDir, "bin");
|
|
71613
|
+
const pythonBin = join51(binDir, "python");
|
|
71614
|
+
const pipBin = join51(binDir, "pip");
|
|
71395
71615
|
const targetHash = hashFile(requirementsPath);
|
|
71396
|
-
if (!force &&
|
|
71616
|
+
if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
|
|
71397
71617
|
const existingHash = readFileSync49(stampPath, "utf8").trim();
|
|
71398
71618
|
if (existingHash === targetHash) {
|
|
71399
71619
|
return {
|
|
@@ -71406,12 +71626,12 @@ function ensurePythonEnv(opts) {
|
|
|
71406
71626
|
};
|
|
71407
71627
|
}
|
|
71408
71628
|
}
|
|
71409
|
-
if (
|
|
71629
|
+
if (existsSync55(venvDir)) {
|
|
71410
71630
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
71411
71631
|
}
|
|
71412
71632
|
mkdirSync30(dirname15(venvDir), { recursive: true });
|
|
71413
71633
|
try {
|
|
71414
|
-
|
|
71634
|
+
execFileSync15(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
71415
71635
|
} catch (err) {
|
|
71416
71636
|
const e = err;
|
|
71417
71637
|
throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
|
|
@@ -71423,7 +71643,7 @@ function ensurePythonEnv(opts) {
|
|
|
71423
71643
|
delete childEnv.PIP_TARGET;
|
|
71424
71644
|
delete childEnv.PIP_PREFIX;
|
|
71425
71645
|
delete childEnv.PYTHONUSERBASE;
|
|
71426
|
-
|
|
71646
|
+
execFileSync15(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
|
|
71427
71647
|
} catch (err) {
|
|
71428
71648
|
const e = err;
|
|
71429
71649
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
@@ -71444,15 +71664,15 @@ function ensurePythonEnv(opts) {
|
|
|
71444
71664
|
import { createHash as createHash11 } from "node:crypto";
|
|
71445
71665
|
import {
|
|
71446
71666
|
copyFileSync as copyFileSync9,
|
|
71447
|
-
existsSync as
|
|
71667
|
+
existsSync as existsSync56,
|
|
71448
71668
|
mkdirSync as mkdirSync31,
|
|
71449
71669
|
readFileSync as readFileSync50,
|
|
71450
71670
|
rmSync as rmSync14,
|
|
71451
71671
|
writeFileSync as writeFileSync27
|
|
71452
71672
|
} from "node:fs";
|
|
71453
|
-
import { dirname as dirname16, join as
|
|
71454
|
-
import { homedir as
|
|
71455
|
-
import { execFileSync as
|
|
71673
|
+
import { dirname as dirname16, join as join52 } from "node:path";
|
|
71674
|
+
import { homedir as homedir30 } from "node:os";
|
|
71675
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
71456
71676
|
|
|
71457
71677
|
class NodeEnvError extends Error {
|
|
71458
71678
|
stderr;
|
|
@@ -71474,7 +71694,7 @@ var LOCKFILES_FOR = {
|
|
|
71474
71694
|
npm: ["package-lock.json"]
|
|
71475
71695
|
};
|
|
71476
71696
|
function defaultNodeCacheRoot() {
|
|
71477
|
-
return
|
|
71697
|
+
return join52(homedir30(), ".switchroom", "deps", "node");
|
|
71478
71698
|
}
|
|
71479
71699
|
function hashDepInputs(packageJsonPath) {
|
|
71480
71700
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -71483,8 +71703,8 @@ function hashDepInputs(packageJsonPath) {
|
|
|
71483
71703
|
`);
|
|
71484
71704
|
hasher.update(readFileSync50(packageJsonPath));
|
|
71485
71705
|
for (const lockName of ALL_LOCKFILES) {
|
|
71486
|
-
const lockPath =
|
|
71487
|
-
if (
|
|
71706
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71707
|
+
if (existsSync56(lockPath)) {
|
|
71488
71708
|
hasher.update(`
|
|
71489
71709
|
`);
|
|
71490
71710
|
hasher.update(lockName);
|
|
@@ -71499,16 +71719,16 @@ function ensureNodeEnv(opts) {
|
|
|
71499
71719
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
71500
71720
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
71501
71721
|
const installer = opts.installer ?? "bun";
|
|
71502
|
-
if (!
|
|
71722
|
+
if (!existsSync56(packageJsonPath)) {
|
|
71503
71723
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
71504
71724
|
}
|
|
71505
71725
|
const sourceDir = dirname16(packageJsonPath);
|
|
71506
|
-
const envDir =
|
|
71507
|
-
const stampPath =
|
|
71508
|
-
const nodeModulesDir =
|
|
71509
|
-
const binDir =
|
|
71726
|
+
const envDir = join52(cacheRoot, skillName);
|
|
71727
|
+
const stampPath = join52(envDir, ".package.sha256");
|
|
71728
|
+
const nodeModulesDir = join52(envDir, "node_modules");
|
|
71729
|
+
const binDir = join52(nodeModulesDir, ".bin");
|
|
71510
71730
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
71511
|
-
if (!force &&
|
|
71731
|
+
if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
|
|
71512
71732
|
const existingHash = readFileSync50(stampPath, "utf8").trim();
|
|
71513
71733
|
if (existingHash === targetHash) {
|
|
71514
71734
|
return {
|
|
@@ -71520,26 +71740,26 @@ function ensureNodeEnv(opts) {
|
|
|
71520
71740
|
};
|
|
71521
71741
|
}
|
|
71522
71742
|
}
|
|
71523
|
-
if (
|
|
71743
|
+
if (existsSync56(envDir)) {
|
|
71524
71744
|
rmSync14(envDir, { recursive: true, force: true });
|
|
71525
71745
|
}
|
|
71526
71746
|
mkdirSync31(envDir, { recursive: true });
|
|
71527
|
-
copyFileSync9(packageJsonPath,
|
|
71747
|
+
copyFileSync9(packageJsonPath, join52(envDir, "package.json"));
|
|
71528
71748
|
let copiedLockfile = false;
|
|
71529
71749
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
71530
|
-
const lockPath =
|
|
71531
|
-
if (
|
|
71532
|
-
copyFileSync9(lockPath,
|
|
71750
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71751
|
+
if (existsSync56(lockPath)) {
|
|
71752
|
+
copyFileSync9(lockPath, join52(envDir, lockName));
|
|
71533
71753
|
copiedLockfile = true;
|
|
71534
71754
|
}
|
|
71535
71755
|
}
|
|
71536
71756
|
try {
|
|
71537
71757
|
if (installer === "bun") {
|
|
71538
71758
|
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
71539
|
-
|
|
71759
|
+
execFileSync16("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
71540
71760
|
} else {
|
|
71541
71761
|
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
71542
|
-
|
|
71762
|
+
execFileSync16("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
71543
71763
|
}
|
|
71544
71764
|
} catch (err) {
|
|
71545
71765
|
const e = err;
|
|
@@ -71558,28 +71778,28 @@ function ensureNodeEnv(opts) {
|
|
|
71558
71778
|
|
|
71559
71779
|
// src/cli/deps.ts
|
|
71560
71780
|
function builtinSkillsRoot() {
|
|
71561
|
-
return resolve35(
|
|
71781
|
+
return resolve35(homedir31(), ".switchroom/skills/_bundled");
|
|
71562
71782
|
}
|
|
71563
71783
|
function registerDepsCommand(program3) {
|
|
71564
71784
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
71565
71785
|
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
71566
71786
|
const skillsRoot = builtinSkillsRoot();
|
|
71567
|
-
if (!
|
|
71787
|
+
if (!existsSync57(skillsRoot)) {
|
|
71568
71788
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
71569
71789
|
process.exit(1);
|
|
71570
71790
|
}
|
|
71571
|
-
const skillDir =
|
|
71572
|
-
if (!
|
|
71791
|
+
const skillDir = join53(skillsRoot, skill);
|
|
71792
|
+
if (!existsSync57(skillDir)) {
|
|
71573
71793
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
71574
71794
|
process.exit(1);
|
|
71575
71795
|
}
|
|
71576
|
-
const requirementsPath =
|
|
71577
|
-
const packageJsonPath =
|
|
71578
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
71579
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
71796
|
+
const requirementsPath = join53(skillDir, "requirements.txt");
|
|
71797
|
+
const packageJsonPath = join53(skillDir, "package.json");
|
|
71798
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync57(requirementsPath));
|
|
71799
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync57(packageJsonPath));
|
|
71580
71800
|
let did = 0;
|
|
71581
71801
|
if (wantPython) {
|
|
71582
|
-
if (!
|
|
71802
|
+
if (!existsSync57(requirementsPath)) {
|
|
71583
71803
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
71584
71804
|
process.exit(1);
|
|
71585
71805
|
}
|
|
@@ -71603,7 +71823,7 @@ function registerDepsCommand(program3) {
|
|
|
71603
71823
|
}
|
|
71604
71824
|
}
|
|
71605
71825
|
if (wantNode) {
|
|
71606
|
-
if (!
|
|
71826
|
+
if (!existsSync57(packageJsonPath)) {
|
|
71607
71827
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
71608
71828
|
process.exit(1);
|
|
71609
71829
|
}
|
|
@@ -71636,7 +71856,7 @@ function registerDepsCommand(program3) {
|
|
|
71636
71856
|
// src/cli/workspace.ts
|
|
71637
71857
|
init_helpers();
|
|
71638
71858
|
init_loader();
|
|
71639
|
-
import { existsSync as
|
|
71859
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
71640
71860
|
import { resolve as resolve36, sep as sep3 } from "node:path";
|
|
71641
71861
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
71642
71862
|
|
|
@@ -72413,7 +72633,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72413
72633
|
if (!dir)
|
|
72414
72634
|
return;
|
|
72415
72635
|
const gitDir = resolve36(dir, ".git");
|
|
72416
|
-
if (!
|
|
72636
|
+
if (!existsSync58(gitDir)) {
|
|
72417
72637
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
72418
72638
|
`);
|
|
72419
72639
|
return;
|
|
@@ -72467,7 +72687,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72467
72687
|
if (!dir)
|
|
72468
72688
|
return;
|
|
72469
72689
|
const gitDir = resolve36(dir, ".git");
|
|
72470
|
-
if (!
|
|
72690
|
+
if (!existsSync58(gitDir)) {
|
|
72471
72691
|
process.stdout.write(`Workspace is not a git repository.
|
|
72472
72692
|
`);
|
|
72473
72693
|
return;
|
|
@@ -72492,7 +72712,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
72492
72712
|
const agentsDir = resolveAgentsDir(config);
|
|
72493
72713
|
const agentDir = resolve36(agentsDir, agentName);
|
|
72494
72714
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
72495
|
-
if (!
|
|
72715
|
+
if (!existsSync58(dir)) {
|
|
72496
72716
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
72497
72717
|
`);
|
|
72498
72718
|
return;
|
|
@@ -72528,8 +72748,8 @@ function safeParseInt(value, fallback) {
|
|
|
72528
72748
|
init_helpers();
|
|
72529
72749
|
init_loader();
|
|
72530
72750
|
init_merge();
|
|
72531
|
-
import { copyFileSync as copyFileSync10, existsSync as
|
|
72532
|
-
import { join as
|
|
72751
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync59, readFileSync as readFileSync51, writeFileSync as writeFileSync28 } from "node:fs";
|
|
72752
|
+
import { join as join54, resolve as resolve37 } from "node:path";
|
|
72533
72753
|
init_schema();
|
|
72534
72754
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
72535
72755
|
const config = getConfig(program3);
|
|
@@ -72544,7 +72764,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72544
72764
|
const agentsDir = resolveAgentsDir(config);
|
|
72545
72765
|
const agentDir = resolve37(agentsDir, agentName);
|
|
72546
72766
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72547
|
-
if (!
|
|
72767
|
+
if (!existsSync59(workspaceDir)) {
|
|
72548
72768
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
72549
72769
|
process.exit(1);
|
|
72550
72770
|
}
|
|
@@ -72553,7 +72773,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72553
72773
|
profileName,
|
|
72554
72774
|
profilePath,
|
|
72555
72775
|
workspaceDir,
|
|
72556
|
-
soulPath:
|
|
72776
|
+
soulPath: join54(workspaceDir, "SOUL.md"),
|
|
72557
72777
|
soul: merged.soul
|
|
72558
72778
|
};
|
|
72559
72779
|
}
|
|
@@ -72570,7 +72790,7 @@ function registerSoulCommand(program3) {
|
|
|
72570
72790
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
72571
72791
|
if (!t)
|
|
72572
72792
|
return;
|
|
72573
|
-
if (!
|
|
72793
|
+
if (!existsSync59(t.soulPath)) {
|
|
72574
72794
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
72575
72795
|
process.exit(1);
|
|
72576
72796
|
}
|
|
@@ -72585,7 +72805,7 @@ function registerSoulCommand(program3) {
|
|
|
72585
72805
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
72586
72806
|
process.exit(1);
|
|
72587
72807
|
}
|
|
72588
|
-
const exists =
|
|
72808
|
+
const exists = existsSync59(t.soulPath);
|
|
72589
72809
|
if (exists && !opts.yes) {
|
|
72590
72810
|
if (!isInteractive()) {
|
|
72591
72811
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -72600,7 +72820,7 @@ function registerSoulCommand(program3) {
|
|
|
72600
72820
|
let backupPath;
|
|
72601
72821
|
if (exists) {
|
|
72602
72822
|
backupPath = `${t.soulPath}.bak`;
|
|
72603
|
-
if (
|
|
72823
|
+
if (existsSync59(backupPath)) {
|
|
72604
72824
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
72605
72825
|
}
|
|
72606
72826
|
copyFileSync10(t.soulPath, backupPath);
|
|
@@ -72619,8 +72839,8 @@ function registerSoulCommand(program3) {
|
|
|
72619
72839
|
// src/cli/debug.ts
|
|
72620
72840
|
init_helpers();
|
|
72621
72841
|
init_loader();
|
|
72622
|
-
import { existsSync as
|
|
72623
|
-
import { resolve as resolve38, join as
|
|
72842
|
+
import { existsSync as existsSync60, readFileSync as readFileSync52, readdirSync as readdirSync21, statSync as statSync26 } from "node:fs";
|
|
72843
|
+
import { resolve as resolve38, join as join55 } from "node:path";
|
|
72624
72844
|
import { createHash as createHash12 } from "node:crypto";
|
|
72625
72845
|
init_merge();
|
|
72626
72846
|
init_hindsight();
|
|
@@ -72634,8 +72854,8 @@ function sha256(content) {
|
|
|
72634
72854
|
return createHash12("sha256").update(content).digest("hex").slice(0, 16);
|
|
72635
72855
|
}
|
|
72636
72856
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
72637
|
-
const projectsDir =
|
|
72638
|
-
if (!
|
|
72857
|
+
const projectsDir = join55(claudeConfigDir, "projects");
|
|
72858
|
+
if (!existsSync60(projectsDir))
|
|
72639
72859
|
return;
|
|
72640
72860
|
try {
|
|
72641
72861
|
const entries = readdirSync21(projectsDir, { withFileTypes: true });
|
|
@@ -72643,11 +72863,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
72643
72863
|
for (const entry of entries) {
|
|
72644
72864
|
if (!entry.isDirectory())
|
|
72645
72865
|
continue;
|
|
72646
|
-
const projectPath =
|
|
72647
|
-
const transcriptPath =
|
|
72648
|
-
if (!
|
|
72866
|
+
const projectPath = join55(projectsDir, entry.name);
|
|
72867
|
+
const transcriptPath = join55(projectPath, "transcript.jsonl");
|
|
72868
|
+
if (!existsSync60(transcriptPath))
|
|
72649
72869
|
continue;
|
|
72650
|
-
const stat3 =
|
|
72870
|
+
const stat3 = statSync26(transcriptPath);
|
|
72651
72871
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
72652
72872
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
72653
72873
|
}
|
|
@@ -72708,16 +72928,16 @@ function registerDebugCommand(program3) {
|
|
|
72708
72928
|
}
|
|
72709
72929
|
const agentsDir = resolveAgentsDir(config);
|
|
72710
72930
|
const agentDir = resolve38(agentsDir, agentName);
|
|
72711
|
-
if (!
|
|
72931
|
+
if (!existsSync60(agentDir)) {
|
|
72712
72932
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
72713
72933
|
process.exit(1);
|
|
72714
72934
|
}
|
|
72715
72935
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72716
|
-
const claudeConfigDir =
|
|
72717
|
-
const claudeMdPath =
|
|
72718
|
-
const soulMdPath =
|
|
72719
|
-
const workspaceSoulMdPath =
|
|
72720
|
-
const handoffPath =
|
|
72936
|
+
const claudeConfigDir = join55(agentDir, ".claude");
|
|
72937
|
+
const claudeMdPath = join55(agentDir, "CLAUDE.md");
|
|
72938
|
+
const soulMdPath = join55(agentDir, "SOUL.md");
|
|
72939
|
+
const workspaceSoulMdPath = join55(workspaceDir, "SOUL.md");
|
|
72940
|
+
const handoffPath = join55(agentDir, ".handoff.md");
|
|
72721
72941
|
const lastN = parseInt(opts.last, 10);
|
|
72722
72942
|
if (isNaN(lastN) || lastN < 1) {
|
|
72723
72943
|
console.error("--last must be a positive integer");
|
|
@@ -72763,7 +72983,7 @@ function registerDebugCommand(program3) {
|
|
|
72763
72983
|
}
|
|
72764
72984
|
console.log(`=== Append System Prompt (per-session) ===
|
|
72765
72985
|
`);
|
|
72766
|
-
const handoffContent =
|
|
72986
|
+
const handoffContent = existsSync60(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
|
|
72767
72987
|
if (handoffContent.trim().length > 0) {
|
|
72768
72988
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
72769
72989
|
console.log(handoffContent);
|
|
@@ -72774,7 +72994,7 @@ function registerDebugCommand(program3) {
|
|
|
72774
72994
|
}
|
|
72775
72995
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
72776
72996
|
`);
|
|
72777
|
-
const claudeMdContent =
|
|
72997
|
+
const claudeMdContent = existsSync60(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
|
|
72778
72998
|
if (claudeMdContent.trim().length > 0) {
|
|
72779
72999
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
72780
73000
|
console.log(claudeMdContent);
|
|
@@ -72785,7 +73005,7 @@ function registerDebugCommand(program3) {
|
|
|
72785
73005
|
}
|
|
72786
73006
|
console.log(`=== Persona (SOUL.md) ===
|
|
72787
73007
|
`);
|
|
72788
|
-
const soulMdContent =
|
|
73008
|
+
const soulMdContent = existsSync60(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync60(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
|
|
72789
73009
|
if (soulMdContent.trim().length > 0) {
|
|
72790
73010
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
72791
73011
|
console.log(soulMdContent);
|
|
@@ -72865,10 +73085,10 @@ function registerDebugCommand(program3) {
|
|
|
72865
73085
|
init_source();
|
|
72866
73086
|
|
|
72867
73087
|
// src/worktree/claim.ts
|
|
72868
|
-
import { execFileSync as
|
|
72869
|
-
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as
|
|
72870
|
-
import { join as
|
|
72871
|
-
import { homedir as
|
|
73088
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
73089
|
+
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync62, unlinkSync as unlinkSync13 } from "node:fs";
|
|
73090
|
+
import { join as join57, resolve as resolve40 } from "node:path";
|
|
73091
|
+
import { homedir as homedir33 } from "node:os";
|
|
72872
73092
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
72873
73093
|
|
|
72874
73094
|
// src/worktree/registry.ts
|
|
@@ -72878,16 +73098,16 @@ import {
|
|
|
72878
73098
|
readFileSync as readFileSync53,
|
|
72879
73099
|
readdirSync as readdirSync22,
|
|
72880
73100
|
unlinkSync as unlinkSync12,
|
|
72881
|
-
existsSync as
|
|
73101
|
+
existsSync as existsSync61,
|
|
72882
73102
|
renameSync as renameSync12
|
|
72883
73103
|
} from "node:fs";
|
|
72884
|
-
import { join as
|
|
72885
|
-
import { homedir as
|
|
73104
|
+
import { join as join56, resolve as resolve39 } from "node:path";
|
|
73105
|
+
import { homedir as homedir32 } from "node:os";
|
|
72886
73106
|
function registryDir() {
|
|
72887
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
73107
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join56(homedir32(), ".switchroom", "worktrees"));
|
|
72888
73108
|
}
|
|
72889
73109
|
function recordPath(id) {
|
|
72890
|
-
return
|
|
73110
|
+
return join56(registryDir(), `${id}.json`);
|
|
72891
73111
|
}
|
|
72892
73112
|
function ensureDir2() {
|
|
72893
73113
|
mkdirSync32(registryDir(), { recursive: true });
|
|
@@ -72938,7 +73158,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72938
73158
|
const lockDir = registryDir();
|
|
72939
73159
|
mkdirSync33(lockDir, { recursive: true });
|
|
72940
73160
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
72941
|
-
const lockPath =
|
|
73161
|
+
const lockPath = join57(lockDir, `.lock-${lockName}`);
|
|
72942
73162
|
const deadline = Date.now() + 5000;
|
|
72943
73163
|
let fd = null;
|
|
72944
73164
|
while (fd === null) {
|
|
@@ -72965,7 +73185,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72965
73185
|
}
|
|
72966
73186
|
var DEFAULT_CONCURRENCY = 5;
|
|
72967
73187
|
function worktreesBaseDir() {
|
|
72968
|
-
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
73188
|
+
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join57(homedir33(), ".switchroom", "worktree-checkouts"));
|
|
72969
73189
|
}
|
|
72970
73190
|
function shortId() {
|
|
72971
73191
|
return randomBytes12(4).toString("hex");
|
|
@@ -72987,12 +73207,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72987
73207
|
}
|
|
72988
73208
|
function expandHome(p) {
|
|
72989
73209
|
if (p.startsWith("~/"))
|
|
72990
|
-
return
|
|
73210
|
+
return join57(homedir33(), p.slice(2));
|
|
72991
73211
|
return p;
|
|
72992
73212
|
}
|
|
72993
73213
|
async function claimWorktree(input, codeRepos) {
|
|
72994
73214
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
72995
|
-
if (!
|
|
73215
|
+
if (!existsSync62(repoPath)) {
|
|
72996
73216
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
72997
73217
|
}
|
|
72998
73218
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -73015,7 +73235,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73015
73235
|
branch = `task/${taskSuffix}-${id}`;
|
|
73016
73236
|
const baseDir = worktreesBaseDir();
|
|
73017
73237
|
mkdirSync33(baseDir, { recursive: true });
|
|
73018
|
-
worktreePath =
|
|
73238
|
+
worktreePath = join57(baseDir, `${id}-${taskSuffix}`);
|
|
73019
73239
|
const now = new Date().toISOString();
|
|
73020
73240
|
const record2 = {
|
|
73021
73241
|
id,
|
|
@@ -73032,7 +73252,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73032
73252
|
releaseLock();
|
|
73033
73253
|
}
|
|
73034
73254
|
try {
|
|
73035
|
-
|
|
73255
|
+
execFileSync17("git", ["worktree", "add", "-b", branch, worktreePath], {
|
|
73036
73256
|
cwd: repoPath,
|
|
73037
73257
|
stdio: "pipe"
|
|
73038
73258
|
});
|
|
@@ -73045,8 +73265,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73045
73265
|
}
|
|
73046
73266
|
|
|
73047
73267
|
// src/worktree/release.ts
|
|
73048
|
-
import { execFileSync as
|
|
73049
|
-
import { existsSync as
|
|
73268
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
73269
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
73050
73270
|
function releaseWorktree(input) {
|
|
73051
73271
|
const { id } = input;
|
|
73052
73272
|
const record2 = readRecord(id);
|
|
@@ -73054,9 +73274,9 @@ function releaseWorktree(input) {
|
|
|
73054
73274
|
return { released: true };
|
|
73055
73275
|
}
|
|
73056
73276
|
let gitSuccess = true;
|
|
73057
|
-
if (
|
|
73277
|
+
if (existsSync63(record2.path)) {
|
|
73058
73278
|
try {
|
|
73059
|
-
|
|
73279
|
+
execFileSync18("git", ["worktree", "remove", "--force", record2.path], {
|
|
73060
73280
|
cwd: record2.repo,
|
|
73061
73281
|
stdio: "pipe"
|
|
73062
73282
|
});
|
|
@@ -73092,16 +73312,16 @@ function listWorktrees() {
|
|
|
73092
73312
|
}
|
|
73093
73313
|
|
|
73094
73314
|
// src/worktree/reaper.ts
|
|
73095
|
-
import { execFileSync as
|
|
73096
|
-
import { existsSync as
|
|
73315
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73316
|
+
import { existsSync as existsSync64 } from "node:fs";
|
|
73097
73317
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
73098
73318
|
function isPathInUse(path7) {
|
|
73099
73319
|
try {
|
|
73100
|
-
|
|
73320
|
+
execFileSync19("fuser", [path7], { stdio: "pipe" });
|
|
73101
73321
|
return true;
|
|
73102
73322
|
} catch {}
|
|
73103
73323
|
try {
|
|
73104
|
-
const out =
|
|
73324
|
+
const out = execFileSync19("lsof", ["-t", path7], {
|
|
73105
73325
|
stdio: ["ignore", "pipe", "ignore"]
|
|
73106
73326
|
}).toString().trim();
|
|
73107
73327
|
if (out.length > 0)
|
|
@@ -73111,7 +73331,7 @@ function isPathInUse(path7) {
|
|
|
73111
73331
|
}
|
|
73112
73332
|
function hasUncommittedChanges(repoPath, worktreePath) {
|
|
73113
73333
|
try {
|
|
73114
|
-
const out =
|
|
73334
|
+
const out = execFileSync19("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
|
|
73115
73335
|
return out.trim().length > 0;
|
|
73116
73336
|
} catch {
|
|
73117
73337
|
return false;
|
|
@@ -73120,12 +73340,12 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
73120
73340
|
function reapRecord(record2) {
|
|
73121
73341
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
73122
73342
|
let warning = null;
|
|
73123
|
-
if (
|
|
73343
|
+
if (existsSync64(path7)) {
|
|
73124
73344
|
if (hasUncommittedChanges(repo, path7)) {
|
|
73125
73345
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
73126
73346
|
}
|
|
73127
73347
|
try {
|
|
73128
|
-
|
|
73348
|
+
execFileSync19("git", ["worktree", "remove", "--force", path7], {
|
|
73129
73349
|
cwd: repo,
|
|
73130
73350
|
stdio: "pipe"
|
|
73131
73351
|
});
|
|
@@ -73141,7 +73361,7 @@ function runReaper(nowMs) {
|
|
|
73141
73361
|
const warnings = [];
|
|
73142
73362
|
for (const record2 of records) {
|
|
73143
73363
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
73144
|
-
const worktreeExists =
|
|
73364
|
+
const worktreeExists = existsSync64(record2.path);
|
|
73145
73365
|
if (!worktreeExists) {
|
|
73146
73366
|
deleteRecord(record2.id);
|
|
73147
73367
|
reaped.push(record2.id);
|
|
@@ -73270,7 +73490,7 @@ import {
|
|
|
73270
73490
|
rmSync as rmSync15,
|
|
73271
73491
|
writeFileSync as writeFileSync30
|
|
73272
73492
|
} from "node:fs";
|
|
73273
|
-
import { join as
|
|
73493
|
+
import { join as join58 } from "node:path";
|
|
73274
73494
|
function encodeCredentialsFilename(email) {
|
|
73275
73495
|
const SAFE = new Set([
|
|
73276
73496
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -73460,16 +73680,16 @@ function resolveCredentialsDir(env2) {
|
|
|
73460
73680
|
if (explicit && explicit.length > 0)
|
|
73461
73681
|
return explicit;
|
|
73462
73682
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
73463
|
-
return
|
|
73683
|
+
return join58(stateBase, "google-workspace-mcp", "credentials");
|
|
73464
73684
|
}
|
|
73465
73685
|
function writeSeedFile(dir, email, seed) {
|
|
73466
73686
|
mkdirSync34(dir, { recursive: true, mode: 448 });
|
|
73467
73687
|
chmodSync9(dir, 448);
|
|
73468
73688
|
for (const name of readdirSync23(dir)) {
|
|
73469
|
-
rmSync15(
|
|
73689
|
+
rmSync15(join58(dir, name), { force: true, recursive: true });
|
|
73470
73690
|
}
|
|
73471
73691
|
const filename = encodeCredentialsFilename(email);
|
|
73472
|
-
const filePath =
|
|
73692
|
+
const filePath = join58(dir, filename);
|
|
73473
73693
|
writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
|
|
73474
73694
|
chmodSync9(filePath, 384);
|
|
73475
73695
|
return filePath;
|
|
@@ -73627,7 +73847,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
73627
73847
|
|
|
73628
73848
|
// src/cli/apply.ts
|
|
73629
73849
|
init_source();
|
|
73630
|
-
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as
|
|
73850
|
+
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
|
|
73631
73851
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
73632
73852
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
73633
73853
|
import readline from "node:readline";
|
|
@@ -73990,16 +74210,16 @@ agents:
|
|
|
73990
74210
|
|
|
73991
74211
|
// src/cli/apply.ts
|
|
73992
74212
|
init_resolver();
|
|
73993
|
-
import { dirname as dirname19, join as
|
|
73994
|
-
import { homedir as
|
|
73995
|
-
import { execFileSync as
|
|
74213
|
+
import { dirname as dirname19, join as join62, resolve as resolve42 } from "node:path";
|
|
74214
|
+
import { homedir as homedir35 } from "node:os";
|
|
74215
|
+
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
73996
74216
|
init_vault();
|
|
73997
74217
|
init_loader();
|
|
73998
74218
|
init_loader();
|
|
73999
74219
|
|
|
74000
74220
|
// src/cli/update-prompt-hook.ts
|
|
74001
|
-
import { existsSync as
|
|
74002
|
-
import { join as
|
|
74221
|
+
import { existsSync as existsSync65, readFileSync as readFileSync54, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync35 } from "node:fs";
|
|
74222
|
+
import { join as join59 } from "node:path";
|
|
74003
74223
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
74004
74224
|
function updatePromptHookScript() {
|
|
74005
74225
|
return `#!/bin/bash
|
|
@@ -74065,12 +74285,12 @@ exit 0
|
|
|
74065
74285
|
`;
|
|
74066
74286
|
}
|
|
74067
74287
|
function installUpdatePromptHook(agentDir) {
|
|
74068
|
-
const hooksDir =
|
|
74288
|
+
const hooksDir = join59(agentDir, ".claude", "hooks");
|
|
74069
74289
|
mkdirSync35(hooksDir, { recursive: true });
|
|
74070
|
-
const scriptPath =
|
|
74290
|
+
const scriptPath = join59(hooksDir, HOOK_FILENAME);
|
|
74071
74291
|
const desired = updatePromptHookScript();
|
|
74072
74292
|
let installed = false;
|
|
74073
|
-
const existing =
|
|
74293
|
+
const existing = existsSync65(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
|
|
74074
74294
|
if (existing !== desired) {
|
|
74075
74295
|
writeFileSync31(scriptPath, desired, { mode: 493 });
|
|
74076
74296
|
chmodSync10(scriptPath, 493);
|
|
@@ -74080,8 +74300,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
74080
74300
|
chmodSync10(scriptPath, 493);
|
|
74081
74301
|
} catch {}
|
|
74082
74302
|
}
|
|
74083
|
-
const settingsPath =
|
|
74084
|
-
if (!
|
|
74303
|
+
const settingsPath = join59(agentDir, ".claude", "settings.json");
|
|
74304
|
+
if (!existsSync65(settingsPath)) {
|
|
74085
74305
|
return { scriptPath, settingsPath, installed };
|
|
74086
74306
|
}
|
|
74087
74307
|
const raw = readFileSync54(settingsPath, "utf-8");
|
|
@@ -74200,13 +74420,13 @@ function detectInstallType() {
|
|
|
74200
74420
|
// src/cli/operator-uid.ts
|
|
74201
74421
|
import {
|
|
74202
74422
|
chownSync as chownSync3,
|
|
74203
|
-
existsSync as
|
|
74423
|
+
existsSync as existsSync67,
|
|
74204
74424
|
lstatSync as lstatSync7,
|
|
74205
74425
|
readdirSync as readdirSync24,
|
|
74206
74426
|
realpathSync as realpathSync6,
|
|
74207
|
-
statSync as
|
|
74427
|
+
statSync as statSync27
|
|
74208
74428
|
} from "node:fs";
|
|
74209
|
-
import { join as
|
|
74429
|
+
import { join as join61 } from "node:path";
|
|
74210
74430
|
function resolveOperatorUid() {
|
|
74211
74431
|
const sudoUid = process.env.SUDO_UID;
|
|
74212
74432
|
if (sudoUid !== undefined) {
|
|
@@ -74222,19 +74442,19 @@ function resolveOperatorUid() {
|
|
|
74222
74442
|
return;
|
|
74223
74443
|
}
|
|
74224
74444
|
function operatorOwnedPaths(home2) {
|
|
74225
|
-
const root =
|
|
74445
|
+
const root = join61(home2, ".switchroom");
|
|
74226
74446
|
return [
|
|
74227
|
-
|
|
74228
|
-
|
|
74229
|
-
|
|
74230
|
-
|
|
74231
|
-
|
|
74232
|
-
|
|
74447
|
+
join61(root, "vault"),
|
|
74448
|
+
join61(root, "vault-auto-unlock"),
|
|
74449
|
+
join61(root, "vault-audit.log"),
|
|
74450
|
+
join61(root, "host-control-audit.log"),
|
|
74451
|
+
join61(root, "accounts"),
|
|
74452
|
+
join61(root, "compose")
|
|
74233
74453
|
];
|
|
74234
74454
|
}
|
|
74235
74455
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
74236
74456
|
const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
|
|
74237
|
-
const exists = deps.exists ?? ((p) =>
|
|
74457
|
+
const exists = deps.exists ?? ((p) => existsSync67(p));
|
|
74238
74458
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
74239
74459
|
try {
|
|
74240
74460
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -74244,7 +74464,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74244
74464
|
});
|
|
74245
74465
|
const isDir = deps.isDir ?? ((p) => {
|
|
74246
74466
|
try {
|
|
74247
|
-
return
|
|
74467
|
+
return statSync27(p).isDirectory();
|
|
74248
74468
|
} catch {
|
|
74249
74469
|
return false;
|
|
74250
74470
|
}
|
|
@@ -74278,7 +74498,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74278
74498
|
} catch {}
|
|
74279
74499
|
if (isDir(target)) {
|
|
74280
74500
|
for (const entry of readdir2(target)) {
|
|
74281
|
-
visit(
|
|
74501
|
+
visit(join61(target, entry));
|
|
74282
74502
|
}
|
|
74283
74503
|
}
|
|
74284
74504
|
};
|
|
@@ -74292,17 +74512,17 @@ var EMBEDDED_EXAMPLES = {
|
|
|
74292
74512
|
switchroom: switchroom_default,
|
|
74293
74513
|
minimal: minimal_default
|
|
74294
74514
|
};
|
|
74295
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
74515
|
+
var DEFAULT_COMPOSE_PATH2 = join62(homedir35(), ".switchroom", "compose", "docker-compose.yml");
|
|
74296
74516
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
74297
74517
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
74298
74518
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
74299
74519
|
if (isCustomPath && ctx.customVaultPath) {
|
|
74300
74520
|
return dirname19(ctx.customVaultPath);
|
|
74301
74521
|
}
|
|
74302
|
-
return
|
|
74522
|
+
return join62(homeDir, ".switchroom", "vault");
|
|
74303
74523
|
}
|
|
74304
74524
|
function inspectVaultBindMountDir(vaultDir) {
|
|
74305
|
-
if (!
|
|
74525
|
+
if (!existsSync68(vaultDir))
|
|
74306
74526
|
return { kind: "missing" };
|
|
74307
74527
|
const entries = readdirSync25(vaultDir);
|
|
74308
74528
|
const unknown = [];
|
|
@@ -74328,38 +74548,42 @@ function hasVaultRefs(value) {
|
|
|
74328
74548
|
return false;
|
|
74329
74549
|
}
|
|
74330
74550
|
async function ensureHostMountSources(config) {
|
|
74331
|
-
const home2 =
|
|
74551
|
+
const home2 = homedir35();
|
|
74332
74552
|
const dirs = [
|
|
74333
|
-
|
|
74334
|
-
|
|
74335
|
-
|
|
74336
|
-
|
|
74337
|
-
|
|
74553
|
+
join62(home2, ".switchroom", "approvals"),
|
|
74554
|
+
join62(home2, ".switchroom", "scheduler"),
|
|
74555
|
+
join62(home2, ".switchroom", "logs"),
|
|
74556
|
+
join62(home2, ".switchroom", "compose"),
|
|
74557
|
+
join62(home2, ".switchroom", "broker-operator")
|
|
74338
74558
|
];
|
|
74339
74559
|
for (const name of Object.keys(config.agents)) {
|
|
74340
|
-
dirs.push(
|
|
74341
|
-
dirs.push(
|
|
74342
|
-
dirs.push(
|
|
74560
|
+
dirs.push(join62(home2, ".switchroom", "agents", name));
|
|
74561
|
+
dirs.push(join62(home2, ".switchroom", "logs", name));
|
|
74562
|
+
dirs.push(join62(home2, ".claude", "projects", name));
|
|
74343
74563
|
}
|
|
74344
74564
|
for (const dir of dirs) {
|
|
74345
74565
|
await mkdir(dir, { recursive: true });
|
|
74346
74566
|
}
|
|
74347
|
-
const autoUnlockPath =
|
|
74348
|
-
if (!
|
|
74567
|
+
const autoUnlockPath = join62(home2, ".switchroom", "vault-auto-unlock");
|
|
74568
|
+
if (!existsSync68(autoUnlockPath)) {
|
|
74349
74569
|
writeFileSync32(autoUnlockPath, "", { mode: 384 });
|
|
74350
74570
|
}
|
|
74351
|
-
const auditLogPath =
|
|
74352
|
-
if (!
|
|
74571
|
+
const auditLogPath = join62(home2, ".switchroom", "vault-audit.log");
|
|
74572
|
+
if (!existsSync68(auditLogPath)) {
|
|
74353
74573
|
writeFileSync32(auditLogPath, "", { mode: 420 });
|
|
74354
74574
|
}
|
|
74355
|
-
const
|
|
74356
|
-
if (!
|
|
74575
|
+
const grantsDbPath = join62(home2, ".switchroom", "vault-grants.db");
|
|
74576
|
+
if (!existsSync68(grantsDbPath)) {
|
|
74577
|
+
writeFileSync32(grantsDbPath, "", { mode: 384 });
|
|
74578
|
+
}
|
|
74579
|
+
const hostdAuditLogPath = join62(home2, ".switchroom", "host-control-audit.log");
|
|
74580
|
+
if (!existsSync68(hostdAuditLogPath)) {
|
|
74357
74581
|
writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
|
|
74358
74582
|
}
|
|
74359
74583
|
}
|
|
74360
74584
|
function detectComposeV2() {
|
|
74361
74585
|
try {
|
|
74362
|
-
const out =
|
|
74586
|
+
const out = execFileSync20("docker", ["compose", "version"], {
|
|
74363
74587
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74364
74588
|
encoding: "utf8"
|
|
74365
74589
|
});
|
|
@@ -74374,7 +74598,7 @@ ${out.trim()}`;
|
|
|
74374
74598
|
}
|
|
74375
74599
|
function runApplyPreflight(config, opts = {}) {
|
|
74376
74600
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
74377
|
-
if (hasVaultRefs(config) && !
|
|
74601
|
+
if (hasVaultRefs(config) && !existsSync68(vaultPath)) {
|
|
74378
74602
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
74379
74603
|
}
|
|
74380
74604
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -74385,7 +74609,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
74385
74609
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
74386
74610
|
}
|
|
74387
74611
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
74388
|
-
if (!
|
|
74612
|
+
if (!existsSync68(vaultPath))
|
|
74389
74613
|
return;
|
|
74390
74614
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
74391
74615
|
if (!passphrase)
|
|
@@ -74424,10 +74648,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
74424
74648
|
`));
|
|
74425
74649
|
}
|
|
74426
74650
|
}
|
|
74427
|
-
function writeInstallTypeCache(homeDir =
|
|
74651
|
+
function writeInstallTypeCache(homeDir = homedir35()) {
|
|
74428
74652
|
const ctx = detectInstallType();
|
|
74429
|
-
const dir =
|
|
74430
|
-
const out =
|
|
74653
|
+
const dir = join62(homeDir, ".switchroom");
|
|
74654
|
+
const out = join62(dir, "install-type.json");
|
|
74431
74655
|
const tmp = `${out}.tmp`;
|
|
74432
74656
|
mkdirSync36(dir, { recursive: true });
|
|
74433
74657
|
const payload = {
|
|
@@ -74476,14 +74700,14 @@ Applying switchroom config...
|
|
|
74476
74700
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
74477
74701
|
`));
|
|
74478
74702
|
try {
|
|
74479
|
-
installUpdatePromptHook(
|
|
74703
|
+
installUpdatePromptHook(join62(agentsDir, name));
|
|
74480
74704
|
} catch (hookErr) {
|
|
74481
74705
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
74482
74706
|
`));
|
|
74483
74707
|
}
|
|
74484
74708
|
try {
|
|
74485
74709
|
const uid = allocateAgentUid(name);
|
|
74486
|
-
alignAgentUid(name,
|
|
74710
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74487
74711
|
confirm: !options.nonInteractive,
|
|
74488
74712
|
writeOut
|
|
74489
74713
|
});
|
|
@@ -74520,7 +74744,7 @@ Applying switchroom config...
|
|
|
74520
74744
|
for (const name of agentNames) {
|
|
74521
74745
|
try {
|
|
74522
74746
|
const uid = allocateAgentUid(name);
|
|
74523
|
-
alignAgentUid(name,
|
|
74747
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74524
74748
|
confirm: !options.nonInteractive,
|
|
74525
74749
|
writeOut
|
|
74526
74750
|
});
|
|
@@ -74534,7 +74758,7 @@ Applying switchroom config...
|
|
|
74534
74758
|
}
|
|
74535
74759
|
const vaultPathConfigured = config.vault?.path;
|
|
74536
74760
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
74537
|
-
const migrationResult = migrateVaultLayout(
|
|
74761
|
+
const migrationResult = migrateVaultLayout(homedir35(), {
|
|
74538
74762
|
customVaultPath
|
|
74539
74763
|
});
|
|
74540
74764
|
switch (migrationResult.kind) {
|
|
@@ -74560,7 +74784,7 @@ Applying switchroom config...
|
|
|
74560
74784
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
74561
74785
|
process.exit(4);
|
|
74562
74786
|
}
|
|
74563
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
74787
|
+
const postMigrationInspect = inspectVaultLayout(homedir35());
|
|
74564
74788
|
const acceptable = [
|
|
74565
74789
|
"no-vault",
|
|
74566
74790
|
"already-migrated",
|
|
@@ -74575,7 +74799,7 @@ Applying switchroom config...
|
|
|
74575
74799
|
`));
|
|
74576
74800
|
process.exit(5);
|
|
74577
74801
|
}
|
|
74578
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
74802
|
+
const vaultDir = resolveVaultBindMountDir(homedir35(), {
|
|
74579
74803
|
migrationKind: migrationResult.kind,
|
|
74580
74804
|
customVaultPath
|
|
74581
74805
|
});
|
|
@@ -74605,7 +74829,7 @@ Applying switchroom config...
|
|
|
74605
74829
|
imageTag: composeImageTag,
|
|
74606
74830
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
74607
74831
|
buildContext: options.buildContext,
|
|
74608
|
-
homeDir:
|
|
74832
|
+
homeDir: homedir35(),
|
|
74609
74833
|
switchroomConfigPath,
|
|
74610
74834
|
operatorUid
|
|
74611
74835
|
});
|
|
@@ -74625,7 +74849,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
74625
74849
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
74626
74850
|
`));
|
|
74627
74851
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
74628
|
-
const restored = restoreOperatorOwnership(
|
|
74852
|
+
const restored = restoreOperatorOwnership(homedir35(), operatorUid);
|
|
74629
74853
|
if (restored.length > 0) {
|
|
74630
74854
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
74631
74855
|
`));
|
|
@@ -74673,7 +74897,7 @@ function copyExampleConfig2(name) {
|
|
|
74673
74897
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
74674
74898
|
}
|
|
74675
74899
|
const dest = resolve42(process.cwd(), "switchroom.yaml");
|
|
74676
|
-
if (
|
|
74900
|
+
if (existsSync68(dest)) {
|
|
74677
74901
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
74678
74902
|
return;
|
|
74679
74903
|
}
|
|
@@ -74684,7 +74908,7 @@ function copyExampleConfig2(name) {
|
|
|
74684
74908
|
return;
|
|
74685
74909
|
}
|
|
74686
74910
|
const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
74687
|
-
if (!
|
|
74911
|
+
if (!existsSync68(exampleFile)) {
|
|
74688
74912
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
74689
74913
|
}
|
|
74690
74914
|
copyFileSync11(exampleFile, dest);
|
|
@@ -74695,8 +74919,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
74695
74919
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
74696
74920
|
const unwritable = [];
|
|
74697
74921
|
for (const name of targets) {
|
|
74698
|
-
const startSh =
|
|
74699
|
-
if (!
|
|
74922
|
+
const startSh = join62(agentsDir, name, "start.sh");
|
|
74923
|
+
if (!existsSync68(startSh))
|
|
74700
74924
|
continue;
|
|
74701
74925
|
try {
|
|
74702
74926
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -74874,9 +75098,9 @@ function runRedactStdin() {
|
|
|
74874
75098
|
}
|
|
74875
75099
|
|
|
74876
75100
|
// src/cli/status-ask.ts
|
|
74877
|
-
import { readFileSync as readFileSync55, existsSync as
|
|
74878
|
-
import { join as
|
|
74879
|
-
import { homedir as
|
|
75101
|
+
import { readFileSync as readFileSync55, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
|
|
75102
|
+
import { join as join63 } from "node:path";
|
|
75103
|
+
import { homedir as homedir36 } from "node:os";
|
|
74880
75104
|
|
|
74881
75105
|
// src/status-ask/report.ts
|
|
74882
75106
|
function parseJsonl(content) {
|
|
@@ -75197,7 +75421,7 @@ function runReport(opts) {
|
|
|
75197
75421
|
function resolveSources(explicitPath) {
|
|
75198
75422
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
75199
75423
|
const trimmed = explicitPath.trim();
|
|
75200
|
-
if (!
|
|
75424
|
+
if (!existsSync69(trimmed)) {
|
|
75201
75425
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
75202
75426
|
`);
|
|
75203
75427
|
process.exit(1);
|
|
@@ -75211,9 +75435,9 @@ function resolveSources(explicitPath) {
|
|
|
75211
75435
|
const config = loadConfig();
|
|
75212
75436
|
agentsDir = resolveAgentsDir(config);
|
|
75213
75437
|
} catch {
|
|
75214
|
-
agentsDir =
|
|
75438
|
+
agentsDir = join63(homedir36(), ".switchroom", "agents");
|
|
75215
75439
|
}
|
|
75216
|
-
if (!
|
|
75440
|
+
if (!existsSync69(agentsDir))
|
|
75217
75441
|
return [];
|
|
75218
75442
|
const sources = [];
|
|
75219
75443
|
let entries;
|
|
@@ -75223,8 +75447,8 @@ function resolveSources(explicitPath) {
|
|
|
75223
75447
|
return [];
|
|
75224
75448
|
}
|
|
75225
75449
|
for (const name of entries) {
|
|
75226
|
-
const path8 =
|
|
75227
|
-
if (
|
|
75450
|
+
const path8 = join63(agentsDir, name, "runtime-metrics.jsonl");
|
|
75451
|
+
if (existsSync69(path8)) {
|
|
75228
75452
|
sources.push({ path: path8, agent: name });
|
|
75229
75453
|
}
|
|
75230
75454
|
}
|
|
@@ -75245,17 +75469,17 @@ function inferAgentFromPath(p) {
|
|
|
75245
75469
|
|
|
75246
75470
|
// src/cli/agent-config.ts
|
|
75247
75471
|
init_helpers();
|
|
75248
|
-
import { join as
|
|
75249
|
-
import { homedir as
|
|
75472
|
+
import { join as join64 } from "node:path";
|
|
75473
|
+
import { homedir as homedir37 } from "node:os";
|
|
75250
75474
|
import {
|
|
75251
|
-
existsSync as
|
|
75475
|
+
existsSync as existsSync70,
|
|
75252
75476
|
mkdirSync as mkdirSync37,
|
|
75253
75477
|
appendFileSync as appendFileSync4,
|
|
75254
75478
|
readFileSync as readFileSync56
|
|
75255
75479
|
} from "node:fs";
|
|
75256
|
-
var AUDIT_ROOT =
|
|
75480
|
+
var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
|
|
75257
75481
|
function auditPathFor(agent) {
|
|
75258
|
-
return
|
|
75482
|
+
return join64(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
75259
75483
|
}
|
|
75260
75484
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
75261
75485
|
const row = {
|
|
@@ -75269,7 +75493,7 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
75269
75493
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75270
75494
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
75271
75495
|
try {
|
|
75272
|
-
if (!
|
|
75496
|
+
if (!existsSync70(dir)) {
|
|
75273
75497
|
mkdirSync37(dir, { recursive: true });
|
|
75274
75498
|
}
|
|
75275
75499
|
appendFileSync4(path8, JSON.stringify(row) + `
|
|
@@ -75281,7 +75505,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
75281
75505
|
return true;
|
|
75282
75506
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
75283
75507
|
try {
|
|
75284
|
-
if (
|
|
75508
|
+
if (existsSync70(probe2))
|
|
75285
75509
|
return true;
|
|
75286
75510
|
} catch {}
|
|
75287
75511
|
return false;
|
|
@@ -75342,7 +75566,7 @@ function getAgentSlice(config, agent) {
|
|
|
75342
75566
|
}
|
|
75343
75567
|
function readAuditTail(agent, limit, opts = {}) {
|
|
75344
75568
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75345
|
-
if (!
|
|
75569
|
+
if (!existsSync70(path8))
|
|
75346
75570
|
return [];
|
|
75347
75571
|
let raw;
|
|
75348
75572
|
try {
|
|
@@ -75502,32 +75726,32 @@ var import_yaml14 = __toESM(require_dist(), 1);
|
|
|
75502
75726
|
init_paths();
|
|
75503
75727
|
import {
|
|
75504
75728
|
closeSync as closeSync13,
|
|
75505
|
-
existsSync as
|
|
75729
|
+
existsSync as existsSync71,
|
|
75506
75730
|
fsyncSync as fsyncSync6,
|
|
75507
75731
|
mkdirSync as mkdirSync38,
|
|
75508
75732
|
openSync as openSync13,
|
|
75509
75733
|
readdirSync as readdirSync27,
|
|
75510
75734
|
readFileSync as readFileSync57,
|
|
75511
75735
|
renameSync as renameSync14,
|
|
75512
|
-
statSync as
|
|
75736
|
+
statSync as statSync28,
|
|
75513
75737
|
unlinkSync as unlinkSync14,
|
|
75514
75738
|
writeSync as writeSync8
|
|
75515
75739
|
} from "node:fs";
|
|
75516
|
-
import { join as
|
|
75740
|
+
import { join as join65, resolve as resolve43 } from "node:path";
|
|
75517
75741
|
var STAGING_SUBDIR = ".staging";
|
|
75518
75742
|
function overlayPathsFor(agent, opts = {}) {
|
|
75519
75743
|
const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
75520
|
-
const scheduleDir =
|
|
75521
|
-
const scheduleStagingDir =
|
|
75522
|
-
const skillsDir =
|
|
75523
|
-
const skillsStagingDir =
|
|
75744
|
+
const scheduleDir = join65(base, "schedule.d");
|
|
75745
|
+
const scheduleStagingDir = join65(scheduleDir, STAGING_SUBDIR);
|
|
75746
|
+
const skillsDir = join65(base, "skills.d");
|
|
75747
|
+
const skillsStagingDir = join65(skillsDir, STAGING_SUBDIR);
|
|
75524
75748
|
return {
|
|
75525
75749
|
agentRoot: base,
|
|
75526
75750
|
scheduleDir,
|
|
75527
75751
|
scheduleStagingDir,
|
|
75528
75752
|
skillsDir,
|
|
75529
75753
|
skillsStagingDir,
|
|
75530
|
-
lockPath:
|
|
75754
|
+
lockPath: join65(base, ".lock"),
|
|
75531
75755
|
stagingDir: scheduleStagingDir
|
|
75532
75756
|
};
|
|
75533
75757
|
}
|
|
@@ -75553,7 +75777,7 @@ function withAgentLock(paths, fn) {
|
|
|
75553
75777
|
if (e.code !== "EEXIST")
|
|
75554
75778
|
throw err;
|
|
75555
75779
|
try {
|
|
75556
|
-
const age = Date.now() -
|
|
75780
|
+
const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
|
|
75557
75781
|
if (age > 30000) {
|
|
75558
75782
|
unlinkSync14(paths.lockPath);
|
|
75559
75783
|
continue;
|
|
@@ -75581,8 +75805,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75581
75805
|
const paths = overlayPathsFor(agent, opts);
|
|
75582
75806
|
return withAgentLock(paths, () => {
|
|
75583
75807
|
ensureDirs(paths);
|
|
75584
|
-
const stagingPath =
|
|
75585
|
-
const finalPath =
|
|
75808
|
+
const stagingPath = join65(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
75809
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75586
75810
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75587
75811
|
try {
|
|
75588
75812
|
writeSync8(fd, yamlText);
|
|
@@ -75598,8 +75822,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75598
75822
|
const paths = overlayPathsFor(agent, opts);
|
|
75599
75823
|
return withAgentLock(paths, () => {
|
|
75600
75824
|
ensureSkillsDirs(paths);
|
|
75601
|
-
const stagingPath =
|
|
75602
|
-
const finalPath =
|
|
75825
|
+
const stagingPath = join65(paths.skillsStagingDir, `${slug}.yaml`);
|
|
75826
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75603
75827
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75604
75828
|
try {
|
|
75605
75829
|
writeSync8(fd, yamlText);
|
|
@@ -75614,8 +75838,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75614
75838
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
75615
75839
|
const paths = overlayPathsFor(agent, opts);
|
|
75616
75840
|
return withAgentLock(paths, () => {
|
|
75617
|
-
const finalPath =
|
|
75618
|
-
if (!
|
|
75841
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75842
|
+
if (!existsSync71(finalPath))
|
|
75619
75843
|
return false;
|
|
75620
75844
|
unlinkSync14(finalPath);
|
|
75621
75845
|
return true;
|
|
@@ -75623,13 +75847,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
75623
75847
|
}
|
|
75624
75848
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
75625
75849
|
const paths = overlayPathsFor(agent, opts);
|
|
75626
|
-
if (!
|
|
75850
|
+
if (!existsSync71(paths.skillsDir))
|
|
75627
75851
|
return [];
|
|
75628
75852
|
const out = [];
|
|
75629
75853
|
for (const name of readdirSync27(paths.skillsDir)) {
|
|
75630
75854
|
if (!/\.ya?ml$/i.test(name))
|
|
75631
75855
|
continue;
|
|
75632
|
-
const full =
|
|
75856
|
+
const full = join65(paths.skillsDir, name);
|
|
75633
75857
|
try {
|
|
75634
75858
|
const raw = readFileSync57(full, "utf-8");
|
|
75635
75859
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75641,8 +75865,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
75641
75865
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
75642
75866
|
const paths = overlayPathsFor(agent, opts);
|
|
75643
75867
|
return withAgentLock(paths, () => {
|
|
75644
|
-
const finalPath =
|
|
75645
|
-
if (!
|
|
75868
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75869
|
+
if (!existsSync71(finalPath))
|
|
75646
75870
|
return false;
|
|
75647
75871
|
unlinkSync14(finalPath);
|
|
75648
75872
|
return true;
|
|
@@ -75650,13 +75874,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
75650
75874
|
}
|
|
75651
75875
|
function listOverlayEntries(agent, opts = {}) {
|
|
75652
75876
|
const paths = overlayPathsFor(agent, opts);
|
|
75653
|
-
if (!
|
|
75877
|
+
if (!existsSync71(paths.scheduleDir))
|
|
75654
75878
|
return [];
|
|
75655
75879
|
const out = [];
|
|
75656
75880
|
for (const name of readdirSync27(paths.scheduleDir)) {
|
|
75657
75881
|
if (!/\.ya?ml$/i.test(name))
|
|
75658
75882
|
continue;
|
|
75659
|
-
const full =
|
|
75883
|
+
const full = join65(paths.scheduleDir, name);
|
|
75660
75884
|
try {
|
|
75661
75885
|
const raw = readFileSync57(full, "utf-8");
|
|
75662
75886
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75801,7 +76025,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
75801
76025
|
// src/cli/agent-config-pending.ts
|
|
75802
76026
|
import {
|
|
75803
76027
|
closeSync as closeSync14,
|
|
75804
|
-
existsSync as
|
|
76028
|
+
existsSync as existsSync72,
|
|
75805
76029
|
fsyncSync as fsyncSync7,
|
|
75806
76030
|
mkdirSync as mkdirSync39,
|
|
75807
76031
|
openSync as openSync14,
|
|
@@ -75812,12 +76036,12 @@ import {
|
|
|
75812
76036
|
writeFileSync as writeFileSync33,
|
|
75813
76037
|
writeSync as writeSync9
|
|
75814
76038
|
} from "node:fs";
|
|
75815
|
-
import { join as
|
|
76039
|
+
import { join as join66 } from "node:path";
|
|
75816
76040
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
75817
76041
|
var STAGE_ID_PREFIX = "cap_";
|
|
75818
76042
|
function pendingDir(agent, opts = {}) {
|
|
75819
76043
|
const paths = overlayPathsFor(agent, opts);
|
|
75820
|
-
return
|
|
76044
|
+
return join66(paths.scheduleDir, ".pending");
|
|
75821
76045
|
}
|
|
75822
76046
|
function ensurePendingDir(agent, opts = {}) {
|
|
75823
76047
|
const dir = pendingDir(agent, opts);
|
|
@@ -75830,8 +76054,8 @@ function newStageId() {
|
|
|
75830
76054
|
function stagePendingScheduleEntry(opts) {
|
|
75831
76055
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
75832
76056
|
const stageId = opts.stageId ?? newStageId();
|
|
75833
|
-
const yamlPath =
|
|
75834
|
-
const metaPath =
|
|
76057
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76058
|
+
const metaPath = join66(dir, `${stageId}.meta.json`);
|
|
75835
76059
|
const meta = {
|
|
75836
76060
|
v: 1,
|
|
75837
76061
|
stage_id: stageId,
|
|
@@ -75858,16 +76082,16 @@ function stagePendingScheduleEntry(opts) {
|
|
|
75858
76082
|
}
|
|
75859
76083
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
75860
76084
|
const dir = pendingDir(agent, opts);
|
|
75861
|
-
if (!
|
|
76085
|
+
if (!existsSync72(dir))
|
|
75862
76086
|
return [];
|
|
75863
76087
|
const out = [];
|
|
75864
76088
|
for (const name of readdirSync28(dir).sort()) {
|
|
75865
76089
|
if (!name.endsWith(".meta.json"))
|
|
75866
76090
|
continue;
|
|
75867
76091
|
const stageId = name.slice(0, -".meta.json".length);
|
|
75868
|
-
const metaPath =
|
|
75869
|
-
const yamlPath =
|
|
75870
|
-
if (!
|
|
76092
|
+
const metaPath = join66(dir, name);
|
|
76093
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76094
|
+
if (!existsSync72(yamlPath))
|
|
75871
76095
|
continue;
|
|
75872
76096
|
try {
|
|
75873
76097
|
const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
|
|
@@ -75885,8 +76109,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
75885
76109
|
return { committed: false, reason: "not_found" };
|
|
75886
76110
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
75887
76111
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
75888
|
-
const finalPath =
|
|
75889
|
-
if (
|
|
76112
|
+
const finalPath = join66(paths.scheduleDir, `${slug}.yaml`);
|
|
76113
|
+
if (existsSync72(finalPath)) {
|
|
75890
76114
|
return { committed: false, reason: "slug_collision" };
|
|
75891
76115
|
}
|
|
75892
76116
|
renameSync15(match.yamlPath, finalPath);
|
|
@@ -75908,7 +76132,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
75908
76132
|
}
|
|
75909
76133
|
|
|
75910
76134
|
// src/cli/agent-config-write.ts
|
|
75911
|
-
import { existsSync as
|
|
76135
|
+
import { existsSync as existsSync73, readFileSync as readFileSync59 } from "node:fs";
|
|
75912
76136
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
75913
76137
|
function checkOperatorContext(verb, env2 = process.env) {
|
|
75914
76138
|
if (env2.SWITCHROOM_OPERATOR === "1")
|
|
@@ -76142,7 +76366,7 @@ function scheduleRemove(opts) {
|
|
|
76142
76366
|
}
|
|
76143
76367
|
let priorContent = null;
|
|
76144
76368
|
try {
|
|
76145
|
-
if (
|
|
76369
|
+
if (existsSync73(match.path))
|
|
76146
76370
|
priorContent = readFileSync59(match.path, "utf-8");
|
|
76147
76371
|
} catch {}
|
|
76148
76372
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -76335,10 +76559,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
76335
76559
|
|
|
76336
76560
|
// src/cli/agent-config-skill-write.ts
|
|
76337
76561
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
76338
|
-
import { existsSync as
|
|
76562
|
+
import { existsSync as existsSync74 } from "node:fs";
|
|
76339
76563
|
init_reconcile_default_skills();
|
|
76340
76564
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
76341
|
-
import { join as
|
|
76565
|
+
import { join as join67 } from "node:path";
|
|
76342
76566
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
76343
76567
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
76344
76568
|
function exitCodeFor2(code) {
|
|
@@ -76413,8 +76637,8 @@ function skillInstall(opts) {
|
|
|
76413
76637
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
76414
76638
|
}
|
|
76415
76639
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
76416
|
-
const skillPath =
|
|
76417
|
-
if (!
|
|
76640
|
+
const skillPath = join67(poolDir, skillName);
|
|
76641
|
+
if (!existsSync74(skillPath)) {
|
|
76418
76642
|
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.`);
|
|
76419
76643
|
}
|
|
76420
76644
|
const yamlText = import_yaml15.stringify({ skills: [skillName] });
|
|
@@ -76598,23 +76822,23 @@ init_source();
|
|
|
76598
76822
|
init_helpers();
|
|
76599
76823
|
init_loader();
|
|
76600
76824
|
import {
|
|
76601
|
-
existsSync as
|
|
76825
|
+
existsSync as existsSync76,
|
|
76602
76826
|
readdirSync as readdirSync29,
|
|
76603
76827
|
readFileSync as readFileSync61,
|
|
76604
76828
|
renameSync as renameSync16,
|
|
76605
|
-
statSync as
|
|
76829
|
+
statSync as statSync29,
|
|
76606
76830
|
unlinkSync as unlinkSync16
|
|
76607
76831
|
} from "node:fs";
|
|
76608
76832
|
import { createHash as createHash13 } from "node:crypto";
|
|
76609
|
-
import { join as
|
|
76833
|
+
import { join as join68 } from "node:path";
|
|
76610
76834
|
function planCronUnitRenames(agentsDir, agents) {
|
|
76611
76835
|
const plans = [];
|
|
76612
76836
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
76613
76837
|
const schedule = agentConfig.schedule ?? [];
|
|
76614
76838
|
if (schedule.length === 0)
|
|
76615
76839
|
continue;
|
|
76616
|
-
const telegramDir =
|
|
76617
|
-
if (!
|
|
76840
|
+
const telegramDir = join68(agentsDir, agentName, "telegram");
|
|
76841
|
+
if (!existsSync76(telegramDir))
|
|
76618
76842
|
continue;
|
|
76619
76843
|
let entries;
|
|
76620
76844
|
try {
|
|
@@ -76635,8 +76859,8 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
76635
76859
|
continue;
|
|
76636
76860
|
plans.push({
|
|
76637
76861
|
agent: agentName,
|
|
76638
|
-
from:
|
|
76639
|
-
to:
|
|
76862
|
+
from: join68(telegramDir, file),
|
|
76863
|
+
to: join68(telegramDir, canonical),
|
|
76640
76864
|
scheduleIdx: idx,
|
|
76641
76865
|
entry
|
|
76642
76866
|
});
|
|
@@ -76648,7 +76872,7 @@ function sha256File2(path8) {
|
|
|
76648
76872
|
return createHash13("sha256").update(readFileSync61(path8)).digest("hex");
|
|
76649
76873
|
}
|
|
76650
76874
|
function renamePair(from, to, opts = {}) {
|
|
76651
|
-
if (
|
|
76875
|
+
if (existsSync76(to)) {
|
|
76652
76876
|
let identical = false;
|
|
76653
76877
|
try {
|
|
76654
76878
|
identical = sha256File2(from) === sha256File2(to);
|
|
@@ -76741,7 +76965,7 @@ function registerMigrateCommand(program3) {
|
|
|
76741
76965
|
const fromSidecar = p.from.replace(/\.sh$/, ".source");
|
|
76742
76966
|
const toSidecar = p.to.replace(/\.sh$/, ".source");
|
|
76743
76967
|
let sidecarStatus = null;
|
|
76744
|
-
if (
|
|
76968
|
+
if (existsSync76(fromSidecar) && statSync29(fromSidecar).isFile()) {
|
|
76745
76969
|
sidecarStatus = renamePair(fromSidecar, toSidecar);
|
|
76746
76970
|
}
|
|
76747
76971
|
switch (status.kind) {
|
|
@@ -76773,9 +76997,9 @@ function registerMigrateCommand(program3) {
|
|
|
76773
76997
|
// src/cli/hostd.ts
|
|
76774
76998
|
init_source();
|
|
76775
76999
|
init_helpers();
|
|
76776
|
-
import { existsSync as
|
|
76777
|
-
import { homedir as
|
|
76778
|
-
import { join as
|
|
77000
|
+
import { existsSync as existsSync77, mkdirSync as mkdirSync40, readdirSync as readdirSync30, readFileSync as readFileSync62, writeFileSync as writeFileSync34, statSync as statSync30, copyFileSync as copyFileSync12 } from "node:fs";
|
|
77001
|
+
import { homedir as homedir38 } from "node:os";
|
|
77002
|
+
import { join as join69 } from "node:path";
|
|
76779
77003
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
76780
77004
|
init_audit_reader();
|
|
76781
77005
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -76866,14 +77090,14 @@ networks:
|
|
|
76866
77090
|
`;
|
|
76867
77091
|
}
|
|
76868
77092
|
function hostdDir() {
|
|
76869
|
-
return
|
|
77093
|
+
return join69(homedir38(), ".switchroom", "hostd");
|
|
76870
77094
|
}
|
|
76871
77095
|
function hostdComposePath() {
|
|
76872
|
-
return
|
|
77096
|
+
return join69(hostdDir(), "docker-compose.yml");
|
|
76873
77097
|
}
|
|
76874
77098
|
function backupExistingCompose() {
|
|
76875
77099
|
const p = hostdComposePath();
|
|
76876
|
-
if (!
|
|
77100
|
+
if (!existsSync77(p))
|
|
76877
77101
|
return null;
|
|
76878
77102
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
76879
77103
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -76908,7 +77132,7 @@ async function doInstall(opts, program3) {
|
|
|
76908
77132
|
const composePath = hostdComposePath();
|
|
76909
77133
|
mkdirSync40(dir, { recursive: true });
|
|
76910
77134
|
const yaml = renderHostdComposeFile({
|
|
76911
|
-
hostHome:
|
|
77135
|
+
hostHome: homedir38(),
|
|
76912
77136
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76913
77137
|
operatorUid: resolveOperatorUid()
|
|
76914
77138
|
});
|
|
@@ -76950,7 +77174,7 @@ function doStatus() {
|
|
|
76950
77174
|
const composeYml = hostdComposePath();
|
|
76951
77175
|
console.log(source_default.bold("switchroom-hostd"));
|
|
76952
77176
|
console.log("");
|
|
76953
|
-
if (!
|
|
77177
|
+
if (!existsSync77(composeYml)) {
|
|
76954
77178
|
console.log(source_default.yellow(" compose: not installed"));
|
|
76955
77179
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
76956
77180
|
return;
|
|
@@ -76971,15 +77195,15 @@ function doStatus() {
|
|
|
76971
77195
|
} else {
|
|
76972
77196
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
76973
77197
|
}
|
|
76974
|
-
if (
|
|
77198
|
+
if (existsSync77(dir)) {
|
|
76975
77199
|
const entries = [];
|
|
76976
77200
|
try {
|
|
76977
77201
|
for (const name of readdirSync30(dir)) {
|
|
76978
77202
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
76979
77203
|
continue;
|
|
76980
|
-
const sockPath =
|
|
76981
|
-
if (
|
|
76982
|
-
const st =
|
|
77204
|
+
const sockPath = join69(dir, name, "sock");
|
|
77205
|
+
if (existsSync77(sockPath)) {
|
|
77206
|
+
const st = statSync30(sockPath);
|
|
76983
77207
|
if ((st.mode & 61440) === 49152) {
|
|
76984
77208
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
76985
77209
|
}
|
|
@@ -76997,7 +77221,7 @@ function doStatus() {
|
|
|
76997
77221
|
}
|
|
76998
77222
|
function doUninstall() {
|
|
76999
77223
|
const composeYml = hostdComposePath();
|
|
77000
|
-
if (!
|
|
77224
|
+
if (!existsSync77(composeYml)) {
|
|
77001
77225
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
77002
77226
|
return;
|
|
77003
77227
|
}
|
|
@@ -77021,7 +77245,7 @@ function registerHostdCommand(program3) {
|
|
|
77021
77245
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
77022
77246
|
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
77023
77247
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
77024
|
-
if (!
|
|
77248
|
+
if (!existsSync77(logPath)) {
|
|
77025
77249
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
77026
77250
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
77027
77251
|
return;
|