@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.
- package/dist/index.js +217 -20
- 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
|
|
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
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
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(
|
|
8853
|
-
|
|
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 {
|
|
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(
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|