@sechroom/cli 2026.6.19 → 2026.6.21

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 +83 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2016,10 +2016,7 @@ function resolveSurfaces(surface, cwd) {
2016
2016
  if (surface === "codex") return ["codex"];
2017
2017
  if (surface === "both") return ["claude", "codex"];
2018
2018
  if (surface) throw new Error(`--surface must be one of claude | codex | both (got '${surface}')`);
2019
- const detected = detectInstalledClients(cwd);
2020
- const surfaces = [];
2021
- if (detected.includes("claude-code")) surfaces.push("claude");
2022
- if (detected.includes("codex")) surfaces.push("codex");
2019
+ const surfaces = detectHookSurfaces(cwd);
2023
2020
  return surfaces.length > 0 ? surfaces : ["claude", "codex"];
2024
2021
  }
2025
2022
  function describe(result, dryRun) {
@@ -2027,6 +2024,31 @@ function describe(result, dryRun) {
2027
2024
  const verb = dryRun ? "would" : result.status === "created" ? "created" : "updated";
2028
2025
  return ` \u2713 ${result.path} (${dryRun ? `${verb} ${result.status === "created" ? "create" : "update"}` : verb})`;
2029
2026
  }
2027
+ var HOOK_SURFACE_LABEL = {
2028
+ claude: "Claude Code",
2029
+ codex: "Codex"
2030
+ };
2031
+ function installHookSurfaces(surfaces, opts) {
2032
+ const out = [];
2033
+ for (const surface of surfaces) {
2034
+ if (surface === "claude") {
2035
+ const path = opts.local ? join4(opts.cwd, ".claude", "settings.json") : join4(opts.home, ".claude", "settings.json");
2036
+ out.push({ surface, results: [installHooksJson(path, opts.dryRun)] });
2037
+ } else {
2038
+ const hooksJson = installHooksJson(join4(opts.home, ".codex", "hooks.json"), opts.dryRun);
2039
+ const featureFlag = installCodexFeatureFlag(join4(opts.home, ".codex", "config.toml"), opts.dryRun);
2040
+ out.push({ surface, results: [hooksJson, featureFlag] });
2041
+ }
2042
+ }
2043
+ return out;
2044
+ }
2045
+ function detectHookSurfaces(cwd) {
2046
+ const detected = detectInstalledClients(cwd);
2047
+ const surfaces = [];
2048
+ if (detected.includes("claude-code")) surfaces.push("claude");
2049
+ if (detected.includes("codex")) surfaces.push("codex");
2050
+ return surfaces;
2051
+ }
2030
2052
  function registerHook(program2) {
2031
2053
  const hook = program2.command("hook").description("Agent-lifecycle hook adapter (Claude Code / Codex) \u2014 bridges hooks to continuity");
2032
2054
  hook.addHelpText(
@@ -2115,26 +2137,15 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
2115
2137
  `);
2116
2138
  return process.exit(2);
2117
2139
  }
2118
- const home = homedir3();
2119
2140
  const results = [];
2120
2141
  try {
2121
- for (const surface of surfaces) {
2122
- if (surface === "claude") {
2123
- const path = opts.local ? join4(cwd, ".claude", "settings.json") : join4(home, ".claude", "settings.json");
2124
- process.stdout.write(`Claude Code:
2142
+ const installed = installHookSurfaces(surfaces, { dryRun, local: opts.local, cwd, home: homedir3() });
2143
+ for (const { surface, results: surfaceResults } of installed) {
2144
+ process.stdout.write(`${HOOK_SURFACE_LABEL[surface]}:
2125
2145
  `);
2126
- const r = installHooksJson(path, dryRun);
2146
+ for (const r of surfaceResults) {
2127
2147
  results.push(r);
2128
2148
  process.stdout.write(describe(r, dryRun) + "\n");
2129
- } else {
2130
- process.stdout.write(`Codex:
2131
- `);
2132
- const hooksJson = installHooksJson(join4(home, ".codex", "hooks.json"), dryRun);
2133
- results.push(hooksJson);
2134
- process.stdout.write(describe(hooksJson, dryRun) + "\n");
2135
- const featureFlag = installCodexFeatureFlag(join4(home, ".codex", "config.toml"), dryRun);
2136
- results.push(featureFlag);
2137
- process.stdout.write(describe(featureFlag, dryRun) + "\n");
2138
2149
  }
2139
2150
  }
2140
2151
  } catch (err2) {
@@ -2577,9 +2588,46 @@ async function applyClient(cfg, setup, target, opts) {
2577
2588
  return actions;
2578
2589
  }
2579
2590
 
2591
+ // src/setup/hooks-offer.ts
2592
+ import { homedir as homedir4 } from "os";
2593
+ async function maybeOfferHooks(opts) {
2594
+ if (opts.dryRun) return;
2595
+ const cwd = opts.cwd ?? process.cwd();
2596
+ const surfaces = detectHookSurfaces(cwd);
2597
+ if (surfaces.length === 0) return;
2598
+ const names = surfaces.map((s) => HOOK_SURFACE_LABEL[s]).join(" + ");
2599
+ process.stderr.write(
2600
+ `
2601
+ Sechroom can wire continuity lifecycle hooks into ${style.bold(names)} so your agent
2602
+ auto-resumes where you left off and checkpoints working state before compacting.
2603
+ `
2604
+ );
2605
+ const install = opts.yes ? true : canPrompt() ? await promptYesNo(`Install the continuity hooks for ${names}?`) : false;
2606
+ if (!install) return;
2607
+ try {
2608
+ const installed = installHookSurfaces(surfaces, { dryRun: false, cwd, home: homedir4() });
2609
+ let changed = false;
2610
+ for (const { surface, results } of installed) {
2611
+ for (const r of results) {
2612
+ if (r.status !== "current") changed = true;
2613
+ const verb = r.status === "current" ? "already configured" : r.status === "created" ? "created" : "updated";
2614
+ process.stderr.write(`${style.green("\u2713")} ${HOOK_SURFACE_LABEL[surface]}: ${r.path} (${verb})
2615
+ `);
2616
+ }
2617
+ }
2618
+ if (changed) {
2619
+ process.stderr.write(`${style.dim("Restart (or reload) your agent for the hooks to take effect.")}
2620
+ `);
2621
+ }
2622
+ } catch (err2) {
2623
+ process.stderr.write(`${style.dim(`(skipped hook install: ${err2.message})`)}
2624
+ `);
2625
+ }
2626
+ }
2627
+
2580
2628
  // src/setup/skills-offer.ts
2581
2629
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
2582
- import { homedir as homedir4 } from "os";
2630
+ import { homedir as homedir5 } from "os";
2583
2631
  import { join as join5 } from "path";
2584
2632
 
2585
2633
  // src/setup/lane-pin.ts
@@ -2695,7 +2743,7 @@ async function maybeOfferSkills(cfg, personalWorkspaceId, opts) {
2695
2743
  Found ${style.bold(String(names.length))} operator skill(s) installed in your workspace: ${names.join(", ")}.
2696
2744
  `
2697
2745
  );
2698
- const dir = join5(homedir4(), ".claude", "skills");
2746
+ const dir = join5(homedir5(), ".claude", "skills");
2699
2747
  const materialise = opts.yes ? true : canPrompt() ? await promptYesNo(`Write them to ${dir}/ so ${surface} can use them?`) : false;
2700
2748
  if (!materialise) return;
2701
2749
  const written = [];
@@ -2799,6 +2847,9 @@ Examples:
2799
2847
  if (!json && !opts.dryRun && !opts.mcpOnly) {
2800
2848
  await maybeOfferSkills(cfg, personalWorkspaceId, { yes: false, dryRun: Boolean(opts.dryRun), surface: "claude-code" });
2801
2849
  }
2850
+ if (!json && !opts.dryRun && !opts.mcpOnly) {
2851
+ await maybeOfferHooks({ yes: false, dryRun: Boolean(opts.dryRun), cwd: process.cwd() });
2852
+ }
2802
2853
  if (json) {
2803
2854
  emit({ dryRun: Boolean(opts.dryRun), clients: result }, true);
2804
2855
  return;
@@ -3145,7 +3196,10 @@ Examples:
3145
3196
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, clients: [] }, true);
3146
3197
  return;
3147
3198
  }
3148
- if (!dryRun) await ensureLanePin(cfg, { yes, dryRun, clients: detectInstalledClients(process.cwd()) });
3199
+ if (!dryRun) {
3200
+ await ensureLanePin(cfg, { yes, dryRun, clients: detectInstalledClients(process.cwd()) });
3201
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
3202
+ }
3149
3203
  process.stdout.write(
3150
3204
  `
3151
3205
  ${style.bold("Done.")} The CLI is configured for ${style.cyan(cfg.tenant)} \u2014 no AI-client files written.
@@ -3203,6 +3257,9 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
3203
3257
  if (!json && !dryRun) {
3204
3258
  await maybeOfferSkills(cfg, personalWorkspaceId, { yes, dryRun, surface: "claude-code" });
3205
3259
  }
3260
+ if (!json && !dryRun) {
3261
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
3262
+ }
3206
3263
  if (json) {
3207
3264
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, eval: evalCounts, clients: result }, true);
3208
3265
  return;
@@ -3275,14 +3332,14 @@ ${style.bold("Next:")} paste this into your AI agent to get going \u2014
3275
3332
  }
3276
3333
 
3277
3334
  // src/commands/skills.ts
3278
- import { homedir as homedir5 } from "os";
3335
+ import { homedir as homedir6 } from "os";
3279
3336
  import { join as join6 } from "path";
3280
3337
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3281
3338
  var DEFAULT_SLUG = "operator-skills";
3282
3339
  var ROLE_TAGS = ["sechroom:role:skill-template", "role:skill-template"];
3283
3340
  var LOCK = ".sechroom-skills.json";
3284
3341
  function skillsDir(global) {
3285
- return global ? join6(homedir5(), ".claude", "skills") : join6(process.cwd(), ".claude", "skills");
3342
+ return global ? join6(homedir6(), ".claude", "skills") : join6(process.cwd(), ".claude", "skills");
3286
3343
  }
3287
3344
  function tagValue2(tags, prefix) {
3288
3345
  return (tags ?? []).find((t) => t.startsWith(prefix))?.slice(prefix.length);
@@ -3493,12 +3550,12 @@ Examples:
3493
3550
  }
3494
3551
 
3495
3552
  // src/commands/reset.ts
3496
- import { homedir as homedir6 } from "os";
3553
+ import { homedir as homedir7 } from "os";
3497
3554
  import { join as join7 } from "path";
3498
3555
  import { existsSync as existsSync7, readFileSync as readFileSync6, rmSync as rmSync3 } from "fs";
3499
3556
  var SKILLS_LOCK = ".sechroom-skills.json";
3500
3557
  var localSkillsDir = () => join7(process.cwd(), ".claude", "skills");
3501
- var globalSkillsDir = () => join7(homedir6(), ".claude", "skills");
3558
+ var globalSkillsDir = () => join7(homedir7(), ".claude", "skills");
3502
3559
  function removeMaterialisedSkills(dir) {
3503
3560
  const removed = [];
3504
3561
  const lockPath = join7(dir, SKILLS_LOCK);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.19",
3
+ "version": "2026.6.21",
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",