@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 +21 -0
- package/README.md +29 -3
- package/dist/index.js +284 -5
- 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
|
@@ -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,
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
279
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.26";
|
|
277
280
|
} catch {
|
|
278
|
-
return "0.7.
|
|
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.
|
|
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.
|
|
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
|
+
}
|