@sechroom/cli 2026.6.33 → 2026.6.34

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 +204 -83
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3307,6 +3307,41 @@ function resolveAgents(systemRows, personalRows, surface) {
3307
3307
  return resolveByRole(systemRows, personalRows, surface, AGENT_ROLE_TAG, AGENT_NAME_PREFIX);
3308
3308
  }
3309
3309
 
3310
+ // src/setup/skill-resolution-io.ts
3311
+ var AGENT_TARGET = { "claude-code": "claude-agent" };
3312
+ function agentTargetFor(surface) {
3313
+ return AGENT_TARGET[surface] ?? `${surface}-agent`;
3314
+ }
3315
+ async function fetchFeedRows(cfg, workspaceId) {
3316
+ try {
3317
+ const client = await makeClient(cfg);
3318
+ const feed = await client.GET("/workspaces/{workspaceId}/memories/feed", {
3319
+ params: {
3320
+ path: { workspaceId },
3321
+ // cascadeWorkspaces: skills land in an "Operator Skills" SUB-workspace;
3322
+ // includeText: the feed omits bodies by default, we need them for SKILL.md.
3323
+ query: { limit: 200, cascadeWorkspaces: true, includeText: true }
3324
+ }
3325
+ }).then((r) => r.data).catch(() => void 0);
3326
+ return feed?.results ?? feed?.Results ?? [];
3327
+ } catch {
3328
+ return [];
3329
+ }
3330
+ }
3331
+ async function fetchTemplateRows(cfg, personalWorkspaceId) {
3332
+ const [systemRows, personalRows] = await Promise.all([
3333
+ fetchFeedRows(cfg, SYSTEM_WORKSPACE_ID),
3334
+ personalWorkspaceId ? fetchFeedRows(cfg, personalWorkspaceId) : Promise.resolve([])
3335
+ ]);
3336
+ return { systemRows, personalRows };
3337
+ }
3338
+ function resolveSkillSet(rows, surface) {
3339
+ return resolveSkills(rows.systemRows, rows.personalRows, surface);
3340
+ }
3341
+ function resolveAgentSet(rows, surface) {
3342
+ return resolveAgents(rows.systemRows, rows.personalRows, agentTargetFor(surface));
3343
+ }
3344
+
3310
3345
  // src/setup/skills-lock.ts
3311
3346
  import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3312
3347
  import { join as join7 } from "path";
@@ -3338,31 +3373,12 @@ function recordMaterialisedSkills(dir, slug, skills, meta = {}) {
3338
3373
  }
3339
3374
 
3340
3375
  // src/setup/skills-offer.ts
3341
- async function fetchFeedRows(cfg, workspaceId) {
3342
- try {
3343
- const client = await makeClient(cfg);
3344
- const feed = await client.GET("/workspaces/{workspaceId}/memories/feed", {
3345
- params: {
3346
- path: { workspaceId },
3347
- // cascadeWorkspaces: skills land in an "Operator Skills" SUB-workspace;
3348
- // includeText: the feed omits bodies by default, we need them for SKILL.md.
3349
- query: { limit: 200, cascadeWorkspaces: true, includeText: true }
3350
- }
3351
- }).then((r) => r.data).catch(() => void 0);
3352
- return feed?.results ?? feed?.Results ?? [];
3353
- } catch {
3354
- return [];
3355
- }
3356
- }
3357
3376
  async function maybeOfferSkills(cfg, personalWorkspaceId, opts) {
3358
3377
  const surface = opts.surface ?? "claude-code";
3359
3378
  const configDir = opts.configDir ?? resolveClaudeTargets({})[0].dir;
3360
- const [systemRows, personalRows] = await Promise.all([
3361
- fetchFeedRows(cfg, SYSTEM_WORKSPACE_ID),
3362
- personalWorkspaceId ? fetchFeedRows(cfg, personalWorkspaceId) : Promise.resolve([])
3363
- ]);
3364
- const skills = resolveSkills(systemRows, personalRows, surface);
3365
- const agents = resolveAgents(systemRows, personalRows, surface);
3379
+ const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
3380
+ const skills = resolveSkillSet(rows, surface);
3381
+ const agents = resolveAgentSet(rows, surface);
3366
3382
  if (skills.length === 0 && agents.length === 0) return;
3367
3383
  const sDir = skillsDir(configDir);
3368
3384
  const aDir = agentsDir(configDir);
@@ -4600,10 +4616,6 @@ Examples:
4600
4616
  });
4601
4617
  }
4602
4618
 
4603
- // src/commands/skills.ts
4604
- import { join as join12 } from "path";
4605
- import { existsSync as existsSync10, rmSync as rmSync2 } from "fs";
4606
-
4607
4619
  // src/commands/lane.ts
4608
4620
  var LANE_KEYS = ["code-lane", "design-lane"];
4609
4621
  function showLane(json) {
@@ -4659,72 +4671,159 @@ In a non-primary git worktree the ratified concurrent-session -N suffix is auto-
4659
4671
  );
4660
4672
  }
4661
4673
 
4674
+ // src/setup/materialise.ts
4675
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "fs";
4676
+ import { join as join12 } from "path";
4677
+ var CLIENT_SURFACE = "claude-code";
4678
+ function writeSkills(dir, skills, surface) {
4679
+ const written = [];
4680
+ for (const s of skills) {
4681
+ mkdirSync8(join12(dir, s.name), { recursive: true });
4682
+ writeFileSync8(join12(dir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
4683
+ written.push(s.name);
4684
+ }
4685
+ if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
4686
+ return written;
4687
+ }
4688
+ function writeAgents(dir, agents, surface) {
4689
+ if (agents.length) mkdirSync8(dir, { recursive: true });
4690
+ const written = [];
4691
+ for (const a of agents) {
4692
+ const file = `${a.name}.md`;
4693
+ writeFileSync8(join12(dir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
4694
+ written.push(file);
4695
+ }
4696
+ if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
4697
+ return written;
4698
+ }
4699
+ var SKILL_SPEC = { kind: "skill", dir: skillsDir, resolve: resolveSkillSet, write: writeSkills };
4700
+ var AGENT_SPEC = { kind: "agent", dir: agentsDir, resolve: resolveAgentSet, write: writeAgents };
4701
+ function scopeOf(opts) {
4702
+ return opts.local ? "project" : resolveScope(opts.scope);
4703
+ }
4704
+ async function runInstall(spec, cmd, opts) {
4705
+ const g = cmd.optsWithGlobals();
4706
+ let scope;
4707
+ try {
4708
+ scope = scopeOf(opts);
4709
+ } catch (err2) {
4710
+ return fail(err2.message);
4711
+ }
4712
+ const cfg = resolveConfig(g);
4713
+ const dryRun = Boolean(opts.dryRun);
4714
+ const json = Boolean(g.json || opts.json);
4715
+ const targets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
4716
+ const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
4717
+ const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
4718
+ const items = spec.resolve(rows, CLIENT_SURFACE);
4719
+ const results = targets.map((t) => {
4720
+ const dir = spec.dir(t.dir);
4721
+ const written = dryRun ? items.map((i) => i.name) : spec.write(dir, items, CLIENT_SURFACE);
4722
+ return { dir, label: t.label, written };
4723
+ });
4724
+ if (json) return emit({ kind: spec.kind, dryRun, available: items.length, targets: results }, true);
4725
+ if (items.length === 0) {
4726
+ console.log(style.dim(`No ${spec.kind}s available to install \u2014 is the bundle installed for your account?`));
4727
+ return;
4728
+ }
4729
+ for (const r of results) {
4730
+ console.log(
4731
+ `${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.written.length} ${spec.kind}(s) ${style.dim("\u2192")} ${r.dir}`
4732
+ );
4733
+ if (dryRun) for (const i of items) console.log(` ${i.name} ${style.dim(`[${i.source}]`)}`);
4734
+ }
4735
+ }
4736
+ function runList(spec, cmd, opts) {
4737
+ const g = cmd.optsWithGlobals();
4738
+ let scope;
4739
+ try {
4740
+ scope = scopeOf(opts);
4741
+ } catch (err2) {
4742
+ return fail(err2.message);
4743
+ }
4744
+ const json = Boolean(g.json || opts.json);
4745
+ const targets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
4746
+ const out = targets.map((t) => {
4747
+ const dir = spec.dir(t.dir);
4748
+ const lock = readSkillsLock(dir);
4749
+ const entries = Object.entries(lock).flatMap(
4750
+ ([slug, e]) => (e.skills ?? []).map((name) => ({ slug, name, present: existsSync10(join12(dir, name)) }))
4751
+ );
4752
+ return { dir, label: t.label, entries };
4753
+ });
4754
+ if (json) return emit({ kind: spec.kind, targets: out }, true);
4755
+ let any = false;
4756
+ for (const t of out) {
4757
+ if (t.entries.length === 0) continue;
4758
+ any = true;
4759
+ console.log(style.bold(t.dir) + ":");
4760
+ for (const e of t.entries) {
4761
+ const flag = e.present ? "" : style.dim("(missing) ");
4762
+ console.log(` ${flag}${e.name} ${style.dim(`[${e.slug}]`)}`);
4763
+ }
4764
+ }
4765
+ if (!any) console.log(style.dim(`No ${spec.kind}s materialised. Run \`sechroom ${spec.kind}s install\`.`));
4766
+ }
4767
+ function runClean(spec, cmd, opts, slugArg) {
4768
+ const g = cmd.optsWithGlobals();
4769
+ const slug = slugArg || DEFAULT_SKILLS_SLUG;
4770
+ let scope;
4771
+ try {
4772
+ scope = scopeOf(opts);
4773
+ } catch (err2) {
4774
+ return fail(err2.message);
4775
+ }
4776
+ const json = Boolean(g.json || opts.json);
4777
+ const targets = resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() });
4778
+ const cleaned = [];
4779
+ const missing = [];
4780
+ for (const t of targets) {
4781
+ const dir = spec.dir(t.dir);
4782
+ const lock = readSkillsLock(dir);
4783
+ const entry = lock[slug];
4784
+ if (!entry) {
4785
+ missing.push(join12(dir, SKILLS_LOCK));
4786
+ continue;
4787
+ }
4788
+ const removed = [];
4789
+ for (const name of entry.skills) {
4790
+ const p = join12(dir, name);
4791
+ if (existsSync10(p)) {
4792
+ rmSync2(p, { recursive: true, force: true });
4793
+ removed.push(name);
4794
+ }
4795
+ }
4796
+ delete lock[slug];
4797
+ writeSkillsLock(dir, lock);
4798
+ cleaned.push({ dir, removed });
4799
+ }
4800
+ if (cleaned.length === 0) {
4801
+ return fail(`No materialised ${spec.kind}s recorded for '${slug}' in ${missing.join(", ")}.`);
4802
+ }
4803
+ if (json) return emit({ kind: spec.kind, slug, cleaned, missing }, true);
4804
+ for (const c of cleaned) {
4805
+ console.log(style.green(`Removed ${c.removed.length} ${spec.kind}(s) for ${slug} from ${c.dir}`));
4806
+ }
4807
+ }
4808
+
4662
4809
  // src/commands/skills.ts
4663
4810
  function registerSkills(program2) {
4664
- const skills = program2.command("skills").description("Manage operator skills (materialised by `onboard`)");
4811
+ const skills = program2.command("skills").description("Manage operator skills (install to disk, list, clean)");
4665
4812
  skills.addHelpText(
4666
4813
  "after",
4667
4814
  `
4668
4815
  Examples:
4669
- $ sechroom skills list
4816
+ $ sechroom skills install materialise your installed skills to ~/.claude/skills
4817
+ $ sechroom skills install --scope project write them to ./.claude/skills instead
4818
+ $ sechroom skills list what's materialised on disk
4819
+ $ sechroom skills clean remove the materialised skill files
4670
4820
  $ sechroom skills set-lane --code-lane claude-code-chris --design-lane claude-design-chris
4671
- $ sechroom skills lane
4672
- $ sechroom skills clean
4673
4821
 
4674
- To install/refresh skills, run 'sechroom onboard' (it offers to materialise them).`
4822
+ `
4675
4823
  );
4676
- skills.command("list").description("List your installed bundles (GET /me/bundle-installs)").option("--json", "machine output").action(async (opts, cmd) => {
4677
- const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
4678
- const data = await runApi("reading your installs", () => client.GET("/me/bundle-installs", {}));
4679
- if (opts.json) return emit(data, true);
4680
- const installs = data?.installs ?? data?.Installs ?? [];
4681
- if (installs.length === 0) return console.log(style.dim("No bundles installed."));
4682
- installs.forEach((i) => {
4683
- const inst = i.instance ?? i.Instance ?? "";
4684
- const tag = inst ? style.dim(` [${inst}]`) : "";
4685
- console.log(` ${i.bundleSlug ?? i.BundleSlug}@${i.bundleVersion ?? i.BundleVersion ?? "?"}${tag}`);
4686
- });
4687
- });
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();
4690
- const slug = slugArg || DEFAULT_SKILLS_SLUG;
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;
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}`));
4726
- }
4727
- });
4824
+ skills.command("install").description("Materialise your installed skills to disk (the already-installed bundle \u2014 no server install)").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 be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(SKILL_SPEC, cmd, opts));
4825
+ skills.command("list").description("List the skills materialised on disk (per resolved config dir)").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((opts, cmd) => runList(SKILL_SPEC, cmd, opts));
4826
+ skills.command("clean [slug]").description(`Remove skill files materialised to disk (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((slugArg, opts, cmd) => runClean(SKILL_SPEC, cmd, opts, slugArg));
4728
4827
  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(
4729
4828
  (opts, cmd) => setLane({
4730
4829
  codeLane: opts.codeLane,
@@ -4795,6 +4894,27 @@ To install/refresh skills, run 'sechroom onboard' (it offers to materialise them
4795
4894
  });
4796
4895
  }
4797
4896
 
4897
+ // src/commands/agents.ts
4898
+ function registerAgents(program2) {
4899
+ const agents = program2.command("agents").description("Manage operator subagents (install to disk, list, clean)");
4900
+ agents.addHelpText(
4901
+ "after",
4902
+ `
4903
+ Examples:
4904
+ $ sechroom agents install materialise your installed agents to ~/.claude/agents
4905
+ $ sechroom agents install --scope project write them to ./.claude/agents instead
4906
+ $ sechroom agents install --claude-config-dir ~/.claude-abcs target another instance
4907
+ $ sechroom agents list what's materialised on disk
4908
+ $ sechroom agents clean remove the materialised agent files
4909
+
4910
+ Agents are resolved from the agent target (target:claude-agent), the dispatchable
4911
+ workers your loop skills call (e.g. find-prior-art \u2192 substrate-miner).`
4912
+ );
4913
+ agents.command("install").description("Materialise your installed subagents to disk (the already-installed bundle \u2014 no server install)").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 be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(AGENT_SPEC, cmd, opts));
4914
+ agents.command("list").description("List the subagents materialised on disk (per resolved config dir)").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((opts, cmd) => runList(AGENT_SPEC, cmd, opts));
4915
+ agents.command("clean [slug]").description(`Remove subagent files materialised to disk (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((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
4916
+ }
4917
+
4798
4918
  // src/commands/reset.ts
4799
4919
  import { homedir as homedir4 } from "os";
4800
4920
  import { join as join13 } from "path";
@@ -5021,6 +5141,7 @@ registerNamespace(program);
5021
5141
  registerOnboard(program);
5022
5142
  registerSweep(program);
5023
5143
  registerSkills(program);
5144
+ registerAgents(program);
5024
5145
  registerLane(program);
5025
5146
  registerReset(program);
5026
5147
  program.parseAsync().catch((err2) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.33",
3
+ "version": "2026.6.34",
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",