@sechroom/cli 2026.6.229-rc.0d108cee → 2026.6.230-rc.326bc4de

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 +142 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4697,6 +4697,10 @@ Examples:
4697
4697
  });
4698
4698
  }
4699
4699
 
4700
+ // src/commands/skills.ts
4701
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
4702
+ import { join as join13 } from "path";
4703
+
4700
4704
  // src/commands/lane.ts
4701
4705
  var LANE_KEYS = ["code-lane", "design-lane"];
4702
4706
  function showLane(json) {
@@ -4905,6 +4909,51 @@ function runClean(spec, cmd, opts, slugArg) {
4905
4909
  }
4906
4910
 
4907
4911
  // src/commands/skills.ts
4912
+ function filenameFromDisposition(header) {
4913
+ if (!header) return void 0;
4914
+ const m = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(header);
4915
+ return m?.[1];
4916
+ }
4917
+ function resolveOutputPath(output, serverFilename) {
4918
+ const filename = serverFilename || "skills.zip";
4919
+ if (!output) return join13(process.cwd(), filename);
4920
+ const looksLikeDir = output.endsWith("/") || existsSync11(output) && statSync4(output).isDirectory();
4921
+ if (looksLikeDir) {
4922
+ mkdirSync9(output, { recursive: true });
4923
+ return join13(output, filename);
4924
+ }
4925
+ return output;
4926
+ }
4927
+ async function downloadZip(label, call, output) {
4928
+ const s = spinner(label);
4929
+ let res;
4930
+ try {
4931
+ res = await call();
4932
+ } catch (err2) {
4933
+ s.fail();
4934
+ fail(err2);
4935
+ }
4936
+ if (!res.response || !res.response.ok) {
4937
+ s.fail();
4938
+ let detail = res.response ? `HTTP ${res.response.status} ${res.response.statusText}`.trim() : "request failed";
4939
+ const e = res.error;
4940
+ if (e && typeof e === "object" && "title" in e) detail = String(e.title);
4941
+ else if (e instanceof ArrayBuffer) {
4942
+ try {
4943
+ const j = JSON.parse(new TextDecoder().decode(e));
4944
+ detail = j.title ?? j.detail ?? detail;
4945
+ } catch {
4946
+ }
4947
+ }
4948
+ fail(detail);
4949
+ }
4950
+ s.succeed();
4951
+ const buf = Buffer.from(res.data);
4952
+ const filename = filenameFromDisposition(res.response.headers.get("content-disposition")) ?? "skills.zip";
4953
+ const path = resolveOutputPath(output, filename);
4954
+ writeFileSync9(path, buf);
4955
+ return { path, bytes: buf.length, filename };
4956
+ }
4908
4957
  function registerSkills(program2) {
4909
4958
  const skills = program2.command("skills").description("Manage operator skills (install to disk, list, clean)");
4910
4959
  skills.addHelpText(
@@ -4915,6 +4964,9 @@ Examples:
4915
4964
  $ sechroom skills install --scope project write them to ./.claude/skills instead
4916
4965
  $ sechroom skills list what's materialised on disk
4917
4966
  $ sechroom skills clean remove the materialised skill files
4967
+ $ sechroom skills preview --workspace wsp_abc render a draft bundle from source (no install)
4968
+ $ sechroom skills package my-bundle download the installed bundle's skills as a zip
4969
+ $ sechroom skills package --from-source --workspace wsp_abc -o ./dist zip a draft from source
4918
4970
  $ sechroom skills set-lane --code-lane claude-code-chris --design-lane claude-design-chris
4919
4971
 
4920
4972
  `
@@ -4922,6 +4974,80 @@ Examples:
4922
4974
  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));
4923
4975
  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));
4924
4976
  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));
4977
+ skills.command("preview").description("Render a workspace's draft bundle from source (no publish/install) and report the components").requiredOption("--workspace <id>", "workspace holding the draft bundle sources (wsp_\u2026)").option("--slug <slug>", "override the derived bundle slug").option("--title <title>", "override the derived bundle title").option("--version <version>", "override the derived bundle version").option("--default-install-parent <path>", "override the derived default install parent").option("--json", "machine output (the full RenderBundlePreviewResponse)").action(async (opts, cmd) => {
4978
+ const json = Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json);
4979
+ const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
4980
+ const data = await runApi(
4981
+ "rendering bundle preview",
4982
+ () => client.POST("/workspaces/{workspaceId}/render-bundle-preview", {
4983
+ params: { path: { workspaceId: opts.workspace } },
4984
+ body: {
4985
+ slug: opts.slug ?? null,
4986
+ title: opts.title ?? null,
4987
+ version: opts.version ?? null,
4988
+ defaultInstallParent: opts.defaultInstallParent ?? null
4989
+ }
4990
+ })
4991
+ );
4992
+ if (json) return emit(data, true);
4993
+ console.log(
4994
+ style.bold(data.bundleSlug) + style.dim("@") + (data.bundleVersion || style.dim("(no version)"))
4995
+ );
4996
+ const components = data.components ?? [];
4997
+ if (components.length === 0) console.log(" " + style.dim("(no components rendered)"));
4998
+ for (const c of components) {
4999
+ const bits = [c.role, c.skillName ? style.dim("skill=") + c.skillName : null, c.target ? style.dim("target=") + c.target : null].filter(Boolean).join(" ");
5000
+ const flags = [
5001
+ c.intakeBlocked ? style.yellow("intake-blocked") : null,
5002
+ c.unresolvedTokens?.length ? style.yellow(`${c.unresolvedTokens.length} unresolved`) : null
5003
+ ].filter(Boolean).join(" ");
5004
+ console.log(" " + style.green("\u2022") + " " + style.bold(c.slug) + " " + bits + (flags ? " " + flags : ""));
5005
+ }
5006
+ const skipped = data.skipped ?? [];
5007
+ if (skipped.length) {
5008
+ console.log(style.dim(` skipped ${skipped.length}:`));
5009
+ for (const sk of skipped) console.log(" " + style.dim(`- ${sk.slug ?? sk.reason ?? JSON.stringify(sk)}`));
5010
+ }
5011
+ });
5012
+ skills.command("package [slug]").description("Download a bundle's skill-templates as a zip to disk (installed bundle, or --from-source draft)").option("--from-source", "package a workspace's DRAFT from source (requires --workspace) instead of an installed bundle").option("--workspace <id>", "with --from-source: the workspace holding the draft (wsp_\u2026)").option("--target <surface>", "surface to render (server default: claude-web installed, claude-code from-source)").option("--slug <slug>", "with --from-source: override the derived bundle slug").option("--title <title>", "with --from-source: override the derived bundle title").option("--version <version>", "with --from-source: override the derived bundle version").option("--default-install-parent <path>", "with --from-source: override the derived default install parent").option("-o, --output <path>", "where to write the zip \u2014 a dir gets the server filename, a file path is used as-is (default: cwd)").option("--json", "machine output ({ path, filename, bytes })").action(async (slugArg, opts, cmd) => {
5013
+ const json = Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json);
5014
+ if (opts.fromSource) {
5015
+ if (!opts.workspace) fail("--from-source requires --workspace <id> (the workspace holding the draft).");
5016
+ } else if (!slugArg) {
5017
+ fail("Provide a bundle slug to package, or use --from-source --workspace <id>.");
5018
+ }
5019
+ const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
5020
+ let result;
5021
+ if (opts.fromSource) {
5022
+ const query = {};
5023
+ if (opts.target) query.target = opts.target;
5024
+ if (slugArg ?? opts.slug) query.slug = slugArg ?? opts.slug;
5025
+ if (opts.title) query.title = opts.title;
5026
+ if (opts.version) query.version = opts.version;
5027
+ if (opts.defaultInstallParent) query.defaultInstallParent = opts.defaultInstallParent;
5028
+ result = await downloadZip(
5029
+ "packaging draft from source",
5030
+ () => client.GET("/workspaces/{workspaceId}/bundle-preview/package", {
5031
+ params: { path: { workspaceId: opts.workspace }, query },
5032
+ parseAs: "arrayBuffer"
5033
+ }),
5034
+ opts.output
5035
+ );
5036
+ } else {
5037
+ const query = {};
5038
+ if (opts.target) query.target = opts.target;
5039
+ result = await downloadZip(
5040
+ "packaging installed bundle",
5041
+ () => client.GET("/me/bundle-installs/{bundleSlug}/package", {
5042
+ params: { path: { bundleSlug: slugArg }, query },
5043
+ parseAs: "arrayBuffer"
5044
+ }),
5045
+ opts.output
5046
+ );
5047
+ }
5048
+ if (json) return emit(result, true);
5049
+ console.log(style.green("\u2713") + ` wrote ${style.bold(result.path)} ${style.dim(`(${result.bytes} bytes)`)}`);
5050
+ });
4925
5051
  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(
4926
5052
  (opts, cmd) => setLane({
4927
5053
  codeLane: opts.codeLane,
@@ -5015,23 +5141,23 @@ workers your loop skills call (e.g. find-prior-art \u2192 substrate-miner).`
5015
5141
 
5016
5142
  // src/commands/reset.ts
5017
5143
  import { homedir as homedir4 } from "os";
5018
- import { join as join13 } from "path";
5019
- import { existsSync as existsSync11, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
5144
+ import { join as join14 } from "path";
5145
+ import { existsSync as existsSync12, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
5020
5146
  var SKILLS_LOCK2 = ".sechroom-skills.json";
5021
- var localSkillsDir = () => join13(process.cwd(), ".claude", "skills");
5022
- var globalSkillsDir = () => join13(homedir4(), ".claude", "skills");
5023
- var localAgentsDir = () => join13(process.cwd(), ".claude", "agents");
5024
- var globalAgentsDir = () => join13(homedir4(), ".claude", "agents");
5147
+ var localSkillsDir = () => join14(process.cwd(), ".claude", "skills");
5148
+ var globalSkillsDir = () => join14(homedir4(), ".claude", "skills");
5149
+ var localAgentsDir = () => join14(process.cwd(), ".claude", "agents");
5150
+ var globalAgentsDir = () => join14(homedir4(), ".claude", "agents");
5025
5151
  function removeMaterialisedSkills(dir) {
5026
5152
  const removed = [];
5027
- const lockPath = join13(dir, SKILLS_LOCK2);
5028
- if (!existsSync11(lockPath)) return removed;
5153
+ const lockPath = join14(dir, SKILLS_LOCK2);
5154
+ if (!existsSync12(lockPath)) return removed;
5029
5155
  try {
5030
5156
  const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
5031
5157
  for (const entry of Object.values(lock)) {
5032
5158
  for (const name of entry.skills ?? []) {
5033
- const p = join13(dir, name);
5034
- if (existsSync11(p)) {
5159
+ const p = join14(dir, name);
5160
+ if (existsSync12(p)) {
5035
5161
  rmSync3(p, { recursive: true, force: true });
5036
5162
  removed.push(p);
5037
5163
  }
@@ -5076,18 +5202,18 @@ function registerReset(program2) {
5076
5202
  }
5077
5203
  }
5078
5204
  const removed = [];
5079
- const stateDir = join13(process.cwd(), ".sechroom");
5080
- if (existsSync11(stateDir)) {
5205
+ const stateDir = join14(process.cwd(), ".sechroom");
5206
+ if (existsSync12(stateDir)) {
5081
5207
  rmSync3(stateDir, { recursive: true, force: true });
5082
5208
  removed.push(stateDir);
5083
5209
  }
5084
- const legacyCfg = join13(process.cwd(), ".sechroom.json");
5085
- if (existsSync11(legacyCfg)) {
5210
+ const legacyCfg = join14(process.cwd(), ".sechroom.json");
5211
+ if (existsSync12(legacyCfg)) {
5086
5212
  rmSync3(legacyCfg, { force: true });
5087
5213
  removed.push(legacyCfg);
5088
5214
  }
5089
- const legacySem = join13(process.cwd(), ".sem");
5090
- if (existsSync11(legacySem)) {
5215
+ const legacySem = join14(process.cwd(), ".sem");
5216
+ if (existsSync12(legacySem)) {
5091
5217
  rmSync3(legacySem, { force: true });
5092
5218
  removed.push(legacySem);
5093
5219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.229-rc.0d108cee",
3
+ "version": "2026.6.230-rc.326bc4de",
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",