@sechroom/cli 2026.7.25 → 2026.7.27

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 +126 -37
  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) {
@@ -3556,29 +3556,29 @@ Examples:
3556
3556
  });
3557
3557
  }
3558
3558
 
3559
- // src/commands/decomposition.ts
3559
+ // src/commands/work-plan.ts
3560
3560
  import { readFile } from "fs/promises";
3561
- function registerDecomposition(program2) {
3562
- const decomposition = program2.command("decomposition").description(
3563
- "Drive a WLP decomposition: decompose a brief, then execute / accept / reject"
3561
+ function registerWorkPlan(program2) {
3562
+ const workPlan = program2.command("work-plan").description(
3563
+ "Drive a work plan: create one from a brief, then execute / accept / reject"
3564
3564
  );
3565
- decomposition.addHelpText(
3565
+ workPlan.addHelpText(
3566
3566
  "after",
3567
3567
  `
3568
3568
  Examples:
3569
- $ sechroom decomposition decompose mem_XXXX
3570
- $ sechroom decomposition from-plan mem_XXXX --file plan.json
3571
- $ sechroom decomposition plan wlp_XXXX
3572
- $ sechroom decomposition execute sug_XXXX
3573
- $ sechroom decomposition publish-run sug_XXXX
3574
- $ sechroom decomposition accept sug_XXXX
3575
- $ sechroom decomposition reject sug_XXXX --reason "wrong shape"`
3569
+ $ sechroom work-plan create mem_XXXX
3570
+ $ sechroom work-plan create-from-tasks mem_XXXX --file plan.json
3571
+ $ sechroom work-plan get-plan wlp_XXXX
3572
+ $ sechroom work-plan execute wlp_XXXX
3573
+ $ sechroom work-plan publish-context-pack wlp_XXXX
3574
+ $ sechroom work-plan accept wlp_XXXX
3575
+ $ sechroom work-plan reject wlp_XXXX --reason "wrong shape"`
3576
3576
  );
3577
- decomposition.command("decompose <briefId>").description(
3578
- "Decompose a work brief into a candidate Task graph (POST /work-briefs/{id}/decompose)"
3577
+ workPlan.command("create <briefId>").description(
3578
+ "Create a work plan from a work brief \u2014 a candidate Task graph (POST /work-briefs/{id}/decompose)"
3579
3579
  ).action(async (briefId, _opts, cmd) => {
3580
3580
  const cfg = resolveConfig(cmd.optsWithGlobals());
3581
- const data = await runApi("Queueing decomposition", async () => {
3581
+ const data = await runApi("Creating work plan", async () => {
3582
3582
  const client = await makeClient(cfg);
3583
3583
  return client.POST("/work-briefs/{id}/decompose", {
3584
3584
  params: { path: { id: briefId } },
@@ -3586,13 +3586,13 @@ Examples:
3586
3586
  });
3587
3587
  });
3588
3588
  emitAction(
3589
- `queued decomposition of ${style.bold(briefId)} \u2192 ${style.bold(data.suggestionId)}`,
3589
+ `created work plan from ${style.bold(briefId)} \u2192 ${style.bold(data.suggestionId)}`,
3590
3590
  data,
3591
3591
  cmd.optsWithGlobals().json
3592
3592
  );
3593
3593
  });
3594
- decomposition.command("from-plan <briefId>").description(
3595
- "Create a decomposition from a hand-authored JSON plan; tasks may carry Unix-millisecond wakeAtMs"
3594
+ workPlan.command("create-from-tasks <briefId>").description(
3595
+ "Create a work plan from a hand-authored JSON task list; tasks may carry Unix-millisecond wakeAtMs"
3596
3596
  ).requiredOption(
3597
3597
  "--file <path>",
3598
3598
  "JSON file containing { project, tasks, source? }; use - for stdin"
@@ -3601,7 +3601,7 @@ Examples:
3601
3601
  const body = parsePlanInput(raw, opts.file);
3602
3602
  const cfg = resolveConfig(cmd.optsWithGlobals());
3603
3603
  const data = await runApi(
3604
- "Creating decomposition from plan",
3604
+ "Creating work plan from tasks",
3605
3605
  async () => {
3606
3606
  const client = await makeClient(cfg);
3607
3607
  return client.POST("/work-briefs/{id}/decompose-from-plan", {
@@ -3616,11 +3616,11 @@ Examples:
3616
3616
  cmd.optsWithGlobals().json
3617
3617
  );
3618
3618
  });
3619
- decomposition.command("plan <decompositionId>").description(
3619
+ workPlan.command("get-plan <decompositionId>").description(
3620
3620
  "Inspect the compiled plan, including each scheduled step's Unix-millisecond wakeAtMs"
3621
3621
  ).action(async (decompositionId, _opts, cmd) => {
3622
3622
  const cfg = resolveConfig(cmd.optsWithGlobals());
3623
- const data = await runApi("Reading decomposition plan", async () => {
3623
+ const data = await runApi("Reading work plan", async () => {
3624
3624
  const client = await makeClient(cfg);
3625
3625
  return client.GET("/decompositions/{id}/plan", {
3626
3626
  params: { path: { id: decompositionId } }
@@ -3632,11 +3632,11 @@ Examples:
3632
3632
  cmd.optsWithGlobals().json
3633
3633
  );
3634
3634
  });
3635
- decomposition.command("execute <decompositionId>").description(
3636
- "Execute a decomposition's Task graph (POST /decompositions/{id}/execute)"
3635
+ workPlan.command("execute <decompositionId>").description(
3636
+ "Execute a work plan's Task graph (POST /decompositions/{id}/execute)"
3637
3637
  ).action(async (decompositionId, _opts, cmd) => {
3638
3638
  const cfg = resolveConfig(cmd.optsWithGlobals());
3639
- const data = await runApi("Executing decomposition", async () => {
3639
+ const data = await runApi("Executing work plan", async () => {
3640
3640
  const client = await makeClient(cfg);
3641
3641
  return client.POST("/decompositions/{id}/execute", {
3642
3642
  params: { path: { id: decompositionId } },
@@ -3649,8 +3649,8 @@ Examples:
3649
3649
  cmd.optsWithGlobals().json
3650
3650
  );
3651
3651
  });
3652
- decomposition.command("publish-run <decompositionId>").description(
3653
- "Publish an accepted decomposition's context pack on demand (POST /decompositions/{id}/publish-run)"
3652
+ workPlan.command("publish-context-pack <decompositionId>").description(
3653
+ "Publish an accepted work plan's context pack on demand (POST /decompositions/{id}/publish-run)"
3654
3654
  ).action(async (decompositionId, _opts, cmd) => {
3655
3655
  const cfg = resolveConfig(cmd.optsWithGlobals());
3656
3656
  const data = await runApi("Publishing context pack", async () => {
@@ -3666,11 +3666,11 @@ Examples:
3666
3666
  cmd.optsWithGlobals().json
3667
3667
  );
3668
3668
  });
3669
- decomposition.command("accept <decompositionId>").description(
3670
- "Accept a Pending decomposition \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
3669
+ workPlan.command("accept <decompositionId>").description(
3670
+ "Accept a Pending work plan \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
3671
3671
  ).action(async (decompositionId, _opts, cmd) => {
3672
3672
  const cfg = resolveConfig(cmd.optsWithGlobals());
3673
- const data = await runApi("Accepting decomposition", async () => {
3673
+ const data = await runApi("Accepting work plan", async () => {
3674
3674
  const client = await makeClient(cfg);
3675
3675
  return client.POST("/decompositions/{id}/accept", {
3676
3676
  params: { path: { id: decompositionId } },
@@ -3678,16 +3678,16 @@ Examples:
3678
3678
  });
3679
3679
  });
3680
3680
  emitAction(
3681
- `accepted decomposition ${style.bold(decompositionId)}`,
3681
+ `accepted work plan ${style.bold(decompositionId)}`,
3682
3682
  data,
3683
3683
  cmd.optsWithGlobals().json
3684
3684
  );
3685
3685
  });
3686
- decomposition.command("reject <decompositionId>").description(
3687
- "Reject a Pending decomposition \u2014 archive its Tasks, bounce the brief (POST /decompositions/{id}/reject)"
3686
+ workPlan.command("reject <decompositionId>").description(
3687
+ "Reject a Pending work plan \u2014 archive its Tasks, bounce the brief (POST /decompositions/{id}/reject)"
3688
3688
  ).option("--reason <reason>", "Optional free-text rejection reason").action(async (decompositionId, opts, cmd) => {
3689
3689
  const cfg = resolveConfig(cmd.optsWithGlobals());
3690
- const data = await runApi("Rejecting decomposition", async () => {
3690
+ const data = await runApi("Rejecting work plan", async () => {
3691
3691
  const client = await makeClient(cfg);
3692
3692
  return client.POST("/decompositions/{id}/reject", {
3693
3693
  params: { path: { id: decompositionId } },
@@ -3695,7 +3695,7 @@ Examples:
3695
3695
  });
3696
3696
  });
3697
3697
  emitAction(
3698
- `rejected decomposition ${style.bold(decompositionId)}`,
3698
+ `rejected work plan ${style.bold(decompositionId)}`,
3699
3699
  data,
3700
3700
  cmd.optsWithGlobals().json
3701
3701
  );
@@ -6511,6 +6511,23 @@ function registerTelemetry(program2) {
6511
6511
  );
6512
6512
  }
6513
6513
  });
6514
+ telemetry.command("show <decompositionId>").description(
6515
+ "Read a run's telemetry \u2014 per-task meters + the raw/parsed/approval/terminal timeline (GET /decompositions/{id}/run/telemetry). Returns hasTelemetry:false, not an error, before any event is ingested \u2014 so an unstarted run and a stalled one read differently. The read side of this group; `emit` is the source side. (FR-sechroom-442 slice 1 step 4; mirrors the work_plan_run_telemetry MCP tool.)"
6516
+ ).action(async (decompositionId, _opts, cmd) => {
6517
+ const globals = cmd.optsWithGlobals();
6518
+ const cfg = resolveConfig(globals);
6519
+ const data = await runApi("Reading run telemetry", async () => {
6520
+ const client = await makeClient(cfg);
6521
+ return client.GET("/decompositions/{id}/run/telemetry", {
6522
+ params: { path: { id: decompositionId } }
6523
+ });
6524
+ });
6525
+ emitAction(
6526
+ data.hasTelemetry ? `read telemetry for ${style.bold(decompositionId)}` : `no telemetry yet for ${style.bold(decompositionId)} (run not started or not yet reporting)`,
6527
+ data,
6528
+ globals.json
6529
+ );
6530
+ });
6514
6531
  telemetry.command("bind").description(
6515
6532
  "Bind this checkout to a decomposition+task so the Stop hook auto-emits per-turn telemetry"
6516
6533
  ).requiredOption(
@@ -7025,6 +7042,77 @@ function capitalize(value) {
7025
7042
  return value.charAt(0).toUpperCase() + value.slice(1);
7026
7043
  }
7027
7044
 
7045
+ // src/commands/work-task.ts
7046
+ function registerWorkTask(program2) {
7047
+ const workTask = program2.command("work-task").description("Read work tasks and close them out (list \xB7 card \xB7 mark-no-residue)");
7048
+ workTask.addHelpText(
7049
+ "after",
7050
+ `
7051
+ Examples:
7052
+ $ sechroom work-task list --status in-progress --lane claude-code-chris
7053
+ $ sechroom work-task card mem_XXXX
7054
+ $ sechroom work-task mark-no-residue mem_XXXX --decomposition wlp_XXXX`
7055
+ );
7056
+ workTask.command("list").description("List work tasks, newest-first (GET /work-tasks)").option("--shape <shape>", "Filter: bare | managed").option("--lane <lane>", "Filter by dispatch-lane value, e.g. claude-code-chris").option("--status <status>", "Filter by status value, e.g. in-progress").option("--page <n>", "1-based page number", (v) => Number.parseInt(v, 10)).option(
7057
+ "--page-size <n>",
7058
+ "Page size (default 50, capped 200)",
7059
+ (v) => Number.parseInt(v, 10)
7060
+ ).action(async (opts, cmd) => {
7061
+ const globals = cmd.optsWithGlobals();
7062
+ const cfg = resolveConfig(globals);
7063
+ const data = await runApi("Listing work tasks", async () => {
7064
+ const client = await makeClient(cfg);
7065
+ return client.GET("/work-tasks", {
7066
+ params: {
7067
+ query: {
7068
+ shape: opts.shape,
7069
+ lane: opts.lane,
7070
+ status: opts.status,
7071
+ page: opts.page,
7072
+ pageSize: opts.pageSize
7073
+ }
7074
+ }
7075
+ });
7076
+ });
7077
+ emitAction(
7078
+ `listed ${style.bold(String(data.items.length))} of ${data.count} task(s)`,
7079
+ data,
7080
+ globals.json
7081
+ );
7082
+ });
7083
+ workTask.command("card <taskId>").description("Read one task's runnable card \u2014 its full working instructions (GET /tasks/{id}/card)").action(async (taskId, _opts, cmd) => {
7084
+ const globals = cmd.optsWithGlobals();
7085
+ const cfg = resolveConfig(globals);
7086
+ const data = await runApi("Reading task card", async () => {
7087
+ const client = await makeClient(cfg);
7088
+ return client.GET("/tasks/{id}/card", {
7089
+ params: { path: { id: taskId } }
7090
+ });
7091
+ });
7092
+ emitAction(`read card ${style.bold(taskId)}`, data, globals.json);
7093
+ });
7094
+ workTask.command("mark-no-residue <taskId>").description(
7095
+ "Mark a task as having produced no residue \u2014 the explicit no-residue marker that lets a lane finish a run it drove end-to-end (POST /work-tasks/mark-no-residue)"
7096
+ ).requiredOption(
7097
+ "--decomposition <id>",
7098
+ "The work plan id (wlp_\u2026) the task belongs to \u2014 the marker is keyed (decompositionId, taskId)"
7099
+ ).action(async (taskId, opts, cmd) => {
7100
+ const globals = cmd.optsWithGlobals();
7101
+ const cfg = resolveConfig(globals);
7102
+ const data = await runApi("Marking task no-residue", async () => {
7103
+ const client = await makeClient(cfg);
7104
+ return client.POST("/work-tasks/mark-no-residue", {
7105
+ body: { taskId, decompositionId: opts.decomposition }
7106
+ });
7107
+ });
7108
+ emitAction(
7109
+ `marked ${style.bold(taskId)} no-residue (minted: ${data.minted})`,
7110
+ data,
7111
+ globals.json
7112
+ );
7113
+ });
7114
+ }
7115
+
7028
7116
  // src/index.ts
7029
7117
  function resolveVersion() {
7030
7118
  try {
@@ -7188,7 +7276,8 @@ registerRelationships(program);
7188
7276
  registerWorkspace(program);
7189
7277
  registerProject(program);
7190
7278
  registerWorkBrief(program);
7191
- registerDecomposition(program);
7279
+ registerWorkTask(program);
7280
+ registerWorkPlan(program);
7192
7281
  registerExecutor(program);
7193
7282
  registerClose(program);
7194
7283
  registerFiling(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.25",
3
+ "version": "2026.7.27",
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",