@swarmvaultai/cli 0.1.28 → 0.1.30
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/README.md +34 -2
- package/dist/index.js +82 -37
- 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
|
|
|
@@ -41,6 +41,7 @@ swarmvault hook install
|
|
|
41
41
|
swarmvault graph serve
|
|
42
42
|
swarmvault graph export --html ./exports/graph.html
|
|
43
43
|
swarmvault graph export --cypher ./exports/graph.cypher
|
|
44
|
+
swarmvault graph push neo4j --dry-run
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
## Commands
|
|
@@ -70,6 +71,7 @@ Ingest a local file path, directory path, or URL into immutable source storage a
|
|
|
70
71
|
- directory ingest respects `.gitignore` unless you pass `--no-gitignore`
|
|
71
72
|
- repo-aware directory ingest records `repoRelativePath` and later compile writes `state/code-index.json`
|
|
72
73
|
- URL ingest still localizes remote image references by default
|
|
74
|
+
- local file ingest supports markdown, text, HTML, PDF, DOCX, images, and code
|
|
73
75
|
- code-aware directory ingest currently covers JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, PHP, Ruby, and PowerShell
|
|
74
76
|
|
|
75
77
|
Useful flags:
|
|
@@ -101,7 +103,7 @@ Capture supported URLs through a normalized markdown layer before ingesting them
|
|
|
101
103
|
|
|
102
104
|
### `swarmvault inbox import [dir]`
|
|
103
105
|
|
|
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/`.
|
|
106
|
+
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
107
|
|
|
106
108
|
### `swarmvault compile [--approve]`
|
|
107
109
|
|
|
@@ -255,6 +257,29 @@ Export the current graph as one of four formats:
|
|
|
255
257
|
- `--graphml` for graph-tool interoperability
|
|
256
258
|
- `--cypher` for Neo4j-style import scripts
|
|
257
259
|
|
|
260
|
+
### `swarmvault graph push neo4j`
|
|
261
|
+
|
|
262
|
+
Push the compiled graph directly into Neo4j over Bolt/Aura instead of writing an intermediate file.
|
|
263
|
+
|
|
264
|
+
Useful flags:
|
|
265
|
+
|
|
266
|
+
- `--uri <bolt-uri>`
|
|
267
|
+
- `--username <user>`
|
|
268
|
+
- `--password-env <env-var>`
|
|
269
|
+
- `--database <name>`
|
|
270
|
+
- `--vault-id <id>`
|
|
271
|
+
- `--include-third-party`
|
|
272
|
+
- `--include-resources`
|
|
273
|
+
- `--include-generated`
|
|
274
|
+
- `--dry-run`
|
|
275
|
+
|
|
276
|
+
Defaults:
|
|
277
|
+
|
|
278
|
+
- reads `graphSinks.neo4j` from `swarmvault.config.json` when present
|
|
279
|
+
- includes only `first_party` graph material unless you opt into more source classes
|
|
280
|
+
- namespaces every remote record by `vaultId` so multiple vaults can safely share one Neo4j database
|
|
281
|
+
- upserts current graph records and does not prune stale remote data yet
|
|
282
|
+
|
|
258
283
|
### `swarmvault install --agent <codex|claude|cursor|goose|pi|gemini|opencode|aider|copilot>`
|
|
259
284
|
|
|
260
285
|
Install agent-specific rules into the current project so an agent understands the SwarmVault workspace contract and workflow.
|
|
@@ -277,6 +302,13 @@ Agent target mapping:
|
|
|
277
302
|
- `copilot` writes `.github/copilot-instructions.md` plus `AGENTS.md`
|
|
278
303
|
- `cursor` writes `.cursor/rules/swarmvault.mdc`
|
|
279
304
|
|
|
305
|
+
Hook semantics:
|
|
306
|
+
|
|
307
|
+
- `claude --hook` writes `.claude/settings.json` plus `.claude/hooks/swarmvault-graph-first.js` and adds model-visible advisory context through structured hook JSON
|
|
308
|
+
- `gemini --hook` writes `.gemini/settings.json` plus `.gemini/hooks/swarmvault-graph-first.js` and stays advisory/model-visible
|
|
309
|
+
- `opencode --hook` writes `.opencode/plugins/swarmvault-graph-first.js` and stays advisory/log-only
|
|
310
|
+
- `copilot --hook` writes `.github/hooks/swarmvault-graph-first.json` plus `.github/hooks/swarmvault-graph-first.js` and remains decision-based rather than advisory
|
|
311
|
+
|
|
280
312
|
`aider` is intentionally file/config-based in this release rather than hook-based.
|
|
281
313
|
|
|
282
314
|
## Provider Configuration
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
loadVaultConfig,
|
|
30
30
|
pathGraphVault,
|
|
31
31
|
promoteCandidate,
|
|
32
|
+
pushGraphNeo4j,
|
|
32
33
|
queryGraphVault,
|
|
33
34
|
queryVault,
|
|
34
35
|
readApproval,
|
|
@@ -216,11 +217,16 @@ program.name("swarmvault").description("SwarmVault is a local-first LLM wiki com
|
|
|
216
217
|
function readCliVersion() {
|
|
217
218
|
try {
|
|
218
219
|
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.
|
|
220
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.1.30";
|
|
220
221
|
} catch {
|
|
221
|
-
return "0.1.
|
|
222
|
+
return "0.1.30";
|
|
222
223
|
}
|
|
223
224
|
}
|
|
225
|
+
function parsePositiveInt(value, fallback) {
|
|
226
|
+
if (value === void 0) return fallback;
|
|
227
|
+
const parsed = Number.parseInt(value, 10);
|
|
228
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
229
|
+
}
|
|
224
230
|
function isJson() {
|
|
225
231
|
return program.opts().json === true;
|
|
226
232
|
}
|
|
@@ -273,8 +279,8 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
|
|
|
273
279
|
});
|
|
274
280
|
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
281
|
async (input, options) => {
|
|
276
|
-
const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ?
|
|
277
|
-
const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ?
|
|
282
|
+
const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
|
|
283
|
+
const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
|
|
278
284
|
const extractClasses = [
|
|
279
285
|
"first_party",
|
|
280
286
|
...options.includeThirdParty ? ["third_party"] : [],
|
|
@@ -283,11 +289,11 @@ program.command("ingest").description("Ingest a local file path, directory path,
|
|
|
283
289
|
];
|
|
284
290
|
const commonOptions = {
|
|
285
291
|
includeAssets: options.includeAssets,
|
|
286
|
-
maxAssetSize
|
|
292
|
+
maxAssetSize,
|
|
287
293
|
repoRoot: options.repoRoot,
|
|
288
294
|
include: options.include,
|
|
289
295
|
exclude: options.exclude,
|
|
290
|
-
maxFiles
|
|
296
|
+
maxFiles,
|
|
291
297
|
gitignore: options.gitignore,
|
|
292
298
|
extractClasses
|
|
293
299
|
};
|
|
@@ -366,10 +372,10 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
|
|
|
366
372
|
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
373
|
new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
368
374
|
).action(async (question, options) => {
|
|
369
|
-
const stepCount =
|
|
375
|
+
const stepCount = parsePositiveInt(options.steps, 3);
|
|
370
376
|
const result = await exploreVault(process2.cwd(), {
|
|
371
377
|
question,
|
|
372
|
-
steps:
|
|
378
|
+
steps: stepCount,
|
|
373
379
|
format: options.format
|
|
374
380
|
});
|
|
375
381
|
if (isJson()) {
|
|
@@ -409,8 +415,45 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
|
|
|
409
415
|
}
|
|
410
416
|
});
|
|
411
417
|
var graph = program.command("graph").description("Graph-related commands.");
|
|
418
|
+
var graphPush = graph.command("push").description("Push the compiled graph into external sinks.");
|
|
419
|
+
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(
|
|
420
|
+
async (options) => {
|
|
421
|
+
const batchSize = typeof options.batchSize === "string" && options.batchSize.trim() ? parsePositiveInt(options.batchSize, 0) || void 0 : void 0;
|
|
422
|
+
const includeClasses = [
|
|
423
|
+
"first_party",
|
|
424
|
+
...options.includeThirdParty ? ["third_party"] : [],
|
|
425
|
+
...options.includeResources ? ["resource"] : [],
|
|
426
|
+
...options.includeGenerated ? ["generated"] : []
|
|
427
|
+
];
|
|
428
|
+
const result = await pushGraphNeo4j(process2.cwd(), {
|
|
429
|
+
uri: options.uri,
|
|
430
|
+
username: options.username,
|
|
431
|
+
passwordEnv: options.passwordEnv,
|
|
432
|
+
database: options.database,
|
|
433
|
+
vaultId: options.vaultId,
|
|
434
|
+
batchSize,
|
|
435
|
+
includeClasses,
|
|
436
|
+
dryRun: options.dryRun ?? false
|
|
437
|
+
});
|
|
438
|
+
if (isJson()) {
|
|
439
|
+
emitJson(result);
|
|
440
|
+
} else {
|
|
441
|
+
log(
|
|
442
|
+
`${result.dryRun ? "Planned" : "Pushed"} ${result.counts.nodes} nodes, ${result.counts.relationships} relationships, ${result.counts.hyperedges} hyperedges, and ${result.counts.groupMembers} group-member links to ${result.uri}/${result.database} as ${result.vaultId}.`
|
|
443
|
+
);
|
|
444
|
+
if (result.skipped.nodes || result.skipped.relationships || result.skipped.hyperedges) {
|
|
445
|
+
log(
|
|
446
|
+
`Skipped ${result.skipped.nodes} node(s), ${result.skipped.relationships} relationship(s), and ${result.skipped.hyperedges} hyperedge(s) outside the selected source classes.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
for (const warning of result.warnings) {
|
|
450
|
+
log(`Warning: ${warning}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
);
|
|
412
455
|
graph.command("serve").description("Serve the local graph viewer.").option("--port <port>", "Port override").action(async (options) => {
|
|
413
|
-
const port = options.port ?
|
|
456
|
+
const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
|
|
414
457
|
const server = await startGraphServer(process2.cwd(), port);
|
|
415
458
|
if (isJson()) {
|
|
416
459
|
emitJson({ port: server.port, url: `http://localhost:${server.port}` });
|
|
@@ -418,7 +461,10 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
|
|
|
418
461
|
log(`Graph viewer running at http://localhost:${server.port}`);
|
|
419
462
|
}
|
|
420
463
|
process2.on("SIGINT", async () => {
|
|
421
|
-
|
|
464
|
+
try {
|
|
465
|
+
await server.close();
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
422
468
|
process2.exit(0);
|
|
423
469
|
});
|
|
424
470
|
});
|
|
@@ -441,10 +487,10 @@ graph.command("export").description("Export the graph as HTML, SVG, GraphML, or
|
|
|
441
487
|
}
|
|
442
488
|
});
|
|
443
489
|
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 ?
|
|
490
|
+
const budget = options.budget ? parsePositiveInt(options.budget, 0) || void 0 : void 0;
|
|
445
491
|
const result = await queryGraphVault(process2.cwd(), question, {
|
|
446
492
|
traversal: options.dfs ? "dfs" : "bfs",
|
|
447
|
-
budget
|
|
493
|
+
budget
|
|
448
494
|
});
|
|
449
495
|
if (isJson()) {
|
|
450
496
|
emitJson(result);
|
|
@@ -469,8 +515,8 @@ graph.command("explain").description("Explain a graph node, its page, community,
|
|
|
469
515
|
log(result.summary);
|
|
470
516
|
});
|
|
471
517
|
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 =
|
|
473
|
-
const result = await listGodNodes(process2.cwd(),
|
|
518
|
+
const limit = parsePositiveInt(options.limit, 10);
|
|
519
|
+
const result = await listGodNodes(process2.cwd(), limit);
|
|
474
520
|
if (isJson()) {
|
|
475
521
|
emitJson(result);
|
|
476
522
|
return;
|
|
@@ -555,12 +601,12 @@ candidate.command("archive").description("Archive a candidate by removing it fro
|
|
|
555
601
|
}
|
|
556
602
|
});
|
|
557
603
|
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 =
|
|
604
|
+
const debounceMs = parsePositiveInt(options.debounce, 900);
|
|
559
605
|
if (options.once) {
|
|
560
606
|
const result = await runWatchCycle(process2.cwd(), {
|
|
561
607
|
lint: options.lint ?? false,
|
|
562
608
|
repo: options.repo ?? false,
|
|
563
|
-
debounceMs
|
|
609
|
+
debounceMs
|
|
564
610
|
});
|
|
565
611
|
if (isJson()) {
|
|
566
612
|
emitJson(result);
|
|
@@ -575,7 +621,7 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
575
621
|
const controller = await watchVault(process2.cwd(), {
|
|
576
622
|
lint: options.lint ?? false,
|
|
577
623
|
repo: options.repo ?? false,
|
|
578
|
-
debounceMs
|
|
624
|
+
debounceMs
|
|
579
625
|
});
|
|
580
626
|
if (isJson()) {
|
|
581
627
|
emitJson({ status: "watching", inboxDir: paths.inboxDir, repo: options.repo ?? false });
|
|
@@ -583,23 +629,14 @@ var watch = program.command("watch").description("Watch the inbox directory and
|
|
|
583
629
|
log(`Watching inbox${options.repo ? " and tracked repos" : ""} for changes. Press Ctrl+C to stop.`);
|
|
584
630
|
}
|
|
585
631
|
process2.on("SIGINT", async () => {
|
|
586
|
-
|
|
632
|
+
try {
|
|
633
|
+
await controller.close();
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
587
636
|
process2.exit(0);
|
|
588
637
|
});
|
|
589
638
|
});
|
|
590
|
-
|
|
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 () => {
|
|
639
|
+
async function showWatchStatus() {
|
|
603
640
|
const result = await getWatchStatus(process2.cwd());
|
|
604
641
|
if (isJson()) {
|
|
605
642
|
emitJson(result);
|
|
@@ -610,7 +647,9 @@ program.command("watch-status").description("Show the latest watch run plus pend
|
|
|
610
647
|
for (const entry of result.pendingSemanticRefresh.slice(0, 8)) {
|
|
611
648
|
log(`- ${entry.changeType} ${entry.path}`);
|
|
612
649
|
}
|
|
613
|
-
}
|
|
650
|
+
}
|
|
651
|
+
watch.command("status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
|
|
652
|
+
program.command("watch-status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
|
|
614
653
|
var hook = program.command("hook").description("Install local git hooks that keep tracked repos and the vault in sync.");
|
|
615
654
|
hook.command("install").description("Install post-commit and post-checkout hooks for the nearest git repository.").action(async () => {
|
|
616
655
|
const status = await installGitHooks(process2.cwd());
|
|
@@ -670,15 +709,18 @@ schedule.command("run").description("Run one configured schedule job immediately
|
|
|
670
709
|
);
|
|
671
710
|
});
|
|
672
711
|
schedule.command("serve").description("Run the local schedule loop.").option("--poll <ms>", "Polling interval in milliseconds", "30000").action(async (options) => {
|
|
673
|
-
const pollMs =
|
|
674
|
-
const controller = await serveSchedules(process2.cwd(),
|
|
712
|
+
const pollMs = parsePositiveInt(options.poll, 3e4);
|
|
713
|
+
const controller = await serveSchedules(process2.cwd(), pollMs);
|
|
675
714
|
if (isJson()) {
|
|
676
|
-
emitJson({ status: "serving", pollMs
|
|
715
|
+
emitJson({ status: "serving", pollMs });
|
|
677
716
|
} else {
|
|
678
717
|
log("Serving schedules. Press Ctrl+C to stop.");
|
|
679
718
|
}
|
|
680
719
|
process2.on("SIGINT", async () => {
|
|
681
|
-
|
|
720
|
+
try {
|
|
721
|
+
await controller.close();
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
682
724
|
process2.exit(0);
|
|
683
725
|
});
|
|
684
726
|
});
|
|
@@ -689,7 +731,10 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
|
|
|
689
731
|
}
|
|
690
732
|
const controller = await startMcpServer(process2.cwd());
|
|
691
733
|
process2.on("SIGINT", async () => {
|
|
692
|
-
|
|
734
|
+
try {
|
|
735
|
+
await controller.close();
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
693
738
|
process2.exit(0);
|
|
694
739
|
});
|
|
695
740
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmvaultai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
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.
|
|
46
|
+
"@swarmvaultai/engine": "0.1.30",
|
|
47
47
|
"commander": "^14.0.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|