@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.
- package/dist/index.js +78 -24
- 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
|
|
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
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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