@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.
Files changed (2) hide show
  1. package/dist/index.js +147 -14
  2. 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 refresh(
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 refresh(cfg, id, opts.ttl);
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 refresh(cfg, id, opts.ttl);
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 refresh(cfg, id, ttlSeconds) {
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/{id}/accept)").action(async (id, _opts, cmd) => {
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/{id}/accept", {
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/{id}/reject)").option("--reason <reason>", "Why it's being rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
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/{id}/reject", {
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 readStdin2();
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 readStdin2() {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.21",
3
+ "version": "2026.7.22-rc.f3ceef6d",
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",