@swarmvaultai/cli 0.8.0 → 0.10.0

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/dist/index.js +93 -30
  2. package/package.json +9 -8
  3. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -14,6 +14,8 @@ import {
14
14
  benchmarkVault,
15
15
  blastRadiusVault,
16
16
  compileVault,
17
+ consolidateVault,
18
+ createSupersessionEdge,
17
19
  deleteManagedSource,
18
20
  explainGraphVault,
19
21
  exploreVault,
@@ -278,9 +280,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
278
280
  function readCliVersion() {
279
281
  try {
280
282
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
281
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.8.0";
283
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.10.0";
282
284
  } catch {
283
- return "0.8.0";
285
+ return "0.10.0";
284
286
  }
285
287
  }
286
288
  function parsePositiveInt(value, fallback) {
@@ -306,6 +308,18 @@ function slugForCli(value) {
306
308
  function isJson() {
307
309
  return activeCommand?.opts().json === true || program.opts().json === true;
308
310
  }
311
+ function summarizeRedactions(redactions) {
312
+ if (!redactions || redactions.length === 0) {
313
+ return void 0;
314
+ }
315
+ const totalMatches = redactions.reduce((total, entry) => total + entry.matches.reduce((sum, match) => sum + match.count, 0), 0);
316
+ if (totalMatches === 0) {
317
+ return void 0;
318
+ }
319
+ const secretsLabel = totalMatches === 1 ? "secret" : "secrets";
320
+ const sourceLabel = redactions.length === 1 ? "source" : "sources";
321
+ return `Redacted ${totalMatches} ${secretsLabel} across ${redactions.length} ${sourceLabel} (run --no-redact to disable).`;
322
+ }
309
323
  function emitJson(data) {
310
324
  process2.stdout.write(`${JSON.stringify(data)}
311
325
  `);
@@ -424,20 +438,29 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
424
438
  program.command("init").description("Initialize a SwarmVault workspace in the current directory.").option("--obsidian", "Generate a minimal .obsidian workspace alongside the vault", false).option(
425
439
  "--profile <profile>",
426
440
  "Starter workspace profile or comma-separated preset list (for example: personal-research or reader,timeline)"
441
+ ).option(
442
+ "--lite",
443
+ "Minimal LLM-Wiki starter (raw/, wiki/, wiki/index.md, wiki/log.md, swarmvault.schema.md) without config, state, or agent installs",
444
+ false
427
445
  ).action(async (options) => {
428
- await initVault(process2.cwd(), { obsidian: options.obsidian ?? false, profile: options.profile });
446
+ await initVault(process2.cwd(), {
447
+ obsidian: options.obsidian ?? false,
448
+ profile: options.profile,
449
+ lite: options.lite ?? false
450
+ });
429
451
  if (isJson()) {
430
452
  emitJson({
431
453
  status: "initialized",
432
454
  rootDir: process2.cwd(),
433
455
  obsidian: options.obsidian ?? false,
434
- profile: options.profile ?? "default"
456
+ profile: options.profile ?? "default",
457
+ lite: options.lite ?? false
435
458
  });
436
459
  } else {
437
- log("Initialized SwarmVault workspace.");
460
+ log(options.lite ? "Initialized SwarmVault lite workspace." : "Initialized SwarmVault workspace.");
438
461
  }
439
462
  });
440
- 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("--review", "Stage a source review artifact after ingest and compile", false).option("--guide", "Stage a guided source integration bundle after ingest and compile (default: from config)").option("--no-guide", "Skip guided mode even if enabled in config").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").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").option("--resume <run-id>", "Re-run only the failed files from a prior ingest run id").option("--commit", "Auto-commit wiki and state changes after ingest").action(
463
+ 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("--review", "Stage a source review artifact after ingest and compile", false).option("--guide", "Stage a guided source integration bundle after ingest and compile (default: from config)").option("--no-guide", "Skip guided mode even if enabled in config").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").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").option("--resume <run-id>", "Re-run only the failed files from a prior ingest run id").option("--commit", "Auto-commit wiki and state changes after ingest").option("--no-redact", "Skip PII/secret redaction for this run (overrides config)").action(
441
464
  async (input, options) => {
442
465
  const guideAnswers = readGuideAnswersFile(options.answersFile);
443
466
  const vaultConfig = await loadVaultConfig(process2.cwd()).catch(() => null);
@@ -459,7 +482,8 @@ program.command("ingest").description("Ingest a local file path, directory path,
459
482
  maxFiles,
460
483
  gitignore: options.gitignore,
461
484
  extractClasses,
462
- resume: options.resume
485
+ resume: options.resume,
486
+ redact: options.redact
463
487
  };
464
488
  const directoryResult = !/^https?:\/\//i.test(input) ? await import("fs/promises").then(
465
489
  (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
@@ -519,6 +543,10 @@ program.command("ingest").description("Ingest a local file path, directory path,
519
543
  } else if (completedGuide2?.guidePath) {
520
544
  log(`Staged guided session at ${completedGuide2.guidePath}.`);
521
545
  }
546
+ const redactionLine = summarizeRedactions(directoryResult.redactions);
547
+ if (redactionLine) {
548
+ log(redactionLine);
549
+ }
522
550
  }
523
551
  if (options.commit) {
524
552
  const msg = await autoCommitWikiChanges(process2.cwd(), "ingest", input, { force: true });
@@ -558,6 +586,10 @@ program.command("ingest").description("Ingest a local file path, directory path,
558
586
  } else if (completedGuide?.guidePath) {
559
587
  log(`Staged guided session at ${completedGuide.guidePath}.`);
560
588
  }
589
+ const redactionLine = summarizeRedactions(ingest.redactions);
590
+ if (redactionLine) {
591
+ log(redactionLine);
592
+ }
561
593
  }
562
594
  if (options.commit) {
563
595
  const msg = await autoCommitWikiChanges(process2.cwd(), "ingest", input, { force: true });
@@ -565,10 +597,11 @@ program.command("ingest").description("Ingest a local file path, directory path,
565
597
  }
566
598
  }
567
599
  );
568
- program.command("add").description("Capture supported URLs into normalized markdown before ingesting them.").argument("<input>", "Supported URL or bare arXiv id").option("--author <name>", "Human author or curator for this capture").option("--contributor <name>", "Additional contributor metadata for this capture").action(async (input, options) => {
600
+ program.command("add").description("Capture supported URLs into normalized markdown before ingesting them.").argument("<input>", "Supported URL or bare arXiv id").option("--author <name>", "Human author or curator for this capture").option("--contributor <name>", "Additional contributor metadata for this capture").option("--no-redact", "Skip PII/secret redaction for this capture (overrides config)").action(async (input, options) => {
569
601
  const result = await addInput(process2.cwd(), input, {
570
602
  author: options.author,
571
- contributor: options.contributor
603
+ contributor: options.contributor,
604
+ redact: options.redact
572
605
  });
573
606
  if (isJson()) {
574
607
  emitJson(result);
@@ -725,14 +758,28 @@ program.command("compile").description("Compile manifests into wiki pages, graph
725
758
  }
726
759
  await maybeEmitHeuristicNotice(["compile"]);
727
760
  });
728
- 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").option("--commit", "Auto-commit wiki changes after query").addOption(
761
+ program.command("consolidate").description("Roll working-tier insights up into episodic, semantic, and procedural tiers.").option("--dry-run", "Return decisions without writing any files", false).action(async (options) => {
762
+ const result = await consolidateVault(process2.cwd(), { dryRun: options.dryRun ?? false });
763
+ if (isJson()) {
764
+ emitJson(result);
765
+ return;
766
+ }
767
+ log(
768
+ `${options.dryRun ? "Would consolidate" : "Consolidated"} ${result.newPages.length} new tier page(s); ${result.promoted.length} promotion(s).`
769
+ );
770
+ for (const decision of result.decisions) {
771
+ log(decision);
772
+ }
773
+ });
774
+ 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").option("--commit", "Auto-commit wiki changes after query").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").addOption(
729
775
  new Option("--format <format>", "Output format").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
730
776
  ).action(
731
777
  async (question, options) => {
732
778
  const result = await queryVault(process2.cwd(), {
733
779
  question,
734
780
  save: options.save ?? true,
735
- format: options.format
781
+ format: options.format,
782
+ gapFill: options.gapFill ?? false
736
783
  });
737
784
  if (isJson()) {
738
785
  emitJson(result);
@@ -749,23 +796,26 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
749
796
  await maybeEmitHeuristicNotice(["query"]);
750
797
  }
751
798
  );
752
- 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(
799
+ 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").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.exploreProvider).").addOption(
753
800
  new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
754
- ).action(async (question, options) => {
755
- const stepCount = parsePositiveInt(options.steps, 3);
756
- const result = await exploreVault(process2.cwd(), {
757
- question,
758
- steps: stepCount,
759
- format: options.format
760
- });
761
- if (isJson()) {
762
- emitJson(result);
763
- } else {
764
- log(`Exploration hub saved to ${result.hubPath}`);
765
- log(`Completed ${result.stepCount} step(s).`);
801
+ ).action(
802
+ async (question, options) => {
803
+ const stepCount = parsePositiveInt(options.steps, 3);
804
+ const result = await exploreVault(process2.cwd(), {
805
+ question,
806
+ steps: stepCount,
807
+ format: options.format,
808
+ gapFill: options.gapFill ?? false
809
+ });
810
+ if (isJson()) {
811
+ emitJson(result);
812
+ } else {
813
+ log(`Exploration hub saved to ${result.hubPath}`);
814
+ log(`Completed ${result.stepCount} step(s).`);
815
+ }
816
+ await maybeEmitHeuristicNotice(["explore"]);
766
817
  }
767
- await maybeEmitHeuristicNotice(["explore"]);
768
- });
818
+ );
769
819
  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) => {
770
820
  const result = await benchmarkVault(process2.cwd(), {
771
821
  questions: options.question
@@ -784,13 +834,15 @@ program.command("benchmark").description("Measure graph-guided context reduction
784
834
  }
785
835
  }
786
836
  });
787
- program.command("lint").description("Run anti-drift and wiki-health checks.").option("--deep", "Run LLM-powered advisory lint (default: from config)").option("--no-deep", "Skip deep lint even if enabled in config").option("--web", "Augment deep lint with configured web search", false).option("--conflicts", "Filter to contradiction findings only", false).action(async (options) => {
837
+ program.command("lint").description("Run anti-drift and wiki-health checks.").option("--deep", "Run LLM-powered advisory lint (default: from config)").option("--no-deep", "Skip deep lint even if enabled in config").option("--web", "Augment deep lint with configured web search", false).option("--conflicts", "Filter to contradiction findings only", false).option("--decay", "Filter to decay-related findings only", false).option("--tiers", "Filter to consolidation-tier findings only", false).action(async (options) => {
788
838
  const lintConfig = await loadVaultConfig(process2.cwd()).catch(() => null);
789
- const deepEnabled = options.deep ?? lintConfig?.config.profile.deepLintDefault ?? false;
839
+ const deepEnabled = options.decay || options.tiers ? false : options.deep ?? lintConfig?.config.profile.deepLintDefault ?? false;
790
840
  const findings = await lintVault(process2.cwd(), {
791
841
  deep: deepEnabled,
792
842
  web: options.web ?? false,
793
- conflicts: options.conflicts ?? false
843
+ conflicts: options.conflicts ?? false,
844
+ decay: options.decay ?? false,
845
+ tiers: options.tiers ?? false
794
846
  });
795
847
  if (isJson()) {
796
848
  emitJson(findings);
@@ -960,6 +1012,14 @@ graph.command("blast").description("Show the blast radius of changing a file or
960
1012
  log(` ${" ".repeat(mod.depth - 1)}${mod.label} (depth ${mod.depth})`);
961
1013
  }
962
1014
  });
1015
+ graph.command("supersession").description("Record that one page has been replaced by another (writes a superseded_by edge).").argument("<pageId>", "Page id or path of the older page").argument("<replacedById>", "Page id or path of the replacement page").action(async (pageId, replacedById) => {
1016
+ const result = await createSupersessionEdge(process2.cwd(), pageId, replacedById);
1017
+ if (isJson()) {
1018
+ emitJson(result);
1019
+ return;
1020
+ }
1021
+ log(`Superseded ${result.oldPageId} by ${result.newPageId} (edge ${result.edgeId}).`);
1022
+ });
963
1023
  var review = program.command("review").description("Review staged compile approval bundles.");
964
1024
  review.command("list").description("List staged approval bundles and their resolution status.").action(async () => {
965
1025
  const approvals = await listApprovals(process2.cwd());
@@ -1214,7 +1274,10 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
1214
1274
  process2.exit(0);
1215
1275
  });
1216
1276
  });
1217
- 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(
1277
+ program.command("install").description("Install SwarmVault instructions for an agent in the current project.").requiredOption(
1278
+ "--agent <agent>",
1279
+ "codex, claude, cursor, goose, pi, gemini, opencode, aider, copilot, trae, claw, droid, kiro, hermes, antigravity, or vscode"
1280
+ ).option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).action(
1218
1281
  async (options) => {
1219
1282
  const hookCapableAgents = /* @__PURE__ */ new Set(["claude", "opencode", "gemini", "copilot"]);
1220
1283
  if (options.hook && !hookCapableAgents.has(options.agent)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,18 +37,19 @@
37
37
  "engines": {
38
38
  "node": ">=24.0.0"
39
39
  },
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format esm --dts",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit",
44
+ "prepublishOnly": "node ../../scripts/check-release-sync.mjs && node ../../scripts/check-published-manifests.mjs"
45
+ },
40
46
  "dependencies": {
41
- "@swarmvaultai/engine": "0.8.0",
47
+ "@swarmvaultai/engine": "0.10.0",
42
48
  "commander": "^14.0.1"
43
49
  },
44
50
  "devDependencies": {
45
51
  "@types/node": "^24.6.0",
46
52
  "tsup": "^8.5.0",
47
53
  "vitest": "^3.2.4"
48
- },
49
- "scripts": {
50
- "build": "tsup src/index.ts --format esm --dts",
51
- "test": "vitest run",
52
- "typecheck": "tsc --noEmit"
53
54
  }
54
- }
55
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 SwarmVault
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.