@swarmvaultai/cli 0.1.17 → 0.1.19

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 +41 -5
  2. package/dist/index.js +79 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -25,11 +25,14 @@ cd my-vault
25
25
  swarmvault init --obsidian
26
26
  sed -n '1,120p' swarmvault.schema.md
27
27
  swarmvault ingest ./notes.md
28
+ swarmvault ingest ./repo
28
29
  swarmvault compile
29
30
  swarmvault query "What keeps recurring?"
30
31
  swarmvault query "Turn this into slides" --format slides
31
32
  swarmvault explore "What should I research next?" --steps 3
32
33
  swarmvault lint --deep
34
+ swarmvault graph query "Which nodes bridge the biggest clusters?"
35
+ swarmvault graph explain "concept:drift"
33
36
  swarmvault graph serve
34
37
  swarmvault graph export --html ./exports/graph.html
35
38
  ```
@@ -55,7 +58,22 @@ The schema file is the vault-specific instruction layer. Edit it to define namin
55
58
 
56
59
  ### `swarmvault ingest <path-or-url>`
57
60
 
58
- Ingest a local file path or URL into immutable source storage and write a manifest to `state/manifests/`.
61
+ Ingest a local file path, directory path, or URL into immutable source storage and write manifests to `state/manifests/`.
62
+
63
+ - local directories recurse by default
64
+ - directory ingest respects `.gitignore` unless you pass `--no-gitignore`
65
+ - repo-aware directory ingest records `repoRelativePath` and later compile writes `state/code-index.json`
66
+ - URL ingest still localizes remote image references by default
67
+
68
+ Useful flags:
69
+
70
+ - `--repo-root <path>`
71
+ - `--include <glob...>`
72
+ - `--exclude <glob...>`
73
+ - `--max-files <n>`
74
+ - `--no-gitignore`
75
+ - `--no-include-assets`
76
+ - `--max-asset-size <bytes>`
59
77
 
60
78
  ### `swarmvault inbox import [dir]`
61
79
 
@@ -71,6 +89,8 @@ Compile the current manifests into:
71
89
 
72
90
  The compiler also reads `swarmvault.schema.md` and records a `schema_hash` plus lifecycle metadata such as `status`, `created_at`, `updated_at`, `compiled_from`, and `managed_by` in generated pages so schema edits can mark pages stale without losing lifecycle state.
73
91
 
92
+ For ingested code trees, compile also writes `state/code-index.json` so local imports and module aliases can resolve across the repo-aware code graph.
93
+
74
94
  New concept and entity pages are staged into `wiki/candidates/` first. A later matching compile promotes them into `wiki/concepts/` or `wiki/entities/`.
75
95
 
76
96
  With `--approve`, compile writes a staged review bundle into `state/approvals/` without applying active wiki changes.
@@ -96,7 +116,7 @@ Inspect and resolve staged concept and entity candidates.
96
116
 
97
117
  Targets can be page ids or relative paths under `wiki/candidates/`.
98
118
 
99
- ### `swarmvault query "<question>" [--no-save] [--format markdown|report|slides]`
119
+ ### `swarmvault query "<question>" [--no-save] [--format markdown|report|slides|chart|image]`
100
120
 
101
121
  Query the compiled vault. The query layer also reads `swarmvault.schema.md`, so answers follow the vault’s own structure and grounding rules.
102
122
 
@@ -111,7 +131,7 @@ Saved outputs also carry related page, node, and source metadata so SwarmVault c
111
131
 
112
132
  Human-authored pages in `wiki/insights/` are also indexed into search and query context, but SwarmVault does not rewrite them after initialization.
113
133
 
114
- ### `swarmvault explore "<question>" [--steps <n>]`
134
+ ### `swarmvault explore "<question>" [--steps <n>] [--format markdown|report|slides|chart|image]`
115
135
 
116
136
  Run a save-first multi-step research loop.
117
137
 
@@ -159,11 +179,27 @@ The MCP surface also exposes `swarmvault://schema`, `swarmvault://sessions`, `sw
159
179
 
160
180
  ### `swarmvault graph serve`
161
181
 
162
- Start the local graph workspace backed by `state/graph.json`, `/api/search`, and `/api/page`.
182
+ Start the local graph workspace backed by `state/graph.json`, `/api/search`, `/api/page`, and local graph query/path/explain endpoints.
183
+
184
+ ### `swarmvault graph query "<question>" [--dfs] [--budget <n>]`
185
+
186
+ Run a deterministic local graph traversal seeded from local search and graph labels.
187
+
188
+ ### `swarmvault graph path <from> <to>`
189
+
190
+ Return the shortest high-confidence path between two graph targets.
191
+
192
+ ### `swarmvault graph explain <target>`
193
+
194
+ Inspect graph metadata, community membership, neighbors, and provenance for a node or page.
195
+
196
+ ### `swarmvault graph god-nodes [--limit <n>]`
197
+
198
+ List the most connected bridge-heavy nodes in the current graph.
163
199
 
164
200
  ### `swarmvault graph export --html <output>`
165
201
 
166
- Export the graph workspace as a standalone HTML file with embedded graph and page data for offline sharing.
202
+ Export the graph workspace as a standalone HTML file with embedded graph and page data for offline sharing. The exported file keeps read-only graph browsing, search, and page preview. The live graph query/path/explain actions remain part of `graph serve` and the MCP surface.
167
203
 
168
204
  ### `swarmvault install --agent <codex|claude|cursor|goose|pi|gemini|opencode>`
169
205
 
package/dist/index.js CHANGED
@@ -7,18 +7,23 @@ import {
7
7
  acceptApproval,
8
8
  archiveCandidate,
9
9
  compileVault,
10
+ explainGraphVault,
10
11
  exploreVault,
11
12
  exportGraphHtml,
12
13
  importInbox,
14
+ ingestDirectory,
13
15
  ingestInput,
14
16
  initVault,
15
17
  installAgent,
16
18
  lintVault,
17
19
  listApprovals,
18
20
  listCandidates,
21
+ listGodNodes,
19
22
  listSchedules,
20
23
  loadVaultConfig,
24
+ pathGraphVault,
21
25
  promoteCandidate,
26
+ queryGraphVault,
22
27
  queryVault,
23
28
  readApproval,
24
29
  rejectApproval,
@@ -35,9 +40,9 @@ program.name("swarmvault").description("SwarmVault is a local-first LLM wiki com
35
40
  function readCliVersion() {
36
41
  try {
37
42
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
38
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.16";
43
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.19";
39
44
  } catch {
40
- return "0.1.16";
45
+ return "0.1.19";
41
46
  }
42
47
  }
43
48
  function isJson() {
@@ -64,18 +69,40 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
64
69
  log("Initialized SwarmVault workspace.");
65
70
  }
66
71
  });
67
- program.command("ingest").description("Ingest a local file path or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path or URL").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").action(async (input, options) => {
68
- const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? Number.parseInt(options.maxAssetSize, 10) : void 0;
69
- const manifest = await ingestInput(process.cwd(), input, {
70
- includeAssets: options.includeAssets,
71
- maxAssetSize: Number.isFinite(maxAssetSize) ? maxAssetSize : void 0
72
- });
73
- if (isJson()) {
74
- emitJson(manifest);
75
- } else {
76
- log(manifest.sourceId);
72
+ program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
73
+ async (input, options) => {
74
+ const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? Number.parseInt(options.maxAssetSize, 10) : void 0;
75
+ const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? Number.parseInt(options.maxFiles, 10) : void 0;
76
+ const commonOptions = {
77
+ includeAssets: options.includeAssets,
78
+ maxAssetSize: Number.isFinite(maxAssetSize) ? maxAssetSize : void 0,
79
+ repoRoot: options.repoRoot,
80
+ include: options.include,
81
+ exclude: options.exclude,
82
+ maxFiles: Number.isFinite(maxFiles) ? maxFiles : void 0,
83
+ gitignore: options.gitignore
84
+ };
85
+ const directoryResult = !/^https?:\/\//i.test(input) ? await import("fs/promises").then(
86
+ (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process.cwd(), input, commonOptions) : null).catch(() => null)
87
+ ) : null;
88
+ if (directoryResult) {
89
+ if (isJson()) {
90
+ emitJson(directoryResult);
91
+ } else {
92
+ log(
93
+ `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
94
+ );
95
+ }
96
+ return;
97
+ }
98
+ const manifest = await ingestInput(process.cwd(), input, commonOptions);
99
+ if (isJson()) {
100
+ emitJson(manifest);
101
+ } else {
102
+ log(manifest.sourceId);
103
+ }
77
104
  }
78
- });
105
+ );
79
106
  var inbox = program.command("inbox").description("Inbox and capture workflows.");
80
107
  inbox.command("import").description("Import supported files from the configured inbox directory.").argument("[dir]", "Optional inbox directory override").action(async (dir) => {
81
108
  const result = await importInbox(process.cwd(), dir);
@@ -171,6 +198,45 @@ graph.command("export").description("Export the graph viewer as a single self-co
171
198
  log(`Exported graph HTML to ${outputPath}`);
172
199
  }
173
200
  });
201
+ 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) => {
202
+ const budget = options.budget ? Number.parseInt(options.budget, 10) : void 0;
203
+ const result = await queryGraphVault(process.cwd(), question, {
204
+ traversal: options.dfs ? "dfs" : "bfs",
205
+ budget: Number.isFinite(budget) ? budget : void 0
206
+ });
207
+ if (isJson()) {
208
+ emitJson(result);
209
+ return;
210
+ }
211
+ log(result.summary);
212
+ });
213
+ graph.command("path").description("Find the shortest graph path between two nodes or pages.").argument("<from>", "Source node/page label or id").argument("<to>", "Target node/page label or id").action(async (from, to) => {
214
+ const result = await pathGraphVault(process.cwd(), from, to);
215
+ if (isJson()) {
216
+ emitJson(result);
217
+ return;
218
+ }
219
+ log(result.summary);
220
+ });
221
+ graph.command("explain").description("Explain a graph node, its page, community, and neighbors.").argument("<target>", "Node/page label or id").action(async (target) => {
222
+ const result = await explainGraphVault(process.cwd(), target);
223
+ if (isJson()) {
224
+ emitJson(result);
225
+ return;
226
+ }
227
+ log(result.summary);
228
+ });
229
+ graph.command("god-nodes").description("List the highest-connectivity non-source graph nodes.").option("--limit <n>", "Maximum number of nodes to return", "10").action(async (options) => {
230
+ const limit = Number.parseInt(options.limit ?? "10", 10);
231
+ const result = await listGodNodes(process.cwd(), Number.isFinite(limit) ? limit : 10);
232
+ if (isJson()) {
233
+ emitJson(result);
234
+ return;
235
+ }
236
+ for (const node of result) {
237
+ log(`${node.label} degree=${node.degree ?? 0} bridge=${node.bridgeScore ?? 0}`);
238
+ }
239
+ });
174
240
  var review = program.command("review").description("Review staged compile approval bundles.");
175
241
  review.command("list").description("List staged approval bundles and their resolution status.").action(async () => {
176
242
  const approvals = await listApprovals(process.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "commander": "^14.0.1",
42
- "@swarmvaultai/engine": "0.1.17"
42
+ "@swarmvaultai/engine": "0.1.19"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.6.0",