@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.
- package/dist/index.js +83 -26
- 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) {
|
|
@@ -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
|
|
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(
|
|
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)
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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