@swarmvaultai/cli 1.0.1 → 1.2.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/index.js +113 -12
  4. package/package.json +8 -9
package/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
package/README.md CHANGED
@@ -39,6 +39,7 @@ swarmvault ingest ./repo
39
39
  swarmvault add https://arxiv.org/abs/2401.12345
40
40
  swarmvault compile --max-tokens 120000
41
41
  swarmvault diff
42
+ swarmvault graph share --post
42
43
  swarmvault benchmark
43
44
  swarmvault query "What keeps recurring?" --commit
44
45
  swarmvault query "Turn this into slides" --format slides
@@ -85,6 +86,7 @@ Quick-start a scratch vault from a local directory in one command.
85
86
  - initializes the current directory as a SwarmVault workspace
86
87
  - ingests the supplied directory as local sources
87
88
  - compiles the vault immediately
89
+ - writes `wiki/graph/share-card.md` and prints the path
88
90
  - starts `graph serve` unless you pass `--no-serve`
89
91
  - respects `--port` when you want a specific viewer port
90
92
 
@@ -97,6 +99,7 @@ Create a temporary sample vault with bundled sources, compile it immediately, an
97
99
  - writes the demo vault under the system temp directory
98
100
  - requires no API keys or extra setup
99
101
  - is the fastest way to inspect the full init + ingest + compile + graph workflow on a clean machine
102
+ - writes `wiki/graph/share-card.md` inside the demo vault
100
103
  - respects `--port` when you want a specific viewer port
101
104
 
102
105
  ### `swarmvault diff`
@@ -207,6 +210,7 @@ Compile the current manifests into:
207
210
  - generated markdown in `wiki/`
208
211
  - structured graph data in `state/graph.json`
209
212
  - local search data in `state/search.sqlite`
213
+ - a share card at `wiki/graph/share-card.md`
210
214
 
211
215
  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.
212
216
 
@@ -366,6 +370,15 @@ Inspect graph metadata, community membership, neighbors, provenance, and group-p
366
370
 
367
371
  List the most connected bridge-heavy nodes in the current graph.
368
372
 
373
+ ### `swarmvault graph share [--post]`
374
+
375
+ Print a shareable summary of the compiled graph.
376
+
377
+ - default output is the same markdown shape written to `wiki/graph/share-card.md`
378
+ - `--post` prints only the concise social-post text
379
+ - `--json` emits the structured share artifact for automation
380
+ - useful immediately after `swarmvault scan`, `swarmvault demo`, or a normal compile
381
+
369
382
  ### `swarmvault graph blast <target> [--depth <n>]`
370
383
 
371
384
  Trace the reverse-import blast radius of changing a file or module.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/index.ts
4
4
  import { readFileSync } from "fs";
5
5
  import { readFile as readFile2 } from "fs/promises";
6
+ import path2 from "path";
6
7
  import process2 from "process";
7
8
  import { createInterface } from "readline/promises";
8
9
  import {
@@ -14,10 +15,12 @@ import {
14
15
  autoCommitWikiChanges,
15
16
  benchmarkVault,
16
17
  blastRadiusVault,
18
+ buildGraphShareArtifact,
17
19
  compileVault,
18
20
  consolidateVault,
19
21
  createSupersessionEdge,
20
22
  deleteManagedSource,
23
+ downloadWhisperModel,
21
24
  explainGraphVault,
22
25
  exploreVault,
23
26
  exportGraphFormat,
@@ -52,9 +55,12 @@ import {
52
55
  queryGraphVault,
53
56
  queryVault,
54
57
  readApproval,
58
+ readGraphReport,
59
+ registerLocalWhisperProvider,
55
60
  rejectApproval,
56
61
  reloadManagedSources,
57
62
  removeWatchedRoot,
63
+ renderGraphShareMarkdown,
58
64
  resumeSourceSession,
59
65
  reviewManagedSource,
60
66
  reviewSourceScope,
@@ -65,6 +71,7 @@ import {
65
71
  serveSchedules,
66
72
  startGraphServer,
67
73
  startMcpServer,
74
+ summarizeLocalWhisperSetup,
68
75
  uninstallGitHooks,
69
76
  watchVault
70
77
  } from "@swarmvaultai/engine";
@@ -284,9 +291,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
284
291
  function readCliVersion() {
285
292
  try {
286
293
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
287
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "1.0.1";
294
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "1.2.0";
288
295
  } catch {
289
- return "1.0.1";
296
+ return "1.2.0";
290
297
  }
291
298
  }
292
299
  function parsePositiveInt(value, fallback) {
@@ -968,6 +975,22 @@ graph.command("export").description(
968
975
  }
969
976
  }
970
977
  );
978
+ graph.command("share").description("Print a shareable summary of the compiled graph.").option("--post", "Print only the short social post text", false).action(async (options) => {
979
+ const { paths } = await loadVaultConfig(process2.cwd());
980
+ const raw = await readFile2(paths.graphPath, "utf-8");
981
+ const graph2 = JSON.parse(raw);
982
+ const report = await readGraphReport(process2.cwd());
983
+ const artifact = buildGraphShareArtifact({
984
+ graph: graph2,
985
+ report,
986
+ vaultName: path2.basename(paths.rootDir)
987
+ });
988
+ if (isJson()) {
989
+ emitJson(artifact);
990
+ return;
991
+ }
992
+ log(options.post ? artifact.shortPost : renderGraphShareMarkdown(artifact));
993
+ });
971
994
  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) => {
972
995
  const budget = options.budget ? parsePositiveInt(options.budget, 0) || void 0 : void 0;
973
996
  const result = await queryGraphVault(process2.cwd(), question, {
@@ -1141,6 +1164,77 @@ candidate.command("preview-scores").description("Show promotion scores for stage
1141
1164
  log(`${verdict} ${decision.pageId} score=${decision.score.toFixed(2)} ${decision.reasons.join("; ")}`);
1142
1165
  }
1143
1166
  });
1167
+ var provider = program.command("provider").description("Configure provider adapters.");
1168
+ provider.command("setup").description("Interactive setup for a provider (currently: local-whisper). Checks for required binaries and downloads models.").option("--local-whisper", "Set up the local Whisper (whisper.cpp) provider", false).option("--model <model>", "Whisper model to install (e.g. tiny.en, base.en, small.en)", "base.en").option("--apply", "Skip confirmation prompts and download/install automatically", false).option("--set-audio-provider", "Force tasks.audioProvider to local-whisper even if another provider is already configured", false).action(async (options) => {
1169
+ if (!options.localWhisper) {
1170
+ throw new Error("Specify a provider to set up (currently: --local-whisper).");
1171
+ }
1172
+ const modelName = (options.model ?? "base.en").trim();
1173
+ if (!modelName) {
1174
+ throw new Error("Model name cannot be empty.");
1175
+ }
1176
+ const status = await summarizeLocalWhisperSetup({ modelName });
1177
+ if (isJson()) {
1178
+ emitJson({ ...status, apply: Boolean(options.apply), configWrite: null });
1179
+ return;
1180
+ }
1181
+ log(`whisper.cpp binary: ${status.binary.found ? status.binary.path : "NOT FOUND"}`);
1182
+ if (!status.binary.found) {
1183
+ log(status.binary.installHint);
1184
+ log("Re-run once whisper.cpp is on $PATH.");
1185
+ process2.exitCode = 1;
1186
+ return;
1187
+ }
1188
+ log(`Model "${modelName}": ${status.model.exists ? status.model.expectedPath : "missing"}`);
1189
+ if (!status.model.exists) {
1190
+ const sizeHint = status.model.approximateSize ? ` (~${status.model.approximateSize})` : "";
1191
+ log(`Download plan: ${status.model.downloadUrl}${sizeHint} -> ${status.model.expectedPath}`);
1192
+ const proceed = options.apply === true || await confirmInteractive(`Download ggml-${modelName}.bin now?`);
1193
+ if (!proceed) {
1194
+ log("Skipped download. Run with --apply (or confirm y) to download.");
1195
+ process2.exitCode = 1;
1196
+ return;
1197
+ }
1198
+ const downloaded = await downloadWhisperModel({
1199
+ modelName,
1200
+ onProgress: (progress) => {
1201
+ if (progress.totalBytes) {
1202
+ const percent = Math.floor(progress.downloadedBytes / progress.totalBytes * 100);
1203
+ process2.stderr.write(`\r[swarmvault] downloading ggml-${modelName}.bin: ${percent}%`);
1204
+ }
1205
+ }
1206
+ });
1207
+ process2.stderr.write("\n");
1208
+ log(`Downloaded ${downloaded.bytes} bytes to ${downloaded.path}.`);
1209
+ }
1210
+ const registration = await registerLocalWhisperProvider({
1211
+ rootDir: process2.cwd(),
1212
+ model: modelName,
1213
+ setAsAudioProvider: options.setAudioProvider ? true : void 0
1214
+ });
1215
+ if (registration.providerWasAdded) {
1216
+ log(`Registered provider "local-whisper" in ${registration.configPath}.`);
1217
+ } else if (registration.providerWasUpdated) {
1218
+ log(`Updated existing "local-whisper" provider entry in ${registration.configPath}.`);
1219
+ } else {
1220
+ log(`Provider "local-whisper" already configured in ${registration.configPath}.`);
1221
+ }
1222
+ if (registration.audioProviderSet) {
1223
+ log(`Set tasks.audioProvider = "local-whisper".`);
1224
+ } else if (registration.previousAudioProvider && registration.previousAudioProvider !== "local-whisper") {
1225
+ log(`Left tasks.audioProvider = "${registration.previousAudioProvider}" untouched (use --set-audio-provider to override).`);
1226
+ }
1227
+ });
1228
+ async function confirmInteractive(message) {
1229
+ if (!process2.stdin.isTTY) return false;
1230
+ const rl = createInterface({ input: process2.stdin, output: process2.stderr });
1231
+ try {
1232
+ const answer = (await rl.question(`${message} [y/N] `)).trim().toLowerCase();
1233
+ return answer === "y" || answer === "yes";
1234
+ } finally {
1235
+ rl.close();
1236
+ }
1237
+ }
1144
1238
  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").option("--root <path>", "Watch this repo root instead of config/auto-discovery (repeat for multiple)", collectRepeated, []).action(async (options) => {
1145
1239
  const debounceMs = parsePositiveInt(options.debounce, 900);
1146
1240
  const overrideRoots = options.root && options.root.length > 0 ? options.root : void 0;
@@ -1357,15 +1451,15 @@ program.command("install").description("Install SwarmVault instructions for an a
1357
1451
  program.command("demo").description("Try SwarmVault with a bundled sample vault \u2014 zero config, zero API keys.").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").action(async (options) => {
1358
1452
  const { mkdtemp, writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
1359
1453
  const { tmpdir } = await import("os");
1360
- const path2 = await import("path");
1361
- const demoDir = await mkdtemp(path2.join(tmpdir(), "swarmvault-demo-"));
1454
+ const path3 = await import("path");
1455
+ const demoDir = await mkdtemp(path3.join(tmpdir(), "swarmvault-demo-"));
1362
1456
  if (!isJson()) {
1363
1457
  log(`Creating demo vault in ${demoDir}`);
1364
1458
  }
1365
- const rawDir = path2.join(demoDir, "raw", "sources");
1459
+ const rawDir = path3.join(demoDir, "raw", "sources");
1366
1460
  await mkdir2(rawDir, { recursive: true });
1367
1461
  await writeFile2(
1368
- path2.join(rawDir, "llm-wiki-pattern.md"),
1462
+ path3.join(rawDir, "llm-wiki-pattern.md"),
1369
1463
  [
1370
1464
  "# The LLM Wiki Pattern",
1371
1465
  "",
@@ -1394,7 +1488,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1394
1488
  ].join("\n")
1395
1489
  );
1396
1490
  await writeFile2(
1397
- path2.join(rawDir, "knowledge-graphs.md"),
1491
+ path3.join(rawDir, "knowledge-graphs.md"),
1398
1492
  [
1399
1493
  "# Knowledge Graphs for AI",
1400
1494
  "",
@@ -1427,7 +1521,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1427
1521
  ].join("\n")
1428
1522
  );
1429
1523
  await writeFile2(
1430
- path2.join(rawDir, "local-first-tools.md"),
1524
+ path3.join(rawDir, "local-first-tools.md"),
1431
1525
  [
1432
1526
  "# Local-First AI Tools",
1433
1527
  "",
@@ -1457,6 +1551,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1457
1551
  await ingestDirectory(demoDir, rawDir, {});
1458
1552
  await compileVault(demoDir, {});
1459
1553
  const { paths } = await loadVaultConfig(demoDir);
1554
+ const shareCardPath = path3.join(demoDir, "wiki", "graph", "share-card.md");
1460
1555
  let graphStats = "";
1461
1556
  try {
1462
1557
  const raw = await readFile2(paths.graphPath, "utf-8");
@@ -1474,17 +1569,19 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1474
1569
  log(" 3. Generated wiki pages with cross-references and a graph report");
1475
1570
  log("");
1476
1571
  log(`Vault location: ${demoDir}`);
1572
+ log(`Share card: ${shareCardPath}`);
1477
1573
  }
1478
1574
  if (options.serve !== false) {
1479
1575
  const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
1480
1576
  const server = await startGraphServer(demoDir, port, { full: false });
1481
1577
  if (isJson()) {
1482
- emitJson({ demoDir, graphStats: graphStats.trim(), port: server.port, url: `http://localhost:${server.port}` });
1578
+ emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath, port: server.port, url: `http://localhost:${server.port}` });
1483
1579
  } else {
1484
1580
  log(`Graph viewer running at http://localhost:${server.port}`);
1485
1581
  log("");
1486
1582
  log("Try next:");
1487
1583
  log(` cd ${demoDir}`);
1584
+ log(" swarmvault graph share --post");
1488
1585
  log(' swarmvault query "How does contradiction detection work?"');
1489
1586
  log(" swarmvault lint");
1490
1587
  }
@@ -1496,11 +1593,12 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1496
1593
  process2.exit(0);
1497
1594
  });
1498
1595
  } else if (isJson()) {
1499
- emitJson({ demoDir, graphStats: graphStats.trim() });
1596
+ emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath });
1500
1597
  } else {
1501
1598
  log("");
1502
1599
  log("Try next:");
1503
1600
  log(` cd ${demoDir}`);
1601
+ log(" swarmvault graph share --post");
1504
1602
  log(" swarmvault graph serve");
1505
1603
  log(' swarmvault query "How does contradiction detection work?"');
1506
1604
  }
@@ -1628,14 +1726,17 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
1628
1726
  log(`Ingested ${result.imported.length} file(s).`);
1629
1727
  }
1630
1728
  const compiled = await compileVault(rootDir, {});
1729
+ const shareCardPath = path2.join(rootDir, "wiki", "graph", "share-card.md");
1631
1730
  if (!isJson()) {
1632
1731
  log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
1732
+ log(`Share card: ${shareCardPath}`);
1733
+ log("Post text: swarmvault graph share --post");
1633
1734
  }
1634
1735
  if (options.serve !== false) {
1635
1736
  const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
1636
1737
  const server = await startGraphServer(rootDir, port, { full: false });
1637
1738
  if (isJson()) {
1638
- emitJson({ ...result, compiled, port: server.port, url: `http://localhost:${server.port}` });
1739
+ emitJson({ ...result, compiled, shareCardPath, port: server.port, url: `http://localhost:${server.port}` });
1639
1740
  } else {
1640
1741
  log(`Graph viewer running at http://localhost:${server.port}`);
1641
1742
  }
@@ -1647,7 +1748,7 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
1647
1748
  process2.exit(0);
1648
1749
  });
1649
1750
  } else if (isJson()) {
1650
- emitJson({ ...result, compiled });
1751
+ emitJson({ ...result, compiled, shareCardPath });
1651
1752
  }
1652
1753
  });
1653
1754
  function enableStructuredJsonOnSubcommands(command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,19 +37,18 @@
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
- },
46
40
  "dependencies": {
47
- "@swarmvaultai/engine": "1.0.1",
41
+ "@swarmvaultai/engine": "1.2.0",
48
42
  "commander": "^14.0.1"
49
43
  },
50
44
  "devDependencies": {
51
45
  "@types/node": "^24.6.0",
52
46
  "tsup": "^8.5.0",
53
47
  "vitest": "^3.2.4"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup src/index.ts --format esm --dts",
51
+ "test": "vitest run",
52
+ "typecheck": "tsc --noEmit"
54
53
  }
55
- }
54
+ }