@sechroom/cli 2026.7.14 → 2026.7.16
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/index.js +215 -77
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/auth.ts
|
|
@@ -1595,6 +1595,12 @@ function installHooksJson(path, commands, dryRun) {
|
|
|
1595
1595
|
function installClaudeCommands(claudeDir, commands, dryRun) {
|
|
1596
1596
|
return installHooksJson(join6(claudeDir, "settings.json"), commands, dryRun);
|
|
1597
1597
|
}
|
|
1598
|
+
function installCodexCommands(codexHome, commands, dryRun) {
|
|
1599
|
+
return [
|
|
1600
|
+
installHooksJson(join6(codexHome, "hooks.json"), commands, dryRun),
|
|
1601
|
+
installCodexFeatureFlag(join6(codexHome, "config.toml"), dryRun)
|
|
1602
|
+
];
|
|
1603
|
+
}
|
|
1598
1604
|
function ensureCodexFeaturesHooks(content) {
|
|
1599
1605
|
const lines = content.split("\n");
|
|
1600
1606
|
const headerIdx = lines.findIndex((l) => l.trim() === "[features]");
|
|
@@ -3141,10 +3147,119 @@ Examples:
|
|
|
3141
3147
|
}
|
|
3142
3148
|
|
|
3143
3149
|
// src/commands/executor.ts
|
|
3150
|
+
import { dirname as dirname8, join as join11 } from "path";
|
|
3151
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
3152
|
+
var EXECUTOR_STATE = "executor.json";
|
|
3153
|
+
var EXECUTOR_PULSE_COMMAND = "sechroom executor hook-pulse";
|
|
3154
|
+
var EXECUTOR_STOP_COMMAND = "sechroom executor hook-stop";
|
|
3155
|
+
var CLAUDE_EXECUTOR_HOOKS = {
|
|
3156
|
+
SessionStart: EXECUTOR_PULSE_COMMAND,
|
|
3157
|
+
UserPromptSubmit: EXECUTOR_PULSE_COMMAND,
|
|
3158
|
+
PreToolUse: EXECUTOR_PULSE_COMMAND,
|
|
3159
|
+
Stop: EXECUTOR_PULSE_COMMAND,
|
|
3160
|
+
SessionEnd: EXECUTOR_STOP_COMMAND
|
|
3161
|
+
};
|
|
3162
|
+
var CODEX_EXECUTOR_HOOKS = {
|
|
3163
|
+
SessionStart: EXECUTOR_PULSE_COMMAND,
|
|
3164
|
+
UserPromptSubmit: EXECUTOR_PULSE_COMMAND,
|
|
3165
|
+
PreToolUse: EXECUTOR_PULSE_COMMAND,
|
|
3166
|
+
Stop: EXECUTOR_PULSE_COMMAND
|
|
3167
|
+
};
|
|
3144
3168
|
function registerExecutor(program2) {
|
|
3145
3169
|
const executor = program2.command("executor").description(
|
|
3146
3170
|
"Register and operate a local Claude Code/Codex executor advertisement"
|
|
3147
3171
|
);
|
|
3172
|
+
executor.command("install").description("Configure this checkout's harness to advertise itself as a WLP executor").option("--connector <id>", "Approved local-session ConnectorDefinition id").option("--instance-key <key>", "Stable executor identity (defaults to .sechroom/lane.json code-lane)").option("--runtime <kind>", "claude-code | codex").option("--surface <surface>", "claude | codex").option("--capability <key...>", "Capability operation keys claimed by this instance").option("--relay <id>", "Relay identity shared by sibling instances", "sechroom-cli-local").option("--subscription-name <name>", "SignalR delivery binding name", "executor-dispatch").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 600).option("--refresh-after <seconds>", "Minimum age before a hook refreshes", parseInteger, 40).option("-y, --yes", "Non-interactive: accept detected surface and lane defaults", false).option("--dry-run", "Show hook files without writing", false).action(async (opts, cmd) => {
|
|
3173
|
+
const lane = readSem()?.values["code-lane"];
|
|
3174
|
+
const detected = detectHookSurfaces(process.cwd());
|
|
3175
|
+
let surface = opts.surface;
|
|
3176
|
+
let instanceKey = opts.instanceKey;
|
|
3177
|
+
let runtime = opts.runtime;
|
|
3178
|
+
let connector = opts.connector;
|
|
3179
|
+
let capabilities = opts.capability;
|
|
3180
|
+
const surfaceDefault = detected.length === 1 ? detected[0] : lane?.includes("codex") ? "codex" : "claude";
|
|
3181
|
+
if (!opts.yes && canPrompt()) {
|
|
3182
|
+
surface = await promptText("Harness surface (claude or codex)?", surface ?? surfaceDefault);
|
|
3183
|
+
instanceKey = await promptText("Executor instance key?", instanceKey ?? lane ?? "");
|
|
3184
|
+
runtime = await promptText("Runtime (claude-code or codex)?", runtime ?? (surface === "codex" ? "codex" : "claude-code"));
|
|
3185
|
+
connector = await promptText("Approved local-session connector id?", connector ?? "");
|
|
3186
|
+
const capabilityText = await promptText("Capability keys (comma-separated; blank for none)?", capabilities?.join(",") ?? "");
|
|
3187
|
+
capabilities = capabilityText.split(",").map((x) => x.trim()).filter(Boolean);
|
|
3188
|
+
}
|
|
3189
|
+
surface ??= surfaceDefault;
|
|
3190
|
+
instanceKey ??= lane;
|
|
3191
|
+
runtime ??= surface === "codex" ? "codex" : "claude-code";
|
|
3192
|
+
if (!connector) fail("executor install requires --connector (or an interactive connector id)");
|
|
3193
|
+
if (!instanceKey) fail("no instance key resolved; pass --instance-key or pin .sechroom/lane.json code-lane");
|
|
3194
|
+
if (!opts.yes && !canPrompt()) fail("non-interactive executor install requires --yes");
|
|
3195
|
+
parseRuntimeKind(runtime);
|
|
3196
|
+
if (!["claude", "codex"].includes(surface)) fail("surface must be claude or codex");
|
|
3197
|
+
if (opts.refreshAfter >= opts.ttl) fail("refresh-after must be shorter than the TTL");
|
|
3198
|
+
const sem = readSem();
|
|
3199
|
+
const checkout = sem ? dirname8(dirname8(sem.path)) : process.cwd();
|
|
3200
|
+
const statePath = join11(checkout, ".sechroom", EXECUTOR_STATE);
|
|
3201
|
+
const state = {
|
|
3202
|
+
schemaVersion: 1,
|
|
3203
|
+
instanceKey,
|
|
3204
|
+
runtime: runtime.toLowerCase() === "codex" ? "codex" : "claude-code",
|
|
3205
|
+
connectorId: connector,
|
|
3206
|
+
capabilityKeys: capabilities ?? [],
|
|
3207
|
+
relayId: opts.relay,
|
|
3208
|
+
subscriptionName: opts.subscriptionName,
|
|
3209
|
+
ttlSeconds: opts.ttl,
|
|
3210
|
+
refreshAfterSeconds: opts.refreshAfter
|
|
3211
|
+
};
|
|
3212
|
+
if (!opts.dryRun) {
|
|
3213
|
+
mkdirSync9(dirname8(statePath), { recursive: true });
|
|
3214
|
+
writeFileSync9(statePath, JSON.stringify(state, null, 2) + "\n");
|
|
3215
|
+
ensureStateDirIgnored(checkout);
|
|
3216
|
+
}
|
|
3217
|
+
const surfaces = [surface];
|
|
3218
|
+
for (const s of surfaces) {
|
|
3219
|
+
const results = s === "claude" ? [installClaudeCommands(join11(checkout, ".claude"), CLAUDE_EXECUTOR_HOOKS, opts.dryRun)] : installCodexCommands(join11(checkout, ".codex"), CODEX_EXECUTOR_HOOKS, opts.dryRun);
|
|
3220
|
+
for (const result of results) process.stderr.write(describe(result, opts.dryRun) + "\n");
|
|
3221
|
+
}
|
|
3222
|
+
warnIfSechroomNotOnPath();
|
|
3223
|
+
process.stderr.write(style.green("executor harness configured") + style.dim(` \u2014 ${instanceKey}
|
|
3224
|
+
`));
|
|
3225
|
+
});
|
|
3226
|
+
executor.command("hook-pulse").description("Hook adapter: register or refresh this checkout's executor").action(async (_opts, cmd) => {
|
|
3227
|
+
await drainStdin();
|
|
3228
|
+
const located = readExecutorState();
|
|
3229
|
+
if (!located) return;
|
|
3230
|
+
const { state, path } = located;
|
|
3231
|
+
const age = state.lastRefreshAt ? Date.now() - Date.parse(state.lastRefreshAt) : Number.POSITIVE_INFINITY;
|
|
3232
|
+
if (state.instanceId && age < state.refreshAfterSeconds * 1e3) return;
|
|
3233
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3234
|
+
try {
|
|
3235
|
+
let data;
|
|
3236
|
+
if (state.instanceId) {
|
|
3237
|
+
try {
|
|
3238
|
+
data = await refresh(cfg, state.instanceId, state.ttlSeconds);
|
|
3239
|
+
} catch {
|
|
3240
|
+
data = await registerInstance(cfg, state);
|
|
3241
|
+
}
|
|
3242
|
+
} else {
|
|
3243
|
+
data = await registerInstance(cfg, state);
|
|
3244
|
+
}
|
|
3245
|
+
state.instanceId = data.id;
|
|
3246
|
+
state.lastRefreshAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3247
|
+
writeFileSync9(path, JSON.stringify(state, null, 2) + "\n");
|
|
3248
|
+
} catch {
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
executor.command("hook-stop").description("Hook adapter: deregister this checkout's executor").action(async (_opts, cmd) => {
|
|
3252
|
+
await drainStdin();
|
|
3253
|
+
const located = readExecutorState();
|
|
3254
|
+
if (!located?.state.instanceId) return;
|
|
3255
|
+
try {
|
|
3256
|
+
await api(resolveConfig(cmd.optsWithGlobals()), `/me/executor-instances/${encodeURIComponent(located.state.instanceId)}`, { method: "DELETE", body: JSON.stringify({}) });
|
|
3257
|
+
delete located.state.instanceId;
|
|
3258
|
+
delete located.state.lastRefreshAt;
|
|
3259
|
+
writeFileSync9(located.path, JSON.stringify(located.state, null, 2) + "\n");
|
|
3260
|
+
} catch {
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3148
3263
|
executor.command("submit-connector").description(
|
|
3149
3264
|
"Submit a local-session connector definition for governed approval"
|
|
3150
3265
|
).requiredOption("--slug <slug>", "Unique connector definition slug").requiredOption("--display-name <name>", "Human-readable connector name").requiredOption("--transport <kind>", "push | pull").option("--profile <profile...>", "Advertised runtime profiles", [
|
|
@@ -3305,6 +3420,29 @@ async function refresh(cfg, id, ttlSeconds) {
|
|
|
3305
3420
|
}
|
|
3306
3421
|
);
|
|
3307
3422
|
}
|
|
3423
|
+
async function registerInstance(cfg, state) {
|
|
3424
|
+
if (!cfg.workspaceId) fail("executor registration requires a configured workspaceId");
|
|
3425
|
+
const subscription = await api(cfg, "/me/delivery-subscriptions/signalr", {
|
|
3426
|
+
method: "POST",
|
|
3427
|
+
body: JSON.stringify({ name: state.subscriptionName, enabled: true, filter: { tags: ["kind:task"], workspaceScope: [cfg.workspaceId] } })
|
|
3428
|
+
});
|
|
3429
|
+
return api(cfg, "/me/executor-instances", {
|
|
3430
|
+
method: "POST",
|
|
3431
|
+
body: JSON.stringify({ relayId: state.relayId, instanceKey: state.instanceKey, runtimeKind: parseRuntimeKind(state.runtime), activationMode: "Attached", deliverySubscriptionId: subscription.id, connectorId: state.connectorId, claimedCapabilityKeys: state.capabilityKeys, toolSetRef: null, ttlSeconds: state.ttlSeconds })
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
function readExecutorState(start = process.cwd()) {
|
|
3435
|
+
const semPath = resolveSemPathForRead(start);
|
|
3436
|
+
const sem = semPath ? readSem(semPath) : void 0;
|
|
3437
|
+
const path = join11(sem ? dirname8(sem.path) : join11(start, ".sechroom"), EXECUTOR_STATE);
|
|
3438
|
+
if (!existsSync9(path)) return void 0;
|
|
3439
|
+
return { state: JSON.parse(readFileSync8(path, "utf8")), path };
|
|
3440
|
+
}
|
|
3441
|
+
async function drainStdin() {
|
|
3442
|
+
if (process.stdin.isTTY) return;
|
|
3443
|
+
for await (const _chunk of process.stdin) {
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3308
3446
|
async function api(cfg, path, init) {
|
|
3309
3447
|
const token = await requireToken(cfg);
|
|
3310
3448
|
const response = await fetch(`${cfg.baseUrl}${path}`, {
|
|
@@ -3850,8 +3988,8 @@ Examples:
|
|
|
3850
3988
|
|
|
3851
3989
|
// src/setup/apply.ts
|
|
3852
3990
|
import { createHash as createHash3 } from "crypto";
|
|
3853
|
-
import { mkdirSync as
|
|
3854
|
-
import { dirname as
|
|
3991
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10, existsSync as existsSync10 } from "fs";
|
|
3992
|
+
import { dirname as dirname9 } from "path";
|
|
3855
3993
|
var MARKER_BEGIN = "<!-- @sechroom/cli:begin";
|
|
3856
3994
|
var MARKER_END = "<!-- @sechroom/cli:end";
|
|
3857
3995
|
function normalizeBody(s) {
|
|
@@ -3904,22 +4042,22 @@ function parseManagedBlock(content, block) {
|
|
|
3904
4042
|
return null;
|
|
3905
4043
|
}
|
|
3906
4044
|
function ensureDir2(path) {
|
|
3907
|
-
|
|
4045
|
+
mkdirSync10(dirname9(path), { recursive: true });
|
|
3908
4046
|
}
|
|
3909
4047
|
function readOr(path, fallback) {
|
|
3910
4048
|
try {
|
|
3911
|
-
return
|
|
4049
|
+
return readFileSync9(path, "utf8");
|
|
3912
4050
|
} catch {
|
|
3913
4051
|
return fallback;
|
|
3914
4052
|
}
|
|
3915
4053
|
}
|
|
3916
4054
|
function mergeMcpJson(path, snippet, dryRun) {
|
|
3917
4055
|
const incoming = JSON.parse(snippet);
|
|
3918
|
-
const existed =
|
|
4056
|
+
const existed = existsSync10(path);
|
|
3919
4057
|
let current = {};
|
|
3920
4058
|
if (existed) {
|
|
3921
4059
|
try {
|
|
3922
|
-
current = JSON.parse(
|
|
4060
|
+
current = JSON.parse(readFileSync9(path, "utf8"));
|
|
3923
4061
|
} catch {
|
|
3924
4062
|
return { kind: "mcp", path, status: "skipped", note: "existing file isn't valid JSON \u2014 left untouched" };
|
|
3925
4063
|
}
|
|
@@ -3927,26 +4065,26 @@ function mergeMcpJson(path, snippet, dryRun) {
|
|
|
3927
4065
|
current.mcpServers = { ...current.mcpServers ?? {}, ...incoming.mcpServers ?? {} };
|
|
3928
4066
|
if (dryRun) return { kind: "mcp", path, status: "dry-run" };
|
|
3929
4067
|
ensureDir2(path);
|
|
3930
|
-
|
|
4068
|
+
writeFileSync10(path, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
|
|
3931
4069
|
return { kind: "mcp", path, status: existed ? "merged" : "created" };
|
|
3932
4070
|
}
|
|
3933
4071
|
function mergeCodexToml(path, snippet, dryRun) {
|
|
3934
|
-
const existed =
|
|
4072
|
+
const existed = existsSync10(path);
|
|
3935
4073
|
let body = readOr(path, "");
|
|
3936
4074
|
body = body.replace(/(^|\n)\[mcp_servers\.sechroom\][^[]*/, "\n").replace(/\n{3,}/g, "\n\n");
|
|
3937
4075
|
const trimmed = body.trim();
|
|
3938
4076
|
const next = (trimmed.length > 0 ? trimmed + "\n\n" : "") + snippet.trim() + "\n";
|
|
3939
4077
|
if (dryRun) return { kind: "mcp", path, status: "dry-run" };
|
|
3940
4078
|
ensureDir2(path);
|
|
3941
|
-
|
|
4079
|
+
writeFileSync10(path, next, { mode: 384 });
|
|
3942
4080
|
return { kind: "mcp", path, status: existed ? "merged" : "created" };
|
|
3943
4081
|
}
|
|
3944
4082
|
function writeInstructionBlock(path, write, dryRun) {
|
|
3945
|
-
const existed =
|
|
4083
|
+
const existed = existsSync10(path);
|
|
3946
4084
|
const next = computeBlockFile(readOr(path, ""), write);
|
|
3947
4085
|
if (dryRun) return { kind: "instruction", path, status: "dry-run" };
|
|
3948
4086
|
ensureDir2(path);
|
|
3949
|
-
|
|
4087
|
+
writeFileSync10(path, next);
|
|
3950
4088
|
return { kind: "instruction", path, status: existed ? "merged" : "created" };
|
|
3951
4089
|
}
|
|
3952
4090
|
function computeBlockFile(current, write) {
|
|
@@ -3987,7 +4125,7 @@ function applyBlock(path, write, mode, dryRun) {
|
|
|
3987
4125
|
const next = computeBlockFile(current, write);
|
|
3988
4126
|
if (!dryRun) {
|
|
3989
4127
|
ensureDir2(proposedPath);
|
|
3990
|
-
|
|
4128
|
+
writeFileSync10(proposedPath, next);
|
|
3991
4129
|
}
|
|
3992
4130
|
return {
|
|
3993
4131
|
kind: "instruction",
|
|
@@ -4117,8 +4255,8 @@ auto-resumes where you left off and checkpoints working state before compacting.
|
|
|
4117
4255
|
}
|
|
4118
4256
|
|
|
4119
4257
|
// src/setup/skills-offer.ts
|
|
4120
|
-
import { mkdirSync as
|
|
4121
|
-
import { join as
|
|
4258
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
4259
|
+
import { join as join12 } from "path";
|
|
4122
4260
|
|
|
4123
4261
|
// src/setup/lane-pin.ts
|
|
4124
4262
|
var CODE_LANE_PREFIX_BY_CLIENT = {
|
|
@@ -4234,8 +4372,8 @@ Found ${summary} available to you for ${surface}.
|
|
|
4234
4372
|
if (skills.length > 0) {
|
|
4235
4373
|
const written = [];
|
|
4236
4374
|
for (const s of skills) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4375
|
+
mkdirSync11(join12(sDir, s.name), { recursive: true });
|
|
4376
|
+
writeFileSync11(join12(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
|
|
4239
4377
|
written.push(s.name);
|
|
4240
4378
|
}
|
|
4241
4379
|
recordMaterialisedSkills(sDir, DEFAULT_SKILLS_SLUG, written, { surface });
|
|
@@ -4243,11 +4381,11 @@ Found ${summary} available to you for ${surface}.
|
|
|
4243
4381
|
`);
|
|
4244
4382
|
}
|
|
4245
4383
|
if (agents.length > 0) {
|
|
4246
|
-
|
|
4384
|
+
mkdirSync11(aDir, { recursive: true });
|
|
4247
4385
|
const written = [];
|
|
4248
4386
|
for (const a of agents) {
|
|
4249
4387
|
const file = `${a.name}.md`;
|
|
4250
|
-
|
|
4388
|
+
writeFileSync11(join12(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
|
|
4251
4389
|
written.push(file);
|
|
4252
4390
|
}
|
|
4253
4391
|
recordMaterialisedSkills(aDir, DEFAULT_SKILLS_SLUG, written, { surface });
|
|
@@ -4630,13 +4768,13 @@ Wired to namespace '${slug}'. Restart your AI client (or reload MCP) to pick it
|
|
|
4630
4768
|
}
|
|
4631
4769
|
|
|
4632
4770
|
// src/commands/onboard.ts
|
|
4633
|
-
import { existsSync as
|
|
4634
|
-
import { basename as basename2, join as
|
|
4771
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4772
|
+
import { basename as basename2, join as join14 } from "path";
|
|
4635
4773
|
|
|
4636
4774
|
// src/commands/fanout.ts
|
|
4637
4775
|
import { spawnSync } from "child_process";
|
|
4638
|
-
import { existsSync as
|
|
4639
|
-
import { isAbsolute, join as
|
|
4776
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
4777
|
+
import { isAbsolute, join as join13, resolve } from "path";
|
|
4640
4778
|
var ICON = {
|
|
4641
4779
|
refresh: "\u21BB",
|
|
4642
4780
|
bind: "+",
|
|
@@ -4656,21 +4794,21 @@ function discoverChildren(root) {
|
|
|
4656
4794
|
const out = [];
|
|
4657
4795
|
for (const name of names.sort()) {
|
|
4658
4796
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
4659
|
-
const dir =
|
|
4797
|
+
const dir = join13(root, name);
|
|
4660
4798
|
try {
|
|
4661
4799
|
if (!statSync3(dir).isDirectory()) continue;
|
|
4662
4800
|
} catch {
|
|
4663
4801
|
continue;
|
|
4664
4802
|
}
|
|
4665
|
-
if (
|
|
4803
|
+
if (existsSync11(join13(dir, ".git")) || committedBindingPath(dir)) out.push(name);
|
|
4666
4804
|
}
|
|
4667
4805
|
return out;
|
|
4668
4806
|
}
|
|
4669
4807
|
function readManifest(path) {
|
|
4670
|
-
if (!
|
|
4808
|
+
if (!existsSync11(path)) return null;
|
|
4671
4809
|
let parsed;
|
|
4672
4810
|
try {
|
|
4673
|
-
parsed = JSON.parse(
|
|
4811
|
+
parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
4674
4812
|
} catch (err2) {
|
|
4675
4813
|
throw new Error(`couldn't parse ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
4676
4814
|
}
|
|
@@ -5036,10 +5174,10 @@ async function chooseScope(scopeFlag, yes) {
|
|
|
5036
5174
|
}
|
|
5037
5175
|
async function planRecurseChild(entry, root, client, opts) {
|
|
5038
5176
|
const dir = resolveChildDir(entry.path, root);
|
|
5039
|
-
if (!
|
|
5177
|
+
if (!existsSync12(dir)) {
|
|
5040
5178
|
return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
|
|
5041
5179
|
}
|
|
5042
|
-
if (
|
|
5180
|
+
if (existsSync12(join14(dir, ".sechroom.json"))) {
|
|
5043
5181
|
return {
|
|
5044
5182
|
label: entry.path,
|
|
5045
5183
|
dir,
|
|
@@ -5112,7 +5250,7 @@ This fan-out will pin the same lane in every repo:
|
|
|
5112
5250
|
async function runRecurse(cfg, g, opts) {
|
|
5113
5251
|
const { yes, dryRun, json } = opts;
|
|
5114
5252
|
const root = process.cwd();
|
|
5115
|
-
const manifestPath =
|
|
5253
|
+
const manifestPath = join14(root, ".sechroom", "repos.json");
|
|
5116
5254
|
const fromManifest = readManifest(manifestPath);
|
|
5117
5255
|
const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
|
|
5118
5256
|
const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
|
|
@@ -5645,23 +5783,23 @@ Examples:
|
|
|
5645
5783
|
|
|
5646
5784
|
// src/commands/reset.ts
|
|
5647
5785
|
import { homedir as homedir4 } from "os";
|
|
5648
|
-
import { join as
|
|
5649
|
-
import { existsSync as
|
|
5786
|
+
import { join as join15 } from "path";
|
|
5787
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, rmSync as rmSync3 } from "fs";
|
|
5650
5788
|
var SKILLS_LOCK2 = ".sechroom-skills.json";
|
|
5651
|
-
var localSkillsDir = () =>
|
|
5652
|
-
var globalSkillsDir = () =>
|
|
5653
|
-
var localAgentsDir = () =>
|
|
5654
|
-
var globalAgentsDir = () =>
|
|
5789
|
+
var localSkillsDir = () => join15(process.cwd(), ".claude", "skills");
|
|
5790
|
+
var globalSkillsDir = () => join15(homedir4(), ".claude", "skills");
|
|
5791
|
+
var localAgentsDir = () => join15(process.cwd(), ".claude", "agents");
|
|
5792
|
+
var globalAgentsDir = () => join15(homedir4(), ".claude", "agents");
|
|
5655
5793
|
function removeMaterialisedSkills(dir) {
|
|
5656
5794
|
const removed = [];
|
|
5657
|
-
const lockPath =
|
|
5658
|
-
if (!
|
|
5795
|
+
const lockPath = join15(dir, SKILLS_LOCK2);
|
|
5796
|
+
if (!existsSync13(lockPath)) return removed;
|
|
5659
5797
|
try {
|
|
5660
|
-
const lock = JSON.parse(
|
|
5798
|
+
const lock = JSON.parse(readFileSync11(lockPath, "utf8"));
|
|
5661
5799
|
for (const entry of Object.values(lock)) {
|
|
5662
5800
|
for (const name of entry.skills ?? []) {
|
|
5663
|
-
const p =
|
|
5664
|
-
if (
|
|
5801
|
+
const p = join15(dir, name);
|
|
5802
|
+
if (existsSync13(p)) {
|
|
5665
5803
|
rmSync3(p, { recursive: true, force: true });
|
|
5666
5804
|
removed.push(p);
|
|
5667
5805
|
}
|
|
@@ -5706,18 +5844,18 @@ function registerReset(program2) {
|
|
|
5706
5844
|
}
|
|
5707
5845
|
}
|
|
5708
5846
|
const removed = [];
|
|
5709
|
-
const stateDir =
|
|
5710
|
-
if (
|
|
5847
|
+
const stateDir = join15(process.cwd(), ".sechroom");
|
|
5848
|
+
if (existsSync13(stateDir)) {
|
|
5711
5849
|
rmSync3(stateDir, { recursive: true, force: true });
|
|
5712
5850
|
removed.push(stateDir);
|
|
5713
5851
|
}
|
|
5714
|
-
const legacyCfg =
|
|
5715
|
-
if (
|
|
5852
|
+
const legacyCfg = join15(process.cwd(), ".sechroom.json");
|
|
5853
|
+
if (existsSync13(legacyCfg)) {
|
|
5716
5854
|
rmSync3(legacyCfg, { force: true });
|
|
5717
5855
|
removed.push(legacyCfg);
|
|
5718
5856
|
}
|
|
5719
|
-
const legacySem =
|
|
5720
|
-
if (
|
|
5857
|
+
const legacySem = join15(process.cwd(), ".sem");
|
|
5858
|
+
if (existsSync13(legacySem)) {
|
|
5721
5859
|
rmSync3(legacySem, { force: true });
|
|
5722
5860
|
removed.push(legacySem);
|
|
5723
5861
|
}
|
|
@@ -5743,8 +5881,8 @@ function registerReset(program2) {
|
|
|
5743
5881
|
}
|
|
5744
5882
|
|
|
5745
5883
|
// src/commands/skills.ts
|
|
5746
|
-
import { existsSync as
|
|
5747
|
-
import { join as
|
|
5884
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, statSync as statSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
5885
|
+
import { join as join16 } from "path";
|
|
5748
5886
|
function filenameFromDisposition(header) {
|
|
5749
5887
|
if (!header) return void 0;
|
|
5750
5888
|
const m = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(header);
|
|
@@ -5752,11 +5890,11 @@ function filenameFromDisposition(header) {
|
|
|
5752
5890
|
}
|
|
5753
5891
|
function resolveOutputPath(output, serverFilename) {
|
|
5754
5892
|
const filename = serverFilename || "skills.zip";
|
|
5755
|
-
if (!output) return
|
|
5756
|
-
const looksLikeDir = output.endsWith("/") ||
|
|
5893
|
+
if (!output) return join16(process.cwd(), filename);
|
|
5894
|
+
const looksLikeDir = output.endsWith("/") || existsSync14(output) && statSync4(output).isDirectory();
|
|
5757
5895
|
if (looksLikeDir) {
|
|
5758
|
-
|
|
5759
|
-
return
|
|
5896
|
+
mkdirSync12(output, { recursive: true });
|
|
5897
|
+
return join16(output, filename);
|
|
5760
5898
|
}
|
|
5761
5899
|
return output;
|
|
5762
5900
|
}
|
|
@@ -5787,7 +5925,7 @@ async function downloadZip(label, call, output) {
|
|
|
5787
5925
|
const buf = Buffer.from(res.data);
|
|
5788
5926
|
const filename = filenameFromDisposition(res.response.headers.get("content-disposition")) ?? "skills.zip";
|
|
5789
5927
|
const path = resolveOutputPath(output, filename);
|
|
5790
|
-
|
|
5928
|
+
writeFileSync12(path, buf);
|
|
5791
5929
|
return { path, bytes: buf.length, filename };
|
|
5792
5930
|
}
|
|
5793
5931
|
function registerSkills(program2) {
|
|
@@ -5961,12 +6099,12 @@ clients select all; an unconfigured machine preserves the legacy Claude default.
|
|
|
5961
6099
|
}
|
|
5962
6100
|
|
|
5963
6101
|
// src/commands/sweep.ts
|
|
5964
|
-
import { existsSync as
|
|
5965
|
-
import { dirname as
|
|
5966
|
-
var DEFAULT_MANIFEST =
|
|
6102
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6103
|
+
import { dirname as dirname10, join as join17, resolve as resolve2 } from "path";
|
|
6104
|
+
var DEFAULT_MANIFEST = join17(".sechroom", "repos.json");
|
|
5967
6105
|
function planEntry(entry, root) {
|
|
5968
6106
|
const dir = resolveChildDir(entry.path, root);
|
|
5969
|
-
if (!
|
|
6107
|
+
if (!existsSync15(dir)) {
|
|
5970
6108
|
return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
|
|
5971
6109
|
}
|
|
5972
6110
|
if (committedBindingPath(dir)) {
|
|
@@ -6042,7 +6180,7 @@ Examples:
|
|
|
6042
6180
|
`);
|
|
6043
6181
|
return;
|
|
6044
6182
|
}
|
|
6045
|
-
const root =
|
|
6183
|
+
const root = dirname10(dirname10(manifestPath));
|
|
6046
6184
|
const plans = repos.map((entry) => planEntry(entry, root));
|
|
6047
6185
|
if (!json) {
|
|
6048
6186
|
process.stderr.write(
|
|
@@ -6061,13 +6199,13 @@ Examples:
|
|
|
6061
6199
|
|
|
6062
6200
|
// src/commands/telemetry.ts
|
|
6063
6201
|
import {
|
|
6064
|
-
existsSync as
|
|
6065
|
-
mkdirSync as
|
|
6066
|
-
readFileSync as
|
|
6202
|
+
existsSync as existsSync16,
|
|
6203
|
+
mkdirSync as mkdirSync13,
|
|
6204
|
+
readFileSync as readFileSync12,
|
|
6067
6205
|
rmSync as rmSync4,
|
|
6068
|
-
writeFileSync as
|
|
6206
|
+
writeFileSync as writeFileSync13
|
|
6069
6207
|
} from "fs";
|
|
6070
|
-
import { dirname as
|
|
6208
|
+
import { dirname as dirname11, join as join18 } from "path";
|
|
6071
6209
|
function registerTelemetry(program2) {
|
|
6072
6210
|
const telemetry = program2.command("telemetry").description(
|
|
6073
6211
|
"Emit WLP run telemetry (an executor leg's progress events) into a decomposition run"
|
|
@@ -6137,14 +6275,14 @@ function registerTelemetry(program2) {
|
|
|
6137
6275
|
"Decomposition id this session executes"
|
|
6138
6276
|
).requiredOption("--task <id>", "Task id this session executes").action((opts, cmd) => {
|
|
6139
6277
|
const json = Boolean(cmd.optsWithGlobals().json);
|
|
6140
|
-
const dir =
|
|
6141
|
-
|
|
6142
|
-
const path =
|
|
6278
|
+
const dir = join18(process.cwd(), ".sechroom");
|
|
6279
|
+
mkdirSync13(dir, { recursive: true });
|
|
6280
|
+
const path = join18(dir, BINDING_FILE);
|
|
6143
6281
|
const binding = {
|
|
6144
6282
|
decompositionId: opts.decomposition,
|
|
6145
6283
|
taskId: opts.task
|
|
6146
6284
|
};
|
|
6147
|
-
|
|
6285
|
+
writeFileSync13(path, JSON.stringify(binding, null, 2) + "\n");
|
|
6148
6286
|
ensureStateDirIgnored(process.cwd());
|
|
6149
6287
|
if (json) {
|
|
6150
6288
|
emit({ bound: true, ...binding, path }, true);
|
|
@@ -6159,8 +6297,8 @@ function registerTelemetry(program2) {
|
|
|
6159
6297
|
});
|
|
6160
6298
|
telemetry.command("unbind").description("Clear this checkout's telemetry binding").action((_opts, cmd) => {
|
|
6161
6299
|
const json = Boolean(cmd.optsWithGlobals().json);
|
|
6162
|
-
const path =
|
|
6163
|
-
const existed =
|
|
6300
|
+
const path = join18(process.cwd(), ".sechroom", BINDING_FILE);
|
|
6301
|
+
const existed = existsSync16(path);
|
|
6164
6302
|
if (existed) rmSync4(path);
|
|
6165
6303
|
if (json) emit({ unbound: existed, path }, true);
|
|
6166
6304
|
else
|
|
@@ -6268,11 +6406,11 @@ async function postTelemetry(cfg, decompositionId, events) {
|
|
|
6268
6406
|
function findBinding(start) {
|
|
6269
6407
|
let dir = start;
|
|
6270
6408
|
for (; ; ) {
|
|
6271
|
-
const path =
|
|
6272
|
-
if (
|
|
6409
|
+
const path = join18(dir, ".sechroom", BINDING_FILE);
|
|
6410
|
+
if (existsSync16(path)) {
|
|
6273
6411
|
try {
|
|
6274
6412
|
const b = JSON.parse(
|
|
6275
|
-
|
|
6413
|
+
readFileSync12(path, "utf8")
|
|
6276
6414
|
);
|
|
6277
6415
|
if (b.decompositionId && b.taskId)
|
|
6278
6416
|
return { decompositionId: b.decompositionId, taskId: b.taskId };
|
|
@@ -6280,18 +6418,18 @@ function findBinding(start) {
|
|
|
6280
6418
|
}
|
|
6281
6419
|
return null;
|
|
6282
6420
|
}
|
|
6283
|
-
const parent =
|
|
6421
|
+
const parent = dirname11(dir);
|
|
6284
6422
|
if (parent === dir) return null;
|
|
6285
6423
|
dir = parent;
|
|
6286
6424
|
}
|
|
6287
6425
|
}
|
|
6288
6426
|
function parseTranscript(path) {
|
|
6289
|
-
if (!
|
|
6427
|
+
if (!existsSync16(path)) return null;
|
|
6290
6428
|
let tokensIn = 0;
|
|
6291
6429
|
let tokensOut = 0;
|
|
6292
6430
|
let contextUsed = 0;
|
|
6293
6431
|
let model = "";
|
|
6294
|
-
for (const line of
|
|
6432
|
+
for (const line of readFileSync12(path, "utf8").split("\n")) {
|
|
6295
6433
|
if (!line.trim()) continue;
|
|
6296
6434
|
let obj;
|
|
6297
6435
|
try {
|
|
@@ -6598,7 +6736,7 @@ Examples:
|
|
|
6598
6736
|
function resolveVersion() {
|
|
6599
6737
|
try {
|
|
6600
6738
|
const pkg = JSON.parse(
|
|
6601
|
-
|
|
6739
|
+
readFileSync13(new URL("../package.json", import.meta.url), "utf8")
|
|
6602
6740
|
);
|
|
6603
6741
|
return pkg.version ?? "0.0.0";
|
|
6604
6742
|
} catch {
|
package/package.json
CHANGED