@sechroom/cli 2026.7.11 → 2026.7.13

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 +116 -21
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1403,10 +1403,15 @@ var CLAUDE_HOOK_COMMANDS = {
1403
1403
  SessionStart: "sechroom hook session-start",
1404
1404
  PreCompact: "sechroom hook pre-compact",
1405
1405
  SessionEnd: "sechroom hook session-end",
1406
- // WLP telemetry tap (D-WLP-10) — per-turn executor self-report. No-op (exit 0) unless this
1407
- // checkout is bound to a decomposition+task via `sechroom telemetry bind`, so it's safe to wire
1408
- // for every Claude install. Claude-only (it parses a Claude Code transcript).
1409
- Stop: "sechroom telemetry hook"
1406
+ // WLP telemetry tap (D-WLP-10 + FR-352 Tier 1) — per-turn executor self-report. The one
1407
+ // `telemetry hook` verb dispatches on hook_event_name: Stop/SubagentStop parsed (token/context) +
1408
+ // terminal (turn end), Notification/PermissionDenied → approval. No-op (exit 0) unless this checkout
1409
+ // is bound via `sechroom telemetry bind`, so it's safe to wire for every Claude install; an event a
1410
+ // given Claude Code version doesn't know is inert (never fires). Claude-only.
1411
+ Stop: "sechroom telemetry hook",
1412
+ SubagentStop: "sechroom telemetry hook",
1413
+ Notification: "sechroom telemetry hook",
1414
+ PermissionDenied: "sechroom telemetry hook"
1410
1415
  };
1411
1416
  var CODEX_HOOK_COMMANDS = {
1412
1417
  SessionStart: "sechroom hook session-start",
@@ -2566,6 +2571,9 @@ function registerClose(program2) {
2566
2571
  ).option(
2567
2572
  "--no-status-flip",
2568
2573
  "Skip the status flip on a BARE task. (Managed runs never flip here \u2014 the run engine reflects the verdict onto the task on resume.)"
2574
+ ).option(
2575
+ "--to-version <n>",
2576
+ "Pin the Reference edge at this task version \u2014 the DISPATCHED version the executor worked against (D-continuity-2). Default: the task's current version (status flips are metadata edits that don't bump the version, so current == dispatched in the normal case; pass this when the task's content changed between dispatch and close)."
2569
2577
  ).option("--source <source>", "Source / lane stamp", "cli").addHelpText(
2570
2578
  "after",
2571
2579
  `
@@ -2599,6 +2607,7 @@ Examples:
2599
2607
  );
2600
2608
  const client = await makeClient(cfg);
2601
2609
  let matchTags;
2610
+ let dispatchedVersion;
2602
2611
  if (opts.decomposition) {
2603
2612
  const run = await runApi(
2604
2613
  "Reading run contract",
@@ -2616,6 +2625,8 @@ Examples:
2616
2625
  `run ${opts.decomposition} is awaiting task ${run.awaitingTaskId}, not ${opts.task} \u2014 refusing to mis-key the resume.`
2617
2626
  );
2618
2627
  matchTags = await_.matchTags;
2628
+ if (typeof await_.dispatchedTaskVersion === "number")
2629
+ dispatchedVersion = await_.dispatchedTaskVersion;
2619
2630
  } else {
2620
2631
  matchTags = [`wlp-task:${opts.task}`];
2621
2632
  }
@@ -2642,11 +2653,34 @@ Examples:
2642
2653
  }
2643
2654
  })
2644
2655
  );
2656
+ let toVersion;
2657
+ if (opts.toVersion !== void 0) {
2658
+ toVersion = Number(opts.toVersion);
2659
+ if (!Number.isInteger(toVersion) || toVersion < 1)
2660
+ fail(
2661
+ `--to-version must be an integer >= 1 \u2014 got '${opts.toVersion}'`
2662
+ );
2663
+ } else if (dispatchedVersion !== void 0) {
2664
+ toVersion = dispatchedVersion;
2665
+ } else {
2666
+ const task = await runApi(
2667
+ "Reading task version",
2668
+ async () => client.GET("/memories/{memoryId}", {
2669
+ params: { path: { memoryId: opts.task } }
2670
+ })
2671
+ );
2672
+ const v = task?.item?.currentVersion ?? task?.currentVersion;
2673
+ if (typeof v !== "number" || v < 1)
2674
+ fail(
2675
+ `could not read task ${opts.task} version to pin the Reference edge \u2014 pass --to-version <n>`
2676
+ );
2677
+ toVersion = v;
2678
+ }
2645
2679
  const edge = await runApi(
2646
2680
  "Wiring Reference edge",
2647
2681
  async () => client.POST("/memories/{memoryId}/relationships", {
2648
2682
  params: { path: { memoryId: closeout.id } },
2649
- body: { toMemoryId: opts.task, type: "Reference" }
2683
+ body: { toMemoryId: opts.task, type: "Reference", toVersion }
2650
2684
  })
2651
2685
  );
2652
2686
  let taskStatus;
@@ -2862,6 +2896,7 @@ function registerDecomposition(program2) {
2862
2896
  Examples:
2863
2897
  $ sechroom decomposition decompose mem_XXXX
2864
2898
  $ sechroom decomposition execute sug_XXXX
2899
+ $ sechroom decomposition publish-run sug_XXXX
2865
2900
  $ sechroom decomposition accept sug_XXXX
2866
2901
  $ sechroom decomposition reject sug_XXXX --reason "wrong shape"`
2867
2902
  );
@@ -2899,6 +2934,23 @@ Examples:
2899
2934
  cmd.optsWithGlobals().json
2900
2935
  );
2901
2936
  });
2937
+ decomposition.command("publish-run <decompositionId>").description(
2938
+ "Publish an accepted decomposition's context pack on demand (POST /decompositions/{id}/publish-run)"
2939
+ ).action(async (decompositionId, _opts, cmd) => {
2940
+ const cfg = resolveConfig(cmd.optsWithGlobals());
2941
+ const data = await runApi("Publishing context pack", async () => {
2942
+ const client = await makeClient(cfg);
2943
+ return client.POST("/decompositions/{id}/publish-run", {
2944
+ params: { path: { id: decompositionId } },
2945
+ body: {}
2946
+ });
2947
+ });
2948
+ emitAction(
2949
+ `published ${style.bold(decompositionId)} \u2192 run ${style.bold(data.runRecordId)} (${data.outcome})`,
2950
+ data,
2951
+ cmd.optsWithGlobals().json
2952
+ );
2953
+ });
2902
2954
  decomposition.command("accept <decompositionId>").description(
2903
2955
  "Accept a Pending decomposition \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
2904
2956
  ).action(async (decompositionId, _opts, cmd) => {
@@ -5759,7 +5811,7 @@ function registerTelemetry(program2) {
5759
5811
  );
5760
5812
  });
5761
5813
  telemetry.command("hook").description(
5762
- "Per-turn telemetry self-report for a Claude Code Stop hook (reads stdin; no-op unless bound). Fail-soft."
5814
+ "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."
5763
5815
  ).action(async (_opts, cmd) => {
5764
5816
  try {
5765
5817
  const raw = await readStdin2();
@@ -5768,21 +5820,10 @@ function registerTelemetry(program2) {
5768
5820
  const binding = findBinding(cwd);
5769
5821
  if (!binding) return process.exit(0);
5770
5822
  const usage = input.transcript_path ? parseTranscript(input.transcript_path) : null;
5771
- if (!usage) return process.exit(0);
5823
+ const events = buildHookEvents(input, usage, binding.taskId);
5824
+ if (events.length === 0) return process.exit(0);
5772
5825
  const cfg = resolveConfig(cmd.optsWithGlobals());
5773
- await postTelemetry(cfg, binding.decompositionId, [
5774
- {
5775
- taskId: binding.taskId,
5776
- kind: "Parsed",
5777
- tokensIn: usage.tokensIn,
5778
- tokensOut: usage.tokensOut,
5779
- contextUsed: usage.contextUsed,
5780
- contextWindow: usage.contextWindow,
5781
- text: null,
5782
- approvalState: null,
5783
- verdict: null
5784
- }
5785
- ]);
5826
+ await postTelemetry(cfg, binding.decompositionId, events);
5786
5827
  return process.exit(0);
5787
5828
  } catch {
5788
5829
  return process.exit(0);
@@ -5810,7 +5851,12 @@ function registerTelemetry(program2) {
5810
5851
  scope,
5811
5852
  cwd
5812
5853
  });
5813
- const commands = { Stop: "sechroom telemetry hook" };
5854
+ const commands = {
5855
+ Stop: "sechroom telemetry hook",
5856
+ SubagentStop: "sechroom telemetry hook",
5857
+ Notification: "sechroom telemetry hook",
5858
+ PermissionDenied: "sechroom telemetry hook"
5859
+ };
5814
5860
  try {
5815
5861
  const multi = targets.length > 1;
5816
5862
  const results = targets.map((t) => {
@@ -5911,6 +5957,55 @@ function windowFor(model, contextUsed = 0) {
5911
5957
  if (m.includes("[1m]") || m.includes("-1m")) return 1e6;
5912
5958
  return contextUsed > 2e5 ? 1e6 : 2e5;
5913
5959
  }
5960
+ function buildHookEvents(input, usage, taskId) {
5961
+ const events = [];
5962
+ const base = (kind, over) => ({
5963
+ taskId,
5964
+ kind,
5965
+ tokensIn: null,
5966
+ tokensOut: null,
5967
+ contextUsed: null,
5968
+ contextWindow: null,
5969
+ text: null,
5970
+ approvalState: null,
5971
+ verdict: null,
5972
+ ...over
5973
+ });
5974
+ if (usage) {
5975
+ events.push(
5976
+ base("Parsed", {
5977
+ tokensIn: usage.tokensIn,
5978
+ tokensOut: usage.tokensOut,
5979
+ contextUsed: usage.contextUsed,
5980
+ contextWindow: usage.contextWindow
5981
+ })
5982
+ );
5983
+ }
5984
+ switch (input.hook_event_name) {
5985
+ case "PermissionDenied":
5986
+ events.push(
5987
+ base("Approval", {
5988
+ approvalState: "denied",
5989
+ text: input.tool_name ?? input.message ?? null
5990
+ })
5991
+ );
5992
+ break;
5993
+ case "Notification":
5994
+ if (isPermissionNotification(input))
5995
+ events.push(base("Approval", { text: input.message ?? null }));
5996
+ break;
5997
+ case "Stop":
5998
+ case "SubagentStop":
5999
+ events.push(base("Terminal", { text: input.last_assistant_message ?? null }));
6000
+ break;
6001
+ }
6002
+ return events;
6003
+ }
6004
+ function isPermissionNotification(input) {
6005
+ const t = (input.notification_type ?? input.type ?? "").toLowerCase();
6006
+ if (t) return t.includes("permission");
6007
+ return (input.message ?? "").toLowerCase().includes("permission");
6008
+ }
5914
6009
  async function readStdin2() {
5915
6010
  if (process.stdin.isTTY) return "";
5916
6011
  const chunks = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.11",
3
+ "version": "2026.7.13",
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",
@@ -36,7 +36,8 @@
36
36
  "openapi-typescript": "^7.4.0",
37
37
  "tsup": "^8.3.0",
38
38
  "tsx": "^4.19.0",
39
- "typescript": "^5.6.0"
39
+ "typescript": "^5.6.0",
40
+ "typescript-7": "npm:typescript@^7.0.2"
40
41
  },
41
42
  "scripts": {
42
43
  "gen": "openapi-typescript \"${SECHROOM_OPENAPI_URL:-https://app.sechroom.ai/api/openapi/v1.json}\" -o src/generated/api.d.ts",