@sechroom/cli 2026.6.19 → 2026.6.20

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 +78 -24
  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) {
@@ -2854,6 +2865,43 @@ async function runClients(clients, cmd, opts) {
2854
2865
  process.stdout.write(opts.dryRun ? "\n(dry run \u2014 nothing written)\n" : "\nDone.\n");
2855
2866
  }
2856
2867
 
2868
+ // src/setup/hooks-offer.ts
2869
+ import { homedir as homedir5 } from "os";
2870
+ async function maybeOfferHooks(opts) {
2871
+ if (opts.dryRun) return;
2872
+ const cwd = opts.cwd ?? process.cwd();
2873
+ const surfaces = detectHookSurfaces(cwd);
2874
+ if (surfaces.length === 0) return;
2875
+ const names = surfaces.map((s) => HOOK_SURFACE_LABEL[s]).join(" + ");
2876
+ process.stderr.write(
2877
+ `
2878
+ Sechroom can wire continuity lifecycle hooks into ${style.bold(names)} so your agent
2879
+ auto-resumes where you left off and checkpoints working state before compacting.
2880
+ `
2881
+ );
2882
+ const install = opts.yes ? true : canPrompt() ? await promptYesNo(`Install the continuity hooks for ${names}?`) : false;
2883
+ if (!install) return;
2884
+ try {
2885
+ const installed = installHookSurfaces(surfaces, { dryRun: false, cwd, home: homedir5() });
2886
+ let changed = false;
2887
+ for (const { surface, results } of installed) {
2888
+ for (const r of results) {
2889
+ if (r.status !== "current") changed = true;
2890
+ const verb = r.status === "current" ? "already configured" : r.status === "created" ? "created" : "updated";
2891
+ process.stderr.write(`${style.green("\u2713")} ${HOOK_SURFACE_LABEL[surface]}: ${r.path} (${verb})
2892
+ `);
2893
+ }
2894
+ }
2895
+ if (changed) {
2896
+ process.stderr.write(`${style.dim("Restart (or reload) your agent for the hooks to take effect.")}
2897
+ `);
2898
+ }
2899
+ } catch (err2) {
2900
+ process.stderr.write(`${style.dim(`(skipped hook install: ${err2.message})`)}
2901
+ `);
2902
+ }
2903
+ }
2904
+
2857
2905
  // src/commands/onboard.ts
2858
2906
  var DEFAULT_BASE_URL2 = "https://app.sechroom.ai/api";
2859
2907
  function systemTimezone() {
@@ -3145,7 +3193,10 @@ Examples:
3145
3193
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, clients: [] }, true);
3146
3194
  return;
3147
3195
  }
3148
- if (!dryRun) await ensureLanePin(cfg, { yes, dryRun, clients: detectInstalledClients(process.cwd()) });
3196
+ if (!dryRun) {
3197
+ await ensureLanePin(cfg, { yes, dryRun, clients: detectInstalledClients(process.cwd()) });
3198
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
3199
+ }
3149
3200
  process.stdout.write(
3150
3201
  `
3151
3202
  ${style.bold("Done.")} The CLI is configured for ${style.cyan(cfg.tenant)} \u2014 no AI-client files written.
@@ -3203,6 +3254,9 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
3203
3254
  if (!json && !dryRun) {
3204
3255
  await maybeOfferSkills(cfg, personalWorkspaceId, { yes, dryRun, surface: "claude-code" });
3205
3256
  }
3257
+ if (!json && !dryRun) {
3258
+ await maybeOfferHooks({ yes, dryRun, cwd: process.cwd() });
3259
+ }
3206
3260
  if (json) {
3207
3261
  emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, workspaceId: cfg.workspaceId ?? null, timezone: tz, wire, eval: evalCounts, clients: result }, true);
3208
3262
  return;
@@ -3275,14 +3329,14 @@ ${style.bold("Next:")} paste this into your AI agent to get going \u2014
3275
3329
  }
3276
3330
 
3277
3331
  // src/commands/skills.ts
3278
- import { homedir as homedir5 } from "os";
3332
+ import { homedir as homedir6 } from "os";
3279
3333
  import { join as join6 } from "path";
3280
3334
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3281
3335
  var DEFAULT_SLUG = "operator-skills";
3282
3336
  var ROLE_TAGS = ["sechroom:role:skill-template", "role:skill-template"];
3283
3337
  var LOCK = ".sechroom-skills.json";
3284
3338
  function skillsDir(global) {
3285
- return global ? join6(homedir5(), ".claude", "skills") : join6(process.cwd(), ".claude", "skills");
3339
+ return global ? join6(homedir6(), ".claude", "skills") : join6(process.cwd(), ".claude", "skills");
3286
3340
  }
3287
3341
  function tagValue2(tags, prefix) {
3288
3342
  return (tags ?? []).find((t) => t.startsWith(prefix))?.slice(prefix.length);
@@ -3493,12 +3547,12 @@ Examples:
3493
3547
  }
3494
3548
 
3495
3549
  // src/commands/reset.ts
3496
- import { homedir as homedir6 } from "os";
3550
+ import { homedir as homedir7 } from "os";
3497
3551
  import { join as join7 } from "path";
3498
3552
  import { existsSync as existsSync7, readFileSync as readFileSync6, rmSync as rmSync3 } from "fs";
3499
3553
  var SKILLS_LOCK = ".sechroom-skills.json";
3500
3554
  var localSkillsDir = () => join7(process.cwd(), ".claude", "skills");
3501
- var globalSkillsDir = () => join7(homedir6(), ".claude", "skills");
3555
+ var globalSkillsDir = () => join7(homedir7(), ".claude", "skills");
3502
3556
  function removeMaterialisedSkills(dir) {
3503
3557
  const removed = [];
3504
3558
  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.20",
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",