@swarmvaultai/cli 0.7.25 → 0.7.26

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.
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
@@ -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, reStructuredText and DOCX documents, 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, audio transcripts, YouTube URLs, reStructuredText and DOCX documents, browser clips, saved query outputs, and guided exploration runs.
6
6
 
7
7
  ## Install
8
8
 
@@ -24,6 +24,7 @@ mkdir my-vault
24
24
  cd my-vault
25
25
  swarmvault init --obsidian --profile personal-research
26
26
  swarmvault init --obsidian --profile reader,timeline
27
+ swarmvault demo
27
28
  swarmvault source add https://github.com/karpathy/micrograd
28
29
  swarmvault source add https://example.com/docs/getting-started
29
30
  swarmvault source add ./exports/customer-call.srt --guide
@@ -32,9 +33,12 @@ swarmvault source list
32
33
  swarmvault source reload --all
33
34
  sed -n '1,120p' swarmvault.schema.md
34
35
  swarmvault ingest ./notes.md
36
+ swarmvault ingest ./customer-call.mp3
37
+ swarmvault ingest https://www.youtube.com/watch?v=dQw4w9WgXcQ
35
38
  swarmvault ingest ./repo
36
39
  swarmvault add https://arxiv.org/abs/2401.12345
37
40
  swarmvault compile --max-tokens 120000
41
+ swarmvault diff
38
42
  swarmvault benchmark
39
43
  swarmvault query "What keeps recurring?" --commit
40
44
  swarmvault query "Turn this into slides" --format slides
@@ -86,6 +90,23 @@ Quick-start a scratch vault from a local directory in one command.
86
90
 
87
91
  Use this when you want the fastest repo or docs-tree walkthrough without first deciding on managed-source registration.
88
92
 
93
+ ### `swarmvault demo [--port <port>] [--no-serve]`
94
+
95
+ Create a temporary sample vault with bundled sources, compile it immediately, and launch the graph viewer unless you pass `--no-serve`.
96
+
97
+ - writes the demo vault under the system temp directory
98
+ - requires no API keys or extra setup
99
+ - is the fastest way to inspect the full init + ingest + compile + graph workflow on a clean machine
100
+ - respects `--port` when you want a specific viewer port
101
+
102
+ ### `swarmvault diff`
103
+
104
+ Compare the current `state/graph.json` against the last committed graph in git.
105
+
106
+ - when a prior committed graph exists, prints added and removed nodes, pages, and edges
107
+ - when no git baseline exists, falls back to a summary of the current graph state
108
+ - supports `--json` for structured automation output
109
+
89
110
  ### `swarmvault source add|list|reload|review|guide|session|delete`
90
111
 
91
112
  Manage recurring source roots through a registry-backed workflow.
@@ -132,7 +153,8 @@ Ingest a local file path, directory path, or URL into immutable source storage a
132
153
  - repo-aware directory ingest records `repoRelativePath` and later compile writes `state/code-index.json`
133
154
  - use `source add` instead when the same local directory, public GitHub repo root, or docs hub should stay registered and reloadable
134
155
  - URL ingest still localizes remote image references by default
135
- - local file and archive ingest supports markdown, text, reStructuredText, HTML, PDF, Word, RTF, OpenDocument, EPUB, CSV/TSV, Excel, PowerPoint, Jupyter notebooks, BibTeX, Org-mode, AsciiDoc, transcripts, Slack exports, email, calendar, structured config/data, developer manifests, images, and code
156
+ - YouTube URLs short-circuit to direct transcript capture instead of generic HTML fetch
157
+ - local file and archive ingest supports markdown, text, reStructuredText, HTML, PDF, Word, RTF, OpenDocument, EPUB, CSV/TSV, Excel, PowerPoint, Jupyter notebooks, BibTeX, Org-mode, AsciiDoc, transcripts, Slack exports, email, calendar, audio, structured config/data, developer manifests, images, and code
136
158
  - add `--guide` when you want a resumable source session, source brief, source review, source guide, and approval-bundled canonical page edits when `profile.guidedSessionMode` is `canonical_review`, with `wiki/insights/` fallback for `insights_only`
137
159
  - set `profile.guidedIngestDefault: true` when guided mode should be the default for `ingest`, and use `--no-guide` to force a plain ingest for one run
138
160
  - code-aware directory ingest currently covers JavaScript, JSX, TypeScript, TSX, Bash/shell scripts, Python, Go, Rust, Java, Kotlin, Scala, Dart, Lua, Zig, C#, C, C++, PHP, Ruby, PowerShell, Elixir, OCaml, Objective-C, ReScript, Solidity, HTML, CSS, and Vue single-file components
@@ -157,6 +179,8 @@ Repo ingest defaults to `first_party` material. The extra `--include-*` flags op
157
179
 
158
180
  Large repo ingest now emits low-noise progress on materially large batches, and parser compatibility failures stay local to the affected source instead of aborting unrelated analysis.
159
181
 
182
+ Audio files use `tasks.audioProvider` when you configure a provider with `audio` capability. When no such provider is configured, SwarmVault still ingests the source and records an explicit extraction warning instead of failing. YouTube transcript ingest does not require a model provider.
183
+
160
184
  When `--commit` is set, SwarmVault stages `wiki/` and `state/` changes and creates a git commit when the vault root is inside a git worktree. Outside git, it becomes a no-op instead of failing.
161
185
 
162
186
  ### `swarmvault add <url>`
@@ -361,11 +385,13 @@ Export the current graph as one or more shareable formats:
361
385
  - `--graphml` for graph-tool interoperability
362
386
  - `--cypher` for Neo4j-style import scripts
363
387
  - `--json` for a deterministic machine-readable graph package
364
- - `--obsidian` for an Obsidian-friendly markdown vault with one note per node plus community notes
388
+ - `--obsidian` for an Obsidian-friendly markdown vault that preserves wiki folders, appends graph connections, emits orphan-node stubs and community notes, copies assets, and writes a minimal `.obsidian/` config
365
389
  - `--canvas` for an Obsidian canvas grouped by community
366
390
 
367
391
  You can combine multiple flags in one run to write several exports at once.
368
392
 
393
+ Set `graph.communityResolution` in `swarmvault.config.json` when you want to pin the Louvain clustering resolution used by graph reports and Obsidian community output instead of relying on the adaptive default.
394
+
369
395
  ### `swarmvault graph push neo4j`
370
396
 
371
397
  Push the compiled graph directly into Neo4j over Bolt/Aura instead of writing an intermediate file.
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync } from "fs";
5
+ import { readFile as readFile2 } from "fs/promises";
5
6
  import process2 from "process";
6
7
  import { createInterface } from "readline/promises";
7
8
  import {
@@ -23,6 +24,7 @@ import {
23
24
  exportObsidianVault,
24
25
  getGitHookStatus,
25
26
  getWatchStatus,
27
+ graphDiff,
26
28
  guideManagedSource,
27
29
  guideSourceScope,
28
30
  importInbox,
@@ -269,13 +271,14 @@ function normalizeVersion(version) {
269
271
  // src/index.ts
270
272
  var program = new Command();
271
273
  var CLI_VERSION = readCliVersion();
272
- program.name("swarmvault").description("SwarmVault is a local-first knowledge compiler with graph outputs and optional provider-backed workflows.").version(CLI_VERSION).option("--json", "Emit structured JSON output", false);
274
+ var activeCommand = null;
275
+ program.name("swarmvault").description("SwarmVault is a local-first knowledge compiler with graph outputs and optional provider-backed workflows.").version(CLI_VERSION).enablePositionalOptions().option("--json", "Emit structured JSON output", false);
273
276
  function readCliVersion() {
274
277
  try {
275
278
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
276
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.25";
279
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.26";
277
280
  } catch {
278
- return "0.7.25";
281
+ return "0.7.26";
279
282
  }
280
283
  }
281
284
  function parsePositiveInt(value, fallback) {
@@ -299,7 +302,7 @@ function slugForCli(value) {
299
302
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "source";
300
303
  }
301
304
  function isJson() {
302
- return program.opts().json === true;
305
+ return activeCommand?.opts().json === true || program.opts().json === true;
303
306
  }
304
307
  function emitJson(data) {
305
308
  process2.stdout.write(`${JSON.stringify(data)}
@@ -789,7 +792,7 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
789
792
  log(`[${finding.severity}] ${finding.code}: ${finding.message}${finding.pagePath ? ` (${finding.pagePath})` : ""}`);
790
793
  }
791
794
  });
792
- var graph = program.command("graph").description("Graph-related commands.");
795
+ var graph = program.command("graph").description("Graph-related commands.").enablePositionalOptions();
793
796
  var graphPush = graph.command("push").description("Push the compiled graph into external sinks.");
794
797
  graphPush.command("neo4j").description("Push the compiled graph directly into Neo4j over Bolt/Aura.").option("--uri <bolt-uri>", "Neo4j Bolt or Aura URI").option("--username <user>", "Neo4j username").option("--password-env <env-var>", "Environment variable containing the Neo4j password").option("--database <name>", "Neo4j database name").option("--vault-id <id>", "Stable vault identifier used for shared-database namespacing").option("--batch-size <n>", "Maximum rows to write per Neo4j transaction batch").option("--include-third-party", "Also push third-party repo material", false).option("--include-resources", "Also push resource-like content", false).option("--include-generated", "Also push generated output", false).option("--dry-run", "Show what would be pushed without writing to Neo4j", false).action(
795
798
  async (options) => {
@@ -1189,6 +1192,269 @@ program.command("install").description("Install SwarmVault instructions for an a
1189
1192
  }
1190
1193
  }
1191
1194
  );
1195
+ 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) => {
1196
+ const { mkdtemp, writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
1197
+ const { tmpdir } = await import("os");
1198
+ const path2 = await import("path");
1199
+ const demoDir = await mkdtemp(path2.join(tmpdir(), "swarmvault-demo-"));
1200
+ if (!isJson()) {
1201
+ log(`Creating demo vault in ${demoDir}`);
1202
+ }
1203
+ const rawDir = path2.join(demoDir, "raw", "sources");
1204
+ await mkdir2(rawDir, { recursive: true });
1205
+ await writeFile2(
1206
+ path2.join(rawDir, "llm-wiki-pattern.md"),
1207
+ [
1208
+ "# The LLM Wiki Pattern",
1209
+ "",
1210
+ "Most AI tools answer a question then throw away the work. The LLM Wiki pattern",
1211
+ "keeps a durable wiki between you and raw sources. The LLM does the bookkeeping \u2014",
1212
+ "updating cross-references, noting contradictions, maintaining consistency \u2014 while",
1213
+ "you do the thinking.",
1214
+ "",
1215
+ "## Three Layers",
1216
+ "",
1217
+ "1. **Raw sources** \u2014 immutable documents: books, articles, papers, transcripts, code.",
1218
+ "2. **The wiki** \u2014 LLM-generated markdown: summaries, entity pages, concept pages, cross-references.",
1219
+ "3. **The schema** \u2014 rules for how the wiki is organized and what matters in your domain.",
1220
+ "",
1221
+ "## Why This Beats RAG",
1222
+ "",
1223
+ "RAG re-derives knowledge on every query. The wiki compiles knowledge once and compounds",
1224
+ "it over time. Good answers get filed back as new pages, so exploration builds on itself.",
1225
+ "",
1226
+ "## Key Operations",
1227
+ "",
1228
+ "- **Ingest** \u2014 read a source, write summaries, update cross-references",
1229
+ "- **Query** \u2014 search the wiki, synthesize answers, file results back",
1230
+ "- **Lint** \u2014 health-check for contradictions, orphans, stale claims",
1231
+ ""
1232
+ ].join("\n")
1233
+ );
1234
+ await writeFile2(
1235
+ path2.join(rawDir, "knowledge-graphs.md"),
1236
+ [
1237
+ "# Knowledge Graphs for AI",
1238
+ "",
1239
+ "A knowledge graph represents information as typed nodes and edges with provenance.",
1240
+ "Unlike flat document stores, graphs let you traverse relationships, detect communities,",
1241
+ "and find surprising connections between concepts.",
1242
+ "",
1243
+ "## Node Types",
1244
+ "",
1245
+ "- **Sources** \u2014 the raw documents that feed the graph",
1246
+ "- **Concepts** \u2014 abstract ideas extracted from sources",
1247
+ "- **Entities** \u2014 named things: people, tools, organizations",
1248
+ "- **Modules** \u2014 code units with import/export relationships",
1249
+ "",
1250
+ "## Edge Semantics",
1251
+ "",
1252
+ "Every edge carries an evidence class: `extracted` (directly found), `inferred`",
1253
+ "(derived by reasoning), or `conflicted` (contradictory evidence). This provenance",
1254
+ "tracking prevents hallucination from compounding silently.",
1255
+ "",
1256
+ "## Contradiction Detection",
1257
+ "",
1258
+ "When two sources make conflicting claims, the graph flags the contradiction rather",
1259
+ "than silently picking one. This is critical for research wikis where outdated claims",
1260
+ "from older papers may conflict with newer findings.",
1261
+ "",
1262
+ "RAG systems do not track contradictions because they re-derive everything per query.",
1263
+ "A compiled wiki with a graph layer can detect and surface them automatically.",
1264
+ ""
1265
+ ].join("\n")
1266
+ );
1267
+ await writeFile2(
1268
+ path2.join(rawDir, "local-first-tools.md"),
1269
+ [
1270
+ "# Local-First AI Tools",
1271
+ "",
1272
+ "Local-first means your data stays on your machine by default. No cloud dependency,",
1273
+ "no API keys required for basic operation. Privacy is the default, not an option.",
1274
+ "",
1275
+ "## Advantages",
1276
+ "",
1277
+ "- **Privacy** \u2014 sensitive documents never leave your machine",
1278
+ "- **Speed** \u2014 no network latency for search and graph operations",
1279
+ "- **Reliability** \u2014 works offline, no service outages",
1280
+ "- **Cost** \u2014 no per-query API charges for basic workflows",
1281
+ "",
1282
+ "## The Heuristic Provider",
1283
+ "",
1284
+ "A heuristic provider uses deterministic text analysis \u2014 keyword extraction, TF-IDF,",
1285
+ "structural parsing \u2014 instead of LLM inference. It produces lower-quality summaries",
1286
+ "but runs instantly with zero setup. This makes it ideal for first-run experiences",
1287
+ "and air-gapped environments.",
1288
+ "",
1289
+ "For sharper concept extraction, pair with a local LLM via Ollama. This keeps",
1290
+ "everything on-device while getting model-backed analysis.",
1291
+ ""
1292
+ ].join("\n")
1293
+ );
1294
+ await initVault(demoDir, {});
1295
+ await ingestDirectory(demoDir, rawDir, {});
1296
+ await compileVault(demoDir, {});
1297
+ const { paths } = await loadVaultConfig(demoDir);
1298
+ let graphStats = "";
1299
+ try {
1300
+ const raw = await readFile2(paths.graphPath, "utf-8");
1301
+ const graph2 = JSON.parse(raw);
1302
+ graphStats = ` (${graph2.nodes.length} nodes, ${graph2.edges.length} edges)`;
1303
+ } catch {
1304
+ }
1305
+ if (!isJson()) {
1306
+ log("");
1307
+ log(`Demo vault created${graphStats}.`);
1308
+ log("");
1309
+ log("What just happened:");
1310
+ log(" 1. Created 3 sample sources about LLM wikis, knowledge graphs, and local-first tools");
1311
+ log(" 2. Ingested and compiled them into a knowledge graph");
1312
+ log(" 3. Generated wiki pages with cross-references and a graph report");
1313
+ log("");
1314
+ log(`Vault location: ${demoDir}`);
1315
+ }
1316
+ if (options.serve !== false) {
1317
+ const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
1318
+ const server = await startGraphServer(demoDir, port, { full: false });
1319
+ if (isJson()) {
1320
+ emitJson({ demoDir, graphStats: graphStats.trim(), port: server.port, url: `http://localhost:${server.port}` });
1321
+ } else {
1322
+ log(`Graph viewer running at http://localhost:${server.port}`);
1323
+ log("");
1324
+ log("Try next:");
1325
+ log(` cd ${demoDir}`);
1326
+ log(' swarmvault query "How does contradiction detection work?"');
1327
+ log(" swarmvault lint");
1328
+ }
1329
+ process2.on("SIGINT", async () => {
1330
+ try {
1331
+ await server.close();
1332
+ } catch {
1333
+ }
1334
+ process2.exit(0);
1335
+ });
1336
+ } else if (isJson()) {
1337
+ emitJson({ demoDir, graphStats: graphStats.trim() });
1338
+ } else {
1339
+ log("");
1340
+ log("Try next:");
1341
+ log(` cd ${demoDir}`);
1342
+ log(" swarmvault graph serve");
1343
+ log(' swarmvault query "How does contradiction detection work?"');
1344
+ }
1345
+ });
1346
+ program.command("diff").description("Show what changed in the knowledge graph since the last compile.").action(async () => {
1347
+ const rootDir = process2.cwd();
1348
+ const { paths } = await loadVaultConfig(rootDir);
1349
+ let currentGraph;
1350
+ try {
1351
+ const raw = await readFile2(paths.graphPath, "utf-8");
1352
+ currentGraph = JSON.parse(raw);
1353
+ } catch {
1354
+ if (isJson()) {
1355
+ emitJson({ error: "No compiled graph found. Run swarmvault compile first." });
1356
+ } else {
1357
+ log("No compiled graph found. Run swarmvault compile first.");
1358
+ }
1359
+ return;
1360
+ }
1361
+ let previousGraph;
1362
+ const { execFileSync } = await import("child_process");
1363
+ try {
1364
+ const relPath = paths.graphPath.replace(`${rootDir}/`, "");
1365
+ const previousRaw = execFileSync("git", ["show", `HEAD:${relPath}`], {
1366
+ cwd: rootDir,
1367
+ encoding: "utf-8",
1368
+ stdio: ["pipe", "pipe", "pipe"]
1369
+ });
1370
+ previousGraph = JSON.parse(previousRaw);
1371
+ } catch {
1372
+ }
1373
+ if (!previousGraph) {
1374
+ if (isJson()) {
1375
+ emitJson({
1376
+ status: "no-baseline",
1377
+ current: {
1378
+ nodes: currentGraph.nodes.length,
1379
+ edges: currentGraph.edges.length,
1380
+ pages: currentGraph.pages.length,
1381
+ communities: currentGraph.communities?.length ?? 0
1382
+ }
1383
+ });
1384
+ } else {
1385
+ log("No previous graph found (not in a git repo or no prior commit).");
1386
+ log("");
1387
+ log("Current graph:");
1388
+ log(` ${currentGraph.nodes.length} nodes, ${currentGraph.edges.length} edges, ${currentGraph.pages.length} pages`);
1389
+ if (currentGraph.communities?.length) {
1390
+ log(` ${currentGraph.communities.length} communities`);
1391
+ }
1392
+ const conflicted = currentGraph.edges.filter((e) => e.status === "conflicted");
1393
+ if (conflicted.length) {
1394
+ log(` ${conflicted.length} conflicted edges`);
1395
+ }
1396
+ }
1397
+ return;
1398
+ }
1399
+ const diff = graphDiff(previousGraph, currentGraph);
1400
+ if (isJson()) {
1401
+ emitJson(diff);
1402
+ return;
1403
+ }
1404
+ if (diff.summary === "No changes") {
1405
+ log("No changes since last commit.");
1406
+ return;
1407
+ }
1408
+ log(diff.summary);
1409
+ log("");
1410
+ if (diff.addedNodes.length) {
1411
+ log("Added nodes:");
1412
+ for (const node of diff.addedNodes) {
1413
+ log(` + [${node.type}] ${node.label}`);
1414
+ }
1415
+ log("");
1416
+ }
1417
+ if (diff.removedNodes.length) {
1418
+ log("Removed nodes:");
1419
+ for (const node of diff.removedNodes) {
1420
+ log(` - [${node.type}] ${node.label}`);
1421
+ }
1422
+ log("");
1423
+ }
1424
+ if (diff.addedPages.length) {
1425
+ log("Added pages:");
1426
+ for (const page of diff.addedPages) {
1427
+ log(` + [${page.kind}] ${page.title} (${page.path})`);
1428
+ }
1429
+ log("");
1430
+ }
1431
+ if (diff.removedPages.length) {
1432
+ log("Removed pages:");
1433
+ for (const page of diff.removedPages) {
1434
+ log(` - [${page.kind}] ${page.title} (${page.path})`);
1435
+ }
1436
+ log("");
1437
+ }
1438
+ if (diff.addedEdges.length) {
1439
+ log(`Added edges: ${diff.addedEdges.length}`);
1440
+ for (const edge of diff.addedEdges.slice(0, 20)) {
1441
+ log(` + ${edge.source} -[${edge.relation}]-> ${edge.target} (${edge.evidenceClass})`);
1442
+ }
1443
+ if (diff.addedEdges.length > 20) {
1444
+ log(` ... and ${diff.addedEdges.length - 20} more`);
1445
+ }
1446
+ log("");
1447
+ }
1448
+ if (diff.removedEdges.length) {
1449
+ log(`Removed edges: ${diff.removedEdges.length}`);
1450
+ for (const edge of diff.removedEdges.slice(0, 20)) {
1451
+ log(` - ${edge.source} -[${edge.relation}]-> ${edge.target}`);
1452
+ }
1453
+ if (diff.removedEdges.length > 20) {
1454
+ log(` ... and ${diff.removedEdges.length - 20} more`);
1455
+ }
1456
+ }
1457
+ });
1192
1458
  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) => {
1193
1459
  const rootDir = process2.cwd();
1194
1460
  await initVault(rootDir, {});
@@ -1222,6 +1488,19 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
1222
1488
  emitJson({ ...result, compiled });
1223
1489
  }
1224
1490
  });
1491
+ function enableStructuredJsonOnSubcommands(command) {
1492
+ for (const subcommand of command.commands) {
1493
+ const hasJsonOption = subcommand.options.some((option) => option.attributeName() === "json");
1494
+ if (!hasJsonOption) {
1495
+ subcommand.option("--json", "Emit structured JSON output", false);
1496
+ }
1497
+ enableStructuredJsonOnSubcommands(subcommand);
1498
+ }
1499
+ }
1500
+ enableStructuredJsonOnSubcommands(program);
1501
+ program.hook("preAction", (_command, actionCommand) => {
1502
+ activeCommand = actionCommand;
1503
+ });
1225
1504
  program.parseAsync(process2.argv).catch((error) => {
1226
1505
  const message = error instanceof Error ? error.message : String(error);
1227
1506
  if (isJson()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.7.25",
3
+ "version": "0.7.26",
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": "0.7.25",
41
+ "@swarmvaultai/engine": "0.7.26",
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
+ }