@swarmvaultai/cli 3.14.2 → 3.16.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.
- package/README.md +39 -9
- package/dist/index.js +230 -38
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -105,25 +105,26 @@ Set `SWARMVAULT_OUT=<dir>` when generated artifacts should be isolated from the
|
|
|
105
105
|
|
|
106
106
|
`--profile` accepts `default`, `personal-research`, or a comma-separated preset list such as `reader,timeline`. For fully custom vault behavior, edit the `profile` block in `swarmvault.config.json`; that deterministic profile layer works alongside the human-written `swarmvault.schema.md`. The `personal-research` preset also sets `profile.guidedIngestDefault: true` and `profile.deepLintDefault: true`, so guided ingest/source and lint flows are on by default until you override them with `--no-guide` or `--no-deep`.
|
|
107
107
|
|
|
108
|
-
### `swarmvault quickstart <directory|github-url> [--port <port>] [--no-serve] [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
108
|
+
### `swarmvault quickstart <file|directory|github-url> [--port <port>] [--no-serve] [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>] [--install-agent-rules]`
|
|
109
109
|
|
|
110
110
|
Beginner-friendly alias for `swarmvault scan`.
|
|
111
111
|
|
|
112
112
|
- initializes the current directory as a SwarmVault workspace
|
|
113
|
-
- ingests the supplied local directory, or registers/syncs the supplied public GitHub repo root URL
|
|
113
|
+
- ingests the supplied local file or directory, or registers/syncs the supplied public GitHub repo root URL
|
|
114
114
|
- compiles wiki, graph, search, and share artifacts immediately
|
|
115
115
|
- prints the generated `raw/`, `wiki/`, `state/graph.json`, and `wiki/graph/` paths in human output
|
|
116
116
|
- starts `graph serve` unless you pass `--no-serve` or `--no-viz`
|
|
117
117
|
- keeps the same JSON output contract as `scan`
|
|
118
|
+
- leaves agent rule files alone unless you pass `--install-agent-rules`
|
|
118
119
|
|
|
119
120
|
Use this as the default first-run command in docs and onboarding.
|
|
120
121
|
|
|
121
|
-
### `swarmvault scan <directory|github-url> [--port <port>] [--no-serve] [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
122
|
+
### `swarmvault scan <file|directory|github-url> [--port <port>] [--no-serve] [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>] [--install-agent-rules]`
|
|
122
123
|
|
|
123
|
-
Quick-start a scratch vault from a local directory or public GitHub repo root URL in one command.
|
|
124
|
+
Quick-start a scratch vault from a local file, directory, or public GitHub repo root URL in one command.
|
|
124
125
|
|
|
125
126
|
- initializes the current directory as a SwarmVault workspace
|
|
126
|
-
- ingests the supplied directory as local sources, or registers/syncs the supplied public GitHub repo root URL
|
|
127
|
+
- ingests the supplied file or directory as local sources, or registers/syncs the supplied public GitHub repo root URL
|
|
127
128
|
- compiles the vault immediately
|
|
128
129
|
- writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/`, then prints the paths
|
|
129
130
|
- starts `graph serve` unless you pass `--no-serve` or `--no-viz`
|
|
@@ -131,10 +132,11 @@ Quick-start a scratch vault from a local directory or public GitHub repo root UR
|
|
|
131
132
|
- `--mcp` starts the MCP stdio server after compile instead of the graph viewer
|
|
132
133
|
- respects `--port` when you want a specific viewer port
|
|
133
134
|
- for GitHub repo URLs, supports `--branch`, `--ref`, and `--checkout-dir`
|
|
135
|
+
- `--install-agent-rules` installs the configured `agents` targets during initialization
|
|
134
136
|
|
|
135
137
|
Use this when you want the fastest repo or docs-tree walkthrough without first deciding on managed-source registration.
|
|
136
138
|
|
|
137
|
-
### `swarmvault clone <directory|github-url> [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>]`
|
|
139
|
+
### `swarmvault clone <file|directory|github-url> [--no-viz] [--mcp] [--branch <name>] [--ref <ref>] [--checkout-dir <path>] [--install-agent-rules]`
|
|
138
140
|
|
|
139
141
|
Compatibility alias for `swarmvault scan`.
|
|
140
142
|
|
|
@@ -268,7 +270,7 @@ Useful flags:
|
|
|
268
270
|
|
|
269
271
|
Repo ingest defaults to `first_party` material. The extra `--include-*` flags opt dependency trees, resource bundles, and generated output back in when you actually want them in the vault.
|
|
270
272
|
|
|
271
|
-
|
|
273
|
+
Interactive file and directory ingest now emits bounded stderr progress, including the active file and processed content size. JSON, MCP, watch, and CI-style flows stay quiet, and parser compatibility failures stay local to the affected source instead of aborting unrelated analysis.
|
|
272
274
|
|
|
273
275
|
Audio and video files use `tasks.audioProvider` when you configure a provider with `audio` capability. Local video extraction shells out to `ffmpeg`; public video URL extraction with `--video` shells out to `yt-dlp`. When no audio provider or extractor binary 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.
|
|
274
276
|
|
|
@@ -670,7 +672,15 @@ Trace the reverse-import blast radius of changing a file or module.
|
|
|
670
672
|
- follows reverse `imports` edges through the compiled graph
|
|
671
673
|
- reports affected modules by depth so you can estimate downstream impact before editing
|
|
672
674
|
|
|
673
|
-
### `swarmvault graph
|
|
675
|
+
### `swarmvault graph cycles [--relation <name>] [--limit <n>] [--max-depth <n>]`
|
|
676
|
+
|
|
677
|
+
Find deterministic directed cycles in the compiled graph.
|
|
678
|
+
|
|
679
|
+
- defaults to `imports` edges for module cycle checks
|
|
680
|
+
- accepts repeated `--relation` flags to inspect other directed relationships
|
|
681
|
+
- supports global `--json` for automation
|
|
682
|
+
|
|
683
|
+
### `swarmvault graph export --html|--html-standalone|--report|--svg|--graphml|--cypher|--json|--callflow|--obsidian|--canvas <output>`
|
|
674
684
|
|
|
675
685
|
Export the current graph as one or more shareable formats:
|
|
676
686
|
|
|
@@ -681,6 +691,7 @@ Export the current graph as one or more shareable formats:
|
|
|
681
691
|
- `--graphml` for graph-tool interoperability
|
|
682
692
|
- `--cypher` for Neo4j-style import scripts
|
|
683
693
|
- `--json` for a deterministic machine-readable graph package
|
|
694
|
+
- `--callflow` for compact directed relationship HTML
|
|
684
695
|
- `--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
|
|
685
696
|
- `--canvas` for an Obsidian canvas grouped by community
|
|
686
697
|
|
|
@@ -711,10 +722,12 @@ Defaults:
|
|
|
711
722
|
- namespaces every remote record by `vaultId` so multiple vaults can safely share one Neo4j database
|
|
712
723
|
- upserts current graph records and does not prune stale remote data yet
|
|
713
724
|
|
|
714
|
-
### `swarmvault install --agent <agent
|
|
725
|
+
### `swarmvault install --agent <agent> [--scope project|user]`
|
|
715
726
|
|
|
716
727
|
Install agent-specific rules into the current project so an agent understands the SwarmVault workspace contract and workflow.
|
|
717
728
|
|
|
729
|
+
`init`, `quickstart`, `scan`, and `clone` do not write project-local agent rule files by default. Run `swarmvault install --agent <agent> --scope project` for one target at a time, use `--scope user` for supported user-scope skill installs, or list targets in `swarmvault.config.json` and pass `--install-agent-rules` to `init`, `quickstart`, `scan`, or `clone` when you intentionally want configured targets installed together.
|
|
730
|
+
|
|
718
731
|
Hook-capable installs:
|
|
719
732
|
|
|
720
733
|
```bash
|
|
@@ -723,6 +736,7 @@ swarmvault install --agent claude --hook
|
|
|
723
736
|
swarmvault install --agent gemini --hook
|
|
724
737
|
swarmvault install --agent opencode --hook
|
|
725
738
|
swarmvault install --agent copilot --hook
|
|
739
|
+
swarmvault install --agent kilo --hook
|
|
726
740
|
```
|
|
727
741
|
|
|
728
742
|
Agent target mapping:
|
|
@@ -737,9 +751,13 @@ Agent target mapping:
|
|
|
737
751
|
- `claw` writes `.claw/skills/swarmvault/SKILL.md`
|
|
738
752
|
- `droid` writes `.factory/rules/swarmvault.md`
|
|
739
753
|
- `kiro` writes `.kiro/skills/swarmvault/SKILL.md` and `.kiro/steering/swarmvault.md`
|
|
754
|
+
- `kilo` project-scope writes `AGENTS.md`; with `--hook`, it also writes `.kilo/plugins/swarmvault.js` and `.kilo/kilo.json` while preserving an existing `.kilo/kilo.jsonc`
|
|
740
755
|
- `hermes` writes `~/.hermes/skills/swarmvault/SKILL.md` plus `AGENTS.md`
|
|
741
756
|
- `antigravity` writes `.agents/rules/swarmvault.md` and `.agents/workflows/swarmvault.md`, and removes older fully managed `.agent/` files during reinstall
|
|
742
757
|
- `vscode` writes `.github/chatmodes/swarmvault.chatmode.md` plus `.github/copilot-instructions.md`
|
|
758
|
+
- `devin` writes `.devin/skills/swarmvault/SKILL.md` plus `.windsurf/rules/swarmvault.md`
|
|
759
|
+
|
|
760
|
+
`swarmvault install status --agent <agent> [--scope project|user] [--hook]` reports the expected install paths and whether they exist without writing files.
|
|
743
761
|
|
|
744
762
|
SwarmVault only owns the managed block inside shared markdown rule files. It keeps the SwarmVault block aligned across targets while preserving any user-owned text before or after the block, so `AGENTS.md` and `CLAUDE.md` do not need to be byte-identical.
|
|
745
763
|
|
|
@@ -750,6 +768,7 @@ Hook semantics:
|
|
|
750
768
|
- `gemini --hook` writes `.gemini/settings.json` plus `.gemini/hooks/swarmvault-graph-first.js` and stays advisory/model-visible
|
|
751
769
|
- `opencode --hook` writes `.opencode/plugins/swarmvault-graph-first.js` and stays advisory/log-only
|
|
752
770
|
- `copilot --hook` writes `.github/hooks/swarmvault-graph-first.json` plus `.github/hooks/swarmvault-graph-first.js` and remains decision-based rather than advisory
|
|
771
|
+
- `kilo --hook` writes `.kilo/plugins/swarmvault.js` and registers it in `.kilo/kilo.json`
|
|
753
772
|
|
|
754
773
|
`aider` is intentionally file/config-based in this release rather than hook-based.
|
|
755
774
|
|
|
@@ -771,6 +790,17 @@ npm install -g @swarmvaultai/cli
|
|
|
771
790
|
|
|
772
791
|
SwarmVault defaults to a local `heuristic` provider so the CLI works without API keys, but real vaults will usually point at an actual model provider.
|
|
773
792
|
|
|
793
|
+
CLI registry commands:
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
swarmvault provider add router --type openrouter --model openrouter/auto --api-key-env OPENROUTER_API_KEY --capability chat --capability structured --task queryProvider
|
|
797
|
+
swarmvault provider list
|
|
798
|
+
swarmvault provider show router
|
|
799
|
+
swarmvault provider remove router --fallback local
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
`provider add|remove` preserves unrelated config fields and stores secret references through `apiKeyEnv`, not literal API key values.
|
|
803
|
+
|
|
774
804
|
Example:
|
|
775
805
|
|
|
776
806
|
```json
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
|
-
import { access, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
5
|
+
import { access, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
6
6
|
import path2 from "path";
|
|
7
7
|
import process2 from "process";
|
|
8
8
|
import { createInterface } from "readline/promises";
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
acceptApproval,
|
|
11
11
|
addInput,
|
|
12
12
|
addManagedSource,
|
|
13
|
+
addProviderConfig,
|
|
13
14
|
addWatchedRoot,
|
|
14
15
|
archiveCandidate,
|
|
15
16
|
askChatSession,
|
|
@@ -37,9 +38,12 @@ import {
|
|
|
37
38
|
exportGraphTree,
|
|
38
39
|
exportObsidianCanvas,
|
|
39
40
|
exportObsidianVault,
|
|
41
|
+
findGraphCycles,
|
|
40
42
|
finishMemoryTask,
|
|
43
|
+
getAgentInstallStatus,
|
|
41
44
|
getGitHookStatus,
|
|
42
45
|
getGraphStatus,
|
|
46
|
+
getProviderConfigEntry,
|
|
43
47
|
getRetrievalStatus,
|
|
44
48
|
getWatchStatus,
|
|
45
49
|
graphDiff,
|
|
@@ -61,6 +65,7 @@ import {
|
|
|
61
65
|
listManagedSourceRecords,
|
|
62
66
|
listManifests,
|
|
63
67
|
listMemoryTasks,
|
|
68
|
+
listProviderConfigEntries,
|
|
64
69
|
listSchedules,
|
|
65
70
|
listWatchedRoots,
|
|
66
71
|
loadVaultConfig,
|
|
@@ -80,6 +85,7 @@ import {
|
|
|
80
85
|
registerLocalWhisperProvider,
|
|
81
86
|
rejectApproval,
|
|
82
87
|
reloadManagedSources,
|
|
88
|
+
removeProviderConfig,
|
|
83
89
|
removeWatchedRoot,
|
|
84
90
|
renderContextPackLlms,
|
|
85
91
|
renderContextPackMarkdown,
|
|
@@ -330,9 +336,9 @@ program.addHelpText(
|
|
|
330
336
|
function readCliVersion() {
|
|
331
337
|
try {
|
|
332
338
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
333
|
-
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.
|
|
339
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.16.0";
|
|
334
340
|
} catch {
|
|
335
|
-
return "3.
|
|
341
|
+
return "3.16.0";
|
|
336
342
|
}
|
|
337
343
|
}
|
|
338
344
|
function parsePositiveInt(value, fallback) {
|
|
@@ -340,6 +346,60 @@ function parsePositiveInt(value, fallback) {
|
|
|
340
346
|
const parsed = Number.parseInt(value, 10);
|
|
341
347
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
342
348
|
}
|
|
349
|
+
var providerTypes = [
|
|
350
|
+
"heuristic",
|
|
351
|
+
"openai",
|
|
352
|
+
"ollama",
|
|
353
|
+
"anthropic",
|
|
354
|
+
"gemini",
|
|
355
|
+
"openai-compatible",
|
|
356
|
+
"openrouter",
|
|
357
|
+
"groq",
|
|
358
|
+
"together",
|
|
359
|
+
"xai",
|
|
360
|
+
"cerebras",
|
|
361
|
+
"local-whisper",
|
|
362
|
+
"custom"
|
|
363
|
+
];
|
|
364
|
+
var providerCapabilities = [
|
|
365
|
+
"responses",
|
|
366
|
+
"chat",
|
|
367
|
+
"structured",
|
|
368
|
+
"tools",
|
|
369
|
+
"vision",
|
|
370
|
+
"embeddings",
|
|
371
|
+
"streaming",
|
|
372
|
+
"local",
|
|
373
|
+
"image_generation",
|
|
374
|
+
"audio"
|
|
375
|
+
];
|
|
376
|
+
var providerTaskKeys = [
|
|
377
|
+
"compileProvider",
|
|
378
|
+
"queryProvider",
|
|
379
|
+
"lintProvider",
|
|
380
|
+
"visionProvider",
|
|
381
|
+
"imageProvider",
|
|
382
|
+
"embeddingProvider",
|
|
383
|
+
"audioProvider"
|
|
384
|
+
];
|
|
385
|
+
function parseProviderType(value) {
|
|
386
|
+
if (providerTypes.includes(value)) {
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
throw new Error(`Unknown provider type "${value}". Use one of: ${providerTypes.join(", ")}.`);
|
|
390
|
+
}
|
|
391
|
+
function parseProviderCapability(value) {
|
|
392
|
+
if (providerCapabilities.includes(value)) {
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
throw new Error(`Unknown provider capability "${value}". Use one of: ${providerCapabilities.join(", ")}.`);
|
|
396
|
+
}
|
|
397
|
+
function parseProviderTask(value) {
|
|
398
|
+
if (providerTaskKeys.includes(value)) {
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
throw new Error(`Unknown provider task "${value}". Use one of: ${providerTaskKeys.join(", ")}.`);
|
|
402
|
+
}
|
|
343
403
|
function parsePositiveNumber(value) {
|
|
344
404
|
if (value === void 0) return void 0;
|
|
345
405
|
const parsed = Number.parseFloat(value);
|
|
@@ -818,7 +878,8 @@ async function runGraphMergeCommand(graphPaths, options) {
|
|
|
818
878
|
}
|
|
819
879
|
async function runScanCommand(input, options) {
|
|
820
880
|
const rootDir = process2.cwd();
|
|
821
|
-
|
|
881
|
+
const progress = !isJson() && !options.mcp;
|
|
882
|
+
await initVault(rootDir, { installAgentRules: options.installAgentRules ?? false });
|
|
822
883
|
if (!isJson()) {
|
|
823
884
|
log("Initialized workspace.");
|
|
824
885
|
}
|
|
@@ -828,14 +889,17 @@ async function runScanCommand(input, options) {
|
|
|
828
889
|
branch: options.branch,
|
|
829
890
|
ref: options.ref,
|
|
830
891
|
checkoutDir: options.checkoutDir
|
|
831
|
-
}) : await
|
|
892
|
+
}) : await ingestScanInput(rootDir, input, progress);
|
|
832
893
|
if (!isJson()) {
|
|
833
894
|
if ("source" in result) {
|
|
834
895
|
log(
|
|
835
896
|
`Registered ${result.source.kind} source ${result.source.id}. Imported ${result.source.lastSyncCounts?.importedCount ?? 0}, updated ${result.source.lastSyncCounts?.updatedCount ?? 0}.`
|
|
836
897
|
);
|
|
837
|
-
} else {
|
|
898
|
+
} else if ("inputDir" in result) {
|
|
838
899
|
log(`Ingested ${result.imported.length} file(s).`);
|
|
900
|
+
} else {
|
|
901
|
+
const sourceCount = result.created.length + result.updated.length + result.unchanged.length;
|
|
902
|
+
log(`Ingested ${sourceCount} source(s).`);
|
|
839
903
|
}
|
|
840
904
|
}
|
|
841
905
|
const compiled = "compile" in result && result.compile ? result.compile : await compileVault(rootDir, {});
|
|
@@ -901,6 +965,17 @@ async function runScanCommand(input, options) {
|
|
|
901
965
|
emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
|
|
902
966
|
}
|
|
903
967
|
}
|
|
968
|
+
async function ingestScanInput(rootDir, input, progress) {
|
|
969
|
+
const absoluteInput = path2.resolve(rootDir, input);
|
|
970
|
+
const inputStat = await stat(absoluteInput);
|
|
971
|
+
if (inputStat.isDirectory()) {
|
|
972
|
+
return ingestDirectory(rootDir, input, { progress });
|
|
973
|
+
}
|
|
974
|
+
if (inputStat.isFile()) {
|
|
975
|
+
return ingestInputDetailed(rootDir, input, { progress });
|
|
976
|
+
}
|
|
977
|
+
throw new Error(`Input must be a file or directory: ${input}`);
|
|
978
|
+
}
|
|
904
979
|
async function resolveChatResumeId(resume) {
|
|
905
980
|
if (!resume) {
|
|
906
981
|
return void 0;
|
|
@@ -1022,7 +1097,7 @@ program.command("next").description("Show the safest next command for this direc
|
|
|
1022
1097
|
}
|
|
1023
1098
|
printNextCommandReport(report);
|
|
1024
1099
|
});
|
|
1025
|
-
program.command("quickstart").description("Beginner path: initialize, ingest, compile, and optionally open the graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to turn into a vault").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when the input is a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when the input is a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo input").action(runScanCommand);
|
|
1100
|
+
program.command("quickstart").description("Beginner path: initialize, ingest, compile, and optionally open the graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to turn into a vault").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when the input is a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when the input is a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo input").option("--install-agent-rules", "Install configured agent rule files during initialization", false).action(runScanCommand);
|
|
1026
1101
|
program.command("init").description("Initialize a SwarmVault workspace in the current directory.").option("--obsidian", "Generate a minimal .obsidian workspace alongside the vault", false).option(
|
|
1027
1102
|
"--profile <profile>",
|
|
1028
1103
|
"Starter workspace profile or comma-separated preset list (for example: personal-research or reader,timeline)"
|
|
@@ -1030,11 +1105,12 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
|
|
|
1030
1105
|
"--lite",
|
|
1031
1106
|
"Minimal LLM-Wiki starter (raw/, wiki/, wiki/index.md, wiki/log.md, swarmvault.schema.md) without config, state, or agent installs",
|
|
1032
1107
|
false
|
|
1033
|
-
).action(async (options) => {
|
|
1108
|
+
).option("--install-agent-rules", "Install configured agent rule files during initialization", false).action(async (options) => {
|
|
1034
1109
|
await initVault(process2.cwd(), {
|
|
1035
1110
|
obsidian: options.obsidian ?? false,
|
|
1036
1111
|
profile: options.profile,
|
|
1037
|
-
lite: options.lite ?? false
|
|
1112
|
+
lite: options.lite ?? false,
|
|
1113
|
+
installAgentRules: options.installAgentRules ?? false
|
|
1038
1114
|
});
|
|
1039
1115
|
if (isJson()) {
|
|
1040
1116
|
emitJson({
|
|
@@ -1042,7 +1118,8 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
|
|
|
1042
1118
|
rootDir: process2.cwd(),
|
|
1043
1119
|
obsidian: options.obsidian ?? false,
|
|
1044
1120
|
profile: options.profile ?? "default",
|
|
1045
|
-
lite: options.lite ?? false
|
|
1121
|
+
lite: options.lite ?? false,
|
|
1122
|
+
installAgentRules: options.installAgentRules ?? false
|
|
1046
1123
|
});
|
|
1047
1124
|
} else {
|
|
1048
1125
|
log(options.lite ? "Initialized SwarmVault lite workspace." : "Initialized SwarmVault workspace.");
|
|
@@ -1074,11 +1151,10 @@ program.command("ingest").description("Ingest a local file path, directory path,
|
|
|
1074
1151
|
video: options.video,
|
|
1075
1152
|
extractClasses,
|
|
1076
1153
|
resume: options.resume,
|
|
1077
|
-
redact: options.redact
|
|
1154
|
+
redact: options.redact,
|
|
1155
|
+
progress: !isJson()
|
|
1078
1156
|
};
|
|
1079
|
-
const directoryResult = !/^https?:\/\//i.test(input) ? await
|
|
1080
|
-
(fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
|
|
1081
|
-
) : null;
|
|
1157
|
+
const directoryResult = !/^https?:\/\//i.test(input) ? await stat(input).then((inputStat) => inputStat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null) : null;
|
|
1082
1158
|
if (directoryResult) {
|
|
1083
1159
|
const scope2 = options.review || guideEnabled ? await (async () => {
|
|
1084
1160
|
const pathModule = await import("path");
|
|
@@ -1837,8 +1913,8 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
|
|
|
1837
1913
|
});
|
|
1838
1914
|
});
|
|
1839
1915
|
graph.command("export").description(
|
|
1840
|
-
"Export the graph as HTML, report, SVG, GraphML, Cypher, JSON, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
|
|
1841
|
-
).option("--html <output>", "Output HTML file path").option("--html-standalone <output>", "Output lightweight standalone HTML file path (vis.js, no build tooling)").option("--report <output>", "Output self-contained HTML report (graph stats, key nodes, communities)").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--neo4j <output>", "Compatibility alias for --cypher, writing a Neo4j Cypher import file").option("--json <output>", "Output JSON file path").option("--obsidian <output>", "Output Obsidian vault directory path").option("--canvas <output>", "Output Obsidian canvas file path").option("--full", "Include the full graph in HTML export (default; queries traverse complete graph)", true).option("--overview", "Use overview sampling for HTML export (smaller file, queries limited to sampled nodes)", false).action(
|
|
1916
|
+
"Export the graph as HTML, report, SVG, GraphML, Cypher, JSON, callflow HTML, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
|
|
1917
|
+
).option("--html <output>", "Output HTML file path").option("--html-standalone <output>", "Output lightweight standalone HTML file path (vis.js, no build tooling)").option("--report <output>", "Output self-contained HTML report (graph stats, key nodes, communities)").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--neo4j <output>", "Compatibility alias for --cypher, writing a Neo4j Cypher import file").option("--json <output>", "Output JSON file path").option("--callflow <output>", "Output directed callflow HTML file path").option("--obsidian <output>", "Output Obsidian vault directory path").option("--canvas <output>", "Output Obsidian canvas file path").option("--full", "Include the full graph in HTML export (default; queries traverse complete graph)", true).option("--overview", "Use overview sampling for HTML export (smaller file, queries limited to sampled nodes)", false).action(
|
|
1842
1918
|
async (options) => {
|
|
1843
1919
|
const useFullGraph = options.overview ? false : options.full ?? true;
|
|
1844
1920
|
const targets = [
|
|
@@ -1850,12 +1926,13 @@ graph.command("export").description(
|
|
|
1850
1926
|
options.cypher ? { format: "cypher", outputPath: options.cypher } : null,
|
|
1851
1927
|
options.neo4j ? { format: "cypher", outputPath: options.neo4j } : null,
|
|
1852
1928
|
options.json ? { format: "json", outputPath: options.json } : null,
|
|
1929
|
+
options.callflow ? { format: "callflow", outputPath: options.callflow } : null,
|
|
1853
1930
|
options.obsidian ? { format: "obsidian", outputPath: options.obsidian } : null,
|
|
1854
1931
|
options.canvas ? { format: "canvas", outputPath: options.canvas } : null
|
|
1855
1932
|
].filter((target) => Boolean(target));
|
|
1856
1933
|
if (targets.length === 0) {
|
|
1857
1934
|
throw new Error(
|
|
1858
|
-
"Pass at least one of --html, --html-standalone, --report, --svg, --graphml, --cypher, --neo4j, --json, --obsidian, or --canvas."
|
|
1935
|
+
"Pass at least one of --html, --html-standalone, --report, --svg, --graphml, --cypher, --neo4j, --json, --callflow, --obsidian, or --canvas."
|
|
1859
1936
|
);
|
|
1860
1937
|
}
|
|
1861
1938
|
const results = [];
|
|
@@ -1994,6 +2071,24 @@ graph.command("blast").description("Show the blast radius of changing a file or
|
|
|
1994
2071
|
log(` ${" ".repeat(mod.depth - 1)}${mod.label} (depth ${mod.depth})`);
|
|
1995
2072
|
}
|
|
1996
2073
|
});
|
|
2074
|
+
graph.command("cycles").description("Find directed cycles in the compiled graph, defaulting to import edges.").option("--relation <name>", "Relation name to follow (repeatable; default: imports)", collectRepeated, []).option("--limit <n>", "Maximum cycles to report", "25").option("--max-depth <n>", "Maximum cycle depth", "25").action(async (options) => {
|
|
2075
|
+
const { paths } = await loadVaultConfig(process2.cwd());
|
|
2076
|
+
const raw = await readFile2(paths.graphPath, "utf-8");
|
|
2077
|
+
const graphArtifact = JSON.parse(raw);
|
|
2078
|
+
const result = findGraphCycles(graphArtifact, {
|
|
2079
|
+
relations: options.relation?.length ? options.relation : ["imports"],
|
|
2080
|
+
limit: parsePositiveInt(options.limit, 25),
|
|
2081
|
+
maxDepth: parsePositiveInt(options.maxDepth, 25)
|
|
2082
|
+
});
|
|
2083
|
+
if (isJson()) {
|
|
2084
|
+
emitJson(result);
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
log(result.summary);
|
|
2088
|
+
for (const cycle of result.cycles) {
|
|
2089
|
+
log(`- ${cycle.labels.join(" -> ")} -> ${cycle.labels[0]} (${cycle.relations.join(", ")})`);
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
1997
2092
|
graph.command("supersession").description("Record that one page has been replaced by another (writes a superseded_by edge).").argument("<pageId>", "Page id or path of the older page").argument("<replacedById>", "Page id or path of the replacement page").action(async (pageId, replacedById) => {
|
|
1998
2093
|
const result = await createSupersessionEdge(process2.cwd(), pageId, replacedById);
|
|
1999
2094
|
if (isJson()) {
|
|
@@ -2177,6 +2272,88 @@ provider.command("setup").description("Interactive setup for a provider (current
|
|
|
2177
2272
|
log(`Left tasks.audioProvider = "${registration.previousAudioProvider}" untouched (use --set-audio-provider to override).`);
|
|
2178
2273
|
}
|
|
2179
2274
|
});
|
|
2275
|
+
provider.command("add").description("Add or update a named provider in swarmvault.config.json.").argument("<id>", "Provider id").requiredOption("--type <type>", `Provider type: ${providerTypes.join(", ")}`).requiredOption("--model <model>", "Provider model name").option("--base-url <url>", "OpenAI-compatible base URL").option("--api-key-env <name>", "Environment variable that holds the provider API key").option("--capability <capability>", `Provider capability (${providerCapabilities.join(", ")})`, collectRepeated, []).option("--task <task>", `Assign provider to task (${providerTaskKeys.join(", ")})`, collectRepeated, []).option("--api-style <style>", "OpenAI-compatible API style: responses or chat").option("--module <path>", "Custom provider module path").option("--binary-path <path>", "Local provider binary path").option("--model-path <path>", "Local model file path").option("--threads <n>", "Local provider thread count").action(
|
|
2276
|
+
async (id, options) => {
|
|
2277
|
+
const apiStyle = options.apiStyle;
|
|
2278
|
+
if (apiStyle && apiStyle !== "responses" && apiStyle !== "chat") {
|
|
2279
|
+
throw new Error("--api-style must be responses or chat.");
|
|
2280
|
+
}
|
|
2281
|
+
const threads = options.threads ? parsePositiveInt(options.threads, 0) || void 0 : void 0;
|
|
2282
|
+
const result = await addProviderConfig({
|
|
2283
|
+
rootDir: process2.cwd(),
|
|
2284
|
+
providerId: id,
|
|
2285
|
+
provider: {
|
|
2286
|
+
type: parseProviderType(options.type),
|
|
2287
|
+
model: options.model,
|
|
2288
|
+
baseUrl: options.baseUrl,
|
|
2289
|
+
apiKeyEnv: options.apiKeyEnv,
|
|
2290
|
+
capabilities: options.capability?.map(parseProviderCapability),
|
|
2291
|
+
apiStyle,
|
|
2292
|
+
module: options.module,
|
|
2293
|
+
binaryPath: options.binaryPath,
|
|
2294
|
+
modelPath: options.modelPath,
|
|
2295
|
+
threads
|
|
2296
|
+
},
|
|
2297
|
+
tasks: options.task?.map(parseProviderTask)
|
|
2298
|
+
});
|
|
2299
|
+
if (isJson()) {
|
|
2300
|
+
emitJson(result);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
log(`${result.added ? "Added" : result.updated ? "Updated" : "Kept"} provider ${result.providerId} in ${result.configPath}.`);
|
|
2304
|
+
if (result.updatedTasks.length) {
|
|
2305
|
+
log(`Assigned tasks: ${result.updatedTasks.join(", ")}`);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
);
|
|
2309
|
+
provider.command("list").description("List configured providers and task assignments.").action(async () => {
|
|
2310
|
+
const entries = await listProviderConfigEntries(process2.cwd());
|
|
2311
|
+
if (isJson()) {
|
|
2312
|
+
emitJson(entries);
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
if (!entries.length) {
|
|
2316
|
+
log("No providers configured.");
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
for (const entry of entries) {
|
|
2320
|
+
const tasks = entry.assignedTasks.length ? ` tasks=${entry.assignedTasks.join(",")}` : "";
|
|
2321
|
+
const key = entry.apiKeyEnv ? ` key=${entry.apiKeyEnv}` : "";
|
|
2322
|
+
log(`${entry.id} type=${entry.type} model=${entry.model}${key}${tasks}`);
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
provider.command("show").description("Show one configured provider.").argument("<id>", "Provider id").action(async (id) => {
|
|
2326
|
+
const entry = await getProviderConfigEntry(process2.cwd(), id);
|
|
2327
|
+
if (!entry) {
|
|
2328
|
+
throw new Error(`Provider ${id} is not configured.`);
|
|
2329
|
+
}
|
|
2330
|
+
if (isJson()) {
|
|
2331
|
+
emitJson(entry);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
log(`${entry.id}`);
|
|
2335
|
+
log(`type=${entry.type}`);
|
|
2336
|
+
log(`model=${entry.model}`);
|
|
2337
|
+
if (entry.baseUrl) log(`baseUrl=${entry.baseUrl}`);
|
|
2338
|
+
if (entry.apiKeyEnv) log(`apiKeyEnv=${entry.apiKeyEnv}`);
|
|
2339
|
+
if (entry.capabilities.length) log(`capabilities=${entry.capabilities.join(",")}`);
|
|
2340
|
+
if (entry.assignedTasks.length) log(`tasks=${entry.assignedTasks.join(",")}`);
|
|
2341
|
+
});
|
|
2342
|
+
provider.command("remove").description("Remove a configured provider and reassign its tasks to a fallback provider.").argument("<id>", "Provider id").option("--fallback <id>", "Fallback provider for tasks currently assigned to the removed provider").action(async (id, options) => {
|
|
2343
|
+
const result = await removeProviderConfig({
|
|
2344
|
+
rootDir: process2.cwd(),
|
|
2345
|
+
providerId: id,
|
|
2346
|
+
fallbackProviderId: options.fallback
|
|
2347
|
+
});
|
|
2348
|
+
if (isJson()) {
|
|
2349
|
+
emitJson(result);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
log(`${result.removed ? "Removed" : "No provider named"} ${id}.`);
|
|
2353
|
+
if (result.updatedTasks.length) {
|
|
2354
|
+
log(`Reassigned tasks: ${result.updatedTasks.join(", ")}`);
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2180
2357
|
async function confirmInteractive(message) {
|
|
2181
2358
|
if (!process2.stdin.isTTY) return false;
|
|
2182
2359
|
const rl = createInterface({ input: process2.stdin, output: process2.stderr });
|
|
@@ -2390,29 +2567,44 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
|
|
|
2390
2567
|
process2.exit(0);
|
|
2391
2568
|
});
|
|
2392
2569
|
});
|
|
2393
|
-
program.command("install").description("Install SwarmVault instructions for an agent in the current project.")
|
|
2570
|
+
var install = program.command("install").description("Install SwarmVault instructions for an agent in the current project.");
|
|
2571
|
+
install.command("status").description("Show whether SwarmVault instructions are installed for an agent.").requiredOption("--agent <agent>", "Agent name").option("--hook", "Include hook/plugin targets in the status check", false).option("--scope <scope>", "Install scope to inspect: project or user", "project").action(async (options) => {
|
|
2572
|
+
const scope = options.scope === "user" ? "user" : "project";
|
|
2573
|
+
const result = await getAgentInstallStatus(process2.cwd(), options.agent, { hook: options.hook ?? false, scope });
|
|
2574
|
+
if (isJson()) {
|
|
2575
|
+
emitJson(result);
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
log(`${result.agent} ${result.installed ? "installed" : "not installed"} (${result.scope}${result.hook ? ", hook" : ""})`);
|
|
2579
|
+
for (const target of result.targets) {
|
|
2580
|
+
log(`${target.exists ? "ok" : "missing"} ${target.path}`);
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
install.option(
|
|
2394
2584
|
"--agent <agent>",
|
|
2395
|
-
"claude, codex, cursor, gemini, goose, opencode, copilot, aider, droid, pi, trae, claw, kiro, hermes, antigravity, vscode, amp, augment, adal, bob, cline, codebuddy, command-code, continue, cortex, crush, deepagents, firebender, iflow, junie, kilo-code, kimi, kode, mcpjam, mistral-vibe, mux, neovate, openclaw, openhands, pochi, qoder, qwen-code, replit, roo-code, trae-cn, warp, windsurf, or zencoder"
|
|
2396
|
-
).option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).action(
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2585
|
+
"claude, codex, cursor, gemini, goose, opencode, copilot, aider, droid, pi, trae, claw, kiro, kilo, hermes, antigravity, vscode, amp, augment, adal, bob, cline, codebuddy, command-code, continue, cortex, crush, deepagents, devin, firebender, iflow, junie, kilo-code, kimi, kode, mcpjam, mistral-vibe, mux, neovate, openclaw, openhands, pochi, qoder, qwen-code, replit, roo-code, trae-cn, warp, windsurf, or zencoder"
|
|
2586
|
+
).option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).option("--scope <scope>", "Install scope: project or user", "project").action(async (options) => {
|
|
2587
|
+
if (!options.agent) {
|
|
2588
|
+
throw new Error("Specify --agent <agent>.");
|
|
2589
|
+
}
|
|
2590
|
+
const hookCapableAgents = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "gemini", "copilot", "kilo"]);
|
|
2591
|
+
if (options.hook && !hookCapableAgents.has(options.agent)) {
|
|
2592
|
+
throw new Error("--hook is only supported for --agent codex, claude, opencode, gemini, copilot, or kilo");
|
|
2593
|
+
}
|
|
2594
|
+
const scope = options.scope === "user" ? "user" : "project";
|
|
2595
|
+
const result = await installAgent(process2.cwd(), options.agent, { hook: options.hook ?? false, scope });
|
|
2596
|
+
if (isJson()) {
|
|
2597
|
+
emitJson({ ...result, hook: options.hook ?? false, scope });
|
|
2598
|
+
} else {
|
|
2599
|
+
log(`Installed rules into ${result.target}`);
|
|
2600
|
+
if (result.targets.length > 1) {
|
|
2601
|
+
log(`Also wrote: ${result.targets.filter((entry) => entry !== result.target).join(", ")}`);
|
|
2401
2602
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
emitJson({ ...result, hook: options.hook ?? false });
|
|
2405
|
-
} else {
|
|
2406
|
-
log(`Installed rules into ${result.target}`);
|
|
2407
|
-
if (result.targets.length > 1) {
|
|
2408
|
-
log(`Also wrote: ${result.targets.filter((entry) => entry !== result.target).join(", ")}`);
|
|
2409
|
-
}
|
|
2410
|
-
for (const warning of result.warnings ?? []) {
|
|
2411
|
-
emitNotice(warning);
|
|
2412
|
-
}
|
|
2603
|
+
for (const warning of result.warnings ?? []) {
|
|
2604
|
+
emitNotice(warning);
|
|
2413
2605
|
}
|
|
2414
2606
|
}
|
|
2415
|
-
);
|
|
2607
|
+
});
|
|
2416
2608
|
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) => {
|
|
2417
2609
|
const { mkdtemp, writeFile: writeFile3, mkdir: mkdir3 } = await import("fs/promises");
|
|
2418
2610
|
const { tmpdir } = await import("os");
|
|
@@ -2765,8 +2957,8 @@ retrieval.command("doctor").description("Diagnose retrieval index problems and o
|
|
|
2765
2957
|
log(`Warning: ${warning}`);
|
|
2766
2958
|
}
|
|
2767
2959
|
});
|
|
2768
|
-
program.command("scan", { hidden: true }).description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").action(runScanCommand);
|
|
2769
|
-
program.command("clone", { hidden: true }).description("Compatibility alias for scan: initialize, clone/register a public repo URL, and compile it into the vault.").argument("<input>", "Public GitHub repo URL or local directory to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").action(runScanCommand);
|
|
2960
|
+
program.command("scan", { hidden: true }).description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<input>", "Directory or public GitHub repo root URL to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").option("--install-agent-rules", "Install configured agent rule files during initialization", false).action(runScanCommand);
|
|
2961
|
+
program.command("clone", { hidden: true }).description("Compatibility alias for scan: initialize, clone/register a public repo URL, and compile it into the vault.").argument("<input>", "Public GitHub repo URL or local directory to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").option("--no-viz", "Compatibility alias for --no-serve; skip launching the graph viewer after compile").option("--mcp", "Start the MCP stdio server after compile instead of launching the graph viewer", false).option("--branch <name>", "GitHub branch to clone when scanning a public repo URL").option("--ref <ref>", "Git ref, tag, or commit to check out when scanning a public repo URL").option("--checkout-dir <path>", "Persistent checkout directory for a public GitHub repo scan").option("--install-agent-rules", "Install configured agent rule files during initialization", false).action(runScanCommand);
|
|
2770
2962
|
function enableStructuredJsonOnSubcommands(command) {
|
|
2771
2963
|
for (const subcommand of command.commands) {
|
|
2772
2964
|
const hasJsonOption = subcommand.options.some((option) => option.attributeName() === "json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmvaultai/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.0",
|
|
4
4
|
"description": "Global CLI for SwarmVault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepublishOnly": "node ../../scripts/check-release-sync.mjs && node ../../scripts/check-published-manifests.mjs"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@swarmvaultai/engine": "3.
|
|
47
|
+
"@swarmvaultai/engine": "3.16.0",
|
|
48
48
|
"commander": "^14.0.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|