@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.
- package/dist/index.js +160 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3556,29 +3556,30 @@ Examples:
|
|
|
3556
3556
|
});
|
|
3557
3557
|
}
|
|
3558
3558
|
|
|
3559
|
-
// src/commands/
|
|
3559
|
+
// src/commands/work-plan.ts
|
|
3560
3560
|
import { readFile } from "fs/promises";
|
|
3561
|
-
function
|
|
3562
|
-
const
|
|
3563
|
-
"Drive a
|
|
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
|
-
|
|
3565
|
+
workPlan.addHelpText(
|
|
3566
3566
|
"after",
|
|
3567
3567
|
`
|
|
3568
3568
|
Examples:
|
|
3569
|
-
$ sechroom
|
|
3570
|
-
$ sechroom
|
|
3571
|
-
$ sechroom
|
|
3572
|
-
$ sechroom
|
|
3573
|
-
$ sechroom
|
|
3574
|
-
$ sechroom
|
|
3575
|
-
$ sechroom
|
|
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
|
-
|
|
3578
|
-
"
|
|
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("
|
|
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
|
-
`
|
|
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
|
-
|
|
3595
|
-
"Create a
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3636
|
-
"Execute a
|
|
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
|
|
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
|
-
|
|
3653
|
-
"Publish an accepted
|
|
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
|
-
|
|
3670
|
-
"Accept a Pending
|
|
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
|
|
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
|
|
3704
|
+
`accepted work plan ${style.bold(decompositionId)}`,
|
|
3682
3705
|
data,
|
|
3683
3706
|
cmd.optsWithGlobals().json
|
|
3684
3707
|
);
|
|
3685
3708
|
});
|
|
3686
|
-
|
|
3687
|
-
"Reject a Pending
|
|
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
|
|
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
|
|
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
|
-
|
|
7315
|
+
registerWorkTask(program);
|
|
7316
|
+
registerWorkPlan(program);
|
|
7192
7317
|
registerExecutor(program);
|
|
7193
7318
|
registerClose(program);
|
|
7194
7319
|
registerFiling(program);
|
package/package.json
CHANGED