@sechroom/cli 2026.7.26 → 2026.7.28

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 +160 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3556,29 +3556,30 @@ 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 append wlp_XXXX --file append.json
3572
+ $ sechroom work-plan get-plan wlp_XXXX
3573
+ $ sechroom work-plan execute wlp_XXXX
3574
+ $ sechroom work-plan publish-context-pack wlp_XXXX
3575
+ $ sechroom work-plan accept wlp_XXXX
3576
+ $ sechroom work-plan reject wlp_XXXX --reason "wrong shape"`
3576
3577
  );
3577
- decomposition.command("decompose <briefId>").description(
3578
- "Decompose a work brief into a candidate Task graph (POST /work-briefs/{id}/decompose)"
3578
+ workPlan.command("create <briefId>").description(
3579
+ "Create a work plan from a work brief \u2014 a candidate Task graph (POST /work-briefs/{id}/decompose)"
3579
3580
  ).action(async (briefId, _opts, cmd) => {
3580
3581
  const cfg = resolveConfig(cmd.optsWithGlobals());
3581
- const data = await runApi("Queueing decomposition", async () => {
3582
+ const data = await runApi("Creating work plan", async () => {
3582
3583
  const client = await makeClient(cfg);
3583
3584
  return client.POST("/work-briefs/{id}/decompose", {
3584
3585
  params: { path: { id: briefId } },
@@ -3586,13 +3587,13 @@ Examples:
3586
3587
  });
3587
3588
  });
3588
3589
  emitAction(
3589
- `queued decomposition of ${style.bold(briefId)} \u2192 ${style.bold(data.suggestionId)}`,
3590
+ `created work plan from ${style.bold(briefId)} \u2192 ${style.bold(data.suggestionId)}`,
3590
3591
  data,
3591
3592
  cmd.optsWithGlobals().json
3592
3593
  );
3593
3594
  });
3594
- decomposition.command("from-plan <briefId>").description(
3595
- "Create a decomposition from a hand-authored JSON plan; tasks may carry Unix-millisecond wakeAtMs"
3595
+ workPlan.command("create-from-tasks <briefId>").description(
3596
+ "Create a work plan from a hand-authored JSON task list; tasks may carry Unix-millisecond wakeAtMs"
3596
3597
  ).requiredOption(
3597
3598
  "--file <path>",
3598
3599
  "JSON file containing { project, tasks, source? }; use - for stdin"
@@ -3601,7 +3602,7 @@ Examples:
3601
3602
  const body = parsePlanInput(raw, opts.file);
3602
3603
  const cfg = resolveConfig(cmd.optsWithGlobals());
3603
3604
  const data = await runApi(
3604
- "Creating decomposition from plan",
3605
+ "Creating work plan from tasks",
3605
3606
  async () => {
3606
3607
  const client = await makeClient(cfg);
3607
3608
  return client.POST("/work-briefs/{id}/decompose-from-plan", {
@@ -3616,11 +3617,33 @@ Examples:
3616
3617
  cmd.optsWithGlobals().json
3617
3618
  );
3618
3619
  });
3619
- decomposition.command("plan <decompositionId>").description(
3620
+ workPlan.command("append <decompositionId>").description(
3621
+ "Append hand-authored task(s) to a running work plan; they land proposed until ratified (POST /decompositions/{id}/append-tasks)"
3622
+ ).requiredOption(
3623
+ "--file <path>",
3624
+ "JSON file containing { tasks, gates? } (the AppendTasksInput shape); use - for stdin"
3625
+ ).action(async (decompositionId, opts, cmd) => {
3626
+ const raw = opts.file === "-" ? await readStdin2() : await readFile(opts.file, "utf8");
3627
+ const body = parseAppendInput(raw, opts.file);
3628
+ const cfg = resolveConfig(cmd.optsWithGlobals());
3629
+ const data = await runApi("Appending tasks to work plan", async () => {
3630
+ const client = await makeClient(cfg);
3631
+ return client.POST("/decompositions/{id}/append-tasks", {
3632
+ params: { path: { id: decompositionId } },
3633
+ body
3634
+ });
3635
+ });
3636
+ emitAction(
3637
+ `appended ${style.bold(String(data.appendedTaskIds.length))} task(s) to ${style.bold(decompositionId)}`,
3638
+ data,
3639
+ cmd.optsWithGlobals().json
3640
+ );
3641
+ });
3642
+ workPlan.command("get-plan <decompositionId>").description(
3620
3643
  "Inspect the compiled plan, including each scheduled step's Unix-millisecond wakeAtMs"
3621
3644
  ).action(async (decompositionId, _opts, cmd) => {
3622
3645
  const cfg = resolveConfig(cmd.optsWithGlobals());
3623
- const data = await runApi("Reading decomposition plan", async () => {
3646
+ const data = await runApi("Reading work plan", async () => {
3624
3647
  const client = await makeClient(cfg);
3625
3648
  return client.GET("/decompositions/{id}/plan", {
3626
3649
  params: { path: { id: decompositionId } }
@@ -3632,11 +3655,11 @@ Examples:
3632
3655
  cmd.optsWithGlobals().json
3633
3656
  );
3634
3657
  });
3635
- decomposition.command("execute <decompositionId>").description(
3636
- "Execute a decomposition's Task graph (POST /decompositions/{id}/execute)"
3658
+ workPlan.command("execute <decompositionId>").description(
3659
+ "Execute a work plan's Task graph (POST /decompositions/{id}/execute)"
3637
3660
  ).action(async (decompositionId, _opts, cmd) => {
3638
3661
  const cfg = resolveConfig(cmd.optsWithGlobals());
3639
- const data = await runApi("Executing decomposition", async () => {
3662
+ const data = await runApi("Executing work plan", async () => {
3640
3663
  const client = await makeClient(cfg);
3641
3664
  return client.POST("/decompositions/{id}/execute", {
3642
3665
  params: { path: { id: decompositionId } },
@@ -3649,8 +3672,8 @@ Examples:
3649
3672
  cmd.optsWithGlobals().json
3650
3673
  );
3651
3674
  });
3652
- decomposition.command("publish-run <decompositionId>").description(
3653
- "Publish an accepted decomposition's context pack on demand (POST /decompositions/{id}/publish-run)"
3675
+ workPlan.command("publish-context-pack <decompositionId>").description(
3676
+ "Publish an accepted work plan's context pack on demand (POST /decompositions/{id}/publish-run)"
3654
3677
  ).action(async (decompositionId, _opts, cmd) => {
3655
3678
  const cfg = resolveConfig(cmd.optsWithGlobals());
3656
3679
  const data = await runApi("Publishing context pack", async () => {
@@ -3666,11 +3689,11 @@ Examples:
3666
3689
  cmd.optsWithGlobals().json
3667
3690
  );
3668
3691
  });
3669
- decomposition.command("accept <decompositionId>").description(
3670
- "Accept a Pending decomposition \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
3692
+ workPlan.command("accept <decompositionId>").description(
3693
+ "Accept a Pending work plan \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
3671
3694
  ).action(async (decompositionId, _opts, cmd) => {
3672
3695
  const cfg = resolveConfig(cmd.optsWithGlobals());
3673
- const data = await runApi("Accepting decomposition", async () => {
3696
+ const data = await runApi("Accepting work plan", async () => {
3674
3697
  const client = await makeClient(cfg);
3675
3698
  return client.POST("/decompositions/{id}/accept", {
3676
3699
  params: { path: { id: decompositionId } },
@@ -3678,16 +3701,16 @@ Examples:
3678
3701
  });
3679
3702
  });
3680
3703
  emitAction(
3681
- `accepted decomposition ${style.bold(decompositionId)}`,
3704
+ `accepted work plan ${style.bold(decompositionId)}`,
3682
3705
  data,
3683
3706
  cmd.optsWithGlobals().json
3684
3707
  );
3685
3708
  });
3686
- decomposition.command("reject <decompositionId>").description(
3687
- "Reject a Pending decomposition \u2014 archive its Tasks, bounce the brief (POST /decompositions/{id}/reject)"
3709
+ workPlan.command("reject <decompositionId>").description(
3710
+ "Reject a Pending work plan \u2014 archive its Tasks, bounce the brief (POST /decompositions/{id}/reject)"
3688
3711
  ).option("--reason <reason>", "Optional free-text rejection reason").action(async (decompositionId, opts, cmd) => {
3689
3712
  const cfg = resolveConfig(cmd.optsWithGlobals());
3690
- const data = await runApi("Rejecting decomposition", async () => {
3713
+ const data = await runApi("Rejecting work plan", async () => {
3691
3714
  const client = await makeClient(cfg);
3692
3715
  return client.POST("/decompositions/{id}/reject", {
3693
3716
  params: { path: { id: decompositionId } },
@@ -3695,12 +3718,25 @@ Examples:
3695
3718
  });
3696
3719
  });
3697
3720
  emitAction(
3698
- `rejected decomposition ${style.bold(decompositionId)}`,
3721
+ `rejected work plan ${style.bold(decompositionId)}`,
3699
3722
  data,
3700
3723
  cmd.optsWithGlobals().json
3701
3724
  );
3702
3725
  });
3703
3726
  }
3727
+ function parseAppendInput(raw, sourceName = "append input") {
3728
+ let value;
3729
+ try {
3730
+ value = JSON.parse(raw);
3731
+ } catch (error) {
3732
+ throw new Error(
3733
+ `${sourceName} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
3734
+ );
3735
+ }
3736
+ if (!value || typeof value !== "object" || !Array.isArray(value.tasks))
3737
+ throw new Error(`${sourceName} must contain an object with a tasks array`);
3738
+ return value;
3739
+ }
3704
3740
  function parsePlanInput(raw, sourceName = "plan input") {
3705
3741
  let value;
3706
3742
  try {
@@ -6511,6 +6547,23 @@ function registerTelemetry(program2) {
6511
6547
  );
6512
6548
  }
6513
6549
  });
6550
+ telemetry.command("show <decompositionId>").description(
6551
+ "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.)"
6552
+ ).action(async (decompositionId, _opts, cmd) => {
6553
+ const globals = cmd.optsWithGlobals();
6554
+ const cfg = resolveConfig(globals);
6555
+ const data = await runApi("Reading run telemetry", async () => {
6556
+ const client = await makeClient(cfg);
6557
+ return client.GET("/decompositions/{id}/run/telemetry", {
6558
+ params: { path: { id: decompositionId } }
6559
+ });
6560
+ });
6561
+ emitAction(
6562
+ data.hasTelemetry ? `read telemetry for ${style.bold(decompositionId)}` : `no telemetry yet for ${style.bold(decompositionId)} (run not started or not yet reporting)`,
6563
+ data,
6564
+ globals.json
6565
+ );
6566
+ });
6514
6567
  telemetry.command("bind").description(
6515
6568
  "Bind this checkout to a decomposition+task so the Stop hook auto-emits per-turn telemetry"
6516
6569
  ).requiredOption(
@@ -7025,6 +7078,77 @@ function capitalize(value) {
7025
7078
  return value.charAt(0).toUpperCase() + value.slice(1);
7026
7079
  }
7027
7080
 
7081
+ // src/commands/work-task.ts
7082
+ function registerWorkTask(program2) {
7083
+ const workTask = program2.command("work-task").description("Read work tasks and close them out (list \xB7 card \xB7 mark-no-residue)");
7084
+ workTask.addHelpText(
7085
+ "after",
7086
+ `
7087
+ Examples:
7088
+ $ sechroom work-task list --status in-progress --lane claude-code-chris
7089
+ $ sechroom work-task card mem_XXXX
7090
+ $ sechroom work-task mark-no-residue mem_XXXX --decomposition wlp_XXXX`
7091
+ );
7092
+ 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(
7093
+ "--page-size <n>",
7094
+ "Page size (default 50, capped 200)",
7095
+ (v) => Number.parseInt(v, 10)
7096
+ ).action(async (opts, cmd) => {
7097
+ const globals = cmd.optsWithGlobals();
7098
+ const cfg = resolveConfig(globals);
7099
+ const data = await runApi("Listing work tasks", async () => {
7100
+ const client = await makeClient(cfg);
7101
+ return client.GET("/work-tasks", {
7102
+ params: {
7103
+ query: {
7104
+ shape: opts.shape,
7105
+ lane: opts.lane,
7106
+ status: opts.status,
7107
+ page: opts.page,
7108
+ pageSize: opts.pageSize
7109
+ }
7110
+ }
7111
+ });
7112
+ });
7113
+ emitAction(
7114
+ `listed ${style.bold(String(data.items.length))} of ${data.count} task(s)`,
7115
+ data,
7116
+ globals.json
7117
+ );
7118
+ });
7119
+ 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) => {
7120
+ const globals = cmd.optsWithGlobals();
7121
+ const cfg = resolveConfig(globals);
7122
+ const data = await runApi("Reading task card", async () => {
7123
+ const client = await makeClient(cfg);
7124
+ return client.GET("/tasks/{id}/card", {
7125
+ params: { path: { id: taskId } }
7126
+ });
7127
+ });
7128
+ emitAction(`read card ${style.bold(taskId)}`, data, globals.json);
7129
+ });
7130
+ workTask.command("mark-no-residue <taskId>").description(
7131
+ "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)"
7132
+ ).requiredOption(
7133
+ "--decomposition <id>",
7134
+ "The work plan id (wlp_\u2026) the task belongs to \u2014 the marker is keyed (decompositionId, taskId)"
7135
+ ).action(async (taskId, opts, cmd) => {
7136
+ const globals = cmd.optsWithGlobals();
7137
+ const cfg = resolveConfig(globals);
7138
+ const data = await runApi("Marking task no-residue", async () => {
7139
+ const client = await makeClient(cfg);
7140
+ return client.POST("/work-tasks/mark-no-residue", {
7141
+ body: { taskId, decompositionId: opts.decomposition }
7142
+ });
7143
+ });
7144
+ emitAction(
7145
+ `marked ${style.bold(taskId)} no-residue (minted: ${data.minted})`,
7146
+ data,
7147
+ globals.json
7148
+ );
7149
+ });
7150
+ }
7151
+
7028
7152
  // src/index.ts
7029
7153
  function resolveVersion() {
7030
7154
  try {
@@ -7188,7 +7312,8 @@ registerRelationships(program);
7188
7312
  registerWorkspace(program);
7189
7313
  registerProject(program);
7190
7314
  registerWorkBrief(program);
7191
- registerDecomposition(program);
7315
+ registerWorkTask(program);
7316
+ registerWorkPlan(program);
7192
7317
  registerExecutor(program);
7193
7318
  registerClose(program);
7194
7319
  registerFiling(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.26",
3
+ "version": "2026.7.28",
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",