switchroom 0.14.73 → 0.14.74
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 +1151 -817
- package/dist/cli/ui/index.html +77 -5
- package/package.json +1 -1
- package/profiles/coding/workspace/SOUL.default.md.hbs +17 -0
- package/profiles/default/workspace/SOUL.default.md.hbs +17 -0
- package/profiles/executive-assistant/workspace/SOUL.default.md.hbs +17 -0
- package/profiles/health-coach/workspace/SOUL.default.md.hbs +17 -0
- package/telegram-plugin/dist/gateway/gateway.js +4 -4
- package/profiles/_shared/telegram-style.md.hbs +0 -74
package/dist/cli/switchroom.js
CHANGED
|
@@ -28636,6 +28636,86 @@ function formatForCli(entries, opts = {}) {
|
|
|
28636
28636
|
}
|
|
28637
28637
|
var init_audit_reader = () => {};
|
|
28638
28638
|
|
|
28639
|
+
// src/host-control/client.ts
|
|
28640
|
+
import { connect as connect2 } from "node:net";
|
|
28641
|
+
async function hostdRequest(opts, req) {
|
|
28642
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
28643
|
+
return new Promise((resolve27, reject) => {
|
|
28644
|
+
const socket = connect2(opts.socketPath);
|
|
28645
|
+
let buf = "";
|
|
28646
|
+
let settled = false;
|
|
28647
|
+
const timer = setTimeout(() => {
|
|
28648
|
+
if (settled)
|
|
28649
|
+
return;
|
|
28650
|
+
settled = true;
|
|
28651
|
+
socket.destroy();
|
|
28652
|
+
reject(new Error(`hostd: request timed out after ${timeoutMs}ms`));
|
|
28653
|
+
}, timeoutMs);
|
|
28654
|
+
socket.on("connect", () => {
|
|
28655
|
+
try {
|
|
28656
|
+
socket.write(encodeRequest3(req));
|
|
28657
|
+
} catch (err) {
|
|
28658
|
+
if (settled)
|
|
28659
|
+
return;
|
|
28660
|
+
settled = true;
|
|
28661
|
+
clearTimeout(timer);
|
|
28662
|
+
socket.destroy();
|
|
28663
|
+
reject(err);
|
|
28664
|
+
}
|
|
28665
|
+
});
|
|
28666
|
+
socket.on("data", (chunk) => {
|
|
28667
|
+
buf += chunk.toString("utf8");
|
|
28668
|
+
if (Buffer.byteLength(buf, "utf8") > MAX_FRAME_BYTES3 * 2) {
|
|
28669
|
+
if (settled)
|
|
28670
|
+
return;
|
|
28671
|
+
settled = true;
|
|
28672
|
+
clearTimeout(timer);
|
|
28673
|
+
socket.destroy();
|
|
28674
|
+
reject(new Error("hostd: response exceeded frame budget"));
|
|
28675
|
+
return;
|
|
28676
|
+
}
|
|
28677
|
+
const nl = buf.indexOf(`
|
|
28678
|
+
`);
|
|
28679
|
+
if (nl === -1)
|
|
28680
|
+
return;
|
|
28681
|
+
const line = buf.slice(0, nl);
|
|
28682
|
+
try {
|
|
28683
|
+
const resp = decodeResponse3(line);
|
|
28684
|
+
if (settled)
|
|
28685
|
+
return;
|
|
28686
|
+
settled = true;
|
|
28687
|
+
clearTimeout(timer);
|
|
28688
|
+
socket.end();
|
|
28689
|
+
resolve27(resp);
|
|
28690
|
+
} catch (err) {
|
|
28691
|
+
if (settled)
|
|
28692
|
+
return;
|
|
28693
|
+
settled = true;
|
|
28694
|
+
clearTimeout(timer);
|
|
28695
|
+
socket.destroy();
|
|
28696
|
+
reject(new Error(`hostd: bad response frame: ${err.message}`, { cause: err }));
|
|
28697
|
+
}
|
|
28698
|
+
});
|
|
28699
|
+
socket.on("error", (err) => {
|
|
28700
|
+
if (settled)
|
|
28701
|
+
return;
|
|
28702
|
+
settled = true;
|
|
28703
|
+
clearTimeout(timer);
|
|
28704
|
+
reject(err);
|
|
28705
|
+
});
|
|
28706
|
+
socket.on("close", () => {
|
|
28707
|
+
if (settled)
|
|
28708
|
+
return;
|
|
28709
|
+
settled = true;
|
|
28710
|
+
clearTimeout(timer);
|
|
28711
|
+
reject(new Error("hostd: connection closed before response"));
|
|
28712
|
+
});
|
|
28713
|
+
});
|
|
28714
|
+
}
|
|
28715
|
+
var init_client4 = __esm(() => {
|
|
28716
|
+
init_protocol3();
|
|
28717
|
+
});
|
|
28718
|
+
|
|
28639
28719
|
// src/cli/doctor-status.ts
|
|
28640
28720
|
function statusGlyph(status) {
|
|
28641
28721
|
switch (status) {
|
|
@@ -28679,17 +28759,17 @@ var init_thinking_effort_risk = __esm(() => {
|
|
|
28679
28759
|
|
|
28680
28760
|
// src/manifest.ts
|
|
28681
28761
|
import {
|
|
28682
|
-
existsSync as
|
|
28683
|
-
readFileSync as
|
|
28762
|
+
existsSync as existsSync50,
|
|
28763
|
+
readFileSync as readFileSync46,
|
|
28684
28764
|
readdirSync as readdirSync18
|
|
28685
28765
|
} from "node:fs";
|
|
28686
|
-
import { dirname as dirname11, join as
|
|
28766
|
+
import { dirname as dirname11, join as join44 } from "node:path";
|
|
28687
28767
|
import { execSync as execSync2 } from "node:child_process";
|
|
28688
28768
|
function locateManifestPath() {
|
|
28689
28769
|
let dir = import.meta.dirname;
|
|
28690
28770
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
28691
|
-
const candidate =
|
|
28692
|
-
if (
|
|
28771
|
+
const candidate = join44(dir, "dependencies.json");
|
|
28772
|
+
if (existsSync50(candidate))
|
|
28693
28773
|
return candidate;
|
|
28694
28774
|
dir = dirname11(dir);
|
|
28695
28775
|
}
|
|
@@ -28702,7 +28782,7 @@ function loadManifest(manifestPath) {
|
|
|
28702
28782
|
}
|
|
28703
28783
|
let raw;
|
|
28704
28784
|
try {
|
|
28705
|
-
raw =
|
|
28785
|
+
raw = readFileSync46(path4, "utf-8");
|
|
28706
28786
|
} catch (err) {
|
|
28707
28787
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
28708
28788
|
}
|
|
@@ -28758,16 +28838,16 @@ function probeClaudeVersion() {
|
|
|
28758
28838
|
}
|
|
28759
28839
|
function probePlaywrightMcpVersion() {
|
|
28760
28840
|
const home2 = process.env.HOME ?? "";
|
|
28761
|
-
const npxCache =
|
|
28762
|
-
if (!
|
|
28841
|
+
const npxCache = join44(home2, ".npm/_npx");
|
|
28842
|
+
if (!existsSync50(npxCache))
|
|
28763
28843
|
return null;
|
|
28764
28844
|
try {
|
|
28765
28845
|
const entries = readdirSync18(npxCache);
|
|
28766
28846
|
for (const entry of entries) {
|
|
28767
|
-
const pkgPath =
|
|
28768
|
-
if (
|
|
28847
|
+
const pkgPath = join44(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
28848
|
+
if (existsSync50(pkgPath)) {
|
|
28769
28849
|
try {
|
|
28770
|
-
const pkg = JSON.parse(
|
|
28850
|
+
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
28771
28851
|
if (pkg.version)
|
|
28772
28852
|
return pkg.version;
|
|
28773
28853
|
} catch {}
|
|
@@ -28859,7 +28939,7 @@ var init_manifest = __esm(() => {
|
|
|
28859
28939
|
});
|
|
28860
28940
|
|
|
28861
28941
|
// src/cli/doctor-docker.ts
|
|
28862
|
-
import { readFileSync as
|
|
28942
|
+
import { readFileSync as readFileSync47 } from "node:fs";
|
|
28863
28943
|
function imageTagOf(ref) {
|
|
28864
28944
|
if (!ref)
|
|
28865
28945
|
return "<absent>";
|
|
@@ -28874,7 +28954,7 @@ function isDockerMode(opts) {
|
|
|
28874
28954
|
return true;
|
|
28875
28955
|
if (opts?.composePath) {
|
|
28876
28956
|
try {
|
|
28877
|
-
|
|
28957
|
+
readFileSync47(opts.composePath, "utf8");
|
|
28878
28958
|
return true;
|
|
28879
28959
|
} catch {}
|
|
28880
28960
|
}
|
|
@@ -29063,14 +29143,14 @@ var init_doctor_docker = __esm(() => {
|
|
|
29063
29143
|
});
|
|
29064
29144
|
|
|
29065
29145
|
// src/cli/doctor-auth-broker.ts
|
|
29066
|
-
import { existsSync as
|
|
29146
|
+
import { existsSync as existsSync51, readFileSync as readFileSync48 } from "node:fs";
|
|
29067
29147
|
import { createHash as createHash10 } from "node:crypto";
|
|
29068
|
-
import { spawnSync as
|
|
29069
|
-
import { homedir as
|
|
29070
|
-
import { join as
|
|
29148
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
29149
|
+
import { homedir as homedir24 } from "node:os";
|
|
29150
|
+
import { join as join45 } from "node:path";
|
|
29071
29151
|
function defaultDockerInspect(container, format) {
|
|
29072
29152
|
try {
|
|
29073
|
-
const r =
|
|
29153
|
+
const r = spawnSync6("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
29074
29154
|
if (r.status !== 0)
|
|
29075
29155
|
return null;
|
|
29076
29156
|
return r.stdout.trim();
|
|
@@ -29080,7 +29160,7 @@ function defaultDockerInspect(container, format) {
|
|
|
29080
29160
|
}
|
|
29081
29161
|
function defaultDockerExecExists(container, path4) {
|
|
29082
29162
|
try {
|
|
29083
|
-
const r =
|
|
29163
|
+
const r = spawnSync6("docker", ["exec", container, "test", "-S", path4], { encoding: "utf-8", timeout: 5000 });
|
|
29084
29164
|
return r.status === 0;
|
|
29085
29165
|
} catch {
|
|
29086
29166
|
return false;
|
|
@@ -29167,8 +29247,8 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
29167
29247
|
}
|
|
29168
29248
|
function checkAuthBrokerDrift(deps = {}) {
|
|
29169
29249
|
const stateDir = resolveStateDir(deps);
|
|
29170
|
-
const indexPath =
|
|
29171
|
-
if (!
|
|
29250
|
+
const indexPath = join45(stateDir, "sha-index.json");
|
|
29251
|
+
if (!existsSync51(indexPath)) {
|
|
29172
29252
|
return {
|
|
29173
29253
|
name: "auth-broker: drift",
|
|
29174
29254
|
status: "ok",
|
|
@@ -29177,7 +29257,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29177
29257
|
}
|
|
29178
29258
|
let index;
|
|
29179
29259
|
try {
|
|
29180
|
-
index = JSON.parse(
|
|
29260
|
+
index = JSON.parse(readFileSync48(indexPath, "utf-8"));
|
|
29181
29261
|
} catch (err) {
|
|
29182
29262
|
return {
|
|
29183
29263
|
name: "auth-broker: drift",
|
|
@@ -29186,18 +29266,18 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29186
29266
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
29187
29267
|
};
|
|
29188
29268
|
}
|
|
29189
|
-
const home2 = deps.home ??
|
|
29269
|
+
const home2 = deps.home ?? homedir24();
|
|
29190
29270
|
const divergent = [];
|
|
29191
29271
|
const missingOnDisk = [];
|
|
29192
29272
|
for (const [label, expected] of Object.entries(index)) {
|
|
29193
29273
|
const credsPath = accountCredentialsPath(label, home2);
|
|
29194
|
-
if (!
|
|
29274
|
+
if (!existsSync51(credsPath)) {
|
|
29195
29275
|
missingOnDisk.push(label);
|
|
29196
29276
|
continue;
|
|
29197
29277
|
}
|
|
29198
29278
|
let got;
|
|
29199
29279
|
try {
|
|
29200
|
-
got = sha256Hex(
|
|
29280
|
+
got = sha256Hex(readFileSync48(credsPath, "utf-8"));
|
|
29201
29281
|
} catch (err) {
|
|
29202
29282
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
29203
29283
|
continue;
|
|
@@ -29228,8 +29308,8 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29228
29308
|
}
|
|
29229
29309
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
29230
29310
|
const stateDir = resolveStateDir(deps);
|
|
29231
|
-
const path4 =
|
|
29232
|
-
if (!
|
|
29311
|
+
const path4 = join45(stateDir, "threshold-violations.json");
|
|
29312
|
+
if (!existsSync51(path4)) {
|
|
29233
29313
|
return {
|
|
29234
29314
|
name: "auth-broker: threshold violations",
|
|
29235
29315
|
status: "ok",
|
|
@@ -29238,7 +29318,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
29238
29318
|
}
|
|
29239
29319
|
let violations;
|
|
29240
29320
|
try {
|
|
29241
|
-
violations = JSON.parse(
|
|
29321
|
+
violations = JSON.parse(readFileSync48(path4, "utf-8"));
|
|
29242
29322
|
} catch (err) {
|
|
29243
29323
|
return {
|
|
29244
29324
|
name: "auth-broker: threshold violations",
|
|
@@ -29272,9 +29352,9 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
29272
29352
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
29273
29353
|
};
|
|
29274
29354
|
}
|
|
29275
|
-
const home2 = deps.home ??
|
|
29355
|
+
const home2 = deps.home ?? homedir24();
|
|
29276
29356
|
const dir = accountDir(active, home2);
|
|
29277
|
-
if (!
|
|
29357
|
+
if (!existsSync51(dir)) {
|
|
29278
29358
|
return {
|
|
29279
29359
|
name: "auth-broker: fleet active account",
|
|
29280
29360
|
status: "fail",
|
|
@@ -29283,7 +29363,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
29283
29363
|
};
|
|
29284
29364
|
}
|
|
29285
29365
|
const creds = accountCredentialsPath(active, home2);
|
|
29286
|
-
if (!
|
|
29366
|
+
if (!existsSync51(creds)) {
|
|
29287
29367
|
return {
|
|
29288
29368
|
name: "auth-broker: fleet active account",
|
|
29289
29369
|
status: "fail",
|
|
@@ -29315,10 +29395,10 @@ var init_doctor_auth_broker = __esm(() => {
|
|
|
29315
29395
|
});
|
|
29316
29396
|
|
|
29317
29397
|
// src/cli/doctor-hostd.ts
|
|
29318
|
-
import { spawnSync as
|
|
29398
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
29319
29399
|
function realDockerInspect(ref, format) {
|
|
29320
29400
|
try {
|
|
29321
|
-
const r =
|
|
29401
|
+
const r = spawnSync7("docker", ["inspect", ref, "--format", format], {
|
|
29322
29402
|
encoding: "utf-8",
|
|
29323
29403
|
timeout: 5000
|
|
29324
29404
|
});
|
|
@@ -29420,17 +29500,17 @@ var init_doctor_hostd = () => {};
|
|
|
29420
29500
|
import {
|
|
29421
29501
|
accessSync,
|
|
29422
29502
|
constants as fsConstants4,
|
|
29423
|
-
existsSync as
|
|
29503
|
+
existsSync as existsSync52,
|
|
29424
29504
|
realpathSync as realpathSync4,
|
|
29425
29505
|
statSync as statSync21
|
|
29426
29506
|
} from "node:fs";
|
|
29427
|
-
import { userInfo, homedir as
|
|
29428
|
-
import { join as
|
|
29507
|
+
import { userInfo, homedir as homedir25 } from "node:os";
|
|
29508
|
+
import { join as join46 } from "node:path";
|
|
29429
29509
|
function resolveVaultPath2(config) {
|
|
29430
29510
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
29431
29511
|
}
|
|
29432
29512
|
function defaultStatVault(path4) {
|
|
29433
|
-
if (!
|
|
29513
|
+
if (!existsSync52(path4)) {
|
|
29434
29514
|
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
29435
29515
|
}
|
|
29436
29516
|
let real = path4;
|
|
@@ -29563,7 +29643,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
29563
29643
|
};
|
|
29564
29644
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
29565
29645
|
if (!passphrase) {
|
|
29566
|
-
const sock = deps.brokerOperatorSocket ??
|
|
29646
|
+
const sock = deps.brokerOperatorSocket ?? join46(homedir25(), ".switchroom", "broker-operator", "sock");
|
|
29567
29647
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
29568
29648
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
29569
29649
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -29648,8 +29728,8 @@ import {
|
|
|
29648
29728
|
existsSync as realExistsSync,
|
|
29649
29729
|
readFileSync as realReadFileSync
|
|
29650
29730
|
} from "node:fs";
|
|
29651
|
-
import { join as
|
|
29652
|
-
import { homedir as
|
|
29731
|
+
import { join as join47, resolve as resolve30 } from "node:path";
|
|
29732
|
+
import { homedir as homedir26 } from "node:os";
|
|
29653
29733
|
function resolveDeps(config, deps) {
|
|
29654
29734
|
let agentsDir = deps.agentsDir;
|
|
29655
29735
|
if (agentsDir === undefined) {
|
|
@@ -29772,8 +29852,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
29772
29852
|
});
|
|
29773
29853
|
continue;
|
|
29774
29854
|
}
|
|
29775
|
-
const mcpPath =
|
|
29776
|
-
const claudeJsonPath =
|
|
29855
|
+
const mcpPath = join47(agentDir, ".mcp.json");
|
|
29856
|
+
const claudeJsonPath = join47(agentDir, ".claude", ".claude.json");
|
|
29777
29857
|
const mcpRead = readJson(d, mcpPath);
|
|
29778
29858
|
const trustRead = readJson(d, claudeJsonPath);
|
|
29779
29859
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -29886,7 +29966,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
29886
29966
|
}
|
|
29887
29967
|
];
|
|
29888
29968
|
}
|
|
29889
|
-
const sock = deps.brokerOperatorSocket ??
|
|
29969
|
+
const sock = deps.brokerOperatorSocket ?? join47(homedir26(), ".switchroom", "broker-operator", "sock");
|
|
29890
29970
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
29891
29971
|
const results = [];
|
|
29892
29972
|
for (const agent of driveAgents) {
|
|
@@ -29940,8 +30020,8 @@ import {
|
|
|
29940
30020
|
readSync as realReadSync,
|
|
29941
30021
|
closeSync as realCloseSync
|
|
29942
30022
|
} from "node:fs";
|
|
29943
|
-
import { join as
|
|
29944
|
-
import { homedir as
|
|
30023
|
+
import { join as join48 } from "node:path";
|
|
30024
|
+
import { homedir as homedir27 } from "node:os";
|
|
29945
30025
|
function defaultReadHead(p, n) {
|
|
29946
30026
|
let fd;
|
|
29947
30027
|
try {
|
|
@@ -29969,7 +30049,7 @@ function resolveDeps2(config, deps) {
|
|
|
29969
30049
|
}
|
|
29970
30050
|
}
|
|
29971
30051
|
return {
|
|
29972
|
-
homeDir: deps.homeDir ??
|
|
30052
|
+
homeDir: deps.homeDir ?? homedir27(),
|
|
29973
30053
|
agentsDir,
|
|
29974
30054
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
29975
30055
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -29998,7 +30078,7 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
29998
30078
|
return [];
|
|
29999
30079
|
const d = resolveDeps2(config, deps);
|
|
30000
30080
|
const results = [];
|
|
30001
|
-
const binPath =
|
|
30081
|
+
const binPath = join48(d.homeDir, ".switchroom", "bin", "webkite");
|
|
30002
30082
|
if (!d.existsSync(binPath)) {
|
|
30003
30083
|
results.push({
|
|
30004
30084
|
name: "webkite: binary",
|
|
@@ -30028,12 +30108,12 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
30028
30108
|
});
|
|
30029
30109
|
}
|
|
30030
30110
|
}
|
|
30031
|
-
const cloakDir =
|
|
30111
|
+
const cloakDir = join48(d.homeDir, ".cloakbrowser");
|
|
30032
30112
|
let chromeFound = false;
|
|
30033
30113
|
if (d.existsSync(cloakDir)) {
|
|
30034
30114
|
try {
|
|
30035
30115
|
for (const entry of d.readdirSync(cloakDir)) {
|
|
30036
|
-
if (entry.startsWith("chromium-") && d.existsSync(
|
|
30116
|
+
if (entry.startsWith("chromium-") && d.existsSync(join48(cloakDir, entry, "chrome"))) {
|
|
30037
30117
|
chromeFound = true;
|
|
30038
30118
|
break;
|
|
30039
30119
|
}
|
|
@@ -30059,9 +30139,9 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
30059
30139
|
return results;
|
|
30060
30140
|
}
|
|
30061
30141
|
for (const agent of enabledAgents) {
|
|
30062
|
-
const agentDir =
|
|
30063
|
-
const settingsPath =
|
|
30064
|
-
const mcpPath =
|
|
30142
|
+
const agentDir = join48(d.agentsDir, agent);
|
|
30143
|
+
const settingsPath = join48(agentDir, ".claude", "settings.json");
|
|
30144
|
+
const mcpPath = join48(agentDir, ".mcp.json");
|
|
30065
30145
|
if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
|
|
30066
30146
|
continue;
|
|
30067
30147
|
}
|
|
@@ -30125,7 +30205,7 @@ var init_doctor_webkite = __esm(() => {
|
|
|
30125
30205
|
});
|
|
30126
30206
|
|
|
30127
30207
|
// src/cli/doctor-scaffold-wiring.ts
|
|
30128
|
-
import { join as
|
|
30208
|
+
import { join as join49, resolve as resolve31 } from "node:path";
|
|
30129
30209
|
function readJson2(d, path4) {
|
|
30130
30210
|
if (!d.existsSync(path4))
|
|
30131
30211
|
return { kind: "absent" };
|
|
@@ -30168,8 +30248,8 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
30168
30248
|
});
|
|
30169
30249
|
continue;
|
|
30170
30250
|
}
|
|
30171
|
-
const mcpPath =
|
|
30172
|
-
const claudeJsonPath =
|
|
30251
|
+
const mcpPath = join49(agentDir, ".mcp.json");
|
|
30252
|
+
const claudeJsonPath = join49(agentDir, ".claude", ".claude.json");
|
|
30173
30253
|
const mcpRead = readJson2(deps, mcpPath);
|
|
30174
30254
|
const trustRead = readJson2(deps, claudeJsonPath);
|
|
30175
30255
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -30233,14 +30313,14 @@ import {
|
|
|
30233
30313
|
existsSync as realExistsSync3,
|
|
30234
30314
|
readFileSync as realReadFileSync3
|
|
30235
30315
|
} from "node:fs";
|
|
30236
|
-
import { join as
|
|
30237
|
-
import { homedir as
|
|
30316
|
+
import { join as join50 } from "node:path";
|
|
30317
|
+
import { homedir as homedir28 } from "node:os";
|
|
30238
30318
|
function resolveDeps3(deps) {
|
|
30239
|
-
const home2 = deps.homeDir?.() ??
|
|
30319
|
+
const home2 = deps.homeDir?.() ?? homedir28();
|
|
30240
30320
|
return {
|
|
30241
30321
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
30242
30322
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
30243
|
-
agentsDir:
|
|
30323
|
+
agentsDir: join50(home2, ".switchroom", "agents"),
|
|
30244
30324
|
now: deps.now ?? Date.now
|
|
30245
30325
|
};
|
|
30246
30326
|
}
|
|
@@ -30323,7 +30403,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
|
|
|
30323
30403
|
];
|
|
30324
30404
|
}
|
|
30325
30405
|
function readHeartbeat(d, agentName) {
|
|
30326
|
-
const path4 =
|
|
30406
|
+
const path4 = join50(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
|
|
30327
30407
|
if (!d.existsSync(path4)) {
|
|
30328
30408
|
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
30329
30409
|
}
|
|
@@ -30420,15 +30500,15 @@ import {
|
|
|
30420
30500
|
readFileSync as realReadFileSync4,
|
|
30421
30501
|
statSync as realStatSync
|
|
30422
30502
|
} from "node:fs";
|
|
30423
|
-
import { join as
|
|
30424
|
-
import { homedir as
|
|
30503
|
+
import { join as join51 } from "node:path";
|
|
30504
|
+
import { homedir as homedir29 } from "node:os";
|
|
30425
30505
|
function resolveDeps4(deps) {
|
|
30426
|
-
const home2 = deps.homeDir?.() ??
|
|
30506
|
+
const home2 = deps.homeDir?.() ?? homedir29();
|
|
30427
30507
|
return {
|
|
30428
30508
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
30429
30509
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
30430
30510
|
statSync: deps.statSync ?? realStatSync,
|
|
30431
|
-
agentsDir:
|
|
30511
|
+
agentsDir: join51(home2, ".switchroom", "agents"),
|
|
30432
30512
|
now: deps.now ?? Date.now,
|
|
30433
30513
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
30434
30514
|
};
|
|
@@ -30553,7 +30633,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
|
|
|
30553
30633
|
return [];
|
|
30554
30634
|
const results = [];
|
|
30555
30635
|
for (const name of notionAgents) {
|
|
30556
|
-
const heartbeatPath =
|
|
30636
|
+
const heartbeatPath = join51(d.agentsDir, name, "notion-launcher.heartbeat.json");
|
|
30557
30637
|
if (!d.existsSync(heartbeatPath)) {
|
|
30558
30638
|
results.push({
|
|
30559
30639
|
name: `notion:launcher-heartbeat:${name}`,
|
|
@@ -30628,11 +30708,11 @@ import {
|
|
|
30628
30708
|
readdirSync as realReaddirSync2,
|
|
30629
30709
|
statSync as realStatSync2
|
|
30630
30710
|
} from "node:fs";
|
|
30631
|
-
import { homedir as
|
|
30632
|
-
import { join as
|
|
30711
|
+
import { homedir as homedir30 } from "node:os";
|
|
30712
|
+
import { join as join52 } from "node:path";
|
|
30633
30713
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
30634
|
-
const credDir = deps.credentialsDir ??
|
|
30635
|
-
const
|
|
30714
|
+
const credDir = deps.credentialsDir ?? join52(homedir30(), ".switchroom", "credentials");
|
|
30715
|
+
const existsSync53 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
30636
30716
|
const readdirSync20 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
30637
30717
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
30638
30718
|
try {
|
|
@@ -30641,7 +30721,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
30641
30721
|
return false;
|
|
30642
30722
|
}
|
|
30643
30723
|
});
|
|
30644
|
-
if (!
|
|
30724
|
+
if (!existsSync53(credDir))
|
|
30645
30725
|
return [];
|
|
30646
30726
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
30647
30727
|
let entries;
|
|
@@ -30659,7 +30739,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
30659
30739
|
const flat = [];
|
|
30660
30740
|
const perAgentDirs = [];
|
|
30661
30741
|
for (const e of entries) {
|
|
30662
|
-
const full =
|
|
30742
|
+
const full = join52(credDir, e);
|
|
30663
30743
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
30664
30744
|
perAgentDirs.push(e);
|
|
30665
30745
|
} else {
|
|
@@ -30735,7 +30815,7 @@ function runInlinedSecretChecks(_config, deps = {}) {
|
|
|
30735
30815
|
}
|
|
30736
30816
|
let parsed;
|
|
30737
30817
|
try {
|
|
30738
|
-
parsed =
|
|
30818
|
+
parsed = import_yaml17.parse(raw);
|
|
30739
30819
|
} catch {
|
|
30740
30820
|
return [
|
|
30741
30821
|
{
|
|
@@ -30763,9 +30843,9 @@ function runInlinedSecretChecks(_config, deps = {}) {
|
|
|
30763
30843
|
fix: `Move it to the per-agent-ACL'd vault: \`switchroom vault set <name>\` ` + `then replace the literal with \`vault:<name>\`. Rotate the exposed ` + `value if any agent may already have read it.`
|
|
30764
30844
|
}));
|
|
30765
30845
|
}
|
|
30766
|
-
var
|
|
30846
|
+
var import_yaml17, SECRET_KEY_EXACT;
|
|
30767
30847
|
var init_doctor_inlined_secrets = __esm(() => {
|
|
30768
|
-
|
|
30848
|
+
import_yaml17 = __toESM(require_dist(), 1);
|
|
30769
30849
|
SECRET_KEY_EXACT = new Set([
|
|
30770
30850
|
"bot_token",
|
|
30771
30851
|
"client_secret",
|
|
@@ -30782,19 +30862,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
30782
30862
|
|
|
30783
30863
|
// src/cli/doctor-audit-integrity.ts
|
|
30784
30864
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
30785
|
-
import { homedir as
|
|
30786
|
-
import { join as
|
|
30865
|
+
import { homedir as homedir31 } from "node:os";
|
|
30866
|
+
import { join as join53 } from "node:path";
|
|
30787
30867
|
function rootWrittenLogs(home2) {
|
|
30788
30868
|
return [
|
|
30789
|
-
{ label: "vault-broker", path:
|
|
30869
|
+
{ label: "vault-broker", path: join53(home2, ".switchroom", "vault-audit.log") },
|
|
30790
30870
|
{
|
|
30791
30871
|
label: "hostd",
|
|
30792
|
-
path:
|
|
30872
|
+
path: join53(home2, ".switchroom", "host-control-audit.log")
|
|
30793
30873
|
}
|
|
30794
30874
|
];
|
|
30795
30875
|
}
|
|
30796
30876
|
function runAuditIntegrityChecks(deps = {}) {
|
|
30797
|
-
const home2 = deps.homeDir ??
|
|
30877
|
+
const home2 = deps.homeDir ?? homedir31();
|
|
30798
30878
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
30799
30879
|
const results = [];
|
|
30800
30880
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -30850,97 +30930,17 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
30850
30930
|
init_audit_hashchain();
|
|
30851
30931
|
});
|
|
30852
30932
|
|
|
30853
|
-
// src/host-control/client.ts
|
|
30854
|
-
import { connect as connect2 } from "node:net";
|
|
30855
|
-
async function hostdRequest(opts, req) {
|
|
30856
|
-
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
30857
|
-
return new Promise((resolve32, reject) => {
|
|
30858
|
-
const socket = connect2(opts.socketPath);
|
|
30859
|
-
let buf = "";
|
|
30860
|
-
let settled = false;
|
|
30861
|
-
const timer = setTimeout(() => {
|
|
30862
|
-
if (settled)
|
|
30863
|
-
return;
|
|
30864
|
-
settled = true;
|
|
30865
|
-
socket.destroy();
|
|
30866
|
-
reject(new Error(`hostd: request timed out after ${timeoutMs}ms`));
|
|
30867
|
-
}, timeoutMs);
|
|
30868
|
-
socket.on("connect", () => {
|
|
30869
|
-
try {
|
|
30870
|
-
socket.write(encodeRequest3(req));
|
|
30871
|
-
} catch (err) {
|
|
30872
|
-
if (settled)
|
|
30873
|
-
return;
|
|
30874
|
-
settled = true;
|
|
30875
|
-
clearTimeout(timer);
|
|
30876
|
-
socket.destroy();
|
|
30877
|
-
reject(err);
|
|
30878
|
-
}
|
|
30879
|
-
});
|
|
30880
|
-
socket.on("data", (chunk) => {
|
|
30881
|
-
buf += chunk.toString("utf8");
|
|
30882
|
-
if (Buffer.byteLength(buf, "utf8") > MAX_FRAME_BYTES3 * 2) {
|
|
30883
|
-
if (settled)
|
|
30884
|
-
return;
|
|
30885
|
-
settled = true;
|
|
30886
|
-
clearTimeout(timer);
|
|
30887
|
-
socket.destroy();
|
|
30888
|
-
reject(new Error("hostd: response exceeded frame budget"));
|
|
30889
|
-
return;
|
|
30890
|
-
}
|
|
30891
|
-
const nl = buf.indexOf(`
|
|
30892
|
-
`);
|
|
30893
|
-
if (nl === -1)
|
|
30894
|
-
return;
|
|
30895
|
-
const line = buf.slice(0, nl);
|
|
30896
|
-
try {
|
|
30897
|
-
const resp = decodeResponse3(line);
|
|
30898
|
-
if (settled)
|
|
30899
|
-
return;
|
|
30900
|
-
settled = true;
|
|
30901
|
-
clearTimeout(timer);
|
|
30902
|
-
socket.end();
|
|
30903
|
-
resolve32(resp);
|
|
30904
|
-
} catch (err) {
|
|
30905
|
-
if (settled)
|
|
30906
|
-
return;
|
|
30907
|
-
settled = true;
|
|
30908
|
-
clearTimeout(timer);
|
|
30909
|
-
socket.destroy();
|
|
30910
|
-
reject(new Error(`hostd: bad response frame: ${err.message}`, { cause: err }));
|
|
30911
|
-
}
|
|
30912
|
-
});
|
|
30913
|
-
socket.on("error", (err) => {
|
|
30914
|
-
if (settled)
|
|
30915
|
-
return;
|
|
30916
|
-
settled = true;
|
|
30917
|
-
clearTimeout(timer);
|
|
30918
|
-
reject(err);
|
|
30919
|
-
});
|
|
30920
|
-
socket.on("close", () => {
|
|
30921
|
-
if (settled)
|
|
30922
|
-
return;
|
|
30923
|
-
settled = true;
|
|
30924
|
-
clearTimeout(timer);
|
|
30925
|
-
reject(new Error("hostd: connection closed before response"));
|
|
30926
|
-
});
|
|
30927
|
-
});
|
|
30928
|
-
}
|
|
30929
|
-
var init_client4 = __esm(() => {
|
|
30930
|
-
init_protocol3();
|
|
30931
|
-
});
|
|
30932
|
-
|
|
30933
30933
|
// src/cli/doctor-agent-smoke.ts
|
|
30934
|
-
import { existsSync as
|
|
30935
|
-
import { homedir as
|
|
30936
|
-
import { join as
|
|
30937
|
-
import { randomUUID as
|
|
30934
|
+
import { existsSync as existsSync53 } from "node:fs";
|
|
30935
|
+
import { homedir as homedir32 } from "node:os";
|
|
30936
|
+
import { join as join54 } from "node:path";
|
|
30937
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
30938
30938
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
30939
30939
|
if (deps.fast)
|
|
30940
30940
|
return [];
|
|
30941
|
-
const home2 = deps.homeDir ??
|
|
30942
|
-
const sock = deps.operatorSockPath ??
|
|
30943
|
-
if (!deps.hostdRequestImpl && !
|
|
30941
|
+
const home2 = deps.homeDir ?? homedir32();
|
|
30942
|
+
const sock = deps.operatorSockPath ?? join54(home2, ".switchroom", "hostd", "operator", "sock");
|
|
30943
|
+
if (!deps.hostdRequestImpl && !existsSync53(sock)) {
|
|
30944
30944
|
return [
|
|
30945
30945
|
{
|
|
30946
30946
|
name: "agent liveness",
|
|
@@ -30952,7 +30952,7 @@ async function runAgentSmokeChecks(config, deps = {}) {
|
|
|
30952
30952
|
}
|
|
30953
30953
|
const call = deps.hostdRequestImpl ?? ((s, name) => hostdRequest({ socketPath: s, timeoutMs: 8000 }, {
|
|
30954
30954
|
v: 1,
|
|
30955
|
-
request_id: `doctor-smoke-${
|
|
30955
|
+
request_id: `doctor-smoke-${randomUUID5()}`,
|
|
30956
30956
|
op: "agent_smoke",
|
|
30957
30957
|
args: { name }
|
|
30958
30958
|
}));
|
|
@@ -31018,9 +31018,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
31018
31018
|
|
|
31019
31019
|
// src/cli/doctor-vault-broker-durability.ts
|
|
31020
31020
|
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
31021
|
-
import { existsSync as
|
|
31022
|
-
import { homedir as
|
|
31023
|
-
import { join as
|
|
31021
|
+
import { existsSync as existsSync54, statSync as statSync22 } from "node:fs";
|
|
31022
|
+
import { homedir as homedir33 } from "node:os";
|
|
31023
|
+
import { join as join55 } from "node:path";
|
|
31024
31024
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
31025
31025
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
31026
31026
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -31042,7 +31042,7 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
|
31042
31042
|
};
|
|
31043
31043
|
}
|
|
31044
31044
|
function defaultStatHost(p) {
|
|
31045
|
-
if (!
|
|
31045
|
+
if (!existsSync54(p))
|
|
31046
31046
|
return null;
|
|
31047
31047
|
try {
|
|
31048
31048
|
const s = statSync22(p, { bigint: true });
|
|
@@ -31163,22 +31163,22 @@ function defaultBrokerStatusProbe() {
|
|
|
31163
31163
|
}
|
|
31164
31164
|
}
|
|
31165
31165
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
31166
|
-
const home2 =
|
|
31166
|
+
const home2 = homedir33();
|
|
31167
31167
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
31168
31168
|
return [
|
|
31169
31169
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
31170
31170
|
probeAutoUnlockBlob(home2),
|
|
31171
31171
|
probeMachineIdMount(),
|
|
31172
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
31173
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
31174
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
31172
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join55(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join55(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
31173
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join55(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join55(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
31174
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join55(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join55(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
31175
31175
|
probeKernelDbDurability(home2, {
|
|
31176
31176
|
statBroker: opts?.kernelStatBroker
|
|
31177
31177
|
})
|
|
31178
31178
|
];
|
|
31179
31179
|
}
|
|
31180
31180
|
function probeKernelDbDurability(home2, opts) {
|
|
31181
|
-
const hostDir =
|
|
31181
|
+
const hostDir = join55(home2, ".switchroom", "approvals");
|
|
31182
31182
|
const containerDir = "/state/approvals";
|
|
31183
31183
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
31184
31184
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -31246,8 +31246,8 @@ function defaultKernelStatBroker(p) {
|
|
|
31246
31246
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
31247
31247
|
}
|
|
31248
31248
|
function probeAutoUnlockBlob(home2) {
|
|
31249
|
-
const blobPath =
|
|
31250
|
-
if (!
|
|
31249
|
+
const blobPath = join55(home2, ".switchroom", "vault-auto-unlock");
|
|
31250
|
+
if (!existsSync54(blobPath)) {
|
|
31251
31251
|
return {
|
|
31252
31252
|
name: "vault-broker: auto-unlock blob",
|
|
31253
31253
|
status: "warn",
|
|
@@ -31271,7 +31271,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
31271
31271
|
};
|
|
31272
31272
|
}
|
|
31273
31273
|
function probeMachineIdMount() {
|
|
31274
|
-
const hostExists =
|
|
31274
|
+
const hostExists = existsSync54("/etc/machine-id");
|
|
31275
31275
|
if (!hostExists) {
|
|
31276
31276
|
return {
|
|
31277
31277
|
name: "vault-broker: machine-id passthrough",
|
|
@@ -31346,27 +31346,27 @@ __export(exports_doctor, {
|
|
|
31346
31346
|
checkAgents: () => checkAgents,
|
|
31347
31347
|
MFF_VAULT_KEY: () => MFF_VAULT_KEY
|
|
31348
31348
|
});
|
|
31349
|
-
import { execSync as execSync3, spawnSync as
|
|
31349
|
+
import { execSync as execSync3, spawnSync as spawnSync8 } from "node:child_process";
|
|
31350
31350
|
import {
|
|
31351
31351
|
accessSync as accessSync2,
|
|
31352
31352
|
constants as fsConstants5,
|
|
31353
|
-
existsSync as
|
|
31353
|
+
existsSync as existsSync55,
|
|
31354
31354
|
lstatSync as lstatSync5,
|
|
31355
|
-
mkdirSync as
|
|
31356
|
-
readFileSync as
|
|
31355
|
+
mkdirSync as mkdirSync30,
|
|
31356
|
+
readFileSync as readFileSync49,
|
|
31357
31357
|
readdirSync as readdirSync20,
|
|
31358
31358
|
statSync as statSync23
|
|
31359
31359
|
} from "node:fs";
|
|
31360
|
-
import { dirname as dirname12, join as
|
|
31360
|
+
import { dirname as dirname12, join as join56, resolve as resolve32 } from "node:path";
|
|
31361
31361
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
31362
31362
|
function findInNvm(bin) {
|
|
31363
|
-
const nvmRoot =
|
|
31364
|
-
if (!
|
|
31363
|
+
const nvmRoot = join56(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
31364
|
+
if (!existsSync55(nvmRoot))
|
|
31365
31365
|
return null;
|
|
31366
31366
|
try {
|
|
31367
31367
|
const versions = readdirSync20(nvmRoot).sort().reverse();
|
|
31368
31368
|
for (const v of versions) {
|
|
31369
|
-
const candidate =
|
|
31369
|
+
const candidate = join56(nvmRoot, v, "bin", bin);
|
|
31370
31370
|
try {
|
|
31371
31371
|
const s = statSync23(candidate);
|
|
31372
31372
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -31531,21 +31531,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
31531
31531
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
31532
31532
|
cacheLocations.push(envBrowsersPath);
|
|
31533
31533
|
}
|
|
31534
|
-
cacheLocations.push(
|
|
31534
|
+
cacheLocations.push(join56(homeDir, ".cache", "ms-playwright"));
|
|
31535
31535
|
for (const cacheDir of cacheLocations) {
|
|
31536
|
-
if (!
|
|
31536
|
+
if (!existsSync55(cacheDir))
|
|
31537
31537
|
continue;
|
|
31538
31538
|
try {
|
|
31539
31539
|
const entries = readdirSync20(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
31540
31540
|
for (const entry of entries) {
|
|
31541
31541
|
const candidates2 = [
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31542
|
+
join56(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
31543
|
+
join56(cacheDir, entry, "chrome-linux", "chrome"),
|
|
31544
|
+
join56(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
31545
|
+
join56(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
31546
31546
|
];
|
|
31547
31547
|
for (const path4 of candidates2) {
|
|
31548
|
-
if (
|
|
31548
|
+
if (existsSync55(path4))
|
|
31549
31549
|
return path4;
|
|
31550
31550
|
}
|
|
31551
31551
|
}
|
|
@@ -31567,7 +31567,7 @@ function checkChromium() {
|
|
|
31567
31567
|
}
|
|
31568
31568
|
function checkDepsCacheWritable(depsRoot = resolvePath("~/.switchroom/deps")) {
|
|
31569
31569
|
try {
|
|
31570
|
-
|
|
31570
|
+
mkdirSync30(depsRoot, { recursive: true });
|
|
31571
31571
|
accessSync2(depsRoot, fsConstants5.W_OK);
|
|
31572
31572
|
return {
|
|
31573
31573
|
name: "~/.switchroom/deps writable",
|
|
@@ -31665,8 +31665,8 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
|
|
|
31665
31665
|
function checkLegacyState() {
|
|
31666
31666
|
const results = [];
|
|
31667
31667
|
const h = process.env.HOME ?? "/root";
|
|
31668
|
-
const clerkDir =
|
|
31669
|
-
const clerkPresent =
|
|
31668
|
+
const clerkDir = join56(h, LEGACY_STATE_DIR);
|
|
31669
|
+
const clerkPresent = existsSync55(clerkDir);
|
|
31670
31670
|
results.push({
|
|
31671
31671
|
name: "legacy ~/.clerk state",
|
|
31672
31672
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -31675,7 +31675,7 @@ function checkLegacyState() {
|
|
|
31675
31675
|
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."
|
|
31676
31676
|
} : {}
|
|
31677
31677
|
});
|
|
31678
|
-
const legacySock =
|
|
31678
|
+
const legacySock = join56(h, ".switchroom", "vault-broker.sock");
|
|
31679
31679
|
let sockStat = null;
|
|
31680
31680
|
try {
|
|
31681
31681
|
sockStat = lstatSync5(legacySock);
|
|
@@ -31694,7 +31694,7 @@ function probeVaultBrokerSocketPair(agentName) {
|
|
|
31694
31694
|
const dataPath = `/run/switchroom/broker/${agentName}/sock`;
|
|
31695
31695
|
const unlockPath = `/run/switchroom/broker/${agentName}/unlock`;
|
|
31696
31696
|
const script = `D=0; U=0; ` + `test -S '${dataPath}' && D=1; ` + `test -S '${unlockPath}' && U=1; ` + `echo "D=$D U=$U"`;
|
|
31697
|
-
const r =
|
|
31697
|
+
const r = spawnSync8("docker", ["exec", "switchroom-vault-broker", "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
|
|
31698
31698
|
if (r.error || r.status === null)
|
|
31699
31699
|
return "unreachable";
|
|
31700
31700
|
if (r.status !== 0) {
|
|
@@ -31796,7 +31796,7 @@ function checkVault(config) {
|
|
|
31796
31796
|
detail: "Approval auth: passphrase (two-factor)"
|
|
31797
31797
|
};
|
|
31798
31798
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
31799
|
-
if (!
|
|
31799
|
+
if (!existsSync55(vaultPath)) {
|
|
31800
31800
|
return [
|
|
31801
31801
|
postureResult,
|
|
31802
31802
|
{
|
|
@@ -31893,7 +31893,7 @@ function checkHindsightConsumer(config, opts) {
|
|
|
31893
31893
|
}
|
|
31894
31894
|
function probeAuthBrokerSocket(consumerName) {
|
|
31895
31895
|
const containerPath = `/run/switchroom/auth-broker/${consumerName}/sock`;
|
|
31896
|
-
const r =
|
|
31896
|
+
const r = spawnSync8("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
|
|
31897
31897
|
if (r.error || r.status === null)
|
|
31898
31898
|
return "unreachable";
|
|
31899
31899
|
if (r.status === 0)
|
|
@@ -31971,8 +31971,8 @@ async function checkHindsight(config) {
|
|
|
31971
31971
|
}
|
|
31972
31972
|
function checkPendingRetainsQueue(dir) {
|
|
31973
31973
|
const home2 = process.env.HOME ?? "";
|
|
31974
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
31975
|
-
if (!
|
|
31974
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join56(home2, ".hindsight", "pending-retains");
|
|
31975
|
+
if (!existsSync55(pendingDir)) {
|
|
31976
31976
|
return {
|
|
31977
31977
|
name: "pending-retains queue",
|
|
31978
31978
|
status: "ok",
|
|
@@ -32031,7 +32031,7 @@ function classifyReadError(err) {
|
|
|
32031
32031
|
}
|
|
32032
32032
|
function tryReadHostFile(path4) {
|
|
32033
32033
|
try {
|
|
32034
|
-
return { kind: "ok", content:
|
|
32034
|
+
return { kind: "ok", content: readFileSync49(path4, "utf-8") };
|
|
32035
32035
|
} catch (err) {
|
|
32036
32036
|
const kind = classifyReadError(err);
|
|
32037
32037
|
const error = err?.message ?? String(err);
|
|
@@ -32043,11 +32043,11 @@ function tryReadHostFile(path4) {
|
|
|
32043
32043
|
}
|
|
32044
32044
|
}
|
|
32045
32045
|
function parseEnvFile(path4) {
|
|
32046
|
-
if (!
|
|
32046
|
+
if (!existsSync55(path4))
|
|
32047
32047
|
return {};
|
|
32048
32048
|
let content;
|
|
32049
32049
|
try {
|
|
32050
|
-
content =
|
|
32050
|
+
content = readFileSync49(path4, "utf-8");
|
|
32051
32051
|
} catch {
|
|
32052
32052
|
return {};
|
|
32053
32053
|
}
|
|
@@ -32102,7 +32102,7 @@ async function checkTelegram(config) {
|
|
|
32102
32102
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
32103
32103
|
if (plugin !== "switchroom")
|
|
32104
32104
|
continue;
|
|
32105
|
-
const envPath =
|
|
32105
|
+
const envPath = join56(agentsDir, name, "telegram", ".env");
|
|
32106
32106
|
const read = tryReadHostFile(envPath);
|
|
32107
32107
|
if (read.kind === "eacces") {
|
|
32108
32108
|
results.push({
|
|
@@ -32154,7 +32154,7 @@ async function checkTelegram(config) {
|
|
|
32154
32154
|
}
|
|
32155
32155
|
function checkStartShStale(agentName, startShPath) {
|
|
32156
32156
|
const label = `${agentName}: start.sh scheduler block`;
|
|
32157
|
-
if (!
|
|
32157
|
+
if (!existsSync55(startShPath)) {
|
|
32158
32158
|
return {
|
|
32159
32159
|
name: label,
|
|
32160
32160
|
status: "warn",
|
|
@@ -32164,7 +32164,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
32164
32164
|
}
|
|
32165
32165
|
let content;
|
|
32166
32166
|
try {
|
|
32167
|
-
content =
|
|
32167
|
+
content = readFileSync49(startShPath, "utf-8");
|
|
32168
32168
|
} catch (err) {
|
|
32169
32169
|
return {
|
|
32170
32170
|
name: label,
|
|
@@ -32185,7 +32185,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
32185
32185
|
}
|
|
32186
32186
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
32187
32187
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
32188
|
-
const path4 =
|
|
32188
|
+
const path4 = join56(agentDir, "home", ".switchroom");
|
|
32189
32189
|
let stats;
|
|
32190
32190
|
try {
|
|
32191
32191
|
stats = lstatSync5(path4);
|
|
@@ -32222,8 +32222,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
32222
32222
|
}
|
|
32223
32223
|
function checkRepoHygiene(repoRoot) {
|
|
32224
32224
|
const results = [];
|
|
32225
|
-
const exportDir =
|
|
32226
|
-
if (
|
|
32225
|
+
const exportDir = join56(repoRoot, "clerk-export");
|
|
32226
|
+
if (existsSync55(exportDir)) {
|
|
32227
32227
|
results.push({
|
|
32228
32228
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
32229
32229
|
status: "warn",
|
|
@@ -32231,8 +32231,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
32231
32231
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
32232
32232
|
});
|
|
32233
32233
|
}
|
|
32234
|
-
const knownTarball =
|
|
32235
|
-
if (
|
|
32234
|
+
const knownTarball = join56(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
32235
|
+
if (existsSync55(knownTarball)) {
|
|
32236
32236
|
results.push({
|
|
32237
32237
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
32238
32238
|
status: "warn",
|
|
@@ -32249,7 +32249,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
32249
32249
|
results.push({
|
|
32250
32250
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
32251
32251
|
status: "warn",
|
|
32252
|
-
detail: `${
|
|
32252
|
+
detail: `${join56(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
32253
32253
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
32254
32254
|
});
|
|
32255
32255
|
}
|
|
@@ -32272,12 +32272,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
32272
32272
|
}
|
|
32273
32273
|
function isSwitchroomCheckout(dir) {
|
|
32274
32274
|
try {
|
|
32275
|
-
if (!
|
|
32275
|
+
if (!existsSync55(join56(dir, ".git")))
|
|
32276
32276
|
return false;
|
|
32277
|
-
const pkgPath =
|
|
32278
|
-
if (!
|
|
32277
|
+
const pkgPath = join56(dir, "package.json");
|
|
32278
|
+
if (!existsSync55(pkgPath))
|
|
32279
32279
|
return false;
|
|
32280
|
-
const pkg = JSON.parse(
|
|
32280
|
+
const pkg = JSON.parse(readFileSync49(pkgPath, "utf-8"));
|
|
32281
32281
|
return pkg.name === "switchroom";
|
|
32282
32282
|
} catch {
|
|
32283
32283
|
return false;
|
|
@@ -32290,7 +32290,7 @@ function checkAgents(config, configPath) {
|
|
|
32290
32290
|
const authStatuses = getAllAuthStatuses(config);
|
|
32291
32291
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
32292
32292
|
const agentDir = resolve32(agentsDir, name);
|
|
32293
|
-
if (!
|
|
32293
|
+
if (!existsSync55(agentDir)) {
|
|
32294
32294
|
results.push({
|
|
32295
32295
|
name: `${name}: scaffold`,
|
|
32296
32296
|
status: "fail",
|
|
@@ -32311,7 +32311,7 @@ function checkAgents(config, configPath) {
|
|
|
32311
32311
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
32312
32312
|
});
|
|
32313
32313
|
}
|
|
32314
|
-
results.push(checkStartShStale(name,
|
|
32314
|
+
results.push(checkStartShStale(name, join56(agentDir, "start.sh")));
|
|
32315
32315
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
32316
32316
|
const status = statuses[name];
|
|
32317
32317
|
const active = status?.active ?? "unknown";
|
|
@@ -32388,8 +32388,8 @@ function checkAgents(config, configPath) {
|
|
|
32388
32388
|
}
|
|
32389
32389
|
}
|
|
32390
32390
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
32391
|
-
const mcpJsonPath =
|
|
32392
|
-
if (!
|
|
32391
|
+
const mcpJsonPath = join56(agentDir, ".mcp.json");
|
|
32392
|
+
if (!existsSync55(mcpJsonPath)) {
|
|
32393
32393
|
results.push({
|
|
32394
32394
|
name: `${name}: .mcp.json`,
|
|
32395
32395
|
status: "fail",
|
|
@@ -32398,7 +32398,7 @@ function checkAgents(config, configPath) {
|
|
|
32398
32398
|
});
|
|
32399
32399
|
} else {
|
|
32400
32400
|
try {
|
|
32401
|
-
const mcp = JSON.parse(
|
|
32401
|
+
const mcp = JSON.parse(readFileSync49(mcpJsonPath, "utf-8"));
|
|
32402
32402
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
32403
32403
|
const memoryEnabled = isHindsightEnabled(config);
|
|
32404
32404
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -32475,7 +32475,7 @@ function mffEnvPath(config) {
|
|
|
32475
32475
|
return agent ? resolve32(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve32(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
32476
32476
|
}
|
|
32477
32477
|
function mffEnvState(envPath) {
|
|
32478
|
-
if (!
|
|
32478
|
+
if (!existsSync55(envPath))
|
|
32479
32479
|
return "absent";
|
|
32480
32480
|
try {
|
|
32481
32481
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -32493,7 +32493,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
32493
32493
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
32494
32494
|
};
|
|
32495
32495
|
}
|
|
32496
|
-
if (!
|
|
32496
|
+
if (!existsSync55(vaultPath)) {
|
|
32497
32497
|
return {
|
|
32498
32498
|
name: "mff: vault key present",
|
|
32499
32499
|
status: "fail",
|
|
@@ -32546,7 +32546,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
32546
32546
|
}
|
|
32547
32547
|
}
|
|
32548
32548
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
32549
|
-
if (!passphrase || !
|
|
32549
|
+
if (!passphrase || !existsSync55(vaultPath)) {
|
|
32550
32550
|
return {
|
|
32551
32551
|
name: "mff: vault key format",
|
|
32552
32552
|
status: "warn",
|
|
@@ -32690,8 +32690,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
32690
32690
|
};
|
|
32691
32691
|
}
|
|
32692
32692
|
const credDir = dirname12(envPath);
|
|
32693
|
-
const authScript =
|
|
32694
|
-
if (!
|
|
32693
|
+
const authScript = join56(credDir, "claude-auth.py");
|
|
32694
|
+
if (!existsSync55(authScript)) {
|
|
32695
32695
|
return {
|
|
32696
32696
|
name: "mff: auth flow",
|
|
32697
32697
|
status: "warn",
|
|
@@ -32702,7 +32702,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
32702
32702
|
const python3 = which("python3") ?? "python3";
|
|
32703
32703
|
let token;
|
|
32704
32704
|
try {
|
|
32705
|
-
const result =
|
|
32705
|
+
const result = spawnSync8(python3, [authScript, "--quiet"], {
|
|
32706
32706
|
timeout: timeoutMs,
|
|
32707
32707
|
encoding: "utf-8",
|
|
32708
32708
|
env: { ...process.env, ...env2 }
|
|
@@ -32893,11 +32893,11 @@ function runDockerSection(config) {
|
|
|
32893
32893
|
let composeYaml;
|
|
32894
32894
|
let dockerfileAgent;
|
|
32895
32895
|
try {
|
|
32896
|
-
composeYaml =
|
|
32896
|
+
composeYaml = readFileSync49(composePath, "utf8");
|
|
32897
32897
|
} catch {}
|
|
32898
32898
|
const dockerfilePath = resolve32(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
32899
32899
|
try {
|
|
32900
|
-
dockerfileAgent =
|
|
32900
|
+
dockerfileAgent = readFileSync49(dockerfilePath, "utf8");
|
|
32901
32901
|
} catch {}
|
|
32902
32902
|
return runDockerChecks({
|
|
32903
32903
|
config,
|
|
@@ -48726,9 +48726,9 @@ __export(exports_server, {
|
|
|
48726
48726
|
dispatchTool: () => dispatchTool,
|
|
48727
48727
|
TOOLS: () => TOOLS
|
|
48728
48728
|
});
|
|
48729
|
-
import { spawnSync as
|
|
48729
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
48730
48730
|
function execCli(args, stdin) {
|
|
48731
|
-
const r =
|
|
48731
|
+
const r = spawnSync13(CLI_BIN, args, {
|
|
48732
48732
|
encoding: "utf-8",
|
|
48733
48733
|
env: process.env,
|
|
48734
48734
|
timeout: 15000,
|
|
@@ -49190,7 +49190,7 @@ __export(exports_server2, {
|
|
|
49190
49190
|
TOOLS: () => TOOLS2
|
|
49191
49191
|
});
|
|
49192
49192
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
49193
|
-
import { existsSync as
|
|
49193
|
+
import { existsSync as existsSync83, readFileSync as readFileSync68 } from "node:fs";
|
|
49194
49194
|
function selfSocketPath() {
|
|
49195
49195
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
49196
49196
|
}
|
|
@@ -49205,7 +49205,7 @@ async function dispatchTool2(name, args) {
|
|
|
49205
49205
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
49206
49206
|
}
|
|
49207
49207
|
const sockPath = selfSocketPath();
|
|
49208
|
-
if (!
|
|
49208
|
+
if (!existsSync83(sockPath)) {
|
|
49209
49209
|
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.`);
|
|
49210
49210
|
}
|
|
49211
49211
|
let req;
|
|
@@ -49357,18 +49357,18 @@ function resolveAuditLogPath() {
|
|
|
49357
49357
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
49358
49358
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
49359
49359
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
49360
|
-
if (
|
|
49360
|
+
if (existsSync83(bindMounted))
|
|
49361
49361
|
return bindMounted;
|
|
49362
49362
|
return defaultAuditLogPath2();
|
|
49363
49363
|
}
|
|
49364
49364
|
function getLastUpdateApplyStatus() {
|
|
49365
49365
|
const path8 = resolveAuditLogPath();
|
|
49366
|
-
if (!
|
|
49366
|
+
if (!existsSync83(path8)) {
|
|
49367
49367
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
49368
49368
|
}
|
|
49369
49369
|
let raw;
|
|
49370
49370
|
try {
|
|
49371
|
-
raw =
|
|
49371
|
+
raw = readFileSync68(path8, "utf-8");
|
|
49372
49372
|
} catch (err2) {
|
|
49373
49373
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
49374
49374
|
}
|
|
@@ -49601,8 +49601,8 @@ var {
|
|
|
49601
49601
|
} = import__.default;
|
|
49602
49602
|
|
|
49603
49603
|
// src/build-info.ts
|
|
49604
|
-
var VERSION = "0.14.
|
|
49605
|
-
var COMMIT_SHA = "
|
|
49604
|
+
var VERSION = "0.14.74";
|
|
49605
|
+
var COMMIT_SHA = "7e9ebb67";
|
|
49606
49606
|
|
|
49607
49607
|
// src/cli/agent.ts
|
|
49608
49608
|
init_source();
|
|
@@ -49802,7 +49802,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
|
|
|
49802
49802
|
return typeof value === "number" && Number.isFinite(value);
|
|
49803
49803
|
});
|
|
49804
49804
|
var SHARED_FRAGMENTS_DIR = resolve4(PROFILES_ROOT, "_shared");
|
|
49805
|
-
var SHARED_FRAGMENTS = ["
|
|
49805
|
+
var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service"];
|
|
49806
49806
|
for (const name of SHARED_FRAGMENTS) {
|
|
49807
49807
|
const fragPath = join2(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
|
|
49808
49808
|
if (existsSync4(fragPath)) {
|
|
@@ -51115,6 +51115,18 @@ function seedWorkspaceBootstrapFiles(params) {
|
|
|
51115
51115
|
if (entry.endsWith(".hbs")) {
|
|
51116
51116
|
const destRel = relPath.replace(/\.hbs$/, "");
|
|
51117
51117
|
const destPath = join8(agentWorkspaceDir, destRel);
|
|
51118
|
+
if (destRel === "SOUL.default.md") {
|
|
51119
|
+
const rendered = renderTemplate(srcPath, params.context);
|
|
51120
|
+
const existed = existsSync13(destPath);
|
|
51121
|
+
const current = existed ? readFileSync11(destPath, "utf8") : null;
|
|
51122
|
+
if (current !== rendered) {
|
|
51123
|
+
writeFileSync5(destPath, rendered);
|
|
51124
|
+
params.created.push(destPath);
|
|
51125
|
+
} else {
|
|
51126
|
+
params.skipped.push(destPath);
|
|
51127
|
+
}
|
|
51128
|
+
continue;
|
|
51129
|
+
}
|
|
51118
51130
|
const renderFn = () => {
|
|
51119
51131
|
if (destRel === "SOUL.md") {
|
|
51120
51132
|
return renderSoulMd(params.profilePath, agentWorkspaceDir, params.context.soul) ?? renderTemplate(srcPath, params.context);
|
|
@@ -66576,19 +66588,20 @@ init_source();
|
|
|
66576
66588
|
|
|
66577
66589
|
// src/web/server.ts
|
|
66578
66590
|
init_merge();
|
|
66591
|
+
init_loader();
|
|
66579
66592
|
init_lifecycle();
|
|
66580
66593
|
import {
|
|
66581
|
-
readFileSync as
|
|
66582
|
-
existsSync as
|
|
66594
|
+
readFileSync as readFileSync44,
|
|
66595
|
+
existsSync as existsSync48,
|
|
66583
66596
|
realpathSync as realpathSync3,
|
|
66584
|
-
mkdirSync as
|
|
66597
|
+
mkdirSync as mkdirSync28,
|
|
66585
66598
|
openSync as openSync10,
|
|
66586
66599
|
closeSync as closeSync10,
|
|
66587
66600
|
writeSync as writeSync6,
|
|
66588
66601
|
constants as fsConstants3
|
|
66589
66602
|
} from "node:fs";
|
|
66590
|
-
import { resolve as resolve28, extname, join as
|
|
66591
|
-
import { homedir as
|
|
66603
|
+
import { resolve as resolve28, extname, join as join43, relative, dirname as dirname9 } from "node:path";
|
|
66604
|
+
import { homedir as homedir23 } from "node:os";
|
|
66592
66605
|
import { spawn as spawn5 } from "node:child_process";
|
|
66593
66606
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
|
|
66594
66607
|
|
|
@@ -66596,8 +66609,8 @@ import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } fro
|
|
|
66596
66609
|
init_lifecycle();
|
|
66597
66610
|
init_manager();
|
|
66598
66611
|
init_hindsight();
|
|
66599
|
-
import { spawnSync as
|
|
66600
|
-
import { existsSync as
|
|
66612
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
66613
|
+
import { existsSync as existsSync45, readFileSync as readFileSync41 } from "node:fs";
|
|
66601
66614
|
import { resolve as resolve27 } from "node:path";
|
|
66602
66615
|
init_audit_reader();
|
|
66603
66616
|
|
|
@@ -71206,12 +71219,198 @@ function installGlobalErrorHandlers() {
|
|
|
71206
71219
|
// src/web/api.ts
|
|
71207
71220
|
init_loader();
|
|
71208
71221
|
init_merge();
|
|
71222
|
+
|
|
71223
|
+
// src/config/agent-workspace-account.ts
|
|
71224
|
+
var import_yaml13 = __toESM(require_dist(), 1);
|
|
71225
|
+
function blockKey(provider) {
|
|
71226
|
+
return `${provider}_workspace`;
|
|
71227
|
+
}
|
|
71228
|
+
function setAgentWorkspaceAccount(yamlText, provider, agent, account) {
|
|
71229
|
+
const doc = import_yaml13.parseDocument(yamlText);
|
|
71230
|
+
const agentsNode = doc.get("agents");
|
|
71231
|
+
if (!import_yaml13.isMap(agentsNode)) {
|
|
71232
|
+
throw new Error("switchroom.yaml has no `agents:` map");
|
|
71233
|
+
}
|
|
71234
|
+
if (!agentsNode.has(agent)) {
|
|
71235
|
+
throw new Error(`agent '${agent}' is not declared in switchroom.yaml`);
|
|
71236
|
+
}
|
|
71237
|
+
const path4 = ["agents", agent, blockKey(provider), "account"];
|
|
71238
|
+
const current = doc.getIn(path4);
|
|
71239
|
+
if (current === account)
|
|
71240
|
+
return yamlText;
|
|
71241
|
+
doc.setIn(path4, account);
|
|
71242
|
+
return String(doc);
|
|
71243
|
+
}
|
|
71244
|
+
function clearAgentWorkspaceAccount(yamlText, provider, agent) {
|
|
71245
|
+
const doc = import_yaml13.parseDocument(yamlText);
|
|
71246
|
+
const agentsNode = doc.get("agents");
|
|
71247
|
+
if (!import_yaml13.isMap(agentsNode))
|
|
71248
|
+
return yamlText;
|
|
71249
|
+
if (!agentsNode.has(agent))
|
|
71250
|
+
return yamlText;
|
|
71251
|
+
const blockPath = ["agents", agent, blockKey(provider)];
|
|
71252
|
+
const block = doc.getIn(blockPath);
|
|
71253
|
+
if (!import_yaml13.isMap(block))
|
|
71254
|
+
return yamlText;
|
|
71255
|
+
const accountPath = [...blockPath, "account"];
|
|
71256
|
+
if (doc.getIn(accountPath) === undefined)
|
|
71257
|
+
return yamlText;
|
|
71258
|
+
doc.deleteIn(accountPath);
|
|
71259
|
+
const after = doc.getIn(blockPath);
|
|
71260
|
+
if (import_yaml13.isMap(after) && after.items.length === 0) {
|
|
71261
|
+
doc.deleteIn(blockPath);
|
|
71262
|
+
}
|
|
71263
|
+
return String(doc);
|
|
71264
|
+
}
|
|
71265
|
+
function getAgentWorkspaceAccount(yamlText, provider, agent) {
|
|
71266
|
+
const doc = import_yaml13.parseDocument(yamlText);
|
|
71267
|
+
const v = doc.getIn(["agents", agent, blockKey(provider), "account"]);
|
|
71268
|
+
return typeof v === "string" ? v : null;
|
|
71269
|
+
}
|
|
71270
|
+
|
|
71271
|
+
// src/web/config-edit-plan.ts
|
|
71272
|
+
init_schema();
|
|
71273
|
+
var import_yaml14 = __toESM(require_dist(), 1);
|
|
71274
|
+
import { readFileSync as readFileSync40 } from "node:fs";
|
|
71275
|
+
|
|
71276
|
+
class ConfigPlanError extends Error {
|
|
71277
|
+
}
|
|
71278
|
+
function composeTransforms(...transforms) {
|
|
71279
|
+
return (text) => transforms.reduce((acc, t) => t(acc), text);
|
|
71280
|
+
}
|
|
71281
|
+
function planConfigEdit(configPath, transform) {
|
|
71282
|
+
let before;
|
|
71283
|
+
try {
|
|
71284
|
+
before = readFileSync40(configPath, "utf-8");
|
|
71285
|
+
} catch (err) {
|
|
71286
|
+
throw new ConfigPlanError(`could not read config: ${err.message}`);
|
|
71287
|
+
}
|
|
71288
|
+
let after;
|
|
71289
|
+
try {
|
|
71290
|
+
after = transform(before);
|
|
71291
|
+
} catch (err) {
|
|
71292
|
+
throw new ConfigPlanError(`config edit failed: ${err.message}`);
|
|
71293
|
+
}
|
|
71294
|
+
if (after === before) {
|
|
71295
|
+
return { before, after, changed: false };
|
|
71296
|
+
}
|
|
71297
|
+
let parsed;
|
|
71298
|
+
try {
|
|
71299
|
+
parsed = import_yaml14.parse(after);
|
|
71300
|
+
} catch (err) {
|
|
71301
|
+
throw new ConfigPlanError(`edit produced unparseable YAML: ${err.message}`);
|
|
71302
|
+
}
|
|
71303
|
+
const check = SwitchroomConfigSchema.safeParse(parsed);
|
|
71304
|
+
if (!check.success) {
|
|
71305
|
+
throw new ConfigPlanError(`edit produced an invalid config: ${check.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
|
|
71306
|
+
}
|
|
71307
|
+
return { before, after, changed: true };
|
|
71308
|
+
}
|
|
71309
|
+
|
|
71310
|
+
// src/web/config-diff.ts
|
|
71311
|
+
import {
|
|
71312
|
+
mkdirSync as mkdirSync25,
|
|
71313
|
+
mkdtempSync as mkdtemp,
|
|
71314
|
+
rmSync as rmrf,
|
|
71315
|
+
writeFileSync as writeFileSync24
|
|
71316
|
+
} from "node:fs";
|
|
71317
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
71318
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
71319
|
+
import { join as join38 } from "node:path";
|
|
71320
|
+
|
|
71321
|
+
class ConfigDiffError extends Error {
|
|
71322
|
+
}
|
|
71323
|
+
function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
|
|
71324
|
+
if (before === after)
|
|
71325
|
+
return "";
|
|
71326
|
+
const dir = mkdtemp(join38(tmpdir4(), "switchroom-config-diff-"));
|
|
71327
|
+
try {
|
|
71328
|
+
mkdirSync25(join38(dir, "cur"), { recursive: true });
|
|
71329
|
+
mkdirSync25(join38(dir, "new"), { recursive: true });
|
|
71330
|
+
writeFileSync24(join38(dir, "cur", name), before);
|
|
71331
|
+
writeFileSync24(join38(dir, "new", name), after);
|
|
71332
|
+
const r = spawnSync4(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
|
|
71333
|
+
if (r.status === 0)
|
|
71334
|
+
return "";
|
|
71335
|
+
if (r.status !== 1) {
|
|
71336
|
+
throw new ConfigDiffError(`git diff failed (status=${r.status}): ${(r.stderr || r.error?.message || "").toString().trim()}`);
|
|
71337
|
+
}
|
|
71338
|
+
const raw = r.stdout ?? "";
|
|
71339
|
+
if (!raw.includes("@@")) {
|
|
71340
|
+
throw new ConfigDiffError("git diff produced no hunks");
|
|
71341
|
+
}
|
|
71342
|
+
const diff = raw.split(`
|
|
71343
|
+
`).map((line) => {
|
|
71344
|
+
if (line.startsWith("diff --git "))
|
|
71345
|
+
return `diff --git a/${name} b/${name}`;
|
|
71346
|
+
if (line.startsWith("--- "))
|
|
71347
|
+
return `--- a/${name}`;
|
|
71348
|
+
if (line.startsWith("+++ "))
|
|
71349
|
+
return `+++ b/${name}`;
|
|
71350
|
+
return line;
|
|
71351
|
+
}).join(`
|
|
71352
|
+
`);
|
|
71353
|
+
return diff;
|
|
71354
|
+
} finally {
|
|
71355
|
+
try {
|
|
71356
|
+
rmrf(dir, { recursive: true, force: true });
|
|
71357
|
+
} catch {}
|
|
71358
|
+
}
|
|
71359
|
+
}
|
|
71360
|
+
|
|
71361
|
+
// src/web/hostd-config-propose.ts
|
|
71362
|
+
init_client4();
|
|
71363
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
71364
|
+
import { homedir as homedir20 } from "node:os";
|
|
71365
|
+
import { join as join39 } from "node:path";
|
|
71366
|
+
var PROPOSE_TIMEOUT_MS = 11 * 60 * 1000;
|
|
71367
|
+
function resolveHostdOperatorSocket(env2 = process.env) {
|
|
71368
|
+
const override = env2.SWITCHROOM_HOSTD_OPERATOR_SOCKET;
|
|
71369
|
+
if (override && override.length > 0)
|
|
71370
|
+
return override;
|
|
71371
|
+
const candidates = [
|
|
71372
|
+
"/host-home/.switchroom/hostd/operator/sock",
|
|
71373
|
+
join39(homedir20(), ".switchroom", "hostd", "operator", "sock")
|
|
71374
|
+
];
|
|
71375
|
+
for (const c of candidates) {
|
|
71376
|
+
if (existsSync44(c))
|
|
71377
|
+
return c;
|
|
71378
|
+
}
|
|
71379
|
+
return null;
|
|
71380
|
+
}
|
|
71381
|
+
async function proposeConfigEditViaHostd(args) {
|
|
71382
|
+
const req = {
|
|
71383
|
+
v: 1,
|
|
71384
|
+
op: "config_propose_edit",
|
|
71385
|
+
request_id: args.requestId,
|
|
71386
|
+
args: {
|
|
71387
|
+
unified_diff: args.unifiedDiff,
|
|
71388
|
+
reason: args.reason,
|
|
71389
|
+
target_path: "/state/config/switchroom.yaml"
|
|
71390
|
+
}
|
|
71391
|
+
};
|
|
71392
|
+
const send = args.send ?? hostdRequest;
|
|
71393
|
+
try {
|
|
71394
|
+
const resp = await send({ socketPath: args.socketPath, timeoutMs: args.timeoutMs ?? PROPOSE_TIMEOUT_MS }, req);
|
|
71395
|
+
if (resp.result === "completed")
|
|
71396
|
+
return { state: "applied" };
|
|
71397
|
+
if (resp.result === "denied") {
|
|
71398
|
+
return { state: "denied", reason: resp.error ?? "operator denied the change" };
|
|
71399
|
+
}
|
|
71400
|
+
return { state: "error", reason: resp.error ?? `hostd returned '${resp.result}'` };
|
|
71401
|
+
} catch (err) {
|
|
71402
|
+
return { state: "error", reason: err.message };
|
|
71403
|
+
}
|
|
71404
|
+
}
|
|
71405
|
+
|
|
71406
|
+
// src/web/api.ts
|
|
71209
71407
|
init_account_store();
|
|
71210
71408
|
init_client2();
|
|
71409
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
71211
71410
|
|
|
71212
71411
|
// telegram-plugin/registry/turns-schema.ts
|
|
71213
|
-
import { chmodSync as chmodSync8, mkdirSync as
|
|
71214
|
-
import { join as
|
|
71412
|
+
import { chmodSync as chmodSync8, mkdirSync as mkdirSync26 } from "fs";
|
|
71413
|
+
import { join as join40 } from "path";
|
|
71215
71414
|
var DatabaseClass = null;
|
|
71216
71415
|
function loadDatabaseClass() {
|
|
71217
71416
|
if (DatabaseClass != null)
|
|
@@ -71274,9 +71473,9 @@ function applySchema(db) {
|
|
|
71274
71473
|
}
|
|
71275
71474
|
function openTurnsDb(agentDir) {
|
|
71276
71475
|
const Database2 = loadDatabaseClass();
|
|
71277
|
-
const dir =
|
|
71278
|
-
|
|
71279
|
-
const path4 =
|
|
71476
|
+
const dir = join40(agentDir, "telegram");
|
|
71477
|
+
mkdirSync26(dir, { recursive: true, mode: 448 });
|
|
71478
|
+
const path4 = join40(dir, "registry.db");
|
|
71280
71479
|
const db = new Database2(path4, { create: true });
|
|
71281
71480
|
applySchema(db);
|
|
71282
71481
|
try {
|
|
@@ -71441,7 +71640,7 @@ function handleRestartAgent(name) {
|
|
|
71441
71640
|
}
|
|
71442
71641
|
}
|
|
71443
71642
|
function handleGetLogs(name, lines = 50) {
|
|
71444
|
-
const res =
|
|
71643
|
+
const res = spawnSync5("docker", ["logs", "--tail", String(lines), containerName(name)], { encoding: "utf-8", timeout: 5000 });
|
|
71445
71644
|
if (res.error) {
|
|
71446
71645
|
return { ok: false, error: res.error.message };
|
|
71447
71646
|
}
|
|
@@ -71634,7 +71833,7 @@ function inspectEnv(container, keys) {
|
|
|
71634
71833
|
const out = {};
|
|
71635
71834
|
for (const k of keys)
|
|
71636
71835
|
out[k] = null;
|
|
71637
|
-
const res =
|
|
71836
|
+
const res = spawnSync5("docker", ["inspect", "--format", "{{json .Config.Env}}", container], { encoding: "utf-8", timeout: 4000 });
|
|
71638
71837
|
if (res.error || res.status !== 0 || !res.stdout)
|
|
71639
71838
|
return out;
|
|
71640
71839
|
try {
|
|
@@ -71696,9 +71895,9 @@ async function handleGetSystemHealth(home2) {
|
|
|
71696
71895
|
};
|
|
71697
71896
|
try {
|
|
71698
71897
|
const logPath = defaultAuditLogPath2(home2);
|
|
71699
|
-
if (
|
|
71898
|
+
if (existsSync45(logPath)) {
|
|
71700
71899
|
hostd.auditLogPresent = true;
|
|
71701
|
-
const raw =
|
|
71900
|
+
const raw = readFileSync41(logPath, "utf-8");
|
|
71702
71901
|
hostd.recent = readAndFilter(raw, {}, 10);
|
|
71703
71902
|
}
|
|
71704
71903
|
} catch (err) {
|
|
@@ -71807,6 +72006,98 @@ function handleGetNotionWorkspace(config) {
|
|
|
71807
72006
|
fullAccessAgents
|
|
71808
72007
|
};
|
|
71809
72008
|
}
|
|
72009
|
+
var connectionAccessStatuses = new Map;
|
|
72010
|
+
function reapConnectionAccessStatuses(now = Date.now()) {
|
|
72011
|
+
for (const [id, s] of connectionAccessStatuses) {
|
|
72012
|
+
if (s.state !== "pending" && now - s.startedAt > 30 * 60000) {
|
|
72013
|
+
connectionAccessStatuses.delete(id);
|
|
72014
|
+
}
|
|
72015
|
+
}
|
|
72016
|
+
}
|
|
72017
|
+
function handleGetConnectionAccessStatus(requestId) {
|
|
72018
|
+
return connectionAccessStatuses.get(requestId) ?? { state: "unknown" };
|
|
72019
|
+
}
|
|
72020
|
+
function handleSetConnectionAccess(configPath, config, args, deps = {}) {
|
|
72021
|
+
const provider = args.provider;
|
|
72022
|
+
if (provider !== "google" && provider !== "microsoft") {
|
|
72023
|
+
return { ok: false, error: `unsupported provider '${args.provider}' (google|microsoft)` };
|
|
72024
|
+
}
|
|
72025
|
+
if (args.action !== "enable" && args.action !== "disable") {
|
|
72026
|
+
return { ok: false, error: `action must be 'enable' or 'disable'` };
|
|
72027
|
+
}
|
|
72028
|
+
const agent = args.agent;
|
|
72029
|
+
if (!agent || !config.agents?.[agent]) {
|
|
72030
|
+
return { ok: false, error: `unknown agent '${agent}'` };
|
|
72031
|
+
}
|
|
72032
|
+
const account = String(args.account ?? "").trim().toLowerCase();
|
|
72033
|
+
if (!/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/.test(account)) {
|
|
72034
|
+
return { ok: false, error: `invalid account email '${args.account}'` };
|
|
72035
|
+
}
|
|
72036
|
+
const enableAcl = provider === "google" ? enableAgentsOnGoogleAccount : enableAgentsOnMicrosoftAccount;
|
|
72037
|
+
const disableAcl = provider === "google" ? disableAgentsOnGoogleAccount : disableAgentsOnMicrosoftAccount;
|
|
72038
|
+
let transform;
|
|
72039
|
+
if (args.action === "enable") {
|
|
72040
|
+
transform = composeTransforms((y) => enableAcl(y, account, [agent]), (y) => setAgentWorkspaceAccount(y, provider, agent, account));
|
|
72041
|
+
} else {
|
|
72042
|
+
transform = composeTransforms((y) => disableAcl(y, account, [agent]), (y) => getAgentWorkspaceAccount(y, provider, agent) === account ? clearAgentWorkspaceAccount(y, provider, agent) : y);
|
|
72043
|
+
}
|
|
72044
|
+
let plan;
|
|
72045
|
+
try {
|
|
72046
|
+
plan = planConfigEdit(configPath, transform);
|
|
72047
|
+
} catch (err) {
|
|
72048
|
+
return {
|
|
72049
|
+
ok: false,
|
|
72050
|
+
error: err instanceof ConfigPlanError ? err.message : err.message
|
|
72051
|
+
};
|
|
72052
|
+
}
|
|
72053
|
+
if (!plan.changed) {
|
|
72054
|
+
return { ok: true, changed: false };
|
|
72055
|
+
}
|
|
72056
|
+
const socketPath = deps.socketPath !== undefined ? deps.socketPath : resolveHostdOperatorSocket();
|
|
72057
|
+
if (!socketPath) {
|
|
72058
|
+
return {
|
|
72059
|
+
ok: false,
|
|
72060
|
+
error: "hostd operator socket not found \u2014 credential grants require hostd (the Telegram approval path). " + "Ensure host_control is enabled and the switchroom-web container mounts ~/.switchroom."
|
|
72061
|
+
};
|
|
72062
|
+
}
|
|
72063
|
+
let diff;
|
|
72064
|
+
try {
|
|
72065
|
+
diff = (deps.generateDiff ?? generateUnifiedDiff)(plan.before, plan.after);
|
|
72066
|
+
} catch (err) {
|
|
72067
|
+
return { ok: false, error: `could not build config diff: ${err.message}` };
|
|
72068
|
+
}
|
|
72069
|
+
const now = deps.now ?? Date.now;
|
|
72070
|
+
const requestId = randomUUID4();
|
|
72071
|
+
const reason = `dashboard: ${args.action} ${agent} on ${account} (${provider})`;
|
|
72072
|
+
connectionAccessStatuses.set(requestId, { state: "pending", startedAt: now() });
|
|
72073
|
+
reapConnectionAccessStatuses(now());
|
|
72074
|
+
const propose = deps.propose ?? ((reqId, d, r) => proposeConfigEditViaHostd({ requestId: reqId, unifiedDiff: d, reason: r, socketPath }));
|
|
72075
|
+
propose(requestId, diff, reason).then((outcome) => {
|
|
72076
|
+
const startedAt = connectionAccessStatuses.get(requestId)?.startedAt ?? now();
|
|
72077
|
+
if (outcome.state === "applied") {
|
|
72078
|
+
connectionAccessStatuses.set(requestId, { state: "applied", startedAt, restartAgent: agent });
|
|
72079
|
+
} else if (outcome.state === "denied") {
|
|
72080
|
+
connectionAccessStatuses.set(requestId, { state: "denied", startedAt, reason: outcome.reason });
|
|
72081
|
+
} else {
|
|
72082
|
+
connectionAccessStatuses.set(requestId, { state: "error", startedAt, reason: outcome.reason });
|
|
72083
|
+
}
|
|
72084
|
+
captureEvent("connection_access", {
|
|
72085
|
+
provider,
|
|
72086
|
+
action: args.action,
|
|
72087
|
+
outcome: outcome.state,
|
|
72088
|
+
source: "web_api"
|
|
72089
|
+
});
|
|
72090
|
+
}).catch((err) => {
|
|
72091
|
+
const startedAt = connectionAccessStatuses.get(requestId)?.startedAt ?? now();
|
|
72092
|
+
connectionAccessStatuses.set(requestId, {
|
|
72093
|
+
state: "error",
|
|
72094
|
+
startedAt,
|
|
72095
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
72096
|
+
});
|
|
72097
|
+
captureException(err, { action: "connection_access", provider });
|
|
72098
|
+
});
|
|
72099
|
+
return { ok: true, changed: true, requestId, pendingApproval: true };
|
|
72100
|
+
}
|
|
71810
72101
|
function handleGetSchedule(config) {
|
|
71811
72102
|
const entries = collectScheduleEntries(config);
|
|
71812
72103
|
const agentsDir = resolveAgentsDir(config);
|
|
@@ -71841,9 +72132,9 @@ async function handleGetApprovals() {
|
|
|
71841
72132
|
}
|
|
71842
72133
|
|
|
71843
72134
|
// src/web/webhook-handler.ts
|
|
71844
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
71845
|
-
import { join as
|
|
71846
|
-
import { homedir as
|
|
72135
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync47, mkdirSync as mkdirSync27, readFileSync as readFileSync43, writeFileSync as writeFileSync25 } from "fs";
|
|
72136
|
+
import { join as join42 } from "path";
|
|
72137
|
+
import { homedir as homedir22 } from "os";
|
|
71847
72138
|
|
|
71848
72139
|
// src/web/webhook-verify.ts
|
|
71849
72140
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -71993,20 +72284,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
71993
72284
|
}
|
|
71994
72285
|
|
|
71995
72286
|
// src/web/webhook-edge.ts
|
|
71996
|
-
import { existsSync as
|
|
71997
|
-
import { join as
|
|
71998
|
-
import { homedir as
|
|
72287
|
+
import { existsSync as existsSync46, readFileSync as readFileSync42 } from "fs";
|
|
72288
|
+
import { join as join41 } from "path";
|
|
72289
|
+
import { homedir as homedir21 } from "os";
|
|
71999
72290
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
72000
72291
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
72001
72292
|
function edgeSecretPath() {
|
|
72002
|
-
return
|
|
72293
|
+
return join41(homedir21(), ".switchroom", "webhook-edge-secret");
|
|
72003
72294
|
}
|
|
72004
72295
|
function loadEdgeSecret(path4) {
|
|
72005
72296
|
const p = path4 ?? edgeSecretPath();
|
|
72006
72297
|
try {
|
|
72007
|
-
if (!
|
|
72298
|
+
if (!existsSync46(p))
|
|
72008
72299
|
return null;
|
|
72009
|
-
const raw =
|
|
72300
|
+
const raw = readFileSync42(p, "utf-8").trim();
|
|
72010
72301
|
return raw.length > 0 ? raw : null;
|
|
72011
72302
|
} catch {
|
|
72012
72303
|
return null;
|
|
@@ -72038,9 +72329,9 @@ var DEDUP_MAX = 1000;
|
|
|
72038
72329
|
var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
72039
72330
|
function loadDedupFile(path4) {
|
|
72040
72331
|
try {
|
|
72041
|
-
if (!
|
|
72332
|
+
if (!existsSync47(path4))
|
|
72042
72333
|
return {};
|
|
72043
|
-
const raw = JSON.parse(
|
|
72334
|
+
const raw = JSON.parse(readFileSync43(path4, "utf-8"));
|
|
72044
72335
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
72045
72336
|
} catch {
|
|
72046
72337
|
return {};
|
|
@@ -72054,7 +72345,7 @@ function saveDedupFile(path4, deliveries, now) {
|
|
|
72054
72345
|
}
|
|
72055
72346
|
const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
|
|
72056
72347
|
const final = Object.fromEntries(sorted);
|
|
72057
|
-
|
|
72348
|
+
writeFileSync25(path4, JSON.stringify({ deliveries: final }), {
|
|
72058
72349
|
mode: 384
|
|
72059
72350
|
});
|
|
72060
72351
|
}
|
|
@@ -72062,8 +72353,8 @@ var agentDedupCache = new Map;
|
|
|
72062
72353
|
function createFileDedupStore(resolveAgentDir) {
|
|
72063
72354
|
return {
|
|
72064
72355
|
check(agent, deliveryId, now) {
|
|
72065
|
-
const telegramDir =
|
|
72066
|
-
const filePath =
|
|
72356
|
+
const telegramDir = join42(resolveAgentDir(agent), "telegram");
|
|
72357
|
+
const filePath = join42(telegramDir, "webhook-dedup.json");
|
|
72067
72358
|
if (!agentDedupCache.has(agent)) {
|
|
72068
72359
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
72069
72360
|
}
|
|
@@ -72073,7 +72364,7 @@ function createFileDedupStore(resolveAgentDir) {
|
|
|
72073
72364
|
}
|
|
72074
72365
|
deliveries[deliveryId] = now;
|
|
72075
72366
|
try {
|
|
72076
|
-
|
|
72367
|
+
mkdirSync27(telegramDir, { recursive: true });
|
|
72077
72368
|
saveDedupFile(filePath, deliveries, now);
|
|
72078
72369
|
} catch {}
|
|
72079
72370
|
return;
|
|
@@ -72115,9 +72406,9 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
|
|
|
72115
72406
|
return true;
|
|
72116
72407
|
}
|
|
72117
72408
|
function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
72118
|
-
const issuesPath =
|
|
72409
|
+
const issuesPath = join42(telegramDir, "issues.jsonl");
|
|
72119
72410
|
try {
|
|
72120
|
-
|
|
72411
|
+
mkdirSync27(telegramDir, { recursive: true });
|
|
72121
72412
|
const record = {
|
|
72122
72413
|
ts: now,
|
|
72123
72414
|
agent,
|
|
@@ -72140,7 +72431,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
72140
72431
|
async function handleWebhookIngest(args, deps = {}) {
|
|
72141
72432
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
72142
72433
|
const now = (deps.now ?? Date.now)();
|
|
72143
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) =>
|
|
72434
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join42(homedir22(), ".switchroom", "agents", a));
|
|
72144
72435
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
72145
72436
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
72146
72437
|
if (!args.agentExists) {
|
|
@@ -72204,7 +72495,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
72204
72495
|
if (retryAfter !== null) {
|
|
72205
72496
|
if (!args.viaGateway) {
|
|
72206
72497
|
const agentDir2 = resolveAgentDir(args.agent);
|
|
72207
|
-
const telegramDir2 =
|
|
72498
|
+
const telegramDir2 = join42(agentDir2, "telegram");
|
|
72208
72499
|
if (shouldWriteThrottleIssue(args.agent, source, now)) {
|
|
72209
72500
|
writeThrottleIssue(args.agent, source, now, telegramDir2, log);
|
|
72210
72501
|
}
|
|
@@ -72224,7 +72515,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
72224
72515
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : args.source;
|
|
72225
72516
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
72226
72517
|
if (args.viaGateway) {
|
|
72227
|
-
const socketPath =
|
|
72518
|
+
const socketPath = join42(resolveAgentDir(args.agent), "telegram", "webhook.sock");
|
|
72228
72519
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
72229
72520
|
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
72230
72521
|
let resp;
|
|
@@ -72263,10 +72554,10 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
72263
72554
|
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
72264
72555
|
}
|
|
72265
72556
|
const agentDir = resolveAgentDir(args.agent);
|
|
72266
|
-
const telegramDir =
|
|
72267
|
-
const logPath =
|
|
72557
|
+
const telegramDir = join42(agentDir, "telegram");
|
|
72558
|
+
const logPath = join42(telegramDir, "webhook-events.jsonl");
|
|
72268
72559
|
try {
|
|
72269
|
-
|
|
72560
|
+
mkdirSync27(telegramDir, { recursive: true });
|
|
72270
72561
|
const record = {
|
|
72271
72562
|
ts: now,
|
|
72272
72563
|
source,
|
|
@@ -72317,15 +72608,15 @@ function resolveWebToken() {
|
|
|
72317
72608
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
72318
72609
|
if (fromEnv && fromEnv.length > 0)
|
|
72319
72610
|
return fromEnv;
|
|
72320
|
-
const home2 = process.env.HOME ??
|
|
72321
|
-
const tokenPath =
|
|
72322
|
-
if (
|
|
72323
|
-
const existing =
|
|
72611
|
+
const home2 = process.env.HOME ?? homedir23();
|
|
72612
|
+
const tokenPath = join43(home2, ".switchroom", "web-token");
|
|
72613
|
+
if (existsSync48(tokenPath)) {
|
|
72614
|
+
const existing = readFileSync44(tokenPath, "utf8").trim();
|
|
72324
72615
|
if (existing.length > 0)
|
|
72325
72616
|
return existing;
|
|
72326
72617
|
}
|
|
72327
72618
|
const token = randomBytes11(32).toString("hex");
|
|
72328
|
-
|
|
72619
|
+
mkdirSync28(dirname9(tokenPath), { recursive: true, mode: 448 });
|
|
72329
72620
|
try {
|
|
72330
72621
|
const fd = openSync10(tokenPath, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL, 384);
|
|
72331
72622
|
try {
|
|
@@ -72337,7 +72628,7 @@ function resolveWebToken() {
|
|
|
72337
72628
|
return token;
|
|
72338
72629
|
} catch (err) {
|
|
72339
72630
|
if (err.code === "EEXIST") {
|
|
72340
|
-
const existing =
|
|
72631
|
+
const existing = readFileSync44(tokenPath, "utf8").trim();
|
|
72341
72632
|
if (existing.length > 0)
|
|
72342
72633
|
return existing;
|
|
72343
72634
|
}
|
|
@@ -72403,11 +72694,11 @@ function checkWsAuth(req, token, server) {
|
|
|
72403
72694
|
return presented !== null && constantTimeEqual(presented, token);
|
|
72404
72695
|
}
|
|
72405
72696
|
function loadWebhookSecrets() {
|
|
72406
|
-
const path4 =
|
|
72407
|
-
if (!
|
|
72697
|
+
const path4 = join43(homedir23(), ".switchroom", "webhook-secrets.json");
|
|
72698
|
+
if (!existsSync48(path4))
|
|
72408
72699
|
return {};
|
|
72409
72700
|
try {
|
|
72410
|
-
const parsed = JSON.parse(
|
|
72701
|
+
const parsed = JSON.parse(readFileSync44(path4, "utf-8"));
|
|
72411
72702
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
72412
72703
|
} catch (err) {
|
|
72413
72704
|
process.stderr.write(`webhook-ingest: failed to parse ${path4}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
@@ -72499,6 +72790,16 @@ function parseRoute(pathname, method) {
|
|
|
72499
72790
|
if (method === "GET" && pathname === "/api/accounts") {
|
|
72500
72791
|
return { handler: "getAccounts", params: {} };
|
|
72501
72792
|
}
|
|
72793
|
+
if (method === "POST" && pathname === "/api/connections/access") {
|
|
72794
|
+
return { handler: "setConnectionAccess", params: {} };
|
|
72795
|
+
}
|
|
72796
|
+
const accessStatusMatch = pathname.match(/^\/api\/connections\/access\/([^/]+)$/);
|
|
72797
|
+
if (method === "GET" && accessStatusMatch) {
|
|
72798
|
+
return {
|
|
72799
|
+
handler: "getConnectionAccessStatus",
|
|
72800
|
+
params: { requestId: decodeURIComponent(accessStatusMatch[1]) }
|
|
72801
|
+
};
|
|
72802
|
+
}
|
|
72502
72803
|
if (method === "POST" && pathname === "/api/auth/use") {
|
|
72503
72804
|
return { handler: "useAccount", params: {} };
|
|
72504
72805
|
}
|
|
@@ -72517,8 +72818,17 @@ function parseRoute(pathname, method) {
|
|
|
72517
72818
|
}
|
|
72518
72819
|
function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
72519
72820
|
const uiDirRaw = resolve28(import.meta.dirname, "ui");
|
|
72520
|
-
const uiDir =
|
|
72821
|
+
const uiDir = existsSync48(uiDirRaw) ? realpathSync3(uiDirRaw) : uiDirRaw;
|
|
72521
72822
|
const token = resolveWebToken();
|
|
72823
|
+
const freshConfig = () => {
|
|
72824
|
+
if (!configPath)
|
|
72825
|
+
return config;
|
|
72826
|
+
try {
|
|
72827
|
+
return loadConfig(configPath);
|
|
72828
|
+
} catch {
|
|
72829
|
+
return config;
|
|
72830
|
+
}
|
|
72831
|
+
};
|
|
72522
72832
|
const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
72523
72833
|
const server = Bun.serve({
|
|
72524
72834
|
port,
|
|
@@ -72614,11 +72924,11 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
72614
72924
|
case "getSystemHealth":
|
|
72615
72925
|
return (async () => jsonResponse(await handleGetSystemHealth()))();
|
|
72616
72926
|
case "getGoogleAccounts":
|
|
72617
|
-
return (async () => jsonResponse(await handleGetGoogleAccounts(
|
|
72927
|
+
return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
|
|
72618
72928
|
case "getMicrosoftAccounts":
|
|
72619
|
-
return (async () => jsonResponse(await handleGetMicrosoftAccounts(
|
|
72929
|
+
return (async () => jsonResponse(await handleGetMicrosoftAccounts(freshConfig())))();
|
|
72620
72930
|
case "getNotionWorkspace":
|
|
72621
|
-
return jsonResponse(handleGetNotionWorkspace(
|
|
72931
|
+
return jsonResponse(handleGetNotionWorkspace(freshConfig()));
|
|
72622
72932
|
case "getSchedule":
|
|
72623
72933
|
return jsonResponse(handleGetSchedule(config));
|
|
72624
72934
|
case "getApprovals":
|
|
@@ -72644,6 +72954,28 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
72644
72954
|
return jsonResponse(result);
|
|
72645
72955
|
})();
|
|
72646
72956
|
}
|
|
72957
|
+
case "setConnectionAccess": {
|
|
72958
|
+
return (async () => {
|
|
72959
|
+
if (!configPath) {
|
|
72960
|
+
return jsonResponse({ ok: false, error: "config path not available to the web server" }, 500);
|
|
72961
|
+
}
|
|
72962
|
+
let body;
|
|
72963
|
+
try {
|
|
72964
|
+
body = await req.json();
|
|
72965
|
+
} catch {
|
|
72966
|
+
return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
|
|
72967
|
+
}
|
|
72968
|
+
const result = handleSetConnectionAccess(configPath, freshConfig(), {
|
|
72969
|
+
provider: String(body.provider ?? ""),
|
|
72970
|
+
account: String(body.account ?? ""),
|
|
72971
|
+
agent: String(body.agent ?? ""),
|
|
72972
|
+
action: String(body.action ?? "")
|
|
72973
|
+
});
|
|
72974
|
+
return jsonResponse(result, result.ok ? 200 : 400);
|
|
72975
|
+
})();
|
|
72976
|
+
}
|
|
72977
|
+
case "getConnectionAccessStatus":
|
|
72978
|
+
return jsonResponse(handleGetConnectionAccessStatus(route.params.requestId));
|
|
72647
72979
|
case "refreshQuota": {
|
|
72648
72980
|
return (async () => {
|
|
72649
72981
|
let body = {};
|
|
@@ -72676,8 +73008,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
72676
73008
|
}
|
|
72677
73009
|
}
|
|
72678
73010
|
let filePath = pathname === "/" ? "/index.html" : pathname;
|
|
72679
|
-
const fullPath =
|
|
72680
|
-
if (!
|
|
73011
|
+
const fullPath = join43(uiDir, filePath);
|
|
73012
|
+
if (!existsSync48(fullPath)) {
|
|
72681
73013
|
return new Response("Not Found", { status: 404 });
|
|
72682
73014
|
}
|
|
72683
73015
|
let realFullPath;
|
|
@@ -72692,7 +73024,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
72692
73024
|
}
|
|
72693
73025
|
const ext = extname(realFullPath);
|
|
72694
73026
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
72695
|
-
const content =
|
|
73027
|
+
const content = readFileSync44(realFullPath);
|
|
72696
73028
|
return new Response(content, {
|
|
72697
73029
|
headers: { "Content-Type": contentType }
|
|
72698
73030
|
});
|
|
@@ -72826,26 +73158,26 @@ Starting Switchroom dashboard...
|
|
|
72826
73158
|
// src/cli/setup.ts
|
|
72827
73159
|
init_source();
|
|
72828
73160
|
init_loader();
|
|
72829
|
-
import { existsSync as
|
|
73161
|
+
import { existsSync as existsSync49, copyFileSync as copyFileSync8, readFileSync as readFileSync45, writeFileSync as writeFileSync26, mkdirSync as mkdirSync29 } from "node:fs";
|
|
72830
73162
|
import { resolve as resolve29, dirname as dirname10 } from "node:path";
|
|
72831
73163
|
init_vault();
|
|
72832
73164
|
init_manager();
|
|
72833
73165
|
|
|
72834
73166
|
// src/cli/setup-posture-rewrite.ts
|
|
72835
|
-
var
|
|
73167
|
+
var import_yaml15 = __toESM(require_dist(), 1);
|
|
72836
73168
|
function insertVaultBrokerApprovalAuth(source, value = "telegram-id") {
|
|
72837
73169
|
let doc;
|
|
72838
73170
|
try {
|
|
72839
|
-
doc =
|
|
73171
|
+
doc = import_yaml15.default.parseDocument(source);
|
|
72840
73172
|
} catch {
|
|
72841
73173
|
return { kind: "not-found" };
|
|
72842
73174
|
}
|
|
72843
73175
|
const vault = doc.get("vault");
|
|
72844
|
-
if (!vault || !
|
|
73176
|
+
if (!vault || !import_yaml15.default.isMap(vault)) {
|
|
72845
73177
|
return { kind: "not-found" };
|
|
72846
73178
|
}
|
|
72847
73179
|
const broker = vault.get("broker");
|
|
72848
|
-
if (!broker || !
|
|
73180
|
+
if (!broker || !import_yaml15.default.isMap(broker)) {
|
|
72849
73181
|
return { kind: "not-found" };
|
|
72850
73182
|
}
|
|
72851
73183
|
if (broker.has("approvalAuth")) {
|
|
@@ -72896,7 +73228,7 @@ ${childIndent}approvalAuth: ${value}`;
|
|
|
72896
73228
|
}
|
|
72897
73229
|
|
|
72898
73230
|
// src/cli/supergroup-setup-yaml.ts
|
|
72899
|
-
var
|
|
73231
|
+
var import_yaml16 = __toESM(require_dist(), 1);
|
|
72900
73232
|
function isValidSupergroupChatId(value) {
|
|
72901
73233
|
return /^-\d+$/.test(value);
|
|
72902
73234
|
}
|
|
@@ -72904,23 +73236,23 @@ function setAgentSupergroupChatId(yamlText, agentName, chatId) {
|
|
|
72904
73236
|
if (!isValidSupergroupChatId(chatId)) {
|
|
72905
73237
|
throw new Error(`setAgentSupergroupChatId: chat_id must be a negative integer string (got "${chatId}")`);
|
|
72906
73238
|
}
|
|
72907
|
-
const doc =
|
|
73239
|
+
const doc = import_yaml16.parseDocument(yamlText);
|
|
72908
73240
|
const root = doc.contents;
|
|
72909
|
-
if (!
|
|
73241
|
+
if (!import_yaml16.isMap(root)) {
|
|
72910
73242
|
throw new Error("setAgentSupergroupChatId: YAML root is not a map");
|
|
72911
73243
|
}
|
|
72912
73244
|
const agents = root.get("agents", true);
|
|
72913
|
-
if (!
|
|
73245
|
+
if (!import_yaml16.isMap(agents)) {
|
|
72914
73246
|
throw new Error("setAgentSupergroupChatId: config has no `agents:` map");
|
|
72915
73247
|
}
|
|
72916
73248
|
const agent = agents.get(agentName, true);
|
|
72917
|
-
if (!
|
|
73249
|
+
if (!import_yaml16.isMap(agent)) {
|
|
72918
73250
|
throw new Error(`setAgentSupergroupChatId: agent "${agentName}" not found in config`);
|
|
72919
73251
|
}
|
|
72920
73252
|
const channels = agent.get("channels", true);
|
|
72921
|
-
if (
|
|
73253
|
+
if (import_yaml16.isMap(channels)) {
|
|
72922
73254
|
const telegram = channels.get("telegram", true);
|
|
72923
|
-
if (
|
|
73255
|
+
if (import_yaml16.isMap(telegram)) {
|
|
72924
73256
|
if (telegram.get("chat_id") === chatId)
|
|
72925
73257
|
return yamlText;
|
|
72926
73258
|
telegram.set("chat_id", chatId);
|
|
@@ -73008,7 +73340,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
73008
73340
|
existingConfig = null;
|
|
73009
73341
|
}
|
|
73010
73342
|
}
|
|
73011
|
-
if (existingConfig &&
|
|
73343
|
+
if (existingConfig && existsSync49(existingConfig)) {
|
|
73012
73344
|
if (!nonInteractive) {
|
|
73013
73345
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
73014
73346
|
if (!useExisting) {
|
|
@@ -73036,10 +73368,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
73036
73368
|
}
|
|
73037
73369
|
const srcFile = resolve29(examplesDir, `${choice}.yaml`);
|
|
73038
73370
|
const destFile = resolvePath("~/.switchroom/switchroom.yaml");
|
|
73039
|
-
if (!
|
|
73371
|
+
if (!existsSync49(srcFile)) {
|
|
73040
73372
|
throw new ConfigError(`Example config not found: ${choice}.yaml`);
|
|
73041
73373
|
}
|
|
73042
|
-
|
|
73374
|
+
mkdirSync29(dirname10(destFile), { recursive: true });
|
|
73043
73375
|
copyFileSync8(srcFile, destFile);
|
|
73044
73376
|
console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
|
|
73045
73377
|
console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
|
|
@@ -73120,7 +73452,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
73120
73452
|
try {
|
|
73121
73453
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
73122
73454
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
73123
|
-
if (
|
|
73455
|
+
if (existsSync49(vaultPath)) {
|
|
73124
73456
|
const secrets = openVault2(passphrase, vaultPath);
|
|
73125
73457
|
const key = rawToken.replace("vault:", "");
|
|
73126
73458
|
const entry = secrets[key];
|
|
@@ -73148,7 +73480,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
73148
73480
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
73149
73481
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
73150
73482
|
const key = vaultRef.replace("vault:", "");
|
|
73151
|
-
if (!
|
|
73483
|
+
if (!existsSync49(vaultPath)) {
|
|
73152
73484
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
73153
73485
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
73154
73486
|
if (!passphrase) {
|
|
@@ -73321,7 +73653,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
|
73321
73653
|
try {
|
|
73322
73654
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
73323
73655
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
73324
|
-
if (passphrase &&
|
|
73656
|
+
if (passphrase && existsSync49(vaultPath)) {
|
|
73325
73657
|
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
73326
73658
|
if (existing) {
|
|
73327
73659
|
console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
|
|
@@ -73449,16 +73781,16 @@ async function stepScaffoldAgents(config, agentBots, userId, nonInteractive, swi
|
|
|
73449
73781
|
}
|
|
73450
73782
|
async function ensureAuthActiveDefault(configPath) {
|
|
73451
73783
|
const fs4 = await import("node:fs");
|
|
73452
|
-
const { parseDocument:
|
|
73784
|
+
const { parseDocument: parseDocument8, isMap: isMap8 } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
|
|
73453
73785
|
const { atomicWriteFileSync: atomicWriteFileSync3 } = await Promise.resolve().then(() => (init_atomic(), exports_atomic));
|
|
73454
73786
|
const { setAuthActive: setAuthActive2 } = await Promise.resolve().then(() => (init_auth_active_yaml(), exports_auth_active_yaml));
|
|
73455
73787
|
const raw = fs4.readFileSync(configPath, "utf-8");
|
|
73456
|
-
const doc =
|
|
73788
|
+
const doc = parseDocument8(raw);
|
|
73457
73789
|
const root = doc.contents;
|
|
73458
|
-
if (!
|
|
73790
|
+
if (!isMap8(root))
|
|
73459
73791
|
return;
|
|
73460
73792
|
const existing = root.get("auth", true);
|
|
73461
|
-
if (
|
|
73793
|
+
if (isMap8(existing) && existing.has("active"))
|
|
73462
73794
|
return;
|
|
73463
73795
|
const after = setAuthActive2(raw, "default");
|
|
73464
73796
|
if (after === raw)
|
|
@@ -73481,13 +73813,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
73481
73813
|
return;
|
|
73482
73814
|
}
|
|
73483
73815
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
73484
|
-
if (!
|
|
73816
|
+
if (!existsSync49(vaultPath)) {
|
|
73485
73817
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
73486
73818
|
return;
|
|
73487
73819
|
}
|
|
73488
73820
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
73489
73821
|
const credPath = resolvePath(credPathRaw);
|
|
73490
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
73822
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync49(credPath)) {
|
|
73491
73823
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
73492
73824
|
return;
|
|
73493
73825
|
}
|
|
@@ -73553,12 +73885,12 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
73553
73885
|
const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
|
|
73554
73886
|
if (choice === TELEGRAM_ID_CHOICE) {
|
|
73555
73887
|
try {
|
|
73556
|
-
const yamlPath =
|
|
73557
|
-
if (
|
|
73558
|
-
const content =
|
|
73888
|
+
const yamlPath = existsSync49(resolve29(process.cwd(), "switchroom.yaml")) ? resolve29(process.cwd(), "switchroom.yaml") : resolve29(process.cwd(), "switchroom.yml");
|
|
73889
|
+
if (existsSync49(yamlPath)) {
|
|
73890
|
+
const content = readFileSync45(yamlPath, "utf-8");
|
|
73559
73891
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
73560
73892
|
if (result.kind === "rewritten") {
|
|
73561
|
-
|
|
73893
|
+
writeFileSync26(yamlPath, result.content, "utf-8");
|
|
73562
73894
|
console.log(source_default.green(` ${STEP_DONE} Set vault.broker.approvalAuth: telegram-id in ${yamlPath}`));
|
|
73563
73895
|
} else if (result.kind === "already-set") {
|
|
73564
73896
|
console.log(source_default.gray(" approvalAuth already set \u2014 leaving it alone."));
|
|
@@ -73589,8 +73921,8 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
73589
73921
|
resolve29(process.cwd(), "switchroom.yml")
|
|
73590
73922
|
];
|
|
73591
73923
|
for (const configPath of configPaths) {
|
|
73592
|
-
if (
|
|
73593
|
-
let content =
|
|
73924
|
+
if (existsSync49(configPath)) {
|
|
73925
|
+
let content = readFileSync45(configPath, "utf-8");
|
|
73594
73926
|
const agentNames = Object.keys(config.agents);
|
|
73595
73927
|
for (const name of agentNames) {
|
|
73596
73928
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -73604,7 +73936,7 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
73604
73936
|
}
|
|
73605
73937
|
config.agents[name].dangerous_mode = true;
|
|
73606
73938
|
}
|
|
73607
|
-
|
|
73939
|
+
writeFileSync26(configPath, content, "utf-8");
|
|
73608
73940
|
console.log(source_default.green(` ${STEP_DONE} Enabled dangerous_mode for all agents in ${configPath}`));
|
|
73609
73941
|
break;
|
|
73610
73942
|
}
|
|
@@ -73719,17 +74051,17 @@ init_doctor();
|
|
|
73719
74051
|
init_source();
|
|
73720
74052
|
init_loader();
|
|
73721
74053
|
init_lifecycle();
|
|
73722
|
-
import { cpSync as cpSync2, existsSync as
|
|
73723
|
-
import { spawnSync as
|
|
73724
|
-
import { join as
|
|
73725
|
-
import { homedir as
|
|
73726
|
-
var DEFAULT_COMPOSE_PATH =
|
|
74054
|
+
import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync31, readFileSync as readFileSync50, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
|
|
74055
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
74056
|
+
import { join as join57, dirname as dirname13, resolve as resolve33 } from "node:path";
|
|
74057
|
+
import { homedir as homedir34 } from "node:os";
|
|
74058
|
+
var DEFAULT_COMPOSE_PATH = join57(homedir34(), ".switchroom", "compose", "docker-compose.yml");
|
|
73727
74059
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
73728
74060
|
let dir = dirname13(scriptPath);
|
|
73729
74061
|
for (let i = 0;i < 12; i++) {
|
|
73730
|
-
if (
|
|
74062
|
+
if (existsSync56(join57(dir, ".git"))) {
|
|
73731
74063
|
try {
|
|
73732
|
-
const pkg = JSON.parse(
|
|
74064
|
+
const pkg = JSON.parse(readFileSync50(join57(dir, "package.json"), "utf-8"));
|
|
73733
74065
|
if (pkg.name === "switchroom")
|
|
73734
74066
|
return true;
|
|
73735
74067
|
} catch {}
|
|
@@ -73781,7 +74113,7 @@ function planUpdate(opts) {
|
|
|
73781
74113
|
steps.push({
|
|
73782
74114
|
name: "pull-images",
|
|
73783
74115
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
73784
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
74116
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync56(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
73785
74117
|
run: () => {
|
|
73786
74118
|
const r = runner("docker", [
|
|
73787
74119
|
"compose",
|
|
@@ -73880,17 +74212,17 @@ function planUpdate(opts) {
|
|
|
73880
74212
|
return;
|
|
73881
74213
|
}
|
|
73882
74214
|
const source = resolve33(import.meta.dirname, "../../skills");
|
|
73883
|
-
const dest =
|
|
73884
|
-
if (!
|
|
74215
|
+
const dest = join57(homedir34(), ".switchroom", "skills", "_bundled");
|
|
74216
|
+
if (!existsSync56(source)) {
|
|
73885
74217
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
73886
74218
|
`);
|
|
73887
74219
|
return;
|
|
73888
74220
|
}
|
|
73889
74221
|
try {
|
|
73890
|
-
if (
|
|
74222
|
+
if (existsSync56(dest)) {
|
|
73891
74223
|
rmSync12(dest, { recursive: true, force: true });
|
|
73892
74224
|
}
|
|
73893
|
-
|
|
74225
|
+
mkdirSync31(dirname13(dest), { recursive: true });
|
|
73894
74226
|
cpSync2(source, dest, { recursive: true, dereference: false });
|
|
73895
74227
|
} catch (err) {
|
|
73896
74228
|
throw new Error(`sync-bundled-skills failed: ${err.message}`);
|
|
@@ -73949,7 +74281,7 @@ function planUpdate(opts) {
|
|
|
73949
74281
|
return steps;
|
|
73950
74282
|
}
|
|
73951
74283
|
function defaultRunner(cmd, args) {
|
|
73952
|
-
const r =
|
|
74284
|
+
const r = spawnSync9(cmd, args, { stdio: "inherit" });
|
|
73953
74285
|
return { status: r.status ?? 1 };
|
|
73954
74286
|
}
|
|
73955
74287
|
function writeMarkerInPreferredLocation(agent, reason, runner) {
|
|
@@ -73994,10 +74326,10 @@ function defaultStatusProbe(composePath) {
|
|
|
73994
74326
|
} catch {}
|
|
73995
74327
|
let dir = dirname13(scriptPath);
|
|
73996
74328
|
for (let i = 0;i < 8; i++) {
|
|
73997
|
-
const pkgPath =
|
|
73998
|
-
if (
|
|
74329
|
+
const pkgPath = join57(dir, "package.json");
|
|
74330
|
+
if (existsSync56(pkgPath)) {
|
|
73999
74331
|
try {
|
|
74000
|
-
const pkg = JSON.parse(
|
|
74332
|
+
const pkg = JSON.parse(readFileSync50(pkgPath, "utf-8"));
|
|
74001
74333
|
if (typeof pkg.version === "string")
|
|
74002
74334
|
cliVersion = pkg.version;
|
|
74003
74335
|
} catch (err) {
|
|
@@ -74018,13 +74350,13 @@ function defaultStatusProbe(composePath) {
|
|
|
74018
74350
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
74019
74351
|
}
|
|
74020
74352
|
const services = [];
|
|
74021
|
-
if (!
|
|
74353
|
+
if (!existsSync56(composePath)) {
|
|
74022
74354
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
74023
74355
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
74024
74356
|
}
|
|
74025
74357
|
let serviceList = [];
|
|
74026
74358
|
try {
|
|
74027
|
-
const r =
|
|
74359
|
+
const r = spawnSync9("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
|
|
74028
74360
|
if (r.status !== 0) {
|
|
74029
74361
|
warnings.push(`docker compose config --services failed: ${r.stderr?.trim() ?? r.error?.message ?? "unknown"}`);
|
|
74030
74362
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
@@ -74041,7 +74373,7 @@ function defaultStatusProbe(composePath) {
|
|
|
74041
74373
|
let containerCreatedAt = null;
|
|
74042
74374
|
let status = "<unknown>";
|
|
74043
74375
|
try {
|
|
74044
|
-
const r =
|
|
74376
|
+
const r = spawnSync9("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
|
|
74045
74377
|
if (r.status === 0) {
|
|
74046
74378
|
const [img, created, st] = r.stdout.trim().split("|");
|
|
74047
74379
|
image = img ?? null;
|
|
@@ -74057,7 +74389,7 @@ function defaultStatusProbe(composePath) {
|
|
|
74057
74389
|
let imagePulledAt = null;
|
|
74058
74390
|
if (image) {
|
|
74059
74391
|
try {
|
|
74060
|
-
const r =
|
|
74392
|
+
const r = spawnSync9("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
|
|
74061
74393
|
if (r.status === 0) {
|
|
74062
74394
|
const [id, created, lastTag] = r.stdout.trim().split("|");
|
|
74063
74395
|
imageDigestShort = id?.replace(/^sha256:/, "").slice(0, 12) ?? null;
|
|
@@ -74213,8 +74545,8 @@ init_source();
|
|
|
74213
74545
|
init_helpers();
|
|
74214
74546
|
init_lifecycle();
|
|
74215
74547
|
import { execSync as execSync4 } from "node:child_process";
|
|
74216
|
-
import { existsSync as
|
|
74217
|
-
import { dirname as dirname14, join as
|
|
74548
|
+
import { existsSync as existsSync57, readFileSync as readFileSync51 } from "node:fs";
|
|
74549
|
+
import { dirname as dirname14, join as join58 } from "node:path";
|
|
74218
74550
|
function getClaudeCodeVersion() {
|
|
74219
74551
|
try {
|
|
74220
74552
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -74264,11 +74596,11 @@ function formatUptime3(timestamp) {
|
|
|
74264
74596
|
function locateSwitchroomInstallDir() {
|
|
74265
74597
|
let dir = import.meta.dirname;
|
|
74266
74598
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
74267
|
-
const pkgPath =
|
|
74268
|
-
if (
|
|
74599
|
+
const pkgPath = join58(dir, "package.json");
|
|
74600
|
+
if (existsSync57(pkgPath)) {
|
|
74269
74601
|
try {
|
|
74270
|
-
const pkg = JSON.parse(
|
|
74271
|
-
if (pkg.name === "switchroom" &&
|
|
74602
|
+
const pkg = JSON.parse(readFileSync51(pkgPath, "utf-8"));
|
|
74603
|
+
if (pkg.name === "switchroom" && existsSync57(join58(dir, ".git"))) {
|
|
74272
74604
|
return dir;
|
|
74273
74605
|
}
|
|
74274
74606
|
} catch {}
|
|
@@ -74494,18 +74826,18 @@ function registerHandoffCommand(program3) {
|
|
|
74494
74826
|
// src/issues/store.ts
|
|
74495
74827
|
import {
|
|
74496
74828
|
closeSync as closeSync11,
|
|
74497
|
-
existsSync as
|
|
74498
|
-
mkdirSync as
|
|
74829
|
+
existsSync as existsSync58,
|
|
74830
|
+
mkdirSync as mkdirSync32,
|
|
74499
74831
|
openSync as openSync11,
|
|
74500
74832
|
readdirSync as readdirSync21,
|
|
74501
|
-
readFileSync as
|
|
74833
|
+
readFileSync as readFileSync52,
|
|
74502
74834
|
renameSync as renameSync12,
|
|
74503
74835
|
statSync as statSync25,
|
|
74504
74836
|
unlinkSync as unlinkSync11,
|
|
74505
|
-
writeFileSync as
|
|
74837
|
+
writeFileSync as writeFileSync27,
|
|
74506
74838
|
writeSync as writeSync7
|
|
74507
74839
|
} from "node:fs";
|
|
74508
|
-
import { join as
|
|
74840
|
+
import { join as join59 } from "node:path";
|
|
74509
74841
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
74510
74842
|
import { execSync as execSync5 } from "node:child_process";
|
|
74511
74843
|
|
|
@@ -74903,12 +75235,12 @@ function redactedMarker(ruleId) {
|
|
|
74903
75235
|
var ISSUES_FILE = "issues.jsonl";
|
|
74904
75236
|
var ISSUES_LOCK = "issues.lock";
|
|
74905
75237
|
function readAll(stateDir) {
|
|
74906
|
-
const path4 =
|
|
74907
|
-
if (!
|
|
75238
|
+
const path4 = join59(stateDir, ISSUES_FILE);
|
|
75239
|
+
if (!existsSync58(path4))
|
|
74908
75240
|
return [];
|
|
74909
75241
|
let raw;
|
|
74910
75242
|
try {
|
|
74911
|
-
raw =
|
|
75243
|
+
raw = readFileSync52(path4, "utf-8");
|
|
74912
75244
|
} catch {
|
|
74913
75245
|
return [];
|
|
74914
75246
|
}
|
|
@@ -74981,7 +75313,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
74981
75313
|
});
|
|
74982
75314
|
}
|
|
74983
75315
|
function resolve36(stateDir, fingerprint, nowFn = Date.now) {
|
|
74984
|
-
if (!
|
|
75316
|
+
if (!existsSync58(join59(stateDir, ISSUES_FILE)))
|
|
74985
75317
|
return 0;
|
|
74986
75318
|
return withLock(stateDir, () => {
|
|
74987
75319
|
const all = readAll(stateDir);
|
|
@@ -74999,7 +75331,7 @@ function resolve36(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
74999
75331
|
});
|
|
75000
75332
|
}
|
|
75001
75333
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
75002
|
-
if (!
|
|
75334
|
+
if (!existsSync58(join59(stateDir, ISSUES_FILE)))
|
|
75003
75335
|
return 0;
|
|
75004
75336
|
return withLock(stateDir, () => {
|
|
75005
75337
|
const all = readAll(stateDir);
|
|
@@ -75017,7 +75349,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
75017
75349
|
});
|
|
75018
75350
|
}
|
|
75019
75351
|
function prune(stateDir, opts = {}) {
|
|
75020
|
-
if (!
|
|
75352
|
+
if (!existsSync58(join59(stateDir, ISSUES_FILE)))
|
|
75021
75353
|
return 0;
|
|
75022
75354
|
return withLock(stateDir, () => {
|
|
75023
75355
|
const all = readAll(stateDir);
|
|
@@ -75047,16 +75379,16 @@ function prune(stateDir, opts = {}) {
|
|
|
75047
75379
|
});
|
|
75048
75380
|
}
|
|
75049
75381
|
function ensureDir(stateDir) {
|
|
75050
|
-
|
|
75382
|
+
mkdirSync32(stateDir, { recursive: true });
|
|
75051
75383
|
}
|
|
75052
75384
|
function writeAll(stateDir, events) {
|
|
75053
|
-
const path4 =
|
|
75385
|
+
const path4 = join59(stateDir, ISSUES_FILE);
|
|
75054
75386
|
sweepOrphanTmpFiles(stateDir);
|
|
75055
75387
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
75056
75388
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
75057
75389
|
`) + `
|
|
75058
75390
|
`;
|
|
75059
|
-
|
|
75391
|
+
writeFileSync27(tmp, body, "utf-8");
|
|
75060
75392
|
renameSync12(tmp, path4);
|
|
75061
75393
|
}
|
|
75062
75394
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
@@ -75072,7 +75404,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
75072
75404
|
for (const entry of entries) {
|
|
75073
75405
|
if (!entry.startsWith(TMP_PREFIX))
|
|
75074
75406
|
continue;
|
|
75075
|
-
const tmpPath =
|
|
75407
|
+
const tmpPath = join59(stateDir, entry);
|
|
75076
75408
|
try {
|
|
75077
75409
|
const stat = statSync25(tmpPath);
|
|
75078
75410
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -75084,7 +75416,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
75084
75416
|
var LOCK_RETRY_MS = 25;
|
|
75085
75417
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
75086
75418
|
function withLock(stateDir, fn) {
|
|
75087
|
-
const lockPath =
|
|
75419
|
+
const lockPath = join59(stateDir, ISSUES_LOCK);
|
|
75088
75420
|
const startedAt = Date.now();
|
|
75089
75421
|
let fd = null;
|
|
75090
75422
|
while (fd === null) {
|
|
@@ -75119,7 +75451,7 @@ function withLock(stateDir, fn) {
|
|
|
75119
75451
|
function tryStealStaleLock(lockPath) {
|
|
75120
75452
|
let pidStr;
|
|
75121
75453
|
try {
|
|
75122
|
-
pidStr =
|
|
75454
|
+
pidStr = readFileSync52(lockPath, "utf-8").trim();
|
|
75123
75455
|
} catch {
|
|
75124
75456
|
return true;
|
|
75125
75457
|
}
|
|
@@ -75367,21 +75699,21 @@ function relTime(deltaMs) {
|
|
|
75367
75699
|
|
|
75368
75700
|
// src/cli/deps.ts
|
|
75369
75701
|
init_source();
|
|
75370
|
-
import { existsSync as
|
|
75371
|
-
import { homedir as
|
|
75372
|
-
import { join as
|
|
75702
|
+
import { existsSync as existsSync61 } from "node:fs";
|
|
75703
|
+
import { homedir as homedir37 } from "node:os";
|
|
75704
|
+
import { join as join62, resolve as resolve37 } from "node:path";
|
|
75373
75705
|
|
|
75374
75706
|
// src/deps/python.ts
|
|
75375
75707
|
import { createHash as createHash11 } from "node:crypto";
|
|
75376
75708
|
import {
|
|
75377
|
-
existsSync as
|
|
75378
|
-
mkdirSync as
|
|
75379
|
-
readFileSync as
|
|
75709
|
+
existsSync as existsSync59,
|
|
75710
|
+
mkdirSync as mkdirSync33,
|
|
75711
|
+
readFileSync as readFileSync53,
|
|
75380
75712
|
rmSync as rmSync13,
|
|
75381
|
-
writeFileSync as
|
|
75713
|
+
writeFileSync as writeFileSync28
|
|
75382
75714
|
} from "node:fs";
|
|
75383
|
-
import { dirname as dirname15, join as
|
|
75384
|
-
import { homedir as
|
|
75715
|
+
import { dirname as dirname15, join as join60 } from "node:path";
|
|
75716
|
+
import { homedir as homedir35 } from "node:os";
|
|
75385
75717
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
75386
75718
|
|
|
75387
75719
|
class PythonEnvError extends Error {
|
|
@@ -75393,26 +75725,26 @@ class PythonEnvError extends Error {
|
|
|
75393
75725
|
}
|
|
75394
75726
|
}
|
|
75395
75727
|
function defaultPythonCacheRoot() {
|
|
75396
|
-
return
|
|
75728
|
+
return join60(homedir35(), ".switchroom", "deps", "python");
|
|
75397
75729
|
}
|
|
75398
75730
|
function hashFile(path4) {
|
|
75399
|
-
return createHash11("sha256").update(
|
|
75731
|
+
return createHash11("sha256").update(readFileSync53(path4)).digest("hex");
|
|
75400
75732
|
}
|
|
75401
75733
|
function ensurePythonEnv(opts) {
|
|
75402
75734
|
const { skillName, requirementsPath, force = false } = opts;
|
|
75403
75735
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
75404
75736
|
const hostPython = opts.pythonBin ?? "python3";
|
|
75405
|
-
if (!
|
|
75737
|
+
if (!existsSync59(requirementsPath)) {
|
|
75406
75738
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
75407
75739
|
}
|
|
75408
|
-
const venvDir =
|
|
75409
|
-
const stampPath =
|
|
75410
|
-
const binDir =
|
|
75411
|
-
const pythonBin =
|
|
75412
|
-
const pipBin =
|
|
75740
|
+
const venvDir = join60(cacheRoot, skillName);
|
|
75741
|
+
const stampPath = join60(venvDir, ".requirements.sha256");
|
|
75742
|
+
const binDir = join60(venvDir, "bin");
|
|
75743
|
+
const pythonBin = join60(binDir, "python");
|
|
75744
|
+
const pipBin = join60(binDir, "pip");
|
|
75413
75745
|
const targetHash = hashFile(requirementsPath);
|
|
75414
|
-
if (!force &&
|
|
75415
|
-
const existingHash =
|
|
75746
|
+
if (!force && existsSync59(stampPath) && existsSync59(pythonBin)) {
|
|
75747
|
+
const existingHash = readFileSync53(stampPath, "utf8").trim();
|
|
75416
75748
|
if (existingHash === targetHash) {
|
|
75417
75749
|
return {
|
|
75418
75750
|
skillName,
|
|
@@ -75424,10 +75756,10 @@ function ensurePythonEnv(opts) {
|
|
|
75424
75756
|
};
|
|
75425
75757
|
}
|
|
75426
75758
|
}
|
|
75427
|
-
if (
|
|
75759
|
+
if (existsSync59(venvDir)) {
|
|
75428
75760
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
75429
75761
|
}
|
|
75430
|
-
|
|
75762
|
+
mkdirSync33(dirname15(venvDir), { recursive: true });
|
|
75431
75763
|
try {
|
|
75432
75764
|
execFileSync18(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
75433
75765
|
} catch (err) {
|
|
@@ -75446,7 +75778,7 @@ function ensurePythonEnv(opts) {
|
|
|
75446
75778
|
const e = err;
|
|
75447
75779
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
75448
75780
|
}
|
|
75449
|
-
|
|
75781
|
+
writeFileSync28(stampPath, targetHash + `
|
|
75450
75782
|
`);
|
|
75451
75783
|
return {
|
|
75452
75784
|
skillName,
|
|
@@ -75462,14 +75794,14 @@ function ensurePythonEnv(opts) {
|
|
|
75462
75794
|
import { createHash as createHash12 } from "node:crypto";
|
|
75463
75795
|
import {
|
|
75464
75796
|
copyFileSync as copyFileSync9,
|
|
75465
|
-
existsSync as
|
|
75466
|
-
mkdirSync as
|
|
75467
|
-
readFileSync as
|
|
75797
|
+
existsSync as existsSync60,
|
|
75798
|
+
mkdirSync as mkdirSync34,
|
|
75799
|
+
readFileSync as readFileSync54,
|
|
75468
75800
|
rmSync as rmSync14,
|
|
75469
|
-
writeFileSync as
|
|
75801
|
+
writeFileSync as writeFileSync29
|
|
75470
75802
|
} from "node:fs";
|
|
75471
|
-
import { dirname as dirname16, join as
|
|
75472
|
-
import { homedir as
|
|
75803
|
+
import { dirname as dirname16, join as join61 } from "node:path";
|
|
75804
|
+
import { homedir as homedir36 } from "node:os";
|
|
75473
75805
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
75474
75806
|
|
|
75475
75807
|
class NodeEnvError extends Error {
|
|
@@ -75492,23 +75824,23 @@ var LOCKFILES_FOR = {
|
|
|
75492
75824
|
npm: ["package-lock.json"]
|
|
75493
75825
|
};
|
|
75494
75826
|
function defaultNodeCacheRoot() {
|
|
75495
|
-
return
|
|
75827
|
+
return join61(homedir36(), ".switchroom", "deps", "node");
|
|
75496
75828
|
}
|
|
75497
75829
|
function hashDepInputs(packageJsonPath) {
|
|
75498
75830
|
const sourceDir = dirname16(packageJsonPath);
|
|
75499
75831
|
const hasher = createHash12("sha256");
|
|
75500
75832
|
hasher.update(`package.json
|
|
75501
75833
|
`);
|
|
75502
|
-
hasher.update(
|
|
75834
|
+
hasher.update(readFileSync54(packageJsonPath));
|
|
75503
75835
|
for (const lockName of ALL_LOCKFILES) {
|
|
75504
|
-
const lockPath =
|
|
75505
|
-
if (
|
|
75836
|
+
const lockPath = join61(sourceDir, lockName);
|
|
75837
|
+
if (existsSync60(lockPath)) {
|
|
75506
75838
|
hasher.update(`
|
|
75507
75839
|
`);
|
|
75508
75840
|
hasher.update(lockName);
|
|
75509
75841
|
hasher.update(`
|
|
75510
75842
|
`);
|
|
75511
|
-
hasher.update(
|
|
75843
|
+
hasher.update(readFileSync54(lockPath));
|
|
75512
75844
|
}
|
|
75513
75845
|
}
|
|
75514
75846
|
return hasher.digest("hex");
|
|
@@ -75517,17 +75849,17 @@ function ensureNodeEnv(opts) {
|
|
|
75517
75849
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
75518
75850
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
75519
75851
|
const installer = opts.installer ?? "bun";
|
|
75520
|
-
if (!
|
|
75852
|
+
if (!existsSync60(packageJsonPath)) {
|
|
75521
75853
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
75522
75854
|
}
|
|
75523
75855
|
const sourceDir = dirname16(packageJsonPath);
|
|
75524
|
-
const envDir =
|
|
75525
|
-
const stampPath =
|
|
75526
|
-
const nodeModulesDir =
|
|
75527
|
-
const binDir =
|
|
75856
|
+
const envDir = join61(cacheRoot, skillName);
|
|
75857
|
+
const stampPath = join61(envDir, ".package.sha256");
|
|
75858
|
+
const nodeModulesDir = join61(envDir, "node_modules");
|
|
75859
|
+
const binDir = join61(nodeModulesDir, ".bin");
|
|
75528
75860
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
75529
|
-
if (!force &&
|
|
75530
|
-
const existingHash =
|
|
75861
|
+
if (!force && existsSync60(stampPath) && existsSync60(nodeModulesDir)) {
|
|
75862
|
+
const existingHash = readFileSync54(stampPath, "utf8").trim();
|
|
75531
75863
|
if (existingHash === targetHash) {
|
|
75532
75864
|
return {
|
|
75533
75865
|
skillName,
|
|
@@ -75538,16 +75870,16 @@ function ensureNodeEnv(opts) {
|
|
|
75538
75870
|
};
|
|
75539
75871
|
}
|
|
75540
75872
|
}
|
|
75541
|
-
if (
|
|
75873
|
+
if (existsSync60(envDir)) {
|
|
75542
75874
|
rmSync14(envDir, { recursive: true, force: true });
|
|
75543
75875
|
}
|
|
75544
|
-
|
|
75545
|
-
copyFileSync9(packageJsonPath,
|
|
75876
|
+
mkdirSync34(envDir, { recursive: true });
|
|
75877
|
+
copyFileSync9(packageJsonPath, join61(envDir, "package.json"));
|
|
75546
75878
|
let copiedLockfile = false;
|
|
75547
75879
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
75548
|
-
const lockPath =
|
|
75549
|
-
if (
|
|
75550
|
-
copyFileSync9(lockPath,
|
|
75880
|
+
const lockPath = join61(sourceDir, lockName);
|
|
75881
|
+
if (existsSync60(lockPath)) {
|
|
75882
|
+
copyFileSync9(lockPath, join61(envDir, lockName));
|
|
75551
75883
|
copiedLockfile = true;
|
|
75552
75884
|
}
|
|
75553
75885
|
}
|
|
@@ -75563,7 +75895,7 @@ function ensureNodeEnv(opts) {
|
|
|
75563
75895
|
const e = err;
|
|
75564
75896
|
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
75565
75897
|
}
|
|
75566
|
-
|
|
75898
|
+
writeFileSync29(stampPath, targetHash + `
|
|
75567
75899
|
`);
|
|
75568
75900
|
return {
|
|
75569
75901
|
skillName,
|
|
@@ -75576,28 +75908,28 @@ function ensureNodeEnv(opts) {
|
|
|
75576
75908
|
|
|
75577
75909
|
// src/cli/deps.ts
|
|
75578
75910
|
function builtinSkillsRoot() {
|
|
75579
|
-
return resolve37(
|
|
75911
|
+
return resolve37(homedir37(), ".switchroom/skills/_bundled");
|
|
75580
75912
|
}
|
|
75581
75913
|
function registerDepsCommand(program3) {
|
|
75582
75914
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
75583
75915
|
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) => {
|
|
75584
75916
|
const skillsRoot = builtinSkillsRoot();
|
|
75585
|
-
if (!
|
|
75917
|
+
if (!existsSync61(skillsRoot)) {
|
|
75586
75918
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
75587
75919
|
process.exit(1);
|
|
75588
75920
|
}
|
|
75589
|
-
const skillDir =
|
|
75590
|
-
if (!
|
|
75921
|
+
const skillDir = join62(skillsRoot, skill);
|
|
75922
|
+
if (!existsSync61(skillDir)) {
|
|
75591
75923
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
75592
75924
|
process.exit(1);
|
|
75593
75925
|
}
|
|
75594
|
-
const requirementsPath =
|
|
75595
|
-
const packageJsonPath =
|
|
75596
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
75597
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
75926
|
+
const requirementsPath = join62(skillDir, "requirements.txt");
|
|
75927
|
+
const packageJsonPath = join62(skillDir, "package.json");
|
|
75928
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync61(requirementsPath));
|
|
75929
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync61(packageJsonPath));
|
|
75598
75930
|
let did = 0;
|
|
75599
75931
|
if (wantPython) {
|
|
75600
|
-
if (!
|
|
75932
|
+
if (!existsSync61(requirementsPath)) {
|
|
75601
75933
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
75602
75934
|
process.exit(1);
|
|
75603
75935
|
}
|
|
@@ -75621,7 +75953,7 @@ function registerDepsCommand(program3) {
|
|
|
75621
75953
|
}
|
|
75622
75954
|
}
|
|
75623
75955
|
if (wantNode) {
|
|
75624
|
-
if (!
|
|
75956
|
+
if (!existsSync61(packageJsonPath)) {
|
|
75625
75957
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
75626
75958
|
process.exit(1);
|
|
75627
75959
|
}
|
|
@@ -75654,9 +75986,9 @@ function registerDepsCommand(program3) {
|
|
|
75654
75986
|
// src/cli/workspace.ts
|
|
75655
75987
|
init_helpers();
|
|
75656
75988
|
init_loader();
|
|
75657
|
-
import { existsSync as
|
|
75989
|
+
import { existsSync as existsSync62 } from "node:fs";
|
|
75658
75990
|
import { resolve as resolve38, sep as sep3 } from "node:path";
|
|
75659
|
-
import { spawnSync as
|
|
75991
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
75660
75992
|
|
|
75661
75993
|
// src/agents/workspace.ts
|
|
75662
75994
|
import { readFile, stat } from "node:fs/promises";
|
|
@@ -75864,6 +76196,7 @@ function buildBootstrapPromptWarning(params) {
|
|
|
75864
76196
|
|
|
75865
76197
|
// src/agents/bootstrap-types.ts
|
|
75866
76198
|
var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
76199
|
+
var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
|
|
75867
76200
|
var DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
75868
76201
|
var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
75869
76202
|
var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
|
|
@@ -75877,6 +76210,7 @@ var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
|
|
|
75877
76210
|
var DEFAULT_MEMORY_SUBDIR = "memory";
|
|
75878
76211
|
var STABLE_BOOTSTRAP_FILENAMES = [
|
|
75879
76212
|
DEFAULT_AGENTS_FILENAME,
|
|
76213
|
+
DEFAULT_SOUL_DEFAULT_FILENAME,
|
|
75880
76214
|
DEFAULT_SOUL_FILENAME,
|
|
75881
76215
|
DEFAULT_IDENTITY_FILENAME,
|
|
75882
76216
|
DEFAULT_USER_FILENAME,
|
|
@@ -76369,7 +76703,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
76369
76703
|
process.exit(1);
|
|
76370
76704
|
}
|
|
76371
76705
|
const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
|
|
76372
|
-
const child =
|
|
76706
|
+
const child = spawnSync10(editor, [target], { stdio: "inherit" });
|
|
76373
76707
|
if (child.status !== 0 && child.status !== null) {
|
|
76374
76708
|
process.exit(child.status);
|
|
76375
76709
|
}
|
|
@@ -76431,12 +76765,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
76431
76765
|
if (!dir)
|
|
76432
76766
|
return;
|
|
76433
76767
|
const gitDir = resolve38(dir, ".git");
|
|
76434
|
-
if (!
|
|
76768
|
+
if (!existsSync62(gitDir)) {
|
|
76435
76769
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
76436
76770
|
`);
|
|
76437
76771
|
return;
|
|
76438
76772
|
}
|
|
76439
|
-
const statusResult =
|
|
76773
|
+
const statusResult = spawnSync10("git", ["status", "--short"], {
|
|
76440
76774
|
cwd: dir,
|
|
76441
76775
|
encoding: "utf-8"
|
|
76442
76776
|
});
|
|
@@ -76451,7 +76785,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
76451
76785
|
return;
|
|
76452
76786
|
}
|
|
76453
76787
|
const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
|
|
76454
|
-
const addResult =
|
|
76788
|
+
const addResult = spawnSync10("git", ["add", "-A"], {
|
|
76455
76789
|
cwd: dir,
|
|
76456
76790
|
encoding: "utf-8"
|
|
76457
76791
|
});
|
|
@@ -76460,7 +76794,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
76460
76794
|
`);
|
|
76461
76795
|
process.exit(1);
|
|
76462
76796
|
}
|
|
76463
|
-
const commitResult =
|
|
76797
|
+
const commitResult = spawnSync10("git", ["commit", "-m", message], {
|
|
76464
76798
|
cwd: dir,
|
|
76465
76799
|
encoding: "utf-8"
|
|
76466
76800
|
});
|
|
@@ -76469,7 +76803,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
76469
76803
|
`);
|
|
76470
76804
|
process.exit(1);
|
|
76471
76805
|
}
|
|
76472
|
-
const shaResult =
|
|
76806
|
+
const shaResult = spawnSync10("git", ["rev-parse", "--short", "HEAD"], {
|
|
76473
76807
|
cwd: dir,
|
|
76474
76808
|
encoding: "utf-8"
|
|
76475
76809
|
});
|
|
@@ -76485,12 +76819,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
76485
76819
|
if (!dir)
|
|
76486
76820
|
return;
|
|
76487
76821
|
const gitDir = resolve38(dir, ".git");
|
|
76488
|
-
if (!
|
|
76822
|
+
if (!existsSync62(gitDir)) {
|
|
76489
76823
|
process.stdout.write(`Workspace is not a git repository.
|
|
76490
76824
|
`);
|
|
76491
76825
|
return;
|
|
76492
76826
|
}
|
|
76493
|
-
const child =
|
|
76827
|
+
const child = spawnSync10("git", ["status", "--short"], {
|
|
76494
76828
|
cwd: dir,
|
|
76495
76829
|
stdio: "inherit"
|
|
76496
76830
|
});
|
|
@@ -76510,7 +76844,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
76510
76844
|
const agentsDir = resolveAgentsDir(config);
|
|
76511
76845
|
const agentDir = resolve38(agentsDir, agentName);
|
|
76512
76846
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
76513
|
-
if (!
|
|
76847
|
+
if (!existsSync62(dir)) {
|
|
76514
76848
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
76515
76849
|
`);
|
|
76516
76850
|
return;
|
|
@@ -76546,8 +76880,8 @@ function safeParseInt(value, fallback) {
|
|
|
76546
76880
|
init_helpers();
|
|
76547
76881
|
init_loader();
|
|
76548
76882
|
init_merge();
|
|
76549
|
-
import { copyFileSync as copyFileSync10, existsSync as
|
|
76550
|
-
import { join as
|
|
76883
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync63, readFileSync as readFileSync55, writeFileSync as writeFileSync30 } from "node:fs";
|
|
76884
|
+
import { join as join63, resolve as resolve39 } from "node:path";
|
|
76551
76885
|
init_schema();
|
|
76552
76886
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
76553
76887
|
const config = getConfig(program3);
|
|
@@ -76562,7 +76896,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
76562
76896
|
const agentsDir = resolveAgentsDir(config);
|
|
76563
76897
|
const agentDir = resolve39(agentsDir, agentName);
|
|
76564
76898
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
76565
|
-
if (!
|
|
76899
|
+
if (!existsSync63(workspaceDir)) {
|
|
76566
76900
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
76567
76901
|
process.exit(1);
|
|
76568
76902
|
}
|
|
@@ -76571,7 +76905,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
76571
76905
|
profileName,
|
|
76572
76906
|
profilePath,
|
|
76573
76907
|
workspaceDir,
|
|
76574
|
-
soulPath:
|
|
76908
|
+
soulPath: join63(workspaceDir, "SOUL.md"),
|
|
76575
76909
|
soul: merged.soul
|
|
76576
76910
|
};
|
|
76577
76911
|
}
|
|
@@ -76588,11 +76922,11 @@ function registerSoulCommand(program3) {
|
|
|
76588
76922
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
76589
76923
|
if (!t)
|
|
76590
76924
|
return;
|
|
76591
|
-
if (!
|
|
76925
|
+
if (!existsSync63(t.soulPath)) {
|
|
76592
76926
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
76593
76927
|
process.exit(1);
|
|
76594
76928
|
}
|
|
76595
|
-
process.stdout.write(
|
|
76929
|
+
process.stdout.write(readFileSync55(t.soulPath, "utf-8"));
|
|
76596
76930
|
}));
|
|
76597
76931
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
76598
76932
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -76603,7 +76937,7 @@ function registerSoulCommand(program3) {
|
|
|
76603
76937
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
76604
76938
|
process.exit(1);
|
|
76605
76939
|
}
|
|
76606
|
-
const exists =
|
|
76940
|
+
const exists = existsSync63(t.soulPath);
|
|
76607
76941
|
if (exists && !opts.yes) {
|
|
76608
76942
|
if (!isInteractive()) {
|
|
76609
76943
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -76618,12 +76952,12 @@ function registerSoulCommand(program3) {
|
|
|
76618
76952
|
let backupPath;
|
|
76619
76953
|
if (exists) {
|
|
76620
76954
|
backupPath = `${t.soulPath}.bak`;
|
|
76621
|
-
if (
|
|
76955
|
+
if (existsSync63(backupPath)) {
|
|
76622
76956
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
76623
76957
|
}
|
|
76624
76958
|
copyFileSync10(t.soulPath, backupPath);
|
|
76625
76959
|
}
|
|
76626
|
-
|
|
76960
|
+
writeFileSync30(t.soulPath, content, "utf-8");
|
|
76627
76961
|
if (backupPath) {
|
|
76628
76962
|
console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
|
|
76629
76963
|
` + ` Previous version saved to ${backupPath}`);
|
|
@@ -76637,8 +76971,8 @@ function registerSoulCommand(program3) {
|
|
|
76637
76971
|
// src/cli/debug.ts
|
|
76638
76972
|
init_helpers();
|
|
76639
76973
|
init_loader();
|
|
76640
|
-
import { existsSync as
|
|
76641
|
-
import { resolve as resolve40, join as
|
|
76974
|
+
import { existsSync as existsSync64, readFileSync as readFileSync56, readdirSync as readdirSync22, statSync as statSync26 } from "node:fs";
|
|
76975
|
+
import { resolve as resolve40, join as join64 } from "node:path";
|
|
76642
76976
|
import { createHash as createHash13 } from "node:crypto";
|
|
76643
76977
|
init_merge();
|
|
76644
76978
|
init_hindsight();
|
|
@@ -76652,8 +76986,8 @@ function sha256(content) {
|
|
|
76652
76986
|
return createHash13("sha256").update(content).digest("hex").slice(0, 16);
|
|
76653
76987
|
}
|
|
76654
76988
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
76655
|
-
const projectsDir =
|
|
76656
|
-
if (!
|
|
76989
|
+
const projectsDir = join64(claudeConfigDir, "projects");
|
|
76990
|
+
if (!existsSync64(projectsDir))
|
|
76657
76991
|
return;
|
|
76658
76992
|
try {
|
|
76659
76993
|
const entries = readdirSync22(projectsDir, { withFileTypes: true });
|
|
@@ -76661,9 +76995,9 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
76661
76995
|
for (const entry of entries) {
|
|
76662
76996
|
if (!entry.isDirectory())
|
|
76663
76997
|
continue;
|
|
76664
|
-
const projectPath =
|
|
76665
|
-
const transcriptPath =
|
|
76666
|
-
if (!
|
|
76998
|
+
const projectPath = join64(projectsDir, entry.name);
|
|
76999
|
+
const transcriptPath = join64(projectPath, "transcript.jsonl");
|
|
77000
|
+
if (!existsSync64(transcriptPath))
|
|
76667
77001
|
continue;
|
|
76668
77002
|
const stat3 = statSync26(transcriptPath);
|
|
76669
77003
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
@@ -76677,7 +77011,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
76677
77011
|
}
|
|
76678
77012
|
function extractLatestUserMessage(transcriptPath) {
|
|
76679
77013
|
try {
|
|
76680
|
-
const content =
|
|
77014
|
+
const content = readFileSync56(transcriptPath, "utf-8");
|
|
76681
77015
|
const lines = content.trim().split(`
|
|
76682
77016
|
`).filter(Boolean);
|
|
76683
77017
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -76726,16 +77060,16 @@ function registerDebugCommand(program3) {
|
|
|
76726
77060
|
}
|
|
76727
77061
|
const agentsDir = resolveAgentsDir(config);
|
|
76728
77062
|
const agentDir = resolve40(agentsDir, agentName);
|
|
76729
|
-
if (!
|
|
77063
|
+
if (!existsSync64(agentDir)) {
|
|
76730
77064
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
76731
77065
|
process.exit(1);
|
|
76732
77066
|
}
|
|
76733
77067
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
76734
|
-
const claudeConfigDir =
|
|
76735
|
-
const claudeMdPath =
|
|
76736
|
-
const soulMdPath =
|
|
76737
|
-
const workspaceSoulMdPath =
|
|
76738
|
-
const handoffPath =
|
|
77068
|
+
const claudeConfigDir = join64(agentDir, ".claude");
|
|
77069
|
+
const claudeMdPath = join64(agentDir, "CLAUDE.md");
|
|
77070
|
+
const soulMdPath = join64(agentDir, "SOUL.md");
|
|
77071
|
+
const workspaceSoulMdPath = join64(workspaceDir, "SOUL.md");
|
|
77072
|
+
const handoffPath = join64(agentDir, ".handoff.md");
|
|
76739
77073
|
const lastN = parseInt(opts.last, 10);
|
|
76740
77074
|
if (isNaN(lastN) || lastN < 1) {
|
|
76741
77075
|
console.error("--last must be a positive integer");
|
|
@@ -76781,7 +77115,7 @@ function registerDebugCommand(program3) {
|
|
|
76781
77115
|
}
|
|
76782
77116
|
console.log(`=== Append System Prompt (per-session) ===
|
|
76783
77117
|
`);
|
|
76784
|
-
const handoffContent =
|
|
77118
|
+
const handoffContent = existsSync64(handoffPath) ? readFileSync56(handoffPath, "utf-8") : "";
|
|
76785
77119
|
if (handoffContent.trim().length > 0) {
|
|
76786
77120
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
76787
77121
|
console.log(handoffContent);
|
|
@@ -76792,7 +77126,7 @@ function registerDebugCommand(program3) {
|
|
|
76792
77126
|
}
|
|
76793
77127
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
76794
77128
|
`);
|
|
76795
|
-
const claudeMdContent =
|
|
77129
|
+
const claudeMdContent = existsSync64(claudeMdPath) ? readFileSync56(claudeMdPath, "utf-8") : "";
|
|
76796
77130
|
if (claudeMdContent.trim().length > 0) {
|
|
76797
77131
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
76798
77132
|
console.log(claudeMdContent);
|
|
@@ -76803,7 +77137,7 @@ function registerDebugCommand(program3) {
|
|
|
76803
77137
|
}
|
|
76804
77138
|
console.log(`=== Persona (SOUL.md) ===
|
|
76805
77139
|
`);
|
|
76806
|
-
const soulMdContent =
|
|
77140
|
+
const soulMdContent = existsSync64(soulMdPath) ? readFileSync56(soulMdPath, "utf-8") : existsSync64(workspaceSoulMdPath) ? readFileSync56(workspaceSoulMdPath, "utf-8") : "";
|
|
76807
77141
|
if (soulMdContent.trim().length > 0) {
|
|
76808
77142
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
76809
77143
|
console.log(soulMdContent);
|
|
@@ -76884,44 +77218,44 @@ init_source();
|
|
|
76884
77218
|
|
|
76885
77219
|
// src/worktree/claim.ts
|
|
76886
77220
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
76887
|
-
import { closeSync as closeSync12, mkdirSync as
|
|
76888
|
-
import { join as
|
|
76889
|
-
import { homedir as
|
|
77221
|
+
import { closeSync as closeSync12, mkdirSync as mkdirSync36, openSync as openSync12, existsSync as existsSync66, unlinkSync as unlinkSync13 } from "node:fs";
|
|
77222
|
+
import { join as join66, resolve as resolve42 } from "node:path";
|
|
77223
|
+
import { homedir as homedir39 } from "node:os";
|
|
76890
77224
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
76891
77225
|
|
|
76892
77226
|
// src/worktree/registry.ts
|
|
76893
77227
|
import {
|
|
76894
|
-
mkdirSync as
|
|
76895
|
-
writeFileSync as
|
|
76896
|
-
readFileSync as
|
|
77228
|
+
mkdirSync as mkdirSync35,
|
|
77229
|
+
writeFileSync as writeFileSync31,
|
|
77230
|
+
readFileSync as readFileSync57,
|
|
76897
77231
|
readdirSync as readdirSync23,
|
|
76898
77232
|
unlinkSync as unlinkSync12,
|
|
76899
|
-
existsSync as
|
|
77233
|
+
existsSync as existsSync65,
|
|
76900
77234
|
renameSync as renameSync13
|
|
76901
77235
|
} from "node:fs";
|
|
76902
|
-
import { join as
|
|
76903
|
-
import { homedir as
|
|
77236
|
+
import { join as join65, resolve as resolve41 } from "node:path";
|
|
77237
|
+
import { homedir as homedir38 } from "node:os";
|
|
76904
77238
|
function registryDir() {
|
|
76905
|
-
return resolve41(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
77239
|
+
return resolve41(process.env.SWITCHROOM_WORKTREE_DIR ?? join65(homedir38(), ".switchroom", "worktrees"));
|
|
76906
77240
|
}
|
|
76907
77241
|
function recordPath(id) {
|
|
76908
|
-
return
|
|
77242
|
+
return join65(registryDir(), `${id}.json`);
|
|
76909
77243
|
}
|
|
76910
77244
|
function ensureDir2() {
|
|
76911
|
-
|
|
77245
|
+
mkdirSync35(registryDir(), { recursive: true });
|
|
76912
77246
|
}
|
|
76913
77247
|
function writeRecord(record2) {
|
|
76914
77248
|
ensureDir2();
|
|
76915
77249
|
const target = recordPath(record2.id);
|
|
76916
77250
|
const tmp = `${target}.tmp${process.pid}`;
|
|
76917
|
-
|
|
77251
|
+
writeFileSync31(tmp, JSON.stringify(record2, null, 2) + `
|
|
76918
77252
|
`, { mode: 384 });
|
|
76919
77253
|
renameSync13(tmp, target);
|
|
76920
77254
|
}
|
|
76921
77255
|
function readRecord(id) {
|
|
76922
77256
|
const path7 = recordPath(id);
|
|
76923
77257
|
try {
|
|
76924
|
-
const raw =
|
|
77258
|
+
const raw = readFileSync57(path7, "utf8");
|
|
76925
77259
|
return JSON.parse(raw);
|
|
76926
77260
|
} catch {
|
|
76927
77261
|
return null;
|
|
@@ -76954,9 +77288,9 @@ function countByRepo(repoPath) {
|
|
|
76954
77288
|
// src/worktree/claim.ts
|
|
76955
77289
|
function acquireRepoLock(repoPath) {
|
|
76956
77290
|
const lockDir = registryDir();
|
|
76957
|
-
|
|
77291
|
+
mkdirSync36(lockDir, { recursive: true });
|
|
76958
77292
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
76959
|
-
const lockPath =
|
|
77293
|
+
const lockPath = join66(lockDir, `.lock-${lockName}`);
|
|
76960
77294
|
const deadline = Date.now() + 5000;
|
|
76961
77295
|
let fd = null;
|
|
76962
77296
|
while (fd === null) {
|
|
@@ -76983,7 +77317,7 @@ function acquireRepoLock(repoPath) {
|
|
|
76983
77317
|
}
|
|
76984
77318
|
var DEFAULT_CONCURRENCY = 5;
|
|
76985
77319
|
function worktreesBaseDir() {
|
|
76986
|
-
return resolve42(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
77320
|
+
return resolve42(process.env.SWITCHROOM_WORKTREE_BASE ?? join66(homedir39(), ".switchroom", "worktree-checkouts"));
|
|
76987
77321
|
}
|
|
76988
77322
|
function shortId() {
|
|
76989
77323
|
return randomBytes13(4).toString("hex");
|
|
@@ -77005,12 +77339,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
77005
77339
|
}
|
|
77006
77340
|
function expandHome(p) {
|
|
77007
77341
|
if (p.startsWith("~/"))
|
|
77008
|
-
return
|
|
77342
|
+
return join66(homedir39(), p.slice(2));
|
|
77009
77343
|
return p;
|
|
77010
77344
|
}
|
|
77011
77345
|
async function claimWorktree(input, codeRepos) {
|
|
77012
77346
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
77013
|
-
if (!
|
|
77347
|
+
if (!existsSync66(repoPath)) {
|
|
77014
77348
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
77015
77349
|
}
|
|
77016
77350
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -77032,8 +77366,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
77032
77366
|
const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
|
|
77033
77367
|
branch = `task/${taskSuffix}-${id}`;
|
|
77034
77368
|
const baseDir = worktreesBaseDir();
|
|
77035
|
-
|
|
77036
|
-
worktreePath =
|
|
77369
|
+
mkdirSync36(baseDir, { recursive: true });
|
|
77370
|
+
worktreePath = join66(baseDir, `${id}-${taskSuffix}`);
|
|
77037
77371
|
const now = new Date().toISOString();
|
|
77038
77372
|
const record2 = {
|
|
77039
77373
|
id,
|
|
@@ -77064,7 +77398,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
77064
77398
|
|
|
77065
77399
|
// src/worktree/release.ts
|
|
77066
77400
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
77067
|
-
import { existsSync as
|
|
77401
|
+
import { existsSync as existsSync67 } from "node:fs";
|
|
77068
77402
|
function releaseWorktree(input) {
|
|
77069
77403
|
const { id } = input;
|
|
77070
77404
|
const record2 = readRecord(id);
|
|
@@ -77072,7 +77406,7 @@ function releaseWorktree(input) {
|
|
|
77072
77406
|
return { released: true };
|
|
77073
77407
|
}
|
|
77074
77408
|
let gitSuccess = true;
|
|
77075
|
-
if (
|
|
77409
|
+
if (existsSync67(record2.path)) {
|
|
77076
77410
|
try {
|
|
77077
77411
|
execFileSync21("git", ["worktree", "remove", "--force", record2.path], {
|
|
77078
77412
|
cwd: record2.repo,
|
|
@@ -77111,7 +77445,7 @@ function listWorktrees() {
|
|
|
77111
77445
|
|
|
77112
77446
|
// src/worktree/reaper.ts
|
|
77113
77447
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
77114
|
-
import { existsSync as
|
|
77448
|
+
import { existsSync as existsSync68 } from "node:fs";
|
|
77115
77449
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
77116
77450
|
function isPathInUse(path7) {
|
|
77117
77451
|
try {
|
|
@@ -77138,7 +77472,7 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
77138
77472
|
function reapRecord(record2) {
|
|
77139
77473
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
77140
77474
|
let warning = null;
|
|
77141
|
-
if (
|
|
77475
|
+
if (existsSync68(path7)) {
|
|
77142
77476
|
if (hasUncommittedChanges(repo, path7)) {
|
|
77143
77477
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
77144
77478
|
}
|
|
@@ -77159,7 +77493,7 @@ function runReaper(nowMs) {
|
|
|
77159
77493
|
const warnings = [];
|
|
77160
77494
|
for (const record2 of records) {
|
|
77161
77495
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
77162
|
-
const worktreeExists =
|
|
77496
|
+
const worktreeExists = existsSync68(record2.path);
|
|
77163
77497
|
if (!worktreeExists) {
|
|
77164
77498
|
deleteRecord(record2.id);
|
|
77165
77499
|
reaped.push(record2.id);
|
|
@@ -77283,12 +77617,12 @@ init_drive();
|
|
|
77283
77617
|
init_scaffold_integration();
|
|
77284
77618
|
import {
|
|
77285
77619
|
chmodSync as chmodSync9,
|
|
77286
|
-
mkdirSync as
|
|
77620
|
+
mkdirSync as mkdirSync37,
|
|
77287
77621
|
readdirSync as readdirSync24,
|
|
77288
77622
|
rmSync as rmSync15,
|
|
77289
|
-
writeFileSync as
|
|
77623
|
+
writeFileSync as writeFileSync32
|
|
77290
77624
|
} from "node:fs";
|
|
77291
|
-
import { join as
|
|
77625
|
+
import { join as join67 } from "node:path";
|
|
77292
77626
|
function encodeCredentialsFilename(email) {
|
|
77293
77627
|
const SAFE = new Set([
|
|
77294
77628
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -77478,17 +77812,17 @@ function resolveCredentialsDir(env2) {
|
|
|
77478
77812
|
if (explicit && explicit.length > 0)
|
|
77479
77813
|
return explicit;
|
|
77480
77814
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
77481
|
-
return
|
|
77815
|
+
return join67(stateBase, "google-workspace-mcp", "credentials");
|
|
77482
77816
|
}
|
|
77483
77817
|
function writeSeedFile(dir, email, seed) {
|
|
77484
|
-
|
|
77818
|
+
mkdirSync37(dir, { recursive: true, mode: 448 });
|
|
77485
77819
|
chmodSync9(dir, 448);
|
|
77486
77820
|
for (const name of readdirSync24(dir)) {
|
|
77487
|
-
rmSync15(
|
|
77821
|
+
rmSync15(join67(dir, name), { force: true, recursive: true });
|
|
77488
77822
|
}
|
|
77489
77823
|
const filename = encodeCredentialsFilename(email);
|
|
77490
|
-
const filePath =
|
|
77491
|
-
|
|
77824
|
+
const filePath = join67(dir, filename);
|
|
77825
|
+
writeFileSync32(filePath, JSON.stringify(seed), { mode: 384 });
|
|
77492
77826
|
chmodSync9(filePath, 384);
|
|
77493
77827
|
return filePath;
|
|
77494
77828
|
}
|
|
@@ -77646,8 +77980,8 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
77646
77980
|
// src/cli/m365-mcp-launcher.ts
|
|
77647
77981
|
init_scaffold_integration();
|
|
77648
77982
|
import { spawn as spawn6 } from "node:child_process";
|
|
77649
|
-
import { writeFileSync as
|
|
77650
|
-
import { dirname as dirname17, join as
|
|
77983
|
+
import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync38 } from "node:fs";
|
|
77984
|
+
import { dirname as dirname17, join as join68 } from "node:path";
|
|
77651
77985
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
77652
77986
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
77653
77987
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -77672,14 +78006,14 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
|
|
|
77672
78006
|
function writeRefreshHeartbeat(agentName, data) {
|
|
77673
78007
|
const path7 = heartbeatPath(agentName);
|
|
77674
78008
|
try {
|
|
77675
|
-
|
|
77676
|
-
|
|
78009
|
+
mkdirSync38(dirname17(path7), { recursive: true });
|
|
78010
|
+
writeFileSync33(path7, JSON.stringify(data, null, 2), { mode: 420 });
|
|
77677
78011
|
} catch {}
|
|
77678
78012
|
}
|
|
77679
78013
|
function heartbeatPath(agentName) {
|
|
77680
78014
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
77681
78015
|
if (override) {
|
|
77682
|
-
return
|
|
78016
|
+
return join68(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
77683
78017
|
}
|
|
77684
78018
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
77685
78019
|
}
|
|
@@ -77864,7 +78198,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
77864
78198
|
// src/cli/notion-mcp-launcher.ts
|
|
77865
78199
|
init_scaffold_integration();
|
|
77866
78200
|
import { spawn as spawn7 } from "node:child_process";
|
|
77867
|
-
import { existsSync as
|
|
78201
|
+
import { existsSync as existsSync69, mkdirSync as mkdirSync39, writeFileSync as writeFileSync34 } from "node:fs";
|
|
77868
78202
|
import { dirname as dirname18 } from "node:path";
|
|
77869
78203
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
77870
78204
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -77876,9 +78210,9 @@ function buildNotionMcpArgs(opts) {
|
|
|
77876
78210
|
function defaultWriteHeartbeat(path7, contents) {
|
|
77877
78211
|
try {
|
|
77878
78212
|
const dir = dirname18(path7);
|
|
77879
|
-
if (!
|
|
77880
|
-
|
|
77881
|
-
|
|
78213
|
+
if (!existsSync69(dir))
|
|
78214
|
+
mkdirSync39(dir, { recursive: true });
|
|
78215
|
+
writeFileSync34(path7, contents);
|
|
77882
78216
|
} catch {}
|
|
77883
78217
|
}
|
|
77884
78218
|
async function runNotionMcpLauncher(opts, runtime) {
|
|
@@ -78235,7 +78569,7 @@ async function fetchToken(vaultKey) {
|
|
|
78235
78569
|
|
|
78236
78570
|
// src/cli/apply.ts
|
|
78237
78571
|
init_source();
|
|
78238
|
-
import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as
|
|
78572
|
+
import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync73, mkdirSync as mkdirSync41, readFileSync as readFileSync59, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync36 } from "node:fs";
|
|
78239
78573
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
78240
78574
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
78241
78575
|
import readline from "node:readline";
|
|
@@ -78624,16 +78958,16 @@ agents:
|
|
|
78624
78958
|
|
|
78625
78959
|
// src/cli/apply.ts
|
|
78626
78960
|
init_resolver();
|
|
78627
|
-
import { dirname as dirname21, join as
|
|
78628
|
-
import { homedir as
|
|
78961
|
+
import { dirname as dirname21, join as join72, resolve as resolve44 } from "node:path";
|
|
78962
|
+
import { homedir as homedir41 } from "node:os";
|
|
78629
78963
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
78630
78964
|
init_vault();
|
|
78631
78965
|
init_loader();
|
|
78632
78966
|
init_loader();
|
|
78633
78967
|
|
|
78634
78968
|
// src/cli/update-prompt-hook.ts
|
|
78635
|
-
import { existsSync as
|
|
78636
|
-
import { join as
|
|
78969
|
+
import { existsSync as existsSync70, readFileSync as readFileSync58, writeFileSync as writeFileSync35, chmodSync as chmodSync10, mkdirSync as mkdirSync40 } from "node:fs";
|
|
78970
|
+
import { join as join69 } from "node:path";
|
|
78637
78971
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
78638
78972
|
function updatePromptHookScript() {
|
|
78639
78973
|
return `#!/bin/bash
|
|
@@ -78699,14 +79033,14 @@ exit 0
|
|
|
78699
79033
|
`;
|
|
78700
79034
|
}
|
|
78701
79035
|
function installUpdatePromptHook(agentDir) {
|
|
78702
|
-
const hooksDir =
|
|
78703
|
-
|
|
78704
|
-
const scriptPath =
|
|
79036
|
+
const hooksDir = join69(agentDir, ".claude", "hooks");
|
|
79037
|
+
mkdirSync40(hooksDir, { recursive: true });
|
|
79038
|
+
const scriptPath = join69(hooksDir, HOOK_FILENAME);
|
|
78705
79039
|
const desired = updatePromptHookScript();
|
|
78706
79040
|
let installed = false;
|
|
78707
|
-
const existing =
|
|
79041
|
+
const existing = existsSync70(scriptPath) ? readFileSync58(scriptPath, "utf-8") : "";
|
|
78708
79042
|
if (existing !== desired) {
|
|
78709
|
-
|
|
79043
|
+
writeFileSync35(scriptPath, desired, { mode: 493 });
|
|
78710
79044
|
chmodSync10(scriptPath, 493);
|
|
78711
79045
|
installed = true;
|
|
78712
79046
|
} else {
|
|
@@ -78714,11 +79048,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
78714
79048
|
chmodSync10(scriptPath, 493);
|
|
78715
79049
|
} catch {}
|
|
78716
79050
|
}
|
|
78717
|
-
const settingsPath =
|
|
78718
|
-
if (!
|
|
79051
|
+
const settingsPath = join69(agentDir, ".claude", "settings.json");
|
|
79052
|
+
if (!existsSync70(settingsPath)) {
|
|
78719
79053
|
return { scriptPath, settingsPath, installed };
|
|
78720
79054
|
}
|
|
78721
|
-
const raw =
|
|
79055
|
+
const raw = readFileSync58(settingsPath, "utf-8");
|
|
78722
79056
|
let parsed;
|
|
78723
79057
|
try {
|
|
78724
79058
|
parsed = JSON.parse(raw);
|
|
@@ -78751,7 +79085,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
78751
79085
|
});
|
|
78752
79086
|
hooks.UserPromptSubmit = list2;
|
|
78753
79087
|
parsed.hooks = hooks;
|
|
78754
|
-
|
|
79088
|
+
writeFileSync35(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
78755
79089
|
`, { mode: 384 });
|
|
78756
79090
|
installed = true;
|
|
78757
79091
|
}
|
|
@@ -78834,13 +79168,13 @@ function detectInstallType() {
|
|
|
78834
79168
|
// src/cli/operator-uid.ts
|
|
78835
79169
|
import {
|
|
78836
79170
|
chownSync as chownSync3,
|
|
78837
|
-
existsSync as
|
|
79171
|
+
existsSync as existsSync72,
|
|
78838
79172
|
lstatSync as lstatSync7,
|
|
78839
79173
|
readdirSync as readdirSync25,
|
|
78840
79174
|
realpathSync as realpathSync6,
|
|
78841
79175
|
statSync as statSync27
|
|
78842
79176
|
} from "node:fs";
|
|
78843
|
-
import { join as
|
|
79177
|
+
import { join as join71 } from "node:path";
|
|
78844
79178
|
function resolveOperatorUid() {
|
|
78845
79179
|
const sudoUid = process.env.SUDO_UID;
|
|
78846
79180
|
if (sudoUid !== undefined) {
|
|
@@ -78856,19 +79190,19 @@ function resolveOperatorUid() {
|
|
|
78856
79190
|
return;
|
|
78857
79191
|
}
|
|
78858
79192
|
function operatorOwnedPaths(home2) {
|
|
78859
|
-
const root =
|
|
79193
|
+
const root = join71(home2, ".switchroom");
|
|
78860
79194
|
return [
|
|
78861
|
-
|
|
78862
|
-
|
|
78863
|
-
|
|
78864
|
-
|
|
78865
|
-
|
|
78866
|
-
|
|
79195
|
+
join71(root, "vault"),
|
|
79196
|
+
join71(root, "vault-auto-unlock"),
|
|
79197
|
+
join71(root, "vault-audit.log"),
|
|
79198
|
+
join71(root, "host-control-audit.log"),
|
|
79199
|
+
join71(root, "accounts"),
|
|
79200
|
+
join71(root, "compose")
|
|
78867
79201
|
];
|
|
78868
79202
|
}
|
|
78869
79203
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
78870
79204
|
const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
|
|
78871
|
-
const exists = deps.exists ?? ((p) =>
|
|
79205
|
+
const exists = deps.exists ?? ((p) => existsSync72(p));
|
|
78872
79206
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
78873
79207
|
try {
|
|
78874
79208
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -78912,7 +79246,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
78912
79246
|
} catch {}
|
|
78913
79247
|
if (isDir(target)) {
|
|
78914
79248
|
for (const entry of readdir2(target)) {
|
|
78915
|
-
visit(
|
|
79249
|
+
visit(join71(target, entry));
|
|
78916
79250
|
}
|
|
78917
79251
|
}
|
|
78918
79252
|
};
|
|
@@ -78926,17 +79260,17 @@ var EMBEDDED_EXAMPLES = {
|
|
|
78926
79260
|
switchroom: switchroom_default,
|
|
78927
79261
|
minimal: minimal_default
|
|
78928
79262
|
};
|
|
78929
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
79263
|
+
var DEFAULT_COMPOSE_PATH2 = join72(homedir41(), ".switchroom", "compose", "docker-compose.yml");
|
|
78930
79264
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
78931
79265
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
78932
79266
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
78933
79267
|
if (isCustomPath && ctx.customVaultPath) {
|
|
78934
79268
|
return dirname21(ctx.customVaultPath);
|
|
78935
79269
|
}
|
|
78936
|
-
return
|
|
79270
|
+
return join72(homeDir, ".switchroom", "vault");
|
|
78937
79271
|
}
|
|
78938
79272
|
function inspectVaultBindMountDir(vaultDir) {
|
|
78939
|
-
if (!
|
|
79273
|
+
if (!existsSync73(vaultDir))
|
|
78940
79274
|
return { kind: "missing" };
|
|
78941
79275
|
const entries = readdirSync26(vaultDir);
|
|
78942
79276
|
const unknown = [];
|
|
@@ -78962,63 +79296,63 @@ function hasVaultRefs(value) {
|
|
|
78962
79296
|
return false;
|
|
78963
79297
|
}
|
|
78964
79298
|
async function ensureHostMountSources(config) {
|
|
78965
|
-
const home2 =
|
|
79299
|
+
const home2 = homedir41();
|
|
78966
79300
|
const dirs = [
|
|
78967
|
-
|
|
78968
|
-
|
|
78969
|
-
|
|
78970
|
-
|
|
78971
|
-
|
|
79301
|
+
join72(home2, ".switchroom", "approvals"),
|
|
79302
|
+
join72(home2, ".switchroom", "scheduler"),
|
|
79303
|
+
join72(home2, ".switchroom", "logs"),
|
|
79304
|
+
join72(home2, ".switchroom", "compose"),
|
|
79305
|
+
join72(home2, ".switchroom", "broker-operator")
|
|
78972
79306
|
];
|
|
78973
79307
|
for (const name of Object.keys(config.agents)) {
|
|
78974
|
-
dirs.push(
|
|
78975
|
-
dirs.push(
|
|
78976
|
-
dirs.push(
|
|
78977
|
-
dirs.push(
|
|
78978
|
-
if (
|
|
78979
|
-
dirs.push(
|
|
79308
|
+
dirs.push(join72(home2, ".switchroom", "agents", name));
|
|
79309
|
+
dirs.push(join72(home2, ".switchroom", "logs", name));
|
|
79310
|
+
dirs.push(join72(home2, ".claude", "projects", name));
|
|
79311
|
+
dirs.push(join72(home2, ".switchroom", "audit", name));
|
|
79312
|
+
if (existsSync73(join72(home2, ".switchroom-config"))) {
|
|
79313
|
+
dirs.push(join72(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
78980
79314
|
}
|
|
78981
79315
|
}
|
|
78982
79316
|
for (const dir of dirs) {
|
|
78983
79317
|
await mkdir(dir, { recursive: true });
|
|
78984
79318
|
}
|
|
78985
|
-
const autoUnlockPath =
|
|
78986
|
-
if (!
|
|
78987
|
-
|
|
79319
|
+
const autoUnlockPath = join72(home2, ".switchroom", "vault-auto-unlock");
|
|
79320
|
+
if (!existsSync73(autoUnlockPath)) {
|
|
79321
|
+
writeFileSync36(autoUnlockPath, "", { mode: 384 });
|
|
78988
79322
|
}
|
|
78989
|
-
const auditLogPath =
|
|
78990
|
-
if (!
|
|
78991
|
-
|
|
79323
|
+
const auditLogPath = join72(home2, ".switchroom", "vault-audit.log");
|
|
79324
|
+
if (!existsSync73(auditLogPath)) {
|
|
79325
|
+
writeFileSync36(auditLogPath, "", { mode: 420 });
|
|
78992
79326
|
}
|
|
78993
|
-
const grantsDbPath =
|
|
78994
|
-
if (!
|
|
78995
|
-
|
|
79327
|
+
const grantsDbPath = join72(home2, ".switchroom", "vault-grants.db");
|
|
79328
|
+
if (!existsSync73(grantsDbPath)) {
|
|
79329
|
+
writeFileSync36(grantsDbPath, "", { mode: 384 });
|
|
78996
79330
|
}
|
|
78997
|
-
const hostdAuditLogPath =
|
|
78998
|
-
if (!
|
|
78999
|
-
|
|
79331
|
+
const hostdAuditLogPath = join72(home2, ".switchroom", "host-control-audit.log");
|
|
79332
|
+
if (!existsSync73(hostdAuditLogPath)) {
|
|
79333
|
+
writeFileSync36(hostdAuditLogPath, "", { mode: 420 });
|
|
79000
79334
|
}
|
|
79001
79335
|
for (const name of Object.keys(config.agents)) {
|
|
79002
|
-
const tokenPath =
|
|
79003
|
-
if (!
|
|
79004
|
-
|
|
79336
|
+
const tokenPath = join72(home2, ".switchroom", "agents", name, ".vault-token");
|
|
79337
|
+
if (!existsSync73(tokenPath)) {
|
|
79338
|
+
writeFileSync36(tokenPath, "", { mode: 384 });
|
|
79005
79339
|
}
|
|
79006
79340
|
try {
|
|
79007
79341
|
const uid = allocateAgentUid(name);
|
|
79008
79342
|
chownSync4(tokenPath, uid, uid);
|
|
79009
79343
|
} catch {}
|
|
79010
79344
|
}
|
|
79011
|
-
const fleetDir =
|
|
79345
|
+
const fleetDir = join72(home2, ".switchroom", "fleet");
|
|
79012
79346
|
await mkdir(fleetDir, { recursive: true });
|
|
79013
|
-
const invariantsPath =
|
|
79347
|
+
const invariantsPath = join72(fleetDir, "switchroom-invariants.md");
|
|
79014
79348
|
const invariantsCanonical = renderFleetInvariants();
|
|
79015
|
-
const invariantsCurrent =
|
|
79349
|
+
const invariantsCurrent = existsSync73(invariantsPath) ? readFileSync59(invariantsPath, "utf-8") : null;
|
|
79016
79350
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
79017
|
-
|
|
79351
|
+
writeFileSync36(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
79018
79352
|
}
|
|
79019
|
-
const fleetClaudePath =
|
|
79020
|
-
if (!
|
|
79021
|
-
|
|
79353
|
+
const fleetClaudePath = join72(fleetDir, "CLAUDE.md");
|
|
79354
|
+
if (!existsSync73(fleetClaudePath)) {
|
|
79355
|
+
writeFileSync36(fleetClaudePath, [
|
|
79022
79356
|
"# Switchroom fleet defaults",
|
|
79023
79357
|
"",
|
|
79024
79358
|
"Operator-owned fleet brain. Every agent reads this via",
|
|
@@ -79049,7 +79383,7 @@ ${out.trim()}`;
|
|
|
79049
79383
|
}
|
|
79050
79384
|
function runApplyPreflight(config, opts = {}) {
|
|
79051
79385
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
79052
|
-
if (hasVaultRefs(config) && !
|
|
79386
|
+
if (hasVaultRefs(config) && !existsSync73(vaultPath)) {
|
|
79053
79387
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
79054
79388
|
}
|
|
79055
79389
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -79060,7 +79394,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
79060
79394
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
79061
79395
|
}
|
|
79062
79396
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
79063
|
-
if (!
|
|
79397
|
+
if (!existsSync73(vaultPath))
|
|
79064
79398
|
return;
|
|
79065
79399
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
79066
79400
|
if (!passphrase)
|
|
@@ -79099,18 +79433,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
79099
79433
|
`));
|
|
79100
79434
|
}
|
|
79101
79435
|
}
|
|
79102
|
-
function writeInstallTypeCache(homeDir =
|
|
79436
|
+
function writeInstallTypeCache(homeDir = homedir41()) {
|
|
79103
79437
|
const ctx = detectInstallType();
|
|
79104
|
-
const dir =
|
|
79105
|
-
const out =
|
|
79438
|
+
const dir = join72(homeDir, ".switchroom");
|
|
79439
|
+
const out = join72(dir, "install-type.json");
|
|
79106
79440
|
const tmp = `${out}.tmp`;
|
|
79107
|
-
|
|
79441
|
+
mkdirSync41(dir, { recursive: true });
|
|
79108
79442
|
const payload = {
|
|
79109
79443
|
install_type: ctx.install_type,
|
|
79110
79444
|
detected_at: new Date().toISOString(),
|
|
79111
79445
|
source_paths: ctx.source_paths
|
|
79112
79446
|
};
|
|
79113
|
-
|
|
79447
|
+
writeFileSync36(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
79114
79448
|
renameSync14(tmp, out);
|
|
79115
79449
|
return out;
|
|
79116
79450
|
}
|
|
@@ -79151,14 +79485,14 @@ Applying switchroom config...
|
|
|
79151
79485
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
79152
79486
|
`));
|
|
79153
79487
|
try {
|
|
79154
|
-
installUpdatePromptHook(
|
|
79488
|
+
installUpdatePromptHook(join72(agentsDir, name));
|
|
79155
79489
|
} catch (hookErr) {
|
|
79156
79490
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
79157
79491
|
`));
|
|
79158
79492
|
}
|
|
79159
79493
|
try {
|
|
79160
79494
|
const uid = allocateAgentUid(name);
|
|
79161
|
-
alignAgentUid(name,
|
|
79495
|
+
alignAgentUid(name, join72(agentsDir, name), uid, {
|
|
79162
79496
|
confirm: !options.nonInteractive,
|
|
79163
79497
|
writeOut
|
|
79164
79498
|
});
|
|
@@ -79195,7 +79529,7 @@ Applying switchroom config...
|
|
|
79195
79529
|
for (const name of agentNames) {
|
|
79196
79530
|
try {
|
|
79197
79531
|
const uid = allocateAgentUid(name);
|
|
79198
|
-
alignAgentUid(name,
|
|
79532
|
+
alignAgentUid(name, join72(agentsDir, name), uid, {
|
|
79199
79533
|
confirm: !options.nonInteractive,
|
|
79200
79534
|
writeOut
|
|
79201
79535
|
});
|
|
@@ -79209,7 +79543,7 @@ Applying switchroom config...
|
|
|
79209
79543
|
}
|
|
79210
79544
|
const vaultPathConfigured = config.vault?.path;
|
|
79211
79545
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
79212
|
-
const migrationResult = migrateVaultLayout(
|
|
79546
|
+
const migrationResult = migrateVaultLayout(homedir41(), {
|
|
79213
79547
|
customVaultPath
|
|
79214
79548
|
});
|
|
79215
79549
|
switch (migrationResult.kind) {
|
|
@@ -79235,7 +79569,7 @@ Applying switchroom config...
|
|
|
79235
79569
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
79236
79570
|
process.exit(4);
|
|
79237
79571
|
}
|
|
79238
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
79572
|
+
const postMigrationInspect = inspectVaultLayout(homedir41());
|
|
79239
79573
|
const acceptable = [
|
|
79240
79574
|
"no-vault",
|
|
79241
79575
|
"already-migrated",
|
|
@@ -79250,7 +79584,7 @@ Applying switchroom config...
|
|
|
79250
79584
|
`));
|
|
79251
79585
|
process.exit(5);
|
|
79252
79586
|
}
|
|
79253
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
79587
|
+
const vaultDir = resolveVaultBindMountDir(homedir41(), {
|
|
79254
79588
|
migrationKind: migrationResult.kind,
|
|
79255
79589
|
customVaultPath
|
|
79256
79590
|
});
|
|
@@ -79280,7 +79614,7 @@ Applying switchroom config...
|
|
|
79280
79614
|
imageTag: composeImageTag,
|
|
79281
79615
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
79282
79616
|
buildContext: options.buildContext,
|
|
79283
|
-
homeDir:
|
|
79617
|
+
homeDir: homedir41(),
|
|
79284
79618
|
switchroomConfigPath,
|
|
79285
79619
|
operatorUid
|
|
79286
79620
|
});
|
|
@@ -79300,7 +79634,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
79300
79634
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
79301
79635
|
`));
|
|
79302
79636
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
79303
|
-
const restored = restoreOperatorOwnership(
|
|
79637
|
+
const restored = restoreOperatorOwnership(homedir41(), operatorUid);
|
|
79304
79638
|
if (restored.length > 0) {
|
|
79305
79639
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
79306
79640
|
`));
|
|
@@ -79348,18 +79682,18 @@ function copyExampleConfig2(name) {
|
|
|
79348
79682
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
79349
79683
|
}
|
|
79350
79684
|
const dest = resolve44(process.cwd(), "switchroom.yaml");
|
|
79351
|
-
if (
|
|
79685
|
+
if (existsSync73(dest)) {
|
|
79352
79686
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
79353
79687
|
return;
|
|
79354
79688
|
}
|
|
79355
79689
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
79356
79690
|
if (embedded !== undefined) {
|
|
79357
|
-
|
|
79691
|
+
writeFileSync36(dest, embedded, { encoding: "utf8" });
|
|
79358
79692
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
79359
79693
|
return;
|
|
79360
79694
|
}
|
|
79361
79695
|
const exampleFile = resolve44(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
79362
|
-
if (!
|
|
79696
|
+
if (!existsSync73(exampleFile)) {
|
|
79363
79697
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
79364
79698
|
}
|
|
79365
79699
|
copyFileSync11(exampleFile, dest);
|
|
@@ -79370,8 +79704,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
79370
79704
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
79371
79705
|
const unwritable = [];
|
|
79372
79706
|
for (const name of targets) {
|
|
79373
|
-
const startSh =
|
|
79374
|
-
if (!
|
|
79707
|
+
const startSh = join72(agentsDir, name, "start.sh");
|
|
79708
|
+
if (!existsSync73(startSh))
|
|
79375
79709
|
continue;
|
|
79376
79710
|
try {
|
|
79377
79711
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -79549,9 +79883,9 @@ function runRedactStdin() {
|
|
|
79549
79883
|
}
|
|
79550
79884
|
|
|
79551
79885
|
// src/cli/status-ask.ts
|
|
79552
|
-
import { readFileSync as
|
|
79553
|
-
import { join as
|
|
79554
|
-
import { homedir as
|
|
79886
|
+
import { readFileSync as readFileSync60, existsSync as existsSync74, readdirSync as readdirSync27 } from "node:fs";
|
|
79887
|
+
import { join as join73 } from "node:path";
|
|
79888
|
+
import { homedir as homedir42 } from "node:os";
|
|
79555
79889
|
|
|
79556
79890
|
// src/status-ask/report.ts
|
|
79557
79891
|
function parseJsonl(content) {
|
|
@@ -79825,7 +80159,7 @@ function runReport(opts) {
|
|
|
79825
80159
|
for (const src of sources) {
|
|
79826
80160
|
let content;
|
|
79827
80161
|
try {
|
|
79828
|
-
content =
|
|
80162
|
+
content = readFileSync60(src.path, "utf-8");
|
|
79829
80163
|
} catch (err) {
|
|
79830
80164
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
79831
80165
|
`);
|
|
@@ -79872,7 +80206,7 @@ function runReport(opts) {
|
|
|
79872
80206
|
function resolveSources(explicitPath) {
|
|
79873
80207
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
79874
80208
|
const trimmed = explicitPath.trim();
|
|
79875
|
-
if (!
|
|
80209
|
+
if (!existsSync74(trimmed)) {
|
|
79876
80210
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
79877
80211
|
`);
|
|
79878
80212
|
process.exit(1);
|
|
@@ -79886,9 +80220,9 @@ function resolveSources(explicitPath) {
|
|
|
79886
80220
|
const config = loadConfig();
|
|
79887
80221
|
agentsDir = resolveAgentsDir(config);
|
|
79888
80222
|
} catch {
|
|
79889
|
-
agentsDir =
|
|
80223
|
+
agentsDir = join73(homedir42(), ".switchroom", "agents");
|
|
79890
80224
|
}
|
|
79891
|
-
if (!
|
|
80225
|
+
if (!existsSync74(agentsDir))
|
|
79892
80226
|
return [];
|
|
79893
80227
|
const sources = [];
|
|
79894
80228
|
let entries;
|
|
@@ -79898,8 +80232,8 @@ function resolveSources(explicitPath) {
|
|
|
79898
80232
|
return [];
|
|
79899
80233
|
}
|
|
79900
80234
|
for (const name of entries) {
|
|
79901
|
-
const path8 =
|
|
79902
|
-
if (
|
|
80235
|
+
const path8 = join73(agentsDir, name, "runtime-metrics.jsonl");
|
|
80236
|
+
if (existsSync74(path8)) {
|
|
79903
80237
|
sources.push({ path: path8, agent: name });
|
|
79904
80238
|
}
|
|
79905
80239
|
}
|
|
@@ -79920,17 +80254,17 @@ function inferAgentFromPath(p) {
|
|
|
79920
80254
|
|
|
79921
80255
|
// src/cli/agent-config.ts
|
|
79922
80256
|
init_helpers();
|
|
79923
|
-
import { join as
|
|
79924
|
-
import { homedir as
|
|
80257
|
+
import { join as join74 } from "node:path";
|
|
80258
|
+
import { homedir as homedir43 } from "node:os";
|
|
79925
80259
|
import {
|
|
79926
|
-
existsSync as
|
|
79927
|
-
mkdirSync as
|
|
80260
|
+
existsSync as existsSync75,
|
|
80261
|
+
mkdirSync as mkdirSync42,
|
|
79928
80262
|
appendFileSync as appendFileSync4,
|
|
79929
|
-
readFileSync as
|
|
80263
|
+
readFileSync as readFileSync61
|
|
79930
80264
|
} from "node:fs";
|
|
79931
|
-
var AUDIT_ROOT =
|
|
80265
|
+
var AUDIT_ROOT = join74(homedir43(), ".switchroom", "audit");
|
|
79932
80266
|
function auditPathFor(agent) {
|
|
79933
|
-
return
|
|
80267
|
+
return join74(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
79934
80268
|
}
|
|
79935
80269
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
79936
80270
|
const row = {
|
|
@@ -79944,8 +80278,8 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
79944
80278
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
79945
80279
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
79946
80280
|
try {
|
|
79947
|
-
if (!
|
|
79948
|
-
|
|
80281
|
+
if (!existsSync75(dir)) {
|
|
80282
|
+
mkdirSync42(dir, { recursive: true });
|
|
79949
80283
|
}
|
|
79950
80284
|
appendFileSync4(path8, JSON.stringify(row) + `
|
|
79951
80285
|
`, { flag: "a" });
|
|
@@ -79956,7 +80290,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
79956
80290
|
return true;
|
|
79957
80291
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
79958
80292
|
try {
|
|
79959
|
-
if (
|
|
80293
|
+
if (existsSync75(probe2))
|
|
79960
80294
|
return true;
|
|
79961
80295
|
} catch {}
|
|
79962
80296
|
return false;
|
|
@@ -80017,11 +80351,11 @@ function getAgentSlice(config, agent) {
|
|
|
80017
80351
|
}
|
|
80018
80352
|
function readAuditTail(agent, limit, opts = {}) {
|
|
80019
80353
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
80020
|
-
if (!
|
|
80354
|
+
if (!existsSync75(path8))
|
|
80021
80355
|
return [];
|
|
80022
80356
|
let raw;
|
|
80023
80357
|
try {
|
|
80024
|
-
raw =
|
|
80358
|
+
raw = readFileSync61(path8, "utf-8");
|
|
80025
80359
|
} catch {
|
|
80026
80360
|
return [];
|
|
80027
80361
|
}
|
|
@@ -80171,51 +80505,51 @@ function registerAgentConfigCommands(program3) {
|
|
|
80171
80505
|
}
|
|
80172
80506
|
|
|
80173
80507
|
// src/cli/agent-config-write.ts
|
|
80174
|
-
var
|
|
80508
|
+
var import_yaml19 = __toESM(require_dist(), 1);
|
|
80175
80509
|
|
|
80176
80510
|
// src/config/overlay-writer.ts
|
|
80177
80511
|
init_paths();
|
|
80178
80512
|
import {
|
|
80179
80513
|
closeSync as closeSync13,
|
|
80180
|
-
existsSync as
|
|
80514
|
+
existsSync as existsSync76,
|
|
80181
80515
|
fsyncSync as fsyncSync6,
|
|
80182
|
-
mkdirSync as
|
|
80516
|
+
mkdirSync as mkdirSync43,
|
|
80183
80517
|
openSync as openSync13,
|
|
80184
80518
|
readdirSync as readdirSync28,
|
|
80185
|
-
readFileSync as
|
|
80519
|
+
readFileSync as readFileSync62,
|
|
80186
80520
|
renameSync as renameSync15,
|
|
80187
80521
|
statSync as statSync28,
|
|
80188
80522
|
unlinkSync as unlinkSync14,
|
|
80189
80523
|
writeSync as writeSync8
|
|
80190
80524
|
} from "node:fs";
|
|
80191
|
-
import { join as
|
|
80525
|
+
import { join as join75, resolve as resolve45 } from "node:path";
|
|
80192
80526
|
var STAGING_SUBDIR = ".staging";
|
|
80193
80527
|
function overlayPathsFor(agent, opts = {}) {
|
|
80194
80528
|
const base = opts.root ? resolve45(opts.root, agent) : resolve45(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
80195
|
-
const scheduleDir =
|
|
80196
|
-
const scheduleStagingDir =
|
|
80197
|
-
const skillsDir =
|
|
80198
|
-
const skillsStagingDir =
|
|
80529
|
+
const scheduleDir = join75(base, "schedule.d");
|
|
80530
|
+
const scheduleStagingDir = join75(scheduleDir, STAGING_SUBDIR);
|
|
80531
|
+
const skillsDir = join75(base, "skills.d");
|
|
80532
|
+
const skillsStagingDir = join75(skillsDir, STAGING_SUBDIR);
|
|
80199
80533
|
return {
|
|
80200
80534
|
agentRoot: base,
|
|
80201
80535
|
scheduleDir,
|
|
80202
80536
|
scheduleStagingDir,
|
|
80203
80537
|
skillsDir,
|
|
80204
80538
|
skillsStagingDir,
|
|
80205
|
-
lockPath:
|
|
80539
|
+
lockPath: join75(base, ".lock"),
|
|
80206
80540
|
stagingDir: scheduleStagingDir
|
|
80207
80541
|
};
|
|
80208
80542
|
}
|
|
80209
80543
|
function ensureDirs(paths) {
|
|
80210
|
-
|
|
80211
|
-
|
|
80544
|
+
mkdirSync43(paths.scheduleDir, { recursive: true });
|
|
80545
|
+
mkdirSync43(paths.scheduleStagingDir, { recursive: true });
|
|
80212
80546
|
}
|
|
80213
80547
|
function ensureSkillsDirs(paths) {
|
|
80214
|
-
|
|
80215
|
-
|
|
80548
|
+
mkdirSync43(paths.skillsDir, { recursive: true });
|
|
80549
|
+
mkdirSync43(paths.skillsStagingDir, { recursive: true });
|
|
80216
80550
|
}
|
|
80217
80551
|
function withAgentLock(paths, fn) {
|
|
80218
|
-
|
|
80552
|
+
mkdirSync43(paths.agentRoot, { recursive: true });
|
|
80219
80553
|
const start = Date.now();
|
|
80220
80554
|
const TIMEOUT_MS = 5000;
|
|
80221
80555
|
let fd = null;
|
|
@@ -80256,8 +80590,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
80256
80590
|
const paths = overlayPathsFor(agent, opts);
|
|
80257
80591
|
return withAgentLock(paths, () => {
|
|
80258
80592
|
ensureDirs(paths);
|
|
80259
|
-
const stagingPath =
|
|
80260
|
-
const finalPath =
|
|
80593
|
+
const stagingPath = join75(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
80594
|
+
const finalPath = join75(paths.scheduleDir, `${slug}.yaml`);
|
|
80261
80595
|
const fd = openSync13(stagingPath, "w", 384);
|
|
80262
80596
|
try {
|
|
80263
80597
|
writeSync8(fd, yamlText);
|
|
@@ -80273,8 +80607,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
80273
80607
|
const paths = overlayPathsFor(agent, opts);
|
|
80274
80608
|
return withAgentLock(paths, () => {
|
|
80275
80609
|
ensureSkillsDirs(paths);
|
|
80276
|
-
const stagingPath =
|
|
80277
|
-
const finalPath =
|
|
80610
|
+
const stagingPath = join75(paths.skillsStagingDir, `${slug}.yaml`);
|
|
80611
|
+
const finalPath = join75(paths.skillsDir, `${slug}.yaml`);
|
|
80278
80612
|
const fd = openSync13(stagingPath, "w", 384);
|
|
80279
80613
|
try {
|
|
80280
80614
|
writeSync8(fd, yamlText);
|
|
@@ -80289,8 +80623,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
80289
80623
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
80290
80624
|
const paths = overlayPathsFor(agent, opts);
|
|
80291
80625
|
return withAgentLock(paths, () => {
|
|
80292
|
-
const finalPath =
|
|
80293
|
-
if (!
|
|
80626
|
+
const finalPath = join75(paths.skillsDir, `${slug}.yaml`);
|
|
80627
|
+
if (!existsSync76(finalPath))
|
|
80294
80628
|
return false;
|
|
80295
80629
|
unlinkSync14(finalPath);
|
|
80296
80630
|
return true;
|
|
@@ -80298,15 +80632,15 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
80298
80632
|
}
|
|
80299
80633
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
80300
80634
|
const paths = overlayPathsFor(agent, opts);
|
|
80301
|
-
if (!
|
|
80635
|
+
if (!existsSync76(paths.skillsDir))
|
|
80302
80636
|
return [];
|
|
80303
80637
|
const out = [];
|
|
80304
80638
|
for (const name of readdirSync28(paths.skillsDir)) {
|
|
80305
80639
|
if (!/\.ya?ml$/i.test(name))
|
|
80306
80640
|
continue;
|
|
80307
|
-
const full =
|
|
80641
|
+
const full = join75(paths.skillsDir, name);
|
|
80308
80642
|
try {
|
|
80309
|
-
const raw =
|
|
80643
|
+
const raw = readFileSync62(full, "utf-8");
|
|
80310
80644
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
80311
80645
|
out.push({ slug, path: full, raw });
|
|
80312
80646
|
} catch {}
|
|
@@ -80316,8 +80650,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
80316
80650
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
80317
80651
|
const paths = overlayPathsFor(agent, opts);
|
|
80318
80652
|
return withAgentLock(paths, () => {
|
|
80319
|
-
const finalPath =
|
|
80320
|
-
if (!
|
|
80653
|
+
const finalPath = join75(paths.scheduleDir, `${slug}.yaml`);
|
|
80654
|
+
if (!existsSync76(finalPath))
|
|
80321
80655
|
return false;
|
|
80322
80656
|
unlinkSync14(finalPath);
|
|
80323
80657
|
return true;
|
|
@@ -80325,15 +80659,15 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
80325
80659
|
}
|
|
80326
80660
|
function listOverlayEntries(agent, opts = {}) {
|
|
80327
80661
|
const paths = overlayPathsFor(agent, opts);
|
|
80328
|
-
if (!
|
|
80662
|
+
if (!existsSync76(paths.scheduleDir))
|
|
80329
80663
|
return [];
|
|
80330
80664
|
const out = [];
|
|
80331
80665
|
for (const name of readdirSync28(paths.scheduleDir)) {
|
|
80332
80666
|
if (!/\.ya?ml$/i.test(name))
|
|
80333
80667
|
continue;
|
|
80334
|
-
const full =
|
|
80668
|
+
const full = join75(paths.scheduleDir, name);
|
|
80335
80669
|
try {
|
|
80336
|
-
const raw =
|
|
80670
|
+
const raw = readFileSync62(full, "utf-8");
|
|
80337
80671
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
80338
80672
|
out.push({ slug, path: full, raw });
|
|
80339
80673
|
} catch {}
|
|
@@ -80364,7 +80698,7 @@ function filterOverlaySecrets(doc, source) {
|
|
|
80364
80698
|
// src/agents/reconcile-dry-run.ts
|
|
80365
80699
|
init_overlay_schema();
|
|
80366
80700
|
init_schema();
|
|
80367
|
-
var
|
|
80701
|
+
var import_yaml18 = __toESM(require_dist(), 1);
|
|
80368
80702
|
init_lifecycle();
|
|
80369
80703
|
var MIN_CRON_INTERVAL_SECS = 5 * 60;
|
|
80370
80704
|
function violatesMinInterval(cron) {
|
|
@@ -80386,7 +80720,7 @@ function violatesMinInterval(cron) {
|
|
|
80386
80720
|
function dryRunReconcile(input) {
|
|
80387
80721
|
let parsed;
|
|
80388
80722
|
try {
|
|
80389
|
-
parsed =
|
|
80723
|
+
parsed = import_yaml18.parse(input.yamlText);
|
|
80390
80724
|
} catch (err) {
|
|
80391
80725
|
return {
|
|
80392
80726
|
ok: false,
|
|
@@ -80476,27 +80810,27 @@ function reconcileAgentCronOnly(agent) {
|
|
|
80476
80810
|
// src/cli/agent-config-pending.ts
|
|
80477
80811
|
import {
|
|
80478
80812
|
closeSync as closeSync14,
|
|
80479
|
-
existsSync as
|
|
80813
|
+
existsSync as existsSync77,
|
|
80480
80814
|
fsyncSync as fsyncSync7,
|
|
80481
|
-
mkdirSync as
|
|
80815
|
+
mkdirSync as mkdirSync44,
|
|
80482
80816
|
openSync as openSync14,
|
|
80483
80817
|
readdirSync as readdirSync29,
|
|
80484
|
-
readFileSync as
|
|
80818
|
+
readFileSync as readFileSync63,
|
|
80485
80819
|
renameSync as renameSync16,
|
|
80486
80820
|
unlinkSync as unlinkSync15,
|
|
80487
|
-
writeFileSync as
|
|
80821
|
+
writeFileSync as writeFileSync37,
|
|
80488
80822
|
writeSync as writeSync9
|
|
80489
80823
|
} from "node:fs";
|
|
80490
|
-
import { join as
|
|
80824
|
+
import { join as join76 } from "node:path";
|
|
80491
80825
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
80492
80826
|
var STAGE_ID_PREFIX = "cap_";
|
|
80493
80827
|
function pendingDir(agent, opts = {}) {
|
|
80494
80828
|
const paths = overlayPathsFor(agent, opts);
|
|
80495
|
-
return
|
|
80829
|
+
return join76(paths.scheduleDir, ".pending");
|
|
80496
80830
|
}
|
|
80497
80831
|
function ensurePendingDir(agent, opts = {}) {
|
|
80498
80832
|
const dir = pendingDir(agent, opts);
|
|
80499
|
-
|
|
80833
|
+
mkdirSync44(dir, { recursive: true });
|
|
80500
80834
|
return dir;
|
|
80501
80835
|
}
|
|
80502
80836
|
function newStageId() {
|
|
@@ -80505,8 +80839,8 @@ function newStageId() {
|
|
|
80505
80839
|
function stagePendingScheduleEntry(opts) {
|
|
80506
80840
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
80507
80841
|
const stageId = opts.stageId ?? newStageId();
|
|
80508
|
-
const yamlPath =
|
|
80509
|
-
const metaPath =
|
|
80842
|
+
const yamlPath = join76(dir, `${stageId}.yaml`);
|
|
80843
|
+
const metaPath = join76(dir, `${stageId}.meta.json`);
|
|
80510
80844
|
const meta = {
|
|
80511
80845
|
v: 1,
|
|
80512
80846
|
stage_id: stageId,
|
|
@@ -80527,25 +80861,25 @@ function stagePendingScheduleEntry(opts) {
|
|
|
80527
80861
|
}
|
|
80528
80862
|
renameSync16(yamlTmp, yamlPath);
|
|
80529
80863
|
}
|
|
80530
|
-
|
|
80864
|
+
writeFileSync37(metaPath, JSON.stringify(meta, null, 2) + `
|
|
80531
80865
|
`, { mode: 384 });
|
|
80532
80866
|
return { stageId, yamlPath, metaPath };
|
|
80533
80867
|
}
|
|
80534
80868
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
80535
80869
|
const dir = pendingDir(agent, opts);
|
|
80536
|
-
if (!
|
|
80870
|
+
if (!existsSync77(dir))
|
|
80537
80871
|
return [];
|
|
80538
80872
|
const out = [];
|
|
80539
80873
|
for (const name of readdirSync29(dir).sort()) {
|
|
80540
80874
|
if (!name.endsWith(".meta.json"))
|
|
80541
80875
|
continue;
|
|
80542
80876
|
const stageId = name.slice(0, -".meta.json".length);
|
|
80543
|
-
const metaPath =
|
|
80544
|
-
const yamlPath =
|
|
80545
|
-
if (!
|
|
80877
|
+
const metaPath = join76(dir, name);
|
|
80878
|
+
const yamlPath = join76(dir, `${stageId}.yaml`);
|
|
80879
|
+
if (!existsSync77(yamlPath))
|
|
80546
80880
|
continue;
|
|
80547
80881
|
try {
|
|
80548
|
-
const meta = JSON.parse(
|
|
80882
|
+
const meta = JSON.parse(readFileSync63(metaPath, "utf-8"));
|
|
80549
80883
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
80550
80884
|
continue;
|
|
80551
80885
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -80560,8 +80894,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
80560
80894
|
return { committed: false, reason: "not_found" };
|
|
80561
80895
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
80562
80896
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
80563
|
-
const finalPath =
|
|
80564
|
-
if (
|
|
80897
|
+
const finalPath = join76(paths.scheduleDir, `${slug}.yaml`);
|
|
80898
|
+
if (existsSync77(finalPath)) {
|
|
80565
80899
|
return { committed: false, reason: "slug_collision" };
|
|
80566
80900
|
}
|
|
80567
80901
|
renameSync16(match.yamlPath, finalPath);
|
|
@@ -80584,7 +80918,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
80584
80918
|
|
|
80585
80919
|
// src/cli/agent-config-write.ts
|
|
80586
80920
|
init_protocol3();
|
|
80587
|
-
import { existsSync as
|
|
80921
|
+
import { existsSync as existsSync78, readFileSync as readFileSync64 } from "node:fs";
|
|
80588
80922
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
80589
80923
|
var MIN_CRON_INTERVAL_MIN = 5;
|
|
80590
80924
|
function extractCronSmallestGapMin(expr) {
|
|
@@ -80692,7 +81026,7 @@ function scheduleAdd(opts) {
|
|
|
80692
81026
|
]
|
|
80693
81027
|
};
|
|
80694
81028
|
const yamlText = (() => {
|
|
80695
|
-
const body =
|
|
81029
|
+
const body = import_yaml19.stringify(doc);
|
|
80696
81030
|
const header = opts.name ? `# name: ${opts.name}
|
|
80697
81031
|
` : "";
|
|
80698
81032
|
return header + body;
|
|
@@ -80802,7 +81136,7 @@ function scheduleAddOrStage(opts) {
|
|
|
80802
81136
|
]
|
|
80803
81137
|
};
|
|
80804
81138
|
const yamlText = (opts.name ? `# name: ${opts.name}
|
|
80805
|
-
` : "") +
|
|
81139
|
+
` : "") + import_yaml19.stringify(doc);
|
|
80806
81140
|
const summary = (() => {
|
|
80807
81141
|
const parts = [`cron=${opts.cronExpr}`];
|
|
80808
81142
|
if (opts.secrets?.length)
|
|
@@ -80852,7 +81186,7 @@ function scheduleRemove(opts) {
|
|
|
80852
81186
|
break;
|
|
80853
81187
|
}
|
|
80854
81188
|
try {
|
|
80855
|
-
const parsed =
|
|
81189
|
+
const parsed = import_yaml19.parse(e.raw);
|
|
80856
81190
|
if (parsed && parsed.name === opts.name) {
|
|
80857
81191
|
match = e;
|
|
80858
81192
|
break;
|
|
@@ -80870,8 +81204,8 @@ function scheduleRemove(opts) {
|
|
|
80870
81204
|
}
|
|
80871
81205
|
let priorContent = null;
|
|
80872
81206
|
try {
|
|
80873
|
-
if (
|
|
80874
|
-
priorContent =
|
|
81207
|
+
if (existsSync78(match.path))
|
|
81208
|
+
priorContent = readFileSync64(match.path, "utf-8");
|
|
80875
81209
|
} catch {}
|
|
80876
81210
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
80877
81211
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -81062,11 +81396,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
81062
81396
|
}
|
|
81063
81397
|
|
|
81064
81398
|
// src/cli/agent-config-skill-write.ts
|
|
81065
|
-
var
|
|
81066
|
-
import { existsSync as
|
|
81399
|
+
var import_yaml20 = __toESM(require_dist(), 1);
|
|
81400
|
+
import { existsSync as existsSync79 } from "node:fs";
|
|
81067
81401
|
init_reconcile_default_skills();
|
|
81068
|
-
var
|
|
81069
|
-
import { join as
|
|
81402
|
+
var import_yaml21 = __toESM(require_dist(), 1);
|
|
81403
|
+
import { join as join77 } from "node:path";
|
|
81070
81404
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
81071
81405
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
81072
81406
|
function exitCodeFor2(code) {
|
|
@@ -81115,7 +81449,7 @@ function countCurrentSkills(agent, opts) {
|
|
|
81115
81449
|
let total = 0;
|
|
81116
81450
|
for (const e of entries) {
|
|
81117
81451
|
try {
|
|
81118
|
-
const doc =
|
|
81452
|
+
const doc = import_yaml21.parse(e.raw);
|
|
81119
81453
|
total += (doc?.skills ?? []).length;
|
|
81120
81454
|
} catch {}
|
|
81121
81455
|
}
|
|
@@ -81141,11 +81475,11 @@ function skillInstall(opts) {
|
|
|
81141
81475
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
81142
81476
|
}
|
|
81143
81477
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
81144
|
-
const skillPath =
|
|
81145
|
-
if (!
|
|
81478
|
+
const skillPath = join77(poolDir, skillName);
|
|
81479
|
+
if (!existsSync79(skillPath)) {
|
|
81146
81480
|
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.`);
|
|
81147
81481
|
}
|
|
81148
|
-
const yamlText =
|
|
81482
|
+
const yamlText = import_yaml20.stringify({ skills: [skillName] });
|
|
81149
81483
|
let path8;
|
|
81150
81484
|
try {
|
|
81151
81485
|
path8 = writeSkillsOverlayEntry(agent, slug, yamlText, { root: opts.root });
|
|
@@ -81306,25 +81640,25 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
81306
81640
|
// src/cli/skill.ts
|
|
81307
81641
|
import {
|
|
81308
81642
|
closeSync as closeSync15,
|
|
81309
|
-
existsSync as
|
|
81643
|
+
existsSync as existsSync80,
|
|
81310
81644
|
lstatSync as lstatSync8,
|
|
81311
|
-
mkdirSync as
|
|
81645
|
+
mkdirSync as mkdirSync45,
|
|
81312
81646
|
mkdtempSync as mkdtempSync5,
|
|
81313
81647
|
openSync as openSync15,
|
|
81314
|
-
readFileSync as
|
|
81648
|
+
readFileSync as readFileSync65,
|
|
81315
81649
|
readdirSync as readdirSync30,
|
|
81316
81650
|
realpathSync as realpathSync7,
|
|
81317
81651
|
renameSync as renameSync17,
|
|
81318
81652
|
rmSync as rmSync16,
|
|
81319
81653
|
statSync as statSync29,
|
|
81320
|
-
writeFileSync as
|
|
81654
|
+
writeFileSync as writeFileSync38
|
|
81321
81655
|
} from "node:fs";
|
|
81322
|
-
import { tmpdir as
|
|
81323
|
-
import { dirname as dirname22, join as
|
|
81324
|
-
import { spawnSync as
|
|
81656
|
+
import { tmpdir as tmpdir5, homedir as homedir44 } from "node:os";
|
|
81657
|
+
import { dirname as dirname22, join as join78, relative as relative2, resolve as resolve46 } from "node:path";
|
|
81658
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
81325
81659
|
|
|
81326
81660
|
// src/cli/skill-common.ts
|
|
81327
|
-
var
|
|
81661
|
+
var import_yaml22 = __toESM(require_dist(), 1);
|
|
81328
81662
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
81329
81663
|
var MAX_SKILL_BYTES = 2 * 1024 * 1024;
|
|
81330
81664
|
var MAX_FILES_PER_SKILL = 50;
|
|
@@ -81428,7 +81762,7 @@ function validateSkillMd(content, expectedName) {
|
|
|
81428
81762
|
}
|
|
81429
81763
|
let parsed;
|
|
81430
81764
|
try {
|
|
81431
|
-
parsed =
|
|
81765
|
+
parsed = import_yaml22.parse(fmText);
|
|
81432
81766
|
} catch (e) {
|
|
81433
81767
|
return authorErr("E_SKILL_INVALID_FRONTMATTER", `frontmatter is not valid YAML: ${e.message}`);
|
|
81434
81768
|
}
|
|
@@ -81514,10 +81848,10 @@ function scanForClaudeP2(content) {
|
|
|
81514
81848
|
function resolveSkillsPoolDir2(override) {
|
|
81515
81849
|
const raw = override ?? "~/.switchroom/skills";
|
|
81516
81850
|
if (raw.startsWith("~/")) {
|
|
81517
|
-
return
|
|
81851
|
+
return join78(homedir44(), raw.slice(2));
|
|
81518
81852
|
}
|
|
81519
81853
|
if (raw === "~")
|
|
81520
|
-
return
|
|
81854
|
+
return homedir44();
|
|
81521
81855
|
return resolve46(raw);
|
|
81522
81856
|
}
|
|
81523
81857
|
function readStdinSync() {
|
|
@@ -81553,7 +81887,7 @@ function loadFromDir(dir) {
|
|
|
81553
81887
|
const walk2 = (sub) => {
|
|
81554
81888
|
const entries = readdirSync30(sub, { withFileTypes: true });
|
|
81555
81889
|
for (const ent of entries) {
|
|
81556
|
-
const full =
|
|
81890
|
+
const full = join78(sub, ent.name);
|
|
81557
81891
|
const rel = relative2(abs, full);
|
|
81558
81892
|
if (ent.isSymbolicLink()) {
|
|
81559
81893
|
fail2(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -81563,7 +81897,7 @@ function loadFromDir(dir) {
|
|
|
81563
81897
|
continue;
|
|
81564
81898
|
}
|
|
81565
81899
|
if (ent.isFile()) {
|
|
81566
|
-
const buf =
|
|
81900
|
+
const buf = readFileSync65(full);
|
|
81567
81901
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
81568
81902
|
}
|
|
81569
81903
|
}
|
|
@@ -81574,7 +81908,7 @@ function loadFromDir(dir) {
|
|
|
81574
81908
|
function loadFromTarball(tarPath) {
|
|
81575
81909
|
const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
|
|
81576
81910
|
const listFlags = isGz ? ["-tzf"] : ["-tf"];
|
|
81577
|
-
const list2 =
|
|
81911
|
+
const list2 = spawnSync11("tar", [...listFlags, tarPath], {
|
|
81578
81912
|
encoding: "utf-8",
|
|
81579
81913
|
stdio: ["ignore", "pipe", "pipe"]
|
|
81580
81914
|
});
|
|
@@ -81588,10 +81922,10 @@ function loadFromTarball(tarPath) {
|
|
|
81588
81922
|
fail2(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
81589
81923
|
}
|
|
81590
81924
|
}
|
|
81591
|
-
const staging = mkdtempSync5(
|
|
81925
|
+
const staging = mkdtempSync5(join78(tmpdir5(), "skill-apply-extract-"));
|
|
81592
81926
|
try {
|
|
81593
81927
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
81594
|
-
const r =
|
|
81928
|
+
const r = spawnSync11("tar", [
|
|
81595
81929
|
...flags,
|
|
81596
81930
|
tarPath,
|
|
81597
81931
|
"-C",
|
|
@@ -81612,7 +81946,7 @@ function loadFromTarball(tarPath) {
|
|
|
81612
81946
|
}
|
|
81613
81947
|
}
|
|
81614
81948
|
function loadSingleFile(filePath) {
|
|
81615
|
-
const content =
|
|
81949
|
+
const content = readFileSync65(filePath, "utf-8");
|
|
81616
81950
|
return { "SKILL.md": content };
|
|
81617
81951
|
}
|
|
81618
81952
|
function loadFromStdin() {
|
|
@@ -81666,7 +82000,7 @@ function validatePayload(name, files) {
|
|
|
81666
82000
|
if (errors2.length === 0) {
|
|
81667
82001
|
for (const [path8, content] of Object.entries(files)) {
|
|
81668
82002
|
if (SH_SCRIPT_RE2.test(path8)) {
|
|
81669
|
-
const r =
|
|
82003
|
+
const r = spawnSync11("bash", ["-n"], {
|
|
81670
82004
|
input: content,
|
|
81671
82005
|
encoding: "utf-8"
|
|
81672
82006
|
});
|
|
@@ -81674,11 +82008,11 @@ function validatePayload(name, files) {
|
|
|
81674
82008
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
81675
82009
|
}
|
|
81676
82010
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
81677
|
-
const tmp = mkdtempSync5(
|
|
81678
|
-
const tmpPy =
|
|
82011
|
+
const tmp = mkdtempSync5(join78(tmpdir5(), "skill-apply-py-"));
|
|
82012
|
+
const tmpPy = join78(tmp, "check.py");
|
|
81679
82013
|
try {
|
|
81680
|
-
|
|
81681
|
-
const r =
|
|
82014
|
+
writeFileSync38(tmpPy, content);
|
|
82015
|
+
const r = spawnSync11("python3", ["-m", "py_compile", tmpPy], {
|
|
81682
82016
|
encoding: "utf-8"
|
|
81683
82017
|
});
|
|
81684
82018
|
if (r.status !== 0) {
|
|
@@ -81695,15 +82029,15 @@ function validatePayload(name, files) {
|
|
|
81695
82029
|
function diffSummary(currentDir, files) {
|
|
81696
82030
|
const lines = [];
|
|
81697
82031
|
const currentFiles = {};
|
|
81698
|
-
if (
|
|
82032
|
+
if (existsSync80(currentDir)) {
|
|
81699
82033
|
const walk2 = (sub) => {
|
|
81700
82034
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
81701
|
-
const full =
|
|
82035
|
+
const full = join78(sub, ent.name);
|
|
81702
82036
|
const rel = relative2(currentDir, full);
|
|
81703
82037
|
if (ent.isDirectory()) {
|
|
81704
82038
|
walk2(full);
|
|
81705
82039
|
} else if (ent.isFile()) {
|
|
81706
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
82040
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync65(full, "utf-8");
|
|
81707
82041
|
}
|
|
81708
82042
|
}
|
|
81709
82043
|
};
|
|
@@ -81731,10 +82065,10 @@ function diffSummary(currentDir, files) {
|
|
|
81731
82065
|
`);
|
|
81732
82066
|
}
|
|
81733
82067
|
function writePayload(poolDir, name, files) {
|
|
81734
|
-
if (!
|
|
81735
|
-
|
|
82068
|
+
if (!existsSync80(poolDir)) {
|
|
82069
|
+
mkdirSync45(poolDir, { recursive: true, mode: 493 });
|
|
81736
82070
|
}
|
|
81737
|
-
const target =
|
|
82071
|
+
const target = join78(poolDir, name);
|
|
81738
82072
|
let targetIsSymlink = false;
|
|
81739
82073
|
try {
|
|
81740
82074
|
const st = lstatSync8(target);
|
|
@@ -81745,15 +82079,15 @@ function writePayload(poolDir, name, files) {
|
|
|
81745
82079
|
if (targetIsSymlink) {
|
|
81746
82080
|
fail2(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
81747
82081
|
}
|
|
81748
|
-
const staging = mkdtempSync5(
|
|
82082
|
+
const staging = mkdtempSync5(join78(poolDir, `.skill-apply-stage-${name}-`));
|
|
81749
82083
|
let oldRename = null;
|
|
81750
82084
|
try {
|
|
81751
82085
|
for (const [path8, content] of Object.entries(files)) {
|
|
81752
|
-
const full =
|
|
81753
|
-
|
|
82086
|
+
const full = join78(staging, path8);
|
|
82087
|
+
mkdirSync45(dirname22(full), { recursive: true, mode: 493 });
|
|
81754
82088
|
const fd = openSync15(full, "wx");
|
|
81755
82089
|
try {
|
|
81756
|
-
|
|
82090
|
+
writeFileSync38(fd, content);
|
|
81757
82091
|
} finally {
|
|
81758
82092
|
closeSync15(fd);
|
|
81759
82093
|
}
|
|
@@ -81780,9 +82114,9 @@ function writePayload(poolDir, name, files) {
|
|
|
81780
82114
|
try {
|
|
81781
82115
|
rmSync16(staging, { recursive: true, force: true });
|
|
81782
82116
|
} catch {}
|
|
81783
|
-
if (oldRename &&
|
|
82117
|
+
if (oldRename && existsSync80(oldRename)) {
|
|
81784
82118
|
try {
|
|
81785
|
-
if (
|
|
82119
|
+
if (existsSync80(target)) {
|
|
81786
82120
|
rmSync16(target, { recursive: true, force: true });
|
|
81787
82121
|
}
|
|
81788
82122
|
renameSync17(oldRename, target);
|
|
@@ -81803,7 +82137,7 @@ function registerSkillCommand(program3) {
|
|
|
81803
82137
|
files = loadFromStdin();
|
|
81804
82138
|
} else {
|
|
81805
82139
|
const fromPath = resolve46(opts.from);
|
|
81806
|
-
if (!
|
|
82140
|
+
if (!existsSync80(fromPath)) {
|
|
81807
82141
|
fail2(`--from path does not exist: ${opts.from}`);
|
|
81808
82142
|
}
|
|
81809
82143
|
const st = statSync29(fromPath);
|
|
@@ -81827,7 +82161,7 @@ function registerSkillCommand(program3) {
|
|
|
81827
82161
|
}
|
|
81828
82162
|
const config = loadConfig();
|
|
81829
82163
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
81830
|
-
const currentDir =
|
|
82164
|
+
const currentDir = join78(poolDir, name);
|
|
81831
82165
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
81832
82166
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
81833
82167
|
console.log(diffSummary(currentDir, files));
|
|
@@ -81841,7 +82175,7 @@ function registerSkillCommand(program3) {
|
|
|
81841
82175
|
\u2713 Wrote ${name} to ${currentDir}`));
|
|
81842
82176
|
const applyBin = process.argv[1] ?? "switchroom";
|
|
81843
82177
|
console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
|
|
81844
|
-
const r =
|
|
82178
|
+
const r = spawnSync11(process.argv0, [applyBin, "apply", "--non-interactive"], { stdio: "inherit" });
|
|
81845
82179
|
if (r.status !== 0) {
|
|
81846
82180
|
console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
|
|
81847
82181
|
}
|
|
@@ -81858,22 +82192,22 @@ function sumBytes(files) {
|
|
|
81858
82192
|
// src/cli/skill-personal.ts
|
|
81859
82193
|
import {
|
|
81860
82194
|
closeSync as closeSync16,
|
|
81861
|
-
existsSync as
|
|
82195
|
+
existsSync as existsSync81,
|
|
81862
82196
|
lstatSync as lstatSync9,
|
|
81863
|
-
mkdirSync as
|
|
82197
|
+
mkdirSync as mkdirSync46,
|
|
81864
82198
|
mkdtempSync as mkdtempSync6,
|
|
81865
82199
|
openSync as openSync16,
|
|
81866
|
-
readFileSync as
|
|
82200
|
+
readFileSync as readFileSync66,
|
|
81867
82201
|
readdirSync as readdirSync31,
|
|
81868
82202
|
renameSync as renameSync18,
|
|
81869
82203
|
rmSync as rmSync17,
|
|
81870
82204
|
statSync as statSync30,
|
|
81871
82205
|
utimesSync,
|
|
81872
|
-
writeFileSync as
|
|
82206
|
+
writeFileSync as writeFileSync39
|
|
81873
82207
|
} from "node:fs";
|
|
81874
|
-
import { dirname as dirname23, join as
|
|
81875
|
-
import { homedir as
|
|
81876
|
-
import { spawnSync as
|
|
82208
|
+
import { dirname as dirname23, join as join79, relative as relative3, resolve as resolve47 } from "node:path";
|
|
82209
|
+
import { homedir as homedir45, tmpdir as tmpdir6 } from "node:os";
|
|
82210
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
81877
82211
|
init_helpers();
|
|
81878
82212
|
init_source();
|
|
81879
82213
|
var PERSONAL_PREFIX = "personal-";
|
|
@@ -81882,15 +82216,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
81882
82216
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
81883
82217
|
function resolveConfigSkillsDir(agent) {
|
|
81884
82218
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
81885
|
-
const candidate = override ? resolve47(override) :
|
|
81886
|
-
if (!
|
|
82219
|
+
const candidate = override ? resolve47(override) : join79(homedir45(), ".switchroom-config");
|
|
82220
|
+
if (!existsSync81(candidate))
|
|
81887
82221
|
return null;
|
|
81888
|
-
return
|
|
82222
|
+
return join79(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
81889
82223
|
}
|
|
81890
82224
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
81891
82225
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
81892
82226
|
try {
|
|
81893
|
-
if (!
|
|
82227
|
+
if (!existsSync81(configSkillsRoot))
|
|
81894
82228
|
return;
|
|
81895
82229
|
const now = Date.now();
|
|
81896
82230
|
for (const ent of readdirSync31(configSkillsRoot)) {
|
|
@@ -81903,7 +82237,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
81903
82237
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
81904
82238
|
continue;
|
|
81905
82239
|
try {
|
|
81906
|
-
rmSync17(
|
|
82240
|
+
rmSync17(join79(configSkillsRoot, ent), { recursive: true, force: true });
|
|
81907
82241
|
} catch {}
|
|
81908
82242
|
}
|
|
81909
82243
|
} catch {}
|
|
@@ -81912,7 +82246,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
81912
82246
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
81913
82247
|
if (!configSkillsRoot)
|
|
81914
82248
|
return;
|
|
81915
|
-
const dest =
|
|
82249
|
+
const dest = join79(configSkillsRoot, name);
|
|
81916
82250
|
try {
|
|
81917
82251
|
if (liveSkillDir !== null) {
|
|
81918
82252
|
try {
|
|
@@ -81926,32 +82260,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
81926
82260
|
}
|
|
81927
82261
|
if (liveSkillDir === null) {
|
|
81928
82262
|
sweepMirrorPriors(configSkillsRoot);
|
|
81929
|
-
if (
|
|
81930
|
-
const trash =
|
|
82263
|
+
if (existsSync81(dest)) {
|
|
82264
|
+
const trash = join79(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
81931
82265
|
renameSync18(dest, trash);
|
|
81932
82266
|
}
|
|
81933
82267
|
return;
|
|
81934
82268
|
}
|
|
81935
|
-
|
|
82269
|
+
mkdirSync46(configSkillsRoot, { recursive: true, mode: 493 });
|
|
81936
82270
|
sweepMirrorPriors(configSkillsRoot);
|
|
81937
|
-
const staging = mkdtempSync6(
|
|
82271
|
+
const staging = mkdtempSync6(join79(configSkillsRoot, `.${name}-staging-`));
|
|
81938
82272
|
const walk2 = (src, dst) => {
|
|
81939
|
-
|
|
82273
|
+
mkdirSync46(dst, { recursive: true, mode: 493 });
|
|
81940
82274
|
for (const ent of readdirSync31(src, { withFileTypes: true })) {
|
|
81941
|
-
const s =
|
|
81942
|
-
const d =
|
|
82275
|
+
const s = join79(src, ent.name);
|
|
82276
|
+
const d = join79(dst, ent.name);
|
|
81943
82277
|
if (ent.isSymbolicLink())
|
|
81944
82278
|
continue;
|
|
81945
82279
|
if (ent.isDirectory())
|
|
81946
82280
|
walk2(s, d);
|
|
81947
82281
|
else if (ent.isFile()) {
|
|
81948
|
-
|
|
82282
|
+
writeFileSync39(d, readFileSync66(s));
|
|
81949
82283
|
}
|
|
81950
82284
|
}
|
|
81951
82285
|
};
|
|
81952
82286
|
walk2(liveSkillDir, staging);
|
|
81953
|
-
if (
|
|
81954
|
-
const prior =
|
|
82287
|
+
if (existsSync81(dest)) {
|
|
82288
|
+
const prior = join79(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
81955
82289
|
renameSync18(dest, prior);
|
|
81956
82290
|
}
|
|
81957
82291
|
renameSync18(staging, dest);
|
|
@@ -81978,13 +82312,13 @@ function resolveAgent(opts) {
|
|
|
81978
82312
|
function resolveAgentsRoot(opts) {
|
|
81979
82313
|
if (opts.root)
|
|
81980
82314
|
return resolve47(opts.root);
|
|
81981
|
-
return
|
|
82315
|
+
return join79(homedir45(), ".switchroom", "agents");
|
|
81982
82316
|
}
|
|
81983
82317
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
81984
|
-
return
|
|
82318
|
+
return join79(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
81985
82319
|
}
|
|
81986
82320
|
function trashDir(agentsRoot, agent) {
|
|
81987
|
-
return
|
|
82321
|
+
return join79(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
81988
82322
|
}
|
|
81989
82323
|
function readStdinSync2() {
|
|
81990
82324
|
const chunks = [];
|
|
@@ -82014,7 +82348,7 @@ function loadFromDir2(dir) {
|
|
|
82014
82348
|
const files = {};
|
|
82015
82349
|
const walk2 = (sub) => {
|
|
82016
82350
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
82017
|
-
const full =
|
|
82351
|
+
const full = join79(sub, ent.name);
|
|
82018
82352
|
if (ent.isSymbolicLink()) {
|
|
82019
82353
|
fail3(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
82020
82354
|
}
|
|
@@ -82024,7 +82358,7 @@ function loadFromDir2(dir) {
|
|
|
82024
82358
|
}
|
|
82025
82359
|
if (ent.isFile()) {
|
|
82026
82360
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
82027
|
-
files[rel] =
|
|
82361
|
+
files[rel] = readFileSync66(full, "utf-8");
|
|
82028
82362
|
}
|
|
82029
82363
|
}
|
|
82030
82364
|
};
|
|
@@ -82062,16 +82396,16 @@ function behavioralValidate(files) {
|
|
|
82062
82396
|
const errors2 = [];
|
|
82063
82397
|
for (const [path8, content] of Object.entries(files)) {
|
|
82064
82398
|
if (SH_SCRIPT_RE.test(path8)) {
|
|
82065
|
-
const r =
|
|
82399
|
+
const r = spawnSync12("bash", ["-n"], { input: content, encoding: "utf-8" });
|
|
82066
82400
|
if (r.status !== 0) {
|
|
82067
82401
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
82068
82402
|
}
|
|
82069
82403
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
82070
|
-
const tmp = mkdtempSync6(
|
|
82071
|
-
const tmpPy =
|
|
82404
|
+
const tmp = mkdtempSync6(join79(tmpdir6(), "skill-personal-py-"));
|
|
82405
|
+
const tmpPy = join79(tmp, "check.py");
|
|
82072
82406
|
try {
|
|
82073
|
-
|
|
82074
|
-
const r =
|
|
82407
|
+
writeFileSync39(tmpPy, content);
|
|
82408
|
+
const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
|
|
82075
82409
|
encoding: "utf-8"
|
|
82076
82410
|
});
|
|
82077
82411
|
if (r.status !== 0) {
|
|
@@ -82086,13 +82420,13 @@ function behavioralValidate(files) {
|
|
|
82086
82420
|
}
|
|
82087
82421
|
function sweepTrash(agentsRoot, agent) {
|
|
82088
82422
|
const trash = trashDir(agentsRoot, agent);
|
|
82089
|
-
if (!
|
|
82423
|
+
if (!existsSync81(trash))
|
|
82090
82424
|
return;
|
|
82091
82425
|
const now = Date.now();
|
|
82092
82426
|
for (const ent of readdirSync31(trash, { withFileTypes: true })) {
|
|
82093
82427
|
if (!ent.isDirectory())
|
|
82094
82428
|
continue;
|
|
82095
|
-
const entPath =
|
|
82429
|
+
const entPath = join79(trash, ent.name);
|
|
82096
82430
|
try {
|
|
82097
82431
|
const st = statSync30(entPath);
|
|
82098
82432
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -82112,16 +82446,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
82112
82446
|
if (targetIsSymlink) {
|
|
82113
82447
|
fail3(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
82114
82448
|
}
|
|
82115
|
-
|
|
82116
|
-
const staging = mkdtempSync6(
|
|
82449
|
+
mkdirSync46(dirname23(targetDir), { recursive: true, mode: 493 });
|
|
82450
|
+
const staging = mkdtempSync6(join79(dirname23(targetDir), `.skill-personal-stage-`));
|
|
82117
82451
|
let oldRename = null;
|
|
82118
82452
|
try {
|
|
82119
82453
|
for (const [path8, content] of Object.entries(files)) {
|
|
82120
|
-
const full =
|
|
82121
|
-
|
|
82454
|
+
const full = join79(staging, path8);
|
|
82455
|
+
mkdirSync46(dirname23(full), { recursive: true, mode: 493 });
|
|
82122
82456
|
const fd = openSync16(full, "wx");
|
|
82123
82457
|
try {
|
|
82124
|
-
|
|
82458
|
+
writeFileSync39(fd, content);
|
|
82125
82459
|
} finally {
|
|
82126
82460
|
closeSync16(fd);
|
|
82127
82461
|
}
|
|
@@ -82148,9 +82482,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
82148
82482
|
try {
|
|
82149
82483
|
rmSync17(staging, { recursive: true, force: true });
|
|
82150
82484
|
} catch {}
|
|
82151
|
-
if (oldRename &&
|
|
82485
|
+
if (oldRename && existsSync81(oldRename)) {
|
|
82152
82486
|
try {
|
|
82153
|
-
if (
|
|
82487
|
+
if (existsSync81(targetDir)) {
|
|
82154
82488
|
rmSync17(targetDir, { recursive: true, force: true });
|
|
82155
82489
|
}
|
|
82156
82490
|
renameSync18(oldRename, targetDir);
|
|
@@ -82202,7 +82536,7 @@ function loadFiles(opts) {
|
|
|
82202
82536
|
return loadFromStdin2();
|
|
82203
82537
|
}
|
|
82204
82538
|
const p = resolve47(opts.from);
|
|
82205
|
-
if (!
|
|
82539
|
+
if (!existsSync81(p)) {
|
|
82206
82540
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
82207
82541
|
}
|
|
82208
82542
|
const st = statSync30(p);
|
|
@@ -82210,7 +82544,7 @@ function loadFiles(opts) {
|
|
|
82210
82544
|
return loadFromDir2(p);
|
|
82211
82545
|
}
|
|
82212
82546
|
if (p.endsWith(".md")) {
|
|
82213
|
-
return { "SKILL.md":
|
|
82547
|
+
return { "SKILL.md": readFileSync66(p, "utf-8") };
|
|
82214
82548
|
}
|
|
82215
82549
|
fail3(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
82216
82550
|
}
|
|
@@ -82250,10 +82584,10 @@ function editPersonalAction(name, opts) {
|
|
|
82250
82584
|
}
|
|
82251
82585
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
82252
82586
|
function defaultSharedRoot() {
|
|
82253
|
-
return
|
|
82587
|
+
return join79(homedir45(), ".switchroom", "skills");
|
|
82254
82588
|
}
|
|
82255
82589
|
function defaultBundledRoot() {
|
|
82256
|
-
return
|
|
82590
|
+
return join79(homedir45(), ".switchroom", "skills", "_bundled");
|
|
82257
82591
|
}
|
|
82258
82592
|
function resolveCloneSource(source, opts) {
|
|
82259
82593
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -82263,8 +82597,8 @@ function resolveCloneSource(source, opts) {
|
|
|
82263
82597
|
const tier = m[1];
|
|
82264
82598
|
const slug = m[2];
|
|
82265
82599
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
82266
|
-
const dir =
|
|
82267
|
-
if (!
|
|
82600
|
+
const dir = join79(root, slug);
|
|
82601
|
+
if (!existsSync81(dir)) {
|
|
82268
82602
|
fail3(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
82269
82603
|
}
|
|
82270
82604
|
const st = lstatSync9(dir);
|
|
@@ -82279,7 +82613,7 @@ function readSourceFiles(dir) {
|
|
|
82279
82613
|
const skipped = [];
|
|
82280
82614
|
const walk2 = (sub) => {
|
|
82281
82615
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
82282
|
-
const full =
|
|
82616
|
+
const full = join79(sub, ent.name);
|
|
82283
82617
|
if (ent.isSymbolicLink()) {
|
|
82284
82618
|
continue;
|
|
82285
82619
|
}
|
|
@@ -82299,7 +82633,7 @@ function readSourceFiles(dir) {
|
|
|
82299
82633
|
fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
82300
82634
|
}
|
|
82301
82635
|
} catch {}
|
|
82302
|
-
files[rel] =
|
|
82636
|
+
files[rel] = readFileSync66(full, "utf-8");
|
|
82303
82637
|
}
|
|
82304
82638
|
}
|
|
82305
82639
|
};
|
|
@@ -82388,9 +82722,9 @@ function removePersonalAction(name, opts) {
|
|
|
82388
82722
|
throw err2;
|
|
82389
82723
|
}
|
|
82390
82724
|
const trashRoot = trashDir(agentsRoot, agent);
|
|
82391
|
-
|
|
82725
|
+
mkdirSync46(trashRoot, { recursive: true, mode: 493 });
|
|
82392
82726
|
const ts = Date.now();
|
|
82393
|
-
const trashTarget =
|
|
82727
|
+
const trashTarget = join79(trashRoot, `${name}-${ts}`);
|
|
82394
82728
|
renameSync18(target, trashTarget);
|
|
82395
82729
|
const now = new Date(ts);
|
|
82396
82730
|
utimesSync(trashTarget, now, now);
|
|
@@ -82409,16 +82743,16 @@ function listPersonalAction(opts) {
|
|
|
82409
82743
|
const agent = resolveAgent(opts);
|
|
82410
82744
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
82411
82745
|
sweepTrash(agentsRoot, agent);
|
|
82412
|
-
const skillsDir =
|
|
82746
|
+
const skillsDir = join79(agentsRoot, agent, ".claude", "skills");
|
|
82413
82747
|
const personal = [];
|
|
82414
|
-
if (
|
|
82748
|
+
if (existsSync81(skillsDir)) {
|
|
82415
82749
|
for (const ent of readdirSync31(skillsDir, { withFileTypes: true })) {
|
|
82416
82750
|
if (!ent.isDirectory())
|
|
82417
82751
|
continue;
|
|
82418
82752
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
82419
82753
|
continue;
|
|
82420
82754
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
82421
|
-
const skillPath =
|
|
82755
|
+
const skillPath = join79(skillsDir, ent.name);
|
|
82422
82756
|
let fileCount = 0;
|
|
82423
82757
|
let totalBytes = 0;
|
|
82424
82758
|
const walk2 = (sub) => {
|
|
@@ -82426,10 +82760,10 @@ function listPersonalAction(opts) {
|
|
|
82426
82760
|
if (e.isFile()) {
|
|
82427
82761
|
fileCount += 1;
|
|
82428
82762
|
try {
|
|
82429
|
-
totalBytes += statSync30(
|
|
82763
|
+
totalBytes += statSync30(join79(sub, e.name)).size;
|
|
82430
82764
|
} catch {}
|
|
82431
82765
|
} else if (e.isDirectory()) {
|
|
82432
|
-
walk2(
|
|
82766
|
+
walk2(join79(sub, e.name));
|
|
82433
82767
|
}
|
|
82434
82768
|
}
|
|
82435
82769
|
};
|
|
@@ -82467,29 +82801,29 @@ function registerSkillPersonalCommands(program3) {
|
|
|
82467
82801
|
|
|
82468
82802
|
// src/cli/skill-search.ts
|
|
82469
82803
|
init_helpers();
|
|
82470
|
-
var
|
|
82471
|
-
import { existsSync as
|
|
82472
|
-
import { homedir as
|
|
82473
|
-
import { join as
|
|
82804
|
+
var import_yaml23 = __toESM(require_dist(), 1);
|
|
82805
|
+
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync67, statSync as statSync31 } from "node:fs";
|
|
82806
|
+
import { homedir as homedir46 } from "node:os";
|
|
82807
|
+
import { join as join80, resolve as resolve48 } from "node:path";
|
|
82474
82808
|
var PERSONAL_PREFIX2 = "personal-";
|
|
82475
82809
|
var BUNDLED_SUBDIR = "_bundled";
|
|
82476
82810
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
82477
82811
|
function defaultAgentsRoot() {
|
|
82478
|
-
return resolve48(
|
|
82812
|
+
return resolve48(homedir46(), ".switchroom/agents");
|
|
82479
82813
|
}
|
|
82480
82814
|
function defaultSharedRoot2() {
|
|
82481
|
-
return resolve48(
|
|
82815
|
+
return resolve48(homedir46(), ".switchroom/skills");
|
|
82482
82816
|
}
|
|
82483
82817
|
function defaultBundledRoot2() {
|
|
82484
|
-
return resolve48(
|
|
82818
|
+
return resolve48(homedir46(), ".switchroom/skills/_bundled");
|
|
82485
82819
|
}
|
|
82486
82820
|
function readSkillFrontmatter(skillDir) {
|
|
82487
|
-
const mdPath =
|
|
82488
|
-
if (!
|
|
82821
|
+
const mdPath = join80(skillDir, "SKILL.md");
|
|
82822
|
+
if (!existsSync82(mdPath))
|
|
82489
82823
|
return null;
|
|
82490
82824
|
let content;
|
|
82491
82825
|
try {
|
|
82492
|
-
content =
|
|
82826
|
+
content = readFileSync67(mdPath, "utf-8");
|
|
82493
82827
|
} catch {
|
|
82494
82828
|
return null;
|
|
82495
82829
|
}
|
|
@@ -82507,7 +82841,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
82507
82841
|
const fmText = rest.slice(0, endIdx);
|
|
82508
82842
|
let parsed;
|
|
82509
82843
|
try {
|
|
82510
|
-
parsed =
|
|
82844
|
+
parsed = import_yaml23.parse(fmText);
|
|
82511
82845
|
} catch (e) {
|
|
82512
82846
|
return { error: `yaml parse: ${e.message}` };
|
|
82513
82847
|
}
|
|
@@ -82517,7 +82851,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
82517
82851
|
return { fm: parsed };
|
|
82518
82852
|
}
|
|
82519
82853
|
function statSkillMd(skillDir) {
|
|
82520
|
-
const mdPath =
|
|
82854
|
+
const mdPath = join80(skillDir, "SKILL.md");
|
|
82521
82855
|
try {
|
|
82522
82856
|
const st = statSync31(mdPath);
|
|
82523
82857
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -82528,8 +82862,8 @@ function statSkillMd(skillDir) {
|
|
|
82528
82862
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
82529
82863
|
if (!AGENT_NAME_RE3.test(agent))
|
|
82530
82864
|
return [];
|
|
82531
|
-
const skillsDir =
|
|
82532
|
-
if (!
|
|
82865
|
+
const skillsDir = join80(agentsRoot, agent, ".claude/skills");
|
|
82866
|
+
if (!existsSync82(skillsDir))
|
|
82533
82867
|
return [];
|
|
82534
82868
|
const out = [];
|
|
82535
82869
|
let entries;
|
|
@@ -82541,7 +82875,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
82541
82875
|
for (const ent of entries) {
|
|
82542
82876
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
82543
82877
|
continue;
|
|
82544
|
-
const dirPath =
|
|
82878
|
+
const dirPath = join80(skillsDir, ent);
|
|
82545
82879
|
try {
|
|
82546
82880
|
if (!statSync31(dirPath).isDirectory())
|
|
82547
82881
|
continue;
|
|
@@ -82567,7 +82901,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
82567
82901
|
return out;
|
|
82568
82902
|
}
|
|
82569
82903
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
82570
|
-
if (!
|
|
82904
|
+
if (!existsSync82(sharedRoot))
|
|
82571
82905
|
return [];
|
|
82572
82906
|
const out = [];
|
|
82573
82907
|
let entries;
|
|
@@ -82581,7 +82915,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
82581
82915
|
continue;
|
|
82582
82916
|
if (ent.startsWith("."))
|
|
82583
82917
|
continue;
|
|
82584
|
-
const dirPath =
|
|
82918
|
+
const dirPath = join80(sharedRoot, ent);
|
|
82585
82919
|
try {
|
|
82586
82920
|
if (!statSync31(dirPath).isDirectory())
|
|
82587
82921
|
continue;
|
|
@@ -82605,7 +82939,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
82605
82939
|
return out;
|
|
82606
82940
|
}
|
|
82607
82941
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
82608
|
-
if (!
|
|
82942
|
+
if (!existsSync82(bundledRoot))
|
|
82609
82943
|
return [];
|
|
82610
82944
|
const out = [];
|
|
82611
82945
|
let entries;
|
|
@@ -82617,7 +82951,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
82617
82951
|
for (const ent of entries) {
|
|
82618
82952
|
if (ent.startsWith("."))
|
|
82619
82953
|
continue;
|
|
82620
|
-
const dirPath =
|
|
82954
|
+
const dirPath = join80(bundledRoot, ent);
|
|
82621
82955
|
try {
|
|
82622
82956
|
if (!statSync31(dirPath).isDirectory())
|
|
82623
82957
|
continue;
|
|
@@ -82761,10 +83095,10 @@ function registerHostdMcpCommand(program3) {
|
|
|
82761
83095
|
// src/cli/hostd.ts
|
|
82762
83096
|
init_source();
|
|
82763
83097
|
init_helpers();
|
|
82764
|
-
import { existsSync as
|
|
82765
|
-
import { homedir as
|
|
82766
|
-
import { join as
|
|
82767
|
-
import { spawnSync as
|
|
83098
|
+
import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync69, writeFileSync as writeFileSync40, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
|
|
83099
|
+
import { homedir as homedir47 } from "node:os";
|
|
83100
|
+
import { join as join81 } from "node:path";
|
|
83101
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
82768
83102
|
init_audit_reader();
|
|
82769
83103
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
82770
83104
|
var HOSTD_COMPOSE_PROJECT = "switchroom-hostd";
|
|
@@ -82854,14 +83188,14 @@ networks:
|
|
|
82854
83188
|
`;
|
|
82855
83189
|
}
|
|
82856
83190
|
function hostdDir() {
|
|
82857
|
-
return
|
|
83191
|
+
return join81(homedir47(), ".switchroom", "hostd");
|
|
82858
83192
|
}
|
|
82859
83193
|
function hostdComposePath() {
|
|
82860
|
-
return
|
|
83194
|
+
return join81(hostdDir(), "docker-compose.yml");
|
|
82861
83195
|
}
|
|
82862
83196
|
function backupExistingCompose() {
|
|
82863
83197
|
const p = hostdComposePath();
|
|
82864
|
-
if (!
|
|
83198
|
+
if (!existsSync84(p))
|
|
82865
83199
|
return null;
|
|
82866
83200
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
82867
83201
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -82869,7 +83203,7 @@ function backupExistingCompose() {
|
|
|
82869
83203
|
return bak;
|
|
82870
83204
|
}
|
|
82871
83205
|
function runDocker(args) {
|
|
82872
|
-
const r =
|
|
83206
|
+
const r = spawnSync14("docker", args, { encoding: "utf8" });
|
|
82873
83207
|
return {
|
|
82874
83208
|
ok: r.status === 0,
|
|
82875
83209
|
stdout: r.stdout ?? "",
|
|
@@ -82894,9 +83228,9 @@ async function doInstall(opts, program3) {
|
|
|
82894
83228
|
}
|
|
82895
83229
|
const dir = hostdDir();
|
|
82896
83230
|
const composePath = hostdComposePath();
|
|
82897
|
-
|
|
83231
|
+
mkdirSync47(dir, { recursive: true });
|
|
82898
83232
|
const yaml = renderHostdComposeFile({
|
|
82899
|
-
hostHome:
|
|
83233
|
+
hostHome: homedir47(),
|
|
82900
83234
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
82901
83235
|
operatorUid: resolveOperatorUid()
|
|
82902
83236
|
});
|
|
@@ -82909,7 +83243,7 @@ async function doInstall(opts, program3) {
|
|
|
82909
83243
|
const bak = backupExistingCompose();
|
|
82910
83244
|
if (bak)
|
|
82911
83245
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
82912
|
-
|
|
83246
|
+
writeFileSync40(composePath, yaml, "utf8");
|
|
82913
83247
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
82914
83248
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
82915
83249
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -82940,7 +83274,7 @@ function doStatus() {
|
|
|
82940
83274
|
const composeYml = hostdComposePath();
|
|
82941
83275
|
console.log(source_default.bold("switchroom-hostd"));
|
|
82942
83276
|
console.log("");
|
|
82943
|
-
if (!
|
|
83277
|
+
if (!existsSync84(composeYml)) {
|
|
82944
83278
|
console.log(source_default.yellow(" compose: not installed"));
|
|
82945
83279
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
82946
83280
|
return;
|
|
@@ -82961,14 +83295,14 @@ function doStatus() {
|
|
|
82961
83295
|
} else {
|
|
82962
83296
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
82963
83297
|
}
|
|
82964
|
-
if (
|
|
83298
|
+
if (existsSync84(dir)) {
|
|
82965
83299
|
const entries = [];
|
|
82966
83300
|
try {
|
|
82967
83301
|
for (const name of readdirSync33(dir)) {
|
|
82968
83302
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
82969
83303
|
continue;
|
|
82970
|
-
const sockPath =
|
|
82971
|
-
if (
|
|
83304
|
+
const sockPath = join81(dir, name, "sock");
|
|
83305
|
+
if (existsSync84(sockPath)) {
|
|
82972
83306
|
const st = statSync32(sockPath);
|
|
82973
83307
|
if ((st.mode & 61440) === 49152) {
|
|
82974
83308
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -82987,7 +83321,7 @@ function doStatus() {
|
|
|
82987
83321
|
}
|
|
82988
83322
|
function doUninstall() {
|
|
82989
83323
|
const composeYml = hostdComposePath();
|
|
82990
|
-
if (!
|
|
83324
|
+
if (!existsSync84(composeYml)) {
|
|
82991
83325
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
82992
83326
|
return;
|
|
82993
83327
|
}
|
|
@@ -83011,12 +83345,12 @@ function registerHostdCommand(program3) {
|
|
|
83011
83345
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
83012
83346
|
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) => {
|
|
83013
83347
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
83014
|
-
if (!
|
|
83348
|
+
if (!existsSync84(logPath)) {
|
|
83015
83349
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
83016
83350
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
83017
83351
|
return;
|
|
83018
83352
|
}
|
|
83019
|
-
const raw =
|
|
83353
|
+
const raw = readFileSync69(logPath, "utf-8");
|
|
83020
83354
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
83021
83355
|
const filters = {
|
|
83022
83356
|
agent: opts.agent,
|
|
@@ -83058,10 +83392,10 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
83058
83392
|
// src/cli/webd.ts
|
|
83059
83393
|
init_source();
|
|
83060
83394
|
init_helpers();
|
|
83061
|
-
import { existsSync as
|
|
83062
|
-
import { homedir as
|
|
83063
|
-
import { join as
|
|
83064
|
-
import { spawnSync as
|
|
83395
|
+
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync41, copyFileSync as copyFileSync13 } from "node:fs";
|
|
83396
|
+
import { homedir as homedir48 } from "node:os";
|
|
83397
|
+
import { join as join82 } from "node:path";
|
|
83398
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
83065
83399
|
var DEFAULT_IMAGE_TAG2 = "latest";
|
|
83066
83400
|
var WEB_COMPOSE_PROJECT = "switchroom-web";
|
|
83067
83401
|
function renderWebComposeFile(opts) {
|
|
@@ -83141,14 +83475,14 @@ services:
|
|
|
83141
83475
|
`;
|
|
83142
83476
|
}
|
|
83143
83477
|
function webdDir() {
|
|
83144
|
-
return
|
|
83478
|
+
return join82(homedir48(), ".switchroom", "web");
|
|
83145
83479
|
}
|
|
83146
83480
|
function webdComposePath() {
|
|
83147
|
-
return
|
|
83481
|
+
return join82(webdDir(), "docker-compose.yml");
|
|
83148
83482
|
}
|
|
83149
83483
|
function backupExistingCompose2() {
|
|
83150
83484
|
const p = webdComposePath();
|
|
83151
|
-
if (!
|
|
83485
|
+
if (!existsSync85(p))
|
|
83152
83486
|
return null;
|
|
83153
83487
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
83154
83488
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -83156,7 +83490,7 @@ function backupExistingCompose2() {
|
|
|
83156
83490
|
return bak;
|
|
83157
83491
|
}
|
|
83158
83492
|
function runDocker2(args) {
|
|
83159
|
-
const r =
|
|
83493
|
+
const r = spawnSync15("docker", args, { encoding: "utf8" });
|
|
83160
83494
|
return {
|
|
83161
83495
|
ok: r.status === 0,
|
|
83162
83496
|
stdout: r.stdout ?? "",
|
|
@@ -83173,9 +83507,9 @@ async function doInstall2(opts) {
|
|
|
83173
83507
|
}
|
|
83174
83508
|
const dir = webdDir();
|
|
83175
83509
|
const composePath = webdComposePath();
|
|
83176
|
-
|
|
83510
|
+
mkdirSync48(dir, { recursive: true });
|
|
83177
83511
|
const yaml = renderWebComposeFile({
|
|
83178
|
-
hostHome:
|
|
83512
|
+
hostHome: homedir48(),
|
|
83179
83513
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG2,
|
|
83180
83514
|
operatorUid
|
|
83181
83515
|
});
|
|
@@ -83188,7 +83522,7 @@ async function doInstall2(opts) {
|
|
|
83188
83522
|
const bak = backupExistingCompose2();
|
|
83189
83523
|
if (bak)
|
|
83190
83524
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
83191
|
-
|
|
83525
|
+
writeFileSync41(composePath, yaml, "utf8");
|
|
83192
83526
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
83193
83527
|
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
83194
83528
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${opts.tag ?? DEFAULT_IMAGE_TAG2}\u2026`));
|
|
@@ -83220,7 +83554,7 @@ function doStatus2() {
|
|
|
83220
83554
|
const composeYml = webdComposePath();
|
|
83221
83555
|
console.log(source_default.bold("switchroom-web"));
|
|
83222
83556
|
console.log("");
|
|
83223
|
-
if (!
|
|
83557
|
+
if (!existsSync85(composeYml)) {
|
|
83224
83558
|
console.log(source_default.yellow(" compose: not installed"));
|
|
83225
83559
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
83226
83560
|
return;
|
|
@@ -83244,7 +83578,7 @@ function doStatus2() {
|
|
|
83244
83578
|
}
|
|
83245
83579
|
function doUninstall2() {
|
|
83246
83580
|
const composeYml = webdComposePath();
|
|
83247
|
-
if (!
|
|
83581
|
+
if (!existsSync85(composeYml)) {
|
|
83248
83582
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
83249
83583
|
return;
|
|
83250
83584
|
}
|