@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.
Files changed (2) hide show
  1. package/dist/index.js +467 -176
  2. 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 (typeof error === "object" && error !== null && "title" in error) {
702
+ } else if (isRecord(error)) {
692
703
  const problem = error;
693
- msg = String(problem.title);
694
- if (problem.errors && typeof problem.errors === "object") {
695
- const detail = Object.entries(problem.errors).flatMap(
696
- ([field, msgs]) => (Array.isArray(msgs) ? msgs : [msgs]).map((m) => ` ${field}: ${String(m)}`)
697
- );
698
- if (detail.length > 0) msg += `
699
- ${detail.join("\n")}`;
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
- "open"
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("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 600).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 ?? "open").toLowerCase() === "restricted" ? "restricted" : "open",
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
- "open"
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("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).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("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (id, opts, cmd) => {
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("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).option("--interval <seconds>", "Refresh interval", parseInteger, 40).action(async (id, opts, cmd) => {
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 ?? "open").trim().toLowerCase()) {
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
- confidence: intent.confidence ?? null,
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("--dry-run", "validate + print the snapshot payload without creating it or writing the file", false).addHelpText(
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
- confidence: opts.confidence != null ? Number(opts.confidence) : base.confidence
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 ?? null,
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
- emit({ dryRun: true, lane, scope, wouldCreate: body }, json);
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: opts.confidence != null ? Number(opts.confidence) : null
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("List work plans, newest-first (GET /decompositions)").option("--status <status>", "Filter by decomposition status").option("--brief <briefId>", "Filter by work-brief memory id").option("--page <n>", "1-based page number", (v) => Number.parseInt(v, 10)).option("--page-size <n>", "Page size", (v) => Number.parseInt(v, 10)).action(async (opts, cmd) => {
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 data = await runApi("Listing work plans", async () => {
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("List filing suggestions (GET /filing/suggestions)").option("--memory-id <memoryId>", "Filter to a single memory's suggestions").option(
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("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (opts, cmd) => {
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 data = await runApi("Listing filing suggestions", async () => {
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("Fetch a filing suggestion by id (GET /filing/suggestions/{id})").action(async (id, _opts, cmd) => {
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}", { params: { path: { 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("Preview a filing suggestion for a memory id or ad-hoc shape (POST /filing/suggestions/preview)").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("--scope-workspace <workspaceId>", "Scope the preview to a workspace").action(async (opts, cmd) => {
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("Accept a filing suggestion (POST /filing/suggestions/{id}/accept)").action(async (id, _opts, cmd) => {
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", { params: { path: { id } }, body: {} });
9008
+ return client.POST("/filing/suggestions/{id}/accept", {
9009
+ params: { path: { id } },
9010
+ body: {}
9011
+ });
8800
9012
  });
8801
- emitAction(`accepted filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
9013
+ emitAction(
9014
+ `accepted filing suggestion ${style.bold(id)}`,
9015
+ data,
9016
+ cmd.optsWithGlobals().json
9017
+ );
8802
9018
  });
8803
- filing.command("reject <id>").description("Reject a filing suggestion (POST /filing/suggestions/{id}/reject)").option("--reason <reason>", "Why the suggestion was rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
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(`rejected filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
9033
+ emitAction(
9034
+ `rejected filing suggestion ${style.bold(id)}`,
9035
+ data,
9036
+ cmd.optsWithGlobals().json
9037
+ );
8816
9038
  });
8817
- filing.command("defer <id>").description("Defer a filing suggestion (POST /filing/suggestions/{id}/defer)").option("--until <iso>", "Defer until an ISO-8601 timestamp (defaults to indefinite)").action(async (id, opts, cmd) => {
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(`deferred filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
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("Override the target then accept (POST /filing/suggestions/{id}/edit-and-accept)").option("--target-kind <kind>", "Workspace | Project").option("--existing-target-id <id>", "File into an existing workspace/project").option("--new-name <name>", "Create a new target with this name").option("--new-description <text>", "Description for the new target").option("--new-parent-workspace <workspaceId>", "Parent workspace for a new project").option("--memory-id <memoryId...>", "Override the memory set (repeatable)").action(async (id, opts, cmd) => {
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("Editing and accepting filing suggestion", async () => {
8831
- const client = await makeClient(cfg);
8832
- return client.POST("/filing/suggestions/{id}/edit-and-accept", {
8833
- params: { path: { id } },
8834
- body: {
8835
- targetKind: opts.targetKind ?? null,
8836
- existingTargetId: opts.existingTargetId ?? null,
8837
- newName: opts.newName ?? null,
8838
- newDescription: opts.newDescription ?? null,
8839
- newParentWorkspaceId: opts.newParentWorkspace ?? null,
8840
- overrideMemoryIds: opts.memoryId ?? null
8841
- }
8842
- });
8843
- });
8844
- emitAction(`edited & accepted filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
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
- const pages = [];
10169
- let page = 1;
10170
- while (true) {
10171
- const current = await runApi(
10172
- `Fetching relationships (page ${page})`,
10173
- async () => {
10174
- const client = await makeClient(cfg);
10175
- return client.GET("/memories/{memoryId}/relationships", {
10176
- params: { path: { memoryId }, query: { page } }
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("List archived memories (GET /memories/archived)").option("--workspace <workspaceId>", "Scope to a workspace").option("--project <projectId>", "Scope to a project").option("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (opts, cmd) => {
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 data = await runApi("Listing archived memories", async () => {
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("Create a relationship (POST /memories/{memoryId}/relationships)").option("--type <type>", "Relationship type (Reference, Related, Parent, Child, Follows, \u2026)", "Reference").option(
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(`--to-version must be a positive integer (got '${opts.toVersion}').`);
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}", { params: { path: { memoryId: toMemoryId } } })
13755
+ async () => client.GET("/memories/{memoryId}", {
13756
+ params: { path: { memoryId: toMemoryId } }
13757
+ })
13518
13758
  );
13519
13759
  if (typeof target.item?.currentVersion !== "number") {
13520
- fail(`Could not resolve the current version of ${toMemoryId}; pass --to-version explicitly.`);
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("List a memory's relationships (GET /memories/{memoryId}/relationships)").option("--direction <direction>", "Both | Outbound | Inbound").option("--include-deleted", "Include deleted relationships", false).option("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (memoryId, opts, cmd) => {
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 data = await runApi("Listing relationships", async () => {
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(`deleted relationship ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
13819
+ emitAction(
13820
+ `deleted relationship ${style.bold(id)}`,
13821
+ data,
13822
+ cmd.optsWithGlobals().json
13823
+ );
13572
13824
  });
13573
- relationship.command("suggest <memoryId>").description("Generate relationship suggestions for a memory (POST /memories/{memoryId}/suggest-relationships)").option("--limit <n>", "Max suggestions to generate").action(async (memoryId, opts, cmd) => {
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("List relationship suggestions (GET /relationship-suggestions)").option("--memory <memoryId>", "Filter to a memory").option(
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("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (opts, cmd) => {
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 data = await runApi("Listing suggestions", async () => {
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("Fetch a suggestion by id (GET /relationship-suggestions/{id})").action(async (id, _opts, cmd) => {
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}", { params: { path: { 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("Accept a suggestion (POST /relationship-suggestions/{instanceId}/accept)").action(async (id, _opts, cmd) => {
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(`accepted suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
13897
+ emitAction(
13898
+ `accepted suggestion ${style.bold(id)}`,
13899
+ data,
13900
+ cmd.optsWithGlobals().json
13901
+ );
13631
13902
  });
13632
- suggestion.command("reject <id>").description("Reject a suggestion (POST /relationship-suggestions/{instanceId}/reject)").option("--reason <reason>", "Why it's being rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
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(`rejected suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
13917
+ emitAction(
13918
+ `rejected suggestion ${style.bold(id)}`,
13919
+ data,
13920
+ cmd.optsWithGlobals().json
13921
+ );
13645
13922
  });
13646
- suggestion.command("defer <id>").description("Defer a suggestion (POST /relationship-suggestions/{id}/defer)").option("--until <iso>", "Defer until this ISO date-time (omit to defer indefinitely)").action(async (id, opts, cmd) => {
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(`deferred suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
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("List work tasks, newest-first (GET /work-tasks)").option("--shape <shape>", "Filter: bare | managed").option(
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("--page <n>", "1-based page number", (v) => Number.parseInt(v, 10)).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 data = await runApi("Listing work tasks", async () => {
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
- page: opts.page,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.8.2",
3
+ "version": "2026.8.3",
4
4
  "description": "Command-line interface for Sechroom — sign in, wire your AI tools, and work with your sechroom from the terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",