@sechroom/cli 2026.7.21 → 2026.7.22-rc.f3ceef6d
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 +147 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1031,7 +1031,7 @@ async function resolveInstruction(cfg, section, personalWorkspaceId) {
|
|
|
1031
1031
|
const { data } = await client.POST("/memories/search", {
|
|
1032
1032
|
body: { query: null, textQuery: null, semanticQuery: null, hybrid: false, limit: 1, includeArchived: false, includeSystem: false, tags }
|
|
1033
1033
|
});
|
|
1034
|
-
const hits = data ?? [];
|
|
1034
|
+
const hits = data?.items ?? [];
|
|
1035
1035
|
if (hits.length === 0) continue;
|
|
1036
1036
|
const templateId = hits[0].id;
|
|
1037
1037
|
const template = await fetchMemoryFields(cfg, templateId);
|
|
@@ -1041,7 +1041,7 @@ async function resolveInstruction(cfg, section, personalWorkspaceId) {
|
|
|
1041
1041
|
const { data: ovr } = await client.POST("/memories/search", {
|
|
1042
1042
|
body: { query: null, textQuery: null, semanticQuery: null, hybrid: false, limit: 1, includeArchived: false, includeSystem: false, tags: ["sechroom:role:override", `sechroom:template-ref:${templateId}`], owner: { type: "Workspace", id: personalWorkspaceId } }
|
|
1043
1043
|
});
|
|
1044
|
-
const ovrHits = ovr ?? [];
|
|
1044
|
+
const ovrHits = ovr?.items ?? [];
|
|
1045
1045
|
if (ovrHits.length > 0) {
|
|
1046
1046
|
const override = await fetchMemoryFields(cfg, ovrHits[0].id);
|
|
1047
1047
|
if (typeof override?.text === "string" && override.text.length > 0) {
|
|
@@ -2213,7 +2213,7 @@ function registerExecutor(program2) {
|
|
|
2213
2213
|
);
|
|
2214
2214
|
});
|
|
2215
2215
|
executor.command("refresh <id>").description("Refresh one advertisement lease once").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (id, opts, cmd) => {
|
|
2216
|
-
const data = await
|
|
2216
|
+
const data = await refreshExecutorInstance(
|
|
2217
2217
|
resolveConfig(cmd.optsWithGlobals()),
|
|
2218
2218
|
id,
|
|
2219
2219
|
opts.ttl
|
|
@@ -2224,13 +2224,13 @@ function registerExecutor(program2) {
|
|
|
2224
2224
|
if (opts.interval >= opts.ttl)
|
|
2225
2225
|
fail("heartbeat interval must be shorter than the TTL");
|
|
2226
2226
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
2227
|
-
await
|
|
2227
|
+
await refreshExecutorInstance(cfg, id, opts.ttl);
|
|
2228
2228
|
process.stderr.write(
|
|
2229
2229
|
style.green("executor heartbeat active") + style.dim(` \u2014 ${id}
|
|
2230
2230
|
`)
|
|
2231
2231
|
);
|
|
2232
2232
|
await holdHeartbeat(async () => {
|
|
2233
|
-
await
|
|
2233
|
+
await refreshExecutorInstance(cfg, id, opts.ttl);
|
|
2234
2234
|
}, opts.interval * 1e3);
|
|
2235
2235
|
});
|
|
2236
2236
|
executor.command("offers <id>").description("List live dispatch offers addressed to this exact instance").action(async (id, _opts, cmd) => {
|
|
@@ -2275,7 +2275,7 @@ function parseTransport(value) {
|
|
|
2275
2275
|
return fail("transport must be push or pull");
|
|
2276
2276
|
}
|
|
2277
2277
|
}
|
|
2278
|
-
async function
|
|
2278
|
+
async function refreshExecutorInstance(cfg, id, ttlSeconds) {
|
|
2279
2279
|
return api(
|
|
2280
2280
|
cfg,
|
|
2281
2281
|
`/me/executor-instances/${encodeURIComponent(id)}/refresh`,
|
|
@@ -2404,6 +2404,10 @@ function registerChannel(program2) {
|
|
|
2404
2404
|
);
|
|
2405
2405
|
await drain();
|
|
2406
2406
|
const stopReconciliation = startOfferReconciliation(drain);
|
|
2407
|
+
const stopHeartbeat = startExecutorHeartbeat(
|
|
2408
|
+
() => refreshExecutorInstance(cfg, instance.id, located.state.ttlSeconds),
|
|
2409
|
+
located.state.refreshAfterSeconds * 1e3
|
|
2410
|
+
);
|
|
2407
2411
|
if (json) {
|
|
2408
2412
|
emit(
|
|
2409
2413
|
{
|
|
@@ -2427,6 +2431,7 @@ function registerChannel(program2) {
|
|
|
2427
2431
|
await holdOpen(conn);
|
|
2428
2432
|
} finally {
|
|
2429
2433
|
stopReconciliation();
|
|
2434
|
+
stopHeartbeat();
|
|
2430
2435
|
}
|
|
2431
2436
|
});
|
|
2432
2437
|
withFilterOpts(
|
|
@@ -2469,6 +2474,10 @@ function registerChannel(program2) {
|
|
|
2469
2474
|
);
|
|
2470
2475
|
await drain();
|
|
2471
2476
|
const stopReconciliation = startOfferReconciliation(drain);
|
|
2477
|
+
const stopHeartbeat = startExecutorHeartbeat(
|
|
2478
|
+
() => refreshExecutorInstance(cfg, instance.id, located.state.ttlSeconds),
|
|
2479
|
+
located.state.refreshAfterSeconds * 1e3
|
|
2480
|
+
);
|
|
2472
2481
|
process.stderr.write(
|
|
2473
2482
|
style.dim(
|
|
2474
2483
|
`sechroom channel (mcp) \u2014 tenant ${cfg.tenant}, executor ${located.state.instanceKey}
|
|
@@ -2479,6 +2488,7 @@ function registerChannel(program2) {
|
|
|
2479
2488
|
await holdOpen(conn);
|
|
2480
2489
|
} finally {
|
|
2481
2490
|
stopReconciliation();
|
|
2491
|
+
stopHeartbeat();
|
|
2482
2492
|
}
|
|
2483
2493
|
});
|
|
2484
2494
|
channel.command("install").description(
|
|
@@ -2584,6 +2594,16 @@ function startOfferReconciliation(drain, intervalMilliseconds = 5e3, dependencie
|
|
|
2584
2594
|
}, intervalMilliseconds);
|
|
2585
2595
|
return () => cancel(timer);
|
|
2586
2596
|
}
|
|
2597
|
+
function startExecutorHeartbeat(refresh, intervalMilliseconds, dependencies = {}) {
|
|
2598
|
+
const schedule = dependencies.setInterval ?? setInterval;
|
|
2599
|
+
const cancel = dependencies.clearInterval ?? clearInterval;
|
|
2600
|
+
const onError = dependencies.onError ?? ((error) => process.stderr.write(err(`channel heartbeat failed: ${String(error)}
|
|
2601
|
+
`)));
|
|
2602
|
+
const timer = schedule(() => {
|
|
2603
|
+
void refresh().catch(onError);
|
|
2604
|
+
}, intervalMilliseconds);
|
|
2605
|
+
return () => cancel(timer);
|
|
2606
|
+
}
|
|
2587
2607
|
async function drainClaims(cfg, executorInstanceId, deliver, dependencies = {}) {
|
|
2588
2608
|
const request = dependencies.request ?? api;
|
|
2589
2609
|
const sleep = dependencies.sleep ?? ((milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds)));
|
|
@@ -3537,6 +3557,7 @@ Examples:
|
|
|
3537
3557
|
}
|
|
3538
3558
|
|
|
3539
3559
|
// src/commands/decomposition.ts
|
|
3560
|
+
import { readFile } from "fs/promises";
|
|
3540
3561
|
function registerDecomposition(program2) {
|
|
3541
3562
|
const decomposition = program2.command("decomposition").description(
|
|
3542
3563
|
"Drive a WLP decomposition: decompose a brief, then execute / accept / reject"
|
|
@@ -3546,6 +3567,8 @@ function registerDecomposition(program2) {
|
|
|
3546
3567
|
`
|
|
3547
3568
|
Examples:
|
|
3548
3569
|
$ sechroom decomposition decompose mem_XXXX
|
|
3570
|
+
$ sechroom decomposition from-plan mem_XXXX --file plan.json
|
|
3571
|
+
$ sechroom decomposition plan wlp_XXXX
|
|
3549
3572
|
$ sechroom decomposition execute sug_XXXX
|
|
3550
3573
|
$ sechroom decomposition publish-run sug_XXXX
|
|
3551
3574
|
$ sechroom decomposition accept sug_XXXX
|
|
@@ -3568,6 +3591,47 @@ Examples:
|
|
|
3568
3591
|
cmd.optsWithGlobals().json
|
|
3569
3592
|
);
|
|
3570
3593
|
});
|
|
3594
|
+
decomposition.command("from-plan <briefId>").description(
|
|
3595
|
+
"Create a decomposition from a hand-authored JSON plan; tasks may carry Unix-millisecond wakeAtMs"
|
|
3596
|
+
).requiredOption(
|
|
3597
|
+
"--file <path>",
|
|
3598
|
+
"JSON file containing { project, tasks, source? }; use - for stdin"
|
|
3599
|
+
).action(async (briefId, opts, cmd) => {
|
|
3600
|
+
const raw = opts.file === "-" ? await readStdin2() : await readFile(opts.file, "utf8");
|
|
3601
|
+
const body = parsePlanInput(raw, opts.file);
|
|
3602
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3603
|
+
const data = await runApi(
|
|
3604
|
+
"Creating decomposition from plan",
|
|
3605
|
+
async () => {
|
|
3606
|
+
const client = await makeClient(cfg);
|
|
3607
|
+
return client.POST("/work-briefs/{id}/decompose-from-plan", {
|
|
3608
|
+
params: { path: { id: briefId } },
|
|
3609
|
+
body
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
);
|
|
3613
|
+
emitAction(
|
|
3614
|
+
`created ${style.bold(data.suggestionId)} from ${data.taskCount} hand-authored task(s)`,
|
|
3615
|
+
data,
|
|
3616
|
+
cmd.optsWithGlobals().json
|
|
3617
|
+
);
|
|
3618
|
+
});
|
|
3619
|
+
decomposition.command("plan <decompositionId>").description(
|
|
3620
|
+
"Inspect the compiled plan, including each scheduled step's Unix-millisecond wakeAtMs"
|
|
3621
|
+
).action(async (decompositionId, _opts, cmd) => {
|
|
3622
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3623
|
+
const data = await runApi("Reading decomposition plan", async () => {
|
|
3624
|
+
const client = await makeClient(cfg);
|
|
3625
|
+
return client.GET("/decompositions/{id}/plan", {
|
|
3626
|
+
params: { path: { id: decompositionId } }
|
|
3627
|
+
});
|
|
3628
|
+
});
|
|
3629
|
+
emitAction(
|
|
3630
|
+
`read ${style.bold(decompositionId)} plan (${data.steps.length} step(s))`,
|
|
3631
|
+
data,
|
|
3632
|
+
cmd.optsWithGlobals().json
|
|
3633
|
+
);
|
|
3634
|
+
});
|
|
3571
3635
|
decomposition.command("execute <decompositionId>").description(
|
|
3572
3636
|
"Execute a decomposition's Task graph (POST /decompositions/{id}/execute)"
|
|
3573
3637
|
).action(async (decompositionId, _opts, cmd) => {
|
|
@@ -3637,6 +3701,24 @@ Examples:
|
|
|
3637
3701
|
);
|
|
3638
3702
|
});
|
|
3639
3703
|
}
|
|
3704
|
+
function parsePlanInput(raw, sourceName = "plan input") {
|
|
3705
|
+
let value;
|
|
3706
|
+
try {
|
|
3707
|
+
value = JSON.parse(raw);
|
|
3708
|
+
} catch (error) {
|
|
3709
|
+
throw new Error(
|
|
3710
|
+
`${sourceName} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
if (!value || typeof value !== "object" || !Array.isArray(value.tasks))
|
|
3714
|
+
throw new Error(`${sourceName} must contain an object with a tasks array`);
|
|
3715
|
+
return value;
|
|
3716
|
+
}
|
|
3717
|
+
async function readStdin2() {
|
|
3718
|
+
const chunks = [];
|
|
3719
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
3720
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3721
|
+
}
|
|
3640
3722
|
|
|
3641
3723
|
// src/commands/filing.ts
|
|
3642
3724
|
function registerFiling(program2) {
|
|
@@ -5902,23 +5984,23 @@ Examples:
|
|
|
5902
5984
|
});
|
|
5903
5985
|
emit(data, cmd.optsWithGlobals().json);
|
|
5904
5986
|
});
|
|
5905
|
-
suggestion.command("accept <id>").description("Accept a suggestion (POST /relationship-suggestions/{
|
|
5987
|
+
suggestion.command("accept <id>").description("Accept a suggestion (POST /relationship-suggestions/{instanceId}/accept)").action(async (id, _opts, cmd) => {
|
|
5906
5988
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
5907
5989
|
const data = await runApi("Accepting suggestion", async () => {
|
|
5908
5990
|
const client = await makeClient(cfg);
|
|
5909
|
-
return client.POST("/relationship-suggestions/{
|
|
5910
|
-
params: { path: { id } },
|
|
5991
|
+
return client.POST("/relationship-suggestions/{instanceId}/accept", {
|
|
5992
|
+
params: { path: { instanceId: id } },
|
|
5911
5993
|
body: {}
|
|
5912
5994
|
});
|
|
5913
5995
|
});
|
|
5914
5996
|
emitAction(`accepted suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
|
|
5915
5997
|
});
|
|
5916
|
-
suggestion.command("reject <id>").description("Reject a suggestion (POST /relationship-suggestions/{
|
|
5998
|
+
suggestion.command("reject <id>").description("Reject a suggestion (POST /relationship-suggestions/{instanceId}/reject)").option("--reason <reason>", "Why it's being rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
|
|
5917
5999
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
5918
6000
|
const data = await runApi("Rejecting suggestion", async () => {
|
|
5919
6001
|
const client = await makeClient(cfg);
|
|
5920
|
-
return client.POST("/relationship-suggestions/{
|
|
5921
|
-
params: { path: { id } },
|
|
6002
|
+
return client.POST("/relationship-suggestions/{instanceId}/reject", {
|
|
6003
|
+
params: { path: { instanceId: id } },
|
|
5922
6004
|
body: {
|
|
5923
6005
|
reason: opts.reason ?? null,
|
|
5924
6006
|
...opts.reasonCode ? { reasonCode: opts.reasonCode } : {}
|
|
@@ -6471,7 +6553,7 @@ function registerTelemetry(program2) {
|
|
|
6471
6553
|
"Per-turn telemetry self-report for Claude Code hooks \u2014 Stop/SubagentStop \u2192 parsed + terminal, Notification/PermissionDenied \u2192 approval (reads stdin; no-op unless bound). Fail-soft."
|
|
6472
6554
|
).action(async (_opts, cmd) => {
|
|
6473
6555
|
try {
|
|
6474
|
-
const raw = await
|
|
6556
|
+
const raw = await readStdin3();
|
|
6475
6557
|
const input = parseHookInput2(raw);
|
|
6476
6558
|
const cwd = input.cwd ?? process.cwd();
|
|
6477
6559
|
const binding = findBinding(cwd);
|
|
@@ -6663,7 +6745,7 @@ function isPermissionNotification(input) {
|
|
|
6663
6745
|
if (t) return t.includes("permission");
|
|
6664
6746
|
return (input.message ?? "").toLowerCase().includes("permission");
|
|
6665
6747
|
}
|
|
6666
|
-
async function
|
|
6748
|
+
async function readStdin3() {
|
|
6667
6749
|
if (process.stdin.isTTY) return "";
|
|
6668
6750
|
const chunks = [];
|
|
6669
6751
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
@@ -6893,6 +6975,56 @@ Examples:
|
|
|
6893
6975
|
});
|
|
6894
6976
|
}
|
|
6895
6977
|
|
|
6978
|
+
// src/commands/work-brief.ts
|
|
6979
|
+
function registerWorkBrief(program2) {
|
|
6980
|
+
const workBrief = program2.command("work-brief").description("Control an active work brief run");
|
|
6981
|
+
workBrief.addHelpText(
|
|
6982
|
+
"after",
|
|
6983
|
+
`
|
|
6984
|
+
Examples:
|
|
6985
|
+
$ sechroom work-brief pause mem_XXXX --reason-code operator-hold --source claude-code-chris
|
|
6986
|
+
$ sechroom work-brief resume mem_XXXX --reason-code operator-resume --source claude-code-chris --reason "Ready to continue"
|
|
6987
|
+
$ sechroom work-brief cancel mem_XXXX --reason-code operator-stopped --source claude-code-chris --reason "Work no longer required"`
|
|
6988
|
+
);
|
|
6989
|
+
registerLifecycleAction(workBrief, "pause");
|
|
6990
|
+
registerLifecycleAction(workBrief, "resume");
|
|
6991
|
+
registerLifecycleAction(workBrief, "cancel");
|
|
6992
|
+
}
|
|
6993
|
+
function registerLifecycleAction(workBrief, action) {
|
|
6994
|
+
const presentParticiple = action === "pause" ? "Pausing" : action === "resume" ? "Resuming" : "Cancelling";
|
|
6995
|
+
const pastTense = action === "pause" ? "paused" : action === "resume" ? "resumed" : "cancelled";
|
|
6996
|
+
workBrief.command(`${action} <briefId>`).description(`${capitalize(action)} an active work brief run`).requiredOption(
|
|
6997
|
+
"--reason-code <code>",
|
|
6998
|
+
"Stable machine-readable reason code"
|
|
6999
|
+
).requiredOption(
|
|
7000
|
+
"--source <source>",
|
|
7001
|
+
"Calling surface or lane recorded in the audit"
|
|
7002
|
+
).option("--reason <text>", "Optional human-readable reason").action(async (briefId, opts, cmd) => {
|
|
7003
|
+
const globals = cmd.optsWithGlobals();
|
|
7004
|
+
const cfg = resolveConfig(globals);
|
|
7005
|
+
const data = await runApi(`${presentParticiple} work brief`, async () => {
|
|
7006
|
+
const client = await makeClient(cfg);
|
|
7007
|
+
return client.POST("/work-briefs/{id}/lifecycle", {
|
|
7008
|
+
params: { path: { id: briefId } },
|
|
7009
|
+
body: {
|
|
7010
|
+
action,
|
|
7011
|
+
reasonCode: opts.reasonCode,
|
|
7012
|
+
source: opts.source,
|
|
7013
|
+
reasonText: opts.reason ?? null
|
|
7014
|
+
}
|
|
7015
|
+
});
|
|
7016
|
+
});
|
|
7017
|
+
emitAction(
|
|
7018
|
+
`${pastTense} work brief ${style.bold(briefId)} \u2192 ${data.outcome}`,
|
|
7019
|
+
data,
|
|
7020
|
+
globals.json
|
|
7021
|
+
);
|
|
7022
|
+
});
|
|
7023
|
+
}
|
|
7024
|
+
function capitalize(value) {
|
|
7025
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
7026
|
+
}
|
|
7027
|
+
|
|
6896
7028
|
// src/index.ts
|
|
6897
7029
|
function resolveVersion() {
|
|
6898
7030
|
try {
|
|
@@ -7055,6 +7187,7 @@ registerLookup(program);
|
|
|
7055
7187
|
registerRelationships(program);
|
|
7056
7188
|
registerWorkspace(program);
|
|
7057
7189
|
registerProject(program);
|
|
7190
|
+
registerWorkBrief(program);
|
|
7058
7191
|
registerDecomposition(program);
|
|
7059
7192
|
registerExecutor(program);
|
|
7060
7193
|
registerClose(program);
|
package/package.json
CHANGED