@sechroom/cli 2026.8.2 → 2026.8.3
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 +467 -176
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -642,6 +642,17 @@ function emitAction(summary, data, json) {
|
|
|
642
642
|
process.stdout.write(`${ok("\u2713")} ${summary}
|
|
643
643
|
`);
|
|
644
644
|
}
|
|
645
|
+
var JSON_NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
646
|
+
function confidenceWireValue(value) {
|
|
647
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
648
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
649
|
+
const raw = String(value).trim();
|
|
650
|
+
if (JSON_NUMBER.test(raw)) {
|
|
651
|
+
const numeric = Number(raw);
|
|
652
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
653
|
+
}
|
|
654
|
+
return String(value);
|
|
655
|
+
}
|
|
645
656
|
var GOVERNANCE_QUEUED_PROBLEM_TYPE = "https://sechroom.dev/problems/governance-review-queued";
|
|
646
657
|
function isGovernanceQueued(body) {
|
|
647
658
|
return typeof body === "object" && body !== null && "type" in body && body.type === GOVERNANCE_QUEUED_PROBLEM_TYPE;
|
|
@@ -688,23 +699,57 @@ function formatFailureMessage(error) {
|
|
|
688
699
|
if (error.cause instanceof Error && error.cause.message) {
|
|
689
700
|
msg += `: ${error.cause.message}`;
|
|
690
701
|
}
|
|
691
|
-
} else if (
|
|
702
|
+
} else if (isRecord(error)) {
|
|
692
703
|
const problem = error;
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
704
|
+
const title = typeof problem.title === "string" && problem.title.length > 0 ? problem.title : void 0;
|
|
705
|
+
const problemDetail = typeof problem.detail === "string" && problem.detail.length > 0 ? problem.detail : void 0;
|
|
706
|
+
const fieldErrors = formatProblemErrors(problem.errors);
|
|
707
|
+
const structuredErrors = fieldErrors.length === 0 ? formatStructuredViolations(problem.violations) : [];
|
|
708
|
+
const parts = [
|
|
709
|
+
...title ? [title] : [],
|
|
710
|
+
...problemDetail ? [problemDetail] : [],
|
|
711
|
+
...fieldErrors,
|
|
712
|
+
...structuredErrors
|
|
713
|
+
];
|
|
714
|
+
if (parts.length > 0) {
|
|
715
|
+
if (title && !problemDetail && fieldErrors.length === 0 && structuredErrors.length === 0) {
|
|
716
|
+
parts.push("No additional error detail was returned by the API.");
|
|
717
|
+
}
|
|
718
|
+
msg = parts.join("\n");
|
|
719
|
+
} else {
|
|
720
|
+
const serialized = JSON.stringify(error);
|
|
721
|
+
msg = serialized && serialized !== "{}" ? serialized : "The API returned an empty error response.";
|
|
700
722
|
}
|
|
701
|
-
} else if (typeof error === "object" && error !== null) {
|
|
702
|
-
msg = JSON.stringify(error);
|
|
703
723
|
} else {
|
|
704
724
|
msg = String(error);
|
|
705
725
|
}
|
|
706
726
|
return msg;
|
|
707
727
|
}
|
|
728
|
+
function isRecord(value) {
|
|
729
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
730
|
+
}
|
|
731
|
+
function formatProblemErrors(errors) {
|
|
732
|
+
if (!isRecord(errors)) return [];
|
|
733
|
+
return Object.entries(errors).flatMap(
|
|
734
|
+
([field, messages]) => (Array.isArray(messages) ? messages : [messages]).map(
|
|
735
|
+
(message) => ` ${field}: ${formatErrorValue(message)}`
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
function formatStructuredViolations(violations) {
|
|
740
|
+
if (!Array.isArray(violations)) return [];
|
|
741
|
+
return violations.flatMap((violation) => {
|
|
742
|
+
if (!isRecord(violation)) return [formatErrorValue(violation)];
|
|
743
|
+
const field = typeof violation.field === "string" ? violation.field : "validation";
|
|
744
|
+
const message = typeof violation.message === "string" ? violation.message : formatErrorValue(violation);
|
|
745
|
+
return [` ${field}: ${message}`];
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
function formatErrorValue(value) {
|
|
749
|
+
if (typeof value === "string") return value;
|
|
750
|
+
const serialized = JSON.stringify(value);
|
|
751
|
+
return serialized ?? String(value);
|
|
752
|
+
}
|
|
708
753
|
function fail(error) {
|
|
709
754
|
const msg = formatFailureMessage(error);
|
|
710
755
|
process.stderr.write(`error: ${msg}
|
|
@@ -5798,6 +5843,7 @@ Call sechroom_lifecycle_signal at each phase boundary (start/work/verify/closeou
|
|
|
5798
5843
|
}
|
|
5799
5844
|
|
|
5800
5845
|
// src/commands/executor.ts
|
|
5846
|
+
var DEFAULT_CLAIM_POLICY = "restricted";
|
|
5801
5847
|
function executorSubscriptionInput(name) {
|
|
5802
5848
|
return {
|
|
5803
5849
|
name,
|
|
@@ -5859,8 +5905,8 @@ function registerExecutor(program2) {
|
|
|
5859
5905
|
"Capability operation keys claimed by this instance"
|
|
5860
5906
|
).option(
|
|
5861
5907
|
"--claim-policy <policy>",
|
|
5862
|
-
"open | restricted (restricted only claims preferred/own-lane/allow-listed-tag work)",
|
|
5863
|
-
|
|
5908
|
+
"open | restricted (restricted only claims preferred/own-lane/allow-listed-tag work; default restricted)",
|
|
5909
|
+
DEFAULT_CLAIM_POLICY
|
|
5864
5910
|
).option(
|
|
5865
5911
|
"--claim-tag <tag...>",
|
|
5866
5912
|
"Task tag this instance accepts under --claim-policy restricted"
|
|
@@ -5875,7 +5921,12 @@ function registerExecutor(program2) {
|
|
|
5875
5921
|
"--subscription-name <name>",
|
|
5876
5922
|
"SignalR delivery binding name",
|
|
5877
5923
|
"executor-dispatch"
|
|
5878
|
-
).option(
|
|
5924
|
+
).option(
|
|
5925
|
+
"--ttl <seconds>",
|
|
5926
|
+
"Advertisement TTL (30 to tenant maximum)",
|
|
5927
|
+
parseInteger,
|
|
5928
|
+
600
|
|
5929
|
+
).option(
|
|
5879
5930
|
"--task-lease-ttl <seconds>",
|
|
5880
5931
|
"Task lease TTL (60-86400)",
|
|
5881
5932
|
parseInteger,
|
|
@@ -5962,7 +6013,7 @@ function registerExecutor(program2) {
|
|
|
5962
6013
|
taskLeaseTtlSeconds: opts.taskLeaseTtl,
|
|
5963
6014
|
modelId: opts.modelId,
|
|
5964
6015
|
effortLabel: opts.effortLabel,
|
|
5965
|
-
claimPolicy: (opts.claimPolicy
|
|
6016
|
+
claimPolicy: parseClaimPolicy(opts.claimPolicy) === "Restricted" ? "restricted" : "open",
|
|
5966
6017
|
claimTags: opts.claimTag ?? [],
|
|
5967
6018
|
excludeTags: opts.excludeTag ?? [],
|
|
5968
6019
|
relayId: opts.relay,
|
|
@@ -6083,8 +6134,8 @@ function registerExecutor(program2) {
|
|
|
6083
6134
|
"Capability operation keys claimed by this instance"
|
|
6084
6135
|
).option(
|
|
6085
6136
|
"--claim-policy <policy>",
|
|
6086
|
-
"open | restricted (restricted only claims preferred/own-lane/allow-listed-tag work)",
|
|
6087
|
-
|
|
6137
|
+
"open | restricted (restricted only claims preferred/own-lane/allow-listed-tag work; default restricted)",
|
|
6138
|
+
DEFAULT_CLAIM_POLICY
|
|
6088
6139
|
).option(
|
|
6089
6140
|
"--claim-tag <tag...>",
|
|
6090
6141
|
"Task tag this instance accepts under --claim-policy restricted"
|
|
@@ -6095,7 +6146,12 @@ function registerExecutor(program2) {
|
|
|
6095
6146
|
"--activation-mode <mode>",
|
|
6096
6147
|
"attached | detached \u2014 detached marks a fleet run as a service that outlives its shell (default attached)",
|
|
6097
6148
|
"attached"
|
|
6098
|
-
).option(
|
|
6149
|
+
).option(
|
|
6150
|
+
"--ttl <seconds>",
|
|
6151
|
+
"Advertisement TTL (30 to tenant maximum)",
|
|
6152
|
+
parseInteger,
|
|
6153
|
+
120
|
|
6154
|
+
).option(
|
|
6099
6155
|
"--task-lease-ttl <seconds>",
|
|
6100
6156
|
"Task lease TTL (60-86400)",
|
|
6101
6157
|
parseInteger,
|
|
@@ -6151,7 +6207,12 @@ function registerExecutor(program2) {
|
|
|
6151
6207
|
`)
|
|
6152
6208
|
);
|
|
6153
6209
|
});
|
|
6154
|
-
executor.command("refresh <id>").description("Refresh one advertisement lease once").option(
|
|
6210
|
+
executor.command("refresh <id>").description("Refresh one advertisement lease once").option(
|
|
6211
|
+
"--ttl <seconds>",
|
|
6212
|
+
"Advertisement TTL (30 to tenant maximum)",
|
|
6213
|
+
parseInteger,
|
|
6214
|
+
120
|
|
6215
|
+
).action(async (id, opts, cmd) => {
|
|
6155
6216
|
const data = await refreshExecutorInstance(
|
|
6156
6217
|
resolveConfig(cmd.optsWithGlobals()),
|
|
6157
6218
|
id,
|
|
@@ -6159,7 +6220,12 @@ function registerExecutor(program2) {
|
|
|
6159
6220
|
);
|
|
6160
6221
|
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
6161
6222
|
});
|
|
6162
|
-
executor.command("heartbeat <id>").description("Keep an advertisement alive until interrupted").option(
|
|
6223
|
+
executor.command("heartbeat <id>").description("Keep an advertisement alive until interrupted").option(
|
|
6224
|
+
"--ttl <seconds>",
|
|
6225
|
+
"Advertisement TTL (30 to tenant maximum)",
|
|
6226
|
+
parseInteger,
|
|
6227
|
+
120
|
|
6228
|
+
).option("--interval <seconds>", "Refresh interval", parseInteger, 40).action(async (id, opts, cmd) => {
|
|
6163
6229
|
if (opts.interval >= opts.ttl)
|
|
6164
6230
|
fail("heartbeat interval must be shorter than the TTL");
|
|
6165
6231
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
@@ -6321,12 +6387,12 @@ function parseRuntimeKind(value) {
|
|
|
6321
6387
|
}
|
|
6322
6388
|
}
|
|
6323
6389
|
function parseClaimPolicy(value) {
|
|
6324
|
-
switch ((value ??
|
|
6390
|
+
switch ((value ?? DEFAULT_CLAIM_POLICY).trim().toLowerCase()) {
|
|
6325
6391
|
case "":
|
|
6326
|
-
case "open":
|
|
6327
|
-
return "Open";
|
|
6328
6392
|
case "restricted":
|
|
6329
6393
|
return "Restricted";
|
|
6394
|
+
case "open":
|
|
6395
|
+
return "Open";
|
|
6330
6396
|
default:
|
|
6331
6397
|
return fail("claim-policy must be open or restricted");
|
|
6332
6398
|
}
|
|
@@ -7654,6 +7720,7 @@ function resolveLane(flagLane, cwd) {
|
|
|
7654
7720
|
return applyWorktreeLaneSuffix(base, start);
|
|
7655
7721
|
}
|
|
7656
7722
|
var INTENT_FILE = join15(".sechroom", "continuity.json");
|
|
7723
|
+
var LOCAL_DRY_RUN_VALIDATION_WARNING = "LOCAL-ONLY \u2014 NOT SERVER-VALIDATED";
|
|
7657
7724
|
function resolveIntentPath(start) {
|
|
7658
7725
|
let dir = start;
|
|
7659
7726
|
for (; ; ) {
|
|
@@ -7678,6 +7745,16 @@ function hasRequiredIntent(i) {
|
|
|
7678
7745
|
i.objective?.trim() && i.state?.trim() && i.lastAction?.trim() && i.nextAction?.trim() && i.resumeInstruction?.trim()
|
|
7679
7746
|
);
|
|
7680
7747
|
}
|
|
7748
|
+
function localDryRunMissingFields(i) {
|
|
7749
|
+
const required = [
|
|
7750
|
+
["objective", "--objective"],
|
|
7751
|
+
["state", "--state"],
|
|
7752
|
+
["lastAction", "--last-action"],
|
|
7753
|
+
["nextAction", "--next-action"],
|
|
7754
|
+
["resumeInstruction", "--resume-instruction"]
|
|
7755
|
+
];
|
|
7756
|
+
return required.filter(([key]) => !String(i[key] ?? "").trim()).map(([, flag]) => flag);
|
|
7757
|
+
}
|
|
7681
7758
|
async function saveSnapshotFromIntent(cmd, cwd, laneFlag, scopeFlag, defaultScope, opts) {
|
|
7682
7759
|
const lane = resolveLane(laneFlag, cwd);
|
|
7683
7760
|
if (!lane) return false;
|
|
@@ -7699,7 +7776,9 @@ async function saveSnapshotFromIntent(cmd, cwd, laneFlag, scopeFlag, defaultScop
|
|
|
7699
7776
|
openQuestions: intent.questions ?? null,
|
|
7700
7777
|
surfaceMarkers: intent.surfaceMarkers ?? null,
|
|
7701
7778
|
relevantArtifactIds: intent.artifacts ?? null,
|
|
7702
|
-
|
|
7779
|
+
// Preserve invalid JSON-string confidence tokens for the server's semantic
|
|
7780
|
+
// validator; never coerce them through Number/NaN/null.
|
|
7781
|
+
confidence: confidenceWireValue(intent.confidence),
|
|
7703
7782
|
// Frequent triggers (compaction, session-end) land within the FR-051 4h
|
|
7704
7783
|
// window; Acknowledge lets the checkpoint persist on the lane.
|
|
7705
7784
|
concurrentSessionPolicy: "Acknowledge"
|
|
@@ -7994,12 +8073,18 @@ Fail-soft: failures exit 0 and never block; session-context refresh failures ren
|
|
|
7994
8073
|
function registerCheckpoint(program2) {
|
|
7995
8074
|
program2.command("checkpoint").description(
|
|
7996
8075
|
"Checkpoint working state: create a continuity snapshot (server-validated) AND sync ./.sechroom/continuity.json in one step"
|
|
7997
|
-
).option("--lane <laneId>", "Lane id (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--scope <scope>", "Snapshot scope (else the file's scope, else 'session')").option("--objective <text>", "Current objective").option("--state <text>", "Current state").option("--last-action <text>", "Last meaningful action").option("--next-action <text>", "Next intended action").option("--resume-instruction <text>", "Resume instruction").option("--constraint <text...>", "Active constraints (repeatable)").option("--question <text...>", "Open questions (repeatable)").option("--surface-marker <text...>", "Surface markers (repeatable)").option("--artifact <id...>", "Relevant artifact ids (repeatable)").option("--confidence <n>", "Confidence 0..1").option(
|
|
8076
|
+
).option("--lane <laneId>", "Lane id (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--scope <scope>", "Snapshot scope (else the file's scope, else 'session')").option("--objective <text>", "Current objective").option("--state <text>", "Current state").option("--last-action <text>", "Last meaningful action").option("--next-action <text>", "Next intended action").option("--resume-instruction <text>", "Resume instruction").option("--constraint <text...>", "Active constraints (repeatable)").option("--question <text...>", "Open questions (repeatable)").option("--surface-marker <text...>", "Surface markers (repeatable)").option("--artifact <id...>", "Relevant artifact ids (repeatable)").option("--confidence <n>", "Confidence 0..1").option(
|
|
8077
|
+
"--dry-run",
|
|
8078
|
+
"run the local required-field check and print the payload (LOCAL-ONLY; NOT SERVER-VALIDATED)",
|
|
8079
|
+
false
|
|
8080
|
+
).addHelpText(
|
|
7998
8081
|
"after",
|
|
7999
8082
|
`
|
|
8000
8083
|
File-first: reads ./.sechroom/continuity.json (kept current as you work) as the base; any flag
|
|
8001
8084
|
overrides that field. The snapshot is created FIRST (server-validated), then the local file is
|
|
8002
8085
|
written/normalized with the returned snapshotId. Lane: --lane > SECHROOM_LANE > ./.sechroom/lane.json code-lane.
|
|
8086
|
+
--dry-run performs only the local required-field check and prints a payload. Its output is
|
|
8087
|
+
explicitly LOCAL-ONLY; NOT SERVER-VALIDATED, so it does not establish write-path parity.
|
|
8003
8088
|
|
|
8004
8089
|
Examples:
|
|
8005
8090
|
$ sechroom checkpoint snapshot from ./.sechroom/continuity.json, then sync it
|
|
@@ -8022,7 +8107,9 @@ Examples:
|
|
|
8022
8107
|
questions: opts.question ?? base.questions,
|
|
8023
8108
|
surfaceMarkers: opts.surfaceMarker ?? base.surfaceMarkers,
|
|
8024
8109
|
artifacts: opts.artifact ?? base.artifacts,
|
|
8025
|
-
|
|
8110
|
+
// Keep the raw token until the server's shared validator sees it. In particular,
|
|
8111
|
+
// Number("high") -> NaN -> JSON null would silently discard the operator's input.
|
|
8112
|
+
confidence: opts.confidence != null ? opts.confidence : base.confidence
|
|
8026
8113
|
};
|
|
8027
8114
|
const lane = resolveLane(opts.lane, cwd);
|
|
8028
8115
|
if (!lane) {
|
|
@@ -8030,19 +8117,6 @@ Examples:
|
|
|
8030
8117
|
"no lane resolved \u2014 pass --lane, set SECHROOM_LANE, or pin one in ./.sechroom/lane.json (code-lane). See `sechroom lane`."
|
|
8031
8118
|
);
|
|
8032
8119
|
}
|
|
8033
|
-
const required = [
|
|
8034
|
-
["objective", "--objective"],
|
|
8035
|
-
["state", "--state"],
|
|
8036
|
-
["lastAction", "--last-action"],
|
|
8037
|
-
["nextAction", "--next-action"],
|
|
8038
|
-
["resumeInstruction", "--resume-instruction"]
|
|
8039
|
-
];
|
|
8040
|
-
const missing = required.filter(([k]) => !String(merged[k] ?? "").trim()).map(([, flag]) => flag);
|
|
8041
|
-
if (missing.length > 0) {
|
|
8042
|
-
fail(
|
|
8043
|
-
`missing required field(s): ${missing.join(", ")} \u2014 supply via flag or in ./.sechroom/continuity.json`
|
|
8044
|
-
);
|
|
8045
|
-
}
|
|
8046
8120
|
const scope = merged.scope ?? "session";
|
|
8047
8121
|
const body = {
|
|
8048
8122
|
laneId: lane,
|
|
@@ -8056,13 +8130,33 @@ Examples:
|
|
|
8056
8130
|
openQuestions: merged.questions ?? null,
|
|
8057
8131
|
surfaceMarkers: merged.surfaceMarkers ?? null,
|
|
8058
8132
|
relevantArtifactIds: merged.artifacts ?? null,
|
|
8059
|
-
confidence: merged.confidence
|
|
8133
|
+
confidence: confidenceWireValue(merged.confidence),
|
|
8060
8134
|
// Explicit checkpoints are often within the FR-051 4h window; Acknowledge
|
|
8061
8135
|
// lets one land on the lane (matches `hook pre-compact`).
|
|
8062
8136
|
concurrentSessionPolicy: "Acknowledge"
|
|
8063
8137
|
};
|
|
8064
8138
|
if (opts.dryRun) {
|
|
8065
|
-
|
|
8139
|
+
const missing = localDryRunMissingFields(merged);
|
|
8140
|
+
if (missing.length > 0) {
|
|
8141
|
+
fail(
|
|
8142
|
+
`LOCAL-ONLY CHECK \u2014 NOT SERVER-VALIDATED: missing required field(s): ${missing.join(", ")} \u2014 supply via flag or in ./.sechroom/continuity.json`
|
|
8143
|
+
);
|
|
8144
|
+
}
|
|
8145
|
+
emit(
|
|
8146
|
+
{
|
|
8147
|
+
dryRun: true,
|
|
8148
|
+
validation: {
|
|
8149
|
+
mode: "local-only",
|
|
8150
|
+
serverValidated: false,
|
|
8151
|
+
checks: ["required-fields"],
|
|
8152
|
+
warning: LOCAL_DRY_RUN_VALIDATION_WARNING
|
|
8153
|
+
},
|
|
8154
|
+
lane,
|
|
8155
|
+
scope,
|
|
8156
|
+
wouldCreate: body
|
|
8157
|
+
},
|
|
8158
|
+
json
|
|
8159
|
+
);
|
|
8066
8160
|
return;
|
|
8067
8161
|
}
|
|
8068
8162
|
const data = await runApi("Creating snapshot", async () => {
|
|
@@ -8299,7 +8393,7 @@ Displaced snapshot recovery surfaces:
|
|
|
8299
8393
|
openQuestions: opts.question ?? null,
|
|
8300
8394
|
surfaceMarkers: opts.surfaceMarker ?? null,
|
|
8301
8395
|
relevantArtifactIds: opts.artifact ?? null,
|
|
8302
|
-
confidence:
|
|
8396
|
+
confidence: confidenceWireValue(opts.confidence)
|
|
8303
8397
|
}
|
|
8304
8398
|
});
|
|
8305
8399
|
});
|
|
@@ -8448,6 +8542,96 @@ function snapshotGetNotFoundHint(includeAll, status) {
|
|
|
8448
8542
|
|
|
8449
8543
|
// src/commands/work-plan.ts
|
|
8450
8544
|
import { readFile as readFile2 } from "fs/promises";
|
|
8545
|
+
|
|
8546
|
+
// src/paging.ts
|
|
8547
|
+
var MAX_AUTO_PAGES = 100;
|
|
8548
|
+
var asNumber = (value) => value === void 0 || value === "" ? void 0 : Number(value);
|
|
8549
|
+
function shouldAutoPage(opts) {
|
|
8550
|
+
if (opts.autoPage === false) return false;
|
|
8551
|
+
return asNumber(opts.page) === void 0;
|
|
8552
|
+
}
|
|
8553
|
+
function singlePageQuery(opts) {
|
|
8554
|
+
const page = asNumber(opts.page);
|
|
8555
|
+
const pageSize = asNumber(opts.pageSize);
|
|
8556
|
+
return {
|
|
8557
|
+
...page === void 0 ? {} : { page },
|
|
8558
|
+
...pageSize === void 0 ? {} : { pageSize }
|
|
8559
|
+
};
|
|
8560
|
+
}
|
|
8561
|
+
async function fetchAllPages(fetchPage, opts = {}, label = "results") {
|
|
8562
|
+
const pages = [];
|
|
8563
|
+
let page = 1;
|
|
8564
|
+
let truncated = false;
|
|
8565
|
+
for (; ; ) {
|
|
8566
|
+
const current = await fetchPage(page);
|
|
8567
|
+
pages.push(current);
|
|
8568
|
+
if (!hasNextPage(current)) break;
|
|
8569
|
+
if (pages.length >= MAX_AUTO_PAGES) {
|
|
8570
|
+
truncated = true;
|
|
8571
|
+
break;
|
|
8572
|
+
}
|
|
8573
|
+
page = current.page + 1;
|
|
8574
|
+
}
|
|
8575
|
+
const first = pages[0];
|
|
8576
|
+
if (!first) throw new Error("paged read returned no envelope at all");
|
|
8577
|
+
if (truncated) {
|
|
8578
|
+
if (!isQuiet()) {
|
|
8579
|
+
process.stderr.write(
|
|
8580
|
+
`${warn("!")} Stopped after ${MAX_AUTO_PAGES} pages \u2014 more ${label} remain. ${style.dim("Narrow the filters, or read a specific window with --page/--page-size.")}
|
|
8581
|
+
`
|
|
8582
|
+
);
|
|
8583
|
+
}
|
|
8584
|
+
const partial = pages.flatMap((p) => p.items);
|
|
8585
|
+
return {
|
|
8586
|
+
...first,
|
|
8587
|
+
items: partial,
|
|
8588
|
+
page: 1,
|
|
8589
|
+
pageSize: partial.length,
|
|
8590
|
+
// Pages OF THIS SIZE, so the number stays consistent with the pageSize just reported.
|
|
8591
|
+
pageCount: partial.length === 0 ? 1 : Math.ceil(first.count / partial.length),
|
|
8592
|
+
hasPreviousPage: false,
|
|
8593
|
+
hasNextPage: true,
|
|
8594
|
+
isFirstPage: true,
|
|
8595
|
+
isLastPage: false,
|
|
8596
|
+
firstItemOnPage: partial.length === 0 ? 0 : 1,
|
|
8597
|
+
lastItemOnPage: partial.length
|
|
8598
|
+
};
|
|
8599
|
+
}
|
|
8600
|
+
if (pages.length === 1) return first;
|
|
8601
|
+
const items = pages.flatMap((current) => current.items);
|
|
8602
|
+
return {
|
|
8603
|
+
...first,
|
|
8604
|
+
items,
|
|
8605
|
+
page: 1,
|
|
8606
|
+
pageSize: items.length,
|
|
8607
|
+
pageCount: 1,
|
|
8608
|
+
hasPreviousPage: false,
|
|
8609
|
+
hasNextPage: false,
|
|
8610
|
+
isFirstPage: true,
|
|
8611
|
+
isLastPage: true,
|
|
8612
|
+
firstItemOnPage: items.length === 0 ? 0 : 1,
|
|
8613
|
+
lastItemOnPage: items.length
|
|
8614
|
+
};
|
|
8615
|
+
}
|
|
8616
|
+
function hasNextPage(current) {
|
|
8617
|
+
if (current.hasNextPage !== void 0) return current.hasNextPage;
|
|
8618
|
+
if (current.pageCount !== void 0) return current.page < current.pageCount;
|
|
8619
|
+
return current.page * current.pageSize < current.count;
|
|
8620
|
+
}
|
|
8621
|
+
var PAGE_OPTION = [
|
|
8622
|
+
"--page <n>",
|
|
8623
|
+
"Page number (reads only that page instead of all)"
|
|
8624
|
+
];
|
|
8625
|
+
var PAGE_SIZE_OPTION = [
|
|
8626
|
+
"--page-size <n>",
|
|
8627
|
+
"Page size used while paging"
|
|
8628
|
+
];
|
|
8629
|
+
var NO_AUTO_PAGE_OPTION = [
|
|
8630
|
+
"--no-auto-page",
|
|
8631
|
+
"Read only the first page instead of walking every page"
|
|
8632
|
+
];
|
|
8633
|
+
|
|
8634
|
+
// src/commands/work-plan.ts
|
|
8451
8635
|
function registerWorkPlan(program2) {
|
|
8452
8636
|
const workPlan = program2.command("work-plan").description(
|
|
8453
8637
|
"Drive a work plan: create one from a brief, then execute / accept / reject"
|
|
@@ -8641,22 +8825,27 @@ Examples:
|
|
|
8641
8825
|
globals.json
|
|
8642
8826
|
);
|
|
8643
8827
|
});
|
|
8644
|
-
workPlan.command("list").description(
|
|
8828
|
+
workPlan.command("list").description(
|
|
8829
|
+
"List work plans, newest-first (GET /decompositions). Walks every page by default."
|
|
8830
|
+
).option("--status <status>", "Filter by decomposition status").option("--brief <briefId>", "Filter by work-brief memory id").option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (opts, cmd) => {
|
|
8645
8831
|
const globals = cmd.optsWithGlobals();
|
|
8646
8832
|
const cfg = resolveConfig(globals);
|
|
8647
|
-
const
|
|
8833
|
+
const readPage = (query) => runApi("Listing work plans", async () => {
|
|
8648
8834
|
const client = await makeClient(cfg);
|
|
8649
8835
|
return client.GET("/decompositions", {
|
|
8650
8836
|
params: {
|
|
8651
|
-
query: {
|
|
8652
|
-
status: opts.status,
|
|
8653
|
-
briefId: opts.brief,
|
|
8654
|
-
page: opts.page,
|
|
8655
|
-
pageSize: opts.pageSize
|
|
8656
|
-
}
|
|
8837
|
+
query: { status: opts.status, briefId: opts.brief, ...query }
|
|
8657
8838
|
}
|
|
8658
8839
|
});
|
|
8659
8840
|
});
|
|
8841
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
8842
|
+
(page) => readPage({
|
|
8843
|
+
page,
|
|
8844
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
8845
|
+
}),
|
|
8846
|
+
opts,
|
|
8847
|
+
"work plans"
|
|
8848
|
+
) : await readPage(singlePageQuery(opts));
|
|
8660
8849
|
emitAction(
|
|
8661
8850
|
`listed ${style.bold(String(data.items.length))} of ${data.count} work plan(s)`,
|
|
8662
8851
|
data,
|
|
@@ -8744,35 +8933,53 @@ Examples:
|
|
|
8744
8933
|
$ sechroom filing reject fsg_XXXX --reason "wrong workspace"
|
|
8745
8934
|
$ sechroom filing edit-and-accept fsg_XXXX --target-kind Workspace --existing-target-id wsp_XXXX`
|
|
8746
8935
|
);
|
|
8747
|
-
filing.command("suggestions").description(
|
|
8936
|
+
filing.command("suggestions").description(
|
|
8937
|
+
"List filing suggestions (GET /filing/suggestions). Walks every page by default."
|
|
8938
|
+
).option("--memory-id <memoryId>", "Filter to a single memory's suggestions").option(
|
|
8748
8939
|
"--status <status>",
|
|
8749
8940
|
"Generating | Pending | Accepted | Rejected | EditedAndAccepted | Deferred | Invalidated"
|
|
8750
|
-
).option(
|
|
8941
|
+
).option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (opts, cmd) => {
|
|
8751
8942
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8752
|
-
const
|
|
8943
|
+
const filters = {
|
|
8944
|
+
...opts.memoryId ? { memoryId: opts.memoryId } : {},
|
|
8945
|
+
...opts.status ? {
|
|
8946
|
+
status: opts.status
|
|
8947
|
+
} : {}
|
|
8948
|
+
};
|
|
8949
|
+
const readPage = (query) => runApi("Listing filing suggestions", async () => {
|
|
8753
8950
|
const client = await makeClient(cfg);
|
|
8754
8951
|
return client.GET("/filing/suggestions", {
|
|
8755
|
-
params: {
|
|
8756
|
-
query: {
|
|
8757
|
-
...opts.memoryId ? { memoryId: opts.memoryId } : {},
|
|
8758
|
-
...opts.status ? { status: opts.status } : {},
|
|
8759
|
-
...opts.page ? { page: Number(opts.page) } : {},
|
|
8760
|
-
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
8761
|
-
}
|
|
8762
|
-
}
|
|
8952
|
+
params: { query: { ...filters, ...query } }
|
|
8763
8953
|
});
|
|
8764
8954
|
});
|
|
8955
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
8956
|
+
(page) => readPage({
|
|
8957
|
+
page,
|
|
8958
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
8959
|
+
}),
|
|
8960
|
+
opts,
|
|
8961
|
+
"filing suggestions"
|
|
8962
|
+
) : await readPage(singlePageQuery(opts));
|
|
8765
8963
|
emit(data, cmd.optsWithGlobals().json);
|
|
8766
8964
|
});
|
|
8767
|
-
filing.command("get <id>").description(
|
|
8965
|
+
filing.command("get <id>").description(
|
|
8966
|
+
"Fetch a filing suggestion by id (GET /filing/suggestions/{id})"
|
|
8967
|
+
).action(async (id, _opts, cmd) => {
|
|
8768
8968
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8769
8969
|
const data = await runApi("Fetching filing suggestion", async () => {
|
|
8770
8970
|
const client = await makeClient(cfg);
|
|
8771
|
-
return client.GET("/filing/suggestions/{id}", {
|
|
8971
|
+
return client.GET("/filing/suggestions/{id}", {
|
|
8972
|
+
params: { path: { id } }
|
|
8973
|
+
});
|
|
8772
8974
|
});
|
|
8773
8975
|
emit(data, cmd.optsWithGlobals().json);
|
|
8774
8976
|
});
|
|
8775
|
-
filing.command("preview").description(
|
|
8977
|
+
filing.command("preview").description(
|
|
8978
|
+
"Preview a filing suggestion for a memory id or ad-hoc shape (POST /filing/suggestions/preview)"
|
|
8979
|
+
).option("--memory-id <memoryId>", "Preview filing for an existing memory").option("--text <text>", "Ad-hoc memory body text (instead of --memory-id)").option("--title <title>", "Ad-hoc memory title").option("--tag <tag...>", "Ad-hoc memory tags (repeatable)").option("--type <type>", "Ad-hoc memory type", "reference").option(
|
|
8980
|
+
"--scope-workspace <workspaceId>",
|
|
8981
|
+
"Scope the preview to a workspace"
|
|
8982
|
+
).action(async (opts, cmd) => {
|
|
8776
8983
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8777
8984
|
const memory = opts.text ? {
|
|
8778
8985
|
text: opts.text,
|
|
@@ -8792,15 +8999,26 @@ Examples:
|
|
|
8792
8999
|
});
|
|
8793
9000
|
emit(data, cmd.optsWithGlobals().json);
|
|
8794
9001
|
});
|
|
8795
|
-
filing.command("accept <id>").description(
|
|
9002
|
+
filing.command("accept <id>").description(
|
|
9003
|
+
"Accept a filing suggestion (POST /filing/suggestions/{id}/accept)"
|
|
9004
|
+
).action(async (id, _opts, cmd) => {
|
|
8796
9005
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8797
9006
|
const data = await runApi("Accepting filing suggestion", async () => {
|
|
8798
9007
|
const client = await makeClient(cfg);
|
|
8799
|
-
return client.POST("/filing/suggestions/{id}/accept", {
|
|
9008
|
+
return client.POST("/filing/suggestions/{id}/accept", {
|
|
9009
|
+
params: { path: { id } },
|
|
9010
|
+
body: {}
|
|
9011
|
+
});
|
|
8800
9012
|
});
|
|
8801
|
-
emitAction(
|
|
9013
|
+
emitAction(
|
|
9014
|
+
`accepted filing suggestion ${style.bold(id)}`,
|
|
9015
|
+
data,
|
|
9016
|
+
cmd.optsWithGlobals().json
|
|
9017
|
+
);
|
|
8802
9018
|
});
|
|
8803
|
-
filing.command("reject <id>").description(
|
|
9019
|
+
filing.command("reject <id>").description(
|
|
9020
|
+
"Reject a filing suggestion (POST /filing/suggestions/{id}/reject)"
|
|
9021
|
+
).option("--reason <reason>", "Why the suggestion was rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
|
|
8804
9022
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8805
9023
|
const data = await runApi("Rejecting filing suggestion", async () => {
|
|
8806
9024
|
const client = await makeClient(cfg);
|
|
@@ -8812,9 +9030,18 @@ Examples:
|
|
|
8812
9030
|
}
|
|
8813
9031
|
});
|
|
8814
9032
|
});
|
|
8815
|
-
emitAction(
|
|
9033
|
+
emitAction(
|
|
9034
|
+
`rejected filing suggestion ${style.bold(id)}`,
|
|
9035
|
+
data,
|
|
9036
|
+
cmd.optsWithGlobals().json
|
|
9037
|
+
);
|
|
8816
9038
|
});
|
|
8817
|
-
filing.command("defer <id>").description(
|
|
9039
|
+
filing.command("defer <id>").description(
|
|
9040
|
+
"Defer a filing suggestion (POST /filing/suggestions/{id}/defer)"
|
|
9041
|
+
).option(
|
|
9042
|
+
"--until <iso>",
|
|
9043
|
+
"Defer until an ISO-8601 timestamp (defaults to indefinite)"
|
|
9044
|
+
).action(async (id, opts, cmd) => {
|
|
8818
9045
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8819
9046
|
const data = await runApi("Deferring filing suggestion", async () => {
|
|
8820
9047
|
const client = await makeClient(cfg);
|
|
@@ -8823,25 +9050,44 @@ Examples:
|
|
|
8823
9050
|
body: { until: opts.until ?? null }
|
|
8824
9051
|
});
|
|
8825
9052
|
});
|
|
8826
|
-
emitAction(
|
|
9053
|
+
emitAction(
|
|
9054
|
+
`deferred filing suggestion ${style.bold(id)}`,
|
|
9055
|
+
data,
|
|
9056
|
+
cmd.optsWithGlobals().json
|
|
9057
|
+
);
|
|
8827
9058
|
});
|
|
8828
|
-
filing.command("edit-and-accept <id>").description(
|
|
9059
|
+
filing.command("edit-and-accept <id>").description(
|
|
9060
|
+
"Override the target then accept (POST /filing/suggestions/{id}/edit-and-accept)"
|
|
9061
|
+
).option("--target-kind <kind>", "Workspace | Project").option(
|
|
9062
|
+
"--existing-target-id <id>",
|
|
9063
|
+
"File into an existing workspace/project"
|
|
9064
|
+
).option("--new-name <name>", "Create a new target with this name").option("--new-description <text>", "Description for the new target").option(
|
|
9065
|
+
"--new-parent-workspace <workspaceId>",
|
|
9066
|
+
"Parent workspace for a new project"
|
|
9067
|
+
).option("--memory-id <memoryId...>", "Override the memory set (repeatable)").action(async (id, opts, cmd) => {
|
|
8829
9068
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
8830
|
-
const data = await runApi(
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
9069
|
+
const data = await runApi(
|
|
9070
|
+
"Editing and accepting filing suggestion",
|
|
9071
|
+
async () => {
|
|
9072
|
+
const client = await makeClient(cfg);
|
|
9073
|
+
return client.POST("/filing/suggestions/{id}/edit-and-accept", {
|
|
9074
|
+
params: { path: { id } },
|
|
9075
|
+
body: {
|
|
9076
|
+
targetKind: opts.targetKind ?? null,
|
|
9077
|
+
existingTargetId: opts.existingTargetId ?? null,
|
|
9078
|
+
newName: opts.newName ?? null,
|
|
9079
|
+
newDescription: opts.newDescription ?? null,
|
|
9080
|
+
newParentWorkspaceId: opts.newParentWorkspace ?? null,
|
|
9081
|
+
overrideMemoryIds: opts.memoryId ?? null
|
|
9082
|
+
}
|
|
9083
|
+
});
|
|
9084
|
+
}
|
|
9085
|
+
);
|
|
9086
|
+
emitAction(
|
|
9087
|
+
`edited & accepted filing suggestion ${style.bold(id)}`,
|
|
9088
|
+
data,
|
|
9089
|
+
cmd.optsWithGlobals().json
|
|
9090
|
+
);
|
|
8845
9091
|
});
|
|
8846
9092
|
}
|
|
8847
9093
|
|
|
@@ -10165,39 +10411,16 @@ function resolveCreateBody(textOpt, fileOpt) {
|
|
|
10165
10411
|
return { text: text2, defaultTitle };
|
|
10166
10412
|
}
|
|
10167
10413
|
async function fetchAllMemoryRelationships(cfg, memoryId) {
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
}
|
|
10179
|
-
);
|
|
10180
|
-
pages.push(current);
|
|
10181
|
-
const hasNextPage = current.hasNextPage ?? (current.pageCount !== void 0 ? current.page < current.pageCount : current.page * current.pageSize < current.count);
|
|
10182
|
-
if (!hasNextPage) break;
|
|
10183
|
-
page = current.page + 1;
|
|
10184
|
-
}
|
|
10185
|
-
const first = pages[0];
|
|
10186
|
-
if (!first || pages.length === 1) return first;
|
|
10187
|
-
const items = pages.flatMap((current) => current.items);
|
|
10188
|
-
return {
|
|
10189
|
-
...first,
|
|
10190
|
-
items,
|
|
10191
|
-
page: 1,
|
|
10192
|
-
pageSize: items.length,
|
|
10193
|
-
pageCount: 1,
|
|
10194
|
-
hasPreviousPage: false,
|
|
10195
|
-
hasNextPage: false,
|
|
10196
|
-
isFirstPage: true,
|
|
10197
|
-
isLastPage: true,
|
|
10198
|
-
firstItemOnPage: 1,
|
|
10199
|
-
lastItemOnPage: items.length
|
|
10200
|
-
};
|
|
10414
|
+
return await fetchAllPages(
|
|
10415
|
+
(page) => runApi(`Fetching relationships (page ${page})`, async () => {
|
|
10416
|
+
const client = await makeClient(cfg);
|
|
10417
|
+
return client.GET("/memories/{memoryId}/relationships", {
|
|
10418
|
+
params: { path: { memoryId }, query: { page } }
|
|
10419
|
+
});
|
|
10420
|
+
}),
|
|
10421
|
+
{},
|
|
10422
|
+
"relationships"
|
|
10423
|
+
);
|
|
10201
10424
|
}
|
|
10202
10425
|
function registerMemory(program2) {
|
|
10203
10426
|
const memory = program2.command("memory").description("Create, read, and search memories");
|
|
@@ -10697,21 +10920,28 @@ Note: CLI memory get omits relationships by default; MCP get_memory includes the
|
|
|
10697
10920
|
cmd.optsWithGlobals().json
|
|
10698
10921
|
);
|
|
10699
10922
|
});
|
|
10700
|
-
memory.command("list-archived").description(
|
|
10923
|
+
memory.command("list-archived").description(
|
|
10924
|
+
"List archived memories (GET /memories/archived). Walks every page by default."
|
|
10925
|
+
).option("--workspace <workspaceId>", "Scope to a workspace").option("--project <projectId>", "Scope to a project").option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (opts, cmd) => {
|
|
10701
10926
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
10702
|
-
const
|
|
10927
|
+
const filters = {
|
|
10928
|
+
...opts.workspace ? { workspaceId: opts.workspace } : {},
|
|
10929
|
+
...opts.project ? { projectId: opts.project } : {}
|
|
10930
|
+
};
|
|
10931
|
+
const readPage = (query) => runApi("Listing archived memories", async () => {
|
|
10703
10932
|
const client = await makeClient(cfg);
|
|
10704
10933
|
return client.GET("/memories/archived", {
|
|
10705
|
-
params: {
|
|
10706
|
-
query: {
|
|
10707
|
-
...opts.workspace ? { workspaceId: opts.workspace } : {},
|
|
10708
|
-
...opts.project ? { projectId: opts.project } : {},
|
|
10709
|
-
...opts.page ? { page: Number(opts.page) } : {},
|
|
10710
|
-
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
10711
|
-
}
|
|
10712
|
-
}
|
|
10934
|
+
params: { query: { ...filters, ...query } }
|
|
10713
10935
|
});
|
|
10714
10936
|
});
|
|
10937
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
10938
|
+
(page) => readPage({
|
|
10939
|
+
page,
|
|
10940
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
10941
|
+
}),
|
|
10942
|
+
opts,
|
|
10943
|
+
"archived memories"
|
|
10944
|
+
) : await readPage(singlePageQuery(opts));
|
|
10715
10945
|
emit(data, cmd.optsWithGlobals().json);
|
|
10716
10946
|
});
|
|
10717
10947
|
memory.command("versions <memoryId>").description("List a memory's versions (GET /memories/{memoryId}/versions)").action(async (memoryId, _opts, cmd) => {
|
|
@@ -13499,7 +13729,13 @@ Examples:
|
|
|
13499
13729
|
$ sechroom relationship suggestions --status Pending --memory mem_XXXX
|
|
13500
13730
|
$ sechroom relationship suggestion accept rsg_XXXX`
|
|
13501
13731
|
);
|
|
13502
|
-
relationship.command("create <fromMemoryId> <toMemoryId>").description(
|
|
13732
|
+
relationship.command("create <fromMemoryId> <toMemoryId>").description(
|
|
13733
|
+
"Create a relationship (POST /memories/{memoryId}/relationships)"
|
|
13734
|
+
).option(
|
|
13735
|
+
"--type <type>",
|
|
13736
|
+
"Relationship type (Reference, Related, Parent, Child, Follows, \u2026)",
|
|
13737
|
+
"Reference"
|
|
13738
|
+
).option(
|
|
13503
13739
|
"--to-version <number>",
|
|
13504
13740
|
"Version of the target memory to pin the edge to (defaults to the target's current version)"
|
|
13505
13741
|
).action(async (fromMemoryId, toMemoryId, opts, cmd) => {
|
|
@@ -13509,15 +13745,21 @@ Examples:
|
|
|
13509
13745
|
if (opts.toVersion !== void 0) {
|
|
13510
13746
|
toVersion = Number(opts.toVersion);
|
|
13511
13747
|
if (!Number.isInteger(toVersion) || toVersion < 1) {
|
|
13512
|
-
fail(
|
|
13748
|
+
fail(
|
|
13749
|
+
`--to-version must be a positive integer (got '${opts.toVersion}').`
|
|
13750
|
+
);
|
|
13513
13751
|
}
|
|
13514
13752
|
} else {
|
|
13515
13753
|
const target = await runApi(
|
|
13516
13754
|
"Resolving target version",
|
|
13517
|
-
async () => client.GET("/memories/{memoryId}", {
|
|
13755
|
+
async () => client.GET("/memories/{memoryId}", {
|
|
13756
|
+
params: { path: { memoryId: toMemoryId } }
|
|
13757
|
+
})
|
|
13518
13758
|
);
|
|
13519
13759
|
if (typeof target.item?.currentVersion !== "number") {
|
|
13520
|
-
fail(
|
|
13760
|
+
fail(
|
|
13761
|
+
`Could not resolve the current version of ${toMemoryId}; pass --to-version explicitly.`
|
|
13762
|
+
);
|
|
13521
13763
|
}
|
|
13522
13764
|
toVersion = target.item.currentVersion;
|
|
13523
13765
|
}
|
|
@@ -13541,22 +13783,28 @@ Examples:
|
|
|
13541
13783
|
cmd.optsWithGlobals().json
|
|
13542
13784
|
);
|
|
13543
13785
|
});
|
|
13544
|
-
relationship.command("list <memoryId>").description(
|
|
13786
|
+
relationship.command("list <memoryId>").description(
|
|
13787
|
+
"List a memory's relationships (GET /memories/{memoryId}/relationships). Walks every page by default."
|
|
13788
|
+
).option("--direction <direction>", "Both | Outbound | Inbound").option("--include-deleted", "Include deleted relationships", false).option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (memoryId, opts, cmd) => {
|
|
13545
13789
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13546
|
-
const
|
|
13790
|
+
const filters = {
|
|
13791
|
+
...opts.direction ? { direction: opts.direction } : {},
|
|
13792
|
+
...opts.includeDeleted ? { includeDeleted: true } : {}
|
|
13793
|
+
};
|
|
13794
|
+
const readPage = (query) => runApi("Listing relationships", async () => {
|
|
13547
13795
|
const client = await makeClient(cfg);
|
|
13548
13796
|
return client.GET("/memories/{memoryId}/relationships", {
|
|
13549
|
-
params: {
|
|
13550
|
-
path: { memoryId },
|
|
13551
|
-
query: {
|
|
13552
|
-
...opts.direction ? { direction: opts.direction } : {},
|
|
13553
|
-
...opts.includeDeleted ? { includeDeleted: true } : {},
|
|
13554
|
-
...opts.page ? { page: Number(opts.page) } : {},
|
|
13555
|
-
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
13556
|
-
}
|
|
13557
|
-
}
|
|
13797
|
+
params: { path: { memoryId }, query: { ...filters, ...query } }
|
|
13558
13798
|
});
|
|
13559
13799
|
});
|
|
13800
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
13801
|
+
(page) => readPage({
|
|
13802
|
+
page,
|
|
13803
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
13804
|
+
}),
|
|
13805
|
+
opts,
|
|
13806
|
+
"relationships"
|
|
13807
|
+
) : await readPage(singlePageQuery(opts));
|
|
13560
13808
|
emit(data, cmd.optsWithGlobals().json);
|
|
13561
13809
|
});
|
|
13562
13810
|
relationship.command("delete <id>").description("Delete a relationship (DELETE /relationships/{id})").action(async (id, _opts, cmd) => {
|
|
@@ -13568,9 +13816,15 @@ Examples:
|
|
|
13568
13816
|
body: {}
|
|
13569
13817
|
});
|
|
13570
13818
|
});
|
|
13571
|
-
emitAction(
|
|
13819
|
+
emitAction(
|
|
13820
|
+
`deleted relationship ${style.bold(id)}`,
|
|
13821
|
+
data,
|
|
13822
|
+
cmd.optsWithGlobals().json
|
|
13823
|
+
);
|
|
13572
13824
|
});
|
|
13573
|
-
relationship.command("suggest <memoryId>").description(
|
|
13825
|
+
relationship.command("suggest <memoryId>").description(
|
|
13826
|
+
"Generate relationship suggestions for a memory (POST /memories/{memoryId}/suggest-relationships)"
|
|
13827
|
+
).option("--limit <n>", "Max suggestions to generate").action(async (memoryId, opts, cmd) => {
|
|
13574
13828
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13575
13829
|
const data = await runApi("Suggesting relationships", async () => {
|
|
13576
13830
|
const client = await makeClient(cfg);
|
|
@@ -13587,38 +13841,51 @@ Examples:
|
|
|
13587
13841
|
cmd.optsWithGlobals().json
|
|
13588
13842
|
);
|
|
13589
13843
|
});
|
|
13590
|
-
relationship.command("suggestions").description(
|
|
13844
|
+
relationship.command("suggestions").description(
|
|
13845
|
+
"List relationship suggestions (GET /relationship-suggestions)"
|
|
13846
|
+
).option("--memory <memoryId>", "Filter to a memory").option(
|
|
13591
13847
|
"--status <status>",
|
|
13592
13848
|
"Pending | Accepted | EditedAndAccepted | Rejected | Superseded | Deferred | Invalidated"
|
|
13593
|
-
).option(
|
|
13849
|
+
).option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (opts, cmd) => {
|
|
13594
13850
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13595
|
-
const
|
|
13851
|
+
const filters = {
|
|
13852
|
+
...opts.memory ? { memoryId: opts.memory } : {},
|
|
13853
|
+
...opts.status ? {
|
|
13854
|
+
status: opts.status
|
|
13855
|
+
} : {}
|
|
13856
|
+
};
|
|
13857
|
+
const readPage = (query) => runApi("Listing suggestions", async () => {
|
|
13596
13858
|
const client = await makeClient(cfg);
|
|
13597
13859
|
return client.GET("/relationship-suggestions", {
|
|
13598
|
-
params: {
|
|
13599
|
-
query: {
|
|
13600
|
-
...opts.memory ? { memoryId: opts.memory } : {},
|
|
13601
|
-
...opts.status ? {
|
|
13602
|
-
status: opts.status
|
|
13603
|
-
} : {},
|
|
13604
|
-
...opts.page ? { page: Number(opts.page) } : {},
|
|
13605
|
-
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
13606
|
-
}
|
|
13607
|
-
}
|
|
13860
|
+
params: { query: { ...filters, ...query } }
|
|
13608
13861
|
});
|
|
13609
13862
|
});
|
|
13863
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
13864
|
+
(page) => readPage({
|
|
13865
|
+
page,
|
|
13866
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
13867
|
+
}),
|
|
13868
|
+
opts,
|
|
13869
|
+
"suggestions"
|
|
13870
|
+
) : await readPage(singlePageQuery(opts));
|
|
13610
13871
|
emit(data, cmd.optsWithGlobals().json);
|
|
13611
13872
|
});
|
|
13612
13873
|
const suggestion = relationship.command("suggestion").description("Inspect and decide on a single relationship suggestion");
|
|
13613
|
-
suggestion.command("get <id>").description(
|
|
13874
|
+
suggestion.command("get <id>").description(
|
|
13875
|
+
"Fetch a suggestion by id (GET /relationship-suggestions/{id})"
|
|
13876
|
+
).action(async (id, _opts, cmd) => {
|
|
13614
13877
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13615
13878
|
const data = await runApi("Fetching suggestion", async () => {
|
|
13616
13879
|
const client = await makeClient(cfg);
|
|
13617
|
-
return client.GET("/relationship-suggestions/{id}", {
|
|
13880
|
+
return client.GET("/relationship-suggestions/{id}", {
|
|
13881
|
+
params: { path: { id } }
|
|
13882
|
+
});
|
|
13618
13883
|
});
|
|
13619
13884
|
emit(data, cmd.optsWithGlobals().json);
|
|
13620
13885
|
});
|
|
13621
|
-
suggestion.command("accept <id>").description(
|
|
13886
|
+
suggestion.command("accept <id>").description(
|
|
13887
|
+
"Accept a suggestion (POST /relationship-suggestions/{instanceId}/accept)"
|
|
13888
|
+
).action(async (id, _opts, cmd) => {
|
|
13622
13889
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13623
13890
|
const data = await runApi("Accepting suggestion", async () => {
|
|
13624
13891
|
const client = await makeClient(cfg);
|
|
@@ -13627,9 +13894,15 @@ Examples:
|
|
|
13627
13894
|
body: {}
|
|
13628
13895
|
});
|
|
13629
13896
|
});
|
|
13630
|
-
emitAction(
|
|
13897
|
+
emitAction(
|
|
13898
|
+
`accepted suggestion ${style.bold(id)}`,
|
|
13899
|
+
data,
|
|
13900
|
+
cmd.optsWithGlobals().json
|
|
13901
|
+
);
|
|
13631
13902
|
});
|
|
13632
|
-
suggestion.command("reject <id>").description(
|
|
13903
|
+
suggestion.command("reject <id>").description(
|
|
13904
|
+
"Reject a suggestion (POST /relationship-suggestions/{instanceId}/reject)"
|
|
13905
|
+
).option("--reason <reason>", "Why it's being rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
|
|
13633
13906
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13634
13907
|
const data = await runApi("Rejecting suggestion", async () => {
|
|
13635
13908
|
const client = await makeClient(cfg);
|
|
@@ -13641,9 +13914,18 @@ Examples:
|
|
|
13641
13914
|
}
|
|
13642
13915
|
});
|
|
13643
13916
|
});
|
|
13644
|
-
emitAction(
|
|
13917
|
+
emitAction(
|
|
13918
|
+
`rejected suggestion ${style.bold(id)}`,
|
|
13919
|
+
data,
|
|
13920
|
+
cmd.optsWithGlobals().json
|
|
13921
|
+
);
|
|
13645
13922
|
});
|
|
13646
|
-
suggestion.command("defer <id>").description(
|
|
13923
|
+
suggestion.command("defer <id>").description(
|
|
13924
|
+
"Defer a suggestion (POST /relationship-suggestions/{id}/defer)"
|
|
13925
|
+
).option(
|
|
13926
|
+
"--until <iso>",
|
|
13927
|
+
"Defer until this ISO date-time (omit to defer indefinitely)"
|
|
13928
|
+
).action(async (id, opts, cmd) => {
|
|
13647
13929
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
13648
13930
|
const data = await runApi("Deferring suggestion", async () => {
|
|
13649
13931
|
const client = await makeClient(cfg);
|
|
@@ -13654,7 +13936,11 @@ Examples:
|
|
|
13654
13936
|
}
|
|
13655
13937
|
});
|
|
13656
13938
|
});
|
|
13657
|
-
emitAction(
|
|
13939
|
+
emitAction(
|
|
13940
|
+
`deferred suggestion ${style.bold(id)}`,
|
|
13941
|
+
data,
|
|
13942
|
+
cmd.optsWithGlobals().json
|
|
13943
|
+
);
|
|
13658
13944
|
});
|
|
13659
13945
|
}
|
|
13660
13946
|
|
|
@@ -14367,17 +14653,15 @@ Examples:
|
|
|
14367
14653
|
$ sechroom work-task mark-no-residue mem_XXXX --decomposition wlp_XXXX
|
|
14368
14654
|
$ sechroom work-task residue-produce mem_XXXX --file residue.json`
|
|
14369
14655
|
);
|
|
14370
|
-
workTask.command("list").description(
|
|
14656
|
+
workTask.command("list").description(
|
|
14657
|
+
"List work tasks, newest-first (GET /work-tasks). Walks every page by default."
|
|
14658
|
+
).option("--shape <shape>", "Filter: bare | managed").option(
|
|
14371
14659
|
"--lane <lane>",
|
|
14372
14660
|
"Filter by dispatch-lane value, e.g. claude-code-chris"
|
|
14373
|
-
).option("--status <status>", "Filter by status value, e.g. in-progress").option(
|
|
14374
|
-
"--page-size <n>",
|
|
14375
|
-
"Page size (default 50, capped 200)",
|
|
14376
|
-
(v) => Number.parseInt(v, 10)
|
|
14377
|
-
).action(async (opts, cmd) => {
|
|
14661
|
+
).option("--status <status>", "Filter by status value, e.g. in-progress").option(...PAGE_OPTION).option(...PAGE_SIZE_OPTION).option(...NO_AUTO_PAGE_OPTION).action(async (opts, cmd) => {
|
|
14378
14662
|
const globals = cmd.optsWithGlobals();
|
|
14379
14663
|
const cfg = resolveConfig(globals);
|
|
14380
|
-
const
|
|
14664
|
+
const readPage = (query) => runApi("Listing work tasks", async () => {
|
|
14381
14665
|
const client = await makeClient(cfg);
|
|
14382
14666
|
return client.GET("/work-tasks", {
|
|
14383
14667
|
params: {
|
|
@@ -14385,12 +14669,19 @@ Examples:
|
|
|
14385
14669
|
shape: opts.shape,
|
|
14386
14670
|
lane: opts.lane,
|
|
14387
14671
|
status: opts.status,
|
|
14388
|
-
|
|
14389
|
-
pageSize: opts.pageSize
|
|
14672
|
+
...query
|
|
14390
14673
|
}
|
|
14391
14674
|
}
|
|
14392
14675
|
});
|
|
14393
14676
|
});
|
|
14677
|
+
const data = shouldAutoPage(opts) ? await fetchAllPages(
|
|
14678
|
+
(page) => readPage({
|
|
14679
|
+
page,
|
|
14680
|
+
...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
|
|
14681
|
+
}),
|
|
14682
|
+
opts,
|
|
14683
|
+
"tasks"
|
|
14684
|
+
) : await readPage(singlePageQuery(opts));
|
|
14394
14685
|
emitAction(
|
|
14395
14686
|
`listed ${style.bold(String(data.items.length))} of ${data.count} task(s)`,
|
|
14396
14687
|
data,
|
package/package.json
CHANGED