@swarmvaultai/cli 0.6.6 → 0.6.7
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/dist/index.js +90 -11
- 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.
|
|
271
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.6.7";
|
|
232
272
|
} catch {
|
|
233
|
-
return "0.6.
|
|
273
|
+
return "0.6.7";
|
|
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
|
-
|
|
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((
|
|
740
|
-
if (targets.length
|
|
741
|
-
throw new Error("Pass
|
|
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({
|
|
823
|
+
emitJson(results.length === 1 ? results[0] : { exports: results });
|
|
747
824
|
} else {
|
|
748
|
-
|
|
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.
|
|
3
|
+
"version": "0.6.7",
|
|
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.
|
|
41
|
+
"@swarmvaultai/engine": "0.6.7",
|
|
42
42
|
"commander": "^14.0.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|