@sechroom/cli 2026.6.140-rc.176770e1 → 2026.6.141-rc.6cc560b2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +79 -137
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2859,9 +2859,9 @@ 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 mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
2863
- import { homedir as homedir5 } from "os";
2864
- import { join as join5 } from "path";
2862
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
2863
+ import { homedir as homedir6 } from "os";
2864
+ import { join as join6 } from "path";
2865
2865
 
2866
2866
  // src/setup/lane-pin.ts
2867
2867
  var CODE_LANE_PREFIX_BY_CLIENT = {
@@ -2981,6 +2981,34 @@ function resolveSkills(systemRows, personalRows, surface) {
2981
2981
  return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
2982
2982
  }
2983
2983
 
2984
+ // src/setup/skills-lock.ts
2985
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
2986
+ import { homedir as homedir5 } from "os";
2987
+ import { join as join5 } from "path";
2988
+ var SKILLS_LOCK = ".sechroom-skills.json";
2989
+ var DEFAULT_SKILLS_SLUG = "operator-skills";
2990
+ function skillsDir(global) {
2991
+ return global ? join5(homedir5(), ".claude", "skills") : join5(process.cwd(), ".claude", "skills");
2992
+ }
2993
+ function readSkillsLock(dir) {
2994
+ const lockPath = join5(dir, SKILLS_LOCK);
2995
+ if (!existsSync6(lockPath)) return {};
2996
+ try {
2997
+ return JSON.parse(readFileSync5(lockPath, "utf8"));
2998
+ } catch {
2999
+ return {};
3000
+ }
3001
+ }
3002
+ function writeSkillsLock(dir, lock) {
3003
+ mkdirSync5(dir, { recursive: true });
3004
+ writeFileSync5(join5(dir, SKILLS_LOCK), JSON.stringify(lock, null, 2) + "\n");
3005
+ }
3006
+ function recordMaterialisedSkills(dir, slug, skills, meta = {}) {
3007
+ const lock = readSkillsLock(dir);
3008
+ lock[slug] = { surface: meta.surface, skills: [...skills].sort() };
3009
+ writeSkillsLock(dir, lock);
3010
+ }
3011
+
2984
3012
  // src/setup/skills-offer.ts
2985
3013
  async function fetchFeedRows(cfg, workspaceId) {
2986
3014
  try {
@@ -3020,18 +3048,19 @@ Would materialise ${style.bold(String(resolved.length))} operator skill(s) for $
3020
3048
  Found ${style.bold(String(names.length))} operator skill(s) available to you: ${names.join(", ")}.
3021
3049
  `
3022
3050
  );
3023
- const dir = join5(homedir5(), ".claude", "skills");
3051
+ const dir = join6(homedir6(), ".claude", "skills");
3024
3052
  const materialise = opts.yes ? true : canPrompt() ? await promptYesNo(`Write them to ${dir}/ so ${surface} can use them?`) : false;
3025
3053
  if (!materialise) return;
3026
3054
  const written = [];
3027
3055
  for (const s of resolved) {
3028
- mkdirSync5(join5(dir, s.name), { recursive: true });
3029
- writeFileSync5(
3030
- join5(dir, s.name, "SKILL.md"),
3056
+ mkdirSync6(join6(dir, s.name), { recursive: true });
3057
+ writeFileSync6(
3058
+ join6(dir, s.name, "SKILL.md"),
3031
3059
  s.body.endsWith("\n") ? s.body : s.body + "\n"
3032
3060
  );
3033
3061
  written.push(s.name);
3034
3062
  }
3063
+ recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
3035
3064
  process.stderr.write(
3036
3065
  `${style.green("\u2713")} wrote ${written.length} skill(s) to ${dir}
3037
3066
  `
@@ -3191,13 +3220,13 @@ async function runClients(clients, cmd, opts) {
3191
3220
  }
3192
3221
 
3193
3222
  // src/commands/onboard.ts
3194
- import { existsSync as existsSync7 } from "fs";
3195
- import { basename as basename3, join as join7 } from "path";
3223
+ import { existsSync as existsSync8 } from "fs";
3224
+ import { basename as basename3, join as join8 } from "path";
3196
3225
 
3197
3226
  // src/commands/fanout.ts
3198
3227
  import { spawnSync } from "child_process";
3199
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync, statSync } from "fs";
3200
- import { isAbsolute, join as join6, resolve } from "path";
3228
+ import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync, statSync } from "fs";
3229
+ import { isAbsolute, join as join7, resolve } from "path";
3201
3230
  var ICON = {
3202
3231
  refresh: "\u21BB",
3203
3232
  bind: "+",
@@ -3217,21 +3246,21 @@ function discoverChildren(root) {
3217
3246
  const out = [];
3218
3247
  for (const name of names.sort()) {
3219
3248
  if (name.startsWith(".") || name === "node_modules") continue;
3220
- const dir = join6(root, name);
3249
+ const dir = join7(root, name);
3221
3250
  try {
3222
3251
  if (!statSync(dir).isDirectory()) continue;
3223
3252
  } catch {
3224
3253
  continue;
3225
3254
  }
3226
- if (existsSync6(join6(dir, ".git")) || committedBindingPath(dir)) out.push(name);
3255
+ if (existsSync7(join7(dir, ".git")) || committedBindingPath(dir)) out.push(name);
3227
3256
  }
3228
3257
  return out;
3229
3258
  }
3230
3259
  function readManifest(path) {
3231
- if (!existsSync6(path)) return null;
3260
+ if (!existsSync7(path)) return null;
3232
3261
  let parsed;
3233
3262
  try {
3234
- parsed = JSON.parse(readFileSync5(path, "utf8"));
3263
+ parsed = JSON.parse(readFileSync6(path, "utf8"));
3235
3264
  } catch (err2) {
3236
3265
  throw new Error(`couldn't parse ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`);
3237
3266
  }
@@ -3596,10 +3625,10 @@ async function chooseClients(clientFlag, yes, cwd) {
3596
3625
  }
3597
3626
  async function planRecurseChild(entry, root, client, opts) {
3598
3627
  const dir = resolveChildDir(entry.path, root);
3599
- if (!existsSync7(dir)) {
3628
+ if (!existsSync8(dir)) {
3600
3629
  return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
3601
3630
  }
3602
- if (existsSync7(join7(dir, ".sechroom.json"))) {
3631
+ if (existsSync8(join8(dir, ".sechroom.json"))) {
3603
3632
  return {
3604
3633
  label: entry.path,
3605
3634
  dir,
@@ -3672,7 +3701,7 @@ This fan-out will pin the same lane in every repo:
3672
3701
  async function runRecurse(cfg, g, opts) {
3673
3702
  const { yes, dryRun, json } = opts;
3674
3703
  const root = process.cwd();
3675
- const manifestPath = join7(root, ".sechroom", "repos.json");
3704
+ const manifestPath = join8(root, ".sechroom", "repos.json");
3676
3705
  const fromManifest = readManifest(manifestPath);
3677
3706
  const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
3678
3707
  const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
@@ -3886,12 +3915,12 @@ async function printStarterPrompt(mode, cfg) {
3886
3915
  }
3887
3916
 
3888
3917
  // src/commands/sweep.ts
3889
- import { existsSync as existsSync8 } from "fs";
3890
- import { dirname as dirname6, join as join8, resolve as resolve2 } from "path";
3891
- var DEFAULT_MANIFEST = join8(".sechroom", "repos.json");
3918
+ import { existsSync as existsSync9 } from "fs";
3919
+ import { dirname as dirname6, join as join9, resolve as resolve2 } from "path";
3920
+ var DEFAULT_MANIFEST = join9(".sechroom", "repos.json");
3892
3921
  function planEntry(entry, root) {
3893
3922
  const dir = resolveChildDir(entry.path, root);
3894
- if (!existsSync8(dir)) {
3923
+ if (!existsSync9(dir)) {
3895
3924
  return { label: entry.path, dir, disposition: "skip-missing", argv: [], reason: "directory does not exist" };
3896
3925
  }
3897
3926
  if (committedBindingPath(dir)) {
@@ -3985,106 +4014,21 @@ Examples:
3985
4014
  }
3986
4015
 
3987
4016
  // src/commands/skills.ts
3988
- import { homedir as homedir6 } from "os";
3989
- import { join as join9 } from "path";
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
- }
4017
+ import { join as join10 } from "path";
4018
+ import { existsSync as existsSync10, rmSync as rmSync2 } from "fs";
4003
4019
  function registerSkills(program2) {
4004
- const skills = program2.command("skills").description("Install + manage operator skills from a bundle");
4020
+ const skills = program2.command("skills").description("Manage operator skills (materialised by `onboard`)");
4005
4021
  skills.addHelpText(
4006
4022
  "after",
4007
4023
  `
4008
4024
  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
4025
  $ sechroom skills list
4012
4026
  $ sechroom skills set-lane --code-lane claude-code-chris --design-lane claude-design-chris
4013
4027
  $ sechroom skills lane
4014
- $ sechroom skills clean`
4028
+ $ sechroom skills clean
4029
+
4030
+ To install/refresh skills, run 'sechroom onboard' (it offers to materialise them).`
4015
4031
  );
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
4032
  skills.command("list").description("List your installed bundles (GET /me/bundle-installs)").option("--json", "machine output").action(async (opts, cmd) => {
4089
4033
  const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
4090
4034
  const data = await runApi("reading your installs", () => client.GET("/me/bundle-installs", {}));
@@ -4097,24 +4041,22 @@ Examples:
4097
4041
  console.log(` ${i.bundleSlug ?? i.BundleSlug}@${i.bundleVersion ?? i.BundleVersion ?? "?"}${tag}`);
4098
4042
  });
4099
4043
  });
4100
- skills.command("clean [slug]").description(`Remove materialised skill files written by install (default ${DEFAULT_SLUG})`).option("--local", "clean ./.claude/skills instead of ~/.claude/skills").option("--json", "machine output").action(async (slugArg, opts) => {
4101
- const slug = slugArg || DEFAULT_SLUG;
4044
+ 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) => {
4045
+ const slug = slugArg || DEFAULT_SKILLS_SLUG;
4102
4046
  const dir = skillsDir(!opts.local);
4103
- const lockPath = join9(dir, LOCK);
4104
- if (!existsSync9(lockPath)) fail(`No skills lockfile at ${lockPath}; nothing to clean.`);
4105
- const lock = JSON.parse(readFileSync6(lockPath, "utf8"));
4047
+ const lock = readSkillsLock(dir);
4106
4048
  const entry = lock[slug];
4107
- if (!entry) fail(`No installed record for '${slug}' in ${lockPath}.`);
4049
+ if (!entry) fail(`No materialised skills recorded for '${slug}' in ${join10(dir, SKILLS_LOCK)}.`);
4108
4050
  const removed = [];
4109
4051
  for (const name of entry.skills) {
4110
- const skillPath = join9(dir, name);
4111
- if (existsSync9(skillPath)) {
4052
+ const skillPath = join10(dir, name);
4053
+ if (existsSync10(skillPath)) {
4112
4054
  rmSync2(skillPath, { recursive: true, force: true });
4113
4055
  removed.push(name);
4114
4056
  }
4115
4057
  }
4116
4058
  delete lock[slug];
4117
- writeFileSync6(lockPath, JSON.stringify(lock, null, 2) + "\n");
4059
+ writeSkillsLock(dir, lock);
4118
4060
  if (opts.json) return emit({ slug, removed, dir }, true);
4119
4061
  console.log(style.green(`Removed ${removed.length} skill(s) for ${slug} from ${dir}`));
4120
4062
  });
@@ -4204,21 +4146,21 @@ Examples:
4204
4146
 
4205
4147
  // src/commands/reset.ts
4206
4148
  import { homedir as homedir7 } from "os";
4207
- import { join as join10 } from "path";
4208
- import { existsSync as existsSync10, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
4209
- var SKILLS_LOCK = ".sechroom-skills.json";
4210
- var localSkillsDir = () => join10(process.cwd(), ".claude", "skills");
4211
- var globalSkillsDir = () => join10(homedir7(), ".claude", "skills");
4149
+ import { join as join11 } from "path";
4150
+ import { existsSync as existsSync11, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
4151
+ var SKILLS_LOCK2 = ".sechroom-skills.json";
4152
+ var localSkillsDir = () => join11(process.cwd(), ".claude", "skills");
4153
+ var globalSkillsDir = () => join11(homedir7(), ".claude", "skills");
4212
4154
  function removeMaterialisedSkills(dir) {
4213
4155
  const removed = [];
4214
- const lockPath = join10(dir, SKILLS_LOCK);
4215
- if (!existsSync10(lockPath)) return removed;
4156
+ const lockPath = join11(dir, SKILLS_LOCK2);
4157
+ if (!existsSync11(lockPath)) return removed;
4216
4158
  try {
4217
4159
  const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
4218
4160
  for (const entry of Object.values(lock)) {
4219
4161
  for (const name of entry.skills ?? []) {
4220
- const p = join10(dir, name);
4221
- if (existsSync10(p)) {
4162
+ const p = join11(dir, name);
4163
+ if (existsSync11(p)) {
4222
4164
  rmSync3(p, { recursive: true, force: true });
4223
4165
  removed.push(p);
4224
4166
  }
@@ -4263,18 +4205,18 @@ function registerReset(program2) {
4263
4205
  }
4264
4206
  }
4265
4207
  const removed = [];
4266
- const stateDir = join10(process.cwd(), ".sechroom");
4267
- if (existsSync10(stateDir)) {
4208
+ const stateDir = join11(process.cwd(), ".sechroom");
4209
+ if (existsSync11(stateDir)) {
4268
4210
  rmSync3(stateDir, { recursive: true, force: true });
4269
4211
  removed.push(stateDir);
4270
4212
  }
4271
- const legacyCfg = join10(process.cwd(), ".sechroom.json");
4272
- if (existsSync10(legacyCfg)) {
4213
+ const legacyCfg = join11(process.cwd(), ".sechroom.json");
4214
+ if (existsSync11(legacyCfg)) {
4273
4215
  rmSync3(legacyCfg, { force: true });
4274
4216
  removed.push(legacyCfg);
4275
4217
  }
4276
- const legacySem = join10(process.cwd(), ".sem");
4277
- if (existsSync10(legacySem)) {
4218
+ const legacySem = join11(process.cwd(), ".sem");
4219
+ if (existsSync11(legacySem)) {
4278
4220
  rmSync3(legacySem, { force: true });
4279
4221
  removed.push(legacySem);
4280
4222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.140-rc.176770e1",
3
+ "version": "2026.6.141-rc.6cc560b2",
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",