@swarmvaultai/cli 0.7.21 → 0.7.23

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 +30 -7
  2. package/dist/index.js +82 -25
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -72,6 +72,18 @@ The schema file is the vault-specific instruction layer. Edit it to define namin
72
72
 
73
73
  `--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` starter profile 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`.
74
74
 
75
+ ### `swarmvault scan <directory> [--port <port>] [--no-serve]`
76
+
77
+ Quick-start a scratch vault from a local directory in one command.
78
+
79
+ - initializes the current directory as a SwarmVault workspace
80
+ - ingests the supplied directory as local sources
81
+ - compiles the vault immediately
82
+ - starts `graph serve` unless you pass `--no-serve`
83
+ - respects `--port` when you want a specific viewer port
84
+
85
+ Use this when you want the fastest repo or docs-tree walkthrough without first deciding on managed-source registration.
86
+
75
87
  ### `swarmvault source add|list|reload|review|guide|session|delete`
76
88
 
77
89
  Manage recurring source roots through a registry-backed workflow.
@@ -244,12 +256,14 @@ Set `profile.deepLintDefault: true` when deep lint should be the default for `sw
244
256
 
245
257
  `--conflicts` filters the results down to contradiction-focused findings so you can audit conflicting claims without the rest of the lint output.
246
258
 
247
- ### `swarmvault watch [--lint] [--repo] [--once] [--debounce <ms>]`
259
+ ### `swarmvault watch [--lint] [--repo] [--once] [--code-only] [--debounce <ms>]`
248
260
 
249
- Watch the inbox directory and trigger import and compile cycles when files change. With `--repo`, each cycle also refreshes tracked repo roots that were previously ingested through directory ingest. With `--once`, SwarmVault runs one refresh cycle immediately instead of starting a long-running watcher. With `--lint`, each cycle also runs linting. Each cycle writes a canonical session artifact to `state/sessions/`, and compatibility run metadata is still appended to `state/jobs.ndjson`.
261
+ Watch the inbox directory and trigger import and compile cycles when files change. With `--repo`, each cycle also refreshes tracked repo roots that were previously ingested through directory ingest. With `--once`, SwarmVault runs one refresh cycle immediately instead of starting a long-running watcher. With `--code-only`, SwarmVault forces the narrower AST-only refresh path and skips non-code semantic re-analysis until you run a normal `compile`. With `--lint`, each cycle also runs linting. Each cycle writes a canonical session artifact to `state/sessions/`, and compatibility run metadata is still appended to `state/jobs.ndjson`.
250
262
 
251
263
  When `--repo` sees non-code changes under tracked repo roots, SwarmVault records those files under `state/watch/pending-semantic-refresh.json`, marks affected compiled pages stale, and exposes the pending set through `watch status` and the local graph workspace instead of silently re-ingesting them.
252
264
 
265
+ When `--repo` sees only code-file changes under tracked repo roots, SwarmVault takes the faster code-only path: it refreshes code pages and graph structure without re-running non-code semantic analysis for unchanged sources.
266
+
253
267
  ### `swarmvault watch status`
254
268
 
255
269
  Show watched repo roots, the latest watch run, and any pending semantic refresh entries for tracked non-code repo changes.
@@ -262,7 +276,7 @@ Manage SwarmVault's local git hook blocks for the nearest git repository.
262
276
  - `hook uninstall` removes only the SwarmVault-managed hook block
263
277
  - `hook status` reports whether those managed hook blocks are installed
264
278
 
265
- The installed hooks run `swarmvault watch --repo --once` from the vault root so repo-aware source changes are re-ingested and recompiled after commit and checkout.
279
+ The installed hooks run `swarmvault watch --repo --once --code-only` from the vault root so commit and checkout refreshes update code pages and graph structure quickly. Run a normal `swarmvault compile` when you also want non-code semantic re-analysis.
266
280
 
267
281
  ### `swarmvault mcp`
268
282
 
@@ -299,14 +313,20 @@ Inspect graph metadata, community membership, neighbors, provenance, and group-p
299
313
 
300
314
  List the most connected bridge-heavy nodes in the current graph.
301
315
 
302
- ### `swarmvault graph export --html|--svg|--graphml|--cypher <output>`
316
+ ### `swarmvault graph export --html|--html-standalone|--svg|--graphml|--cypher|--json|--obsidian|--canvas <output>`
303
317
 
304
- Export the current graph as one of four formats:
318
+ Export the current graph as one or more shareable formats:
305
319
 
306
- - `--html` for the standalone read-only graph workspace
320
+ - `--html` for the full self-contained read-only graph workspace
321
+ - `--html-standalone` for a lighter vis.js export with node search, legend, and sidebar inspection
307
322
  - `--svg` for a static shareable diagram
308
323
  - `--graphml` for graph-tool interoperability
309
324
  - `--cypher` for Neo4j-style import scripts
325
+ - `--json` for a deterministic machine-readable graph package
326
+ - `--obsidian` for an Obsidian-friendly markdown vault with one note per node plus community notes
327
+ - `--canvas` for an Obsidian canvas grouped by community
328
+
329
+ You can combine multiple flags in one run to write several exports at once.
310
330
 
311
331
  ### `swarmvault graph push neo4j`
312
332
 
@@ -331,7 +351,7 @@ Defaults:
331
351
  - namespaces every remote record by `vaultId` so multiple vaults can safely share one Neo4j database
332
352
  - upserts current graph records and does not prune stale remote data yet
333
353
 
334
- ### `swarmvault install --agent <codex|claude|cursor|goose|pi|gemini|opencode|aider|copilot>`
354
+ ### `swarmvault install --agent <codex|claude|cursor|goose|pi|gemini|opencode|aider|copilot|trae|claw|droid>`
335
355
 
336
356
  Install agent-specific rules into the current project so an agent understands the SwarmVault workspace contract and workflow.
337
357
 
@@ -352,6 +372,9 @@ Agent target mapping:
352
372
  - `aider` writes `CONVENTIONS.md` and merges `.aider.conf.yml`
353
373
  - `copilot` writes `.github/copilot-instructions.md` plus `AGENTS.md`
354
374
  - `cursor` writes `.cursor/rules/swarmvault.mdc`
375
+ - `trae` writes `.trae/rules/swarmvault.md`
376
+ - `claw` writes `.claw/skills/swarmvault/SKILL.md`
377
+ - `droid` writes `.factory/rules/swarmvault.md`
355
378
 
356
379
  Hook semantics:
357
380
 
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  exploreVault,
17
17
  exportGraphFormat,
18
18
  exportGraphHtml,
19
+ exportObsidianCanvas,
20
+ exportObsidianVault,
19
21
  getGitHookStatus,
20
22
  getWatchStatus,
21
23
  guideManagedSource,
@@ -268,9 +270,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
268
270
  function readCliVersion() {
269
271
  try {
270
272
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
271
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.21";
273
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.23";
272
274
  } catch {
273
- return "0.7.21";
275
+ return "0.7.23";
274
276
  }
275
277
  }
276
278
  function parsePositiveInt(value, fallback) {
@@ -812,29 +814,49 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
812
814
  process2.exit(0);
813
815
  });
814
816
  });
815
- 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) => {
816
- const targets = [
817
- options.html ? { format: "html", outputPath: options.html } : null,
818
- options.svg ? { format: "svg", outputPath: options.svg } : null,
819
- options.graphml ? { format: "graphml", outputPath: options.graphml } : null,
820
- options.cypher ? { format: "cypher", outputPath: options.cypher } : null
821
- ].filter((target) => Boolean(target));
822
- if (targets.length === 0) {
823
- throw new Error("Pass at least one of --html, --svg, --graphml, or --cypher.");
824
- }
825
- const results = [];
826
- for (const target of targets) {
827
- const outputPath = target.format === "html" ? await exportGraphHtml(process2.cwd(), target.outputPath, { full: options.full ?? false }) : (await exportGraphFormat(process2.cwd(), target.format, target.outputPath)).outputPath;
828
- results.push({ format: target.format, outputPath });
829
- }
830
- if (isJson()) {
831
- emitJson(results.length === 1 ? results[0] : { exports: results });
832
- } else {
833
- for (const result of results) {
834
- log(`Exported graph ${result.format} to ${result.outputPath}`);
817
+ graph.command("export").description(
818
+ "Export the graph as HTML, SVG, GraphML, Cypher, JSON, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
819
+ ).option("--html <output>", "Output HTML file path").option("--html-standalone <output>", "Output lightweight standalone HTML file path (vis.js, no build tooling)").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--json <output>", "Output JSON file path").option("--obsidian <output>", "Output Obsidian vault directory path").option("--canvas <output>", "Output Obsidian canvas file path").option("--full", "Disable overview sampling for HTML export", false).action(
820
+ async (options) => {
821
+ const targets = [
822
+ options.html ? { format: "html", outputPath: options.html } : null,
823
+ options.htmlStandalone ? { format: "html-standalone", outputPath: options.htmlStandalone } : null,
824
+ options.svg ? { format: "svg", outputPath: options.svg } : null,
825
+ options.graphml ? { format: "graphml", outputPath: options.graphml } : null,
826
+ options.cypher ? { format: "cypher", outputPath: options.cypher } : null,
827
+ options.json ? { format: "json", outputPath: options.json } : null,
828
+ options.obsidian ? { format: "obsidian", outputPath: options.obsidian } : null,
829
+ options.canvas ? { format: "canvas", outputPath: options.canvas } : null
830
+ ].filter((target) => Boolean(target));
831
+ if (targets.length === 0) {
832
+ throw new Error("Pass at least one of --html, --html-standalone, --svg, --graphml, --cypher, --json, --obsidian, or --canvas.");
833
+ }
834
+ const results = [];
835
+ for (const target of targets) {
836
+ if (target.format === "html") {
837
+ const outputPath = await exportGraphHtml(process2.cwd(), target.outputPath, { full: options.full ?? false });
838
+ results.push({ format: target.format, outputPath });
839
+ } else if (target.format === "obsidian") {
840
+ const result = await exportObsidianVault(process2.cwd(), target.outputPath);
841
+ results.push({ format: result.format, outputPath: result.outputPath, fileCount: result.fileCount });
842
+ } else if (target.format === "canvas") {
843
+ const result = await exportObsidianCanvas(process2.cwd(), target.outputPath);
844
+ results.push({ format: result.format, outputPath: result.outputPath });
845
+ } else {
846
+ const result = await exportGraphFormat(process2.cwd(), target.format, target.outputPath);
847
+ results.push({ format: result.format, outputPath: result.outputPath });
848
+ }
849
+ }
850
+ if (isJson()) {
851
+ emitJson(results.length === 1 ? results[0] : { exports: results });
852
+ } else {
853
+ for (const result of results) {
854
+ const suffix = result.fileCount ? ` (${result.fileCount} files)` : "";
855
+ log(`Exported graph ${result.format} to ${result.outputPath}${suffix}`);
856
+ }
835
857
  }
836
858
  }
837
- });
859
+ );
838
860
  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) => {
839
861
  const budget = options.budget ? parsePositiveInt(options.budget, 0) || void 0 : void 0;
840
862
  const result = await queryGraphVault(process2.cwd(), question, {
@@ -959,12 +981,13 @@ candidate.command("archive").description("Archive a candidate by removing it fro
959
981
  log(`Archived ${result.pageId}`);
960
982
  }
961
983
  });
962
- 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("--debounce <ms>", "Debounce window in milliseconds", "900").action(async (options) => {
984
+ 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").action(async (options) => {
963
985
  const debounceMs = parsePositiveInt(options.debounce, 900);
964
986
  if (options.once) {
965
987
  const result = await runWatchCycle(process2.cwd(), {
966
988
  lint: options.lint ?? false,
967
989
  repo: options.repo ?? false,
990
+ codeOnly: options.codeOnly ?? false,
968
991
  debounceMs
969
992
  });
970
993
  if (isJson()) {
@@ -980,6 +1003,7 @@ var watch = program.command("watch").description("Watch the inbox directory and
980
1003
  const controller = await watchVault(process2.cwd(), {
981
1004
  lint: options.lint ?? false,
982
1005
  repo: options.repo ?? false,
1006
+ codeOnly: options.codeOnly ?? false,
983
1007
  debounceMs
984
1008
  });
985
1009
  if (isJson()) {
@@ -1097,7 +1121,7 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
1097
1121
  process2.exit(0);
1098
1122
  });
1099
1123
  });
1100
- program.command("install").description("Install SwarmVault instructions for an agent in the current project.").requiredOption("--agent <agent>", "codex, claude, cursor, goose, pi, gemini, opencode, aider, or copilot").option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).action(
1124
+ program.command("install").description("Install SwarmVault instructions for an agent in the current project.").requiredOption("--agent <agent>", "codex, claude, cursor, goose, pi, gemini, opencode, aider, copilot, trae, claw, or droid").option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).action(
1101
1125
  async (options) => {
1102
1126
  const hookCapableAgents = /* @__PURE__ */ new Set(["claude", "opencode", "gemini", "copilot"]);
1103
1127
  if (options.hook && !hookCapableAgents.has(options.agent)) {
@@ -1117,6 +1141,39 @@ program.command("install").description("Install SwarmVault instructions for an a
1117
1141
  }
1118
1142
  }
1119
1143
  );
1144
+ program.command("scan").description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<directory>", "Directory to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").action(async (directory, options) => {
1145
+ const rootDir = process2.cwd();
1146
+ await initVault(rootDir, {});
1147
+ if (!isJson()) {
1148
+ log("Initialized workspace.");
1149
+ }
1150
+ const result = await ingestDirectory(rootDir, directory, {});
1151
+ if (!isJson()) {
1152
+ log(`Ingested ${result.imported.length} file(s).`);
1153
+ }
1154
+ const compiled = await compileVault(rootDir, {});
1155
+ if (!isJson()) {
1156
+ log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
1157
+ }
1158
+ if (options.serve !== false) {
1159
+ const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
1160
+ const server = await startGraphServer(rootDir, port, { full: false });
1161
+ if (isJson()) {
1162
+ emitJson({ ...result, compiled, port: server.port, url: `http://localhost:${server.port}` });
1163
+ } else {
1164
+ log(`Graph viewer running at http://localhost:${server.port}`);
1165
+ }
1166
+ process2.on("SIGINT", async () => {
1167
+ try {
1168
+ await server.close();
1169
+ } catch {
1170
+ }
1171
+ process2.exit(0);
1172
+ });
1173
+ } else if (isJson()) {
1174
+ emitJson({ ...result, compiled });
1175
+ }
1176
+ });
1120
1177
  program.parseAsync(process2.argv).catch((error) => {
1121
1178
  const message = error instanceof Error ? error.message : String(error);
1122
1179
  if (isJson()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.7.21",
3
+ "version": "0.7.23",
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.7.21",
41
+ "@swarmvaultai/engine": "0.7.23",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {