@sechroom/cli 2026.6.8 → 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.
- package/README.md +30 -1
- package/dist/index.js +1093 -1
- 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
|
@@ -510,7 +510,13 @@ Examples:
|
|
|
510
510
|
$ sechroom memory create --text "filed note" --owner-type Workspace --owner-id wsp_XXXX
|
|
511
511
|
$ sechroom memory search "rate limiting" --limit 5 --tag kind:decision
|
|
512
512
|
$ sechroom memory search "auth flow" --workspace wsp_XXXX --json
|
|
513
|
-
$ 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`
|
|
514
520
|
);
|
|
515
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) => {
|
|
516
522
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
@@ -563,6 +569,201 @@ Examples:
|
|
|
563
569
|
});
|
|
564
570
|
emit(data, cmd.optsWithGlobals().json);
|
|
565
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
|
+
});
|
|
566
767
|
}
|
|
567
768
|
|
|
568
769
|
// src/commands/worklog.ts
|
|
@@ -619,6 +820,889 @@ Examples:
|
|
|
619
820
|
});
|
|
620
821
|
}
|
|
621
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
|
+
|
|
622
1706
|
// src/setup/apply.ts
|
|
623
1707
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
624
1708
|
import { dirname as dirname2 } from "path";
|
|
@@ -1303,6 +2387,14 @@ local: ${d.localPath ?? "(none)"} ${JSON.stringify(readLocalConfig())}
|
|
|
1303
2387
|
registerMemory(program);
|
|
1304
2388
|
registerWorklog(program);
|
|
1305
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);
|
|
1306
2398
|
registerInit(program);
|
|
1307
2399
|
registerSetup(program);
|
|
1308
2400
|
registerOnboard(program);
|
package/package.json
CHANGED