@swarmvaultai/cli 3.10.0 → 3.12.0
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 +63 -2
- package/dist/index.js +280 -101
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -56,6 +56,9 @@ swarmvault graph share --svg ./share-card.svg
|
|
|
56
56
|
swarmvault graph share --bundle ./share-kit
|
|
57
57
|
swarmvault benchmark
|
|
58
58
|
swarmvault query "What keeps recurring?" --commit
|
|
59
|
+
swarmvault chat "What should the next agent know?"
|
|
60
|
+
swarmvault chat --resume <session-id> "What changed?"
|
|
61
|
+
swarmvault export ai --out ./exports/ai
|
|
59
62
|
swarmvault context build "Ship this feature safely" --target ./src --budget 8000
|
|
60
63
|
swarmvault task start "Ship this feature safely" --target ./src --agent codex
|
|
61
64
|
swarmvault retrieval status
|
|
@@ -73,6 +76,7 @@ swarmvault update .
|
|
|
73
76
|
swarmvault graph cluster
|
|
74
77
|
swarmvault cluster-only
|
|
75
78
|
swarmvault graph tree --output ./exports/tree.html
|
|
79
|
+
swarmvault tree --output ./exports/tree.html
|
|
76
80
|
swarmvault graph query "Which nodes bridge the biggest clusters?"
|
|
77
81
|
swarmvault graph explain "concept:drift"
|
|
78
82
|
swarmvault watch status
|
|
@@ -84,7 +88,9 @@ swarmvault graph export --html ./exports/graph.html
|
|
|
84
88
|
swarmvault graph export --cypher ./exports/graph.cypher
|
|
85
89
|
swarmvault graph export --neo4j ./exports/graph.cypher
|
|
86
90
|
swarmvault graph merge ./exports/graph.json ./other-graph.json --out ./exports/merged-graph.json
|
|
91
|
+
swarmvault merge-graphs ./exports/graph.json ./other-graph.json --out ./exports/merged-graph.json
|
|
87
92
|
swarmvault graph push neo4j --dry-run
|
|
93
|
+
swarmvault clone https://github.com/owner/repo --no-viz
|
|
88
94
|
```
|
|
89
95
|
|
|
90
96
|
## Commands
|
|
@@ -110,7 +116,7 @@ Set `SWARMVAULT_OUT=<dir>` when generated artifacts should be isolated from the
|
|
|
110
116
|
|
|
111
117
|
`--profile` accepts `default`, `personal-research`, or a comma-separated preset list such as `reader,timeline`. For fully custom vault behavior, edit the `profile` block in `swarmvault.config.json`; that deterministic profile layer works alongside the human-written `swarmvault.schema.md`. The `personal-research` preset also sets `profile.guidedIngestDefault: true` and `profile.deepLintDefault: true`, so guided ingest/source and lint flows are on by default until you override them with `--no-guide` or `--no-deep`.
|
|
112
118
|
|
|
113
|
-
### `swarmvault scan <directory|github-url> [--port <port>] [--no-serve] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
119
|
+
### `swarmvault scan <directory|github-url> [--port <port>] [--no-serve] [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
114
120
|
|
|
115
121
|
Quick-start a scratch vault from a local directory or public GitHub repo root URL in one command.
|
|
116
122
|
|
|
@@ -118,12 +124,22 @@ Quick-start a scratch vault from a local directory or public GitHub repo root UR
|
|
|
118
124
|
- ingests the supplied directory as local sources, or registers/syncs the supplied public GitHub repo root URL
|
|
119
125
|
- compiles the vault immediately
|
|
120
126
|
- writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/`, then prints the paths
|
|
121
|
-
- starts `graph serve` unless you pass `--no-serve`
|
|
127
|
+
- starts `graph serve` unless you pass `--no-serve` or `--no-viz`
|
|
128
|
+
- `--no-viz` is a compatibility alias for `--no-serve`
|
|
129
|
+
- `--mcp` starts the MCP stdio server after compile instead of the graph viewer
|
|
122
130
|
- respects `--port` when you want a specific viewer port
|
|
123
131
|
- for GitHub repo URLs, supports `--branch`, `--ref`, and `--checkout-dir`
|
|
124
132
|
|
|
125
133
|
Use this when you want the fastest repo or docs-tree walkthrough without first deciding on managed-source registration.
|
|
126
134
|
|
|
135
|
+
### `swarmvault clone <directory|github-url> [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
136
|
+
|
|
137
|
+
Compatibility alias for `swarmvault scan`.
|
|
138
|
+
|
|
139
|
+
- initializes, ingests or registers the input, and compiles in one command
|
|
140
|
+
- supports the same public GitHub repo checkout flags as `scan`
|
|
141
|
+
- accepts `--no-serve`, `--no-viz`, `--mcp`, and `--port`
|
|
142
|
+
|
|
127
143
|
### `swarmvault demo [--port <port>] [--no-serve]`
|
|
128
144
|
|
|
129
145
|
Create a temporary sample vault with bundled sources, compile it immediately, and launch the graph viewer unless you pass `--no-serve`.
|
|
@@ -142,6 +158,28 @@ Compare the current `state/graph.json` against the last committed graph in git.
|
|
|
142
158
|
- when no git baseline exists, falls back to a summary of the current graph state
|
|
143
159
|
- supports `--json` for structured automation output
|
|
144
160
|
|
|
161
|
+
### `swarmvault chat [question...] [--resume [id]] [--list] [--delete <id>]`
|
|
162
|
+
|
|
163
|
+
Ask the compiled wiki in a persisted multi-turn session.
|
|
164
|
+
|
|
165
|
+
- without a question, opens an interactive TTY chat loop with `/help`, `/sessions`, `/status`, `/clear`, and `/exit`
|
|
166
|
+
- with a question, runs one turn and persists the transcript under `wiki/outputs/chat-sessions/`
|
|
167
|
+
- stores structured session state under `state/chat-sessions/`
|
|
168
|
+
- `--resume <id>` resumes by id or unique prefix; `--resume` alone resumes the most recent session
|
|
169
|
+
- `--list` prints saved sessions and `--delete <id>` removes one
|
|
170
|
+
- `--save-output` also writes each turn as a regular `wiki/outputs/` query page
|
|
171
|
+
- supports `--format markdown|report|slides|chart|image`, `--gap-fill`, and global `--json`
|
|
172
|
+
|
|
173
|
+
### `swarmvault export ai [--out <dir>] [--max-full-chars <n>] [--no-page-siblings]`
|
|
174
|
+
|
|
175
|
+
Write a static AI handoff pack for agents, crawlers, and documentation systems.
|
|
176
|
+
|
|
177
|
+
- defaults to `wiki/exports/ai/`
|
|
178
|
+
- writes `llms.txt`, `llms-full.txt`, `graph.jsonld`, `manifest.json`, and `ai-readme.md`
|
|
179
|
+
- writes per-page `.txt` and `.json` siblings under `pages/` unless `--no-page-siblings` is passed
|
|
180
|
+
- caps `llms-full.txt` with `--max-full-chars` so huge vaults stay bounded
|
|
181
|
+
- includes SHA-256 hashes and file metadata in `manifest.json`
|
|
182
|
+
|
|
145
183
|
### `swarmvault doctor [--repair]`
|
|
146
184
|
|
|
147
185
|
Run a whole-vault health check before handing the workspace to an agent or opening the live viewer.
|
|
@@ -440,6 +478,13 @@ Write a collapsible HTML source tree for the current `state/graph.json`.
|
|
|
440
478
|
- `--max-children` caps very wide folders or modules with a `+N more` row
|
|
441
479
|
- `--json` returns the output path, source count, node count, and tree payload
|
|
442
480
|
|
|
481
|
+
### `swarmvault tree [--output <html>] [--root <path>] [--label <name>] [--max-children <n>]`
|
|
482
|
+
|
|
483
|
+
Compatibility alias for `swarmvault graph tree`.
|
|
484
|
+
|
|
485
|
+
- writes the same source/module/symbol tree
|
|
486
|
+
- returns the same JSON shape as `graph tree`
|
|
487
|
+
|
|
443
488
|
### `swarmvault graph merge <graph...> --out <path> [--label <name>]`
|
|
444
489
|
|
|
445
490
|
Merge multiple graph JSON files into one namespaced graph artifact.
|
|
@@ -450,6 +495,13 @@ Merge multiple graph JSON files into one namespaced graph artifact.
|
|
|
450
495
|
- maps explicit extracted/inferred/ambiguous edge evidence into SwarmVault edge semantics
|
|
451
496
|
- `--json` returns the merged graph, input summaries, and warnings
|
|
452
497
|
|
|
498
|
+
### `swarmvault merge-graphs <graph...> --out <path> [--label <name>]`
|
|
499
|
+
|
|
500
|
+
Compatibility alias for `swarmvault graph merge`.
|
|
501
|
+
|
|
502
|
+
- accepts the same SwarmVault and NetworkX/node-link graph inputs
|
|
503
|
+
- returns the same JSON shape as `graph merge`
|
|
504
|
+
|
|
453
505
|
### `swarmvault graph status [path]`
|
|
454
506
|
|
|
455
507
|
Read-only graph freshness check for tracked repo roots or one explicit repo path.
|
|
@@ -461,6 +513,15 @@ Read-only graph freshness check for tracked repo roots or one explicit repo path
|
|
|
461
513
|
- recommends `swarmvault compile` when graph/report artifacts are missing, non-code files changed, or a semantic refresh is pending
|
|
462
514
|
- supports global `--json` for automation
|
|
463
515
|
|
|
516
|
+
### `swarmvault watch [path] [--once] [--code-only] [--lint]`
|
|
517
|
+
|
|
518
|
+
Watch or refresh one explicit repo root without first writing `watch.repoRoots`.
|
|
519
|
+
|
|
520
|
+
- positional `[path]` is treated as a repo-root override and turns on repo mode
|
|
521
|
+
- `--once` runs one refresh cycle and exits
|
|
522
|
+
- `--code-only` limits the refresh to parser-backed repo graph artifacts
|
|
523
|
+
- repeated `--root <path>` remains available when you need multiple roots
|
|
524
|
+
|
|
464
525
|
### `swarmvault graph stats`
|
|
465
526
|
|
|
466
527
|
Summarize the current compiled graph without opening the viewer.
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
addManagedSource,
|
|
13
13
|
addWatchedRoot,
|
|
14
14
|
archiveCandidate,
|
|
15
|
+
askChatSession,
|
|
15
16
|
autoCommitWikiChanges,
|
|
16
17
|
benchmarkVault,
|
|
17
18
|
blastRadiusVault,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
compileVault,
|
|
21
22
|
consolidateVault,
|
|
22
23
|
createSupersessionEdge,
|
|
24
|
+
deleteChatSession,
|
|
23
25
|
deleteContextPack,
|
|
24
26
|
deleteManagedSource,
|
|
25
27
|
doctorRetrieval,
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
downloadWhisperModel,
|
|
28
30
|
explainGraphVault,
|
|
29
31
|
exploreVault,
|
|
32
|
+
exportAiPack,
|
|
30
33
|
exportGraphFormat,
|
|
31
34
|
exportGraphHtml,
|
|
32
35
|
exportGraphReportHtml,
|
|
@@ -51,6 +54,7 @@ import {
|
|
|
51
54
|
lintVault,
|
|
52
55
|
listApprovals,
|
|
53
56
|
listCandidates,
|
|
57
|
+
listChatSessions,
|
|
54
58
|
listContextPacks,
|
|
55
59
|
listGodNodes,
|
|
56
60
|
listManagedSourceRecords,
|
|
@@ -315,9 +319,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
|
|
|
315
319
|
function readCliVersion() {
|
|
316
320
|
try {
|
|
317
321
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
318
|
-
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.
|
|
322
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.12.0";
|
|
319
323
|
} catch {
|
|
320
|
-
return "3.
|
|
324
|
+
return "3.12.0";
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
function parsePositiveInt(value, fallback) {
|
|
@@ -545,6 +549,209 @@ async function runGraphClusterCommand(options, rootDir = process2.cwd()) {
|
|
|
545
549
|
`Refreshed ${result.communityCount} communities across ${result.nodeCount} nodes and ${result.edgeCount} edges. Report: ${result.reportPath}`
|
|
546
550
|
);
|
|
547
551
|
}
|
|
552
|
+
async function runGraphTreeCommand(options) {
|
|
553
|
+
const rootDir = options.root ? path2.resolve(process2.cwd(), options.root) : process2.cwd();
|
|
554
|
+
const result = await exportGraphTree(rootDir, options.output, {
|
|
555
|
+
label: options.label,
|
|
556
|
+
maxChildren: parsePositiveInt(options.maxChildren, 250)
|
|
557
|
+
});
|
|
558
|
+
if (isJson()) {
|
|
559
|
+
emitJson(result);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
log(`Graph tree: ${result.outputPath}`);
|
|
563
|
+
log(`Sources: ${result.sourceCount}; nodes: ${result.nodeCount}.`);
|
|
564
|
+
}
|
|
565
|
+
async function runGraphMergeCommand(graphPaths, options) {
|
|
566
|
+
const result = await mergeGraphFiles(
|
|
567
|
+
graphPaths.map((inputPath) => path2.resolve(process2.cwd(), inputPath)),
|
|
568
|
+
path2.resolve(process2.cwd(), options.out),
|
|
569
|
+
{ label: options.label }
|
|
570
|
+
);
|
|
571
|
+
if (isJson()) {
|
|
572
|
+
emitJson(result);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
log(
|
|
576
|
+
`Merged ${result.inputGraphs.length} graph${result.inputGraphs.length === 1 ? "" : "s"} into ${result.outputPath}. Nodes ${result.graph.nodes.length}, edges ${result.graph.edges.length}.`
|
|
577
|
+
);
|
|
578
|
+
for (const warning of result.warnings) {
|
|
579
|
+
log(`Warning: ${warning}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function runScanCommand(input, options) {
|
|
583
|
+
const rootDir = process2.cwd();
|
|
584
|
+
await initVault(rootDir, {});
|
|
585
|
+
if (!isJson()) {
|
|
586
|
+
log("Initialized workspace.");
|
|
587
|
+
}
|
|
588
|
+
const result = isHttpUrlInput(input) ? await addManagedSource(rootDir, input, {
|
|
589
|
+
compile: true,
|
|
590
|
+
brief: false,
|
|
591
|
+
branch: options.branch,
|
|
592
|
+
ref: options.ref,
|
|
593
|
+
checkoutDir: options.checkoutDir
|
|
594
|
+
}) : await ingestDirectory(rootDir, input, {});
|
|
595
|
+
if (!isJson()) {
|
|
596
|
+
if ("source" in result) {
|
|
597
|
+
log(
|
|
598
|
+
`Registered ${result.source.kind} source ${result.source.id}. Imported ${result.source.lastSyncCounts?.importedCount ?? 0}, updated ${result.source.lastSyncCounts?.updatedCount ?? 0}.`
|
|
599
|
+
);
|
|
600
|
+
} else {
|
|
601
|
+
log(`Ingested ${result.imported.length} file(s).`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const compiled = "compile" in result && result.compile ? result.compile : await compileVault(rootDir, {});
|
|
605
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
606
|
+
const shareCardPath = path2.join(paths.wikiDir, "graph", "share-card.md");
|
|
607
|
+
const shareCardSvgPath = path2.join(paths.wikiDir, "graph", "share-card.svg");
|
|
608
|
+
const shareKitPath = path2.join(paths.wikiDir, "graph", "share-kit");
|
|
609
|
+
if (!isJson()) {
|
|
610
|
+
log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
|
|
611
|
+
log(`Share card: ${shareCardPath}`);
|
|
612
|
+
log(`Visual card: ${shareCardSvgPath}`);
|
|
613
|
+
log(`Share kit: ${shareKitPath}`);
|
|
614
|
+
log("Post text: swarmvault graph share --post");
|
|
615
|
+
}
|
|
616
|
+
if (options.mcp) {
|
|
617
|
+
process2.stderr.write(`${JSON.stringify({ status: "running", transport: "stdio", compiled: compiled.sourceCount })}
|
|
618
|
+
`);
|
|
619
|
+
const controller = await startMcpServer(rootDir);
|
|
620
|
+
process2.on("SIGINT", async () => {
|
|
621
|
+
try {
|
|
622
|
+
await controller.close();
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
625
|
+
process2.exit(0);
|
|
626
|
+
});
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (options.serve !== false && options.viz !== false) {
|
|
630
|
+
const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
|
|
631
|
+
const server = await startGraphServer(rootDir, port, { full: false });
|
|
632
|
+
if (isJson()) {
|
|
633
|
+
emitJson({
|
|
634
|
+
...result,
|
|
635
|
+
compiled,
|
|
636
|
+
shareCardPath,
|
|
637
|
+
shareCardSvgPath,
|
|
638
|
+
shareKitPath,
|
|
639
|
+
port: server.port,
|
|
640
|
+
url: `http://localhost:${server.port}`
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
log(`Graph viewer running at http://localhost:${server.port}`);
|
|
644
|
+
}
|
|
645
|
+
process2.on("SIGINT", async () => {
|
|
646
|
+
try {
|
|
647
|
+
await server.close();
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
process2.exit(0);
|
|
651
|
+
});
|
|
652
|
+
} else if (isJson()) {
|
|
653
|
+
emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async function resolveChatResumeId(resume) {
|
|
657
|
+
if (!resume) {
|
|
658
|
+
return void 0;
|
|
659
|
+
}
|
|
660
|
+
if (typeof resume === "string") {
|
|
661
|
+
return resume;
|
|
662
|
+
}
|
|
663
|
+
const sessions = await listChatSessions(process2.cwd());
|
|
664
|
+
return sessions[0]?.id;
|
|
665
|
+
}
|
|
666
|
+
function logChatSessions(sessions) {
|
|
667
|
+
if (!sessions.length) {
|
|
668
|
+
log("No chat sessions yet.");
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
for (const session of sessions) {
|
|
672
|
+
log(`${session.id} ${session.turnCount} turn${session.turnCount === 1 ? "" : "s"} ${session.title}`);
|
|
673
|
+
log(` updated: ${session.updatedAt}`);
|
|
674
|
+
log(` markdown: ${session.markdownPath}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function runChatQuestion(question, options) {
|
|
678
|
+
const sessionId = await resolveChatResumeId(options.resume);
|
|
679
|
+
return askChatSession(process2.cwd(), {
|
|
680
|
+
question,
|
|
681
|
+
sessionId,
|
|
682
|
+
saveOutput: options.saveOutput ?? false,
|
|
683
|
+
gapFill: options.gapFill ?? false,
|
|
684
|
+
format: options.format,
|
|
685
|
+
maxHistoryTurns: parsePositiveInt(options.maxHistoryTurns, 6)
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
async function runInteractiveChat(options) {
|
|
689
|
+
if (isJson()) {
|
|
690
|
+
throw new Error("Interactive chat is not available with --json. Pass a question for one-shot JSON output.");
|
|
691
|
+
}
|
|
692
|
+
if (!process2.stdin.isTTY) {
|
|
693
|
+
throw new Error("Pass a chat question, or run `swarmvault chat` in an interactive terminal.");
|
|
694
|
+
}
|
|
695
|
+
let sessionId = await resolveChatResumeId(options.resume);
|
|
696
|
+
log(sessionId ? `Resuming chat session ${sessionId}.` : "Starting a new chat session.");
|
|
697
|
+
log("Type /help for commands or /exit to quit.");
|
|
698
|
+
const reader = createInterface({ input: process2.stdin, output: process2.stdout });
|
|
699
|
+
try {
|
|
700
|
+
while (true) {
|
|
701
|
+
const input = (await reader.question("swarmvault> ")).trim();
|
|
702
|
+
if (!input) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (input === "/exit" || input === "/quit") {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
if (input === "/help") {
|
|
709
|
+
log(
|
|
710
|
+
[
|
|
711
|
+
"/help Show commands",
|
|
712
|
+
"/sessions List chat sessions",
|
|
713
|
+
"/status Show vault health summary",
|
|
714
|
+
"/clear Start a fresh session",
|
|
715
|
+
"/exit Quit"
|
|
716
|
+
].join("\n")
|
|
717
|
+
);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (input === "/sessions") {
|
|
721
|
+
logChatSessions(await listChatSessions(process2.cwd()));
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (input === "/status") {
|
|
725
|
+
const report = await doctorVault(process2.cwd(), {});
|
|
726
|
+
log(`Vault health: ${report.ok ? "ok" : "needs attention"} (${report.recommendations.length} recommendation(s))`);
|
|
727
|
+
for (const recommendation of report.recommendations.slice(0, 5)) {
|
|
728
|
+
log(`- ${recommendation.label}: ${recommendation.command ?? recommendation.summary}`);
|
|
729
|
+
}
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (input === "/clear") {
|
|
733
|
+
sessionId = void 0;
|
|
734
|
+
log("Started a fresh chat session.");
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const result = await askChatSession(process2.cwd(), {
|
|
738
|
+
question: input,
|
|
739
|
+
sessionId,
|
|
740
|
+
saveOutput: options.saveOutput ?? false,
|
|
741
|
+
gapFill: options.gapFill ?? false,
|
|
742
|
+
format: options.format,
|
|
743
|
+
maxHistoryTurns: parsePositiveInt(options.maxHistoryTurns, 6)
|
|
744
|
+
});
|
|
745
|
+
sessionId = result.session.id;
|
|
746
|
+
log(result.answer);
|
|
747
|
+
log(`Session: ${result.session.id}`);
|
|
748
|
+
log(`Saved transcript: ${result.markdownPath}`);
|
|
749
|
+
await maybeEmitHeuristicNotice(["chat"]);
|
|
750
|
+
}
|
|
751
|
+
} finally {
|
|
752
|
+
reader.close();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
548
755
|
program.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
549
756
|
const notices = await collectCliNotices({
|
|
550
757
|
commandPath: getCommandPath(actionCommand),
|
|
@@ -923,6 +1130,44 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
|
|
|
923
1130
|
await maybeEmitHeuristicNotice(["query"]);
|
|
924
1131
|
}
|
|
925
1132
|
);
|
|
1133
|
+
program.command("chat").description("Ask the compiled wiki in a persisted multi-turn chat session.").argument("[question...]", "Question to ask in a chat session").option("--resume [id]", "Resume a chat session by id/prefix, or the most recent session when no id is supplied").option("--list", "List saved chat sessions", false).option("--delete <id>", "Delete a saved chat session by id/prefix").option("--save-output", "Also persist each answer as a regular wiki/outputs query page", false).option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").option("--max-history-turns <n>", "Number of prior turns to include as conversational context", "6").addOption(
|
|
1134
|
+
new Option("--format <format>", "Answer format for generated turns").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
1135
|
+
).action(
|
|
1136
|
+
async (questionParts, options) => {
|
|
1137
|
+
if (options.list) {
|
|
1138
|
+
const sessions = await listChatSessions(process2.cwd());
|
|
1139
|
+
if (isJson()) {
|
|
1140
|
+
emitJson(sessions);
|
|
1141
|
+
} else {
|
|
1142
|
+
logChatSessions(sessions);
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (options.delete) {
|
|
1147
|
+
const deleted = await deleteChatSession(process2.cwd(), options.delete);
|
|
1148
|
+
if (isJson()) {
|
|
1149
|
+
emitJson(deleted);
|
|
1150
|
+
} else {
|
|
1151
|
+
log(`Deleted chat session ${deleted.id}`);
|
|
1152
|
+
}
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const question = (questionParts ?? []).join(" ").trim();
|
|
1156
|
+
if (!question) {
|
|
1157
|
+
await runInteractiveChat(options);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const result = await runChatQuestion(question, options);
|
|
1161
|
+
if (isJson()) {
|
|
1162
|
+
emitJson(result);
|
|
1163
|
+
} else {
|
|
1164
|
+
log(result.answer);
|
|
1165
|
+
log(`Session: ${result.session.id}`);
|
|
1166
|
+
log(`Saved transcript: ${result.markdownPath}`);
|
|
1167
|
+
}
|
|
1168
|
+
await maybeEmitHeuristicNotice(["chat"]);
|
|
1169
|
+
}
|
|
1170
|
+
);
|
|
926
1171
|
var context = program.command("context").description("Build and manage token-bounded agent context packs.");
|
|
927
1172
|
context.command("build").description("Build a cited, token-bounded context pack for an agent task.").argument("<goal>", "Task, question, or goal the agent needs context for").option("--target <target>", "Optional page, node, path, project, or label to anchor the pack").option("--budget <tokens>", "Approximate token budget for included context", String(8e3)).option("--task <id>", "Attach the context pack to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(
|
|
928
1173
|
async (goal, options) => {
|
|
@@ -1190,6 +1435,23 @@ program.command("benchmark").description("Measure graph-guided context reduction
|
|
|
1190
1435
|
}
|
|
1191
1436
|
}
|
|
1192
1437
|
});
|
|
1438
|
+
var exportCommand = program.command("export").description("Export portable SwarmVault artifacts.");
|
|
1439
|
+
exportCommand.command("ai").description("Export static AI handoff files for agents, crawlers, and documentation systems.").option("--out <dir>", "Output directory", path2.join("wiki", "exports", "ai")).option("--max-full-chars <n>", "Maximum characters to include in llms-full.txt", "5000000").option("--no-page-siblings", "Skip per-page .txt and .json sibling files").action(async (options) => {
|
|
1440
|
+
const result = await exportAiPack(process2.cwd(), {
|
|
1441
|
+
outDir: options.out,
|
|
1442
|
+
maxFullChars: parsePositiveInt(options.maxFullChars, 5e6),
|
|
1443
|
+
pageSiblings: options.pageSiblings ?? true
|
|
1444
|
+
});
|
|
1445
|
+
if (isJson()) {
|
|
1446
|
+
emitJson(result);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
log(`Exported AI handoff pack to ${result.outputDir}`);
|
|
1450
|
+
log(`Files: ${result.files.length}; pages: ${result.pageCount}; nodes: ${result.nodeCount}; edges: ${result.edgeCount}`);
|
|
1451
|
+
if (result.truncatedFullText) {
|
|
1452
|
+
log("llms-full.txt was truncated; rerun with --max-full-chars for a larger export.");
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1193
1455
|
program.command("lint").description("Run anti-drift and wiki-health checks.").option("--deep", "Run LLM-powered advisory lint (default: from config)").option("--no-deep", "Skip deep lint even if enabled in config").option("--web", "Augment deep lint with configured web search", false).option("--conflicts", "Filter to contradiction findings only", false).option("--decay", "Filter to decay-related findings only", false).option("--tiers", "Filter to consolidation-tier findings only", false).action(async (options) => {
|
|
1194
1456
|
const lintConfig = await loadVaultConfig(process2.cwd()).catch(() => null);
|
|
1195
1457
|
const deepEnabled = options.decay || options.tiers ? false : options.deep ?? lintConfig?.config.profile.deepLintDefault ?? false;
|
|
@@ -1215,36 +1477,8 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
|
|
|
1215
1477
|
var graph = program.command("graph").description("Graph-related commands.").enablePositionalOptions();
|
|
1216
1478
|
var graphPush = graph.command("push").description("Push the compiled graph into external sinks.");
|
|
1217
1479
|
graph.command("update").alias("refresh").description("Refresh code-derived graph artifacts from tracked repo roots or one explicit repo path.").argument("[path]", "Optional repo root to refresh instead of configured/tracked roots").option("--lint", "Run lint after the refresh cycle", false).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(runGraphUpdateCommand);
|
|
1218
|
-
graph.command("tree").description("Write a collapsible source/module/symbol tree for the compiled graph.").option("--output <html>", "Output HTML path (default: wiki/graph/tree.html)").option("--root <path>", "Vault root to read instead of the current directory").option("--label <name>", "Tree title").option("--max-children <n>", "Maximum children to render per tree node", "250").action(
|
|
1219
|
-
|
|
1220
|
-
const result = await exportGraphTree(rootDir, options.output, {
|
|
1221
|
-
label: options.label,
|
|
1222
|
-
maxChildren: parsePositiveInt(options.maxChildren, 250)
|
|
1223
|
-
});
|
|
1224
|
-
if (isJson()) {
|
|
1225
|
-
emitJson(result);
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
log(`Graph tree: ${result.outputPath}`);
|
|
1229
|
-
log(`Sources: ${result.sourceCount}; nodes: ${result.nodeCount}.`);
|
|
1230
|
-
});
|
|
1231
|
-
graph.command("merge").description("Merge SwarmVault or node-link JSON graph files into one namespaced graph artifact.").argument("<graphs...>", "Graph JSON files to merge").requiredOption("--out <path>", "Output graph JSON path").option("--label <name>", "Label/prefix to use when merging one graph").action(async (graphPaths, options) => {
|
|
1232
|
-
const result = await mergeGraphFiles(
|
|
1233
|
-
graphPaths.map((inputPath) => path2.resolve(process2.cwd(), inputPath)),
|
|
1234
|
-
path2.resolve(process2.cwd(), options.out),
|
|
1235
|
-
{ label: options.label }
|
|
1236
|
-
);
|
|
1237
|
-
if (isJson()) {
|
|
1238
|
-
emitJson(result);
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
log(
|
|
1242
|
-
`Merged ${result.inputGraphs.length} graph${result.inputGraphs.length === 1 ? "" : "s"} into ${result.outputPath}. Nodes ${result.graph.nodes.length}, edges ${result.graph.edges.length}.`
|
|
1243
|
-
);
|
|
1244
|
-
for (const warning of result.warnings) {
|
|
1245
|
-
log(`Warning: ${warning}`);
|
|
1246
|
-
}
|
|
1247
|
-
});
|
|
1480
|
+
graph.command("tree").description("Write a collapsible source/module/symbol tree for the compiled graph.").option("--output <html>", "Output HTML path (default: wiki/graph/tree.html)").option("--root <path>", "Vault root to read instead of the current directory").option("--label <name>", "Tree title").option("--max-children <n>", "Maximum children to render per tree node", "250").action(runGraphTreeCommand);
|
|
1481
|
+
graph.command("merge").description("Merge SwarmVault or node-link JSON graph files into one namespaced graph artifact.").argument("<graphs...>", "Graph JSON files to merge").requiredOption("--out <path>", "Output graph JSON path").option("--label <name>", "Label/prefix to use when merging one graph").action(runGraphMergeCommand);
|
|
1248
1482
|
graph.command("status").description("Read-only check for graph/report presence and tracked repo changes.").argument("[path]", "Optional repo root to check instead of configured/tracked roots").action(showGraphStatusCommand);
|
|
1249
1483
|
graph.command("stats").description("Summarize compiled graph counts, node types, evidence classes, and relation mix.").action(async () => {
|
|
1250
1484
|
const stats = await graphStatsVault(process2.cwd());
|
|
@@ -1691,14 +1925,16 @@ async function confirmInteractive(message) {
|
|
|
1691
1925
|
rl.close();
|
|
1692
1926
|
}
|
|
1693
1927
|
}
|
|
1694
|
-
var watch = program.command("watch").description("Watch the inbox directory and optionally tracked repos, or run one refresh cycle immediately.").option("--lint", "Run lint after each compile cycle", false).option("--repo", "Also refresh tracked repo sources and watch their repo roots", false).option("--once", "Run one import/refresh cycle immediately instead of starting a watcher", false).option("--code-only", "Only re-extract code sources (AST-only, no LLM re-analysis)", false).option("--debounce <ms>", "Debounce window in milliseconds", "900").option("--root <path>", "Watch this repo root instead of config/auto-discovery (repeat for multiple)", collectRepeated, []).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(
|
|
1695
|
-
async (options) => {
|
|
1928
|
+
var watch = program.command("watch").description("Watch the inbox directory and optionally tracked repos, or run one refresh cycle immediately.").argument("[path]", "Optional repo root to watch or refresh instead of config/auto-discovery").option("--lint", "Run lint after each compile cycle", false).option("--repo", "Also refresh tracked repo sources and watch their repo roots", false).option("--once", "Run one import/refresh cycle immediately instead of starting a watcher", false).option("--code-only", "Only re-extract code sources (AST-only, no LLM re-analysis)", false).option("--debounce <ms>", "Debounce window in milliseconds", "900").option("--root <path>", "Watch this repo root instead of config/auto-discovery (repeat for multiple)", collectRepeated, []).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(
|
|
1929
|
+
async (targetPath, options) => {
|
|
1696
1930
|
const debounceMs = parsePositiveInt(options.debounce, 900);
|
|
1697
|
-
const
|
|
1931
|
+
const rootOverrides = [...targetPath ? [targetPath] : [], ...options.root ?? []];
|
|
1932
|
+
const overrideRoots = rootOverrides.length > 0 ? rootOverrides : void 0;
|
|
1933
|
+
const repoMode = options.repo || Boolean(targetPath);
|
|
1698
1934
|
if (options.once) {
|
|
1699
1935
|
const result = await runWatchCycle(process2.cwd(), {
|
|
1700
1936
|
lint: options.lint ?? false,
|
|
1701
|
-
repo:
|
|
1937
|
+
repo: repoMode,
|
|
1702
1938
|
codeOnly: options.codeOnly ?? false,
|
|
1703
1939
|
debounceMs,
|
|
1704
1940
|
force: options.force ?? false,
|
|
@@ -1708,7 +1944,7 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
1708
1944
|
emitJson(result);
|
|
1709
1945
|
} else {
|
|
1710
1946
|
log(
|
|
1711
|
-
`Refreshed inbox${
|
|
1947
|
+
`Refreshed inbox${repoMode ? " and tracked repos" : ""}. Imported ${result.importedCount}, repo imported ${result.repoImportedCount}, repo updated ${result.repoUpdatedCount}, repo removed ${result.repoRemovedCount}.`
|
|
1712
1948
|
);
|
|
1713
1949
|
}
|
|
1714
1950
|
return;
|
|
@@ -1716,16 +1952,16 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
1716
1952
|
const { paths } = await loadVaultConfig(process2.cwd());
|
|
1717
1953
|
const controller = await watchVault(process2.cwd(), {
|
|
1718
1954
|
lint: options.lint ?? false,
|
|
1719
|
-
repo:
|
|
1955
|
+
repo: repoMode,
|
|
1720
1956
|
codeOnly: options.codeOnly ?? false,
|
|
1721
1957
|
debounceMs,
|
|
1722
1958
|
force: options.force ?? false,
|
|
1723
1959
|
overrideRoots
|
|
1724
1960
|
});
|
|
1725
1961
|
if (isJson()) {
|
|
1726
|
-
emitJson({ status: "watching", inboxDir: paths.inboxDir, repo:
|
|
1962
|
+
emitJson({ status: "watching", inboxDir: paths.inboxDir, repo: repoMode });
|
|
1727
1963
|
} else {
|
|
1728
|
-
log(`Watching inbox${
|
|
1964
|
+
log(`Watching inbox${repoMode ? " and tracked repos" : ""} for changes. Press Ctrl+C to stop.`);
|
|
1729
1965
|
}
|
|
1730
1966
|
process2.on("SIGINT", async () => {
|
|
1731
1967
|
try {
|
|
@@ -1785,6 +2021,8 @@ program.command("update").description("Compatibility alias for graph update: ref
|
|
|
1785
2021
|
program.command("cluster-only").description("Compatibility alias for graph cluster: recompute graph communities and report artifacts without re-ingesting.").argument("[vault]", "Optional vault root to cluster instead of the current directory").option("--resolution <number>", "Override the Louvain community resolution for this run").action(
|
|
1786
2022
|
(vaultPath, options) => runGraphClusterCommand(options, vaultPath ? path2.resolve(process2.cwd(), vaultPath) : process2.cwd())
|
|
1787
2023
|
);
|
|
2024
|
+
program.command("tree").description("Compatibility alias for graph tree: write a collapsible source/module/symbol tree for the compiled graph.").option("--output <html>", "Output HTML path (default: wiki/graph/tree.html)").option("--root <path>", "Vault root to read instead of the current directory").option("--label <name>", "Tree title").option("--max-children <n>", "Maximum children to render per tree node", "250").action(runGraphTreeCommand);
|
|
2025
|
+
program.command("merge-graphs").description("Compatibility alias for graph merge: combine graph JSON files into one namespaced graph artifact.").argument("<graphs...>", "Graph JSON files to merge").requiredOption("--out <path>", "Output graph JSON path").option("--label <name>", "Label/prefix to use when merging one graph").action(runGraphMergeCommand);
|
|
1788
2026
|
var hook = program.command("hook").description("Install local git hooks that keep tracked repos and the vault in sync.");
|
|
1789
2027
|
hook.command("install").description("Install post-commit and post-checkout hooks for the nearest git repository.").action(async () => {
|
|
1790
2028
|
const status = await installGitHooks(process2.cwd());
|
|
@@ -2265,67 +2503,8 @@ retrieval.command("doctor").description("Diagnose retrieval index problems and o
|
|
|
2265
2503
|
log(`Warning: ${warning}`);
|
|
2266
2504
|
}
|
|
2267
2505
|
});
|
|
2268
|
-
program.command("scan").description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").action(
|
|
2269
|
-
|
|
2270
|
-
await initVault(rootDir, {});
|
|
2271
|
-
if (!isJson()) {
|
|
2272
|
-
log("Initialized workspace.");
|
|
2273
|
-
}
|
|
2274
|
-
const result = isHttpUrlInput(input) ? await addManagedSource(rootDir, input, {
|
|
2275
|
-
compile: true,
|
|
2276
|
-
brief: false,
|
|
2277
|
-
branch: options.branch,
|
|
2278
|
-
ref: options.ref,
|
|
2279
|
-
checkoutDir: options.checkoutDir
|
|
2280
|
-
}) : await ingestDirectory(rootDir, input, {});
|
|
2281
|
-
if (!isJson()) {
|
|
2282
|
-
if ("source" in result) {
|
|
2283
|
-
log(
|
|
2284
|
-
`Registered ${result.source.kind} source ${result.source.id}. Imported ${result.source.lastSyncCounts?.importedCount ?? 0}, updated ${result.source.lastSyncCounts?.updatedCount ?? 0}.`
|
|
2285
|
-
);
|
|
2286
|
-
} else {
|
|
2287
|
-
log(`Ingested ${result.imported.length} file(s).`);
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
const compiled = "compile" in result && result.compile ? result.compile : await compileVault(rootDir, {});
|
|
2291
|
-
const { paths } = await loadVaultConfig(rootDir);
|
|
2292
|
-
const shareCardPath = path2.join(paths.wikiDir, "graph", "share-card.md");
|
|
2293
|
-
const shareCardSvgPath = path2.join(paths.wikiDir, "graph", "share-card.svg");
|
|
2294
|
-
const shareKitPath = path2.join(paths.wikiDir, "graph", "share-kit");
|
|
2295
|
-
if (!isJson()) {
|
|
2296
|
-
log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
|
|
2297
|
-
log(`Share card: ${shareCardPath}`);
|
|
2298
|
-
log(`Visual card: ${shareCardSvgPath}`);
|
|
2299
|
-
log(`Share kit: ${shareKitPath}`);
|
|
2300
|
-
log("Post text: swarmvault graph share --post");
|
|
2301
|
-
}
|
|
2302
|
-
if (options.serve !== false) {
|
|
2303
|
-
const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
|
|
2304
|
-
const server = await startGraphServer(rootDir, port, { full: false });
|
|
2305
|
-
if (isJson()) {
|
|
2306
|
-
emitJson({
|
|
2307
|
-
...result,
|
|
2308
|
-
compiled,
|
|
2309
|
-
shareCardPath,
|
|
2310
|
-
shareCardSvgPath,
|
|
2311
|
-
shareKitPath,
|
|
2312
|
-
port: server.port,
|
|
2313
|
-
url: `http://localhost:${server.port}`
|
|
2314
|
-
});
|
|
2315
|
-
} else {
|
|
2316
|
-
log(`Graph viewer running at http://localhost:${server.port}`);
|
|
2317
|
-
}
|
|
2318
|
-
process2.on("SIGINT", async () => {
|
|
2319
|
-
try {
|
|
2320
|
-
await server.close();
|
|
2321
|
-
} catch {
|
|
2322
|
-
}
|
|
2323
|
-
process2.exit(0);
|
|
2324
|
-
});
|
|
2325
|
-
} else if (isJson()) {
|
|
2326
|
-
emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
|
|
2327
|
-
}
|
|
2328
|
-
});
|
|
2506
|
+
program.command("scan").description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").action(runScanCommand);
|
|
2507
|
+
program.command("clone").description("Compatibility alias for scan: initialize, clone/register a public repo URL, and compile it into the vault.").argument("<input>", "Public GitHub repo URL or local directory to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").action(runScanCommand);
|
|
2329
2508
|
function enableStructuredJsonOnSubcommands(command) {
|
|
2330
2509
|
for (const subcommand of command.commands) {
|
|
2331
2510
|
const hasJsonOption = subcommand.options.some((option) => option.attributeName() === "json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmvaultai/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "Global CLI for SwarmVault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepublishOnly": "node ../../scripts/check-release-sync.mjs && node ../../scripts/check-published-manifests.mjs"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@swarmvaultai/engine": "3.
|
|
47
|
+
"@swarmvaultai/engine": "3.12.0",
|
|
48
48
|
"commander": "^14.0.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|