@swarmvaultai/cli 3.11.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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/dist/index.js +160 -2
  3. 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
@@ -155,6 +158,28 @@ Compare the current `state/graph.json` against the last committed graph in git.
155
158
  - when no git baseline exists, falls back to a summary of the current graph state
156
159
  - supports `--json` for structured automation output
157
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
+
158
183
  ### `swarmvault doctor [--repair]`
159
184
 
160
185
  Run a whole-vault health check before handing the workspace to an agent or opening the live 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.11.0";
322
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.12.0";
319
323
  } catch {
320
- return "3.11.0";
324
+ return "3.12.0";
321
325
  }
322
326
  }
323
327
  function parsePositiveInt(value, fallback) {
@@ -649,6 +653,105 @@ async function runScanCommand(input, options) {
649
653
  emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
650
654
  }
651
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
+ }
652
755
  program.hook("postAction", async (_thisCommand, actionCommand) => {
653
756
  const notices = await collectCliNotices({
654
757
  commandPath: getCommandPath(actionCommand),
@@ -1027,6 +1130,44 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
1027
1130
  await maybeEmitHeuristicNotice(["query"]);
1028
1131
  }
1029
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
+ );
1030
1171
  var context = program.command("context").description("Build and manage token-bounded agent context packs.");
1031
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(
1032
1173
  async (goal, options) => {
@@ -1294,6 +1435,23 @@ program.command("benchmark").description("Measure graph-guided context reduction
1294
1435
  }
1295
1436
  }
1296
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
+ });
1297
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) => {
1298
1456
  const lintConfig = await loadVaultConfig(process2.cwd()).catch(() => null);
1299
1457
  const deepEnabled = options.decay || options.tiers ? false : options.deep ?? lintConfig?.config.profile.deepLintDefault ?? false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "3.11.0",
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.11.0",
47
+ "@swarmvaultai/engine": "3.12.0",
48
48
  "commander": "^14.0.1"
49
49
  },
50
50
  "devDependencies": {