@sechroom/cli 2026.6.32 → 2026.6.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +237 -100
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1775,13 +1775,12 @@ Examples:
1775
1775
 
1776
1776
  // src/commands/checkpoint.ts
1777
1777
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1778
- import { dirname as dirname5, join as join5 } from "path";
1778
+ import { dirname as dirname5, join as join6 } from "path";
1779
1779
 
1780
1780
  // src/commands/hook.ts
1781
1781
  import { createHash as createHash2 } from "crypto";
1782
1782
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
1783
- import { homedir as homedir3 } from "os";
1784
- import { delimiter, dirname as dirname4, join as join4 } from "path";
1783
+ import { delimiter, dirname as dirname4, join as join5 } from "path";
1785
1784
 
1786
1785
  // src/sem.ts
1787
1786
  import { basename as basename2, dirname as dirname2, join as join2 } from "path";
@@ -1938,8 +1937,55 @@ function ensureSemIgnored(semPath) {
1938
1937
 
1939
1938
  // src/setup/clients.ts
1940
1939
  import { existsSync as existsSync3 } from "fs";
1940
+ import { homedir as homedir3 } from "os";
1941
+ import { dirname as dirname3, join as join4 } from "path";
1942
+
1943
+ // src/setup/config-dirs.ts
1941
1944
  import { homedir as homedir2 } from "os";
1942
- import { dirname as dirname3, join as join3 } from "path";
1945
+ import { join as join3 } from "path";
1946
+ function expandTilde(p) {
1947
+ if (p === "~") return homedir2();
1948
+ if (p.startsWith("~/")) return join3(homedir2(), p.slice(2));
1949
+ return p;
1950
+ }
1951
+ function splitDirs(raw) {
1952
+ if (!raw) return [];
1953
+ return raw.split(",").map((s) => expandTilde(s.trim())).filter(Boolean);
1954
+ }
1955
+ function resolveScope(flag) {
1956
+ if (flag == null) return "global";
1957
+ if (flag === "global" || flag === "project") return flag;
1958
+ throw new Error(`--scope must be 'global' or 'project' (got '${flag}')`);
1959
+ }
1960
+ function labelFor(dir) {
1961
+ const h = homedir2();
1962
+ if (dir === h) return "~";
1963
+ return dir.startsWith(h + "/") ? "~" + dir.slice(h.length) : dir;
1964
+ }
1965
+ function defaultClaudeDir() {
1966
+ return join3(homedir2(), ".claude");
1967
+ }
1968
+ function defaultCodexHome() {
1969
+ return join3(homedir2(), ".codex");
1970
+ }
1971
+ function resolveClaudeTargets(opts) {
1972
+ const scope = opts.scope ?? "global";
1973
+ const cwd = opts.cwd ?? process.cwd();
1974
+ if (scope === "project") {
1975
+ return [{ dir: join3(cwd, ".claude"), scope, label: "<project>" }];
1976
+ }
1977
+ const fromFlag = splitDirs(opts.override);
1978
+ const fromEnv = splitDirs(process.env.CLAUDE_CONFIG_DIR);
1979
+ const dirs = fromFlag.length ? fromFlag : fromEnv.length ? fromEnv : [defaultClaudeDir()];
1980
+ return dirs.map((dir) => ({ dir, scope, label: labelFor(dir) }));
1981
+ }
1982
+ function resolveCodexHomes(opts) {
1983
+ const scope = opts.scope ?? "global";
1984
+ if (scope === "project") return [];
1985
+ const fromFlag = splitDirs(opts.override);
1986
+ const fromEnv = splitDirs(process.env.CODEX_HOME);
1987
+ return fromFlag.length ? fromFlag : fromEnv.length ? fromEnv : [defaultCodexHome()];
1988
+ }
1943
1989
 
1944
1990
  // src/setup/operator-surface.ts
1945
1991
  var SectionType = {
@@ -2076,51 +2122,53 @@ async function createOverride(cfg, template, personalWorkspaceId) {
2076
2122
  function claudeDesktopConfigPath(home) {
2077
2123
  switch (process.platform) {
2078
2124
  case "darwin":
2079
- return join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2125
+ return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2080
2126
  case "win32":
2081
- return join3(process.env.APPDATA ?? join3(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
2127
+ return join4(process.env.APPDATA ?? join4(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
2082
2128
  default:
2083
- return join3(home, ".config", "Claude", "claude_desktop_config.json");
2129
+ return join4(home, ".config", "Claude", "claude_desktop_config.json");
2084
2130
  }
2085
2131
  }
2086
- function clientTargets(cwd) {
2087
- const home = homedir2();
2132
+ function clientTargets(cwd, opts = {}) {
2133
+ const home = homedir3();
2134
+ const claudeDir = opts.claudeDir ?? join4(home, ".claude");
2135
+ const codexHome = opts.codexHome ?? join4(home, ".codex");
2088
2136
  return {
2089
2137
  "claude-code": {
2090
2138
  key: "claude-code",
2091
2139
  label: "Claude Code",
2092
- mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join3(cwd, ".mcp.json"), format: "json" },
2093
- instruction: { surfaceKey: "claude-code", path: join3(cwd, "CLAUDE.md") }
2140
+ mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join4(cwd, ".mcp.json"), format: "json" },
2141
+ instruction: { surfaceKey: "claude-code", path: join4(cwd, "CLAUDE.md") }
2094
2142
  },
2095
2143
  "claude-desktop": {
2096
2144
  key: "claude-desktop",
2097
2145
  label: "Claude Desktop",
2098
2146
  mcp: { surfaceKey: "claude-desktop", sectionType: SectionType.McpConfig, path: claudeDesktopConfigPath(home), format: "json" },
2099
- instruction: { surfaceKey: "claude-desktop", path: join3(home, ".claude", "CLAUDE.md") }
2147
+ instruction: { surfaceKey: "claude-desktop", path: join4(claudeDir, "CLAUDE.md") }
2100
2148
  },
2101
2149
  codex: {
2102
2150
  key: "codex",
2103
2151
  label: "Codex CLI",
2104
- mcp: { surfaceKey: "chatgpt", sectionType: SectionType.McpConfigToml, path: join3(home, ".codex", "config.toml"), format: "toml" },
2105
- instruction: { surfaceKey: "chatgpt", path: join3(cwd, "AGENTS.md") }
2152
+ mcp: { surfaceKey: "chatgpt", sectionType: SectionType.McpConfigToml, path: join4(codexHome, "config.toml"), format: "toml" },
2153
+ instruction: { surfaceKey: "chatgpt", path: join4(cwd, "AGENTS.md") }
2106
2154
  },
2107
2155
  cursor: {
2108
2156
  key: "cursor",
2109
2157
  label: "Cursor",
2110
- mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join3(cwd, ".cursor", "mcp.json"), format: "json" },
2111
- instruction: { surfaceKey: "chatgpt", path: join3(cwd, "AGENTS.md") }
2158
+ mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join4(cwd, ".cursor", "mcp.json"), format: "json" },
2159
+ instruction: { surfaceKey: "chatgpt", path: join4(cwd, "AGENTS.md") }
2112
2160
  }
2113
2161
  };
2114
2162
  }
2115
2163
  var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor"];
2116
2164
  var DEFAULT_CLIENT_KEY = "claude-code";
2117
2165
  function detectInstalledClients(cwd) {
2118
- const home = homedir2();
2166
+ const home = homedir3();
2119
2167
  const detected = [];
2120
- if (existsSync3(join3(home, ".claude"))) detected.push("claude-code");
2168
+ if (resolveClaudeTargets({}).some((t) => existsSync3(t.dir))) detected.push("claude-code");
2121
2169
  if (existsSync3(dirname3(claudeDesktopConfigPath(home)))) detected.push("claude-desktop");
2122
- if (existsSync3(join3(home, ".codex"))) detected.push("codex");
2123
- if (existsSync3(join3(home, ".cursor")) || existsSync3(join3(cwd, ".cursor"))) detected.push("cursor");
2170
+ if (resolveCodexHomes({}).some((d) => existsSync3(d))) detected.push("codex");
2171
+ if (existsSync3(join4(home, ".cursor")) || existsSync3(join4(cwd, ".cursor"))) detected.push("cursor");
2124
2172
  return detected;
2125
2173
  }
2126
2174
 
@@ -2148,11 +2196,11 @@ function resolveLane(flagLane, cwd) {
2148
2196
  if (!base) return void 0;
2149
2197
  return applyWorktreeLaneSuffix(base, start);
2150
2198
  }
2151
- var INTENT_FILE = join4(".sechroom", "continuity.json");
2199
+ var INTENT_FILE = join5(".sechroom", "continuity.json");
2152
2200
  function resolveIntentPath(start) {
2153
2201
  let dir = start;
2154
2202
  for (; ; ) {
2155
- const candidate = join4(dir, INTENT_FILE);
2203
+ const candidate = join5(dir, INTENT_FILE);
2156
2204
  if (existsSync4(candidate)) return candidate;
2157
2205
  const parent = dirname4(dir);
2158
2206
  if (parent === dir) return void 0;
@@ -2205,8 +2253,8 @@ async function saveSnapshotFromIntent(cmd, cwd, laneFlag, scopeFlag, defaultScop
2205
2253
  }
2206
2254
  function ledgerPath(start) {
2207
2255
  const intent = resolveIntentPath(start);
2208
- const dir = intent ? dirname4(intent) : join4(start, ".sechroom");
2209
- return join4(dir, ".checkpoint-state.json");
2256
+ const dir = intent ? dirname4(intent) : join5(start, ".sechroom");
2257
+ return join5(dir, ".checkpoint-state.json");
2210
2258
  }
2211
2259
  function readLedger(start) {
2212
2260
  try {
@@ -2398,11 +2446,11 @@ function installHookSurfaces(surfaces, opts) {
2398
2446
  const out = [];
2399
2447
  for (const surface of surfaces) {
2400
2448
  if (surface === "claude") {
2401
- const path = opts.local ? join4(opts.cwd, ".claude", "settings.json") : join4(opts.home, ".claude", "settings.json");
2449
+ const path = join5(opts.claudeDir, "settings.json");
2402
2450
  out.push({ surface, results: [installHooksJson(path, CLAUDE_HOOK_COMMANDS, opts.dryRun)] });
2403
2451
  } else {
2404
- const hooksJson = installHooksJson(join4(opts.home, ".codex", "hooks.json"), CODEX_HOOK_COMMANDS, opts.dryRun);
2405
- const featureFlag = installCodexFeatureFlag(join4(opts.home, ".codex", "config.toml"), opts.dryRun);
2452
+ const hooksJson = installHooksJson(join5(opts.codexHome, "hooks.json"), CODEX_HOOK_COMMANDS, opts.dryRun);
2453
+ const featureFlag = installCodexFeatureFlag(join5(opts.codexHome, "config.toml"), opts.dryRun);
2406
2454
  out.push({ surface, results: [hooksJson, featureFlag] });
2407
2455
  }
2408
2456
  }
@@ -2422,7 +2470,7 @@ function isSechroomOnPath() {
2422
2470
  for (const dir of pathEnv.split(delimiter)) {
2423
2471
  if (!dir) continue;
2424
2472
  for (const name of names) {
2425
- if (existsSync4(join4(dir, name))) return true;
2473
+ if (existsSync4(join5(dir, name))) return true;
2426
2474
  }
2427
2475
  }
2428
2476
  return false;
@@ -2503,22 +2551,40 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
2503
2551
  return process.exit(0);
2504
2552
  }
2505
2553
  });
2506
- hook.command("install").description("Wire the session-start + pre-compact hooks into Claude Code and/or Codex config").option("--surface <surface>", "Target surface: claude | codex | both (default: auto-detect installed surfaces)").option("--local", "Claude Code only: write <cwd>/.claude/settings.json instead of ~/.claude/settings.json").option("--dry-run", "Print what would change; write nothing").action((opts) => {
2554
+ hook.command("install").description("Wire the session-start + pre-compact hooks into Claude Code and/or Codex config").option("--surface <surface>", "Target surface: claude | codex | both (default: auto-detect installed surfaces)").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 change; write nothing").action((opts, cmd) => {
2555
+ const g = cmd.optsWithGlobals();
2507
2556
  const dryRun = Boolean(opts.dryRun);
2508
2557
  const cwd = process.cwd();
2558
+ let scope;
2509
2559
  let surfaces;
2510
2560
  try {
2561
+ scope = opts.local ? "project" : resolveScope(opts.scope);
2511
2562
  surfaces = resolveSurfaces(opts.surface, cwd);
2512
2563
  } catch (err2) {
2513
2564
  process.stderr.write(`${err2.message}
2514
2565
  `);
2515
2566
  return process.exit(2);
2516
2567
  }
2568
+ const claudeTargets = surfaces.includes("claude") ? resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd }) : [];
2569
+ const codexHomes = surfaces.includes("codex") ? resolveCodexHomes({ override: g.codexHome, scope }) : [];
2517
2570
  const results = [];
2518
2571
  try {
2519
- const installed = installHookSurfaces(surfaces, { dryRun, local: opts.local, cwd, home: homedir3() });
2520
- for (const { surface, results: surfaceResults } of installed) {
2521
- process.stdout.write(`${HOOK_SURFACE_LABEL[surface]}:
2572
+ const multiClaude = claudeTargets.length > 1;
2573
+ for (const t of claudeTargets) {
2574
+ const surfaceResults = installHookSurfaces(["claude"], { dryRun, claudeDir: t.dir, codexHome: "" })[0].results;
2575
+ process.stdout.write(`${HOOK_SURFACE_LABEL.claude}${multiClaude ? ` (${t.label})` : ""}:
2576
+ `);
2577
+ for (const r of surfaceResults) {
2578
+ results.push(r);
2579
+ process.stdout.write(describe(r, dryRun) + "\n");
2580
+ }
2581
+ }
2582
+ if (surfaces.includes("codex") && codexHomes.length === 0) {
2583
+ process.stdout.write("Codex has no project scope \u2014 skipped (use --scope global for Codex).\n");
2584
+ }
2585
+ for (const codexHome of codexHomes) {
2586
+ const surfaceResults = installHookSurfaces(["codex"], { dryRun, claudeDir: "", codexHome })[0].results;
2587
+ process.stdout.write(`${HOOK_SURFACE_LABEL.codex}:
2522
2588
  `);
2523
2589
  for (const r of surfaceResults) {
2524
2590
  results.push(r);
@@ -2621,7 +2687,7 @@ Examples:
2621
2687
  const client = await makeClient(cfg);
2622
2688
  return client.POST("/continuity/snapshots", { body });
2623
2689
  });
2624
- const path = resolveIntentPath(cwd) ?? join5(cwd, INTENT_FILE);
2690
+ const path = resolveIntentPath(cwd) ?? join6(cwd, INTENT_FILE);
2625
2691
  const fileBody = { ...merged, scope, lastSnapshotId: data.snapshotId };
2626
2692
  mkdirSync4(dirname5(path), { recursive: true });
2627
2693
  writeFileSync4(path, JSON.stringify(fileBody, null, 2) + "\n");
@@ -3062,12 +3128,14 @@ async function applyClient(cfg, setup, target, opts) {
3062
3128
  }
3063
3129
 
3064
3130
  // src/setup/hooks-offer.ts
3065
- import { homedir as homedir4 } from "os";
3066
3131
  async function maybeOfferHooks(opts) {
3067
3132
  if (opts.dryRun) return;
3068
3133
  const cwd = opts.cwd ?? process.cwd();
3134
+ const scope = opts.scope ?? "global";
3069
3135
  const surfaces = detectHookSurfaces(cwd);
3070
3136
  if (surfaces.length === 0) return;
3137
+ const claudeTargets = surfaces.includes("claude") ? resolveClaudeTargets({ override: opts.claudeConfigDir, scope, cwd }) : [];
3138
+ const codexHomes = surfaces.includes("codex") ? resolveCodexHomes({ override: opts.codexHome, scope }) : [];
3071
3139
  const names = surfaces.map((s) => HOOK_SURFACE_LABEL[s]).join(" + ");
3072
3140
  process.stderr.write(
3073
3141
  `
@@ -3078,15 +3146,28 @@ auto-resumes where you left off and checkpoints working state before compacting.
3078
3146
  const install = opts.yes ? true : canPrompt() ? await promptYesNo(`Install the continuity hooks for ${names}?`) : false;
3079
3147
  if (!install) return;
3080
3148
  try {
3081
- const installed = installHookSurfaces(surfaces, { dryRun: false, cwd, home: homedir4() });
3082
3149
  let changed = false;
3083
- for (const { surface, results } of installed) {
3150
+ const emit2 = (surface, results, label) => {
3084
3151
  for (const r of results) {
3085
3152
  if (r.status !== "current") changed = true;
3086
3153
  const verb = r.status === "current" ? "already configured" : r.status === "created" ? "created" : "updated";
3087
- process.stderr.write(`${style.green("\u2713")} ${HOOK_SURFACE_LABEL[surface]}: ${r.path} (${verb})
3154
+ const tag = label ? ` ${style.dim(`(${label})`)}` : "";
3155
+ process.stderr.write(`${style.green("\u2713")} ${HOOK_SURFACE_LABEL[surface]}${tag}: ${r.path} (${verb})
3088
3156
  `);
3089
3157
  }
3158
+ };
3159
+ const multiClaude = claudeTargets.length > 1;
3160
+ for (const t of claudeTargets) {
3161
+ const results = installHookSurfaces(["claude"], { dryRun: false, claudeDir: t.dir, codexHome: "" })[0].results;
3162
+ emit2("claude", results, multiClaude ? t.label : void 0);
3163
+ }
3164
+ if (surfaces.includes("codex") && codexHomes.length === 0) {
3165
+ process.stderr.write(`${style.dim("Codex has no project scope \u2014 skipped (use --scope global for Codex).")}
3166
+ `);
3167
+ }
3168
+ for (const codexHome of codexHomes) {
3169
+ const results = installHookSurfaces(["codex"], { dryRun: false, claudeDir: "", codexHome })[0].results;
3170
+ emit2("codex", results);
3090
3171
  }
3091
3172
  if (changed) {
3092
3173
  process.stderr.write(`${style.dim("Restart (or reload) your agent for the hooks to take effect.")}
@@ -3101,7 +3182,7 @@ auto-resumes where you left off and checkpoints working state before compacting.
3101
3182
 
3102
3183
  // src/setup/skills-offer.ts
3103
3184
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
3104
- import { join as join7 } from "path";
3185
+ import { join as join8 } from "path";
3105
3186
 
3106
3187
  // src/setup/lane-pin.ts
3107
3188
  var CODE_LANE_PREFIX_BY_CLIENT = {
@@ -3228,18 +3309,17 @@ function resolveAgents(systemRows, personalRows, surface) {
3228
3309
 
3229
3310
  // src/setup/skills-lock.ts
3230
3311
  import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3231
- import { homedir as homedir5 } from "os";
3232
- import { join as join6 } from "path";
3312
+ import { join as join7 } from "path";
3233
3313
  var SKILLS_LOCK = ".sechroom-skills.json";
3234
3314
  var DEFAULT_SKILLS_SLUG = "operator-skills";
3235
- function skillsDir(global) {
3236
- return global ? join6(homedir5(), ".claude", "skills") : join6(process.cwd(), ".claude", "skills");
3315
+ function skillsDir(configDir) {
3316
+ return join7(configDir, "skills");
3237
3317
  }
3238
- function agentsDir(global) {
3239
- return global ? join6(homedir5(), ".claude", "agents") : join6(process.cwd(), ".claude", "agents");
3318
+ function agentsDir(configDir) {
3319
+ return join7(configDir, "agents");
3240
3320
  }
3241
3321
  function readSkillsLock(dir) {
3242
- const lockPath = join6(dir, SKILLS_LOCK);
3322
+ const lockPath = join7(dir, SKILLS_LOCK);
3243
3323
  if (!existsSync6(lockPath)) return {};
3244
3324
  try {
3245
3325
  return JSON.parse(readFileSync5(lockPath, "utf8"));
@@ -3249,7 +3329,7 @@ function readSkillsLock(dir) {
3249
3329
  }
3250
3330
  function writeSkillsLock(dir, lock) {
3251
3331
  mkdirSync6(dir, { recursive: true });
3252
- writeFileSync6(join6(dir, SKILLS_LOCK), JSON.stringify(lock, null, 2) + "\n");
3332
+ writeFileSync6(join7(dir, SKILLS_LOCK), JSON.stringify(lock, null, 2) + "\n");
3253
3333
  }
3254
3334
  function recordMaterialisedSkills(dir, slug, skills, meta = {}) {
3255
3335
  const lock = readSkillsLock(dir);
@@ -3276,6 +3356,7 @@ async function fetchFeedRows(cfg, workspaceId) {
3276
3356
  }
3277
3357
  async function maybeOfferSkills(cfg, personalWorkspaceId, opts) {
3278
3358
  const surface = opts.surface ?? "claude-code";
3359
+ const configDir = opts.configDir ?? resolveClaudeTargets({})[0].dir;
3279
3360
  const [systemRows, personalRows] = await Promise.all([
3280
3361
  fetchFeedRows(cfg, SYSTEM_WORKSPACE_ID),
3281
3362
  personalWorkspaceId ? fetchFeedRows(cfg, personalWorkspaceId) : Promise.resolve([])
@@ -3283,8 +3364,8 @@ async function maybeOfferSkills(cfg, personalWorkspaceId, opts) {
3283
3364
  const skills = resolveSkills(systemRows, personalRows, surface);
3284
3365
  const agents = resolveAgents(systemRows, personalRows, surface);
3285
3366
  if (skills.length === 0 && agents.length === 0) return;
3286
- const sDir = skillsDir(true);
3287
- const aDir = agentsDir(true);
3367
+ const sDir = skillsDir(configDir);
3368
+ const aDir = agentsDir(configDir);
3288
3369
  if (opts.dryRun) {
3289
3370
  const lines = (label, items) => items.length === 0 ? "" : `
3290
3371
  Would materialise ${style.bold(String(items.length))} ${label} for ${surface}:
@@ -3309,8 +3390,8 @@ Found ${summary} available to you for ${surface}.
3309
3390
  if (skills.length > 0) {
3310
3391
  const written = [];
3311
3392
  for (const s of skills) {
3312
- mkdirSync7(join7(sDir, s.name), { recursive: true });
3313
- writeFileSync7(join7(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
3393
+ mkdirSync7(join8(sDir, s.name), { recursive: true });
3394
+ writeFileSync7(join8(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
3314
3395
  written.push(s.name);
3315
3396
  }
3316
3397
  recordMaterialisedSkills(sDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -3322,7 +3403,7 @@ Found ${summary} available to you for ${surface}.
3322
3403
  const written = [];
3323
3404
  for (const a of agents) {
3324
3405
  const file = `${a.name}.md`;
3325
- writeFileSync7(join7(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
3406
+ writeFileSync7(join8(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
3326
3407
  written.push(file);
3327
3408
  }
3328
3409
  recordMaterialisedSkills(aDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -3448,7 +3529,7 @@ async function resolveNamespaceChoice(cfg, flag) {
3448
3529
  return picked === GLOBAL_NAMESPACE ? null : picked;
3449
3530
  }
3450
3531
  function registerInit(program2) {
3451
- program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list...>", `clients to wire \u2014 space- or comma-separated (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--namespace <slug>", "MCP namespace for the connection URL (interactive picker if omitted on a TTY; defaults to tenant-global)").option("--refresh", "refresh out-of-date agent-file blocks in place (local edits preserved to .proposed)", false).option("--force", "rewrite agent-file managed blocks, overwriting local edits inside the markers", false).option("--check", "report whether agent files would change and exit (0 = current, 1 = stale/drift/absent); writes nothing", false).addHelpText(
3532
+ program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list...>", `clients to wire \u2014 space- or comma-separated (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--scope <scope>", "install skills/agents/hooks 'global' (config dir / CLAUDE_CONFIG_DIR) or 'project' (<cwd>/.claude) \u2014 default global", "global").option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--namespace <slug>", "MCP namespace for the connection URL (interactive picker if omitted on a TTY; defaults to tenant-global)").option("--refresh", "refresh out-of-date agent-file blocks in place (local edits preserved to .proposed)", false).option("--force", "rewrite agent-file managed blocks, overwriting local edits inside the markers", false).option("--check", "report whether agent files would change and exit (0 = current, 1 = stale/drift/absent); writes nothing", false).addHelpText(
3452
3533
  "after",
3453
3534
  `
3454
3535
  Examples:
@@ -3466,9 +3547,18 @@ Examples:
3466
3547
  "Fetching setup descriptors",
3467
3548
  () => fetchSetup(cfg, namespaceSlug ?? void 0)
3468
3549
  );
3469
- const targets = clientTargets(process.cwd());
3550
+ const g = cmd.optsWithGlobals();
3551
+ let scope;
3552
+ try {
3553
+ scope = resolveScope(opts.scope);
3554
+ } catch (err2) {
3555
+ return fail(err2.message);
3556
+ }
3557
+ const claudeTargets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
3558
+ const codexHomes = resolveCodexHomes({ override: g.codexHome, scope });
3559
+ const targets = clientTargets(process.cwd(), { claudeDir: claudeTargets[0]?.dir, codexHome: codexHomes[0] });
3470
3560
  const keys = resolveClientKeys(opts.client);
3471
- const json = cmd.optsWithGlobals().json;
3561
+ const json = g.json;
3472
3562
  const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
3473
3563
  if (!opts.dryRun && !opts.mcpOnly && !check) {
3474
3564
  await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
@@ -3488,10 +3578,12 @@ Examples:
3488
3578
  }
3489
3579
  summarizeEval(result, mode, Boolean(json), Boolean(opts.dryRun));
3490
3580
  if (!json && !opts.dryRun && !opts.mcpOnly) {
3491
- await maybeOfferSkills(cfg, personalWorkspaceId, { yes: false, dryRun: Boolean(opts.dryRun), surface: "claude-code" });
3581
+ for (const t of claudeTargets) {
3582
+ await maybeOfferSkills(cfg, personalWorkspaceId, { yes: false, dryRun: Boolean(opts.dryRun), surface: "claude-code", configDir: t.dir });
3583
+ }
3492
3584
  }
3493
3585
  if (!json && !opts.dryRun && !opts.mcpOnly) {
3494
- await maybeOfferHooks({ yes: false, dryRun: Boolean(opts.dryRun), cwd: process.cwd() });
3586
+ await maybeOfferHooks({ yes: false, dryRun: Boolean(opts.dryRun), cwd: process.cwd(), scope, claudeConfigDir: g.claudeConfigDir, codexHome: g.codexHome });
3495
3587
  }
3496
3588
  if (json) {
3497
3589
  emit({ dryRun: Boolean(opts.dryRun), clients: result }, true);
@@ -3587,10 +3679,13 @@ ${body}
3587
3679
  });
3588
3680
  }
3589
3681
  async function runClients(clients, cmd, opts) {
3590
- const cfg = resolveConfig(cmd.optsWithGlobals());
3682
+ const g = cmd.optsWithGlobals();
3683
+ const cfg = resolveConfig(g);
3591
3684
  const mode = opts.mode ?? "apply";
3592
3685
  const check = mode === "check";
3593
- const targets = clientTargets(process.cwd());
3686
+ const claudeDir = resolveClaudeTargets({ override: g.claudeConfigDir })[0]?.dir;
3687
+ const codexHome = resolveCodexHomes({ override: g.codexHome })[0];
3688
+ const targets = clientTargets(process.cwd(), { claudeDir, codexHome });
3594
3689
  const keys = resolveClientKeys(clients.join(","));
3595
3690
  const namespaceSlug = opts.mcp ? await resolveNamespaceChoice(cfg, opts.namespace) : null;
3596
3691
  const setupData = await withSpinner(
@@ -3601,7 +3696,7 @@ async function runClients(clients, cmd, opts) {
3601
3696
  if (opts.agentFiles && !opts.dryRun && !check) {
3602
3697
  await maybeOfferCopies(cfg, setupData, targets, keys, personalWorkspaceId, copyChoice(opts));
3603
3698
  }
3604
- const json = cmd.optsWithGlobals().json;
3699
+ const json = g.json;
3605
3700
  const result = [];
3606
3701
  for (const key of keys) {
3607
3702
  const target = targets[key];
@@ -3692,12 +3787,12 @@ Wired to namespace '${slug}'. Restart your AI client (or reload MCP) to pick it
3692
3787
 
3693
3788
  // src/commands/onboard.ts
3694
3789
  import { existsSync as existsSync8 } from "fs";
3695
- import { basename as basename3, join as join9 } from "path";
3790
+ import { basename as basename3, join as join10 } from "path";
3696
3791
 
3697
3792
  // src/commands/fanout.ts
3698
3793
  import { spawnSync } from "child_process";
3699
3794
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
3700
- import { isAbsolute, join as join8, resolve } from "path";
3795
+ import { isAbsolute, join as join9, resolve } from "path";
3701
3796
  var ICON = {
3702
3797
  refresh: "\u21BB",
3703
3798
  bind: "+",
@@ -3717,13 +3812,13 @@ function discoverChildren(root) {
3717
3812
  const out = [];
3718
3813
  for (const name of names.sort()) {
3719
3814
  if (name.startsWith(".") || name === "node_modules") continue;
3720
- const dir = join8(root, name);
3815
+ const dir = join9(root, name);
3721
3816
  try {
3722
3817
  if (!statSync3(dir).isDirectory()) continue;
3723
3818
  } catch {
3724
3819
  continue;
3725
3820
  }
3726
- if (existsSync7(join8(dir, ".git")) || committedBindingPath(dir)) out.push(name);
3821
+ if (existsSync7(join9(dir, ".git")) || committedBindingPath(dir)) out.push(name);
3727
3822
  }
3728
3823
  return out;
3729
3824
  }
@@ -4094,12 +4189,24 @@ async function chooseClients(clientFlag, yes, cwd) {
4094
4189
  );
4095
4190
  return picks.length > 0 ? picks : preselected;
4096
4191
  }
4192
+ async function chooseScope(scopeFlag, yes) {
4193
+ if (scopeFlag != null) return resolveScope(scopeFlag);
4194
+ if (!canPrompt() || yes) return "global";
4195
+ return promptSelect(
4196
+ "Install skills, agents, and hooks globally or just for this project?",
4197
+ [
4198
+ { label: "Globally", value: "global", hint: "~/.claude (or CLAUDE_CONFIG_DIR) \u2014 all projects" },
4199
+ { label: "This project", value: "project", hint: "<repo>/.claude" }
4200
+ ],
4201
+ "global"
4202
+ );
4203
+ }
4097
4204
  async function planRecurseChild(entry, root, client, opts) {
4098
4205
  const dir = resolveChildDir(entry.path, root);
4099
4206
  if (!existsSync8(dir)) {
4100
4207
  return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
4101
4208
  }
4102
- if (existsSync8(join9(dir, ".sechroom.json"))) {
4209
+ if (existsSync8(join10(dir, ".sechroom.json"))) {
4103
4210
  return {
4104
4211
  label: entry.path,
4105
4212
  dir,
@@ -4172,7 +4279,7 @@ This fan-out will pin the same lane in every repo:
4172
4279
  async function runRecurse(cfg, g, opts) {
4173
4280
  const { yes, dryRun, json } = opts;
4174
4281
  const root = process.cwd();
4175
- const manifestPath = join9(root, ".sechroom", "repos.json");
4282
+ const manifestPath = join10(root, ".sechroom", "repos.json");
4176
4283
  const fromManifest = readManifest(manifestPath);
4177
4284
  const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
4178
4285
  const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
@@ -4200,7 +4307,7 @@ async function runRecurse(cfg, g, opts) {
4200
4307
  summarizeFanout(results, { dryRun });
4201
4308
  }
4202
4309
  function registerOnboard(program2) {
4203
- program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--recurse", "orchestration-root mode: onboard every child repo under this dir (auto-discovered, or from ./.sechroom/repos.json) \u2014 refreshes bound repos, prompts a workspace per new one", false).option("--lane <id>", "set the code-lane (substrate source identity) explicitly instead of inferring it; with --recurse it's used for every child repo").option("--design-lane <id>", "set the design-lane explicitly (substrate-authoring identity); with --recurse applies to every child").option("--client <list...>", `clients to wire \u2014 space- or comma-separated (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save the binding (tenant + base URL + workspace) to a committed .sechroom.json in this repo instead of the global config", false).option("--here", "with --local: write the binding at THIS directory even when a parent already carries one \u2014 binds a subtree (e.g. a monorepo's frontend/) to its own workspace", false).option("--workspace <id>", "bind this directory to a workspace (skips the interactive workspace pick)").option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("--refresh", "re-fetch descriptors and refresh any out-of-date managed blocks (local edits preserved to .proposed)", false).option("--force", "rewrite every managed block, overwriting local edits inside the markers (content outside untouched)", false).option("--check", "report whether anything would change and exit (0 = all current, 1 = stale/drift/absent); writes nothing", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
4310
+ program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--recurse", "orchestration-root mode: onboard every child repo under this dir (auto-discovered, or from ./.sechroom/repos.json) \u2014 refreshes bound repos, prompts a workspace per new one", false).option("--lane <id>", "set the code-lane (substrate source identity) explicitly instead of inferring it; with --recurse it's used for every child repo").option("--design-lane <id>", "set the design-lane explicitly (substrate-authoring identity); with --recurse applies to every child").option("--client <list...>", `clients to wire \u2014 space- or comma-separated (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--scope <scope>", "install skills/agents/hooks 'global' (config dir / CLAUDE_CONFIG_DIR) or 'project' (<cwd>/.claude) \u2014 default: prompt, else global").option("--local", "save the binding (tenant + base URL + workspace) to a committed .sechroom.json in this repo instead of the global config", false).option("--here", "with --local: write the binding at THIS directory even when a parent already carries one \u2014 binds a subtree (e.g. a monorepo's frontend/) to its own workspace", false).option("--workspace <id>", "bind this directory to a workspace (skips the interactive workspace pick)").option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("--refresh", "re-fetch descriptors and refresh any out-of-date managed blocks (local edits preserved to .proposed)", false).option("--force", "rewrite every managed block, overwriting local edits inside the markers (content outside untouched)", false).option("--check", "report whether anything would change and exit (0 = all current, 1 = stale/drift/absent); writes nothing", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
4204
4311
  "after",
4205
4312
  `
4206
4313
  Examples:
@@ -4243,6 +4350,13 @@ Examples:
4243
4350
  process.stderr.write(line);
4244
4351
  }
4245
4352
  const wire = await chooseWire(opts, yes);
4353
+ const scope = await chooseScope(opts.scope, yes);
4354
+ const claudeTargets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
4355
+ const codexHomes = resolveCodexHomes({ override: g.codexHome, scope });
4356
+ if (scope === "project" && g.claudeConfigDir && !json) {
4357
+ process.stderr.write(`${style.dim("(--claude-config-dir is ignored for --scope project \u2014 project files are repo-relative)")}
4358
+ `);
4359
+ }
4246
4360
  if (wire === "cli-only") {
4247
4361
  if (json) {
4248
4362
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, clients: [] }, true);
@@ -4250,7 +4364,7 @@ Examples:
4250
4364
  }
4251
4365
  if (!dryRun) {
4252
4366
  await ensureLanePin(cfg, { yes, dryRun, clients: detectInstalledClients(process.cwd()) });
4253
- await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
4367
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd(), scope, claudeConfigDir: g.claudeConfigDir, codexHome: g.codexHome });
4254
4368
  }
4255
4369
  process.stdout.write(
4256
4370
  `
@@ -4263,7 +4377,7 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
4263
4377
  }
4264
4378
  const keys = await chooseClients(opts.client, yes, process.cwd());
4265
4379
  const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
4266
- const targets = clientTargets(process.cwd());
4380
+ const targets = clientTargets(process.cwd(), { claudeDir: claudeTargets[0]?.dir, codexHome: codexHomes[0] });
4267
4381
  const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
4268
4382
  if (!dryRun && !check) {
4269
4383
  await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
@@ -4307,10 +4421,12 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
4307
4421
  await ensureLanePin(cfg, { yes, dryRun, clients: keys });
4308
4422
  }
4309
4423
  if (!json && !dryRun) {
4310
- await maybeOfferSkills(cfg, personalWorkspaceId, { yes, dryRun, surface: "claude-code" });
4424
+ for (const t of claudeTargets) {
4425
+ await maybeOfferSkills(cfg, personalWorkspaceId, { yes, dryRun, surface: "claude-code", configDir: t.dir });
4426
+ }
4311
4427
  }
4312
4428
  if (!json && !dryRun) {
4313
- await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
4429
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd(), scope, claudeConfigDir: g.claudeConfigDir, codexHome: g.codexHome });
4314
4430
  }
4315
4431
  if (json) {
4316
4432
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, eval: evalCounts, clients: result }, true);
@@ -4387,8 +4503,8 @@ async function printStarterPrompt(mode, cfg) {
4387
4503
 
4388
4504
  // src/commands/sweep.ts
4389
4505
  import { existsSync as existsSync9 } from "fs";
4390
- import { dirname as dirname7, join as join10, resolve as resolve2 } from "path";
4391
- var DEFAULT_MANIFEST = join10(".sechroom", "repos.json");
4506
+ import { dirname as dirname7, join as join11, resolve as resolve2 } from "path";
4507
+ var DEFAULT_MANIFEST = join11(".sechroom", "repos.json");
4392
4508
  function planEntry(entry, root) {
4393
4509
  const dir = resolveChildDir(entry.path, root);
4394
4510
  if (!existsSync9(dir)) {
@@ -4485,7 +4601,7 @@ Examples:
4485
4601
  }
4486
4602
 
4487
4603
  // src/commands/skills.ts
4488
- import { join as join11 } from "path";
4604
+ import { join as join12 } from "path";
4489
4605
  import { existsSync as existsSync10, rmSync as rmSync2 } from "fs";
4490
4606
 
4491
4607
  // src/commands/lane.ts
@@ -4569,24 +4685,45 @@ To install/refresh skills, run 'sechroom onboard' (it offers to materialise them
4569
4685
  console.log(` ${i.bundleSlug ?? i.BundleSlug}@${i.bundleVersion ?? i.BundleVersion ?? "?"}${tag}`);
4570
4686
  });
4571
4687
  });
4572
- skills.command("clean [slug]").description(`Remove skill files materialised by onboard (default ${DEFAULT_SKILLS_SLUG})`).option("--local", "clean ./.claude/skills instead of ~/.claude/skills").option("--json", "machine output").action(async (slugArg, opts) => {
4688
+ skills.command("clean [slug]").description(`Remove skill files materialised by onboard (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(async (slugArg, opts, cmd) => {
4689
+ const g = cmd.optsWithGlobals();
4573
4690
  const slug = slugArg || DEFAULT_SKILLS_SLUG;
4574
- const dir = skillsDir(!opts.local);
4575
- const lock = readSkillsLock(dir);
4576
- const entry = lock[slug];
4577
- if (!entry) fail(`No materialised skills recorded for '${slug}' in ${join11(dir, SKILLS_LOCK)}.`);
4578
- const removed = [];
4579
- for (const name of entry.skills) {
4580
- const skillPath = join11(dir, name);
4581
- if (existsSync10(skillPath)) {
4582
- rmSync2(skillPath, { recursive: true, force: true });
4583
- removed.push(name);
4691
+ let scope;
4692
+ try {
4693
+ scope = opts.local ? "project" : resolveScope(opts.scope);
4694
+ } catch (err2) {
4695
+ return fail(err2.message);
4696
+ }
4697
+ const targets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
4698
+ const cleaned = [];
4699
+ const missing = [];
4700
+ for (const t of targets) {
4701
+ const dir = skillsDir(t.dir);
4702
+ const lock = readSkillsLock(dir);
4703
+ const entry = lock[slug];
4704
+ if (!entry) {
4705
+ missing.push(join12(dir, SKILLS_LOCK));
4706
+ continue;
4584
4707
  }
4708
+ const removed = [];
4709
+ for (const name of entry.skills) {
4710
+ const skillPath = join12(dir, name);
4711
+ if (existsSync10(skillPath)) {
4712
+ rmSync2(skillPath, { recursive: true, force: true });
4713
+ removed.push(name);
4714
+ }
4715
+ }
4716
+ delete lock[slug];
4717
+ writeSkillsLock(dir, lock);
4718
+ cleaned.push({ dir, removed });
4719
+ }
4720
+ if (cleaned.length === 0) {
4721
+ return fail(`No materialised skills recorded for '${slug}' in ${missing.join(", ")}.`);
4722
+ }
4723
+ if (opts.json) return emit({ slug, cleaned, missing }, true);
4724
+ for (const { dir, removed } of cleaned) {
4725
+ console.log(style.green(`Removed ${removed.length} skill(s) for ${slug} from ${dir}`));
4585
4726
  }
4586
- delete lock[slug];
4587
- writeSkillsLock(dir, lock);
4588
- if (opts.json) return emit({ slug, removed, dir }, true);
4589
- console.log(style.green(`Removed ${removed.length} skill(s) for ${slug} from ${dir}`));
4590
4727
  });
4591
4728
  skills.command("set-lane").description("Alias of `sechroom lane set` (kept for back-compat) \u2014 write this checkout's lane pin").option("--code-lane <id>", "code-surface lane id (e.g. claude-code-chris)").option("--design-lane <id>", "design / substrate-authoring lane id (e.g. claude-design-chris)").option("--json", "machine output").action(
4592
4729
  (opts, cmd) => setLane({
@@ -4659,23 +4796,23 @@ To install/refresh skills, run 'sechroom onboard' (it offers to materialise them
4659
4796
  }
4660
4797
 
4661
4798
  // src/commands/reset.ts
4662
- import { homedir as homedir6 } from "os";
4663
- import { join as join12 } from "path";
4799
+ import { homedir as homedir4 } from "os";
4800
+ import { join as join13 } from "path";
4664
4801
  import { existsSync as existsSync11, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
4665
4802
  var SKILLS_LOCK2 = ".sechroom-skills.json";
4666
- var localSkillsDir = () => join12(process.cwd(), ".claude", "skills");
4667
- var globalSkillsDir = () => join12(homedir6(), ".claude", "skills");
4668
- var localAgentsDir = () => join12(process.cwd(), ".claude", "agents");
4669
- var globalAgentsDir = () => join12(homedir6(), ".claude", "agents");
4803
+ var localSkillsDir = () => join13(process.cwd(), ".claude", "skills");
4804
+ var globalSkillsDir = () => join13(homedir4(), ".claude", "skills");
4805
+ var localAgentsDir = () => join13(process.cwd(), ".claude", "agents");
4806
+ var globalAgentsDir = () => join13(homedir4(), ".claude", "agents");
4670
4807
  function removeMaterialisedSkills(dir) {
4671
4808
  const removed = [];
4672
- const lockPath = join12(dir, SKILLS_LOCK2);
4809
+ const lockPath = join13(dir, SKILLS_LOCK2);
4673
4810
  if (!existsSync11(lockPath)) return removed;
4674
4811
  try {
4675
4812
  const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
4676
4813
  for (const entry of Object.values(lock)) {
4677
4814
  for (const name of entry.skills ?? []) {
4678
- const p = join12(dir, name);
4815
+ const p = join13(dir, name);
4679
4816
  if (existsSync11(p)) {
4680
4817
  rmSync3(p, { recursive: true, force: true });
4681
4818
  removed.push(p);
@@ -4721,17 +4858,17 @@ function registerReset(program2) {
4721
4858
  }
4722
4859
  }
4723
4860
  const removed = [];
4724
- const stateDir = join12(process.cwd(), ".sechroom");
4861
+ const stateDir = join13(process.cwd(), ".sechroom");
4725
4862
  if (existsSync11(stateDir)) {
4726
4863
  rmSync3(stateDir, { recursive: true, force: true });
4727
4864
  removed.push(stateDir);
4728
4865
  }
4729
- const legacyCfg = join12(process.cwd(), ".sechroom.json");
4866
+ const legacyCfg = join13(process.cwd(), ".sechroom.json");
4730
4867
  if (existsSync11(legacyCfg)) {
4731
4868
  rmSync3(legacyCfg, { force: true });
4732
4869
  removed.push(legacyCfg);
4733
4870
  }
4734
- const legacySem = join12(process.cwd(), ".sem");
4871
+ const legacySem = join13(process.cwd(), ".sem");
4735
4872
  if (existsSync11(legacySem)) {
4736
4873
  rmSync3(legacySem, { force: true });
4737
4874
  removed.push(legacySem);
@@ -4769,7 +4906,7 @@ function resolveVersion() {
4769
4906
  }
4770
4907
  }
4771
4908
  var program = new Command();
4772
- program.name("sechroom").description("Sechroom CLI \u2014 thin generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.").version(resolveVersion()).option("--base-url <url>", "API base URL (overrides config / SECHROOM_BASE_URL)").option("--tenant <tenant>", "Tenant id (required by the API; overrides config / SECHROOM_TENANT)").option("--binding <name>", "Named workspace binding from .sechroom.json `workspaces` (overrides path auto-selection / SECHROOM_BINDING)").option("--json", "Emit compact JSON (for scripts and agents)", false);
4909
+ program.name("sechroom").description("Sechroom CLI \u2014 thin generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.").version(resolveVersion()).option("--base-url <url>", "API base URL (overrides config / SECHROOM_BASE_URL)").option("--tenant <tenant>", "Tenant id (required by the API; overrides config / SECHROOM_TENANT)").option("--binding <name>", "Named workspace binding from .sechroom.json `workspaces` (overrides path auto-selection / SECHROOM_BINDING)").option("--claude-config-dir <dirs>", "Claude config dir(s), comma-separated (overrides CLAUDE_CONFIG_DIR / ~/.claude)").option("--codex-home <dir>", "Codex home (overrides CODEX_HOME / ~/.codex)").option("--json", "Emit compact JSON (for scripts and agents)", false);
4773
4910
  program.addHelpText(
4774
4911
  "after",
4775
4912
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.32",
3
+ "version": "2026.6.33",
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",