@sechroom/cli 2026.6.140-rc.176770e1 → 2026.6.142-rc.770caa0e
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 +142 -173
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2859,9 +2859,8 @@ auto-resumes where you left off and checkpoints working state before compacting.
|
|
|
2859
2859
|
}
|
|
2860
2860
|
|
|
2861
2861
|
// src/setup/skills-offer.ts
|
|
2862
|
-
import { mkdirSync as
|
|
2863
|
-
import {
|
|
2864
|
-
import { join as join5 } from "path";
|
|
2862
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
2863
|
+
import { join as join6 } from "path";
|
|
2865
2864
|
|
|
2866
2865
|
// src/setup/lane-pin.ts
|
|
2867
2866
|
var CODE_LANE_PREFIX_BY_CLIENT = {
|
|
@@ -2945,7 +2944,10 @@ I can pin this checkout's lane so operator skills + the continuity hook resolve
|
|
|
2945
2944
|
|
|
2946
2945
|
// src/setup/skill-resolution.ts
|
|
2947
2946
|
var SYSTEM_WORKSPACE_ID = "wsp_system";
|
|
2948
|
-
var
|
|
2947
|
+
var SKILL_ROLE_TAG = "sechroom:role:skill-template";
|
|
2948
|
+
var SKILL_NAME_PREFIX = "skill:";
|
|
2949
|
+
var AGENT_ROLE_TAG = "sechroom:role:agent-template";
|
|
2950
|
+
var AGENT_NAME_PREFIX = "agent:";
|
|
2949
2951
|
function tagsOf(row) {
|
|
2950
2952
|
const m = row?.item ?? row;
|
|
2951
2953
|
return m?.tags ?? m?.Tags ?? [];
|
|
@@ -2957,29 +2959,62 @@ function bodyOf(row) {
|
|
|
2957
2959
|
const m = row?.item ?? row;
|
|
2958
2960
|
return m?.text ?? m?.Text ?? "";
|
|
2959
2961
|
}
|
|
2960
|
-
function
|
|
2962
|
+
function entriesFromRows(rows, surface, source, roleTag, namePrefix) {
|
|
2961
2963
|
const out = /* @__PURE__ */ new Map();
|
|
2962
2964
|
for (const row of rows ?? []) {
|
|
2963
2965
|
const tags = tagsOf(row);
|
|
2964
|
-
if (!tags.includes(
|
|
2966
|
+
if (!tags.includes(roleTag)) continue;
|
|
2965
2967
|
if (tagValue(tags, "target:") !== surface) continue;
|
|
2966
|
-
const name = tagValue(tags,
|
|
2968
|
+
const name = tagValue(tags, namePrefix);
|
|
2967
2969
|
if (!name) continue;
|
|
2968
2970
|
out.set(name, { name, body: bodyOf(row), source });
|
|
2969
2971
|
}
|
|
2970
2972
|
return out;
|
|
2971
2973
|
}
|
|
2972
|
-
function
|
|
2973
|
-
const merged =
|
|
2974
|
-
for (const [name,
|
|
2975
|
-
|
|
2976
|
-
surface,
|
|
2977
|
-
"personal"
|
|
2978
|
-
)) {
|
|
2979
|
-
merged.set(name, skill);
|
|
2974
|
+
function resolveByRole(systemRows, personalRows, surface, roleTag, namePrefix) {
|
|
2975
|
+
const merged = entriesFromRows(systemRows, surface, "system", roleTag, namePrefix);
|
|
2976
|
+
for (const [name, item] of entriesFromRows(personalRows, surface, "personal", roleTag, namePrefix)) {
|
|
2977
|
+
merged.set(name, item);
|
|
2980
2978
|
}
|
|
2981
2979
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
2982
2980
|
}
|
|
2981
|
+
function resolveSkills(systemRows, personalRows, surface) {
|
|
2982
|
+
return resolveByRole(systemRows, personalRows, surface, SKILL_ROLE_TAG, SKILL_NAME_PREFIX);
|
|
2983
|
+
}
|
|
2984
|
+
function resolveAgents(systemRows, personalRows, surface) {
|
|
2985
|
+
return resolveByRole(systemRows, personalRows, surface, AGENT_ROLE_TAG, AGENT_NAME_PREFIX);
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// src/setup/skills-lock.ts
|
|
2989
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
2990
|
+
import { homedir as homedir5 } from "os";
|
|
2991
|
+
import { join as join5 } from "path";
|
|
2992
|
+
var SKILLS_LOCK = ".sechroom-skills.json";
|
|
2993
|
+
var DEFAULT_SKILLS_SLUG = "operator-skills";
|
|
2994
|
+
function skillsDir(global) {
|
|
2995
|
+
return global ? join5(homedir5(), ".claude", "skills") : join5(process.cwd(), ".claude", "skills");
|
|
2996
|
+
}
|
|
2997
|
+
function agentsDir(global) {
|
|
2998
|
+
return global ? join5(homedir5(), ".claude", "agents") : join5(process.cwd(), ".claude", "agents");
|
|
2999
|
+
}
|
|
3000
|
+
function readSkillsLock(dir) {
|
|
3001
|
+
const lockPath = join5(dir, SKILLS_LOCK);
|
|
3002
|
+
if (!existsSync6(lockPath)) return {};
|
|
3003
|
+
try {
|
|
3004
|
+
return JSON.parse(readFileSync5(lockPath, "utf8"));
|
|
3005
|
+
} catch {
|
|
3006
|
+
return {};
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
function writeSkillsLock(dir, lock) {
|
|
3010
|
+
mkdirSync5(dir, { recursive: true });
|
|
3011
|
+
writeFileSync5(join5(dir, SKILLS_LOCK), JSON.stringify(lock, null, 2) + "\n");
|
|
3012
|
+
}
|
|
3013
|
+
function recordMaterialisedSkills(dir, slug, skills, meta = {}) {
|
|
3014
|
+
const lock = readSkillsLock(dir);
|
|
3015
|
+
lock[slug] = { surface: meta.surface, skills: [...skills].sort() };
|
|
3016
|
+
writeSkillsLock(dir, lock);
|
|
3017
|
+
}
|
|
2983
3018
|
|
|
2984
3019
|
// src/setup/skills-offer.ts
|
|
2985
3020
|
async function fetchFeedRows(cfg, workspaceId) {
|
|
@@ -3004,38 +3039,55 @@ async function maybeOfferSkills(cfg, personalWorkspaceId, opts) {
|
|
|
3004
3039
|
fetchFeedRows(cfg, SYSTEM_WORKSPACE_ID),
|
|
3005
3040
|
personalWorkspaceId ? fetchFeedRows(cfg, personalWorkspaceId) : Promise.resolve([])
|
|
3006
3041
|
]);
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3042
|
+
const skills = resolveSkills(systemRows, personalRows, surface);
|
|
3043
|
+
const agents = resolveAgents(systemRows, personalRows, surface);
|
|
3044
|
+
if (skills.length === 0 && agents.length === 0) return;
|
|
3045
|
+
const sDir = skillsDir(true);
|
|
3046
|
+
const aDir = agentsDir(true);
|
|
3009
3047
|
if (opts.dryRun) {
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
);
|
|
3048
|
+
const lines = (label, items) => items.length === 0 ? "" : `
|
|
3049
|
+
Would materialise ${style.bold(String(items.length))} ${label} for ${surface}:
|
|
3050
|
+
` + items.map((s) => ` ${s.name} ${style.dim(`[${s.source}]`)}`).join("\n") + "\n";
|
|
3051
|
+
process.stderr.write(lines("operator skill(s)", skills) + lines("agent(s)", agents));
|
|
3015
3052
|
return;
|
|
3016
3053
|
}
|
|
3017
|
-
const
|
|
3018
|
-
|
|
3019
|
-
`
|
|
3020
|
-
|
|
3021
|
-
`
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3054
|
+
const summary = [
|
|
3055
|
+
skills.length > 0 ? `${style.bold(String(skills.length))} skill(s)` : "",
|
|
3056
|
+
agents.length > 0 ? `${style.bold(String(agents.length))} agent(s)` : ""
|
|
3057
|
+
].filter(Boolean).join(" + ");
|
|
3058
|
+
process.stderr.write(`
|
|
3059
|
+
Found ${summary} available to you for ${surface}.
|
|
3060
|
+
`);
|
|
3061
|
+
if (skills.length > 0) process.stderr.write(` skills: ${skills.map((s) => s.name).join(", ")}
|
|
3062
|
+
`);
|
|
3063
|
+
if (agents.length > 0) process.stderr.write(` agents: ${agents.map((a) => a.name).join(", ")}
|
|
3064
|
+
`);
|
|
3065
|
+
const dest = [skills.length > 0 ? `${sDir}/` : "", agents.length > 0 ? `${aDir}/` : ""].filter(Boolean).join(" + ");
|
|
3066
|
+
const materialise = opts.yes ? true : canPrompt() ? await promptYesNo(`Write them to ${dest} so ${surface} can use them?`) : false;
|
|
3025
3067
|
if (!materialise) return;
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
written
|
|
3068
|
+
if (skills.length > 0) {
|
|
3069
|
+
const written = [];
|
|
3070
|
+
for (const s of skills) {
|
|
3071
|
+
mkdirSync6(join6(sDir, s.name), { recursive: true });
|
|
3072
|
+
writeFileSync6(join6(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
|
|
3073
|
+
written.push(s.name);
|
|
3074
|
+
}
|
|
3075
|
+
recordMaterialisedSkills(sDir, DEFAULT_SKILLS_SLUG, written, { surface });
|
|
3076
|
+
process.stderr.write(`${style.green("\u2713")} wrote ${written.length} skill(s) to ${sDir}
|
|
3077
|
+
`);
|
|
3078
|
+
}
|
|
3079
|
+
if (agents.length > 0) {
|
|
3080
|
+
mkdirSync6(aDir, { recursive: true });
|
|
3081
|
+
const written = [];
|
|
3082
|
+
for (const a of agents) {
|
|
3083
|
+
const file = `${a.name}.md`;
|
|
3084
|
+
writeFileSync6(join6(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
|
|
3085
|
+
written.push(file);
|
|
3086
|
+
}
|
|
3087
|
+
recordMaterialisedSkills(aDir, DEFAULT_SKILLS_SLUG, written, { surface });
|
|
3088
|
+
process.stderr.write(`${style.green("\u2713")} wrote ${written.length} agent(s) to ${aDir}
|
|
3089
|
+
`);
|
|
3034
3090
|
}
|
|
3035
|
-
process.stderr.write(
|
|
3036
|
-
`${style.green("\u2713")} wrote ${written.length} skill(s) to ${dir}
|
|
3037
|
-
`
|
|
3038
|
-
);
|
|
3039
3091
|
await ensureLanePin(cfg, {
|
|
3040
3092
|
yes: opts.yes,
|
|
3041
3093
|
dryRun: opts.dryRun,
|
|
@@ -3191,13 +3243,13 @@ async function runClients(clients, cmd, opts) {
|
|
|
3191
3243
|
}
|
|
3192
3244
|
|
|
3193
3245
|
// src/commands/onboard.ts
|
|
3194
|
-
import { existsSync as
|
|
3195
|
-
import { basename as basename3, join as
|
|
3246
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3247
|
+
import { basename as basename3, join as join8 } from "path";
|
|
3196
3248
|
|
|
3197
3249
|
// src/commands/fanout.ts
|
|
3198
3250
|
import { spawnSync } from "child_process";
|
|
3199
|
-
import { existsSync as
|
|
3200
|
-
import { isAbsolute, join as
|
|
3251
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync, statSync } from "fs";
|
|
3252
|
+
import { isAbsolute, join as join7, resolve } from "path";
|
|
3201
3253
|
var ICON = {
|
|
3202
3254
|
refresh: "\u21BB",
|
|
3203
3255
|
bind: "+",
|
|
@@ -3217,21 +3269,21 @@ function discoverChildren(root) {
|
|
|
3217
3269
|
const out = [];
|
|
3218
3270
|
for (const name of names.sort()) {
|
|
3219
3271
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
3220
|
-
const dir =
|
|
3272
|
+
const dir = join7(root, name);
|
|
3221
3273
|
try {
|
|
3222
3274
|
if (!statSync(dir).isDirectory()) continue;
|
|
3223
3275
|
} catch {
|
|
3224
3276
|
continue;
|
|
3225
3277
|
}
|
|
3226
|
-
if (
|
|
3278
|
+
if (existsSync7(join7(dir, ".git")) || committedBindingPath(dir)) out.push(name);
|
|
3227
3279
|
}
|
|
3228
3280
|
return out;
|
|
3229
3281
|
}
|
|
3230
3282
|
function readManifest(path) {
|
|
3231
|
-
if (!
|
|
3283
|
+
if (!existsSync7(path)) return null;
|
|
3232
3284
|
let parsed;
|
|
3233
3285
|
try {
|
|
3234
|
-
parsed = JSON.parse(
|
|
3286
|
+
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
3235
3287
|
} catch (err2) {
|
|
3236
3288
|
throw new Error(`couldn't parse ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
3237
3289
|
}
|
|
@@ -3596,10 +3648,10 @@ async function chooseClients(clientFlag, yes, cwd) {
|
|
|
3596
3648
|
}
|
|
3597
3649
|
async function planRecurseChild(entry, root, client, opts) {
|
|
3598
3650
|
const dir = resolveChildDir(entry.path, root);
|
|
3599
|
-
if (!
|
|
3651
|
+
if (!existsSync8(dir)) {
|
|
3600
3652
|
return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
|
|
3601
3653
|
}
|
|
3602
|
-
if (
|
|
3654
|
+
if (existsSync8(join8(dir, ".sechroom.json"))) {
|
|
3603
3655
|
return {
|
|
3604
3656
|
label: entry.path,
|
|
3605
3657
|
dir,
|
|
@@ -3672,7 +3724,7 @@ This fan-out will pin the same lane in every repo:
|
|
|
3672
3724
|
async function runRecurse(cfg, g, opts) {
|
|
3673
3725
|
const { yes, dryRun, json } = opts;
|
|
3674
3726
|
const root = process.cwd();
|
|
3675
|
-
const manifestPath =
|
|
3727
|
+
const manifestPath = join8(root, ".sechroom", "repos.json");
|
|
3676
3728
|
const fromManifest = readManifest(manifestPath);
|
|
3677
3729
|
const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
|
|
3678
3730
|
const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
|
|
@@ -3886,12 +3938,12 @@ async function printStarterPrompt(mode, cfg) {
|
|
|
3886
3938
|
}
|
|
3887
3939
|
|
|
3888
3940
|
// src/commands/sweep.ts
|
|
3889
|
-
import { existsSync as
|
|
3890
|
-
import { dirname as dirname6, join as
|
|
3891
|
-
var DEFAULT_MANIFEST =
|
|
3941
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3942
|
+
import { dirname as dirname6, join as join9, resolve as resolve2 } from "path";
|
|
3943
|
+
var DEFAULT_MANIFEST = join9(".sechroom", "repos.json");
|
|
3892
3944
|
function planEntry(entry, root) {
|
|
3893
3945
|
const dir = resolveChildDir(entry.path, root);
|
|
3894
|
-
if (!
|
|
3946
|
+
if (!existsSync9(dir)) {
|
|
3895
3947
|
return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
|
|
3896
3948
|
}
|
|
3897
3949
|
if (committedBindingPath(dir)) {
|
|
@@ -3985,106 +4037,21 @@ Examples:
|
|
|
3985
4037
|
}
|
|
3986
4038
|
|
|
3987
4039
|
// src/commands/skills.ts
|
|
3988
|
-
import {
|
|
3989
|
-
import {
|
|
3990
|
-
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3991
|
-
var DEFAULT_SLUG = "operator-skills";
|
|
3992
|
-
var ROLE_TAGS = ["sechroom:role:skill-template", "role:skill-template"];
|
|
3993
|
-
var LOCK = ".sechroom-skills.json";
|
|
3994
|
-
function skillsDir(global) {
|
|
3995
|
-
return global ? join9(homedir6(), ".claude", "skills") : join9(process.cwd(), ".claude", "skills");
|
|
3996
|
-
}
|
|
3997
|
-
function tagValue2(tags, prefix) {
|
|
3998
|
-
return (tags ?? []).find((t) => t.startsWith(prefix))?.slice(prefix.length);
|
|
3999
|
-
}
|
|
4000
|
-
function hasAny(tags, candidates) {
|
|
4001
|
-
return (tags ?? []).some((t) => candidates.includes(t));
|
|
4002
|
-
}
|
|
4040
|
+
import { join as join10 } from "path";
|
|
4041
|
+
import { existsSync as existsSync10, rmSync as rmSync2 } from "fs";
|
|
4003
4042
|
function registerSkills(program2) {
|
|
4004
|
-
const skills = program2.command("skills").description("
|
|
4043
|
+
const skills = program2.command("skills").description("Manage operator skills (materialised by `onboard`)");
|
|
4005
4044
|
skills.addHelpText(
|
|
4006
4045
|
"after",
|
|
4007
4046
|
`
|
|
4008
4047
|
Examples:
|
|
4009
|
-
$ sechroom skills install --code-lane claude-code-chris --design-lane claude-design-chris
|
|
4010
|
-
$ sechroom skills install operator-skills --surface claude-code --local
|
|
4011
4048
|
$ sechroom skills list
|
|
4012
4049
|
$ sechroom skills set-lane --code-lane claude-code-chris --design-lane claude-design-chris
|
|
4013
4050
|
$ sechroom skills lane
|
|
4014
|
-
$ sechroom skills clean
|
|
4051
|
+
$ sechroom skills clean
|
|
4052
|
+
|
|
4053
|
+
To install/refresh skills, run 'sechroom onboard' (it offers to materialise them).`
|
|
4015
4054
|
);
|
|
4016
|
-
skills.command("install [slug]").description(`Install a skills bundle (default ${DEFAULT_SLUG}) into your personal workspace + write SKILL.md files`).option("--version <v>", "bundle version (default: latest published in the catalogue)").option("--instance <name>", "install as a named, separate instance (install the same bundle more than once)").option("--code-lane <id>", "identity.code-lane binding (e.g. claude-code-chris)").option("--design-lane <id>", "identity.design-lane binding (e.g. claude-design-chris)").option("--surface <s>", "skill target surface to materialise", "claude-code").option("--local", "write to ./.claude/skills instead of ~/.claude/skills").option("--json", "machine output").action(async (slugArg, opts, cmd) => {
|
|
4017
|
-
const slug = slugArg || DEFAULT_SLUG;
|
|
4018
|
-
const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
|
|
4019
|
-
const pw = await runApi("resolving personal workspace", () => client.GET("/me/personal-workspace", {}));
|
|
4020
|
-
const personalWsId = pw?.id || pw?.workspaceId || pw?.personalWorkspaceId || pw?.item?.id;
|
|
4021
|
-
if (!personalWsId) fail("Could not resolve your personal workspace.");
|
|
4022
|
-
let version = opts.version;
|
|
4023
|
-
if (!version) {
|
|
4024
|
-
const cat = await runApi("reading the bundle catalogue", () => client.GET("/me/bundles", {}));
|
|
4025
|
-
const item = (cat?.bundles ?? cat?.Bundles ?? []).find((b) => (b.slug ?? b.Slug) === slug);
|
|
4026
|
-
if (!item) fail(`Bundle '${slug}' is not in your self-serve catalogue (must be UserInstallable + Published).`);
|
|
4027
|
-
version = item.latestVersion ?? item.LatestVersion;
|
|
4028
|
-
if (!version) fail(`Bundle '${slug}' has no installable (Published) version.`);
|
|
4029
|
-
}
|
|
4030
|
-
const installOptions = {};
|
|
4031
|
-
if (opts.codeLane) installOptions["identity.code-lane"] = opts.codeLane;
|
|
4032
|
-
if (opts.designLane) installOptions["identity.design-lane"] = opts.designLane;
|
|
4033
|
-
const res = await runApi(
|
|
4034
|
-
`installing ${slug}@${version}${opts.instance ? ` (${opts.instance})` : ""}`,
|
|
4035
|
-
() => client.POST("/me/bundles/{slug}/versions/{version}/install", {
|
|
4036
|
-
params: { path: { slug, version } },
|
|
4037
|
-
// instance: null/absent = the default instance (reinstall updates in
|
|
4038
|
-
// place); a name installs a separate instance.
|
|
4039
|
-
body: { installOptions, instance: opts.instance ?? null }
|
|
4040
|
-
})
|
|
4041
|
-
);
|
|
4042
|
-
const status = String(res?.status ?? res?.Status ?? "");
|
|
4043
|
-
if (status && status.toLowerCase() !== "completed") {
|
|
4044
|
-
fail(`Install did not complete (status=${status}; ${res?.failureReason ?? res?.FailureReason ?? ""}).`);
|
|
4045
|
-
}
|
|
4046
|
-
const feed = await runApi(
|
|
4047
|
-
"materialising skill files",
|
|
4048
|
-
() => client.GET("/workspaces/{workspaceId}/memories/feed", {
|
|
4049
|
-
// cascadeWorkspaces: skills land in an "Operator Skills" SUB-workspace of
|
|
4050
|
-
// the personal workspace, so we recurse from the personal-ws root.
|
|
4051
|
-
// includeText: the feed omits bodies by default; we need them for SKILL.md.
|
|
4052
|
-
params: {
|
|
4053
|
-
path: { workspaceId: personalWsId },
|
|
4054
|
-
query: { limit: 200, cascadeWorkspaces: true, includeText: true }
|
|
4055
|
-
}
|
|
4056
|
-
})
|
|
4057
|
-
);
|
|
4058
|
-
const rows = feed?.results ?? feed?.Results ?? [];
|
|
4059
|
-
const dir = skillsDir(!opts.local);
|
|
4060
|
-
const wantInstance = opts.instance || "default";
|
|
4061
|
-
const written = [];
|
|
4062
|
-
const bundleTagPrefix = `sechroom:bundle:${slug}@`;
|
|
4063
|
-
for (const r of rows) {
|
|
4064
|
-
const m = r.item ?? r;
|
|
4065
|
-
const tags = m.tags ?? m.Tags ?? [];
|
|
4066
|
-
if (!hasAny(tags, ROLE_TAGS)) continue;
|
|
4067
|
-
if (tagValue2(tags, "target:") !== opts.surface) continue;
|
|
4068
|
-
if (!tags.some((t) => t.startsWith(bundleTagPrefix))) continue;
|
|
4069
|
-
if ((tagValue2(tags, "sechroom:skill-instance:") ?? "default") !== wantInstance) continue;
|
|
4070
|
-
const name = tagValue2(tags, "skill:");
|
|
4071
|
-
if (!name) continue;
|
|
4072
|
-
const body = m.text ?? m.Text ?? "";
|
|
4073
|
-
mkdirSync6(join9(dir, name), { recursive: true });
|
|
4074
|
-
writeFileSync6(join9(dir, name, "SKILL.md"), body.endsWith("\n") ? body : body + "\n");
|
|
4075
|
-
written.push(name);
|
|
4076
|
-
}
|
|
4077
|
-
mkdirSync6(dir, { recursive: true });
|
|
4078
|
-
const lockPath = join9(dir, LOCK);
|
|
4079
|
-
const lock = existsSync9(lockPath) ? JSON.parse(readFileSync6(lockPath, "utf8")) : {};
|
|
4080
|
-
lock[slug] = { surface: opts.surface, version, instance: wantInstance, skills: written.sort() };
|
|
4081
|
-
writeFileSync6(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
4082
|
-
if (opts.json) return emit({ slug, version, instance: wantInstance, surface: opts.surface, dir, installed: written }, true);
|
|
4083
|
-
const instanceNote = opts.instance ? ` (${opts.instance})` : "";
|
|
4084
|
-
console.log(style.green(`Installed ${slug}@${version}${instanceNote} \u2014 ${written.length} skill(s) \u2192 ${dir}`));
|
|
4085
|
-
written.forEach((n) => console.log(" " + style.dim("\u2022") + " " + n));
|
|
4086
|
-
if (written.length === 0) console.log(style.dim(` (no '${opts.surface}' skill bodies found; check --surface)`));
|
|
4087
|
-
});
|
|
4088
4055
|
skills.command("list").description("List your installed bundles (GET /me/bundle-installs)").option("--json", "machine output").action(async (opts, cmd) => {
|
|
4089
4056
|
const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
|
|
4090
4057
|
const data = await runApi("reading your installs", () => client.GET("/me/bundle-installs", {}));
|
|
@@ -4097,24 +4064,22 @@ Examples:
|
|
|
4097
4064
|
console.log(` ${i.bundleSlug ?? i.BundleSlug}@${i.bundleVersion ?? i.BundleVersion ?? "?"}${tag}`);
|
|
4098
4065
|
});
|
|
4099
4066
|
});
|
|
4100
|
-
skills.command("clean [slug]").description(`Remove
|
|
4101
|
-
const slug = slugArg ||
|
|
4067
|
+
skills.command("clean [slug]").description(`Remove skill files materialised by onboard (default ${DEFAULT_SKILLS_SLUG})`).option("--local", "clean ./.claude/skills instead of ~/.claude/skills").option("--json", "machine output").action(async (slugArg, opts) => {
|
|
4068
|
+
const slug = slugArg || DEFAULT_SKILLS_SLUG;
|
|
4102
4069
|
const dir = skillsDir(!opts.local);
|
|
4103
|
-
const
|
|
4104
|
-
if (!existsSync9(lockPath)) fail(`No skills lockfile at ${lockPath}; nothing to clean.`);
|
|
4105
|
-
const lock = JSON.parse(readFileSync6(lockPath, "utf8"));
|
|
4070
|
+
const lock = readSkillsLock(dir);
|
|
4106
4071
|
const entry = lock[slug];
|
|
4107
|
-
if (!entry) fail(`No
|
|
4072
|
+
if (!entry) fail(`No materialised skills recorded for '${slug}' in ${join10(dir, SKILLS_LOCK)}.`);
|
|
4108
4073
|
const removed = [];
|
|
4109
4074
|
for (const name of entry.skills) {
|
|
4110
|
-
const skillPath =
|
|
4111
|
-
if (
|
|
4075
|
+
const skillPath = join10(dir, name);
|
|
4076
|
+
if (existsSync10(skillPath)) {
|
|
4112
4077
|
rmSync2(skillPath, { recursive: true, force: true });
|
|
4113
4078
|
removed.push(name);
|
|
4114
4079
|
}
|
|
4115
4080
|
}
|
|
4116
4081
|
delete lock[slug];
|
|
4117
|
-
|
|
4082
|
+
writeSkillsLock(dir, lock);
|
|
4118
4083
|
if (opts.json) return emit({ slug, removed, dir }, true);
|
|
4119
4084
|
console.log(style.green(`Removed ${removed.length} skill(s) for ${slug} from ${dir}`));
|
|
4120
4085
|
});
|
|
@@ -4203,22 +4168,24 @@ Examples:
|
|
|
4203
4168
|
}
|
|
4204
4169
|
|
|
4205
4170
|
// src/commands/reset.ts
|
|
4206
|
-
import { homedir as
|
|
4207
|
-
import { join as
|
|
4208
|
-
import { existsSync as
|
|
4209
|
-
var
|
|
4210
|
-
var localSkillsDir = () =>
|
|
4211
|
-
var globalSkillsDir = () =>
|
|
4171
|
+
import { homedir as homedir6 } from "os";
|
|
4172
|
+
import { join as join11 } from "path";
|
|
4173
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
|
|
4174
|
+
var SKILLS_LOCK2 = ".sechroom-skills.json";
|
|
4175
|
+
var localSkillsDir = () => join11(process.cwd(), ".claude", "skills");
|
|
4176
|
+
var globalSkillsDir = () => join11(homedir6(), ".claude", "skills");
|
|
4177
|
+
var localAgentsDir = () => join11(process.cwd(), ".claude", "agents");
|
|
4178
|
+
var globalAgentsDir = () => join11(homedir6(), ".claude", "agents");
|
|
4212
4179
|
function removeMaterialisedSkills(dir) {
|
|
4213
4180
|
const removed = [];
|
|
4214
|
-
const lockPath =
|
|
4215
|
-
if (!
|
|
4181
|
+
const lockPath = join11(dir, SKILLS_LOCK2);
|
|
4182
|
+
if (!existsSync11(lockPath)) return removed;
|
|
4216
4183
|
try {
|
|
4217
4184
|
const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
|
|
4218
4185
|
for (const entry of Object.values(lock)) {
|
|
4219
4186
|
for (const name of entry.skills ?? []) {
|
|
4220
|
-
const p =
|
|
4221
|
-
if (
|
|
4187
|
+
const p = join11(dir, name);
|
|
4188
|
+
if (existsSync11(p)) {
|
|
4222
4189
|
rmSync3(p, { recursive: true, force: true });
|
|
4223
4190
|
removed.push(p);
|
|
4224
4191
|
}
|
|
@@ -4263,28 +4230,30 @@ function registerReset(program2) {
|
|
|
4263
4230
|
}
|
|
4264
4231
|
}
|
|
4265
4232
|
const removed = [];
|
|
4266
|
-
const stateDir =
|
|
4267
|
-
if (
|
|
4233
|
+
const stateDir = join11(process.cwd(), ".sechroom");
|
|
4234
|
+
if (existsSync11(stateDir)) {
|
|
4268
4235
|
rmSync3(stateDir, { recursive: true, force: true });
|
|
4269
4236
|
removed.push(stateDir);
|
|
4270
4237
|
}
|
|
4271
|
-
const legacyCfg =
|
|
4272
|
-
if (
|
|
4238
|
+
const legacyCfg = join11(process.cwd(), ".sechroom.json");
|
|
4239
|
+
if (existsSync11(legacyCfg)) {
|
|
4273
4240
|
rmSync3(legacyCfg, { force: true });
|
|
4274
4241
|
removed.push(legacyCfg);
|
|
4275
4242
|
}
|
|
4276
|
-
const legacySem =
|
|
4277
|
-
if (
|
|
4243
|
+
const legacySem = join11(process.cwd(), ".sem");
|
|
4244
|
+
if (existsSync11(legacySem)) {
|
|
4278
4245
|
rmSync3(legacySem, { force: true });
|
|
4279
4246
|
removed.push(legacySem);
|
|
4280
4247
|
}
|
|
4281
4248
|
removed.push(...removeMaterialisedSkills(localSkillsDir()));
|
|
4249
|
+
removed.push(...removeMaterialisedSkills(localAgentsDir()));
|
|
4282
4250
|
if (global) {
|
|
4283
4251
|
const tok = clearToken();
|
|
4284
4252
|
if (tok) removed.push(tok);
|
|
4285
4253
|
const cfg = clearPersisted();
|
|
4286
4254
|
if (cfg) removed.push(cfg);
|
|
4287
4255
|
removed.push(...removeMaterialisedSkills(globalSkillsDir()));
|
|
4256
|
+
removed.push(...removeMaterialisedSkills(globalAgentsDir()));
|
|
4288
4257
|
}
|
|
4289
4258
|
if (json) return emit({ global, removed }, true);
|
|
4290
4259
|
if (removed.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sechroom/cli",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.142-rc.770caa0e",
|
|
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",
|