switchroom 0.13.32 → 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
|
@@ -29400,6 +29400,220 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
29400
29400
|
init_client4();
|
|
29401
29401
|
});
|
|
29402
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
|
+
|
|
29403
29617
|
// src/cli/doctor.ts
|
|
29404
29618
|
var exports_doctor = {};
|
|
29405
29619
|
__export(exports_doctor, {
|
|
@@ -29445,25 +29659,25 @@ import { execSync as execSync3, spawnSync as spawnSync7 } from "node:child_proce
|
|
|
29445
29659
|
import {
|
|
29446
29660
|
accessSync as accessSync2,
|
|
29447
29661
|
constants as fsConstants5,
|
|
29448
|
-
existsSync as
|
|
29662
|
+
existsSync as existsSync51,
|
|
29449
29663
|
lstatSync as lstatSync5,
|
|
29450
29664
|
mkdirSync as mkdirSync27,
|
|
29451
29665
|
readFileSync as readFileSync45,
|
|
29452
29666
|
readdirSync as readdirSync19,
|
|
29453
|
-
statSync as
|
|
29667
|
+
statSync as statSync23
|
|
29454
29668
|
} from "node:fs";
|
|
29455
|
-
import { dirname as dirname12, join as
|
|
29669
|
+
import { dirname as dirname12, join as join47, resolve as resolve30 } from "node:path";
|
|
29456
29670
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
29457
29671
|
function findInNvm(bin) {
|
|
29458
|
-
const nvmRoot =
|
|
29459
|
-
if (!
|
|
29672
|
+
const nvmRoot = join47(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
29673
|
+
if (!existsSync51(nvmRoot))
|
|
29460
29674
|
return null;
|
|
29461
29675
|
try {
|
|
29462
29676
|
const versions = readdirSync19(nvmRoot).sort().reverse();
|
|
29463
29677
|
for (const v of versions) {
|
|
29464
|
-
const candidate =
|
|
29678
|
+
const candidate = join47(nvmRoot, v, "bin", bin);
|
|
29465
29679
|
try {
|
|
29466
|
-
const s =
|
|
29680
|
+
const s = statSync23(candidate);
|
|
29467
29681
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
29468
29682
|
return candidate;
|
|
29469
29683
|
}
|
|
@@ -29626,21 +29840,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29626
29840
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
29627
29841
|
cacheLocations.push(envBrowsersPath);
|
|
29628
29842
|
}
|
|
29629
|
-
cacheLocations.push(
|
|
29843
|
+
cacheLocations.push(join47(homeDir, ".cache", "ms-playwright"));
|
|
29630
29844
|
for (const cacheDir of cacheLocations) {
|
|
29631
|
-
if (!
|
|
29845
|
+
if (!existsSync51(cacheDir))
|
|
29632
29846
|
continue;
|
|
29633
29847
|
try {
|
|
29634
29848
|
const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
29635
29849
|
for (const entry of entries) {
|
|
29636
29850
|
const candidates2 = [
|
|
29637
|
-
|
|
29638
|
-
|
|
29639
|
-
|
|
29640
|
-
|
|
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")
|
|
29641
29855
|
];
|
|
29642
29856
|
for (const path4 of candidates2) {
|
|
29643
|
-
if (
|
|
29857
|
+
if (existsSync51(path4))
|
|
29644
29858
|
return path4;
|
|
29645
29859
|
}
|
|
29646
29860
|
}
|
|
@@ -29720,8 +29934,8 @@ function checkConfig(config, configPath) {
|
|
|
29720
29934
|
function checkLegacyState() {
|
|
29721
29935
|
const results = [];
|
|
29722
29936
|
const h = process.env.HOME ?? "/root";
|
|
29723
|
-
const clerkDir =
|
|
29724
|
-
const clerkPresent =
|
|
29937
|
+
const clerkDir = join47(h, LEGACY_STATE_DIR);
|
|
29938
|
+
const clerkPresent = existsSync51(clerkDir);
|
|
29725
29939
|
results.push({
|
|
29726
29940
|
name: "legacy ~/.clerk state",
|
|
29727
29941
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -29730,7 +29944,7 @@ function checkLegacyState() {
|
|
|
29730
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."
|
|
29731
29945
|
} : {}
|
|
29732
29946
|
});
|
|
29733
|
-
const legacySock =
|
|
29947
|
+
const legacySock = join47(h, ".switchroom", "vault-broker.sock");
|
|
29734
29948
|
let sockStat = null;
|
|
29735
29949
|
try {
|
|
29736
29950
|
sockStat = lstatSync5(legacySock);
|
|
@@ -29851,7 +30065,7 @@ function checkVault(config) {
|
|
|
29851
30065
|
detail: "Approval auth: passphrase (two-factor)"
|
|
29852
30066
|
};
|
|
29853
30067
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
29854
|
-
if (!
|
|
30068
|
+
if (!existsSync51(vaultPath)) {
|
|
29855
30069
|
return [
|
|
29856
30070
|
postureResult,
|
|
29857
30071
|
{
|
|
@@ -30026,8 +30240,8 @@ async function checkHindsight(config) {
|
|
|
30026
30240
|
}
|
|
30027
30241
|
function checkPendingRetainsQueue(dir) {
|
|
30028
30242
|
const home2 = process.env.HOME ?? "";
|
|
30029
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
30030
|
-
if (!
|
|
30243
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join47(home2, ".hindsight", "pending-retains");
|
|
30244
|
+
if (!existsSync51(pendingDir)) {
|
|
30031
30245
|
return {
|
|
30032
30246
|
name: "pending-retains queue",
|
|
30033
30247
|
status: "ok",
|
|
@@ -30098,7 +30312,7 @@ function tryReadHostFile(path4) {
|
|
|
30098
30312
|
}
|
|
30099
30313
|
}
|
|
30100
30314
|
function parseEnvFile(path4) {
|
|
30101
|
-
if (!
|
|
30315
|
+
if (!existsSync51(path4))
|
|
30102
30316
|
return {};
|
|
30103
30317
|
let content;
|
|
30104
30318
|
try {
|
|
@@ -30157,7 +30371,7 @@ async function checkTelegram(config) {
|
|
|
30157
30371
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
30158
30372
|
if (plugin !== "switchroom")
|
|
30159
30373
|
continue;
|
|
30160
|
-
const envPath =
|
|
30374
|
+
const envPath = join47(agentsDir, name, "telegram", ".env");
|
|
30161
30375
|
const read = tryReadHostFile(envPath);
|
|
30162
30376
|
if (read.kind === "eacces") {
|
|
30163
30377
|
results.push({
|
|
@@ -30209,7 +30423,7 @@ async function checkTelegram(config) {
|
|
|
30209
30423
|
}
|
|
30210
30424
|
function checkStartShStale(agentName, startShPath) {
|
|
30211
30425
|
const label = `${agentName}: start.sh scheduler block`;
|
|
30212
|
-
if (!
|
|
30426
|
+
if (!existsSync51(startShPath)) {
|
|
30213
30427
|
return {
|
|
30214
30428
|
name: label,
|
|
30215
30429
|
status: "warn",
|
|
@@ -30240,7 +30454,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
30240
30454
|
}
|
|
30241
30455
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
30242
30456
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
30243
|
-
const path4 =
|
|
30457
|
+
const path4 = join47(agentDir, "home", ".switchroom");
|
|
30244
30458
|
let stats;
|
|
30245
30459
|
try {
|
|
30246
30460
|
stats = lstatSync5(path4);
|
|
@@ -30277,8 +30491,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
30277
30491
|
}
|
|
30278
30492
|
function checkRepoHygiene(repoRoot) {
|
|
30279
30493
|
const results = [];
|
|
30280
|
-
const exportDir =
|
|
30281
|
-
if (
|
|
30494
|
+
const exportDir = join47(repoRoot, "clerk-export");
|
|
30495
|
+
if (existsSync51(exportDir)) {
|
|
30282
30496
|
results.push({
|
|
30283
30497
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
30284
30498
|
status: "warn",
|
|
@@ -30286,8 +30500,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30286
30500
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
30287
30501
|
});
|
|
30288
30502
|
}
|
|
30289
|
-
const knownTarball =
|
|
30290
|
-
if (
|
|
30503
|
+
const knownTarball = join47(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
30504
|
+
if (existsSync51(knownTarball)) {
|
|
30291
30505
|
results.push({
|
|
30292
30506
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
30293
30507
|
status: "warn",
|
|
@@ -30304,7 +30518,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30304
30518
|
results.push({
|
|
30305
30519
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
30306
30520
|
status: "warn",
|
|
30307
|
-
detail: `${
|
|
30521
|
+
detail: `${join47(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
30308
30522
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
30309
30523
|
});
|
|
30310
30524
|
}
|
|
@@ -30327,10 +30541,10 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30327
30541
|
}
|
|
30328
30542
|
function isSwitchroomCheckout(dir) {
|
|
30329
30543
|
try {
|
|
30330
|
-
if (!
|
|
30544
|
+
if (!existsSync51(join47(dir, ".git")))
|
|
30331
30545
|
return false;
|
|
30332
|
-
const pkgPath =
|
|
30333
|
-
if (!
|
|
30546
|
+
const pkgPath = join47(dir, "package.json");
|
|
30547
|
+
if (!existsSync51(pkgPath))
|
|
30334
30548
|
return false;
|
|
30335
30549
|
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
30336
30550
|
return pkg.name === "switchroom";
|
|
@@ -30345,7 +30559,7 @@ function checkAgents(config, configPath) {
|
|
|
30345
30559
|
const authStatuses = getAllAuthStatuses(config);
|
|
30346
30560
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
30347
30561
|
const agentDir = resolve30(agentsDir, name);
|
|
30348
|
-
if (!
|
|
30562
|
+
if (!existsSync51(agentDir)) {
|
|
30349
30563
|
results.push({
|
|
30350
30564
|
name: `${name}: scaffold`,
|
|
30351
30565
|
status: "fail",
|
|
@@ -30366,7 +30580,7 @@ function checkAgents(config, configPath) {
|
|
|
30366
30580
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
30367
30581
|
});
|
|
30368
30582
|
}
|
|
30369
|
-
results.push(checkStartShStale(name,
|
|
30583
|
+
results.push(checkStartShStale(name, join47(agentDir, "start.sh")));
|
|
30370
30584
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
30371
30585
|
const status = statuses[name];
|
|
30372
30586
|
const active = status?.active ?? "unknown";
|
|
@@ -30443,8 +30657,8 @@ function checkAgents(config, configPath) {
|
|
|
30443
30657
|
}
|
|
30444
30658
|
}
|
|
30445
30659
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
30446
|
-
const mcpJsonPath =
|
|
30447
|
-
if (!
|
|
30660
|
+
const mcpJsonPath = join47(agentDir, ".mcp.json");
|
|
30661
|
+
if (!existsSync51(mcpJsonPath)) {
|
|
30448
30662
|
results.push({
|
|
30449
30663
|
name: `${name}: .mcp.json`,
|
|
30450
30664
|
status: "fail",
|
|
@@ -30529,7 +30743,7 @@ function mffEnvPath(config) {
|
|
|
30529
30743
|
return agent ? resolve30(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve30(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
30530
30744
|
}
|
|
30531
30745
|
function mffEnvState(envPath) {
|
|
30532
|
-
if (!
|
|
30746
|
+
if (!existsSync51(envPath))
|
|
30533
30747
|
return "absent";
|
|
30534
30748
|
try {
|
|
30535
30749
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -30547,7 +30761,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
30547
30761
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
30548
30762
|
};
|
|
30549
30763
|
}
|
|
30550
|
-
if (!
|
|
30764
|
+
if (!existsSync51(vaultPath)) {
|
|
30551
30765
|
return {
|
|
30552
30766
|
name: "mff: vault key present",
|
|
30553
30767
|
status: "fail",
|
|
@@ -30600,7 +30814,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
30600
30814
|
}
|
|
30601
30815
|
}
|
|
30602
30816
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
30603
|
-
if (!passphrase || !
|
|
30817
|
+
if (!passphrase || !existsSync51(vaultPath)) {
|
|
30604
30818
|
return {
|
|
30605
30819
|
name: "mff: vault key format",
|
|
30606
30820
|
status: "warn",
|
|
@@ -30744,8 +30958,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
30744
30958
|
};
|
|
30745
30959
|
}
|
|
30746
30960
|
const credDir = dirname12(envPath);
|
|
30747
|
-
const authScript =
|
|
30748
|
-
if (!
|
|
30961
|
+
const authScript = join47(credDir, "claude-auth.py");
|
|
30962
|
+
if (!existsSync51(authScript)) {
|
|
30749
30963
|
return {
|
|
30750
30964
|
name: "mff: auth flow",
|
|
30751
30965
|
status: "warn",
|
|
@@ -31023,6 +31237,10 @@ function registerDoctorCommand(program3) {
|
|
|
31023
31237
|
},
|
|
31024
31238
|
{ title: "Legacy State", results: checkLegacyState() },
|
|
31025
31239
|
{ title: "Vault", results: checkVault(config) },
|
|
31240
|
+
{
|
|
31241
|
+
title: "Vault-broker durability",
|
|
31242
|
+
results: runVaultBrokerDurabilityChecks(config)
|
|
31243
|
+
},
|
|
31026
31244
|
{ title: "Vault access", results: await runSecretAccessChecks(config) },
|
|
31027
31245
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
31028
31246
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
@@ -31106,6 +31324,7 @@ var init_doctor = __esm(() => {
|
|
|
31106
31324
|
init_doctor_inlined_secrets();
|
|
31107
31325
|
init_doctor_audit_integrity();
|
|
31108
31326
|
init_doctor_agent_smoke();
|
|
31327
|
+
init_doctor_vault_broker_durability();
|
|
31109
31328
|
MANIFEST_WARN_ONLY = new Set([
|
|
31110
31329
|
"@playwright/mcp",
|
|
31111
31330
|
"hindsight.backend",
|
|
@@ -47034,7 +47253,7 @@ __export(exports_server2, {
|
|
|
47034
47253
|
TOOLS: () => TOOLS2
|
|
47035
47254
|
});
|
|
47036
47255
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47037
|
-
import { existsSync as
|
|
47256
|
+
import { existsSync as existsSync75, readFileSync as readFileSync60 } from "node:fs";
|
|
47038
47257
|
function selfSocketPath() {
|
|
47039
47258
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
47040
47259
|
}
|
|
@@ -47049,7 +47268,7 @@ async function dispatchTool2(name, args) {
|
|
|
47049
47268
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
47050
47269
|
}
|
|
47051
47270
|
const sockPath = selfSocketPath();
|
|
47052
|
-
if (!
|
|
47271
|
+
if (!existsSync75(sockPath)) {
|
|
47053
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.`);
|
|
47054
47273
|
}
|
|
47055
47274
|
let req;
|
|
@@ -47193,13 +47412,13 @@ function resolveAuditLogPath() {
|
|
|
47193
47412
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
47194
47413
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
47195
47414
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
47196
|
-
if (
|
|
47415
|
+
if (existsSync75(bindMounted))
|
|
47197
47416
|
return bindMounted;
|
|
47198
47417
|
return defaultAuditLogPath2();
|
|
47199
47418
|
}
|
|
47200
47419
|
function getLastUpdateApplyStatus() {
|
|
47201
47420
|
const path8 = resolveAuditLogPath();
|
|
47202
|
-
if (!
|
|
47421
|
+
if (!existsSync75(path8)) {
|
|
47203
47422
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
47204
47423
|
}
|
|
47205
47424
|
let raw;
|
|
@@ -47437,8 +47656,8 @@ var {
|
|
|
47437
47656
|
} = import__.default;
|
|
47438
47657
|
|
|
47439
47658
|
// src/build-info.ts
|
|
47440
|
-
var VERSION = "0.13.
|
|
47441
|
-
var COMMIT_SHA = "
|
|
47659
|
+
var VERSION = "0.13.33";
|
|
47660
|
+
var COMMIT_SHA = "a8018071";
|
|
47442
47661
|
|
|
47443
47662
|
// src/cli/agent.ts
|
|
47444
47663
|
init_source();
|
|
@@ -69811,17 +70030,17 @@ init_doctor();
|
|
|
69811
70030
|
init_source();
|
|
69812
70031
|
init_loader();
|
|
69813
70032
|
init_lifecycle();
|
|
69814
|
-
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";
|
|
69815
70034
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
69816
|
-
import { join as
|
|
69817
|
-
import { homedir as
|
|
69818
|
-
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");
|
|
69819
70038
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
69820
70039
|
let dir = dirname13(scriptPath);
|
|
69821
70040
|
for (let i = 0;i < 12; i++) {
|
|
69822
|
-
if (
|
|
70041
|
+
if (existsSync52(join48(dir, ".git"))) {
|
|
69823
70042
|
try {
|
|
69824
|
-
const pkg = JSON.parse(readFileSync46(
|
|
70043
|
+
const pkg = JSON.parse(readFileSync46(join48(dir, "package.json"), "utf-8"));
|
|
69825
70044
|
if (pkg.name === "switchroom")
|
|
69826
70045
|
return true;
|
|
69827
70046
|
} catch {}
|
|
@@ -69873,7 +70092,7 @@ function planUpdate(opts) {
|
|
|
69873
70092
|
steps.push({
|
|
69874
70093
|
name: "pull-images",
|
|
69875
70094
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
69876
|
-
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,
|
|
69877
70096
|
run: () => {
|
|
69878
70097
|
const r = runner("docker", [
|
|
69879
70098
|
"compose",
|
|
@@ -69952,14 +70171,14 @@ function planUpdate(opts) {
|
|
|
69952
70171
|
return;
|
|
69953
70172
|
}
|
|
69954
70173
|
const source = resolve31(import.meta.dirname, "../../skills");
|
|
69955
|
-
const dest =
|
|
69956
|
-
if (!
|
|
70174
|
+
const dest = join48(homedir28(), ".switchroom", "skills", "_bundled");
|
|
70175
|
+
if (!existsSync52(source)) {
|
|
69957
70176
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69958
70177
|
`);
|
|
69959
70178
|
return;
|
|
69960
70179
|
}
|
|
69961
70180
|
try {
|
|
69962
|
-
if (
|
|
70181
|
+
if (existsSync52(dest)) {
|
|
69963
70182
|
rmSync12(dest, { recursive: true, force: true });
|
|
69964
70183
|
}
|
|
69965
70184
|
mkdirSync28(dirname13(dest), { recursive: true });
|
|
@@ -70062,12 +70281,12 @@ function defaultStatusProbe(composePath) {
|
|
|
70062
70281
|
} catch {}
|
|
70063
70282
|
if (scriptPath) {
|
|
70064
70283
|
try {
|
|
70065
|
-
cliBuiltAt = new Date(
|
|
70284
|
+
cliBuiltAt = new Date(statSync24(scriptPath).mtimeMs).toISOString();
|
|
70066
70285
|
} catch {}
|
|
70067
70286
|
let dir = dirname13(scriptPath);
|
|
70068
70287
|
for (let i = 0;i < 8; i++) {
|
|
70069
|
-
const pkgPath =
|
|
70070
|
-
if (
|
|
70288
|
+
const pkgPath = join48(dir, "package.json");
|
|
70289
|
+
if (existsSync52(pkgPath)) {
|
|
70071
70290
|
try {
|
|
70072
70291
|
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
70073
70292
|
if (typeof pkg.version === "string")
|
|
@@ -70090,7 +70309,7 @@ function defaultStatusProbe(composePath) {
|
|
|
70090
70309
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
70091
70310
|
}
|
|
70092
70311
|
const services = [];
|
|
70093
|
-
if (!
|
|
70312
|
+
if (!existsSync52(composePath)) {
|
|
70094
70313
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
70095
70314
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
70096
70315
|
}
|
|
@@ -70285,8 +70504,8 @@ init_source();
|
|
|
70285
70504
|
init_helpers();
|
|
70286
70505
|
init_lifecycle();
|
|
70287
70506
|
import { execSync as execSync4 } from "node:child_process";
|
|
70288
|
-
import { existsSync as
|
|
70289
|
-
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";
|
|
70290
70509
|
function getClaudeCodeVersion() {
|
|
70291
70510
|
try {
|
|
70292
70511
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -70336,11 +70555,11 @@ function formatUptime3(timestamp) {
|
|
|
70336
70555
|
function locateSwitchroomInstallDir() {
|
|
70337
70556
|
let dir = import.meta.dirname;
|
|
70338
70557
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
70339
|
-
const pkgPath =
|
|
70340
|
-
if (
|
|
70558
|
+
const pkgPath = join49(dir, "package.json");
|
|
70559
|
+
if (existsSync53(pkgPath)) {
|
|
70341
70560
|
try {
|
|
70342
70561
|
const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
|
|
70343
|
-
if (pkg.name === "switchroom" &&
|
|
70562
|
+
if (pkg.name === "switchroom" && existsSync53(join49(dir, ".git"))) {
|
|
70344
70563
|
return dir;
|
|
70345
70564
|
}
|
|
70346
70565
|
} catch {}
|
|
@@ -70555,18 +70774,18 @@ function registerHandoffCommand(program3) {
|
|
|
70555
70774
|
// src/issues/store.ts
|
|
70556
70775
|
import {
|
|
70557
70776
|
closeSync as closeSync11,
|
|
70558
|
-
existsSync as
|
|
70777
|
+
existsSync as existsSync54,
|
|
70559
70778
|
mkdirSync as mkdirSync29,
|
|
70560
70779
|
openSync as openSync11,
|
|
70561
70780
|
readdirSync as readdirSync20,
|
|
70562
70781
|
readFileSync as readFileSync48,
|
|
70563
70782
|
renameSync as renameSync11,
|
|
70564
|
-
statSync as
|
|
70783
|
+
statSync as statSync25,
|
|
70565
70784
|
unlinkSync as unlinkSync11,
|
|
70566
70785
|
writeFileSync as writeFileSync25,
|
|
70567
70786
|
writeSync as writeSync7
|
|
70568
70787
|
} from "node:fs";
|
|
70569
|
-
import { join as
|
|
70788
|
+
import { join as join50 } from "node:path";
|
|
70570
70789
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
70571
70790
|
import { execSync as execSync5 } from "node:child_process";
|
|
70572
70791
|
|
|
@@ -70886,8 +71105,8 @@ function redactedMarker(ruleId) {
|
|
|
70886
71105
|
var ISSUES_FILE = "issues.jsonl";
|
|
70887
71106
|
var ISSUES_LOCK = "issues.lock";
|
|
70888
71107
|
function readAll(stateDir) {
|
|
70889
|
-
const path4 =
|
|
70890
|
-
if (!
|
|
71108
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71109
|
+
if (!existsSync54(path4))
|
|
70891
71110
|
return [];
|
|
70892
71111
|
let raw;
|
|
70893
71112
|
try {
|
|
@@ -70964,7 +71183,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
70964
71183
|
});
|
|
70965
71184
|
}
|
|
70966
71185
|
function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
70967
|
-
if (!
|
|
71186
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70968
71187
|
return 0;
|
|
70969
71188
|
return withLock(stateDir, () => {
|
|
70970
71189
|
const all = readAll(stateDir);
|
|
@@ -70982,7 +71201,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
70982
71201
|
});
|
|
70983
71202
|
}
|
|
70984
71203
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
70985
|
-
if (!
|
|
71204
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70986
71205
|
return 0;
|
|
70987
71206
|
return withLock(stateDir, () => {
|
|
70988
71207
|
const all = readAll(stateDir);
|
|
@@ -71000,7 +71219,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
71000
71219
|
});
|
|
71001
71220
|
}
|
|
71002
71221
|
function prune(stateDir, opts = {}) {
|
|
71003
|
-
if (!
|
|
71222
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
71004
71223
|
return 0;
|
|
71005
71224
|
return withLock(stateDir, () => {
|
|
71006
71225
|
const all = readAll(stateDir);
|
|
@@ -71033,7 +71252,7 @@ function ensureDir(stateDir) {
|
|
|
71033
71252
|
mkdirSync29(stateDir, { recursive: true });
|
|
71034
71253
|
}
|
|
71035
71254
|
function writeAll(stateDir, events) {
|
|
71036
|
-
const path4 =
|
|
71255
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71037
71256
|
sweepOrphanTmpFiles(stateDir);
|
|
71038
71257
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
|
|
71039
71258
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -71055,9 +71274,9 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71055
71274
|
for (const entry of entries) {
|
|
71056
71275
|
if (!entry.startsWith(TMP_PREFIX))
|
|
71057
71276
|
continue;
|
|
71058
|
-
const tmpPath =
|
|
71277
|
+
const tmpPath = join50(stateDir, entry);
|
|
71059
71278
|
try {
|
|
71060
|
-
const stat =
|
|
71279
|
+
const stat = statSync25(tmpPath);
|
|
71061
71280
|
if (stat.mtimeMs < cutoff) {
|
|
71062
71281
|
unlinkSync11(tmpPath);
|
|
71063
71282
|
}
|
|
@@ -71067,7 +71286,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71067
71286
|
var LOCK_RETRY_MS = 25;
|
|
71068
71287
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
71069
71288
|
function withLock(stateDir, fn) {
|
|
71070
|
-
const lockPath =
|
|
71289
|
+
const lockPath = join50(stateDir, ISSUES_LOCK);
|
|
71071
71290
|
const startedAt = Date.now();
|
|
71072
71291
|
let fd = null;
|
|
71073
71292
|
while (fd === null) {
|
|
@@ -71350,22 +71569,22 @@ function relTime(deltaMs) {
|
|
|
71350
71569
|
|
|
71351
71570
|
// src/cli/deps.ts
|
|
71352
71571
|
init_source();
|
|
71353
|
-
import { existsSync as
|
|
71354
|
-
import { homedir as
|
|
71355
|
-
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";
|
|
71356
71575
|
|
|
71357
71576
|
// src/deps/python.ts
|
|
71358
71577
|
import { createHash as createHash10 } from "node:crypto";
|
|
71359
71578
|
import {
|
|
71360
|
-
existsSync as
|
|
71579
|
+
existsSync as existsSync55,
|
|
71361
71580
|
mkdirSync as mkdirSync30,
|
|
71362
71581
|
readFileSync as readFileSync49,
|
|
71363
71582
|
rmSync as rmSync13,
|
|
71364
71583
|
writeFileSync as writeFileSync26
|
|
71365
71584
|
} from "node:fs";
|
|
71366
|
-
import { dirname as dirname15, join as
|
|
71367
|
-
import { homedir as
|
|
71368
|
-
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";
|
|
71369
71588
|
|
|
71370
71589
|
class PythonEnvError extends Error {
|
|
71371
71590
|
stderr;
|
|
@@ -71376,7 +71595,7 @@ class PythonEnvError extends Error {
|
|
|
71376
71595
|
}
|
|
71377
71596
|
}
|
|
71378
71597
|
function defaultPythonCacheRoot() {
|
|
71379
|
-
return
|
|
71598
|
+
return join51(homedir29(), ".switchroom", "deps", "python");
|
|
71380
71599
|
}
|
|
71381
71600
|
function hashFile(path4) {
|
|
71382
71601
|
return createHash10("sha256").update(readFileSync49(path4)).digest("hex");
|
|
@@ -71385,16 +71604,16 @@ function ensurePythonEnv(opts) {
|
|
|
71385
71604
|
const { skillName, requirementsPath, force = false } = opts;
|
|
71386
71605
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
71387
71606
|
const hostPython = opts.pythonBin ?? "python3";
|
|
71388
|
-
if (!
|
|
71607
|
+
if (!existsSync55(requirementsPath)) {
|
|
71389
71608
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
71390
71609
|
}
|
|
71391
|
-
const venvDir =
|
|
71392
|
-
const stampPath =
|
|
71393
|
-
const binDir =
|
|
71394
|
-
const pythonBin =
|
|
71395
|
-
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");
|
|
71396
71615
|
const targetHash = hashFile(requirementsPath);
|
|
71397
|
-
if (!force &&
|
|
71616
|
+
if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
|
|
71398
71617
|
const existingHash = readFileSync49(stampPath, "utf8").trim();
|
|
71399
71618
|
if (existingHash === targetHash) {
|
|
71400
71619
|
return {
|
|
@@ -71407,12 +71626,12 @@ function ensurePythonEnv(opts) {
|
|
|
71407
71626
|
};
|
|
71408
71627
|
}
|
|
71409
71628
|
}
|
|
71410
|
-
if (
|
|
71629
|
+
if (existsSync55(venvDir)) {
|
|
71411
71630
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
71412
71631
|
}
|
|
71413
71632
|
mkdirSync30(dirname15(venvDir), { recursive: true });
|
|
71414
71633
|
try {
|
|
71415
|
-
|
|
71634
|
+
execFileSync15(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
71416
71635
|
} catch (err) {
|
|
71417
71636
|
const e = err;
|
|
71418
71637
|
throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
|
|
@@ -71424,7 +71643,7 @@ function ensurePythonEnv(opts) {
|
|
|
71424
71643
|
delete childEnv.PIP_TARGET;
|
|
71425
71644
|
delete childEnv.PIP_PREFIX;
|
|
71426
71645
|
delete childEnv.PYTHONUSERBASE;
|
|
71427
|
-
|
|
71646
|
+
execFileSync15(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
|
|
71428
71647
|
} catch (err) {
|
|
71429
71648
|
const e = err;
|
|
71430
71649
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
@@ -71445,15 +71664,15 @@ function ensurePythonEnv(opts) {
|
|
|
71445
71664
|
import { createHash as createHash11 } from "node:crypto";
|
|
71446
71665
|
import {
|
|
71447
71666
|
copyFileSync as copyFileSync9,
|
|
71448
|
-
existsSync as
|
|
71667
|
+
existsSync as existsSync56,
|
|
71449
71668
|
mkdirSync as mkdirSync31,
|
|
71450
71669
|
readFileSync as readFileSync50,
|
|
71451
71670
|
rmSync as rmSync14,
|
|
71452
71671
|
writeFileSync as writeFileSync27
|
|
71453
71672
|
} from "node:fs";
|
|
71454
|
-
import { dirname as dirname16, join as
|
|
71455
|
-
import { homedir as
|
|
71456
|
-
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";
|
|
71457
71676
|
|
|
71458
71677
|
class NodeEnvError extends Error {
|
|
71459
71678
|
stderr;
|
|
@@ -71475,7 +71694,7 @@ var LOCKFILES_FOR = {
|
|
|
71475
71694
|
npm: ["package-lock.json"]
|
|
71476
71695
|
};
|
|
71477
71696
|
function defaultNodeCacheRoot() {
|
|
71478
|
-
return
|
|
71697
|
+
return join52(homedir30(), ".switchroom", "deps", "node");
|
|
71479
71698
|
}
|
|
71480
71699
|
function hashDepInputs(packageJsonPath) {
|
|
71481
71700
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -71484,8 +71703,8 @@ function hashDepInputs(packageJsonPath) {
|
|
|
71484
71703
|
`);
|
|
71485
71704
|
hasher.update(readFileSync50(packageJsonPath));
|
|
71486
71705
|
for (const lockName of ALL_LOCKFILES) {
|
|
71487
|
-
const lockPath =
|
|
71488
|
-
if (
|
|
71706
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71707
|
+
if (existsSync56(lockPath)) {
|
|
71489
71708
|
hasher.update(`
|
|
71490
71709
|
`);
|
|
71491
71710
|
hasher.update(lockName);
|
|
@@ -71500,16 +71719,16 @@ function ensureNodeEnv(opts) {
|
|
|
71500
71719
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
71501
71720
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
71502
71721
|
const installer = opts.installer ?? "bun";
|
|
71503
|
-
if (!
|
|
71722
|
+
if (!existsSync56(packageJsonPath)) {
|
|
71504
71723
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
71505
71724
|
}
|
|
71506
71725
|
const sourceDir = dirname16(packageJsonPath);
|
|
71507
|
-
const envDir =
|
|
71508
|
-
const stampPath =
|
|
71509
|
-
const nodeModulesDir =
|
|
71510
|
-
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");
|
|
71511
71730
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
71512
|
-
if (!force &&
|
|
71731
|
+
if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
|
|
71513
71732
|
const existingHash = readFileSync50(stampPath, "utf8").trim();
|
|
71514
71733
|
if (existingHash === targetHash) {
|
|
71515
71734
|
return {
|
|
@@ -71521,26 +71740,26 @@ function ensureNodeEnv(opts) {
|
|
|
71521
71740
|
};
|
|
71522
71741
|
}
|
|
71523
71742
|
}
|
|
71524
|
-
if (
|
|
71743
|
+
if (existsSync56(envDir)) {
|
|
71525
71744
|
rmSync14(envDir, { recursive: true, force: true });
|
|
71526
71745
|
}
|
|
71527
71746
|
mkdirSync31(envDir, { recursive: true });
|
|
71528
|
-
copyFileSync9(packageJsonPath,
|
|
71747
|
+
copyFileSync9(packageJsonPath, join52(envDir, "package.json"));
|
|
71529
71748
|
let copiedLockfile = false;
|
|
71530
71749
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
71531
|
-
const lockPath =
|
|
71532
|
-
if (
|
|
71533
|
-
copyFileSync9(lockPath,
|
|
71750
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71751
|
+
if (existsSync56(lockPath)) {
|
|
71752
|
+
copyFileSync9(lockPath, join52(envDir, lockName));
|
|
71534
71753
|
copiedLockfile = true;
|
|
71535
71754
|
}
|
|
71536
71755
|
}
|
|
71537
71756
|
try {
|
|
71538
71757
|
if (installer === "bun") {
|
|
71539
71758
|
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
71540
|
-
|
|
71759
|
+
execFileSync16("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
71541
71760
|
} else {
|
|
71542
71761
|
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
71543
|
-
|
|
71762
|
+
execFileSync16("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
71544
71763
|
}
|
|
71545
71764
|
} catch (err) {
|
|
71546
71765
|
const e = err;
|
|
@@ -71559,28 +71778,28 @@ function ensureNodeEnv(opts) {
|
|
|
71559
71778
|
|
|
71560
71779
|
// src/cli/deps.ts
|
|
71561
71780
|
function builtinSkillsRoot() {
|
|
71562
|
-
return resolve35(
|
|
71781
|
+
return resolve35(homedir31(), ".switchroom/skills/_bundled");
|
|
71563
71782
|
}
|
|
71564
71783
|
function registerDepsCommand(program3) {
|
|
71565
71784
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
71566
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) => {
|
|
71567
71786
|
const skillsRoot = builtinSkillsRoot();
|
|
71568
|
-
if (!
|
|
71787
|
+
if (!existsSync57(skillsRoot)) {
|
|
71569
71788
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
71570
71789
|
process.exit(1);
|
|
71571
71790
|
}
|
|
71572
|
-
const skillDir =
|
|
71573
|
-
if (!
|
|
71791
|
+
const skillDir = join53(skillsRoot, skill);
|
|
71792
|
+
if (!existsSync57(skillDir)) {
|
|
71574
71793
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
71575
71794
|
process.exit(1);
|
|
71576
71795
|
}
|
|
71577
|
-
const requirementsPath =
|
|
71578
|
-
const packageJsonPath =
|
|
71579
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
71580
|
-
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));
|
|
71581
71800
|
let did = 0;
|
|
71582
71801
|
if (wantPython) {
|
|
71583
|
-
if (!
|
|
71802
|
+
if (!existsSync57(requirementsPath)) {
|
|
71584
71803
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
71585
71804
|
process.exit(1);
|
|
71586
71805
|
}
|
|
@@ -71604,7 +71823,7 @@ function registerDepsCommand(program3) {
|
|
|
71604
71823
|
}
|
|
71605
71824
|
}
|
|
71606
71825
|
if (wantNode) {
|
|
71607
|
-
if (!
|
|
71826
|
+
if (!existsSync57(packageJsonPath)) {
|
|
71608
71827
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
71609
71828
|
process.exit(1);
|
|
71610
71829
|
}
|
|
@@ -71637,7 +71856,7 @@ function registerDepsCommand(program3) {
|
|
|
71637
71856
|
// src/cli/workspace.ts
|
|
71638
71857
|
init_helpers();
|
|
71639
71858
|
init_loader();
|
|
71640
|
-
import { existsSync as
|
|
71859
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
71641
71860
|
import { resolve as resolve36, sep as sep3 } from "node:path";
|
|
71642
71861
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
71643
71862
|
|
|
@@ -72414,7 +72633,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72414
72633
|
if (!dir)
|
|
72415
72634
|
return;
|
|
72416
72635
|
const gitDir = resolve36(dir, ".git");
|
|
72417
|
-
if (!
|
|
72636
|
+
if (!existsSync58(gitDir)) {
|
|
72418
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.
|
|
72419
72638
|
`);
|
|
72420
72639
|
return;
|
|
@@ -72468,7 +72687,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72468
72687
|
if (!dir)
|
|
72469
72688
|
return;
|
|
72470
72689
|
const gitDir = resolve36(dir, ".git");
|
|
72471
|
-
if (!
|
|
72690
|
+
if (!existsSync58(gitDir)) {
|
|
72472
72691
|
process.stdout.write(`Workspace is not a git repository.
|
|
72473
72692
|
`);
|
|
72474
72693
|
return;
|
|
@@ -72493,7 +72712,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
72493
72712
|
const agentsDir = resolveAgentsDir(config);
|
|
72494
72713
|
const agentDir = resolve36(agentsDir, agentName);
|
|
72495
72714
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
72496
|
-
if (!
|
|
72715
|
+
if (!existsSync58(dir)) {
|
|
72497
72716
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
72498
72717
|
`);
|
|
72499
72718
|
return;
|
|
@@ -72529,8 +72748,8 @@ function safeParseInt(value, fallback) {
|
|
|
72529
72748
|
init_helpers();
|
|
72530
72749
|
init_loader();
|
|
72531
72750
|
init_merge();
|
|
72532
|
-
import { copyFileSync as copyFileSync10, existsSync as
|
|
72533
|
-
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";
|
|
72534
72753
|
init_schema();
|
|
72535
72754
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
72536
72755
|
const config = getConfig(program3);
|
|
@@ -72545,7 +72764,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72545
72764
|
const agentsDir = resolveAgentsDir(config);
|
|
72546
72765
|
const agentDir = resolve37(agentsDir, agentName);
|
|
72547
72766
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72548
|
-
if (!
|
|
72767
|
+
if (!existsSync59(workspaceDir)) {
|
|
72549
72768
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
72550
72769
|
process.exit(1);
|
|
72551
72770
|
}
|
|
@@ -72554,7 +72773,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72554
72773
|
profileName,
|
|
72555
72774
|
profilePath,
|
|
72556
72775
|
workspaceDir,
|
|
72557
|
-
soulPath:
|
|
72776
|
+
soulPath: join54(workspaceDir, "SOUL.md"),
|
|
72558
72777
|
soul: merged.soul
|
|
72559
72778
|
};
|
|
72560
72779
|
}
|
|
@@ -72571,7 +72790,7 @@ function registerSoulCommand(program3) {
|
|
|
72571
72790
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
72572
72791
|
if (!t)
|
|
72573
72792
|
return;
|
|
72574
|
-
if (!
|
|
72793
|
+
if (!existsSync59(t.soulPath)) {
|
|
72575
72794
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
72576
72795
|
process.exit(1);
|
|
72577
72796
|
}
|
|
@@ -72586,7 +72805,7 @@ function registerSoulCommand(program3) {
|
|
|
72586
72805
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
72587
72806
|
process.exit(1);
|
|
72588
72807
|
}
|
|
72589
|
-
const exists =
|
|
72808
|
+
const exists = existsSync59(t.soulPath);
|
|
72590
72809
|
if (exists && !opts.yes) {
|
|
72591
72810
|
if (!isInteractive()) {
|
|
72592
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).`);
|
|
@@ -72601,7 +72820,7 @@ function registerSoulCommand(program3) {
|
|
|
72601
72820
|
let backupPath;
|
|
72602
72821
|
if (exists) {
|
|
72603
72822
|
backupPath = `${t.soulPath}.bak`;
|
|
72604
|
-
if (
|
|
72823
|
+
if (existsSync59(backupPath)) {
|
|
72605
72824
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
72606
72825
|
}
|
|
72607
72826
|
copyFileSync10(t.soulPath, backupPath);
|
|
@@ -72620,8 +72839,8 @@ function registerSoulCommand(program3) {
|
|
|
72620
72839
|
// src/cli/debug.ts
|
|
72621
72840
|
init_helpers();
|
|
72622
72841
|
init_loader();
|
|
72623
|
-
import { existsSync as
|
|
72624
|
-
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";
|
|
72625
72844
|
import { createHash as createHash12 } from "node:crypto";
|
|
72626
72845
|
init_merge();
|
|
72627
72846
|
init_hindsight();
|
|
@@ -72635,8 +72854,8 @@ function sha256(content) {
|
|
|
72635
72854
|
return createHash12("sha256").update(content).digest("hex").slice(0, 16);
|
|
72636
72855
|
}
|
|
72637
72856
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
72638
|
-
const projectsDir =
|
|
72639
|
-
if (!
|
|
72857
|
+
const projectsDir = join55(claudeConfigDir, "projects");
|
|
72858
|
+
if (!existsSync60(projectsDir))
|
|
72640
72859
|
return;
|
|
72641
72860
|
try {
|
|
72642
72861
|
const entries = readdirSync21(projectsDir, { withFileTypes: true });
|
|
@@ -72644,11 +72863,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
72644
72863
|
for (const entry of entries) {
|
|
72645
72864
|
if (!entry.isDirectory())
|
|
72646
72865
|
continue;
|
|
72647
|
-
const projectPath =
|
|
72648
|
-
const transcriptPath =
|
|
72649
|
-
if (!
|
|
72866
|
+
const projectPath = join55(projectsDir, entry.name);
|
|
72867
|
+
const transcriptPath = join55(projectPath, "transcript.jsonl");
|
|
72868
|
+
if (!existsSync60(transcriptPath))
|
|
72650
72869
|
continue;
|
|
72651
|
-
const stat3 =
|
|
72870
|
+
const stat3 = statSync26(transcriptPath);
|
|
72652
72871
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
72653
72872
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
72654
72873
|
}
|
|
@@ -72709,16 +72928,16 @@ function registerDebugCommand(program3) {
|
|
|
72709
72928
|
}
|
|
72710
72929
|
const agentsDir = resolveAgentsDir(config);
|
|
72711
72930
|
const agentDir = resolve38(agentsDir, agentName);
|
|
72712
|
-
if (!
|
|
72931
|
+
if (!existsSync60(agentDir)) {
|
|
72713
72932
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
72714
72933
|
process.exit(1);
|
|
72715
72934
|
}
|
|
72716
72935
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72717
|
-
const claudeConfigDir =
|
|
72718
|
-
const claudeMdPath =
|
|
72719
|
-
const soulMdPath =
|
|
72720
|
-
const workspaceSoulMdPath =
|
|
72721
|
-
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");
|
|
72722
72941
|
const lastN = parseInt(opts.last, 10);
|
|
72723
72942
|
if (isNaN(lastN) || lastN < 1) {
|
|
72724
72943
|
console.error("--last must be a positive integer");
|
|
@@ -72764,7 +72983,7 @@ function registerDebugCommand(program3) {
|
|
|
72764
72983
|
}
|
|
72765
72984
|
console.log(`=== Append System Prompt (per-session) ===
|
|
72766
72985
|
`);
|
|
72767
|
-
const handoffContent =
|
|
72986
|
+
const handoffContent = existsSync60(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
|
|
72768
72987
|
if (handoffContent.trim().length > 0) {
|
|
72769
72988
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
72770
72989
|
console.log(handoffContent);
|
|
@@ -72775,7 +72994,7 @@ function registerDebugCommand(program3) {
|
|
|
72775
72994
|
}
|
|
72776
72995
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
72777
72996
|
`);
|
|
72778
|
-
const claudeMdContent =
|
|
72997
|
+
const claudeMdContent = existsSync60(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
|
|
72779
72998
|
if (claudeMdContent.trim().length > 0) {
|
|
72780
72999
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
72781
73000
|
console.log(claudeMdContent);
|
|
@@ -72786,7 +73005,7 @@ function registerDebugCommand(program3) {
|
|
|
72786
73005
|
}
|
|
72787
73006
|
console.log(`=== Persona (SOUL.md) ===
|
|
72788
73007
|
`);
|
|
72789
|
-
const soulMdContent =
|
|
73008
|
+
const soulMdContent = existsSync60(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync60(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
|
|
72790
73009
|
if (soulMdContent.trim().length > 0) {
|
|
72791
73010
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
72792
73011
|
console.log(soulMdContent);
|
|
@@ -72866,10 +73085,10 @@ function registerDebugCommand(program3) {
|
|
|
72866
73085
|
init_source();
|
|
72867
73086
|
|
|
72868
73087
|
// src/worktree/claim.ts
|
|
72869
|
-
import { execFileSync as
|
|
72870
|
-
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as
|
|
72871
|
-
import { join as
|
|
72872
|
-
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";
|
|
72873
73092
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
72874
73093
|
|
|
72875
73094
|
// src/worktree/registry.ts
|
|
@@ -72879,16 +73098,16 @@ import {
|
|
|
72879
73098
|
readFileSync as readFileSync53,
|
|
72880
73099
|
readdirSync as readdirSync22,
|
|
72881
73100
|
unlinkSync as unlinkSync12,
|
|
72882
|
-
existsSync as
|
|
73101
|
+
existsSync as existsSync61,
|
|
72883
73102
|
renameSync as renameSync12
|
|
72884
73103
|
} from "node:fs";
|
|
72885
|
-
import { join as
|
|
72886
|
-
import { homedir as
|
|
73104
|
+
import { join as join56, resolve as resolve39 } from "node:path";
|
|
73105
|
+
import { homedir as homedir32 } from "node:os";
|
|
72887
73106
|
function registryDir() {
|
|
72888
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
73107
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join56(homedir32(), ".switchroom", "worktrees"));
|
|
72889
73108
|
}
|
|
72890
73109
|
function recordPath(id) {
|
|
72891
|
-
return
|
|
73110
|
+
return join56(registryDir(), `${id}.json`);
|
|
72892
73111
|
}
|
|
72893
73112
|
function ensureDir2() {
|
|
72894
73113
|
mkdirSync32(registryDir(), { recursive: true });
|
|
@@ -72939,7 +73158,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72939
73158
|
const lockDir = registryDir();
|
|
72940
73159
|
mkdirSync33(lockDir, { recursive: true });
|
|
72941
73160
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
72942
|
-
const lockPath =
|
|
73161
|
+
const lockPath = join57(lockDir, `.lock-${lockName}`);
|
|
72943
73162
|
const deadline = Date.now() + 5000;
|
|
72944
73163
|
let fd = null;
|
|
72945
73164
|
while (fd === null) {
|
|
@@ -72966,7 +73185,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72966
73185
|
}
|
|
72967
73186
|
var DEFAULT_CONCURRENCY = 5;
|
|
72968
73187
|
function worktreesBaseDir() {
|
|
72969
|
-
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
73188
|
+
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join57(homedir33(), ".switchroom", "worktree-checkouts"));
|
|
72970
73189
|
}
|
|
72971
73190
|
function shortId() {
|
|
72972
73191
|
return randomBytes12(4).toString("hex");
|
|
@@ -72988,12 +73207,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72988
73207
|
}
|
|
72989
73208
|
function expandHome(p) {
|
|
72990
73209
|
if (p.startsWith("~/"))
|
|
72991
|
-
return
|
|
73210
|
+
return join57(homedir33(), p.slice(2));
|
|
72992
73211
|
return p;
|
|
72993
73212
|
}
|
|
72994
73213
|
async function claimWorktree(input, codeRepos) {
|
|
72995
73214
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
72996
|
-
if (!
|
|
73215
|
+
if (!existsSync62(repoPath)) {
|
|
72997
73216
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
72998
73217
|
}
|
|
72999
73218
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -73016,7 +73235,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73016
73235
|
branch = `task/${taskSuffix}-${id}`;
|
|
73017
73236
|
const baseDir = worktreesBaseDir();
|
|
73018
73237
|
mkdirSync33(baseDir, { recursive: true });
|
|
73019
|
-
worktreePath =
|
|
73238
|
+
worktreePath = join57(baseDir, `${id}-${taskSuffix}`);
|
|
73020
73239
|
const now = new Date().toISOString();
|
|
73021
73240
|
const record2 = {
|
|
73022
73241
|
id,
|
|
@@ -73033,7 +73252,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73033
73252
|
releaseLock();
|
|
73034
73253
|
}
|
|
73035
73254
|
try {
|
|
73036
|
-
|
|
73255
|
+
execFileSync17("git", ["worktree", "add", "-b", branch, worktreePath], {
|
|
73037
73256
|
cwd: repoPath,
|
|
73038
73257
|
stdio: "pipe"
|
|
73039
73258
|
});
|
|
@@ -73046,8 +73265,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73046
73265
|
}
|
|
73047
73266
|
|
|
73048
73267
|
// src/worktree/release.ts
|
|
73049
|
-
import { execFileSync as
|
|
73050
|
-
import { existsSync as
|
|
73268
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
73269
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
73051
73270
|
function releaseWorktree(input) {
|
|
73052
73271
|
const { id } = input;
|
|
73053
73272
|
const record2 = readRecord(id);
|
|
@@ -73055,9 +73274,9 @@ function releaseWorktree(input) {
|
|
|
73055
73274
|
return { released: true };
|
|
73056
73275
|
}
|
|
73057
73276
|
let gitSuccess = true;
|
|
73058
|
-
if (
|
|
73277
|
+
if (existsSync63(record2.path)) {
|
|
73059
73278
|
try {
|
|
73060
|
-
|
|
73279
|
+
execFileSync18("git", ["worktree", "remove", "--force", record2.path], {
|
|
73061
73280
|
cwd: record2.repo,
|
|
73062
73281
|
stdio: "pipe"
|
|
73063
73282
|
});
|
|
@@ -73093,16 +73312,16 @@ function listWorktrees() {
|
|
|
73093
73312
|
}
|
|
73094
73313
|
|
|
73095
73314
|
// src/worktree/reaper.ts
|
|
73096
|
-
import { execFileSync as
|
|
73097
|
-
import { existsSync as
|
|
73315
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73316
|
+
import { existsSync as existsSync64 } from "node:fs";
|
|
73098
73317
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
73099
73318
|
function isPathInUse(path7) {
|
|
73100
73319
|
try {
|
|
73101
|
-
|
|
73320
|
+
execFileSync19("fuser", [path7], { stdio: "pipe" });
|
|
73102
73321
|
return true;
|
|
73103
73322
|
} catch {}
|
|
73104
73323
|
try {
|
|
73105
|
-
const out =
|
|
73324
|
+
const out = execFileSync19("lsof", ["-t", path7], {
|
|
73106
73325
|
stdio: ["ignore", "pipe", "ignore"]
|
|
73107
73326
|
}).toString().trim();
|
|
73108
73327
|
if (out.length > 0)
|
|
@@ -73112,7 +73331,7 @@ function isPathInUse(path7) {
|
|
|
73112
73331
|
}
|
|
73113
73332
|
function hasUncommittedChanges(repoPath, worktreePath) {
|
|
73114
73333
|
try {
|
|
73115
|
-
const out =
|
|
73334
|
+
const out = execFileSync19("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
|
|
73116
73335
|
return out.trim().length > 0;
|
|
73117
73336
|
} catch {
|
|
73118
73337
|
return false;
|
|
@@ -73121,12 +73340,12 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
73121
73340
|
function reapRecord(record2) {
|
|
73122
73341
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
73123
73342
|
let warning = null;
|
|
73124
|
-
if (
|
|
73343
|
+
if (existsSync64(path7)) {
|
|
73125
73344
|
if (hasUncommittedChanges(repo, path7)) {
|
|
73126
73345
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
73127
73346
|
}
|
|
73128
73347
|
try {
|
|
73129
|
-
|
|
73348
|
+
execFileSync19("git", ["worktree", "remove", "--force", path7], {
|
|
73130
73349
|
cwd: repo,
|
|
73131
73350
|
stdio: "pipe"
|
|
73132
73351
|
});
|
|
@@ -73142,7 +73361,7 @@ function runReaper(nowMs) {
|
|
|
73142
73361
|
const warnings = [];
|
|
73143
73362
|
for (const record2 of records) {
|
|
73144
73363
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
73145
|
-
const worktreeExists =
|
|
73364
|
+
const worktreeExists = existsSync64(record2.path);
|
|
73146
73365
|
if (!worktreeExists) {
|
|
73147
73366
|
deleteRecord(record2.id);
|
|
73148
73367
|
reaped.push(record2.id);
|
|
@@ -73271,7 +73490,7 @@ import {
|
|
|
73271
73490
|
rmSync as rmSync15,
|
|
73272
73491
|
writeFileSync as writeFileSync30
|
|
73273
73492
|
} from "node:fs";
|
|
73274
|
-
import { join as
|
|
73493
|
+
import { join as join58 } from "node:path";
|
|
73275
73494
|
function encodeCredentialsFilename(email) {
|
|
73276
73495
|
const SAFE = new Set([
|
|
73277
73496
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -73461,16 +73680,16 @@ function resolveCredentialsDir(env2) {
|
|
|
73461
73680
|
if (explicit && explicit.length > 0)
|
|
73462
73681
|
return explicit;
|
|
73463
73682
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
73464
|
-
return
|
|
73683
|
+
return join58(stateBase, "google-workspace-mcp", "credentials");
|
|
73465
73684
|
}
|
|
73466
73685
|
function writeSeedFile(dir, email, seed) {
|
|
73467
73686
|
mkdirSync34(dir, { recursive: true, mode: 448 });
|
|
73468
73687
|
chmodSync9(dir, 448);
|
|
73469
73688
|
for (const name of readdirSync23(dir)) {
|
|
73470
|
-
rmSync15(
|
|
73689
|
+
rmSync15(join58(dir, name), { force: true, recursive: true });
|
|
73471
73690
|
}
|
|
73472
73691
|
const filename = encodeCredentialsFilename(email);
|
|
73473
|
-
const filePath =
|
|
73692
|
+
const filePath = join58(dir, filename);
|
|
73474
73693
|
writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
|
|
73475
73694
|
chmodSync9(filePath, 384);
|
|
73476
73695
|
return filePath;
|
|
@@ -73628,7 +73847,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
73628
73847
|
|
|
73629
73848
|
// src/cli/apply.ts
|
|
73630
73849
|
init_source();
|
|
73631
|
-
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";
|
|
73632
73851
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
73633
73852
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
73634
73853
|
import readline from "node:readline";
|
|
@@ -73991,16 +74210,16 @@ agents:
|
|
|
73991
74210
|
|
|
73992
74211
|
// src/cli/apply.ts
|
|
73993
74212
|
init_resolver();
|
|
73994
|
-
import { dirname as dirname19, join as
|
|
73995
|
-
import { homedir as
|
|
73996
|
-
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";
|
|
73997
74216
|
init_vault();
|
|
73998
74217
|
init_loader();
|
|
73999
74218
|
init_loader();
|
|
74000
74219
|
|
|
74001
74220
|
// src/cli/update-prompt-hook.ts
|
|
74002
|
-
import { existsSync as
|
|
74003
|
-
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";
|
|
74004
74223
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
74005
74224
|
function updatePromptHookScript() {
|
|
74006
74225
|
return `#!/bin/bash
|
|
@@ -74066,12 +74285,12 @@ exit 0
|
|
|
74066
74285
|
`;
|
|
74067
74286
|
}
|
|
74068
74287
|
function installUpdatePromptHook(agentDir) {
|
|
74069
|
-
const hooksDir =
|
|
74288
|
+
const hooksDir = join59(agentDir, ".claude", "hooks");
|
|
74070
74289
|
mkdirSync35(hooksDir, { recursive: true });
|
|
74071
|
-
const scriptPath =
|
|
74290
|
+
const scriptPath = join59(hooksDir, HOOK_FILENAME);
|
|
74072
74291
|
const desired = updatePromptHookScript();
|
|
74073
74292
|
let installed = false;
|
|
74074
|
-
const existing =
|
|
74293
|
+
const existing = existsSync65(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
|
|
74075
74294
|
if (existing !== desired) {
|
|
74076
74295
|
writeFileSync31(scriptPath, desired, { mode: 493 });
|
|
74077
74296
|
chmodSync10(scriptPath, 493);
|
|
@@ -74081,8 +74300,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
74081
74300
|
chmodSync10(scriptPath, 493);
|
|
74082
74301
|
} catch {}
|
|
74083
74302
|
}
|
|
74084
|
-
const settingsPath =
|
|
74085
|
-
if (!
|
|
74303
|
+
const settingsPath = join59(agentDir, ".claude", "settings.json");
|
|
74304
|
+
if (!existsSync65(settingsPath)) {
|
|
74086
74305
|
return { scriptPath, settingsPath, installed };
|
|
74087
74306
|
}
|
|
74088
74307
|
const raw = readFileSync54(settingsPath, "utf-8");
|
|
@@ -74201,13 +74420,13 @@ function detectInstallType() {
|
|
|
74201
74420
|
// src/cli/operator-uid.ts
|
|
74202
74421
|
import {
|
|
74203
74422
|
chownSync as chownSync3,
|
|
74204
|
-
existsSync as
|
|
74423
|
+
existsSync as existsSync67,
|
|
74205
74424
|
lstatSync as lstatSync7,
|
|
74206
74425
|
readdirSync as readdirSync24,
|
|
74207
74426
|
realpathSync as realpathSync6,
|
|
74208
|
-
statSync as
|
|
74427
|
+
statSync as statSync27
|
|
74209
74428
|
} from "node:fs";
|
|
74210
|
-
import { join as
|
|
74429
|
+
import { join as join61 } from "node:path";
|
|
74211
74430
|
function resolveOperatorUid() {
|
|
74212
74431
|
const sudoUid = process.env.SUDO_UID;
|
|
74213
74432
|
if (sudoUid !== undefined) {
|
|
@@ -74223,19 +74442,19 @@ function resolveOperatorUid() {
|
|
|
74223
74442
|
return;
|
|
74224
74443
|
}
|
|
74225
74444
|
function operatorOwnedPaths(home2) {
|
|
74226
|
-
const root =
|
|
74445
|
+
const root = join61(home2, ".switchroom");
|
|
74227
74446
|
return [
|
|
74228
|
-
|
|
74229
|
-
|
|
74230
|
-
|
|
74231
|
-
|
|
74232
|
-
|
|
74233
|
-
|
|
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")
|
|
74234
74453
|
];
|
|
74235
74454
|
}
|
|
74236
74455
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
74237
74456
|
const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
|
|
74238
|
-
const exists = deps.exists ?? ((p) =>
|
|
74457
|
+
const exists = deps.exists ?? ((p) => existsSync67(p));
|
|
74239
74458
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
74240
74459
|
try {
|
|
74241
74460
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -74245,7 +74464,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74245
74464
|
});
|
|
74246
74465
|
const isDir = deps.isDir ?? ((p) => {
|
|
74247
74466
|
try {
|
|
74248
|
-
return
|
|
74467
|
+
return statSync27(p).isDirectory();
|
|
74249
74468
|
} catch {
|
|
74250
74469
|
return false;
|
|
74251
74470
|
}
|
|
@@ -74279,7 +74498,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74279
74498
|
} catch {}
|
|
74280
74499
|
if (isDir(target)) {
|
|
74281
74500
|
for (const entry of readdir2(target)) {
|
|
74282
|
-
visit(
|
|
74501
|
+
visit(join61(target, entry));
|
|
74283
74502
|
}
|
|
74284
74503
|
}
|
|
74285
74504
|
};
|
|
@@ -74293,17 +74512,17 @@ var EMBEDDED_EXAMPLES = {
|
|
|
74293
74512
|
switchroom: switchroom_default,
|
|
74294
74513
|
minimal: minimal_default
|
|
74295
74514
|
};
|
|
74296
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
74515
|
+
var DEFAULT_COMPOSE_PATH2 = join62(homedir35(), ".switchroom", "compose", "docker-compose.yml");
|
|
74297
74516
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
74298
74517
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
74299
74518
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
74300
74519
|
if (isCustomPath && ctx.customVaultPath) {
|
|
74301
74520
|
return dirname19(ctx.customVaultPath);
|
|
74302
74521
|
}
|
|
74303
|
-
return
|
|
74522
|
+
return join62(homeDir, ".switchroom", "vault");
|
|
74304
74523
|
}
|
|
74305
74524
|
function inspectVaultBindMountDir(vaultDir) {
|
|
74306
|
-
if (!
|
|
74525
|
+
if (!existsSync68(vaultDir))
|
|
74307
74526
|
return { kind: "missing" };
|
|
74308
74527
|
const entries = readdirSync25(vaultDir);
|
|
74309
74528
|
const unknown = [];
|
|
@@ -74329,42 +74548,42 @@ function hasVaultRefs(value) {
|
|
|
74329
74548
|
return false;
|
|
74330
74549
|
}
|
|
74331
74550
|
async function ensureHostMountSources(config) {
|
|
74332
|
-
const home2 =
|
|
74551
|
+
const home2 = homedir35();
|
|
74333
74552
|
const dirs = [
|
|
74334
|
-
|
|
74335
|
-
|
|
74336
|
-
|
|
74337
|
-
|
|
74338
|
-
|
|
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")
|
|
74339
74558
|
];
|
|
74340
74559
|
for (const name of Object.keys(config.agents)) {
|
|
74341
|
-
dirs.push(
|
|
74342
|
-
dirs.push(
|
|
74343
|
-
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));
|
|
74344
74563
|
}
|
|
74345
74564
|
for (const dir of dirs) {
|
|
74346
74565
|
await mkdir(dir, { recursive: true });
|
|
74347
74566
|
}
|
|
74348
|
-
const autoUnlockPath =
|
|
74349
|
-
if (!
|
|
74567
|
+
const autoUnlockPath = join62(home2, ".switchroom", "vault-auto-unlock");
|
|
74568
|
+
if (!existsSync68(autoUnlockPath)) {
|
|
74350
74569
|
writeFileSync32(autoUnlockPath, "", { mode: 384 });
|
|
74351
74570
|
}
|
|
74352
|
-
const auditLogPath =
|
|
74353
|
-
if (!
|
|
74571
|
+
const auditLogPath = join62(home2, ".switchroom", "vault-audit.log");
|
|
74572
|
+
if (!existsSync68(auditLogPath)) {
|
|
74354
74573
|
writeFileSync32(auditLogPath, "", { mode: 420 });
|
|
74355
74574
|
}
|
|
74356
|
-
const grantsDbPath =
|
|
74357
|
-
if (!
|
|
74575
|
+
const grantsDbPath = join62(home2, ".switchroom", "vault-grants.db");
|
|
74576
|
+
if (!existsSync68(grantsDbPath)) {
|
|
74358
74577
|
writeFileSync32(grantsDbPath, "", { mode: 384 });
|
|
74359
74578
|
}
|
|
74360
|
-
const hostdAuditLogPath =
|
|
74361
|
-
if (!
|
|
74579
|
+
const hostdAuditLogPath = join62(home2, ".switchroom", "host-control-audit.log");
|
|
74580
|
+
if (!existsSync68(hostdAuditLogPath)) {
|
|
74362
74581
|
writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
|
|
74363
74582
|
}
|
|
74364
74583
|
}
|
|
74365
74584
|
function detectComposeV2() {
|
|
74366
74585
|
try {
|
|
74367
|
-
const out =
|
|
74586
|
+
const out = execFileSync20("docker", ["compose", "version"], {
|
|
74368
74587
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74369
74588
|
encoding: "utf8"
|
|
74370
74589
|
});
|
|
@@ -74379,7 +74598,7 @@ ${out.trim()}`;
|
|
|
74379
74598
|
}
|
|
74380
74599
|
function runApplyPreflight(config, opts = {}) {
|
|
74381
74600
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
74382
|
-
if (hasVaultRefs(config) && !
|
|
74601
|
+
if (hasVaultRefs(config) && !existsSync68(vaultPath)) {
|
|
74383
74602
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
74384
74603
|
}
|
|
74385
74604
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -74390,7 +74609,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
74390
74609
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
74391
74610
|
}
|
|
74392
74611
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
74393
|
-
if (!
|
|
74612
|
+
if (!existsSync68(vaultPath))
|
|
74394
74613
|
return;
|
|
74395
74614
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
74396
74615
|
if (!passphrase)
|
|
@@ -74429,10 +74648,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
74429
74648
|
`));
|
|
74430
74649
|
}
|
|
74431
74650
|
}
|
|
74432
|
-
function writeInstallTypeCache(homeDir =
|
|
74651
|
+
function writeInstallTypeCache(homeDir = homedir35()) {
|
|
74433
74652
|
const ctx = detectInstallType();
|
|
74434
|
-
const dir =
|
|
74435
|
-
const out =
|
|
74653
|
+
const dir = join62(homeDir, ".switchroom");
|
|
74654
|
+
const out = join62(dir, "install-type.json");
|
|
74436
74655
|
const tmp = `${out}.tmp`;
|
|
74437
74656
|
mkdirSync36(dir, { recursive: true });
|
|
74438
74657
|
const payload = {
|
|
@@ -74481,14 +74700,14 @@ Applying switchroom config...
|
|
|
74481
74700
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
74482
74701
|
`));
|
|
74483
74702
|
try {
|
|
74484
|
-
installUpdatePromptHook(
|
|
74703
|
+
installUpdatePromptHook(join62(agentsDir, name));
|
|
74485
74704
|
} catch (hookErr) {
|
|
74486
74705
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
74487
74706
|
`));
|
|
74488
74707
|
}
|
|
74489
74708
|
try {
|
|
74490
74709
|
const uid = allocateAgentUid(name);
|
|
74491
|
-
alignAgentUid(name,
|
|
74710
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74492
74711
|
confirm: !options.nonInteractive,
|
|
74493
74712
|
writeOut
|
|
74494
74713
|
});
|
|
@@ -74525,7 +74744,7 @@ Applying switchroom config...
|
|
|
74525
74744
|
for (const name of agentNames) {
|
|
74526
74745
|
try {
|
|
74527
74746
|
const uid = allocateAgentUid(name);
|
|
74528
|
-
alignAgentUid(name,
|
|
74747
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74529
74748
|
confirm: !options.nonInteractive,
|
|
74530
74749
|
writeOut
|
|
74531
74750
|
});
|
|
@@ -74539,7 +74758,7 @@ Applying switchroom config...
|
|
|
74539
74758
|
}
|
|
74540
74759
|
const vaultPathConfigured = config.vault?.path;
|
|
74541
74760
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
74542
|
-
const migrationResult = migrateVaultLayout(
|
|
74761
|
+
const migrationResult = migrateVaultLayout(homedir35(), {
|
|
74543
74762
|
customVaultPath
|
|
74544
74763
|
});
|
|
74545
74764
|
switch (migrationResult.kind) {
|
|
@@ -74565,7 +74784,7 @@ Applying switchroom config...
|
|
|
74565
74784
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
74566
74785
|
process.exit(4);
|
|
74567
74786
|
}
|
|
74568
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
74787
|
+
const postMigrationInspect = inspectVaultLayout(homedir35());
|
|
74569
74788
|
const acceptable = [
|
|
74570
74789
|
"no-vault",
|
|
74571
74790
|
"already-migrated",
|
|
@@ -74580,7 +74799,7 @@ Applying switchroom config...
|
|
|
74580
74799
|
`));
|
|
74581
74800
|
process.exit(5);
|
|
74582
74801
|
}
|
|
74583
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
74802
|
+
const vaultDir = resolveVaultBindMountDir(homedir35(), {
|
|
74584
74803
|
migrationKind: migrationResult.kind,
|
|
74585
74804
|
customVaultPath
|
|
74586
74805
|
});
|
|
@@ -74610,7 +74829,7 @@ Applying switchroom config...
|
|
|
74610
74829
|
imageTag: composeImageTag,
|
|
74611
74830
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
74612
74831
|
buildContext: options.buildContext,
|
|
74613
|
-
homeDir:
|
|
74832
|
+
homeDir: homedir35(),
|
|
74614
74833
|
switchroomConfigPath,
|
|
74615
74834
|
operatorUid
|
|
74616
74835
|
});
|
|
@@ -74630,7 +74849,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
74630
74849
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
74631
74850
|
`));
|
|
74632
74851
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
74633
|
-
const restored = restoreOperatorOwnership(
|
|
74852
|
+
const restored = restoreOperatorOwnership(homedir35(), operatorUid);
|
|
74634
74853
|
if (restored.length > 0) {
|
|
74635
74854
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
74636
74855
|
`));
|
|
@@ -74678,7 +74897,7 @@ function copyExampleConfig2(name) {
|
|
|
74678
74897
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
74679
74898
|
}
|
|
74680
74899
|
const dest = resolve42(process.cwd(), "switchroom.yaml");
|
|
74681
|
-
if (
|
|
74900
|
+
if (existsSync68(dest)) {
|
|
74682
74901
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
74683
74902
|
return;
|
|
74684
74903
|
}
|
|
@@ -74689,7 +74908,7 @@ function copyExampleConfig2(name) {
|
|
|
74689
74908
|
return;
|
|
74690
74909
|
}
|
|
74691
74910
|
const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
74692
|
-
if (!
|
|
74911
|
+
if (!existsSync68(exampleFile)) {
|
|
74693
74912
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
74694
74913
|
}
|
|
74695
74914
|
copyFileSync11(exampleFile, dest);
|
|
@@ -74700,8 +74919,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
74700
74919
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
74701
74920
|
const unwritable = [];
|
|
74702
74921
|
for (const name of targets) {
|
|
74703
|
-
const startSh =
|
|
74704
|
-
if (!
|
|
74922
|
+
const startSh = join62(agentsDir, name, "start.sh");
|
|
74923
|
+
if (!existsSync68(startSh))
|
|
74705
74924
|
continue;
|
|
74706
74925
|
try {
|
|
74707
74926
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -74879,9 +75098,9 @@ function runRedactStdin() {
|
|
|
74879
75098
|
}
|
|
74880
75099
|
|
|
74881
75100
|
// src/cli/status-ask.ts
|
|
74882
|
-
import { readFileSync as readFileSync55, existsSync as
|
|
74883
|
-
import { join as
|
|
74884
|
-
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";
|
|
74885
75104
|
|
|
74886
75105
|
// src/status-ask/report.ts
|
|
74887
75106
|
function parseJsonl(content) {
|
|
@@ -75202,7 +75421,7 @@ function runReport(opts) {
|
|
|
75202
75421
|
function resolveSources(explicitPath) {
|
|
75203
75422
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
75204
75423
|
const trimmed = explicitPath.trim();
|
|
75205
|
-
if (!
|
|
75424
|
+
if (!existsSync69(trimmed)) {
|
|
75206
75425
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
75207
75426
|
`);
|
|
75208
75427
|
process.exit(1);
|
|
@@ -75216,9 +75435,9 @@ function resolveSources(explicitPath) {
|
|
|
75216
75435
|
const config = loadConfig();
|
|
75217
75436
|
agentsDir = resolveAgentsDir(config);
|
|
75218
75437
|
} catch {
|
|
75219
|
-
agentsDir =
|
|
75438
|
+
agentsDir = join63(homedir36(), ".switchroom", "agents");
|
|
75220
75439
|
}
|
|
75221
|
-
if (!
|
|
75440
|
+
if (!existsSync69(agentsDir))
|
|
75222
75441
|
return [];
|
|
75223
75442
|
const sources = [];
|
|
75224
75443
|
let entries;
|
|
@@ -75228,8 +75447,8 @@ function resolveSources(explicitPath) {
|
|
|
75228
75447
|
return [];
|
|
75229
75448
|
}
|
|
75230
75449
|
for (const name of entries) {
|
|
75231
|
-
const path8 =
|
|
75232
|
-
if (
|
|
75450
|
+
const path8 = join63(agentsDir, name, "runtime-metrics.jsonl");
|
|
75451
|
+
if (existsSync69(path8)) {
|
|
75233
75452
|
sources.push({ path: path8, agent: name });
|
|
75234
75453
|
}
|
|
75235
75454
|
}
|
|
@@ -75250,17 +75469,17 @@ function inferAgentFromPath(p) {
|
|
|
75250
75469
|
|
|
75251
75470
|
// src/cli/agent-config.ts
|
|
75252
75471
|
init_helpers();
|
|
75253
|
-
import { join as
|
|
75254
|
-
import { homedir as
|
|
75472
|
+
import { join as join64 } from "node:path";
|
|
75473
|
+
import { homedir as homedir37 } from "node:os";
|
|
75255
75474
|
import {
|
|
75256
|
-
existsSync as
|
|
75475
|
+
existsSync as existsSync70,
|
|
75257
75476
|
mkdirSync as mkdirSync37,
|
|
75258
75477
|
appendFileSync as appendFileSync4,
|
|
75259
75478
|
readFileSync as readFileSync56
|
|
75260
75479
|
} from "node:fs";
|
|
75261
|
-
var AUDIT_ROOT =
|
|
75480
|
+
var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
|
|
75262
75481
|
function auditPathFor(agent) {
|
|
75263
|
-
return
|
|
75482
|
+
return join64(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
75264
75483
|
}
|
|
75265
75484
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
75266
75485
|
const row = {
|
|
@@ -75274,7 +75493,7 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
75274
75493
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75275
75494
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
75276
75495
|
try {
|
|
75277
|
-
if (!
|
|
75496
|
+
if (!existsSync70(dir)) {
|
|
75278
75497
|
mkdirSync37(dir, { recursive: true });
|
|
75279
75498
|
}
|
|
75280
75499
|
appendFileSync4(path8, JSON.stringify(row) + `
|
|
@@ -75286,7 +75505,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
75286
75505
|
return true;
|
|
75287
75506
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
75288
75507
|
try {
|
|
75289
|
-
if (
|
|
75508
|
+
if (existsSync70(probe2))
|
|
75290
75509
|
return true;
|
|
75291
75510
|
} catch {}
|
|
75292
75511
|
return false;
|
|
@@ -75347,7 +75566,7 @@ function getAgentSlice(config, agent) {
|
|
|
75347
75566
|
}
|
|
75348
75567
|
function readAuditTail(agent, limit, opts = {}) {
|
|
75349
75568
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75350
|
-
if (!
|
|
75569
|
+
if (!existsSync70(path8))
|
|
75351
75570
|
return [];
|
|
75352
75571
|
let raw;
|
|
75353
75572
|
try {
|
|
@@ -75507,32 +75726,32 @@ var import_yaml14 = __toESM(require_dist(), 1);
|
|
|
75507
75726
|
init_paths();
|
|
75508
75727
|
import {
|
|
75509
75728
|
closeSync as closeSync13,
|
|
75510
|
-
existsSync as
|
|
75729
|
+
existsSync as existsSync71,
|
|
75511
75730
|
fsyncSync as fsyncSync6,
|
|
75512
75731
|
mkdirSync as mkdirSync38,
|
|
75513
75732
|
openSync as openSync13,
|
|
75514
75733
|
readdirSync as readdirSync27,
|
|
75515
75734
|
readFileSync as readFileSync57,
|
|
75516
75735
|
renameSync as renameSync14,
|
|
75517
|
-
statSync as
|
|
75736
|
+
statSync as statSync28,
|
|
75518
75737
|
unlinkSync as unlinkSync14,
|
|
75519
75738
|
writeSync as writeSync8
|
|
75520
75739
|
} from "node:fs";
|
|
75521
|
-
import { join as
|
|
75740
|
+
import { join as join65, resolve as resolve43 } from "node:path";
|
|
75522
75741
|
var STAGING_SUBDIR = ".staging";
|
|
75523
75742
|
function overlayPathsFor(agent, opts = {}) {
|
|
75524
75743
|
const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
75525
|
-
const scheduleDir =
|
|
75526
|
-
const scheduleStagingDir =
|
|
75527
|
-
const skillsDir =
|
|
75528
|
-
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);
|
|
75529
75748
|
return {
|
|
75530
75749
|
agentRoot: base,
|
|
75531
75750
|
scheduleDir,
|
|
75532
75751
|
scheduleStagingDir,
|
|
75533
75752
|
skillsDir,
|
|
75534
75753
|
skillsStagingDir,
|
|
75535
|
-
lockPath:
|
|
75754
|
+
lockPath: join65(base, ".lock"),
|
|
75536
75755
|
stagingDir: scheduleStagingDir
|
|
75537
75756
|
};
|
|
75538
75757
|
}
|
|
@@ -75558,7 +75777,7 @@ function withAgentLock(paths, fn) {
|
|
|
75558
75777
|
if (e.code !== "EEXIST")
|
|
75559
75778
|
throw err;
|
|
75560
75779
|
try {
|
|
75561
|
-
const age = Date.now() -
|
|
75780
|
+
const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
|
|
75562
75781
|
if (age > 30000) {
|
|
75563
75782
|
unlinkSync14(paths.lockPath);
|
|
75564
75783
|
continue;
|
|
@@ -75586,8 +75805,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75586
75805
|
const paths = overlayPathsFor(agent, opts);
|
|
75587
75806
|
return withAgentLock(paths, () => {
|
|
75588
75807
|
ensureDirs(paths);
|
|
75589
|
-
const stagingPath =
|
|
75590
|
-
const finalPath =
|
|
75808
|
+
const stagingPath = join65(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
75809
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75591
75810
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75592
75811
|
try {
|
|
75593
75812
|
writeSync8(fd, yamlText);
|
|
@@ -75603,8 +75822,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75603
75822
|
const paths = overlayPathsFor(agent, opts);
|
|
75604
75823
|
return withAgentLock(paths, () => {
|
|
75605
75824
|
ensureSkillsDirs(paths);
|
|
75606
|
-
const stagingPath =
|
|
75607
|
-
const finalPath =
|
|
75825
|
+
const stagingPath = join65(paths.skillsStagingDir, `${slug}.yaml`);
|
|
75826
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75608
75827
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75609
75828
|
try {
|
|
75610
75829
|
writeSync8(fd, yamlText);
|
|
@@ -75619,8 +75838,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75619
75838
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
75620
75839
|
const paths = overlayPathsFor(agent, opts);
|
|
75621
75840
|
return withAgentLock(paths, () => {
|
|
75622
|
-
const finalPath =
|
|
75623
|
-
if (!
|
|
75841
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75842
|
+
if (!existsSync71(finalPath))
|
|
75624
75843
|
return false;
|
|
75625
75844
|
unlinkSync14(finalPath);
|
|
75626
75845
|
return true;
|
|
@@ -75628,13 +75847,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
75628
75847
|
}
|
|
75629
75848
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
75630
75849
|
const paths = overlayPathsFor(agent, opts);
|
|
75631
|
-
if (!
|
|
75850
|
+
if (!existsSync71(paths.skillsDir))
|
|
75632
75851
|
return [];
|
|
75633
75852
|
const out = [];
|
|
75634
75853
|
for (const name of readdirSync27(paths.skillsDir)) {
|
|
75635
75854
|
if (!/\.ya?ml$/i.test(name))
|
|
75636
75855
|
continue;
|
|
75637
|
-
const full =
|
|
75856
|
+
const full = join65(paths.skillsDir, name);
|
|
75638
75857
|
try {
|
|
75639
75858
|
const raw = readFileSync57(full, "utf-8");
|
|
75640
75859
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75646,8 +75865,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
75646
75865
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
75647
75866
|
const paths = overlayPathsFor(agent, opts);
|
|
75648
75867
|
return withAgentLock(paths, () => {
|
|
75649
|
-
const finalPath =
|
|
75650
|
-
if (!
|
|
75868
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75869
|
+
if (!existsSync71(finalPath))
|
|
75651
75870
|
return false;
|
|
75652
75871
|
unlinkSync14(finalPath);
|
|
75653
75872
|
return true;
|
|
@@ -75655,13 +75874,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
75655
75874
|
}
|
|
75656
75875
|
function listOverlayEntries(agent, opts = {}) {
|
|
75657
75876
|
const paths = overlayPathsFor(agent, opts);
|
|
75658
|
-
if (!
|
|
75877
|
+
if (!existsSync71(paths.scheduleDir))
|
|
75659
75878
|
return [];
|
|
75660
75879
|
const out = [];
|
|
75661
75880
|
for (const name of readdirSync27(paths.scheduleDir)) {
|
|
75662
75881
|
if (!/\.ya?ml$/i.test(name))
|
|
75663
75882
|
continue;
|
|
75664
|
-
const full =
|
|
75883
|
+
const full = join65(paths.scheduleDir, name);
|
|
75665
75884
|
try {
|
|
75666
75885
|
const raw = readFileSync57(full, "utf-8");
|
|
75667
75886
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75806,7 +76025,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
75806
76025
|
// src/cli/agent-config-pending.ts
|
|
75807
76026
|
import {
|
|
75808
76027
|
closeSync as closeSync14,
|
|
75809
|
-
existsSync as
|
|
76028
|
+
existsSync as existsSync72,
|
|
75810
76029
|
fsyncSync as fsyncSync7,
|
|
75811
76030
|
mkdirSync as mkdirSync39,
|
|
75812
76031
|
openSync as openSync14,
|
|
@@ -75817,12 +76036,12 @@ import {
|
|
|
75817
76036
|
writeFileSync as writeFileSync33,
|
|
75818
76037
|
writeSync as writeSync9
|
|
75819
76038
|
} from "node:fs";
|
|
75820
|
-
import { join as
|
|
76039
|
+
import { join as join66 } from "node:path";
|
|
75821
76040
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
75822
76041
|
var STAGE_ID_PREFIX = "cap_";
|
|
75823
76042
|
function pendingDir(agent, opts = {}) {
|
|
75824
76043
|
const paths = overlayPathsFor(agent, opts);
|
|
75825
|
-
return
|
|
76044
|
+
return join66(paths.scheduleDir, ".pending");
|
|
75826
76045
|
}
|
|
75827
76046
|
function ensurePendingDir(agent, opts = {}) {
|
|
75828
76047
|
const dir = pendingDir(agent, opts);
|
|
@@ -75835,8 +76054,8 @@ function newStageId() {
|
|
|
75835
76054
|
function stagePendingScheduleEntry(opts) {
|
|
75836
76055
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
75837
76056
|
const stageId = opts.stageId ?? newStageId();
|
|
75838
|
-
const yamlPath =
|
|
75839
|
-
const metaPath =
|
|
76057
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76058
|
+
const metaPath = join66(dir, `${stageId}.meta.json`);
|
|
75840
76059
|
const meta = {
|
|
75841
76060
|
v: 1,
|
|
75842
76061
|
stage_id: stageId,
|
|
@@ -75863,16 +76082,16 @@ function stagePendingScheduleEntry(opts) {
|
|
|
75863
76082
|
}
|
|
75864
76083
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
75865
76084
|
const dir = pendingDir(agent, opts);
|
|
75866
|
-
if (!
|
|
76085
|
+
if (!existsSync72(dir))
|
|
75867
76086
|
return [];
|
|
75868
76087
|
const out = [];
|
|
75869
76088
|
for (const name of readdirSync28(dir).sort()) {
|
|
75870
76089
|
if (!name.endsWith(".meta.json"))
|
|
75871
76090
|
continue;
|
|
75872
76091
|
const stageId = name.slice(0, -".meta.json".length);
|
|
75873
|
-
const metaPath =
|
|
75874
|
-
const yamlPath =
|
|
75875
|
-
if (!
|
|
76092
|
+
const metaPath = join66(dir, name);
|
|
76093
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76094
|
+
if (!existsSync72(yamlPath))
|
|
75876
76095
|
continue;
|
|
75877
76096
|
try {
|
|
75878
76097
|
const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
|
|
@@ -75890,8 +76109,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
75890
76109
|
return { committed: false, reason: "not_found" };
|
|
75891
76110
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
75892
76111
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
75893
|
-
const finalPath =
|
|
75894
|
-
if (
|
|
76112
|
+
const finalPath = join66(paths.scheduleDir, `${slug}.yaml`);
|
|
76113
|
+
if (existsSync72(finalPath)) {
|
|
75895
76114
|
return { committed: false, reason: "slug_collision" };
|
|
75896
76115
|
}
|
|
75897
76116
|
renameSync15(match.yamlPath, finalPath);
|
|
@@ -75913,7 +76132,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
75913
76132
|
}
|
|
75914
76133
|
|
|
75915
76134
|
// src/cli/agent-config-write.ts
|
|
75916
|
-
import { existsSync as
|
|
76135
|
+
import { existsSync as existsSync73, readFileSync as readFileSync59 } from "node:fs";
|
|
75917
76136
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
75918
76137
|
function checkOperatorContext(verb, env2 = process.env) {
|
|
75919
76138
|
if (env2.SWITCHROOM_OPERATOR === "1")
|
|
@@ -76147,7 +76366,7 @@ function scheduleRemove(opts) {
|
|
|
76147
76366
|
}
|
|
76148
76367
|
let priorContent = null;
|
|
76149
76368
|
try {
|
|
76150
|
-
if (
|
|
76369
|
+
if (existsSync73(match.path))
|
|
76151
76370
|
priorContent = readFileSync59(match.path, "utf-8");
|
|
76152
76371
|
} catch {}
|
|
76153
76372
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -76340,10 +76559,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
76340
76559
|
|
|
76341
76560
|
// src/cli/agent-config-skill-write.ts
|
|
76342
76561
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
76343
|
-
import { existsSync as
|
|
76562
|
+
import { existsSync as existsSync74 } from "node:fs";
|
|
76344
76563
|
init_reconcile_default_skills();
|
|
76345
76564
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
76346
|
-
import { join as
|
|
76565
|
+
import { join as join67 } from "node:path";
|
|
76347
76566
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
76348
76567
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
76349
76568
|
function exitCodeFor2(code) {
|
|
@@ -76418,8 +76637,8 @@ function skillInstall(opts) {
|
|
|
76418
76637
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
76419
76638
|
}
|
|
76420
76639
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
76421
|
-
const skillPath =
|
|
76422
|
-
if (!
|
|
76640
|
+
const skillPath = join67(poolDir, skillName);
|
|
76641
|
+
if (!existsSync74(skillPath)) {
|
|
76423
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.`);
|
|
76424
76643
|
}
|
|
76425
76644
|
const yamlText = import_yaml15.stringify({ skills: [skillName] });
|
|
@@ -76603,23 +76822,23 @@ init_source();
|
|
|
76603
76822
|
init_helpers();
|
|
76604
76823
|
init_loader();
|
|
76605
76824
|
import {
|
|
76606
|
-
existsSync as
|
|
76825
|
+
existsSync as existsSync76,
|
|
76607
76826
|
readdirSync as readdirSync29,
|
|
76608
76827
|
readFileSync as readFileSync61,
|
|
76609
76828
|
renameSync as renameSync16,
|
|
76610
|
-
statSync as
|
|
76829
|
+
statSync as statSync29,
|
|
76611
76830
|
unlinkSync as unlinkSync16
|
|
76612
76831
|
} from "node:fs";
|
|
76613
76832
|
import { createHash as createHash13 } from "node:crypto";
|
|
76614
|
-
import { join as
|
|
76833
|
+
import { join as join68 } from "node:path";
|
|
76615
76834
|
function planCronUnitRenames(agentsDir, agents) {
|
|
76616
76835
|
const plans = [];
|
|
76617
76836
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
76618
76837
|
const schedule = agentConfig.schedule ?? [];
|
|
76619
76838
|
if (schedule.length === 0)
|
|
76620
76839
|
continue;
|
|
76621
|
-
const telegramDir =
|
|
76622
|
-
if (!
|
|
76840
|
+
const telegramDir = join68(agentsDir, agentName, "telegram");
|
|
76841
|
+
if (!existsSync76(telegramDir))
|
|
76623
76842
|
continue;
|
|
76624
76843
|
let entries;
|
|
76625
76844
|
try {
|
|
@@ -76640,8 +76859,8 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
76640
76859
|
continue;
|
|
76641
76860
|
plans.push({
|
|
76642
76861
|
agent: agentName,
|
|
76643
|
-
from:
|
|
76644
|
-
to:
|
|
76862
|
+
from: join68(telegramDir, file),
|
|
76863
|
+
to: join68(telegramDir, canonical),
|
|
76645
76864
|
scheduleIdx: idx,
|
|
76646
76865
|
entry
|
|
76647
76866
|
});
|
|
@@ -76653,7 +76872,7 @@ function sha256File2(path8) {
|
|
|
76653
76872
|
return createHash13("sha256").update(readFileSync61(path8)).digest("hex");
|
|
76654
76873
|
}
|
|
76655
76874
|
function renamePair(from, to, opts = {}) {
|
|
76656
|
-
if (
|
|
76875
|
+
if (existsSync76(to)) {
|
|
76657
76876
|
let identical = false;
|
|
76658
76877
|
try {
|
|
76659
76878
|
identical = sha256File2(from) === sha256File2(to);
|
|
@@ -76746,7 +76965,7 @@ function registerMigrateCommand(program3) {
|
|
|
76746
76965
|
const fromSidecar = p.from.replace(/\.sh$/, ".source");
|
|
76747
76966
|
const toSidecar = p.to.replace(/\.sh$/, ".source");
|
|
76748
76967
|
let sidecarStatus = null;
|
|
76749
|
-
if (
|
|
76968
|
+
if (existsSync76(fromSidecar) && statSync29(fromSidecar).isFile()) {
|
|
76750
76969
|
sidecarStatus = renamePair(fromSidecar, toSidecar);
|
|
76751
76970
|
}
|
|
76752
76971
|
switch (status.kind) {
|
|
@@ -76778,9 +76997,9 @@ function registerMigrateCommand(program3) {
|
|
|
76778
76997
|
// src/cli/hostd.ts
|
|
76779
76998
|
init_source();
|
|
76780
76999
|
init_helpers();
|
|
76781
|
-
import { existsSync as
|
|
76782
|
-
import { homedir as
|
|
76783
|
-
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";
|
|
76784
77003
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
76785
77004
|
init_audit_reader();
|
|
76786
77005
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -76871,14 +77090,14 @@ networks:
|
|
|
76871
77090
|
`;
|
|
76872
77091
|
}
|
|
76873
77092
|
function hostdDir() {
|
|
76874
|
-
return
|
|
77093
|
+
return join69(homedir38(), ".switchroom", "hostd");
|
|
76875
77094
|
}
|
|
76876
77095
|
function hostdComposePath() {
|
|
76877
|
-
return
|
|
77096
|
+
return join69(hostdDir(), "docker-compose.yml");
|
|
76878
77097
|
}
|
|
76879
77098
|
function backupExistingCompose() {
|
|
76880
77099
|
const p = hostdComposePath();
|
|
76881
|
-
if (!
|
|
77100
|
+
if (!existsSync77(p))
|
|
76882
77101
|
return null;
|
|
76883
77102
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
76884
77103
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -76913,7 +77132,7 @@ async function doInstall(opts, program3) {
|
|
|
76913
77132
|
const composePath = hostdComposePath();
|
|
76914
77133
|
mkdirSync40(dir, { recursive: true });
|
|
76915
77134
|
const yaml = renderHostdComposeFile({
|
|
76916
|
-
hostHome:
|
|
77135
|
+
hostHome: homedir38(),
|
|
76917
77136
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76918
77137
|
operatorUid: resolveOperatorUid()
|
|
76919
77138
|
});
|
|
@@ -76955,7 +77174,7 @@ function doStatus() {
|
|
|
76955
77174
|
const composeYml = hostdComposePath();
|
|
76956
77175
|
console.log(source_default.bold("switchroom-hostd"));
|
|
76957
77176
|
console.log("");
|
|
76958
|
-
if (!
|
|
77177
|
+
if (!existsSync77(composeYml)) {
|
|
76959
77178
|
console.log(source_default.yellow(" compose: not installed"));
|
|
76960
77179
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
76961
77180
|
return;
|
|
@@ -76976,15 +77195,15 @@ function doStatus() {
|
|
|
76976
77195
|
} else {
|
|
76977
77196
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
76978
77197
|
}
|
|
76979
|
-
if (
|
|
77198
|
+
if (existsSync77(dir)) {
|
|
76980
77199
|
const entries = [];
|
|
76981
77200
|
try {
|
|
76982
77201
|
for (const name of readdirSync30(dir)) {
|
|
76983
77202
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
76984
77203
|
continue;
|
|
76985
|
-
const sockPath =
|
|
76986
|
-
if (
|
|
76987
|
-
const st =
|
|
77204
|
+
const sockPath = join69(dir, name, "sock");
|
|
77205
|
+
if (existsSync77(sockPath)) {
|
|
77206
|
+
const st = statSync30(sockPath);
|
|
76988
77207
|
if ((st.mode & 61440) === 49152) {
|
|
76989
77208
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
76990
77209
|
}
|
|
@@ -77002,7 +77221,7 @@ function doStatus() {
|
|
|
77002
77221
|
}
|
|
77003
77222
|
function doUninstall() {
|
|
77004
77223
|
const composeYml = hostdComposePath();
|
|
77005
|
-
if (!
|
|
77224
|
+
if (!existsSync77(composeYml)) {
|
|
77006
77225
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
77007
77226
|
return;
|
|
77008
77227
|
}
|
|
@@ -77026,7 +77245,7 @@ function registerHostdCommand(program3) {
|
|
|
77026
77245
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
77027
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) => {
|
|
77028
77247
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
77029
|
-
if (!
|
|
77248
|
+
if (!existsSync77(logPath)) {
|
|
77030
77249
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
77031
77250
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
77032
77251
|
return;
|