@sechroom/cli 2026.9.26-rc.fa5538238 → 2026.10.1-rc.8cc2a58bd

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 +217 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8308,7 +8308,7 @@ function str(v) {
8308
8308
  }
8309
8309
 
8310
8310
  // src/commands/hook.ts
8311
- import { createHash as createHash4 } from "crypto";
8311
+ import { createHash as createHash5 } from "crypto";
8312
8312
  import { existsSync as existsSync13, mkdirSync as mkdirSync15, readFileSync as readFileSync14, statSync as statSync4, writeFileSync as writeFileSync14 } from "fs";
8313
8313
  import { dirname as dirname14, join as join17 } from "path";
8314
8314
 
@@ -8454,7 +8454,7 @@ function installLaneCommitHook(start) {
8454
8454
  }
8455
8455
 
8456
8456
  // src/commands/session-context.ts
8457
- import { randomUUID as randomUUID3 } from "crypto";
8457
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
8458
8458
  import { mkdirSync as mkdirSync14, renameSync as renameSync3, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
8459
8459
  import { dirname as dirname13, join as join16 } from "path";
8460
8460
 
@@ -8795,6 +8795,31 @@ function removeDirectoryIfEmpty(path) {
8795
8795
 
8796
8796
  // src/commands/session-context.ts
8797
8797
  var DYNAMIC_AGENT_CONTEXT_FILE = join16(".sechroom", "CLAUDE.md");
8798
+ function deliveredContext(result, lane) {
8799
+ if (result.status !== "ready") return renderSessionContext(result, lane);
8800
+ const contract = [
8801
+ result.renderVersion,
8802
+ result.text,
8803
+ result.digest,
8804
+ result.revision
8805
+ ];
8806
+ if (contract.every((value) => value === void 0)) {
8807
+ return renderSessionContext(result, lane);
8808
+ }
8809
+ if (contract.some((value) => typeof value !== "string" || value.length === 0)) {
8810
+ throw new Error(
8811
+ "session context resolution returned a partial or malformed render contract (renderVersion, text, digest and revision must all be present); refusing to write it and preserving existing compiled skills"
8812
+ );
8813
+ }
8814
+ const text2 = result.text;
8815
+ const actual = createHash4("sha256").update(text2, "utf8").digest("hex");
8816
+ if (result.digest !== actual) {
8817
+ throw new Error(
8818
+ "session context text does not match its digest; refusing to write it and preserving existing compiled skills"
8819
+ );
8820
+ }
8821
+ return text2;
8822
+ }
8798
8823
  function checkoutRoot(start) {
8799
8824
  const semPath = resolveSemPathForRead(start);
8800
8825
  return semPath ? dirname13(dirname13(semPath)) : start;
@@ -8819,6 +8844,9 @@ function renderSessionContext(result, lane) {
8819
8844
  `<!-- sechroom-context lane=${JSON.stringify(lane)} workspace=${JSON.stringify(result.workspaceId ?? "")} -->`,
8820
8845
  ""
8821
8846
  ];
8847
+ for (const notice of result.notices ?? []) {
8848
+ lines.push(`> Notice: ${notice}`, "");
8849
+ }
8822
8850
  for (const member of result.members) {
8823
8851
  lines.push(
8824
8852
  `<!-- sechroom-context-member key=${JSON.stringify(member.key)} layer=${JSON.stringify(member.layer)} bundle=${JSON.stringify(`${member.bundleSlug}@${member.bundleVersion}`)} component=${JSON.stringify(member.componentSlug)} id=${JSON.stringify(member.sourceId)} sourceVersion=${member.sourceVersion} -->`
@@ -8832,39 +8860,54 @@ function renderSessionContext(result, lane) {
8832
8860
  function writeSessionContext(start, lane, result, options = {}) {
8833
8861
  const root = checkoutRoot(start);
8834
8862
  const path = join16(root, DYNAMIC_AGENT_CONTEXT_FILE);
8863
+ const context = deliveredContext(result, lane);
8835
8864
  const skills = materialiseCompiledSkillCompositions(
8836
8865
  root,
8837
8866
  result.status === "hold" ? {
8838
8867
  skills: [],
8839
- holds: [{
8840
- code: "session-context-hold",
8841
- reason: result.reason?.trim() || "Session context resolution held without a reason.",
8842
- skillName: null
8843
- }],
8868
+ holds: [
8869
+ {
8870
+ code: "session-context-hold",
8871
+ reason: result.reason?.trim() || "Session context resolution held without a reason.",
8872
+ skillName: null
8873
+ }
8874
+ ],
8844
8875
  preserveExisting: true
8845
8876
  } : { skills: result.skills },
8846
8877
  options
8847
8878
  );
8848
- const context = renderSessionContext(result, lane);
8849
8879
  mkdirSync14(dirname13(path), { recursive: true });
8850
8880
  const temporaryPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
8851
8881
  try {
8852
- writeFileSync13(temporaryPath, context.endsWith("\n") ? context : `${context}
8853
- `, "utf8");
8882
+ writeFileSync13(
8883
+ temporaryPath,
8884
+ context.endsWith("\n") ? context : `${context}
8885
+ `,
8886
+ "utf8"
8887
+ );
8854
8888
  renameSync3(temporaryPath, path);
8855
8889
  } catch (error) {
8856
8890
  rmSync8(temporaryPath, { force: true });
8857
8891
  throw error;
8858
8892
  }
8859
- return { status: result.status, path, context, skills };
8893
+ return {
8894
+ status: result.status,
8895
+ path,
8896
+ context,
8897
+ skills,
8898
+ revision: result.revision ?? null
8899
+ };
8860
8900
  }
8861
8901
  async function materializeSessionContext(client, cfg, lane, start, options = {}) {
8862
- const { data, error, response } = await client.POST("/session-context/resolve", {
8863
- body: {
8864
- laneId: lane,
8865
- workspaceId: cfg.workspaceId ?? null
8902
+ const { data, error, response } = await client.POST(
8903
+ "/session-context/resolve",
8904
+ {
8905
+ body: {
8906
+ laneId: lane,
8907
+ workspaceId: cfg.workspaceId ?? null
8908
+ }
8866
8909
  }
8867
- });
8910
+ );
8868
8911
  if (!data) {
8869
8912
  const detail = error ? JSON.stringify(error) : `HTTP ${response?.status ?? "unknown"}`;
8870
8913
  throw new Error(`session context resolution failed: ${detail}`);
@@ -8873,7 +8916,9 @@ async function materializeSessionContext(client, cfg, lane, start, options = {})
8873
8916
  return writeSessionContext(start, lane, data, options);
8874
8917
  }
8875
8918
  function assertSessionContextResult(value) {
8876
- if (value === null || typeof value !== "object" || !["ready", "hold"].includes(String(value.status)) || !Array.isArray(value.members) || !Array.isArray(value.skills)) {
8919
+ if (value === null || typeof value !== "object" || !["ready", "hold"].includes(
8920
+ String(value.status)
8921
+ ) || !Array.isArray(value.members) || !Array.isArray(value.skills)) {
8877
8922
  throw new Error(
8878
8923
  "session context resolution returned an invalid payload; preserving existing compiled skills"
8879
8924
  );
@@ -9062,7 +9107,7 @@ function intentHash(i) {
9062
9107
  artifacts: i.artifacts ?? [],
9063
9108
  confidence: i.confidence ?? null
9064
9109
  });
9065
- return createHash4("sha256").update(canonical, "utf8").digest("hex");
9110
+ return createHash5("sha256").update(canonical, "utf8").digest("hex");
9066
9111
  }
9067
9112
  function recentlyCheckpointed(start, minutes) {
9068
9113
  const { lastEpochMs } = readLedger(start);
@@ -9879,6 +9924,18 @@ Displaced snapshot recovery surfaces:
9879
9924
  });
9880
9925
  emit(data, cmd.optsWithGlobals().json);
9881
9926
  });
9927
+ continuity.command("snapshot-seed <id>").description(
9928
+ "Render a snapshot you own or were granted as seed text, at most 16 KiB, with its revision and sha256 digest (GET /continuity/snapshots/{id}/seed)"
9929
+ ).action(async (id, _opts, cmd) => {
9930
+ const cfg = resolveConfig(cmd.optsWithGlobals());
9931
+ const data = await runApi(
9932
+ "Rendering snapshot seed",
9933
+ async () => (await makeClient(cfg)).GET("/continuity/snapshots/{id}/seed", {
9934
+ params: { path: { id } }
9935
+ })
9936
+ );
9937
+ emit(data, cmd.optsWithGlobals().json);
9938
+ });
9882
9939
  const snapshots = continuity.command("snapshots").description(
9883
9940
  "List the caller's own snapshots, including displaced recovery candidates (GET /me/continuity/snapshots)"
9884
9941
  ).option("--scope <scope>", "Filter by scope").option("--lane <laneId>", "Filter by lane id").action(async (opts, cmd) => {
@@ -10114,6 +10171,21 @@ function snapshotGetNotFoundHint(includeAll, status) {
10114
10171
  }
10115
10172
 
10116
10173
  // src/commands/desk.ts
10174
+ function deskPacksRequest(opts) {
10175
+ const filter = opts.filter ?? "published";
10176
+ if (filter !== "published" && filter !== "work-layer")
10177
+ throw new Error('--filter must be "published" or "work-layer"');
10178
+ let pageSize;
10179
+ if (opts.pageSize !== void 0) {
10180
+ pageSize = Number(opts.pageSize);
10181
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100)
10182
+ throw new Error(`--page-size must be a whole number from 1 to 100, got "${opts.pageSize}"`);
10183
+ }
10184
+ return {
10185
+ path: "/desk/packs",
10186
+ query: { filter, workspaceId: opts.workspaceId, cursor: opts.cursor, pageSize }
10187
+ };
10188
+ }
10117
10189
  function downloadRequestsRequest(opts) {
10118
10190
  if (opts.platform) {
10119
10191
  if (opts.take !== void 0 || opts.user !== void 0)
@@ -10138,6 +10210,7 @@ function downloadRequestsRequest(opts) {
10138
10210
  }
10139
10211
  function registerDesk(program2) {
10140
10212
  const desk = program2.command("desk").description("Sechroom Desk administration");
10213
+ registerDeskPacks(desk);
10141
10214
  const downloads = desk.command("downloads").description("Desk Downloads: which tenants see the page, and who requested which build").addHelpText(
10142
10215
  "after",
10143
10216
  `
@@ -10209,6 +10282,25 @@ Examples:
10209
10282
  registerDeskRooms(desk);
10210
10283
  registerDeskWorkspaces(desk);
10211
10284
  }
10285
+ function registerDeskPacks(desk) {
10286
+ desk.command("packs").description("Browse readable published context packs and visible Work Layer task packs").command("list").description("Page through readable Sechroom packs in this tenant (GET /desk/packs)").option("--filter <filter>", 'published or work-layer (default "published")').option("--workspace-id <id>", "limit Work Layer task packs to this workspace").option("--cursor <cursor>", "continue from the previous page").option("--page-size <n>", "rows per page, from 1 to 100 (default 50)").action(async (opts, cmd) => {
10287
+ let request;
10288
+ try {
10289
+ request = deskPacksRequest(opts);
10290
+ } catch (error) {
10291
+ fail(error);
10292
+ }
10293
+ const globals = cmd.optsWithGlobals();
10294
+ const cfg = resolveConfig(globals);
10295
+ const data = await runApi(
10296
+ "Listing Desk context packs",
10297
+ async () => (await makeClient(cfg)).GET("/desk/packs", {
10298
+ params: { query: request.query }
10299
+ })
10300
+ );
10301
+ emit(data, globals.json);
10302
+ });
10303
+ }
10212
10304
  function registerDeskRooms(desk) {
10213
10305
  const rooms = desk.command("rooms").description("Governed Desk rooms: which user a device is, and who may be invited").addHelpText(
10214
10306
  "after",
@@ -12986,8 +13078,112 @@ Note: CLI memory get omits relationships by default; MCP get_memory includes the
12986
13078
  });
12987
13079
  }
12988
13080
 
13081
+ // src/commands/retrieval.ts
13082
+ function parseWholeNumber(name, raw) {
13083
+ if (!/^\d+$/.test(raw.trim())) {
13084
+ fail(`${name} must be a non-negative whole number (got "${raw}").`);
13085
+ }
13086
+ const n = Number(raw);
13087
+ if (!Number.isSafeInteger(n) || n > 2147483647) {
13088
+ fail(`${name} is out of range (got "${raw}").`);
13089
+ }
13090
+ return n;
13091
+ }
13092
+ var MAX_SCOPE_PAGES = 8;
13093
+ function registerRetrieval(program2) {
13094
+ const memory = program2.commands.find((c) => c.name() === "memory") ?? program2.command("memory").description("Create, read, and search memories");
13095
+ memory.command("evidence <memoryId>").description(
13096
+ "Read a byte-exact span of a memory's text at an explicit version, with digests (GET /memories/{targetMemoryId}/evidence)"
13097
+ ).requiredOption(
13098
+ "--at <version>",
13099
+ "Required: 'latest' or a positive version number (no default, so a citation never comes from an implicit head)"
13100
+ ).option("--start <byte>", "Inclusive start byte (with --end)").option("--end <byte>", "Exclusive end byte (with --start); both on UTF-8 character boundaries").option("--include-archived", "Allow reading an archived memory", false).addHelpText(
13101
+ "after",
13102
+ `
13103
+ Offsets are BYTES of the UTF-8 text, not characters. Omit --start/--end for the whole text (refused above 64 KiB).
13104
+ --at is required. An exact version that is unavailable is refused; it never falls back to another version.
13105
+
13106
+ Examples:
13107
+ $ sechroom memory evidence mem_XXXX --at latest --json
13108
+ $ sechroom memory evidence mem_XXXX --at 3 --start 0 --end 120 --json`
13109
+ ).action(async (memoryId, opts, cmd) => {
13110
+ const cfg = resolveConfig(cmd.optsWithGlobals());
13111
+ if (opts.start == null !== (opts.end == null)) {
13112
+ fail("Provide both --start and --end, or neither.");
13113
+ }
13114
+ const query = {};
13115
+ query.version = String(opts.at);
13116
+ if (opts.start != null) {
13117
+ query.startByte = parseWholeNumber("--start", String(opts.start));
13118
+ query.endByte = parseWholeNumber("--end", String(opts.end));
13119
+ }
13120
+ if (opts.includeArchived) query.includeArchived = true;
13121
+ const data = await runApi("Reading evidence", async () => {
13122
+ const client = await makeClient(cfg);
13123
+ return client.GET("/memories/{targetMemoryId}/evidence", {
13124
+ params: { path: { targetMemoryId: memoryId }, query }
13125
+ });
13126
+ });
13127
+ emit(data, cmd.optsWithGlobals().json);
13128
+ });
13129
+ const scope = program2.command("scope").description("Navigate the authorised workspace/project scope");
13130
+ scope.command("ls").description("List the workspaces and projects you may see under a scope (GET /retrieval/scope)").option("--kind <kind>", "tenant (default), workspace, project or memories").option("--id <id>", "Workspace or project id (kind workspace/project)").option("--mode <mode>", "direct (default) or subtree").option("--memory-ids <ids>", "Comma-separated memory ids (kind memories, max 100)").option("--limit <n>", "Page size, 1 to 100 (default 50)").option("--cursor <token>", "Continuation token from the previous page").option("--include-archived", "Include archived nodes", false).option(
13131
+ "--all",
13132
+ `Follow continuations (up to ${MAX_SCOPE_PAGES} pages) and print one combined result`,
13133
+ false
13134
+ ).addHelpText(
13135
+ "after",
13136
+ `
13137
+ Hidden nodes are never named or counted; an id you cannot see is refused like one that does not exist.
13138
+ coverage.scopeExhausted=true with no continuation means the authorised scope is fully listed.
13139
+ A continuation is bound to you, this credential and the current authority state: if anything changes the read is
13140
+ refused (409 continuation_invalidated) and you restart it.
13141
+
13142
+ Examples:
13143
+ $ sechroom scope ls --json
13144
+ $ sechroom scope ls --kind workspace --id wsp_XXXX --mode subtree --all --json
13145
+ $ sechroom scope ls --kind memories --memory-ids mem_A,mem_B --json`
13146
+ ).action(async (opts, cmd) => {
13147
+ const cfg = resolveConfig(cmd.optsWithGlobals());
13148
+ const base = {};
13149
+ if (opts.kind) base.kind = String(opts.kind);
13150
+ if (opts.id) base.id = String(opts.id);
13151
+ if (opts.mode) base.mode = String(opts.mode);
13152
+ if (opts.memoryIds) base.memoryIds = String(opts.memoryIds);
13153
+ if (opts.limit != null) base.limit = parseWholeNumber("--limit", String(opts.limit));
13154
+ if (opts.includeArchived) base.includeArchived = true;
13155
+ const fetchPage = (cursor) => runApi("Listing scope", async () => {
13156
+ const client = await makeClient(cfg);
13157
+ return client.GET("/retrieval/scope", {
13158
+ params: { query: { ...base, ...cursor ? { cursor } : {} } }
13159
+ });
13160
+ });
13161
+ let page = await fetchPage(opts.cursor);
13162
+ if (!opts.all) {
13163
+ emit(page, cmd.optsWithGlobals().json);
13164
+ return;
13165
+ }
13166
+ const items = [...page.items];
13167
+ let pages = 1;
13168
+ while (page.continuation && pages < MAX_SCOPE_PAGES) {
13169
+ page = await fetchPage(page.continuation);
13170
+ items.push(...page.items);
13171
+ pages++;
13172
+ }
13173
+ emit(
13174
+ {
13175
+ ...page,
13176
+ items,
13177
+ coverage: { ...page.coverage, returned: items.length },
13178
+ pages
13179
+ },
13180
+ cmd.optsWithGlobals().json
13181
+ );
13182
+ });
13183
+ }
13184
+
12989
13185
  // src/setup/apply.ts
12990
- import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
13186
+ import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
12991
13187
  import {
12992
13188
  chmodSync as chmodSync2,
12993
13189
  copyFileSync,
@@ -13014,7 +13210,7 @@ function normalizeBody(s) {
13014
13210
  return s.replace(/\r\n/g, "\n").trim();
13015
13211
  }
13016
13212
  function bodySha256(body) {
13017
- return createHash5("sha256").update(normalizeBody(body), "utf8").digest("hex");
13213
+ return createHash6("sha256").update(normalizeBody(body), "utf8").digest("hex");
13018
13214
  }
13019
13215
  function renderBlock(write) {
13020
13216
  const body = normalizeBody(write.body);
@@ -17501,6 +17697,7 @@ local: ${d.localPath ?? "(none)"} ${JSON.stringify(readLocalConfig())}
17501
17697
  });
17502
17698
  registerAccounts(program);
17503
17699
  registerMemory(program);
17700
+ registerRetrieval(program);
17504
17701
  registerLaneGrant(program);
17505
17702
  registerWorklog(program);
17506
17703
  registerFeedback(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.9.26-rc.fa5538238",
3
+ "version": "2026.10.1-rc.8cc2a58bd",
4
4
  "description": "Command-line interface for Sechroom — sign in, wire your AI tools, and work with your sechroom from the terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",