@swarmvaultai/cli 0.6.6 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +90 -11
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -61,10 +61,23 @@ import { mkdir, readFile, writeFile } from "fs/promises";
61
61
  import os from "os";
62
62
  import path from "path";
63
63
  var NOTICE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
64
+ var HEURISTIC_NOTICE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
64
65
  var NOTICE_TIMEOUT_MS = 2e3;
65
66
  var STAR_URL = "https://github.com/swarmclawai/swarmvault";
66
67
  var NPM_PACKAGE = "@swarmvaultai/cli";
67
68
  var SUPPRESSED_COMMANDS = /* @__PURE__ */ new Set(["graph serve", "mcp", "schedule serve", "watch"]);
69
+ var HEURISTIC_NOTICE_MESSAGE = [
70
+ "SwarmVault is using the heuristic analyzer for compile/query.",
71
+ "For much sharper concepts, entities, and summaries, configure an LLM provider.",
72
+ "Recommended local setup:",
73
+ " ollama pull gemma4",
74
+ "then add to swarmvault.config.json:",
75
+ ' "providers": { "llm": { "type": "ollama", "model": "gemma4" } },',
76
+ ' "tasks": { "compileProvider": "llm", "queryProvider": "llm" }',
77
+ "You can also configure openai, anthropic, gemini, openrouter, groq, together, xai,",
78
+ "cerebras, or any openai-compatible endpoint as a provider.",
79
+ "Set SWARMVAULT_NO_NOTICES=1 to hide this notice."
80
+ ].join("\n");
68
81
  function resolveCliStatePath(env = process.env) {
69
82
  const override = env.SWARMVAULT_CLI_STATE_PATH?.trim();
70
83
  if (override) {
@@ -93,6 +106,33 @@ function shouldEmitCliNotices(options) {
93
106
  const commandKey = options.commandPath.join(" ").trim();
94
107
  return !SUPPRESSED_COMMANDS.has(commandKey);
95
108
  }
109
+ async function collectHeuristicProviderNotice(options) {
110
+ if (!shouldEmitCliNotices({
111
+ commandPath: options.commandPath ?? ["compile"],
112
+ currentVersion: "",
113
+ json: options.json,
114
+ env: options.env,
115
+ stderrIsTTY: options.stderrIsTTY,
116
+ stdoutIsTTY: options.stdoutIsTTY
117
+ })) {
118
+ return null;
119
+ }
120
+ const env = options.env ?? process.env;
121
+ const statePath = options.statePath ?? resolveCliStatePath(env);
122
+ if (!statePath) {
123
+ return null;
124
+ }
125
+ const state = await readNoticeState(statePath);
126
+ const now = options.now ?? /* @__PURE__ */ new Date();
127
+ const lastMs = state.lastHeuristicNoticeAt ? Date.parse(state.lastHeuristicNoticeAt) : Number.NaN;
128
+ const dueForReminder = !Number.isFinite(lastMs) || now.getTime() - lastMs >= HEURISTIC_NOTICE_TTL_MS;
129
+ if (!dueForReminder) {
130
+ return null;
131
+ }
132
+ const nextState = { ...state, lastHeuristicNoticeAt: now.toISOString() };
133
+ await writeNoticeState(statePath, nextState);
134
+ return HEURISTIC_NOTICE_MESSAGE;
135
+ }
96
136
  async function collectCliNotices(options) {
97
137
  if (!shouldEmitCliNotices(options)) {
98
138
  return [];
@@ -228,9 +268,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
228
268
  function readCliVersion() {
229
269
  try {
230
270
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
231
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.6.6";
271
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.6.8";
232
272
  } catch {
233
- return "0.6.6";
273
+ return "0.6.8";
234
274
  }
235
275
  }
236
276
  function parsePositiveInt(value, fallback) {
@@ -273,6 +313,31 @@ function emitNotice(message) {
273
313
  process2.stderr.write(`[swarmvault] ${message}
274
314
  `);
275
315
  }
316
+ async function maybeEmitHeuristicNotice(commandPath) {
317
+ if (isJson()) {
318
+ return;
319
+ }
320
+ try {
321
+ const { config } = await loadVaultConfig(process2.cwd());
322
+ const analysisTaskKeys = ["compileProvider", "queryProvider", "lintProvider"];
323
+ const usingHeuristic = analysisTaskKeys.every((task) => {
324
+ const providerId = config.tasks[task];
325
+ const providerConfig = config.providers[providerId];
326
+ return !providerConfig || providerConfig.type === "heuristic";
327
+ });
328
+ if (!usingHeuristic) {
329
+ return;
330
+ }
331
+ const notice = await collectHeuristicProviderNotice({
332
+ commandPath,
333
+ json: isJson()
334
+ });
335
+ if (notice) {
336
+ emitNotice(notice);
337
+ }
338
+ } catch {
339
+ }
340
+ }
276
341
  function canPromptGuide() {
277
342
  return Boolean(process2.stdin.isTTY && process2.stdout.isTTY && !isJson());
278
343
  }
@@ -612,6 +677,7 @@ program.command("compile").description("Compile manifests into wiki pages, graph
612
677
  log(`Compiled ${result.sourceCount} source(s), ${result.pageCount} page(s). Changed: ${result.changedPages.length}.`);
613
678
  }
614
679
  }
680
+ await maybeEmitHeuristicNotice(["compile"]);
615
681
  });
616
682
  program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").addOption(
617
683
  new Option("--format <format>", "Output format").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
@@ -629,6 +695,7 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
629
695
  log(`Saved to ${result.savedPath}`);
630
696
  }
631
697
  }
698
+ await maybeEmitHeuristicNotice(["query"]);
632
699
  });
633
700
  program.command("explore").description("Run a save-first multi-step exploration loop against the vault.").argument("<question>", "Root question to explore").option("--steps <n>", "Maximum number of exploration steps", "3").addOption(
634
701
  new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
@@ -645,6 +712,7 @@ program.command("explore").description("Run a save-first multi-step exploration
645
712
  log(`Exploration hub saved to ${result.hubPath}`);
646
713
  log(`Completed ${result.stepCount} step(s).`);
647
714
  }
715
+ await maybeEmitHeuristicNotice(["explore"]);
648
716
  });
649
717
  program.command("benchmark").description("Measure graph-guided context reduction against a naive full-corpus read.").option("--question <text...>", "Optional custom benchmark question(s)").action(async (options) => {
650
718
  const result = await benchmarkVault(process2.cwd(), {
@@ -655,7 +723,13 @@ program.command("benchmark").description("Measure graph-guided context reduction
655
723
  } else {
656
724
  log(`Corpus tokens: ${result.corpusTokens}`);
657
725
  log(`Average query tokens: ${result.avgQueryTokens}`);
658
- log(`Reduction ratio: ${(result.reductionRatio * 100).toFixed(1)}%`);
726
+ const ratioPercent = (result.reductionRatio * 100).toFixed(1);
727
+ log(`Reduction ratio: ${ratioPercent}%`);
728
+ if (result.reductionRatio < 0) {
729
+ log(
730
+ "Note: graph-guided context is larger than the full corpus on this vault. The benchmark is only meaningful once the corpus exceeds the graph traversal budget."
731
+ );
732
+ }
659
733
  }
660
734
  });
661
735
  program.command("lint").description("Run anti-drift and wiki-health checks.").option("--deep", "Run LLM-powered advisory lint", false).option("--web", "Augment deep lint with configured web search", false).option("--conflicts", "Filter to contradiction findings only", false).action(async (options) => {
@@ -730,22 +804,27 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
730
804
  process2.exit(0);
731
805
  });
732
806
  });
733
- graph.command("export").description("Export the graph as HTML, SVG, GraphML, or Cypher.").option("--html <output>", "Output HTML file path").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--full", "Disable overview sampling for HTML export", false).action(async (options) => {
807
+ graph.command("export").description("Export the graph as HTML, SVG, GraphML, or Cypher. Combine flags to write multiple formats in one run.").option("--html <output>", "Output HTML file path").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--full", "Disable overview sampling for HTML export", false).action(async (options) => {
734
808
  const targets = [
735
809
  options.html ? { format: "html", outputPath: options.html } : null,
736
810
  options.svg ? { format: "svg", outputPath: options.svg } : null,
737
811
  options.graphml ? { format: "graphml", outputPath: options.graphml } : null,
738
812
  options.cypher ? { format: "cypher", outputPath: options.cypher } : null
739
- ].filter((target2) => Boolean(target2));
740
- if (targets.length !== 1) {
741
- throw new Error("Pass exactly one of --html, --svg, --graphml, or --cypher.");
813
+ ].filter((target) => Boolean(target));
814
+ if (targets.length === 0) {
815
+ throw new Error("Pass at least one of --html, --svg, --graphml, or --cypher.");
816
+ }
817
+ const results = [];
818
+ for (const target of targets) {
819
+ const outputPath = target.format === "html" ? await exportGraphHtml(process2.cwd(), target.outputPath, { full: options.full ?? false }) : (await exportGraphFormat(process2.cwd(), target.format, target.outputPath)).outputPath;
820
+ results.push({ format: target.format, outputPath });
742
821
  }
743
- const target = targets[0];
744
- const outputPath = target.format === "html" ? await exportGraphHtml(process2.cwd(), target.outputPath, { full: options.full ?? false }) : (await exportGraphFormat(process2.cwd(), target.format, target.outputPath)).outputPath;
745
822
  if (isJson()) {
746
- emitJson({ format: target.format, outputPath });
823
+ emitJson(results.length === 1 ? results[0] : { exports: results });
747
824
  } else {
748
- log(`Exported graph ${target.format} to ${outputPath}`);
825
+ for (const result of results) {
826
+ log(`Exported graph ${result.format} to ${result.outputPath}`);
827
+ }
749
828
  }
750
829
  });
751
830
  graph.command("query").description("Traverse the compiled graph deterministically from local search seeds.").argument("<question>", "Question or graph search seed").option("--dfs", "Prefer a depth-first traversal instead of breadth-first", false).option("--budget <n>", "Maximum number of graph nodes to summarize").action(async (question, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,7 @@
38
38
  "node": ">=24.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@swarmvaultai/engine": "0.6.6",
41
+ "@swarmvaultai/engine": "0.6.8",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {