@sechroom/cli 2026.7.7-rc.56782977 → 2026.7.8-rc.4b6e8ecc

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.
Files changed (2) hide show
  1. package/dist/index.js +502 -93
  2. 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 readFileSync12 } from "fs";
4
+ import { readFileSync as readFileSync13 } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/auth.ts
@@ -657,9 +657,14 @@ async function runApi(label, fn) {
657
657
  s.succeed();
658
658
  return res.data;
659
659
  }
660
- function fail(error) {
660
+ function formatFailureMessage(error) {
661
661
  let msg;
662
- if (typeof error === "object" && error !== null && "title" in error) {
662
+ if (error instanceof Error) {
663
+ msg = error.message || error.name;
664
+ if (error.cause instanceof Error && error.cause.message) {
665
+ msg += `: ${error.cause.message}`;
666
+ }
667
+ } else if (typeof error === "object" && error !== null && "title" in error) {
663
668
  const problem = error;
664
669
  msg = String(problem.title);
665
670
  if (problem.errors && typeof problem.errors === "object") {
@@ -674,6 +679,10 @@ ${detail.join("\n")}`;
674
679
  } else {
675
680
  msg = String(error);
676
681
  }
682
+ return msg;
683
+ }
684
+ function fail(error) {
685
+ const msg = formatFailureMessage(error);
677
686
  process.stderr.write(`error: ${msg}
678
687
  `);
679
688
  process.exit(1);
@@ -1140,7 +1149,10 @@ function resolveReferences(systemRows, personalRows, surface) {
1140
1149
  }
1141
1150
 
1142
1151
  // src/setup/skill-resolution-io.ts
1143
- var AGENT_TARGET = { "claude-code": "claude-agent" };
1152
+ var AGENT_TARGET = {
1153
+ "claude-code": "claude-agent",
1154
+ "gpt-codex": "gpt-codex-agent"
1155
+ };
1144
1156
  function agentTargetFor(surface) {
1145
1157
  return AGENT_TARGET[surface] ?? `${surface}-agent`;
1146
1158
  }
@@ -1205,12 +1217,58 @@ function writeSkills(dir, skills, surface) {
1205
1217
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
1206
1218
  return written;
1207
1219
  }
1220
+ function splitAgentFrontmatter(body) {
1221
+ body = body.replaceAll("\r\n", "\n");
1222
+ if (!body.startsWith("---\n")) return { instructions: body };
1223
+ const end = body.indexOf("\n---\n", 4);
1224
+ if (end < 0) return { instructions: body };
1225
+ const frontmatter = body.slice(4, end);
1226
+ const field = (key) => {
1227
+ const line = frontmatter.split("\n").find((candidate) => candidate.startsWith(`${key}:`));
1228
+ return line?.slice(key.length + 1).trim().replace(/^(["'])(.*)\1$/, "$2");
1229
+ };
1230
+ return { name: field("name"), description: field("description"), instructions: body.slice(end + 5).trimStart() };
1231
+ }
1232
+ function validateCodexSkills(skills) {
1233
+ for (const skill of skills) {
1234
+ const parsed = splitAgentFrontmatter(skill.body);
1235
+ if (parsed.name !== skill.name || !parsed.description) {
1236
+ throw new Error(
1237
+ `Codex skill '${skill.name}' must begin with YAML frontmatter containing matching name and a description.`
1238
+ );
1239
+ }
1240
+ }
1241
+ }
1242
+ var WORKER_NAMES = ["substrate-drafter", "substrate-miner", "substrate-verifier"];
1243
+ function validateCodexWorkerDependencies(skills, agents) {
1244
+ const available = new Set(agents.map((agent) => agent.name));
1245
+ const missing = WORKER_NAMES.filter(
1246
+ (worker) => skills.some((skill) => skill.body.includes(worker)) && !available.has(worker)
1247
+ );
1248
+ if (missing.length) {
1249
+ throw new Error(`Codex skills reference missing agent template(s): ${missing.join(", ")}.`);
1250
+ }
1251
+ }
1252
+ function codexAgentToml(agent) {
1253
+ const parsed = splitAgentFrontmatter(agent.body);
1254
+ if (!parsed.description) {
1255
+ throw new Error(`Codex agent '${agent.name}' requires a description in its leading YAML frontmatter.`);
1256
+ }
1257
+ return [
1258
+ `name = ${JSON.stringify(parsed.name || agent.name)}`,
1259
+ `description = ${JSON.stringify(parsed.description)}`,
1260
+ `developer_instructions = ${JSON.stringify(parsed.instructions.trimEnd())}`,
1261
+ ""
1262
+ ].join("\n");
1263
+ }
1208
1264
  function writeAgents(dir, agents, surface) {
1209
1265
  if (agents.length) mkdirSync3(dir, { recursive: true });
1210
1266
  const written = [];
1211
1267
  for (const a of agents) {
1212
- const file = `${a.name}.md`;
1213
- writeFileSync3(join4(dir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
1268
+ const codex = surface === CLIENT_SURFACE.codex;
1269
+ const file = `${a.name}.${codex ? "toml" : "md"}`;
1270
+ const body = codex ? codexAgentToml(a) : a.body.endsWith("\n") ? a.body : a.body + "\n";
1271
+ writeFileSync3(join4(dir, file), body);
1214
1272
  written.push(file);
1215
1273
  }
1216
1274
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -1239,7 +1297,7 @@ var AGENT_SPEC = {
1239
1297
  dir: agentsDir,
1240
1298
  resolve: resolveAgentSet,
1241
1299
  write: writeAgents,
1242
- supportsCodex: false
1300
+ supportsCodex: true
1243
1301
  };
1244
1302
  function scopeOf(opts) {
1245
1303
  return opts.local ? "project" : resolveScope(opts.scope);
@@ -1315,6 +1373,10 @@ async function runInstall(spec, cmd, opts) {
1315
1373
  const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
1316
1374
  const results = targets.map((t) => {
1317
1375
  const items = spec.resolve(rows, t.surface);
1376
+ if (t.client === "codex" && spec.kind === "skill") {
1377
+ validateCodexSkills(items);
1378
+ validateCodexWorkerDependencies(items, resolveAgentSet(rows, t.surface));
1379
+ }
1318
1380
  const refs = spec.kind === "skill" ? resolveReferenceSet(rows, t.surface) : [];
1319
1381
  const written = dryRun ? items.map((i) => i.name) : spec.write(t.dir, items, t.surface);
1320
1382
  const refsWritten = dryRun ? refs.map((r) => r.name) : writeReferencesIntoSkillDirs(t.dir, items, refs);
@@ -1440,17 +1502,19 @@ function registerAgents(program2) {
1440
1502
  `
1441
1503
  Examples:
1442
1504
  $ sechroom agents install materialise your installed agents to ~/.claude/agents
1505
+ $ sechroom agents install --client codex materialise native TOML agents to ~/.codex/agents
1443
1506
  $ sechroom agents install --scope project write them to ./.claude/agents instead
1444
1507
  $ sechroom agents install --claude-config-dir ~/.claude-work target another instance
1445
1508
  $ sechroom agents list what's materialised on disk
1446
1509
  $ sechroom agents clean remove the materialised agent files
1447
1510
 
1448
- Agents are resolved from the agent target (target:claude-agent), the dispatchable
1449
- workers your loop skills call (e.g. find-prior-art \u2192 substrate-miner).`
1511
+ Agents are resolved from the client's agent target (target:claude-agent or
1512
+ target:gpt-codex-agent), the dispatchable workers your loop skills call
1513
+ (e.g. find-prior-art \u2192 substrate-miner).`
1450
1514
  );
1451
- agents.command("install").description("Materialise your installed subagents to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--dry-run", "print what would be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(AGENT_SPEC, cmd, opts));
1452
- agents.command("list").description("List the subagents materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").action((opts, cmd) => runList(AGENT_SPEC, cmd, opts));
1453
- agents.command("clean [slug]").description(`Remove subagent files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").action((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
1515
+ agents.command("install").description("Materialise your installed subagents to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--dry-run", "print what would be written; write nothing").option("--client <client>", "claude, codex, or all").option("--json", "machine output").action((opts, cmd) => runInstall(AGENT_SPEC, cmd, opts));
1516
+ agents.command("list").description("List the subagents materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").option("--client <client>", "claude, codex, or all").action((opts, cmd) => runList(AGENT_SPEC, cmd, opts));
1517
+ agents.command("clean [slug]").description(`Remove subagent files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").option("--client <client>", "claude, codex, or all").action((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
1454
1518
  }
1455
1519
 
1456
1520
  // src/commands/channel.ts
@@ -1595,6 +1659,12 @@ function installHooksJson(path, commands, dryRun) {
1595
1659
  function installClaudeCommands(claudeDir, commands, dryRun) {
1596
1660
  return installHooksJson(join6(claudeDir, "settings.json"), commands, dryRun);
1597
1661
  }
1662
+ function installCodexCommands(codexHome, commands, dryRun) {
1663
+ return [
1664
+ installHooksJson(join6(codexHome, "hooks.json"), commands, dryRun),
1665
+ installCodexFeatureFlag(join6(codexHome, "config.toml"), dryRun)
1666
+ ];
1667
+ }
1598
1668
  function ensureCodexFeaturesHooks(content) {
1599
1669
  const lines = content.split("\n");
1600
1670
  const headerIdx = lines.findIndex((l) => l.trim() === "[features]");
@@ -1697,7 +1767,10 @@ function registerChannel(program2) {
1697
1767
  "--tag <tag...>",
1698
1768
  "Tag(s) the event must carry to match (repeatable). Default targets WLP task dispatches.",
1699
1769
  ["kind:task"]
1700
- ).option("--workspace <wsp...>", "Restrict to these workspace id(s)");
1770
+ ).option("--workspace <wsp...>", "Restrict to these workspace id(s)").option(
1771
+ "--executor-instance <id>",
1772
+ "Join the exact executor-instance SignalR group"
1773
+ );
1701
1774
  withFilterOpts(
1702
1775
  channel.command("connect").description(
1703
1776
  "Register a SignalR subscription and stream matched events to stdout"
@@ -1715,7 +1788,7 @@ function registerChannel(program2) {
1715
1788
  (typeof payload === "string" ? payload : JSON.stringify(payload)) + "\n"
1716
1789
  )
1717
1790
  );
1718
- const conn = await openConnection(cfg, deliver);
1791
+ const conn = await openConnection(cfg, deliver, opts.executorInstance);
1719
1792
  await reconcile(cfg, filter, deliver);
1720
1793
  if (json) {
1721
1794
  emit(
@@ -1764,7 +1837,7 @@ function registerChannel(program2) {
1764
1837
  `))
1765
1838
  );
1766
1839
  });
1767
- const conn = await openConnection(cfg, deliver);
1840
+ const conn = await openConnection(cfg, deliver, opts.executorInstance);
1768
1841
  await reconcile(cfg, filter, deliver);
1769
1842
  process.stderr.write(
1770
1843
  style.dim(
@@ -1875,8 +1948,9 @@ async function ensureSubscription(cfg, name, filter) {
1875
1948
  );
1876
1949
  return await resp.json();
1877
1950
  }
1878
- async function openConnection(cfg, onEvent) {
1879
- const conn = new HubConnectionBuilder().withUrl(`${cfg.baseUrl}/notifications/${cfg.tenant}`, {
1951
+ async function openConnection(cfg, onEvent, executorInstanceId) {
1952
+ const query = executorInstanceId ? `?executorInstanceId=${encodeURIComponent(executorInstanceId)}` : "";
1953
+ const conn = new HubConnectionBuilder().withUrl(`${cfg.baseUrl}/notifications/${cfg.tenant}${query}`, {
1880
1954
  transport: HttpTransportType.LongPolling,
1881
1955
  accessTokenFactory: () => requireToken(cfg)
1882
1956
  }).withAutomaticReconnect().build();
@@ -3136,6 +3210,340 @@ Examples:
3136
3210
  });
3137
3211
  }
3138
3212
 
3213
+ // src/commands/executor.ts
3214
+ import { dirname as dirname8, join as join11 } from "path";
3215
+ import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
3216
+ var EXECUTOR_STATE = "executor.json";
3217
+ var EXECUTOR_PULSE_COMMAND = "sechroom executor hook-pulse";
3218
+ var EXECUTOR_STOP_COMMAND = "sechroom executor hook-stop";
3219
+ var CLAUDE_EXECUTOR_HOOKS = {
3220
+ SessionStart: EXECUTOR_PULSE_COMMAND,
3221
+ UserPromptSubmit: EXECUTOR_PULSE_COMMAND,
3222
+ PreToolUse: EXECUTOR_PULSE_COMMAND,
3223
+ Stop: EXECUTOR_PULSE_COMMAND,
3224
+ SessionEnd: EXECUTOR_STOP_COMMAND
3225
+ };
3226
+ var CODEX_EXECUTOR_HOOKS = {
3227
+ SessionStart: EXECUTOR_PULSE_COMMAND,
3228
+ UserPromptSubmit: EXECUTOR_PULSE_COMMAND,
3229
+ PreToolUse: EXECUTOR_PULSE_COMMAND,
3230
+ Stop: EXECUTOR_PULSE_COMMAND
3231
+ };
3232
+ function registerExecutor(program2) {
3233
+ const executor = program2.command("executor").description(
3234
+ "Register and operate a local Claude Code/Codex executor advertisement"
3235
+ );
3236
+ 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) => {
3237
+ const globals = cmd.optsWithGlobals();
3238
+ const lane = readSem()?.values["code-lane"];
3239
+ const detected = detectHookSurfaces(process.cwd());
3240
+ let surface = opts.surface;
3241
+ let instanceKey = opts.instanceKey;
3242
+ let runtime = opts.runtime;
3243
+ let connector = opts.connector;
3244
+ let capabilities = opts.capability;
3245
+ const surfaceDefault = detected.length === 1 ? detected[0] : lane?.includes("codex") ? "codex" : "claude";
3246
+ if (!opts.yes && canPrompt()) {
3247
+ surface = await promptText("Harness surface (claude or codex)?", surface ?? surfaceDefault);
3248
+ instanceKey = await promptText("Executor instance key?", instanceKey ?? lane ?? "");
3249
+ runtime = await promptText("Runtime (claude-code or codex)?", runtime ?? (surface === "codex" ? "codex" : "claude-code"));
3250
+ connector = await promptText("Approved local-session connector id?", connector ?? "");
3251
+ const capabilityText = await promptText("Capability keys (comma-separated; blank for none)?", capabilities?.join(",") ?? "");
3252
+ capabilities = capabilityText.split(",").map((x) => x.trim()).filter(Boolean);
3253
+ }
3254
+ surface ??= surfaceDefault;
3255
+ instanceKey ??= lane;
3256
+ runtime ??= surface === "codex" ? "codex" : "claude-code";
3257
+ if (!connector) fail("executor install requires --connector (or an interactive connector id)");
3258
+ if (!instanceKey) fail("no instance key resolved; pass --instance-key or pin .sechroom/lane.json code-lane");
3259
+ if (!opts.yes && !canPrompt()) fail("non-interactive executor install requires --yes");
3260
+ parseRuntimeKind(runtime);
3261
+ if (!["claude", "codex"].includes(surface)) fail("surface must be claude or codex");
3262
+ if (opts.refreshAfter >= opts.ttl) fail("refresh-after must be shorter than the TTL");
3263
+ const sem = readSem();
3264
+ const checkout = sem ? dirname8(dirname8(sem.path)) : process.cwd();
3265
+ const statePath = join11(checkout, ".sechroom", EXECUTOR_STATE);
3266
+ const state = {
3267
+ schemaVersion: 1,
3268
+ instanceKey,
3269
+ runtime: runtime.toLowerCase() === "codex" ? "codex" : "claude-code",
3270
+ connectorId: connector,
3271
+ capabilityKeys: capabilities ?? [],
3272
+ relayId: opts.relay,
3273
+ subscriptionName: opts.subscriptionName,
3274
+ ttlSeconds: opts.ttl,
3275
+ refreshAfterSeconds: opts.refreshAfter
3276
+ };
3277
+ if (!opts.dryRun) {
3278
+ mkdirSync9(dirname8(statePath), { recursive: true });
3279
+ writeFileSync9(statePath, JSON.stringify(state, null, 2) + "\n");
3280
+ ensureStateDirIgnored(checkout);
3281
+ }
3282
+ const configuredClaudeDirs = globals.claudeConfigDir || process.env.CLAUDE_CONFIG_DIR ? resolveClaudeTargets({ override: globals.claudeConfigDir }).map((target) => target.dir) : [join11(checkout, ".claude")];
3283
+ const configuredCodexHomes = globals.codexHome || process.env.CODEX_HOME ? resolveCodexHomes({ override: globals.codexHome }) : [join11(checkout, ".codex")];
3284
+ const hookTargets = surface === "claude" ? configuredClaudeDirs : configuredCodexHomes;
3285
+ for (const target of hookTargets) {
3286
+ const results = surface === "claude" ? [installClaudeCommands(target, CLAUDE_EXECUTOR_HOOKS, opts.dryRun)] : installCodexCommands(target, CODEX_EXECUTOR_HOOKS, opts.dryRun);
3287
+ for (const result of results) process.stderr.write(describe(result, opts.dryRun) + "\n");
3288
+ }
3289
+ warnIfSechroomNotOnPath();
3290
+ process.stderr.write(style.green("executor harness configured") + style.dim(` \u2014 ${instanceKey}
3291
+ `));
3292
+ });
3293
+ executor.command("hook-pulse").description("Hook adapter: register or refresh this checkout's executor").action(async (_opts, cmd) => {
3294
+ await drainStdin();
3295
+ const located = readExecutorState();
3296
+ if (!located) return;
3297
+ const { state, path } = located;
3298
+ const age = state.lastRefreshAt ? Date.now() - Date.parse(state.lastRefreshAt) : Number.POSITIVE_INFINITY;
3299
+ if (state.instanceId && age < state.refreshAfterSeconds * 1e3) return;
3300
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3301
+ try {
3302
+ let data;
3303
+ if (state.instanceId) {
3304
+ try {
3305
+ data = await refresh(cfg, state.instanceId, state.ttlSeconds);
3306
+ } catch {
3307
+ data = await registerInstance(cfg, state);
3308
+ }
3309
+ } else {
3310
+ data = await registerInstance(cfg, state);
3311
+ }
3312
+ state.instanceId = data.id;
3313
+ state.lastRefreshAt = (/* @__PURE__ */ new Date()).toISOString();
3314
+ writeFileSync9(path, JSON.stringify(state, null, 2) + "\n");
3315
+ } catch {
3316
+ }
3317
+ });
3318
+ executor.command("hook-stop").description("Hook adapter: deregister this checkout's executor").action(async (_opts, cmd) => {
3319
+ await drainStdin();
3320
+ const located = readExecutorState();
3321
+ if (!located?.state.instanceId) return;
3322
+ try {
3323
+ await api(resolveConfig(cmd.optsWithGlobals()), `/me/executor-instances/${encodeURIComponent(located.state.instanceId)}`, { method: "DELETE", body: JSON.stringify({}) });
3324
+ delete located.state.instanceId;
3325
+ delete located.state.lastRefreshAt;
3326
+ writeFileSync9(located.path, JSON.stringify(located.state, null, 2) + "\n");
3327
+ } catch {
3328
+ }
3329
+ });
3330
+ executor.command("submit-connector").description(
3331
+ "Submit a local-session connector definition for governed approval"
3332
+ ).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", [
3333
+ "base",
3334
+ "dotnet-10"
3335
+ ]).action(async (opts, cmd) => {
3336
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3337
+ const data = await api(cfg, "/connectors/definitions", {
3338
+ method: "POST",
3339
+ body: JSON.stringify({
3340
+ slug: opts.slug,
3341
+ displayName: opts.displayName,
3342
+ runtimeProfiles: opts.profile,
3343
+ connectorKind: "ExecutionRuntime",
3344
+ providerKind: "local-session",
3345
+ dispatchTransport: parseTransport(opts.transport)
3346
+ })
3347
+ });
3348
+ emit(data, Boolean(cmd.optsWithGlobals().json));
3349
+ if (!cmd.optsWithGlobals().json) {
3350
+ process.stderr.write(
3351
+ style.dim(
3352
+ "approve this connector definition before registering executors\n"
3353
+ )
3354
+ );
3355
+ }
3356
+ });
3357
+ executor.command("register").description(
3358
+ "Create/reuse a SignalR binding and register this local executor instance"
3359
+ ).requiredOption(
3360
+ "--instance-key <key>",
3361
+ "Stable key for this concrete session/lane"
3362
+ ).requiredOption(
3363
+ "--connector <id>",
3364
+ "Approved local-session ConnectorDefinition id"
3365
+ ).option("--runtime <kind>", "claude-code | codex", "claude-code").option(
3366
+ "--relay <id>",
3367
+ "Relay identity shared by sibling instances",
3368
+ "sechroom-cli-local"
3369
+ ).option(
3370
+ "--subscription-name <name>",
3371
+ "SignalR delivery binding name",
3372
+ "executor-dispatch"
3373
+ ).option(
3374
+ "--capability <key...>",
3375
+ "Capability operation keys claimed by this instance"
3376
+ ).option("--tool-set-ref <ref>", "Optional governed tool-set reference").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (opts, cmd) => {
3377
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3378
+ if (!cfg.workspaceId)
3379
+ fail("executor register requires a configured workspaceId");
3380
+ const subscription = await api(
3381
+ cfg,
3382
+ "/me/delivery-subscriptions/signalr",
3383
+ {
3384
+ method: "POST",
3385
+ body: JSON.stringify({
3386
+ name: opts.subscriptionName,
3387
+ enabled: true,
3388
+ filter: { tags: ["kind:task"], workspaceScope: [cfg.workspaceId] }
3389
+ })
3390
+ }
3391
+ );
3392
+ const data = await api(
3393
+ cfg,
3394
+ "/me/executor-instances",
3395
+ {
3396
+ method: "POST",
3397
+ body: JSON.stringify({
3398
+ relayId: opts.relay,
3399
+ instanceKey: opts.instanceKey,
3400
+ runtimeKind: parseRuntimeKind(opts.runtime),
3401
+ activationMode: "Attached",
3402
+ deliverySubscriptionId: subscription.id,
3403
+ connectorId: opts.connector,
3404
+ claimedCapabilityKeys: opts.capability ?? [],
3405
+ toolSetRef: opts.toolSetRef ?? null,
3406
+ ttlSeconds: opts.ttl
3407
+ })
3408
+ }
3409
+ );
3410
+ emit(data, Boolean(cmd.optsWithGlobals().json));
3411
+ if (!cmd.optsWithGlobals().json)
3412
+ process.stderr.write(
3413
+ style.dim(`refresh with: sechroom executor heartbeat ${data.id}
3414
+ `)
3415
+ );
3416
+ });
3417
+ executor.command("refresh <id>").description("Refresh one advertisement lease once").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (id, opts, cmd) => {
3418
+ const data = await refresh(
3419
+ resolveConfig(cmd.optsWithGlobals()),
3420
+ id,
3421
+ opts.ttl
3422
+ );
3423
+ emit(data, Boolean(cmd.optsWithGlobals().json));
3424
+ });
3425
+ executor.command("heartbeat <id>").description("Keep an advertisement alive until interrupted").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).option("--interval <seconds>", "Refresh interval", parseInteger, 40).action(async (id, opts, cmd) => {
3426
+ if (opts.interval >= opts.ttl)
3427
+ fail("heartbeat interval must be shorter than the TTL");
3428
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3429
+ await refresh(cfg, id, opts.ttl);
3430
+ process.stderr.write(
3431
+ style.green("executor heartbeat active") + style.dim(` \u2014 ${id}
3432
+ `)
3433
+ );
3434
+ await holdHeartbeat(async () => {
3435
+ await refresh(cfg, id, opts.ttl);
3436
+ }, opts.interval * 1e3);
3437
+ });
3438
+ executor.command("offers <id>").description("List live dispatch offers addressed to this exact instance").action(async (id, _opts, cmd) => {
3439
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3440
+ const data = await api(
3441
+ cfg,
3442
+ `/me/executor-instances/${encodeURIComponent(id)}/dispatch-offers`
3443
+ );
3444
+ emit(data, Boolean(cmd.optsWithGlobals().json));
3445
+ });
3446
+ executor.command("deregister <id>").description("Stop advertising this executor instance").action(async (id, _opts, cmd) => {
3447
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3448
+ const data = await api(
3449
+ cfg,
3450
+ `/me/executor-instances/${encodeURIComponent(id)}`,
3451
+ {
3452
+ method: "DELETE",
3453
+ body: JSON.stringify({})
3454
+ }
3455
+ );
3456
+ emit(data, Boolean(cmd.optsWithGlobals().json));
3457
+ });
3458
+ }
3459
+ function parseRuntimeKind(value) {
3460
+ switch (value.trim().toLowerCase()) {
3461
+ case "claude":
3462
+ case "claude-code":
3463
+ return "ClaudeCode";
3464
+ case "codex":
3465
+ return "Codex";
3466
+ default:
3467
+ return fail("runtime must be claude-code or codex");
3468
+ }
3469
+ }
3470
+ function parseTransport(value) {
3471
+ switch (value.trim().toLowerCase()) {
3472
+ case "push":
3473
+ return "Push";
3474
+ case "pull":
3475
+ return "Pull";
3476
+ default:
3477
+ return fail("transport must be push or pull");
3478
+ }
3479
+ }
3480
+ async function refresh(cfg, id, ttlSeconds) {
3481
+ return api(
3482
+ cfg,
3483
+ `/me/executor-instances/${encodeURIComponent(id)}/refresh`,
3484
+ {
3485
+ method: "POST",
3486
+ body: JSON.stringify({ ttlSeconds })
3487
+ }
3488
+ );
3489
+ }
3490
+ async function registerInstance(cfg, state) {
3491
+ if (!cfg.workspaceId) fail("executor registration requires a configured workspaceId");
3492
+ const subscription = await api(cfg, "/me/delivery-subscriptions/signalr", {
3493
+ method: "POST",
3494
+ body: JSON.stringify({ name: state.subscriptionName, enabled: true, filter: { tags: ["kind:task"], workspaceScope: [cfg.workspaceId] } })
3495
+ });
3496
+ return api(cfg, "/me/executor-instances", {
3497
+ method: "POST",
3498
+ 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 })
3499
+ });
3500
+ }
3501
+ function readExecutorState(start = process.cwd()) {
3502
+ const semPath = resolveSemPathForRead(start);
3503
+ const sem = semPath ? readSem(semPath) : void 0;
3504
+ const path = join11(sem ? dirname8(sem.path) : join11(start, ".sechroom"), EXECUTOR_STATE);
3505
+ if (!existsSync9(path)) return void 0;
3506
+ return { state: JSON.parse(readFileSync8(path, "utf8")), path };
3507
+ }
3508
+ async function drainStdin() {
3509
+ if (process.stdin.isTTY) return;
3510
+ for await (const _chunk of process.stdin) {
3511
+ }
3512
+ }
3513
+ async function api(cfg, path, init) {
3514
+ const token = await requireToken(cfg);
3515
+ const response = await fetch(`${cfg.baseUrl}${path}`, {
3516
+ ...init,
3517
+ headers: {
3518
+ authorization: `Bearer ${token}`,
3519
+ tenant: cfg.tenant,
3520
+ "content-type": "application/json",
3521
+ "x-sechroom-surface": "cli"
3522
+ }
3523
+ });
3524
+ if (!response.ok)
3525
+ fail(
3526
+ `${init?.method ?? "GET"} ${path} failed (${response.status}): ${await response.text()}`
3527
+ );
3528
+ return response.json();
3529
+ }
3530
+ function parseInteger(value) {
3531
+ const parsed = Number.parseInt(value, 10);
3532
+ if (!Number.isFinite(parsed)) fail(`expected an integer, got '${value}'`);
3533
+ return parsed;
3534
+ }
3535
+ function holdHeartbeat(tick, intervalMs) {
3536
+ return new Promise((resolve3, reject) => {
3537
+ const timer = setInterval(() => void tick().catch(reject), intervalMs);
3538
+ const stop = () => {
3539
+ clearInterval(timer);
3540
+ resolve3();
3541
+ };
3542
+ process.once("SIGINT", stop);
3543
+ process.once("SIGTERM", stop);
3544
+ });
3545
+ }
3546
+
3139
3547
  // src/commands/filing.ts
3140
3548
  function registerFiling(program2) {
3141
3549
  const filing = program2.command("filing").description("Review and act on filing suggestions");
@@ -3647,8 +4055,8 @@ Examples:
3647
4055
 
3648
4056
  // src/setup/apply.ts
3649
4057
  import { createHash as createHash3 } from "crypto";
3650
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
3651
- import { dirname as dirname8 } from "path";
4058
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10, existsSync as existsSync10 } from "fs";
4059
+ import { dirname as dirname9 } from "path";
3652
4060
  var MARKER_BEGIN = "<!-- @sechroom/cli:begin";
3653
4061
  var MARKER_END = "<!-- @sechroom/cli:end";
3654
4062
  function normalizeBody(s) {
@@ -3701,22 +4109,22 @@ function parseManagedBlock(content, block) {
3701
4109
  return null;
3702
4110
  }
3703
4111
  function ensureDir2(path) {
3704
- mkdirSync9(dirname8(path), { recursive: true });
4112
+ mkdirSync10(dirname9(path), { recursive: true });
3705
4113
  }
3706
4114
  function readOr(path, fallback) {
3707
4115
  try {
3708
- return readFileSync8(path, "utf8");
4116
+ return readFileSync9(path, "utf8");
3709
4117
  } catch {
3710
4118
  return fallback;
3711
4119
  }
3712
4120
  }
3713
4121
  function mergeMcpJson(path, snippet, dryRun) {
3714
4122
  const incoming = JSON.parse(snippet);
3715
- const existed = existsSync9(path);
4123
+ const existed = existsSync10(path);
3716
4124
  let current = {};
3717
4125
  if (existed) {
3718
4126
  try {
3719
- current = JSON.parse(readFileSync8(path, "utf8"));
4127
+ current = JSON.parse(readFileSync9(path, "utf8"));
3720
4128
  } catch {
3721
4129
  return { kind: "mcp", path, status: "skipped", note: "existing file isn't valid JSON \u2014 left untouched" };
3722
4130
  }
@@ -3724,26 +4132,26 @@ function mergeMcpJson(path, snippet, dryRun) {
3724
4132
  current.mcpServers = { ...current.mcpServers ?? {}, ...incoming.mcpServers ?? {} };
3725
4133
  if (dryRun) return { kind: "mcp", path, status: "dry-run" };
3726
4134
  ensureDir2(path);
3727
- writeFileSync9(path, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
4135
+ writeFileSync10(path, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
3728
4136
  return { kind: "mcp", path, status: existed ? "merged" : "created" };
3729
4137
  }
3730
4138
  function mergeCodexToml(path, snippet, dryRun) {
3731
- const existed = existsSync9(path);
4139
+ const existed = existsSync10(path);
3732
4140
  let body = readOr(path, "");
3733
4141
  body = body.replace(/(^|\n)\[mcp_servers\.sechroom\][^[]*/, "\n").replace(/\n{3,}/g, "\n\n");
3734
4142
  const trimmed = body.trim();
3735
4143
  const next = (trimmed.length > 0 ? trimmed + "\n\n" : "") + snippet.trim() + "\n";
3736
4144
  if (dryRun) return { kind: "mcp", path, status: "dry-run" };
3737
4145
  ensureDir2(path);
3738
- writeFileSync9(path, next, { mode: 384 });
4146
+ writeFileSync10(path, next, { mode: 384 });
3739
4147
  return { kind: "mcp", path, status: existed ? "merged" : "created" };
3740
4148
  }
3741
4149
  function writeInstructionBlock(path, write, dryRun) {
3742
- const existed = existsSync9(path);
4150
+ const existed = existsSync10(path);
3743
4151
  const next = computeBlockFile(readOr(path, ""), write);
3744
4152
  if (dryRun) return { kind: "instruction", path, status: "dry-run" };
3745
4153
  ensureDir2(path);
3746
- writeFileSync9(path, next);
4154
+ writeFileSync10(path, next);
3747
4155
  return { kind: "instruction", path, status: existed ? "merged" : "created" };
3748
4156
  }
3749
4157
  function computeBlockFile(current, write) {
@@ -3784,7 +4192,7 @@ function applyBlock(path, write, mode, dryRun) {
3784
4192
  const next = computeBlockFile(current, write);
3785
4193
  if (!dryRun) {
3786
4194
  ensureDir2(proposedPath);
3787
- writeFileSync9(proposedPath, next);
4195
+ writeFileSync10(proposedPath, next);
3788
4196
  }
3789
4197
  return {
3790
4198
  kind: "instruction",
@@ -3914,8 +4322,8 @@ auto-resumes where you left off and checkpoints working state before compacting.
3914
4322
  }
3915
4323
 
3916
4324
  // src/setup/skills-offer.ts
3917
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
3918
- import { join as join11 } from "path";
4325
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
4326
+ import { join as join12 } from "path";
3919
4327
 
3920
4328
  // src/setup/lane-pin.ts
3921
4329
  var CODE_LANE_PREFIX_BY_CLIENT = {
@@ -4031,8 +4439,8 @@ Found ${summary} available to you for ${surface}.
4031
4439
  if (skills.length > 0) {
4032
4440
  const written = [];
4033
4441
  for (const s of skills) {
4034
- mkdirSync10(join11(sDir, s.name), { recursive: true });
4035
- writeFileSync10(join11(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
4442
+ mkdirSync11(join12(sDir, s.name), { recursive: true });
4443
+ writeFileSync11(join12(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
4036
4444
  written.push(s.name);
4037
4445
  }
4038
4446
  recordMaterialisedSkills(sDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -4040,11 +4448,11 @@ Found ${summary} available to you for ${surface}.
4040
4448
  `);
4041
4449
  }
4042
4450
  if (agents.length > 0) {
4043
- mkdirSync10(aDir, { recursive: true });
4451
+ mkdirSync11(aDir, { recursive: true });
4044
4452
  const written = [];
4045
4453
  for (const a of agents) {
4046
4454
  const file = `${a.name}.md`;
4047
- writeFileSync10(join11(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
4455
+ writeFileSync11(join12(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
4048
4456
  written.push(file);
4049
4457
  }
4050
4458
  recordMaterialisedSkills(aDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -4427,13 +4835,13 @@ Wired to namespace '${slug}'. Restart your AI client (or reload MCP) to pick it
4427
4835
  }
4428
4836
 
4429
4837
  // src/commands/onboard.ts
4430
- import { existsSync as existsSync11 } from "fs";
4431
- import { basename as basename2, join as join13 } from "path";
4838
+ import { existsSync as existsSync12 } from "fs";
4839
+ import { basename as basename2, join as join14 } from "path";
4432
4840
 
4433
4841
  // src/commands/fanout.ts
4434
4842
  import { spawnSync } from "child_process";
4435
- import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
4436
- import { isAbsolute, join as join12, resolve } from "path";
4843
+ import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
4844
+ import { isAbsolute, join as join13, resolve } from "path";
4437
4845
  var ICON = {
4438
4846
  refresh: "\u21BB",
4439
4847
  bind: "+",
@@ -4453,21 +4861,21 @@ function discoverChildren(root) {
4453
4861
  const out = [];
4454
4862
  for (const name of names.sort()) {
4455
4863
  if (name.startsWith(".") || name === "node_modules") continue;
4456
- const dir = join12(root, name);
4864
+ const dir = join13(root, name);
4457
4865
  try {
4458
4866
  if (!statSync3(dir).isDirectory()) continue;
4459
4867
  } catch {
4460
4868
  continue;
4461
4869
  }
4462
- if (existsSync10(join12(dir, ".git")) || committedBindingPath(dir)) out.push(name);
4870
+ if (existsSync11(join13(dir, ".git")) || committedBindingPath(dir)) out.push(name);
4463
4871
  }
4464
4872
  return out;
4465
4873
  }
4466
4874
  function readManifest(path) {
4467
- if (!existsSync10(path)) return null;
4875
+ if (!existsSync11(path)) return null;
4468
4876
  let parsed;
4469
4877
  try {
4470
- parsed = JSON.parse(readFileSync9(path, "utf8"));
4878
+ parsed = JSON.parse(readFileSync10(path, "utf8"));
4471
4879
  } catch (err2) {
4472
4880
  throw new Error(`couldn't parse ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`);
4473
4881
  }
@@ -4833,10 +5241,10 @@ async function chooseScope(scopeFlag, yes) {
4833
5241
  }
4834
5242
  async function planRecurseChild(entry, root, client, opts) {
4835
5243
  const dir = resolveChildDir(entry.path, root);
4836
- if (!existsSync11(dir)) {
5244
+ if (!existsSync12(dir)) {
4837
5245
  return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
4838
5246
  }
4839
- if (existsSync11(join13(dir, ".sechroom.json"))) {
5247
+ if (existsSync12(join14(dir, ".sechroom.json"))) {
4840
5248
  return {
4841
5249
  label: entry.path,
4842
5250
  dir,
@@ -4909,7 +5317,7 @@ This fan-out will pin the same lane in every repo:
4909
5317
  async function runRecurse(cfg, g, opts) {
4910
5318
  const { yes, dryRun, json } = opts;
4911
5319
  const root = process.cwd();
4912
- const manifestPath = join13(root, ".sechroom", "repos.json");
5320
+ const manifestPath = join14(root, ".sechroom", "repos.json");
4913
5321
  const fromManifest = readManifest(manifestPath);
4914
5322
  const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
4915
5323
  const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
@@ -5442,23 +5850,23 @@ Examples:
5442
5850
 
5443
5851
  // src/commands/reset.ts
5444
5852
  import { homedir as homedir4 } from "os";
5445
- import { join as join14 } from "path";
5446
- import { existsSync as existsSync12, readFileSync as readFileSync10, rmSync as rmSync3 } from "fs";
5853
+ import { join as join15 } from "path";
5854
+ import { existsSync as existsSync13, readFileSync as readFileSync11, rmSync as rmSync3 } from "fs";
5447
5855
  var SKILLS_LOCK2 = ".sechroom-skills.json";
5448
- var localSkillsDir = () => join14(process.cwd(), ".claude", "skills");
5449
- var globalSkillsDir = () => join14(homedir4(), ".claude", "skills");
5450
- var localAgentsDir = () => join14(process.cwd(), ".claude", "agents");
5451
- var globalAgentsDir = () => join14(homedir4(), ".claude", "agents");
5856
+ var localSkillsDir = () => join15(process.cwd(), ".claude", "skills");
5857
+ var globalSkillsDir = () => join15(homedir4(), ".claude", "skills");
5858
+ var localAgentsDir = () => join15(process.cwd(), ".claude", "agents");
5859
+ var globalAgentsDir = () => join15(homedir4(), ".claude", "agents");
5452
5860
  function removeMaterialisedSkills(dir) {
5453
5861
  const removed = [];
5454
- const lockPath = join14(dir, SKILLS_LOCK2);
5455
- if (!existsSync12(lockPath)) return removed;
5862
+ const lockPath = join15(dir, SKILLS_LOCK2);
5863
+ if (!existsSync13(lockPath)) return removed;
5456
5864
  try {
5457
- const lock = JSON.parse(readFileSync10(lockPath, "utf8"));
5865
+ const lock = JSON.parse(readFileSync11(lockPath, "utf8"));
5458
5866
  for (const entry of Object.values(lock)) {
5459
5867
  for (const name of entry.skills ?? []) {
5460
- const p = join14(dir, name);
5461
- if (existsSync12(p)) {
5868
+ const p = join15(dir, name);
5869
+ if (existsSync13(p)) {
5462
5870
  rmSync3(p, { recursive: true, force: true });
5463
5871
  removed.push(p);
5464
5872
  }
@@ -5503,18 +5911,18 @@ function registerReset(program2) {
5503
5911
  }
5504
5912
  }
5505
5913
  const removed = [];
5506
- const stateDir = join14(process.cwd(), ".sechroom");
5507
- if (existsSync12(stateDir)) {
5914
+ const stateDir = join15(process.cwd(), ".sechroom");
5915
+ if (existsSync13(stateDir)) {
5508
5916
  rmSync3(stateDir, { recursive: true, force: true });
5509
5917
  removed.push(stateDir);
5510
5918
  }
5511
- const legacyCfg = join14(process.cwd(), ".sechroom.json");
5512
- if (existsSync12(legacyCfg)) {
5919
+ const legacyCfg = join15(process.cwd(), ".sechroom.json");
5920
+ if (existsSync13(legacyCfg)) {
5513
5921
  rmSync3(legacyCfg, { force: true });
5514
5922
  removed.push(legacyCfg);
5515
5923
  }
5516
- const legacySem = join14(process.cwd(), ".sem");
5517
- if (existsSync12(legacySem)) {
5924
+ const legacySem = join15(process.cwd(), ".sem");
5925
+ if (existsSync13(legacySem)) {
5518
5926
  rmSync3(legacySem, { force: true });
5519
5927
  removed.push(legacySem);
5520
5928
  }
@@ -5540,8 +5948,8 @@ function registerReset(program2) {
5540
5948
  }
5541
5949
 
5542
5950
  // src/commands/skills.ts
5543
- import { existsSync as existsSync13, mkdirSync as mkdirSync11, statSync as statSync4, writeFileSync as writeFileSync11 } from "fs";
5544
- import { join as join15 } from "path";
5951
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, statSync as statSync4, writeFileSync as writeFileSync12 } from "fs";
5952
+ import { join as join16 } from "path";
5545
5953
  function filenameFromDisposition(header) {
5546
5954
  if (!header) return void 0;
5547
5955
  const m = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(header);
@@ -5549,11 +5957,11 @@ function filenameFromDisposition(header) {
5549
5957
  }
5550
5958
  function resolveOutputPath(output, serverFilename) {
5551
5959
  const filename = serverFilename || "skills.zip";
5552
- if (!output) return join15(process.cwd(), filename);
5553
- const looksLikeDir = output.endsWith("/") || existsSync13(output) && statSync4(output).isDirectory();
5960
+ if (!output) return join16(process.cwd(), filename);
5961
+ const looksLikeDir = output.endsWith("/") || existsSync14(output) && statSync4(output).isDirectory();
5554
5962
  if (looksLikeDir) {
5555
- mkdirSync11(output, { recursive: true });
5556
- return join15(output, filename);
5963
+ mkdirSync12(output, { recursive: true });
5964
+ return join16(output, filename);
5557
5965
  }
5558
5966
  return output;
5559
5967
  }
@@ -5584,7 +5992,7 @@ async function downloadZip(label, call, output) {
5584
5992
  const buf = Buffer.from(res.data);
5585
5993
  const filename = filenameFromDisposition(res.response.headers.get("content-disposition")) ?? "skills.zip";
5586
5994
  const path = resolveOutputPath(output, filename);
5587
- writeFileSync11(path, buf);
5995
+ writeFileSync12(path, buf);
5588
5996
  return { path, bytes: buf.length, filename };
5589
5997
  }
5590
5998
  function registerSkills(program2) {
@@ -5758,12 +6166,12 @@ clients select all; an unconfigured machine preserves the legacy Claude default.
5758
6166
  }
5759
6167
 
5760
6168
  // src/commands/sweep.ts
5761
- import { existsSync as existsSync14 } from "fs";
5762
- import { dirname as dirname9, join as join16, resolve as resolve2 } from "path";
5763
- var DEFAULT_MANIFEST = join16(".sechroom", "repos.json");
6169
+ import { existsSync as existsSync15 } from "fs";
6170
+ import { dirname as dirname10, join as join17, resolve as resolve2 } from "path";
6171
+ var DEFAULT_MANIFEST = join17(".sechroom", "repos.json");
5764
6172
  function planEntry(entry, root) {
5765
6173
  const dir = resolveChildDir(entry.path, root);
5766
- if (!existsSync14(dir)) {
6174
+ if (!existsSync15(dir)) {
5767
6175
  return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
5768
6176
  }
5769
6177
  if (committedBindingPath(dir)) {
@@ -5839,7 +6247,7 @@ Examples:
5839
6247
  `);
5840
6248
  return;
5841
6249
  }
5842
- const root = dirname9(dirname9(manifestPath));
6250
+ const root = dirname10(dirname10(manifestPath));
5843
6251
  const plans = repos.map((entry) => planEntry(entry, root));
5844
6252
  if (!json) {
5845
6253
  process.stderr.write(
@@ -5858,13 +6266,13 @@ Examples:
5858
6266
 
5859
6267
  // src/commands/telemetry.ts
5860
6268
  import {
5861
- existsSync as existsSync15,
5862
- mkdirSync as mkdirSync12,
5863
- readFileSync as readFileSync11,
6269
+ existsSync as existsSync16,
6270
+ mkdirSync as mkdirSync13,
6271
+ readFileSync as readFileSync12,
5864
6272
  rmSync as rmSync4,
5865
- writeFileSync as writeFileSync12
6273
+ writeFileSync as writeFileSync13
5866
6274
  } from "fs";
5867
- import { dirname as dirname10, join as join17 } from "path";
6275
+ import { dirname as dirname11, join as join18 } from "path";
5868
6276
  function registerTelemetry(program2) {
5869
6277
  const telemetry = program2.command("telemetry").description(
5870
6278
  "Emit WLP run telemetry (an executor leg's progress events) into a decomposition run"
@@ -5934,14 +6342,14 @@ function registerTelemetry(program2) {
5934
6342
  "Decomposition id this session executes"
5935
6343
  ).requiredOption("--task <id>", "Task id this session executes").action((opts, cmd) => {
5936
6344
  const json = Boolean(cmd.optsWithGlobals().json);
5937
- const dir = join17(process.cwd(), ".sechroom");
5938
- mkdirSync12(dir, { recursive: true });
5939
- const path = join17(dir, BINDING_FILE);
6345
+ const dir = join18(process.cwd(), ".sechroom");
6346
+ mkdirSync13(dir, { recursive: true });
6347
+ const path = join18(dir, BINDING_FILE);
5940
6348
  const binding = {
5941
6349
  decompositionId: opts.decomposition,
5942
6350
  taskId: opts.task
5943
6351
  };
5944
- writeFileSync12(path, JSON.stringify(binding, null, 2) + "\n");
6352
+ writeFileSync13(path, JSON.stringify(binding, null, 2) + "\n");
5945
6353
  ensureStateDirIgnored(process.cwd());
5946
6354
  if (json) {
5947
6355
  emit({ bound: true, ...binding, path }, true);
@@ -5956,8 +6364,8 @@ function registerTelemetry(program2) {
5956
6364
  });
5957
6365
  telemetry.command("unbind").description("Clear this checkout's telemetry binding").action((_opts, cmd) => {
5958
6366
  const json = Boolean(cmd.optsWithGlobals().json);
5959
- const path = join17(process.cwd(), ".sechroom", BINDING_FILE);
5960
- const existed = existsSync15(path);
6367
+ const path = join18(process.cwd(), ".sechroom", BINDING_FILE);
6368
+ const existed = existsSync16(path);
5961
6369
  if (existed) rmSync4(path);
5962
6370
  if (json) emit({ unbound: existed, path }, true);
5963
6371
  else
@@ -6065,11 +6473,11 @@ async function postTelemetry(cfg, decompositionId, events) {
6065
6473
  function findBinding(start) {
6066
6474
  let dir = start;
6067
6475
  for (; ; ) {
6068
- const path = join17(dir, ".sechroom", BINDING_FILE);
6069
- if (existsSync15(path)) {
6476
+ const path = join18(dir, ".sechroom", BINDING_FILE);
6477
+ if (existsSync16(path)) {
6070
6478
  try {
6071
6479
  const b = JSON.parse(
6072
- readFileSync11(path, "utf8")
6480
+ readFileSync12(path, "utf8")
6073
6481
  );
6074
6482
  if (b.decompositionId && b.taskId)
6075
6483
  return { decompositionId: b.decompositionId, taskId: b.taskId };
@@ -6077,18 +6485,18 @@ function findBinding(start) {
6077
6485
  }
6078
6486
  return null;
6079
6487
  }
6080
- const parent = dirname10(dir);
6488
+ const parent = dirname11(dir);
6081
6489
  if (parent === dir) return null;
6082
6490
  dir = parent;
6083
6491
  }
6084
6492
  }
6085
6493
  function parseTranscript(path) {
6086
- if (!existsSync15(path)) return null;
6494
+ if (!existsSync16(path)) return null;
6087
6495
  let tokensIn = 0;
6088
6496
  let tokensOut = 0;
6089
6497
  let contextUsed = 0;
6090
6498
  let model = "";
6091
- for (const line of readFileSync11(path, "utf8").split("\n")) {
6499
+ for (const line of readFileSync12(path, "utf8").split("\n")) {
6092
6500
  if (!line.trim()) continue;
6093
6501
  let obj;
6094
6502
  try {
@@ -6395,7 +6803,7 @@ Examples:
6395
6803
  function resolveVersion() {
6396
6804
  try {
6397
6805
  const pkg = JSON.parse(
6398
- readFileSync12(new URL("../package.json", import.meta.url), "utf8")
6806
+ readFileSync13(new URL("../package.json", import.meta.url), "utf8")
6399
6807
  );
6400
6808
  return pkg.version ?? "0.0.0";
6401
6809
  } catch {
@@ -6554,6 +6962,7 @@ registerRelationships(program);
6554
6962
  registerWorkspace(program);
6555
6963
  registerProject(program);
6556
6964
  registerDecomposition(program);
6965
+ registerExecutor(program);
6557
6966
  registerClose(program);
6558
6967
  registerFiling(program);
6559
6968
  registerContinuity(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.7-rc.56782977",
3
+ "version": "2026.7.8-rc.4b6e8ecc",
4
4
  "description": "Sechroom CLI — a thin, generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",