@sechroom/cli 2026.6.7 → 2026.6.9

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 (3) hide show
  1. package/README.md +30 -1
  2. package/dist/index.js +1144 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -132,6 +132,28 @@ export SECHROOM_TENANT=ocd
132
132
  sechroom --json memory search "rate limiting"
133
133
  ```
134
134
 
135
+ ## Command surface (MCP parity)
136
+
137
+ The CLI mirrors the sechroom MCP tool surface — every command is a thin wrapper over the same HTTP endpoint the matching MCP tool shims, so enforcement (`[TenantPermission]`) is identical. Run `sechroom <group> --help` for the subcommands + examples.
138
+
139
+ | Group | Covers |
140
+ |---|---|
141
+ | `memory` | create / get / search · edit-text(+batch) · archive / restore / move · versions / revert · list-archived · owners / tags / types · sum-tokens · similar · by-url |
142
+ | `relationship` | create / list / delete · suggest · `suggestion` get / accept / reject / defer |
143
+ | `workspace` | create / list / get · rename / describe / move · archive / restore · feed |
144
+ | `project` | create / list / get · rename / describe / move · status · victory-conditions · archive / restore |
145
+ | `filing` | suggestions / get · preview · accept / reject / defer / edit-and-accept |
146
+ | `continuity` | snapshot-create / -get · snapshots · resume-me / resume-lane · changed-since · load-set · grant / revoke-grant |
147
+ | `id` | next / peek (FR-_/D-_ sequence allocation) |
148
+ | `account` | profile / set-profile · feed · reviews / review-get / review-accept · lookup-batch |
149
+ | `chat` | messages · replies · stop-tracking (Slack / Discord, via `--surface`) |
150
+ | `worklog` · `lookup` | append · resolve any id |
151
+
152
+ Notes on deliberate gaps (API-rooted, not CLI):
153
+ - **No `memory delete`** — the API exposes no hard DELETE; `memory archive` is the soft-delete path.
154
+ - **No `chat send`** — the unified `/chat/*` surface has no POST send endpoint yet, so the Slack/Discord *send* tools can't be wrapped. Reading works today; `send` lands once the route is added to the spec.
155
+ - **`memory revert`** needs `--text` + `--content` — the revert endpoint doesn't reconstruct a version's body from its number; pull them from `memory versions` / `memory get` first.
156
+
135
157
  ## Onboarding (`init` / `setup`)
136
158
 
137
159
  `sechroom init` wires a project for sechroom by rendering the server's
@@ -196,7 +218,14 @@ src/
196
218
  config.ts base-url / tenant / token resolution + persistence
197
219
  generated/api.d.ts typed client — `pnpm run gen`; real types committed (hermetic)
198
220
  commands/
199
- memory.ts create / get / search
221
+ memory.ts create / get / search / edit / archive / move / versions / …
222
+ relationships.ts relationships + relationship-suggestions
223
+ workspace.ts workspace CRUD + feed
224
+ project.ts project CRUD + status / victory-conditions
225
+ filing.ts filing-suggestion review (accept / reject / defer / edit-and-accept)
226
+ continuity.ts snapshots + resume / grant
227
+ account.ts id next/peek + profile / feed / reviews / lookup-batch
228
+ chat.ts read Slack / Discord messages + replies
200
229
  worklog.ts append
201
230
  lookup.ts resolve any id (mem_…/unprefixed/sechroom:<id>) -> kind/title/url
202
231
  setup.ts init + setup mcp/agent-files
package/dist/index.js CHANGED
@@ -393,6 +393,46 @@ async function promptSelect(question, choices, def) {
393
393
  rl.close();
394
394
  }
395
395
  }
396
+ async function promptMultiSelect(question, choices, preselected = []) {
397
+ if (choices.length === 0) return [];
398
+ const pre = (v) => preselected.includes(v);
399
+ const preValues = () => choices.filter((c) => pre(c.value)).map((c) => c.value);
400
+ if (!canPrompt()) return preValues();
401
+ const { createInterface } = await import("readline");
402
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
403
+ try {
404
+ process.stderr.write(
405
+ `${style.bold(question)} ${style.dim("(numbers or 'all', comma-separated; Enter keeps \u25C9)")}
406
+ `
407
+ );
408
+ choices.forEach((c, i) => {
409
+ const box = pre(c.value) ? style.cyan("\u25C9") : "\u25CB";
410
+ const hint = c.hint ? ` ${style.dim(`\u2014 ${c.hint}`)}` : "";
411
+ process.stderr.write(` ${box} ${style.bold(String(i + 1))}. ${c.label}${hint}
412
+ `);
413
+ });
414
+ const answer = await new Promise((resolve) => {
415
+ rl.question(`Select ${style.dim("[Enter = \u25C9]")} `, resolve);
416
+ });
417
+ const trimmed = answer.trim().toLowerCase();
418
+ if (!trimmed) return preValues();
419
+ if (trimmed === "all") return choices.map((c) => c.value);
420
+ const picks = [];
421
+ for (const tok of trimmed.split(",").map((t) => t.trim()).filter(Boolean)) {
422
+ const n = Number(tok);
423
+ if (Number.isInteger(n) && n >= 1 && n <= choices.length) {
424
+ picks.push(choices[n - 1].value);
425
+ continue;
426
+ }
427
+ const byLabel = choices.find((c) => c.label.toLowerCase().startsWith(tok));
428
+ if (byLabel) picks.push(byLabel.value);
429
+ }
430
+ const uniq = [...new Set(picks)];
431
+ return uniq.length > 0 ? uniq : preValues();
432
+ } finally {
433
+ rl.close();
434
+ }
435
+ }
396
436
  async function withSpinner(text, fn) {
397
437
  const s = spinner(text);
398
438
  try {
@@ -470,7 +510,13 @@ Examples:
470
510
  $ sechroom memory create --text "filed note" --owner-type Workspace --owner-id wsp_XXXX
471
511
  $ sechroom memory search "rate limiting" --limit 5 --tag kind:decision
472
512
  $ sechroom memory search "auth flow" --workspace wsp_XXXX --json
473
- $ sechroom memory get mem_XXXX --json`
513
+ $ sechroom memory get mem_XXXX --json
514
+ $ sechroom memory edit-text mem_XXXX --old "teh" --new "the" --replace-all
515
+ $ sechroom memory edit-text-batch mem_XXXX --edit "foo=>bar" --edit "baz=>qux"
516
+ $ sechroom memory move mem_XXXX --owner-type Workspace --owner-id wsp_XXXX
517
+ $ sechroom memory archive mem_XXXX
518
+ $ sechroom memory list-archived --workspace wsp_XXXX --json
519
+ $ sechroom memory tags --json`
474
520
  );
475
521
  memory.command("create").description("Create a memory (POST /memories)").requiredOption("--text <text>", "Memory body text").option("--type <type>", "Memory type", "reference").option("--title <title>", "Optional title").option("--tag <tag...>", "Tags (repeatable)").option("--owner-type <ownerType>", "Workspace | Project | Unfiled", "Unfiled").option("--owner-id <ownerId>", "Owner id (required for Workspace/Project)").option("--source <source>", "Source / lane stamp", "cli").option("--confidence <n>", "Confidence 0..1", "1.0").action(async (opts, cmd) => {
476
522
  const cfg = resolveConfig(cmd.optsWithGlobals());
@@ -523,6 +569,201 @@ Examples:
523
569
  });
524
570
  emit(data, cmd.optsWithGlobals().json);
525
571
  });
572
+ memory.command("edit-text <memoryId>").description("Find/replace one substring (POST /memories/{memoryId}/edit-text)").requiredOption("--old <text>", "Text to find").requiredOption("--new <text>", "Replacement text").option("--replace-all", "Replace every occurrence (default: first only)", false).option("--regenerate-filing", "Re-run filing after the edit", false).option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
573
+ const cfg = resolveConfig(cmd.optsWithGlobals());
574
+ const data = await runApi("Editing memory text", async () => {
575
+ const client = await makeClient(cfg);
576
+ return client.POST("/memories/{memoryId}/edit-text", {
577
+ params: { path: { memoryId } },
578
+ body: {
579
+ memoryId,
580
+ oldText: opts.old,
581
+ newText: opts.new,
582
+ replaceAll: Boolean(opts.replaceAll),
583
+ regenerateFiling: Boolean(opts.regenerateFiling),
584
+ source: opts.source
585
+ }
586
+ });
587
+ });
588
+ emitAction(`edited ${style.bold(memoryId)} \u2192 v${style.bold(String(data.version))}`, data, cmd.optsWithGlobals().json);
589
+ });
590
+ memory.command("edit-text-batch <memoryId>").description("Apply many find/replace edits (POST /memories/{memoryId}/edit-text-batch)").requiredOption("--edit <old=>new...>", "Edit as 'old=>new' (repeatable)").option("--replace-all", "Apply replaceAll to every edit", false).option("--regenerate-filing", "Re-run filing after the edits", false).option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
591
+ const cfg = resolveConfig(cmd.optsWithGlobals());
592
+ const replaceAll = Boolean(opts.replaceAll);
593
+ const edits = opts.edit.map((spec) => {
594
+ const idx = spec.indexOf("=>");
595
+ if (idx < 0) {
596
+ process.stderr.write(`error: --edit must be 'old=>new', got: ${spec}
597
+ `);
598
+ process.exit(1);
599
+ }
600
+ return { oldText: spec.slice(0, idx), newText: spec.slice(idx + 2), replaceAll };
601
+ });
602
+ const data = await runApi("Applying batch edits", async () => {
603
+ const client = await makeClient(cfg);
604
+ return client.POST("/memories/{memoryId}/edit-text-batch", {
605
+ params: { path: { memoryId } },
606
+ body: {
607
+ memoryId,
608
+ edits,
609
+ regenerateFiling: Boolean(opts.regenerateFiling),
610
+ source: opts.source
611
+ }
612
+ });
613
+ });
614
+ emitAction(
615
+ `applied ${style.bold(String(data.editsApplied))} edit(s) \u2192 v${style.bold(String(data.version))}`,
616
+ data,
617
+ cmd.optsWithGlobals().json
618
+ );
619
+ });
620
+ memory.command("archive <memoryId>").description("Archive a memory (POST /memories/{memoryId}/archive)").option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
621
+ const cfg = resolveConfig(cmd.optsWithGlobals());
622
+ const data = await runApi("Archiving memory", async () => {
623
+ const client = await makeClient(cfg);
624
+ return client.POST("/memories/{memoryId}/archive", {
625
+ params: { path: { memoryId } },
626
+ body: { source: opts.source }
627
+ });
628
+ });
629
+ emitAction(`archived ${style.bold(memoryId)}`, data, cmd.optsWithGlobals().json);
630
+ });
631
+ memory.command("restore <memoryId>").description("Restore an archived memory (POST /memories/{memoryId}/restore)").option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
632
+ const cfg = resolveConfig(cmd.optsWithGlobals());
633
+ const data = await runApi("Restoring memory", async () => {
634
+ const client = await makeClient(cfg);
635
+ return client.POST("/memories/{memoryId}/restore", {
636
+ params: { path: { memoryId } },
637
+ body: { source: opts.source }
638
+ });
639
+ });
640
+ emitAction(`restored ${style.bold(memoryId)}`, data, cmd.optsWithGlobals().json);
641
+ });
642
+ memory.command("move <memoryId>").description("Move a memory to a new owner (POST /memories/{memoryId}/move)").requiredOption("--owner-type <ownerType>", "Unfiled | Workspace | Project | Candidate").option("--owner-id <ownerId>", "Owner id (required unless Unfiled)").option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
643
+ const cfg = resolveConfig(cmd.optsWithGlobals());
644
+ const data = await runApi("Moving memory", async () => {
645
+ const client = await makeClient(cfg);
646
+ return client.POST("/memories/{memoryId}/move", {
647
+ params: { path: { memoryId } },
648
+ body: {
649
+ to: {
650
+ type: opts.ownerType,
651
+ id: String(opts.ownerId ?? "")
652
+ },
653
+ source: opts.source
654
+ }
655
+ });
656
+ });
657
+ emitAction(`moved ${style.bold(memoryId)} \u2192 ${opts.ownerType}`, data, cmd.optsWithGlobals().json);
658
+ });
659
+ 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) => {
660
+ const cfg = resolveConfig(cmd.optsWithGlobals());
661
+ const data = await runApi("Listing archived memories", async () => {
662
+ const client = await makeClient(cfg);
663
+ return client.GET("/memories/archived", {
664
+ params: {
665
+ query: {
666
+ ...opts.workspace ? { workspaceId: opts.workspace } : {},
667
+ ...opts.project ? { projectId: opts.project } : {},
668
+ ...opts.page ? { page: Number(opts.page) } : {},
669
+ ...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
670
+ }
671
+ }
672
+ });
673
+ });
674
+ emit(data, cmd.optsWithGlobals().json);
675
+ });
676
+ memory.command("versions <memoryId>").description("List a memory's versions (GET /memories/{memoryId}/versions)").action(async (memoryId, _opts, cmd) => {
677
+ const cfg = resolveConfig(cmd.optsWithGlobals());
678
+ const data = await runApi("Fetching versions", async () => {
679
+ const client = await makeClient(cfg);
680
+ return client.GET("/memories/{memoryId}/versions", { params: { path: { memoryId } } });
681
+ });
682
+ emit(data, cmd.optsWithGlobals().json);
683
+ });
684
+ memory.command("revert <memoryId>").description("Revert a memory to an earlier version (POST /memories/{memoryId}/revert)").requiredOption("--from-version <n>", "Version to revert from").requiredOption("--text <text>", "Reverted text (the target version's text)").requiredOption("--content <content>", "Reverted content JSON (the target version's content)").option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
685
+ const cfg = resolveConfig(cmd.optsWithGlobals());
686
+ const data = await runApi("Reverting memory", async () => {
687
+ const client = await makeClient(cfg);
688
+ return client.POST("/memories/{memoryId}/revert", {
689
+ params: { path: { memoryId } },
690
+ body: {
691
+ fromVersion: Number(opts.fromVersion),
692
+ revertedContent: opts.content,
693
+ revertedText: opts.text,
694
+ source: opts.source
695
+ }
696
+ });
697
+ });
698
+ emitAction(`reverted ${style.bold(memoryId)} from v${opts.fromVersion}`, data, cmd.optsWithGlobals().json);
699
+ });
700
+ memory.command("owners").description("List owners with memory counts (GET /memories/owners)").option("--include-archived", "Include archived memories in counts", false).action(async (opts, cmd) => {
701
+ const cfg = resolveConfig(cmd.optsWithGlobals());
702
+ const data = await runApi("Fetching owners", async () => {
703
+ const client = await makeClient(cfg);
704
+ return client.GET("/memories/owners", {
705
+ params: { query: { includeArchived: Boolean(opts.includeArchived) } }
706
+ });
707
+ });
708
+ emit(data, cmd.optsWithGlobals().json);
709
+ });
710
+ memory.command("tags").description("List tags with memory counts (GET /memories/tags)").option("--include-archived", "Include archived memories in counts", false).action(async (opts, cmd) => {
711
+ const cfg = resolveConfig(cmd.optsWithGlobals());
712
+ const data = await runApi("Fetching tags", async () => {
713
+ const client = await makeClient(cfg);
714
+ return client.GET("/memories/tags", {
715
+ params: { query: { includeArchived: Boolean(opts.includeArchived) } }
716
+ });
717
+ });
718
+ emit(data, cmd.optsWithGlobals().json);
719
+ });
720
+ memory.command("types").description("List types with memory counts (GET /memories/types)").option("--include-archived", "Include archived memories in counts", false).action(async (opts, cmd) => {
721
+ const cfg = resolveConfig(cmd.optsWithGlobals());
722
+ const data = await runApi("Fetching types", async () => {
723
+ const client = await makeClient(cfg);
724
+ return client.GET("/memories/types", {
725
+ params: { query: { includeArchived: Boolean(opts.includeArchived) } }
726
+ });
727
+ });
728
+ emit(data, cmd.optsWithGlobals().json);
729
+ });
730
+ memory.command("sum-tokens").description("Sum token counts across memories (POST /memories/sum-tokens)").option("--id <memoryId...>", "Memory id(s) to sum (repeatable)").option("--snapshot <snapshotId>", "Sum a continuity snapshot's load set").action(async (opts, cmd) => {
731
+ const cfg = resolveConfig(cmd.optsWithGlobals());
732
+ const data = await runApi("Summing tokens", async () => {
733
+ const client = await makeClient(cfg);
734
+ return client.POST("/memories/sum-tokens", {
735
+ body: {
736
+ ids: opts.id ?? null,
737
+ snapshotId: opts.snapshot ?? null
738
+ }
739
+ });
740
+ });
741
+ emit(data, cmd.optsWithGlobals().json);
742
+ });
743
+ memory.command("similar <memoryId>").description("Find similar memories (GET /memories/{memoryId}/similar)").option("--limit <n>", "Max results").option("--threshold <n>", "Minimum similarity threshold").action(async (memoryId, opts, cmd) => {
744
+ const cfg = resolveConfig(cmd.optsWithGlobals());
745
+ const data = await runApi("Finding similar memories", async () => {
746
+ const client = await makeClient(cfg);
747
+ return client.GET("/memories/{memoryId}/similar", {
748
+ params: {
749
+ path: { memoryId },
750
+ query: {
751
+ ...opts.limit ? { limit: Number(opts.limit) } : {},
752
+ ...opts.threshold ? { threshold: Number(opts.threshold) } : {}
753
+ }
754
+ }
755
+ });
756
+ });
757
+ emit(data, cmd.optsWithGlobals().json);
758
+ });
759
+ memory.command("by-url <url>").description("Resolve a memory by its canonical URL (GET /memories/by-url)").action(async (url, _opts, cmd) => {
760
+ const cfg = resolveConfig(cmd.optsWithGlobals());
761
+ const data = await runApi("Resolving memory by URL", async () => {
762
+ const client = await makeClient(cfg);
763
+ return client.GET("/memories/by-url", { params: { query: { url } } });
764
+ });
765
+ emit(data, cmd.optsWithGlobals().json);
766
+ });
526
767
  }
527
768
 
528
769
  // src/commands/worklog.ts
@@ -579,6 +820,889 @@ Examples:
579
820
  });
580
821
  }
581
822
 
823
+ // src/commands/relationships.ts
824
+ function registerRelationships(program2) {
825
+ const relationship = program2.command("relationship").description("Create, list, and manage memory relationships + suggestions");
826
+ relationship.addHelpText(
827
+ "after",
828
+ `
829
+ Examples:
830
+ $ sechroom relationship create mem_FROM mem_TO --type Reference
831
+ $ sechroom relationship list mem_XXXX --direction Outbound --json
832
+ $ sechroom relationship delete rel_XXXX
833
+ $ sechroom relationship suggest mem_XXXX --limit 5
834
+ $ sechroom relationship suggestions --status Pending --memory mem_XXXX
835
+ $ sechroom relationship suggestion accept rsg_XXXX`
836
+ );
837
+ 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").action(async (fromMemoryId, toMemoryId, opts, cmd) => {
838
+ const cfg = resolveConfig(cmd.optsWithGlobals());
839
+ const data = await runApi("Creating relationship", async () => {
840
+ const client = await makeClient(cfg);
841
+ return client.POST("/memories/{memoryId}/relationships", {
842
+ params: { path: { memoryId: fromMemoryId } },
843
+ body: {
844
+ toMemoryId,
845
+ type: opts.type
846
+ }
847
+ });
848
+ });
849
+ const inversePart = data.inverseId ? ` ${style.dim(`(inverse ${data.inverseId})`)}` : "";
850
+ const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
851
+ emitAction(
852
+ `created relationship ${style.bold(data.id)} ${style.dim(`${fromMemoryId} \u2192 ${toMemoryId}`)}${inversePart}${urlPart}`,
853
+ data,
854
+ cmd.optsWithGlobals().json
855
+ );
856
+ });
857
+ 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) => {
858
+ const cfg = resolveConfig(cmd.optsWithGlobals());
859
+ const data = await runApi("Listing relationships", async () => {
860
+ const client = await makeClient(cfg);
861
+ return client.GET("/memories/{memoryId}/relationships", {
862
+ params: {
863
+ path: { memoryId },
864
+ query: {
865
+ ...opts.direction ? { direction: opts.direction } : {},
866
+ ...opts.includeDeleted ? { includeDeleted: true } : {},
867
+ ...opts.page ? { page: Number(opts.page) } : {},
868
+ ...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
869
+ }
870
+ }
871
+ });
872
+ });
873
+ emit(data, cmd.optsWithGlobals().json);
874
+ });
875
+ relationship.command("delete <id>").description("Delete a relationship (DELETE /relationships/{id})").action(async (id, _opts, cmd) => {
876
+ const cfg = resolveConfig(cmd.optsWithGlobals());
877
+ const data = await runApi("Deleting relationship", async () => {
878
+ const client = await makeClient(cfg);
879
+ return client.DELETE("/relationships/{id}", {
880
+ params: { path: { id } },
881
+ body: {}
882
+ });
883
+ });
884
+ emitAction(`deleted relationship ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
885
+ });
886
+ 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) => {
887
+ const cfg = resolveConfig(cmd.optsWithGlobals());
888
+ const data = await runApi("Suggesting relationships", async () => {
889
+ const client = await makeClient(cfg);
890
+ return client.POST("/memories/{memoryId}/suggest-relationships", {
891
+ params: { path: { memoryId } },
892
+ body: {
893
+ limit: opts.limit ? Number(opts.limit) : null
894
+ }
895
+ });
896
+ });
897
+ emitAction(
898
+ `suggested ${style.bold(String(data.suggested))} for ${memoryId} ${style.dim(`(considered ${data.considered}, suppressed ${data.suppressed})`)}`,
899
+ data,
900
+ cmd.optsWithGlobals().json
901
+ );
902
+ });
903
+ relationship.command("suggestions").description("List relationship suggestions (GET /relationship-suggestions)").option("--memory <memoryId>", "Filter to a memory").option(
904
+ "--status <status>",
905
+ "Pending | Accepted | EditedAndAccepted | Rejected | Superseded | Deferred | Invalidated"
906
+ ).option("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (opts, cmd) => {
907
+ const cfg = resolveConfig(cmd.optsWithGlobals());
908
+ const data = await runApi("Listing suggestions", async () => {
909
+ const client = await makeClient(cfg);
910
+ return client.GET("/relationship-suggestions", {
911
+ params: {
912
+ query: {
913
+ ...opts.memory ? { memoryId: opts.memory } : {},
914
+ ...opts.status ? {
915
+ status: opts.status
916
+ } : {},
917
+ ...opts.page ? { page: Number(opts.page) } : {},
918
+ ...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
919
+ }
920
+ }
921
+ });
922
+ });
923
+ emit(data, cmd.optsWithGlobals().json);
924
+ });
925
+ const suggestion = relationship.command("suggestion").description("Inspect and decide on a single relationship suggestion");
926
+ suggestion.command("get <id>").description("Fetch a suggestion by id (GET /relationship-suggestions/{id})").action(async (id, _opts, cmd) => {
927
+ const cfg = resolveConfig(cmd.optsWithGlobals());
928
+ const data = await runApi("Fetching suggestion", async () => {
929
+ const client = await makeClient(cfg);
930
+ return client.GET("/relationship-suggestions/{id}", { params: { path: { id } } });
931
+ });
932
+ emit(data, cmd.optsWithGlobals().json);
933
+ });
934
+ suggestion.command("accept <id>").description("Accept a suggestion (POST /relationship-suggestions/{id}/accept)").action(async (id, _opts, cmd) => {
935
+ const cfg = resolveConfig(cmd.optsWithGlobals());
936
+ const data = await runApi("Accepting suggestion", async () => {
937
+ const client = await makeClient(cfg);
938
+ return client.POST("/relationship-suggestions/{id}/accept", {
939
+ params: { path: { id } },
940
+ body: {}
941
+ });
942
+ });
943
+ emitAction(`accepted suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
944
+ });
945
+ suggestion.command("reject <id>").description("Reject a suggestion (POST /relationship-suggestions/{id}/reject)").option("--reason <reason>", "Why it's being rejected").option("--reason-code <code>", "Structured reason code").action(async (id, opts, cmd) => {
946
+ const cfg = resolveConfig(cmd.optsWithGlobals());
947
+ const data = await runApi("Rejecting suggestion", async () => {
948
+ const client = await makeClient(cfg);
949
+ return client.POST("/relationship-suggestions/{id}/reject", {
950
+ params: { path: { id } },
951
+ body: {
952
+ reason: opts.reason ?? null,
953
+ ...opts.reasonCode ? { reasonCode: opts.reasonCode } : {}
954
+ }
955
+ });
956
+ });
957
+ emitAction(`rejected suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
958
+ });
959
+ 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) => {
960
+ const cfg = resolveConfig(cmd.optsWithGlobals());
961
+ const data = await runApi("Deferring suggestion", async () => {
962
+ const client = await makeClient(cfg);
963
+ return client.POST("/relationship-suggestions/{id}/defer", {
964
+ params: { path: { id } },
965
+ body: {
966
+ until: opts.until ?? null
967
+ }
968
+ });
969
+ });
970
+ emitAction(`deferred suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
971
+ });
972
+ }
973
+
974
+ // src/commands/workspace.ts
975
+ function registerWorkspace(program2) {
976
+ const workspace = program2.command("workspace").description("Create, browse, and manage workspaces");
977
+ workspace.addHelpText(
978
+ "after",
979
+ `
980
+ Examples:
981
+ $ sechroom workspace create --name "My Workspace" --parent wsp_XXXX
982
+ $ sechroom workspace list --include-archived --json
983
+ $ sechroom workspace get wsp_XXXX --json
984
+ $ sechroom workspace rename wsp_XXXX --name "Renamed"
985
+ $ sechroom workspace move wsp_XXXX --parent wsp_YYYY
986
+ $ sechroom workspace feed wsp_XXXX --limit 20 --cascade`
987
+ );
988
+ workspace.command("create").description("Create a workspace (POST /workspaces)").requiredOption("--name <name>", "Workspace name").option("--description <description>", "Optional description").option("--parent <parentId>", "Parent workspace id (omit for a top-level workspace)").action(async (opts, cmd) => {
989
+ const cfg = resolveConfig(cmd.optsWithGlobals());
990
+ const data = await runApi("Creating workspace", async () => {
991
+ const client = await makeClient(cfg);
992
+ return client.POST("/workspaces", {
993
+ body: {
994
+ name: opts.name,
995
+ description: opts.description ?? null,
996
+ parentId: opts.parent ?? null
997
+ }
998
+ });
999
+ });
1000
+ const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
1001
+ emitAction(
1002
+ `created workspace ${style.bold(data.id)} ${style.dim(`"${opts.name}"`)}${urlPart}`,
1003
+ data,
1004
+ cmd.optsWithGlobals().json
1005
+ );
1006
+ });
1007
+ workspace.command("list").description("List the caller's workspaces (GET /workspaces)").option("--include-archived", "Include archived workspaces", false).action(async (opts, cmd) => {
1008
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1009
+ const data = await runApi("Listing workspaces", async () => {
1010
+ const client = await makeClient(cfg);
1011
+ return client.GET("/workspaces", {
1012
+ params: { query: { includeArchived: Boolean(opts.includeArchived) } }
1013
+ });
1014
+ });
1015
+ emit(data, cmd.optsWithGlobals().json);
1016
+ });
1017
+ workspace.command("get <workspaceId>").description("Fetch a workspace by id (GET /workspaces/{workspaceId})").action(async (workspaceId, _opts, cmd) => {
1018
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1019
+ const data = await runApi("Fetching workspace", async () => {
1020
+ const client = await makeClient(cfg);
1021
+ return client.GET("/workspaces/{workspaceId}", { params: { path: { workspaceId } } });
1022
+ });
1023
+ emit(data, cmd.optsWithGlobals().json);
1024
+ });
1025
+ workspace.command("rename <workspaceId>").description("Rename a workspace (PUT /workspaces/{workspaceId}/rename)").requiredOption("--name <name>", "New workspace name").action(async (workspaceId, opts, cmd) => {
1026
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1027
+ const data = await runApi("Renaming workspace", async () => {
1028
+ const client = await makeClient(cfg);
1029
+ return client.PUT("/workspaces/{workspaceId}/rename", {
1030
+ params: { path: { workspaceId } },
1031
+ body: { newName: opts.name }
1032
+ });
1033
+ });
1034
+ emitAction(
1035
+ `renamed workspace ${style.bold(workspaceId)} ${style.dim(`\u2192 "${opts.name}"`)}`,
1036
+ data,
1037
+ cmd.optsWithGlobals().json
1038
+ );
1039
+ });
1040
+ workspace.command("describe <workspaceId>").description("Set a workspace description (PUT /workspaces/{workspaceId}/description)").requiredOption("--description <description>", "New description").action(async (workspaceId, opts, cmd) => {
1041
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1042
+ const data = await runApi("Updating description", async () => {
1043
+ const client = await makeClient(cfg);
1044
+ return client.PUT("/workspaces/{workspaceId}/description", {
1045
+ params: { path: { workspaceId } },
1046
+ body: { newDescription: opts.description }
1047
+ });
1048
+ });
1049
+ emitAction(
1050
+ `updated description for workspace ${style.bold(workspaceId)}`,
1051
+ data,
1052
+ cmd.optsWithGlobals().json
1053
+ );
1054
+ });
1055
+ workspace.command("move <workspaceId>").description("Move a workspace under a new parent (POST /workspaces/{workspaceId}/move)").option("--parent <parentId>", "New parent workspace id (omit to move to top level)").action(async (workspaceId, opts, cmd) => {
1056
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1057
+ const data = await runApi("Moving workspace", async () => {
1058
+ const client = await makeClient(cfg);
1059
+ return client.POST("/workspaces/{workspaceId}/move", {
1060
+ params: { path: { workspaceId } },
1061
+ body: { newParentId: opts.parent ?? null }
1062
+ });
1063
+ });
1064
+ const target = opts.parent ? `under ${style.bold(opts.parent)}` : "to top level";
1065
+ emitAction(
1066
+ `moved workspace ${style.bold(workspaceId)} ${style.dim(target)}`,
1067
+ data,
1068
+ cmd.optsWithGlobals().json
1069
+ );
1070
+ });
1071
+ workspace.command("archive <workspaceId>").description("Archive a workspace (POST /workspaces/{workspaceId}/archive)").action(async (workspaceId, _opts, cmd) => {
1072
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1073
+ const data = await runApi("Archiving workspace", async () => {
1074
+ const client = await makeClient(cfg);
1075
+ return client.POST("/workspaces/{workspaceId}/archive", {
1076
+ params: { path: { workspaceId } },
1077
+ body: {}
1078
+ });
1079
+ });
1080
+ emitAction(`archived workspace ${style.bold(workspaceId)}`, data, cmd.optsWithGlobals().json);
1081
+ });
1082
+ workspace.command("restore <workspaceId>").description("Restore an archived workspace (POST /workspaces/{workspaceId}/restore)").action(async (workspaceId, _opts, cmd) => {
1083
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1084
+ const data = await runApi("Restoring workspace", async () => {
1085
+ const client = await makeClient(cfg);
1086
+ return client.POST("/workspaces/{workspaceId}/restore", {
1087
+ params: { path: { workspaceId } },
1088
+ body: {}
1089
+ });
1090
+ });
1091
+ emitAction(`restored workspace ${style.bold(workspaceId)}`, data, cmd.optsWithGlobals().json);
1092
+ });
1093
+ workspace.command("feed <workspaceId>").description("List a workspace's memory feed (GET /workspaces/{workspaceId}/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Paging cursor from a prior page").option("--cascade", "Cascade into descendant workspaces", false).option("--include-projects", "Include the workspace's projects", false).option("--include-archived", "Include archived memories", false).option("--query <query>", "Filter the feed by text").option("--tag <tag>", "Filter tags (comma-separated)").action(async (workspaceId, opts, cmd) => {
1094
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1095
+ const data = await runApi("Fetching feed", async () => {
1096
+ const client = await makeClient(cfg);
1097
+ return client.GET("/workspaces/{workspaceId}/memories/feed", {
1098
+ params: {
1099
+ path: { workspaceId },
1100
+ query: {
1101
+ limit: Number(opts.limit),
1102
+ cascadeWorkspaces: Boolean(opts.cascade),
1103
+ includeProjects: Boolean(opts.includeProjects),
1104
+ includeArchived: Boolean(opts.includeArchived),
1105
+ ...opts.cursor ? { cursor: opts.cursor } : {},
1106
+ ...opts.query ? { query: opts.query } : {},
1107
+ ...opts.tag ? { filterTags: opts.tag } : {}
1108
+ }
1109
+ }
1110
+ });
1111
+ });
1112
+ emit(data, cmd.optsWithGlobals().json);
1113
+ });
1114
+ }
1115
+
1116
+ // src/commands/project.ts
1117
+ function registerProject(program2) {
1118
+ const project = program2.command("project").description("Create, browse, and manage projects");
1119
+ project.addHelpText(
1120
+ "after",
1121
+ `
1122
+ Examples:
1123
+ $ sechroom project create --workspace wsp_XXXX --name "Onboarding" --slug onboarding
1124
+ $ sechroom project list --workspace wsp_XXXX --status Active
1125
+ $ sechroom project get prj_XXXX --json
1126
+ $ sechroom project rename prj_XXXX --name "New Name" --slug new-name
1127
+ $ sechroom project status prj_XXXX --status Completed`
1128
+ );
1129
+ project.command("create").description("Create a project (POST /projects)").requiredOption("--workspace <workspaceId>", "Owning workspace id").requiredOption("--name <name>", "Project name").requiredOption("--slug <slug>", "URL slug").option("--description <description>", "Optional description").option("--parent <parentProjectId>", "Optional parent project id").action(async (opts, cmd) => {
1130
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1131
+ const data = await runApi("Creating project", async () => {
1132
+ const client = await makeClient(cfg);
1133
+ return client.POST("/projects", {
1134
+ body: {
1135
+ workspaceId: opts.workspace,
1136
+ name: opts.name,
1137
+ slug: opts.slug,
1138
+ description: opts.description ?? null,
1139
+ parentProjectId: opts.parent ?? null
1140
+ }
1141
+ });
1142
+ });
1143
+ const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
1144
+ emitAction(`created project ${style.bold(data.id)}${urlPart}`, data, cmd.optsWithGlobals().json);
1145
+ });
1146
+ project.command("list").description("List projects (GET /projects)").option("--workspace <workspaceId>", "Scope to a workspace").option("--status <status>", "Draft | Active | OnHold | Completed | Cancelled").option("--include-archived", "Include archived projects", false).action(async (opts, cmd) => {
1147
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1148
+ const data = await runApi("Listing projects", async () => {
1149
+ const client = await makeClient(cfg);
1150
+ return client.GET("/projects", {
1151
+ params: {
1152
+ query: {
1153
+ ...opts.workspace ? { workspaceId: opts.workspace } : {},
1154
+ ...opts.status ? { status: opts.status } : {},
1155
+ ...opts.includeArchived ? { includeArchived: true } : {}
1156
+ }
1157
+ }
1158
+ });
1159
+ });
1160
+ emit(data, cmd.optsWithGlobals().json);
1161
+ });
1162
+ project.command("get <projectId>").description("Fetch a project by id (GET /projects/{projectId})").action(async (projectId, _opts, cmd) => {
1163
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1164
+ const data = await runApi("Fetching project", async () => {
1165
+ const client = await makeClient(cfg);
1166
+ return client.GET("/projects/{projectId}", { params: { path: { projectId } } });
1167
+ });
1168
+ emit(data, cmd.optsWithGlobals().json);
1169
+ });
1170
+ project.command("rename <projectId>").description("Rename a project (PUT /projects/{projectId}/rename)").requiredOption("--name <name>", "New project name").requiredOption("--slug <slug>", "New URL slug").action(async (projectId, opts, cmd) => {
1171
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1172
+ const data = await runApi("Renaming project", async () => {
1173
+ const client = await makeClient(cfg);
1174
+ return client.PUT("/projects/{projectId}/rename", {
1175
+ params: { path: { projectId } },
1176
+ body: { newName: opts.name, newSlug: opts.slug }
1177
+ });
1178
+ });
1179
+ emitAction(`renamed project ${style.bold(projectId)} \u2192 ${style.bold(opts.name)}`, data, cmd.optsWithGlobals().json);
1180
+ });
1181
+ project.command("describe <projectId>").description("Set a project's description (PUT /projects/{projectId}/description)").requiredOption("--description <description>", "New description (empty string to clear)").action(async (projectId, opts, cmd) => {
1182
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1183
+ const data = await runApi("Updating description", async () => {
1184
+ const client = await makeClient(cfg);
1185
+ return client.PUT("/projects/{projectId}/description", {
1186
+ params: { path: { projectId } },
1187
+ body: { newDescription: opts.description }
1188
+ });
1189
+ });
1190
+ emitAction(`updated description on project ${style.bold(projectId)}`, data, cmd.optsWithGlobals().json);
1191
+ });
1192
+ project.command("move <projectId>").description("Move a project to another workspace/parent (POST /projects/{projectId}/move)").requiredOption("--workspace <toWorkspaceId>", "Destination workspace id").option("--parent <toParentId>", "Destination parent project id").action(async (projectId, opts, cmd) => {
1193
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1194
+ const data = await runApi("Moving project", async () => {
1195
+ const client = await makeClient(cfg);
1196
+ return client.POST("/projects/{projectId}/move", {
1197
+ params: { path: { projectId } },
1198
+ body: { toWorkspaceId: opts.workspace, toParentId: opts.parent ?? null }
1199
+ });
1200
+ });
1201
+ emitAction(`moved project ${style.bold(projectId)} \u2192 ${style.bold(opts.workspace)}`, data, cmd.optsWithGlobals().json);
1202
+ });
1203
+ project.command("status <projectId>").description("Set a project's status (POST /projects/{projectId}/status)").requiredOption("--status <status>", "Draft | Active | OnHold | Completed | Cancelled").action(async (projectId, opts, cmd) => {
1204
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1205
+ const data = await runApi("Setting status", async () => {
1206
+ const client = await makeClient(cfg);
1207
+ return client.POST("/projects/{projectId}/status", {
1208
+ params: { path: { projectId } },
1209
+ body: {
1210
+ status: opts.status
1211
+ }
1212
+ });
1213
+ });
1214
+ emitAction(`set project ${style.bold(projectId)} status \u2192 ${style.bold(opts.status)}`, data, cmd.optsWithGlobals().json);
1215
+ });
1216
+ project.command("victory-conditions <projectId>").description("Set a project's victory conditions (PUT /projects/{projectId}/victory-conditions)").requiredOption("--conditions <conditions>", "Victory conditions text (empty string to clear)").action(async (projectId, opts, cmd) => {
1217
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1218
+ const data = await runApi("Updating victory conditions", async () => {
1219
+ const client = await makeClient(cfg);
1220
+ return client.PUT("/projects/{projectId}/victory-conditions", {
1221
+ params: { path: { projectId } },
1222
+ body: { victoryConditions: opts.conditions }
1223
+ });
1224
+ });
1225
+ emitAction(`updated victory conditions on project ${style.bold(projectId)}`, data, cmd.optsWithGlobals().json);
1226
+ });
1227
+ project.command("archive <projectId>").description("Archive a project (POST /projects/{projectId}/archive)").action(async (projectId, _opts, cmd) => {
1228
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1229
+ const data = await runApi("Archiving project", async () => {
1230
+ const client = await makeClient(cfg);
1231
+ return client.POST("/projects/{projectId}/archive", {
1232
+ params: { path: { projectId } },
1233
+ body: {}
1234
+ });
1235
+ });
1236
+ emitAction(`archived project ${style.bold(projectId)}`, data, cmd.optsWithGlobals().json);
1237
+ });
1238
+ project.command("restore <projectId>").description("Restore an archived project (POST /projects/{projectId}/restore)").action(async (projectId, _opts, cmd) => {
1239
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1240
+ const data = await runApi("Restoring project", async () => {
1241
+ const client = await makeClient(cfg);
1242
+ return client.POST("/projects/{projectId}/restore", {
1243
+ params: { path: { projectId } },
1244
+ body: {}
1245
+ });
1246
+ });
1247
+ emitAction(`restored project ${style.bold(projectId)}`, data, cmd.optsWithGlobals().json);
1248
+ });
1249
+ }
1250
+
1251
+ // src/commands/filing.ts
1252
+ function registerFiling(program2) {
1253
+ const filing = program2.command("filing").description("Review and act on filing suggestions");
1254
+ filing.addHelpText(
1255
+ "after",
1256
+ `
1257
+ Examples:
1258
+ $ sechroom filing suggestions --status Pending --page-size 20
1259
+ $ sechroom filing get fsg_XXXX --json
1260
+ $ sechroom filing preview --memory-id mem_XXXX
1261
+ $ sechroom filing accept fsg_XXXX
1262
+ $ sechroom filing reject fsg_XXXX --reason "wrong workspace"
1263
+ $ sechroom filing edit-and-accept fsg_XXXX --target-kind Workspace --existing-target-id wsp_XXXX`
1264
+ );
1265
+ filing.command("suggestions").description("List filing suggestions (GET /filing/suggestions)").option("--memory-id <memoryId>", "Filter to a single memory's suggestions").option(
1266
+ "--status <status>",
1267
+ "Generating | Pending | Accepted | Rejected | EditedAndAccepted | Deferred | Invalidated"
1268
+ ).option("--page <n>", "Page number").option("--page-size <n>", "Page size").action(async (opts, cmd) => {
1269
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1270
+ const data = await runApi("Listing filing suggestions", async () => {
1271
+ const client = await makeClient(cfg);
1272
+ return client.GET("/filing/suggestions", {
1273
+ params: {
1274
+ query: {
1275
+ ...opts.memoryId ? { memoryId: opts.memoryId } : {},
1276
+ ...opts.status ? { status: opts.status } : {},
1277
+ ...opts.page ? { page: Number(opts.page) } : {},
1278
+ ...opts.pageSize ? { pageSize: Number(opts.pageSize) } : {}
1279
+ }
1280
+ }
1281
+ });
1282
+ });
1283
+ emit(data, cmd.optsWithGlobals().json);
1284
+ });
1285
+ filing.command("get <id>").description("Fetch a filing suggestion by id (GET /filing/suggestions/{id})").action(async (id, _opts, cmd) => {
1286
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1287
+ const data = await runApi("Fetching filing suggestion", async () => {
1288
+ const client = await makeClient(cfg);
1289
+ return client.GET("/filing/suggestions/{id}", { params: { path: { id } } });
1290
+ });
1291
+ emit(data, cmd.optsWithGlobals().json);
1292
+ });
1293
+ 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) => {
1294
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1295
+ const memory = opts.text ? {
1296
+ text: opts.text,
1297
+ title: opts.title ?? null,
1298
+ tags: opts.tag ?? null,
1299
+ type: opts.type
1300
+ } : null;
1301
+ const data = await runApi("Previewing filing suggestion", async () => {
1302
+ const client = await makeClient(cfg);
1303
+ return client.POST("/filing/suggestions/preview", {
1304
+ body: {
1305
+ memoryId: opts.memoryId ?? null,
1306
+ memory,
1307
+ scopeWorkspaceId: opts.scopeWorkspace ?? null
1308
+ }
1309
+ });
1310
+ });
1311
+ emit(data, cmd.optsWithGlobals().json);
1312
+ });
1313
+ filing.command("accept <id>").description("Accept a filing suggestion (POST /filing/suggestions/{id}/accept)").action(async (id, _opts, cmd) => {
1314
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1315
+ const data = await runApi("Accepting filing suggestion", async () => {
1316
+ const client = await makeClient(cfg);
1317
+ return client.POST("/filing/suggestions/{id}/accept", { params: { path: { id } }, body: {} });
1318
+ });
1319
+ emitAction(`accepted filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
1320
+ });
1321
+ 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) => {
1322
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1323
+ const data = await runApi("Rejecting filing suggestion", async () => {
1324
+ const client = await makeClient(cfg);
1325
+ return client.POST("/filing/suggestions/{id}/reject", {
1326
+ params: { path: { id } },
1327
+ body: {
1328
+ reason: opts.reason ?? null,
1329
+ ...opts.reasonCode ? { reasonCode: opts.reasonCode } : {}
1330
+ }
1331
+ });
1332
+ });
1333
+ emitAction(`rejected filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
1334
+ });
1335
+ 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) => {
1336
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1337
+ const data = await runApi("Deferring filing suggestion", async () => {
1338
+ const client = await makeClient(cfg);
1339
+ return client.POST("/filing/suggestions/{id}/defer", {
1340
+ params: { path: { id } },
1341
+ body: { until: opts.until ?? null }
1342
+ });
1343
+ });
1344
+ emitAction(`deferred filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
1345
+ });
1346
+ 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) => {
1347
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1348
+ const data = await runApi("Editing and accepting filing suggestion", async () => {
1349
+ const client = await makeClient(cfg);
1350
+ return client.POST("/filing/suggestions/{id}/edit-and-accept", {
1351
+ params: { path: { id } },
1352
+ body: {
1353
+ targetKind: opts.targetKind ?? null,
1354
+ existingTargetId: opts.existingTargetId ?? null,
1355
+ newName: opts.newName ?? null,
1356
+ newDescription: opts.newDescription ?? null,
1357
+ newParentWorkspaceId: opts.newParentWorkspace ?? null,
1358
+ overrideMemoryIds: opts.memoryId ?? null
1359
+ }
1360
+ });
1361
+ });
1362
+ emitAction(`edited & accepted filing suggestion ${style.bold(id)}`, data, cmd.optsWithGlobals().json);
1363
+ });
1364
+ }
1365
+
1366
+ // src/commands/continuity.ts
1367
+ function registerContinuity(program2) {
1368
+ const continuity = program2.command("continuity").description("Continuity snapshots: checkpoint and resume work");
1369
+ continuity.addHelpText(
1370
+ "after",
1371
+ `
1372
+ Examples:
1373
+ $ sechroom continuity snapshot-create --lane claude-code-chris --scope loop-spec \\
1374
+ --objective "ship slice 9" --state "tests green, PR open" \\
1375
+ --last-action "raised PR #1425" --next-action "watch for merge" \\
1376
+ --resume-instruction "load csn id, resume at step 10"
1377
+ $ sechroom continuity snapshots --scope loop-spec
1378
+ $ sechroom continuity snapshot-get csn_XXXX --json
1379
+ $ sechroom continuity resume-me --max-artifacts 20
1380
+ $ sechroom continuity changed-since --since 2026-06-01T00:00:00Z
1381
+ $ sechroom continuity grant csn_XXXX --grantee usr_XXXX`
1382
+ );
1383
+ continuity.command("snapshot-create").description("Create a continuity snapshot (POST /continuity/snapshots)").requiredOption("--lane <laneId>", "Lane id (e.g. claude-code-chris)").requiredOption("--scope <scope>", "Snapshot scope (e.g. loop-spec)").requiredOption("--objective <text>", "Current objective").requiredOption("--state <text>", "Current state").requiredOption("--last-action <text>", "Last meaningful action").requiredOption("--next-action <text>", "Next intended action").requiredOption("--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").action(async (opts, cmd) => {
1384
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1385
+ const data = await runApi("Creating snapshot", async () => {
1386
+ const client = await makeClient(cfg);
1387
+ return client.POST("/continuity/snapshots", {
1388
+ body: {
1389
+ laneId: opts.lane,
1390
+ scope: opts.scope,
1391
+ currentObjective: opts.objective,
1392
+ currentState: opts.state,
1393
+ lastMeaningfulAction: opts.lastAction,
1394
+ nextIntendedAction: opts.nextAction,
1395
+ resumeInstruction: opts.resumeInstruction,
1396
+ activeConstraints: opts.constraint ?? null,
1397
+ openQuestions: opts.question ?? null,
1398
+ surfaceMarkers: opts.surfaceMarker ?? null,
1399
+ relevantArtifactIds: opts.artifact ?? null,
1400
+ confidence: opts.confidence != null ? Number(opts.confidence) : null
1401
+ }
1402
+ });
1403
+ });
1404
+ emitAction(`created snapshot ${style.bold(data.snapshotId)}`, data, cmd.optsWithGlobals().json);
1405
+ });
1406
+ continuity.command("snapshot-get <id>").description("Fetch a snapshot by id (GET /continuity/snapshots/{id})").action(async (id, _opts, cmd) => {
1407
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1408
+ const data = await runApi("Fetching snapshot", async () => {
1409
+ const client = await makeClient(cfg);
1410
+ return client.GET("/continuity/snapshots/{id}", { params: { path: { id } } });
1411
+ });
1412
+ emit(data, cmd.optsWithGlobals().json);
1413
+ });
1414
+ continuity.command("snapshots").description("List the caller's own snapshots (GET /me/continuity/snapshots)").option("--scope <scope>", "Filter by scope").option("--lane <laneId>", "Filter by lane id").action(async (opts, cmd) => {
1415
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1416
+ const data = await runApi("Listing snapshots", async () => {
1417
+ const client = await makeClient(cfg);
1418
+ return client.GET("/me/continuity/snapshots", {
1419
+ params: {
1420
+ query: {
1421
+ ...opts.scope ? { scope: opts.scope } : {},
1422
+ ...opts.lane ? { laneId: opts.lane } : {}
1423
+ }
1424
+ }
1425
+ });
1426
+ });
1427
+ emit(data, cmd.optsWithGlobals().json);
1428
+ });
1429
+ continuity.command("resume-me").description("Resume the caller's own lane (POST /continuity/resume/me)").option("--workspace <workspaceId>", "Scope to a workspace").option("--max-artifacts <n>", "Cap artifacts in the bundle").option("--changed-since <iso>", "Only include changes since this ISO timestamp").option("--looking-at-myself", "Include the caller's own changes", false).action(async (opts, cmd) => {
1430
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1431
+ const data = await runApi("Resuming", async () => {
1432
+ const client = await makeClient(cfg);
1433
+ return client.POST("/continuity/resume/me", {
1434
+ body: {
1435
+ workspaceId: opts.workspace ?? null,
1436
+ maxArtifacts: opts.maxArtifacts != null ? Number(opts.maxArtifacts) : null,
1437
+ includeLookingAtMyself: opts.lookingAtMyself ? true : null,
1438
+ changedSince: opts.changedSince ?? null
1439
+ }
1440
+ });
1441
+ });
1442
+ emit(data, cmd.optsWithGlobals().json);
1443
+ });
1444
+ continuity.command("resume-lane <laneId>").description("Resume a specific lane (POST /continuity/resume/lane)").option("--workspace <workspaceId>", "Scope to a workspace").option("--max-artifacts <n>", "Cap artifacts in the bundle").option("--changed-since <iso>", "Only include changes since this ISO timestamp").option("--looking-at-myself", "Include the caller's own changes", false).action(async (laneId, opts, cmd) => {
1445
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1446
+ const data = await runApi("Resuming lane", async () => {
1447
+ const client = await makeClient(cfg);
1448
+ return client.POST("/continuity/resume/lane", {
1449
+ body: {
1450
+ laneId,
1451
+ workspaceId: opts.workspace ?? null,
1452
+ maxArtifacts: opts.maxArtifacts != null ? Number(opts.maxArtifacts) : null,
1453
+ includeLookingAtMyself: opts.lookingAtMyself ? true : null,
1454
+ changedSince: opts.changedSince ?? null
1455
+ }
1456
+ });
1457
+ });
1458
+ emit(data, cmd.optsWithGlobals().json);
1459
+ });
1460
+ continuity.command("changed-since").description("What changed since a timestamp (POST /continuity/changed-since)").requiredOption("--since <iso>", "ISO-8601 timestamp to compare against").option("--looking-at-myself", "Include the caller's own changes", false).action(async (opts, cmd) => {
1461
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1462
+ const data = await runApi("Computing changes", async () => {
1463
+ const client = await makeClient(cfg);
1464
+ return client.POST("/continuity/changed-since", {
1465
+ body: {
1466
+ since: opts.since,
1467
+ includeLookingAtMyself: opts.lookingAtMyself ? true : null
1468
+ }
1469
+ });
1470
+ });
1471
+ emit(data, cmd.optsWithGlobals().json);
1472
+ });
1473
+ continuity.command("load-set").description("Derive the active load set (POST /continuity/load-set/derive)").option("--workspace <workspaceId>", "Scope to a workspace").option("--max-artifacts <n>", "Cap artifacts in the load set").option("--looking-at-myself", "Include the caller's own changes", false).action(async (opts, cmd) => {
1474
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1475
+ const data = await runApi("Deriving load set", async () => {
1476
+ const client = await makeClient(cfg);
1477
+ return client.POST("/continuity/load-set/derive", {
1478
+ body: {
1479
+ workspaceId: opts.workspace ?? null,
1480
+ maxArtifacts: opts.maxArtifacts != null ? Number(opts.maxArtifacts) : null,
1481
+ includeLookingAtMyself: opts.lookingAtMyself ? true : null
1482
+ }
1483
+ });
1484
+ });
1485
+ emit(data, cmd.optsWithGlobals().json);
1486
+ });
1487
+ continuity.command("grant <snapshotId>").description("Grant another operator read access (POST /continuity/snapshots/{snapshotId}/grants)").requiredOption("--grantee <userId>", "Sechroom user id being granted read access").option("--source <source>", "Permission-set source kind", "TenantRole").option("--source-id <sourceId>", "Permission-set source id (e.g. a tenant role)", "viewer").option("--valid-from <iso>", "Optional ISO-8601 grant start").option("--valid-to <iso>", "Optional ISO-8601 grant expiry").action(async (snapshotId, opts, cmd) => {
1488
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1489
+ const data = await runApi("Minting grant", async () => {
1490
+ const client = await makeClient(cfg);
1491
+ return client.POST("/continuity/snapshots/{snapshotId}/grants", {
1492
+ params: { path: { snapshotId } },
1493
+ body: {
1494
+ userId: opts.grantee,
1495
+ kind: "Allow",
1496
+ source: opts.source,
1497
+ sourceId: opts.sourceId,
1498
+ ...opts.validFrom ? { validFrom: opts.validFrom } : {},
1499
+ ...opts.validTo ? { validTo: opts.validTo } : {}
1500
+ }
1501
+ });
1502
+ });
1503
+ emitAction(
1504
+ `granted ${style.bold(data.userId)} read on ${style.bold(snapshotId)} ${style.dim(`(grant ${data.grantId})`)}`,
1505
+ data,
1506
+ cmd.optsWithGlobals().json
1507
+ );
1508
+ });
1509
+ continuity.command("revoke-grant <snapshotId> <grantId>").description("Revoke a grant (DELETE /continuity/snapshots/{snapshotId}/grants/{grantId})").action(async (snapshotId, grantId, _opts, cmd) => {
1510
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1511
+ const data = await runApi("Revoking grant", async () => {
1512
+ const client = await makeClient(cfg);
1513
+ return client.DELETE("/continuity/snapshots/{snapshotId}/grants/{grantId}", {
1514
+ params: { path: { snapshotId, grantId } },
1515
+ body: {}
1516
+ });
1517
+ });
1518
+ emitAction(
1519
+ `revoked grant ${style.bold(grantId)} on ${style.bold(snapshotId)}`,
1520
+ data,
1521
+ cmd.optsWithGlobals().json
1522
+ );
1523
+ });
1524
+ }
1525
+
1526
+ // src/commands/account.ts
1527
+ function registerId(program2) {
1528
+ const id = program2.command("id").description("Allocate human-authored id sequences (FR-*, D-*)");
1529
+ id.addHelpText(
1530
+ "after",
1531
+ `
1532
+ Examples:
1533
+ $ sechroom id next FR sechroom allocate the next FR-sechroom-NNN id
1534
+ $ sechroom id next D Backend allocate the next D-Backend-NNN id
1535
+ $ sechroom id peek FR sechroom inspect the sequence without consuming
1536
+ $ sechroom id peek FR sechroom --json`
1537
+ );
1538
+ id.command("next <namespaceKind> <scope>").description("Allocate the next id in a sequence (POST /id-registry/allocate)").action(async (namespaceKind, scope, _opts, cmd) => {
1539
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1540
+ const data = await runApi("Allocating id", async () => {
1541
+ const client = await makeClient(cfg);
1542
+ return client.POST("/id-registry/allocate", {
1543
+ body: { namespaceKind, scope, clientNonce: null }
1544
+ });
1545
+ });
1546
+ emitAction(`allocated ${style.bold(data.id)} ${style.dim(`(seq ${data.seq})`)}`, data, cmd.optsWithGlobals().json);
1547
+ });
1548
+ id.command("peek <namespaceKind> <scope>").description("Inspect a sequence without consuming (GET /id-registry/state)").action(async (namespaceKind, scope, _opts, cmd) => {
1549
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1550
+ const data = await runApi("Peeking id sequence", async () => {
1551
+ const client = await makeClient(cfg);
1552
+ return client.GET("/id-registry/state", {
1553
+ params: { query: { namespaceKind, scope } }
1554
+ });
1555
+ });
1556
+ emit(data, cmd.optsWithGlobals().json);
1557
+ });
1558
+ }
1559
+ function registerAccount(program2) {
1560
+ const account = program2.command("account").description("Your profile, feeds, and review queue");
1561
+ account.addHelpText(
1562
+ "after",
1563
+ `
1564
+ Examples:
1565
+ $ sechroom account profile
1566
+ $ sechroom account set-profile --display-name "Chris" --timezone "Europe/London"
1567
+ $ sechroom account feed --limit 20
1568
+ $ sechroom account reviews --status Pending
1569
+ $ sechroom account lookup-batch mem_XXXX wsp_YYYY --json`
1570
+ );
1571
+ account.command("profile").description("Show your resolved profile (GET /me/profile)").action(async (_opts, cmd) => {
1572
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1573
+ const data = await runApi("Fetching profile", async () => {
1574
+ const client = await makeClient(cfg);
1575
+ return client.GET("/me/profile");
1576
+ });
1577
+ emit(data, cmd.optsWithGlobals().json);
1578
+ });
1579
+ account.command("set-profile").description("Update your profile (PUT /me/profile)").option("--display-name <displayName>", "Display name").option("--timezone <timezone>", "IANA timezone (e.g. Europe/London)").option("--bio <bio>", "Short bio").option("--photo-url <photoUrl>", "Avatar / photo URL").action(async (opts, cmd) => {
1580
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1581
+ const data = await runApi("Updating profile", async () => {
1582
+ const client = await makeClient(cfg);
1583
+ return client.PUT("/me/profile", {
1584
+ body: {
1585
+ displayName: opts.displayName ?? null,
1586
+ timezone: opts.timezone ?? null,
1587
+ bio: opts.bio ?? null,
1588
+ photoUrl: opts.photoUrl ?? null
1589
+ }
1590
+ });
1591
+ });
1592
+ emitAction("updated profile", data, cmd.optsWithGlobals().json);
1593
+ });
1594
+ account.command("feed").description("Your recent memory feed (GET /me/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Opaque paging cursor").option("--query <query>", "Free-text filter").option("--filter-tags <tags>", "Comma-separated tag filter").option("--include-archived", "Include archived memories", false).option("--include-text", "Include memory body text", false).action(async (opts, cmd) => {
1595
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1596
+ const data = await runApi("Fetching feed", async () => {
1597
+ const client = await makeClient(cfg);
1598
+ return client.GET("/me/memories/feed", {
1599
+ params: {
1600
+ query: {
1601
+ limit: Number(opts.limit),
1602
+ includeArchived: Boolean(opts.includeArchived),
1603
+ includeText: Boolean(opts.includeText),
1604
+ ...opts.cursor ? { cursor: opts.cursor } : {},
1605
+ ...opts.query ? { query: opts.query } : {},
1606
+ ...opts.filterTags ? { filterTags: opts.filterTags } : {}
1607
+ }
1608
+ }
1609
+ });
1610
+ });
1611
+ emit(data, cmd.optsWithGlobals().json);
1612
+ });
1613
+ account.command("reviews").description("Your review queue (GET /reviews)").option("--status <status>", "Pending | Resolved | Empty").option("--scope-kind <scopeKind>", "Memory | Project | Workspace").option("--scope-target-id <id>", "Scope target id").option("--limit <n>", "Max results", "20").action(async (opts, cmd) => {
1614
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1615
+ const data = await runApi("Fetching reviews", async () => {
1616
+ const client = await makeClient(cfg);
1617
+ return client.GET("/reviews", {
1618
+ params: {
1619
+ query: {
1620
+ limit: Number(opts.limit),
1621
+ ...opts.status ? { status: opts.status } : {},
1622
+ ...opts.scopeKind ? { scopeKind: opts.scopeKind } : {},
1623
+ ...opts.scopeTargetId ? { scopeTargetId: opts.scopeTargetId } : {}
1624
+ }
1625
+ }
1626
+ });
1627
+ });
1628
+ emit(data, cmd.optsWithGlobals().json);
1629
+ });
1630
+ account.command("review-get <reviewId>").description("Fetch one review bundle (GET /reviews/{reviewId})").action(async (reviewId, _opts, cmd) => {
1631
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1632
+ const data = await runApi("Fetching review", async () => {
1633
+ const client = await makeClient(cfg);
1634
+ return client.GET("/reviews/{reviewId}", { params: { path: { reviewId } } });
1635
+ });
1636
+ emit(data, cmd.optsWithGlobals().json);
1637
+ });
1638
+ account.command("review-accept <reviewId>").description("Accept a review bundle (POST /reviews/{reviewId}/accept)").action(async (reviewId, _opts, cmd) => {
1639
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1640
+ const data = await runApi("Accepting review", async () => {
1641
+ const client = await makeClient(cfg);
1642
+ return client.POST("/reviews/{reviewId}/accept", {
1643
+ params: { path: { reviewId } },
1644
+ body: { decisions: {} }
1645
+ });
1646
+ });
1647
+ emitAction(`accepted review ${style.bold(reviewId)}`, data, cmd.optsWithGlobals().json);
1648
+ });
1649
+ account.command("lookup-batch <ids...>").description("Resolve many ids at once (POST /lookup/batch)").action(async (ids, _opts, cmd) => {
1650
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1651
+ const data = await runApi(`Resolving ${ids.length} ids`, async () => {
1652
+ const client = await makeClient(cfg);
1653
+ return client.POST("/lookup/batch", { body: { ids } });
1654
+ });
1655
+ emit(data, cmd.optsWithGlobals().json);
1656
+ });
1657
+ }
1658
+
1659
+ // src/commands/chat.ts
1660
+ function registerChat(program2) {
1661
+ const chat = program2.command("chat").description("Read Slack / Discord channel messages + thread replies").option("--surface <surface>", "slack | discord", "slack");
1662
+ chat.addHelpText(
1663
+ "after",
1664
+ `
1665
+ Examples:
1666
+ $ sechroom chat messages --surface slack
1667
+ $ sechroom chat messages --surface discord --json
1668
+ $ sechroom chat replies 1718049600.123456 --surface slack
1669
+ $ sechroom chat replies 1234567890123456789 --surface discord --json
1670
+ $ sechroom chat stop-tracking 1718049600.123456 --surface slack`
1671
+ );
1672
+ chat.command("messages").description("List recent channel messages (GET /chat/channel-messages/{surface})").action(async (_opts, cmd) => {
1673
+ const { surface, ...globals } = cmd.optsWithGlobals();
1674
+ const cfg = resolveConfig(globals);
1675
+ const data = await runApi("Fetching messages", async () => {
1676
+ const client = await makeClient(cfg);
1677
+ return client.GET("/chat/channel-messages/{surface}", {
1678
+ params: { path: { surface: String(surface) } }
1679
+ });
1680
+ });
1681
+ emit(data, cmd.optsWithGlobals().json);
1682
+ });
1683
+ chat.command("replies <messageId>").description("List thread replies for a message (GET /chat/channel-messages/by-id/{id}/replies)").action(async (messageId, _opts, cmd) => {
1684
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1685
+ const data = await runApi("Fetching replies", async () => {
1686
+ const client = await makeClient(cfg);
1687
+ return client.GET("/chat/channel-messages/by-id/{id}/replies", {
1688
+ params: { path: { id: messageId } }
1689
+ });
1690
+ });
1691
+ emit(data, cmd.optsWithGlobals().json);
1692
+ });
1693
+ chat.command("stop-tracking <messageId>").description("Stop watching a message for replies (POST .../by-id/{id}/stop-tracking-replies)").action(async (messageId, _opts, cmd) => {
1694
+ const cfg = resolveConfig(cmd.optsWithGlobals());
1695
+ const data = await runApi("Stopping reply tracking", async () => {
1696
+ const client = await makeClient(cfg);
1697
+ return client.POST("/chat/channel-messages/by-id/{id}/stop-tracking-replies", {
1698
+ params: { path: { id: messageId } },
1699
+ body: {}
1700
+ });
1701
+ });
1702
+ emitAction(`stopped tracking replies on ${style.bold(messageId)}`, data, cmd.optsWithGlobals().json);
1703
+ });
1704
+ }
1705
+
582
1706
  // src/setup/apply.ts
583
1707
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
584
1708
  import { dirname as dirname2 } from "path";
@@ -1053,16 +2177,18 @@ async function ensureTimezone(cfg, opts) {
1053
2177
  async function chooseClients(clientFlag, yes, cwd) {
1054
2178
  if (clientFlag) return resolveClientKeys(clientFlag);
1055
2179
  const detected = detectInstalledClients(cwd);
1056
- const fallback = (detected.length > 0 ? detected : [DEFAULT_CLIENT_KEY]).join(",");
1057
- if (!canPrompt() || yes) return resolveClientKeys(fallback);
1058
- process.stderr.write(
1059
- `
1060
- Available clients: ${ALL_CLIENT_KEYS.join(", ")}
1061
- ` + (detected.length > 0 ? `Detected on this machine: ${detected.join(", ")}
1062
- ` : "No clients auto-detected.\n")
2180
+ const preselected = detected.length > 0 ? detected : [DEFAULT_CLIENT_KEY];
2181
+ if (!canPrompt() || yes) return preselected;
2182
+ const picks = await promptMultiSelect(
2183
+ "Which AI clients should I wire?",
2184
+ ALL_CLIENT_KEYS.map((k) => ({
2185
+ label: k,
2186
+ value: k,
2187
+ hint: detected.includes(k) ? "detected" : void 0
2188
+ })),
2189
+ preselected
1063
2190
  );
1064
- const answer = await promptText("Which to wire? (comma-separated, or 'all')", fallback);
1065
- return resolveClientKeys(answer);
2191
+ return picks.length > 0 ? picks : preselected;
1066
2192
  }
1067
2193
  function registerOnboard(program2) {
1068
2194
  program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save tenant + base URL to a directory-local .sechroom.json instead of the global config", false).option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
@@ -1261,6 +2387,14 @@ local: ${d.localPath ?? "(none)"} ${JSON.stringify(readLocalConfig())}
1261
2387
  registerMemory(program);
1262
2388
  registerWorklog(program);
1263
2389
  registerLookup(program);
2390
+ registerRelationships(program);
2391
+ registerWorkspace(program);
2392
+ registerProject(program);
2393
+ registerFiling(program);
2394
+ registerContinuity(program);
2395
+ registerId(program);
2396
+ registerAccount(program);
2397
+ registerChat(program);
1264
2398
  registerInit(program);
1265
2399
  registerSetup(program);
1266
2400
  registerOnboard(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.7",
3
+ "version": "2026.6.9",
4
4
  "description": "Sechroom CLI — a thin, generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",