@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.
- package/dist/index.js +237 -100
- 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
|
|
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 {
|
|
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 {
|
|
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
|
|
2125
|
+
return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2080
2126
|
case "win32":
|
|
2081
|
-
return
|
|
2127
|
+
return join4(process.env.APPDATA ?? join4(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
2082
2128
|
default:
|
|
2083
|
-
return
|
|
2129
|
+
return join4(home, ".config", "Claude", "claude_desktop_config.json");
|
|
2084
2130
|
}
|
|
2085
2131
|
}
|
|
2086
|
-
function clientTargets(cwd) {
|
|
2087
|
-
const home =
|
|
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:
|
|
2093
|
-
instruction: { surfaceKey: "claude-code", path:
|
|
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:
|
|
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:
|
|
2105
|
-
instruction: { surfaceKey: "chatgpt", path:
|
|
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:
|
|
2111
|
-
instruction: { surfaceKey: "chatgpt", path:
|
|
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 =
|
|
2166
|
+
const home = homedir3();
|
|
2119
2167
|
const detected = [];
|
|
2120
|
-
if (
|
|
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 (
|
|
2123
|
-
if (existsSync3(
|
|
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 =
|
|
2199
|
+
var INTENT_FILE = join5(".sechroom", "continuity.json");
|
|
2152
2200
|
function resolveIntentPath(start) {
|
|
2153
2201
|
let dir = start;
|
|
2154
2202
|
for (; ; ) {
|
|
2155
|
-
const candidate =
|
|
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) :
|
|
2209
|
-
return
|
|
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 =
|
|
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(
|
|
2405
|
-
const featureFlag = installCodexFeatureFlag(
|
|
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(
|
|
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("--
|
|
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
|
|
2520
|
-
for (const
|
|
2521
|
-
|
|
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) ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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(
|
|
3236
|
-
return
|
|
3315
|
+
function skillsDir(configDir) {
|
|
3316
|
+
return join7(configDir, "skills");
|
|
3237
3317
|
}
|
|
3238
|
-
function agentsDir(
|
|
3239
|
-
return
|
|
3318
|
+
function agentsDir(configDir) {
|
|
3319
|
+
return join7(configDir, "agents");
|
|
3240
3320
|
}
|
|
3241
3321
|
function readSkillsLock(dir) {
|
|
3242
|
-
const lockPath =
|
|
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(
|
|
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(
|
|
3287
|
-
const aDir = agentsDir(
|
|
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(
|
|
3313
|
-
writeFileSync7(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4391
|
-
var DEFAULT_MANIFEST =
|
|
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
|
|
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("--
|
|
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
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
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
|
|
4663
|
-
import { join as
|
|
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 = () =>
|
|
4667
|
-
var globalSkillsDir = () =>
|
|
4668
|
-
var localAgentsDir = () =>
|
|
4669
|
-
var globalAgentsDir = () =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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