@swarmvaultai/cli 0.1.28 → 0.1.29

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 +10 -2
  2. package/dist/index.js +44 -37
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `@swarmvaultai/cli` is the global command-line entry point for SwarmVault.
4
4
 
5
- It gives you the `swarmvault` command for building a local-first knowledge vault from files, URLs, browser clips, saved query outputs, and guided exploration runs.
5
+ It gives you the `swarmvault` command for building a local-first knowledge vault from files, DOCX documents, URLs, browser clips, saved query outputs, and guided exploration runs.
6
6
 
7
7
  ## Install
8
8
 
@@ -70,6 +70,7 @@ Ingest a local file path, directory path, or URL into immutable source storage a
70
70
  - directory ingest respects `.gitignore` unless you pass `--no-gitignore`
71
71
  - repo-aware directory ingest records `repoRelativePath` and later compile writes `state/code-index.json`
72
72
  - URL ingest still localizes remote image references by default
73
+ - local file ingest supports markdown, text, HTML, PDF, DOCX, images, and code
73
74
  - code-aware directory ingest currently covers JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, PHP, Ruby, and PowerShell
74
75
 
75
76
  Useful flags:
@@ -101,7 +102,7 @@ Capture supported URLs through a normalized markdown layer before ingesting them
101
102
 
102
103
  ### `swarmvault inbox import [dir]`
103
104
 
104
- Import supported files from the configured inbox directory. This is meant for browser-clipper style markdown bundles and other capture workflows. Local image and asset references are preserved and copied into canonical storage under `raw/assets/`.
105
+ Import supported files from the configured inbox directory. This is meant for browser-clipper style markdown bundles, HTML clip bundles, and other capture workflows. Local image and asset references are preserved and copied into canonical storage under `raw/assets/`.
105
106
 
106
107
  ### `swarmvault compile [--approve]`
107
108
 
@@ -277,6 +278,13 @@ Agent target mapping:
277
278
  - `copilot` writes `.github/copilot-instructions.md` plus `AGENTS.md`
278
279
  - `cursor` writes `.cursor/rules/swarmvault.mdc`
279
280
 
281
+ Hook semantics:
282
+
283
+ - `claude --hook` writes `.claude/settings.json` plus `.claude/hooks/swarmvault-graph-first.js` and adds model-visible advisory context through structured hook JSON
284
+ - `gemini --hook` writes `.gemini/settings.json` plus `.gemini/hooks/swarmvault-graph-first.js` and stays advisory/model-visible
285
+ - `opencode --hook` writes `.opencode/plugins/swarmvault-graph-first.js` and stays advisory/log-only
286
+ - `copilot --hook` writes `.github/hooks/swarmvault-graph-first.json` plus `.github/hooks/swarmvault-graph-first.js` and remains decision-based rather than advisory
287
+
280
288
  `aider` is intentionally file/config-based in this release rather than hook-based.
281
289
 
282
290
  ## Provider Configuration
package/dist/index.js CHANGED
@@ -216,11 +216,16 @@ program.name("swarmvault").description("SwarmVault is a local-first LLM wiki com
216
216
  function readCliVersion() {
217
217
  try {
218
218
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
219
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.28";
219
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.29";
220
220
  } catch {
221
- return "0.1.28";
221
+ return "0.1.29";
222
222
  }
223
223
  }
224
+ function parsePositiveInt(value, fallback) {
225
+ if (value === void 0) return fallback;
226
+ const parsed = Number.parseInt(value, 10);
227
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
228
+ }
224
229
  function isJson() {
225
230
  return program.opts().json === true;
226
231
  }
@@ -273,8 +278,8 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
273
278
  });
274
279
  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("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
275
280
  async (input, options) => {
276
- const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? Number.parseInt(options.maxAssetSize, 10) : void 0;
277
- const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? Number.parseInt(options.maxFiles, 10) : void 0;
281
+ const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
282
+ const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
278
283
  const extractClasses = [
279
284
  "first_party",
280
285
  ...options.includeThirdParty ? ["third_party"] : [],
@@ -283,11 +288,11 @@ program.command("ingest").description("Ingest a local file path, directory path,
283
288
  ];
284
289
  const commonOptions = {
285
290
  includeAssets: options.includeAssets,
286
- maxAssetSize: Number.isFinite(maxAssetSize) ? maxAssetSize : void 0,
291
+ maxAssetSize,
287
292
  repoRoot: options.repoRoot,
288
293
  include: options.include,
289
294
  exclude: options.exclude,
290
- maxFiles: Number.isFinite(maxFiles) ? maxFiles : void 0,
295
+ maxFiles,
291
296
  gitignore: options.gitignore,
292
297
  extractClasses
293
298
  };
@@ -366,10 +371,10 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
366
371
  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(
367
372
  new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
368
373
  ).action(async (question, options) => {
369
- const stepCount = Number.parseInt(options.steps ?? "3", 10);
374
+ const stepCount = parsePositiveInt(options.steps, 3);
370
375
  const result = await exploreVault(process2.cwd(), {
371
376
  question,
372
- steps: Number.isFinite(stepCount) ? stepCount : 3,
377
+ steps: stepCount,
373
378
  format: options.format
374
379
  });
375
380
  if (isJson()) {
@@ -410,7 +415,7 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
410
415
  });
411
416
  var graph = program.command("graph").description("Graph-related commands.");
412
417
  graph.command("serve").description("Serve the local graph viewer.").option("--port <port>", "Port override").action(async (options) => {
413
- const port = options.port ? Number.parseInt(options.port, 10) : void 0;
418
+ const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
414
419
  const server = await startGraphServer(process2.cwd(), port);
415
420
  if (isJson()) {
416
421
  emitJson({ port: server.port, url: `http://localhost:${server.port}` });
@@ -418,7 +423,10 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
418
423
  log(`Graph viewer running at http://localhost:${server.port}`);
419
424
  }
420
425
  process2.on("SIGINT", async () => {
421
- await server.close();
426
+ try {
427
+ await server.close();
428
+ } catch {
429
+ }
422
430
  process2.exit(0);
423
431
  });
424
432
  });
@@ -441,10 +449,10 @@ graph.command("export").description("Export the graph as HTML, SVG, GraphML, or
441
449
  }
442
450
  });
443
451
  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) => {
444
- const budget = options.budget ? Number.parseInt(options.budget, 10) : void 0;
452
+ const budget = options.budget ? parsePositiveInt(options.budget, 0) || void 0 : void 0;
445
453
  const result = await queryGraphVault(process2.cwd(), question, {
446
454
  traversal: options.dfs ? "dfs" : "bfs",
447
- budget: Number.isFinite(budget) ? budget : void 0
455
+ budget
448
456
  });
449
457
  if (isJson()) {
450
458
  emitJson(result);
@@ -469,8 +477,8 @@ graph.command("explain").description("Explain a graph node, its page, community,
469
477
  log(result.summary);
470
478
  });
471
479
  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) => {
472
- const limit = Number.parseInt(options.limit ?? "10", 10);
473
- const result = await listGodNodes(process2.cwd(), Number.isFinite(limit) ? limit : 10);
480
+ const limit = parsePositiveInt(options.limit, 10);
481
+ const result = await listGodNodes(process2.cwd(), limit);
474
482
  if (isJson()) {
475
483
  emitJson(result);
476
484
  return;
@@ -555,12 +563,12 @@ candidate.command("archive").description("Archive a candidate by removing it fro
555
563
  }
556
564
  });
557
565
  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) => {
558
- const debounceMs = Number.parseInt(options.debounce ?? "900", 10);
566
+ const debounceMs = parsePositiveInt(options.debounce, 900);
559
567
  if (options.once) {
560
568
  const result = await runWatchCycle(process2.cwd(), {
561
569
  lint: options.lint ?? false,
562
570
  repo: options.repo ?? false,
563
- debounceMs: Number.isFinite(debounceMs) ? debounceMs : 900
571
+ debounceMs
564
572
  });
565
573
  if (isJson()) {
566
574
  emitJson(result);
@@ -575,7 +583,7 @@ var watch = program.command("watch").description("Watch the inbox directory and
575
583
  const controller = await watchVault(process2.cwd(), {
576
584
  lint: options.lint ?? false,
577
585
  repo: options.repo ?? false,
578
- debounceMs: Number.isFinite(debounceMs) ? debounceMs : 900
586
+ debounceMs
579
587
  });
580
588
  if (isJson()) {
581
589
  emitJson({ status: "watching", inboxDir: paths.inboxDir, repo: options.repo ?? false });
@@ -583,23 +591,14 @@ var watch = program.command("watch").description("Watch the inbox directory and
583
591
  log(`Watching inbox${options.repo ? " and tracked repos" : ""} for changes. Press Ctrl+C to stop.`);
584
592
  }
585
593
  process2.on("SIGINT", async () => {
586
- await controller.close();
594
+ try {
595
+ await controller.close();
596
+ } catch {
597
+ }
587
598
  process2.exit(0);
588
599
  });
589
600
  });
590
- watch.command("status").description("Show the latest watch run plus pending semantic refresh entries.").action(async () => {
591
- const result = await getWatchStatus(process2.cwd());
592
- if (isJson()) {
593
- emitJson(result);
594
- return;
595
- }
596
- log(`Watched repo roots: ${result.watchedRepoRoots.length}`);
597
- log(`Pending semantic refresh: ${result.pendingSemanticRefresh.length}`);
598
- for (const entry of result.pendingSemanticRefresh.slice(0, 8)) {
599
- log(`- ${entry.changeType} ${entry.path}`);
600
- }
601
- });
602
- program.command("watch-status").description("Show the latest watch run plus pending semantic refresh entries.").action(async () => {
601
+ async function showWatchStatus() {
603
602
  const result = await getWatchStatus(process2.cwd());
604
603
  if (isJson()) {
605
604
  emitJson(result);
@@ -610,7 +609,9 @@ program.command("watch-status").description("Show the latest watch run plus pend
610
609
  for (const entry of result.pendingSemanticRefresh.slice(0, 8)) {
611
610
  log(`- ${entry.changeType} ${entry.path}`);
612
611
  }
613
- });
612
+ }
613
+ watch.command("status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
614
+ program.command("watch-status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
614
615
  var hook = program.command("hook").description("Install local git hooks that keep tracked repos and the vault in sync.");
615
616
  hook.command("install").description("Install post-commit and post-checkout hooks for the nearest git repository.").action(async () => {
616
617
  const status = await installGitHooks(process2.cwd());
@@ -670,15 +671,18 @@ schedule.command("run").description("Run one configured schedule job immediately
670
671
  );
671
672
  });
672
673
  schedule.command("serve").description("Run the local schedule loop.").option("--poll <ms>", "Polling interval in milliseconds", "30000").action(async (options) => {
673
- const pollMs = Number.parseInt(options.poll ?? "30000", 10);
674
- const controller = await serveSchedules(process2.cwd(), Number.isFinite(pollMs) ? pollMs : 3e4);
674
+ const pollMs = parsePositiveInt(options.poll, 3e4);
675
+ const controller = await serveSchedules(process2.cwd(), pollMs);
675
676
  if (isJson()) {
676
- emitJson({ status: "serving", pollMs: Number.isFinite(pollMs) ? pollMs : 3e4 });
677
+ emitJson({ status: "serving", pollMs });
677
678
  } else {
678
679
  log("Serving schedules. Press Ctrl+C to stop.");
679
680
  }
680
681
  process2.on("SIGINT", async () => {
681
- await controller.close();
682
+ try {
683
+ await controller.close();
684
+ } catch {
685
+ }
682
686
  process2.exit(0);
683
687
  });
684
688
  });
@@ -689,7 +693,10 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
689
693
  }
690
694
  const controller = await startMcpServer(process2.cwd());
691
695
  process2.on("SIGINT", async () => {
692
- await controller.close();
696
+ try {
697
+ await controller.close();
698
+ } catch {
699
+ }
693
700
  process2.exit(0);
694
701
  });
695
702
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -43,7 +43,7 @@
43
43
  "typecheck": "tsc --noEmit"
44
44
  },
45
45
  "dependencies": {
46
- "@swarmvaultai/engine": "0.1.28",
46
+ "@swarmvaultai/engine": "0.1.29",
47
47
  "commander": "^14.0.1"
48
48
  },
49
49
  "devDependencies": {