@swarmvaultai/cli 1.3.0 → 3.0.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 +75 -15
- package/dist/index.js +341 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,8 +41,12 @@ swarmvault compile --max-tokens 120000
|
|
|
41
41
|
swarmvault diff
|
|
42
42
|
swarmvault graph share --post
|
|
43
43
|
swarmvault graph share --svg ./share-card.svg
|
|
44
|
+
swarmvault graph share --bundle ./share-kit
|
|
44
45
|
swarmvault benchmark
|
|
45
46
|
swarmvault query "What keeps recurring?" --commit
|
|
47
|
+
swarmvault context build "Ship this feature safely" --target ./src --budget 8000
|
|
48
|
+
swarmvault task start "Ship this feature safely" --target ./src --agent codex
|
|
49
|
+
swarmvault retrieval status
|
|
46
50
|
swarmvault query "Turn this into slides" --format slides
|
|
47
51
|
swarmvault explore "What should I research next?" --steps 3
|
|
48
52
|
swarmvault lint --deep
|
|
@@ -87,7 +91,7 @@ Quick-start a scratch vault from a local directory in one command.
|
|
|
87
91
|
- initializes the current directory as a SwarmVault workspace
|
|
88
92
|
- ingests the supplied directory as local sources
|
|
89
93
|
- compiles the vault immediately
|
|
90
|
-
- writes `wiki/graph/share-card.md
|
|
94
|
+
- writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/`, then prints the paths
|
|
91
95
|
- starts `graph serve` unless you pass `--no-serve`
|
|
92
96
|
- respects `--port` when you want a specific viewer port
|
|
93
97
|
|
|
@@ -100,7 +104,7 @@ Create a temporary sample vault with bundled sources, compile it immediately, an
|
|
|
100
104
|
- writes the demo vault under the system temp directory
|
|
101
105
|
- requires no API keys or extra setup
|
|
102
106
|
- is the fastest way to inspect the full init + ingest + compile + graph workflow on a clean machine
|
|
103
|
-
- writes `wiki/graph/share-card.md
|
|
107
|
+
- writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/` inside the demo vault
|
|
104
108
|
- respects `--port` when you want a specific viewer port
|
|
105
109
|
|
|
106
110
|
### `swarmvault diff`
|
|
@@ -210,8 +214,8 @@ Compile the current manifests into:
|
|
|
210
214
|
|
|
211
215
|
- generated markdown in `wiki/`
|
|
212
216
|
- structured graph data in `state/graph.json`
|
|
213
|
-
- local
|
|
214
|
-
- share cards at `wiki/graph/share-card.md` and `wiki/graph/share-card.svg`
|
|
217
|
+
- local retrieval data in `state/retrieval/`
|
|
218
|
+
- share cards at `wiki/graph/share-card.md` and `wiki/graph/share-card.svg`, plus a portable share kit at `wiki/graph/share-kit/`
|
|
215
219
|
|
|
216
220
|
The compiler also reads `swarmvault.schema.md` and records a `schema_hash` plus lifecycle metadata such as `status`, `created_at`, `updated_at`, `compiled_from`, and `managed_by` in generated pages so schema edits can mark pages stale without losing lifecycle state.
|
|
217
221
|
|
|
@@ -269,13 +273,47 @@ By default, the answer is written into `wiki/outputs/` and immediately registere
|
|
|
269
273
|
- `wiki/index.md`
|
|
270
274
|
- `wiki/outputs/index.md`
|
|
271
275
|
- `state/graph.json`
|
|
272
|
-
- `state/
|
|
276
|
+
- `state/retrieval/`
|
|
273
277
|
|
|
274
278
|
Saved outputs also carry related page, node, and source metadata so SwarmVault can refresh related source, concept, and entity pages immediately.
|
|
275
279
|
|
|
276
280
|
Human-authored pages in `wiki/insights/` are also indexed into search and query context, but SwarmVault does not rewrite them after initialization.
|
|
277
281
|
|
|
278
|
-
By default, query uses the local SQLite
|
|
282
|
+
By default, query uses the local SQLite retrieval index. When an embedding-capable provider is available and `retrieval.hybrid` is not disabled, semantic page matches are fused into the same candidate set before answer generation. `tasks.embeddingProvider` is the explicit way to choose that backend, but SwarmVault can also fall back to a `queryProvider` with embeddings support. Set `retrieval.rerank: true` when you want the configured `queryProvider` to rerank the merged top hits. `--commit` immediately commits saved `wiki/` and `state/` changes when the vault root is inside a git repo.
|
|
283
|
+
|
|
284
|
+
### `swarmvault context build|list|show|delete`
|
|
285
|
+
|
|
286
|
+
Build and manage agent-ready context packs from the compiled vault.
|
|
287
|
+
|
|
288
|
+
- `context build "<goal>"` assembles relevant pages, graph nodes, edges, hyperedges, citations, and explicit omitted entries into a bounded bundle
|
|
289
|
+
- `--target <path-or-node>` anchors the pack around a file, page id, node id, or graph label
|
|
290
|
+
- `--task <id>` links the newly built context pack to an active task
|
|
291
|
+
- `--memory <id>` remains a compatibility alias for `--task`
|
|
292
|
+
- `--budget <tokens>` caps the estimated token budget; over-budget candidates are listed in `omittedItems`
|
|
293
|
+
- `--format markdown|json|llms` controls the printed output shape, while every pack is still saved as JSON
|
|
294
|
+
- saved artifacts live under `state/context-packs/`, with companion markdown pages under `wiki/context/`
|
|
295
|
+
- `context list`, `context show <id>`, and `context delete <id>` manage saved packs
|
|
296
|
+
|
|
297
|
+
Use this before handing work to an agent, starting a PR review, or preserving the evidence bundle behind a design/debugging decision.
|
|
298
|
+
|
|
299
|
+
### `swarmvault task start|update|finish|list|show|resume`
|
|
300
|
+
|
|
301
|
+
Record a durable local task ledger for agent work.
|
|
302
|
+
|
|
303
|
+
- `task start "<goal>" --target <path-or-node>` creates `state/memory/tasks/<id>.json`, `wiki/memory/tasks/<id>.md`, updates `wiki/memory/index.md`, and builds an initial context pack
|
|
304
|
+
- `task update <id>` records notes, decisions, changed paths, context packs, sessions, sources, pages, nodes, git refs, or status changes
|
|
305
|
+
- `task finish <id> --outcome <text>` marks the task completed and can add one or more `--follow-up` entries
|
|
306
|
+
- `task list`, `task show <id>`, and `task resume <id> --format markdown|json|llms` expose the task history for the next agent
|
|
307
|
+
|
|
308
|
+
`query`, `explore`, and `context build` accept `--task <id>` so saved outputs and context packs can attach to an active task. The 2.0 `memory` command group and `--memory <id>` flag remain compatibility aliases.
|
|
309
|
+
|
|
310
|
+
### `swarmvault retrieval status|rebuild|doctor`
|
|
311
|
+
|
|
312
|
+
Inspect and maintain the local retrieval index under `state/retrieval/`.
|
|
313
|
+
|
|
314
|
+
- `retrieval status` reports backend, configured hybrid/rerank behavior, manifest freshness, page count, and the current SQLite shard path
|
|
315
|
+
- `retrieval rebuild` rebuilds the local shard from current wiki pages and refreshes `state/retrieval/manifest.json`
|
|
316
|
+
- `retrieval doctor` checks for stale or missing retrieval artifacts; add `--repair` to rebuild missing or stale artifacts immediately
|
|
279
317
|
|
|
280
318
|
### `swarmvault explore "<question>" [--steps <n>] [--format markdown|report|slides|chart|image]`
|
|
281
319
|
|
|
@@ -344,10 +382,28 @@ Run SwarmVault as a local MCP server over stdio. This exposes the vault to compa
|
|
|
344
382
|
- `compile_vault`
|
|
345
383
|
- `lint_vault`
|
|
346
384
|
- `blast_radius`
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
385
|
+
- `build_context_pack`
|
|
386
|
+
- `list_context_packs`
|
|
387
|
+
- `read_context_pack`
|
|
388
|
+
- `start_task`
|
|
389
|
+
- `update_task`
|
|
390
|
+
- `finish_task`
|
|
391
|
+
- `list_tasks`
|
|
392
|
+
- `read_task`
|
|
393
|
+
- `resume_task`
|
|
394
|
+
- `start_memory_task`
|
|
395
|
+
- `update_memory_task`
|
|
396
|
+
- `finish_memory_task`
|
|
397
|
+
- `list_memory_tasks`
|
|
398
|
+
- `read_memory_task`
|
|
399
|
+
- `resume_memory_task`
|
|
400
|
+
- `retrieval_status`
|
|
401
|
+
- `rebuild_retrieval`
|
|
402
|
+
- `doctor_retrieval`
|
|
403
|
+
|
|
404
|
+
`compile_vault` also accepts `maxTokens` for bounded wiki output, `blast_radius` traces reverse import impact for a file or module target, `build_context_pack` creates the same bounded agent evidence bundles as `swarmvault context build`, the task tools mirror `swarmvault task`, the memory tools mirror the compatibility command group, and retrieval tools inspect or repair the local index.
|
|
405
|
+
|
|
406
|
+
The MCP surface also exposes `swarmvault://schema`, `swarmvault://sessions`, `swarmvault://sessions/{path}`, `swarmvault://context-packs`, `swarmvault://tasks`, `swarmvault://memory-tasks`, and includes `schemaPath` in `workspace_info`.
|
|
351
407
|
|
|
352
408
|
### `swarmvault graph serve`
|
|
353
409
|
|
|
@@ -371,14 +427,15 @@ Inspect graph metadata, community membership, neighbors, provenance, and group-p
|
|
|
371
427
|
|
|
372
428
|
List the most connected bridge-heavy nodes in the current graph.
|
|
373
429
|
|
|
374
|
-
### `swarmvault graph share [--post] [--svg [path]]`
|
|
430
|
+
### `swarmvault graph share [--post] [--svg [path]] [--bundle [dir]]`
|
|
375
431
|
|
|
376
432
|
Print a shareable summary of the compiled graph.
|
|
377
433
|
|
|
378
434
|
- default output is the same markdown shape written to `wiki/graph/share-card.md`
|
|
379
435
|
- `--post` prints only the concise social-post text
|
|
380
436
|
- `--svg [path]` writes the 1200x630 visual share card, defaulting to `wiki/graph/share-card.svg`
|
|
381
|
-
- `--
|
|
437
|
+
- `--bundle [dir]` writes `share-card.md`, `share-post.txt`, `share-card.svg`, `share-preview.html`, and `share-artifact.json`, defaulting to `wiki/graph/share-kit`
|
|
438
|
+
- `--json` emits the structured share artifact for automation; with `--svg`, it also includes `svgPath`; with `--bundle`, it includes `bundlePath` and named output paths
|
|
382
439
|
- useful immediately after `swarmvault scan`, `swarmvault demo`, or a normal compile
|
|
383
440
|
|
|
384
441
|
### `swarmvault graph blast <target> [--depth <n>]`
|
|
@@ -537,15 +594,18 @@ Search behavior is configurable separately from provider routing:
|
|
|
537
594
|
|
|
538
595
|
```json
|
|
539
596
|
{
|
|
540
|
-
"
|
|
597
|
+
"retrieval": {
|
|
598
|
+
"backend": "sqlite",
|
|
599
|
+
"shardSize": 25000,
|
|
541
600
|
"hybrid": true,
|
|
542
601
|
"rerank": false
|
|
543
602
|
}
|
|
544
603
|
}
|
|
545
604
|
```
|
|
546
605
|
|
|
547
|
-
- `
|
|
548
|
-
- `
|
|
606
|
+
- `retrieval.hybrid` defaults to enabled and merges full-text hits with semantic page matches when an embedding-capable provider is available
|
|
607
|
+
- `retrieval.rerank` optionally asks the current `queryProvider` to rerank the merged top hits before query answers are generated
|
|
608
|
+
- `retrieval.backend` currently supports the local SQLite backend
|
|
549
609
|
|
|
550
610
|
## Troubleshooting
|
|
551
611
|
|
package/dist/index.js
CHANGED
|
@@ -15,11 +15,14 @@ import {
|
|
|
15
15
|
autoCommitWikiChanges,
|
|
16
16
|
benchmarkVault,
|
|
17
17
|
blastRadiusVault,
|
|
18
|
+
buildContextPack,
|
|
18
19
|
buildGraphShareArtifact,
|
|
19
20
|
compileVault,
|
|
20
21
|
consolidateVault,
|
|
21
22
|
createSupersessionEdge,
|
|
23
|
+
deleteContextPack,
|
|
22
24
|
deleteManagedSource,
|
|
25
|
+
doctorRetrieval,
|
|
23
26
|
downloadWhisperModel,
|
|
24
27
|
explainGraphVault,
|
|
25
28
|
exploreVault,
|
|
@@ -28,7 +31,9 @@ import {
|
|
|
28
31
|
exportGraphReportHtml,
|
|
29
32
|
exportObsidianCanvas,
|
|
30
33
|
exportObsidianVault,
|
|
34
|
+
finishMemoryTask,
|
|
31
35
|
getGitHookStatus,
|
|
36
|
+
getRetrievalStatus,
|
|
32
37
|
getWatchStatus,
|
|
33
38
|
graphDiff,
|
|
34
39
|
guideManagedSource,
|
|
@@ -42,9 +47,11 @@ import {
|
|
|
42
47
|
lintVault,
|
|
43
48
|
listApprovals,
|
|
44
49
|
listCandidates,
|
|
50
|
+
listContextPacks,
|
|
45
51
|
listGodNodes,
|
|
46
52
|
listManagedSourceRecords,
|
|
47
53
|
listManifests,
|
|
54
|
+
listMemoryTasks,
|
|
48
55
|
listSchedules,
|
|
49
56
|
listWatchedRoots,
|
|
50
57
|
loadVaultConfig,
|
|
@@ -55,13 +62,20 @@ import {
|
|
|
55
62
|
queryGraphVault,
|
|
56
63
|
queryVault,
|
|
57
64
|
readApproval,
|
|
65
|
+
readContextPack,
|
|
58
66
|
readGraphReport,
|
|
67
|
+
readMemoryTask,
|
|
68
|
+
rebuildRetrievalIndex,
|
|
59
69
|
registerLocalWhisperProvider,
|
|
60
70
|
rejectApproval,
|
|
61
71
|
reloadManagedSources,
|
|
62
72
|
removeWatchedRoot,
|
|
73
|
+
renderContextPackLlms,
|
|
74
|
+
renderContextPackMarkdown,
|
|
75
|
+
renderGraphShareBundleFiles,
|
|
63
76
|
renderGraphShareMarkdown,
|
|
64
77
|
renderGraphShareSvg,
|
|
78
|
+
resumeMemoryTask,
|
|
65
79
|
resumeSourceSession,
|
|
66
80
|
reviewManagedSource,
|
|
67
81
|
reviewSourceScope,
|
|
@@ -72,8 +86,10 @@ import {
|
|
|
72
86
|
serveSchedules,
|
|
73
87
|
startGraphServer,
|
|
74
88
|
startMcpServer,
|
|
89
|
+
startMemoryTask,
|
|
75
90
|
summarizeLocalWhisperSetup,
|
|
76
91
|
uninstallGitHooks,
|
|
92
|
+
updateMemoryTask,
|
|
77
93
|
watchVault
|
|
78
94
|
} from "@swarmvaultai/engine";
|
|
79
95
|
import { Command, Option } from "commander";
|
|
@@ -292,9 +308,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
|
|
|
292
308
|
function readCliVersion() {
|
|
293
309
|
try {
|
|
294
310
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
295
|
-
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "
|
|
311
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.0.0";
|
|
296
312
|
} catch {
|
|
297
|
-
return "
|
|
313
|
+
return "3.0.0";
|
|
298
314
|
}
|
|
299
315
|
}
|
|
300
316
|
function parsePositiveInt(value, fallback) {
|
|
@@ -348,6 +364,20 @@ function log(message) {
|
|
|
348
364
|
`);
|
|
349
365
|
}
|
|
350
366
|
}
|
|
367
|
+
async function writeShareBundle(bundlePath, files) {
|
|
368
|
+
await mkdir2(bundlePath, { recursive: true });
|
|
369
|
+
const bundleFiles = {
|
|
370
|
+
markdownPath: path2.join(bundlePath, "share-card.md"),
|
|
371
|
+
postPath: path2.join(bundlePath, "share-post.txt"),
|
|
372
|
+
svgPath: path2.join(bundlePath, "share-card.svg"),
|
|
373
|
+
previewHtmlPath: path2.join(bundlePath, "share-preview.html"),
|
|
374
|
+
artifactJsonPath: path2.join(bundlePath, "share-artifact.json")
|
|
375
|
+
};
|
|
376
|
+
for (const file of files) {
|
|
377
|
+
await writeFile2(path2.join(bundlePath, file.relativePath), file.content, "utf8");
|
|
378
|
+
}
|
|
379
|
+
return bundleFiles;
|
|
380
|
+
}
|
|
351
381
|
function emitNotice(message) {
|
|
352
382
|
process2.stderr.write(`[swarmvault] ${message}
|
|
353
383
|
`);
|
|
@@ -359,8 +389,8 @@ async function maybeEmitHeuristicNotice(commandPath) {
|
|
|
359
389
|
try {
|
|
360
390
|
const { config } = await loadVaultConfig(process2.cwd());
|
|
361
391
|
const analysisTaskKeys = ["compileProvider", "queryProvider", "lintProvider"];
|
|
362
|
-
const usingHeuristic = analysisTaskKeys.every((
|
|
363
|
-
const providerId = config.tasks[
|
|
392
|
+
const usingHeuristic = analysisTaskKeys.every((task2) => {
|
|
393
|
+
const providerId = config.tasks[task2];
|
|
364
394
|
const providerConfig = config.providers[providerId];
|
|
365
395
|
return !providerConfig || providerConfig.type === "heuristic";
|
|
366
396
|
});
|
|
@@ -786,7 +816,7 @@ program.command("consolidate").description("Roll working-tier insights up into e
|
|
|
786
816
|
log(decision);
|
|
787
817
|
}
|
|
788
818
|
});
|
|
789
|
-
program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").option("--commit", "Auto-commit wiki changes after query").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").addOption(
|
|
819
|
+
program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").option("--commit", "Auto-commit wiki changes after query").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").option("--task <id>", "Attach this query output to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(
|
|
790
820
|
new Option("--format <format>", "Output format").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
791
821
|
).action(
|
|
792
822
|
async (question, options) => {
|
|
@@ -794,7 +824,8 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
|
|
|
794
824
|
question,
|
|
795
825
|
save: options.save ?? true,
|
|
796
826
|
format: options.format,
|
|
797
|
-
gapFill: options.gapFill ?? false
|
|
827
|
+
gapFill: options.gapFill ?? false,
|
|
828
|
+
memoryTaskId: options.task ?? options.memory
|
|
798
829
|
});
|
|
799
830
|
if (isJson()) {
|
|
800
831
|
emitJson(result);
|
|
@@ -811,7 +842,235 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
|
|
|
811
842
|
await maybeEmitHeuristicNotice(["query"]);
|
|
812
843
|
}
|
|
813
844
|
);
|
|
814
|
-
program.command("
|
|
845
|
+
var context = program.command("context").description("Build and manage token-bounded agent context packs.");
|
|
846
|
+
context.command("build").description("Build a cited, token-bounded context pack for an agent task.").argument("<goal>", "Task, question, or goal the agent needs context for").option("--target <target>", "Optional page, node, path, project, or label to anchor the pack").option("--budget <tokens>", "Approximate token budget for included context", String(8e3)).option("--task <id>", "Attach the context pack to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(
|
|
847
|
+
async (goal, options) => {
|
|
848
|
+
const budgetTokens = parsePositiveInt(options.budget, 8e3);
|
|
849
|
+
const result = await buildContextPack(process2.cwd(), {
|
|
850
|
+
goal,
|
|
851
|
+
target: options.target,
|
|
852
|
+
budgetTokens,
|
|
853
|
+
format: options.format,
|
|
854
|
+
memoryTaskId: options.task ?? options.memory
|
|
855
|
+
});
|
|
856
|
+
if (isJson()) {
|
|
857
|
+
emitJson(result);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
log(result.rendered);
|
|
861
|
+
log(`Saved context pack to ${result.markdownPath}`);
|
|
862
|
+
log(`Saved context artifact to ${result.artifactPath}`);
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
context.command("list").description("List saved context packs.").action(async () => {
|
|
866
|
+
const packs = await listContextPacks(process2.cwd());
|
|
867
|
+
if (isJson()) {
|
|
868
|
+
emitJson(packs);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (!packs.length) {
|
|
872
|
+
log("No context packs.");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
for (const pack of packs) {
|
|
876
|
+
log(`${pack.id} \u2014 ${pack.goal} (${pack.itemCount} item(s), ${pack.omittedCount} omitted)`);
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
context.command("show").description("Print a saved context pack.").argument("<id>", "Context pack id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
|
|
880
|
+
const pack = await readContextPack(process2.cwd(), id);
|
|
881
|
+
if (!pack) {
|
|
882
|
+
throw new Error(`Context pack not found: ${id}`);
|
|
883
|
+
}
|
|
884
|
+
if (isJson() || options.format === "json") {
|
|
885
|
+
emitJson(pack);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
log(options.format === "llms" ? renderContextPackLlms(pack) : renderContextPackMarkdown(pack));
|
|
889
|
+
});
|
|
890
|
+
context.command("delete").description("Delete a saved context pack artifact and markdown page.").argument("<id>", "Context pack id").action(async (id) => {
|
|
891
|
+
const deleted = await deleteContextPack(process2.cwd(), id);
|
|
892
|
+
if (!deleted) {
|
|
893
|
+
throw new Error(`Context pack not found: ${id}`);
|
|
894
|
+
}
|
|
895
|
+
if (isJson()) {
|
|
896
|
+
emitJson(deleted);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
log(`Deleted context pack ${deleted.id}.`);
|
|
900
|
+
});
|
|
901
|
+
var memory = program.command("memory").description("Manage git-backed agent memory task ledger entries.");
|
|
902
|
+
memory.command("start").description("Start a durable agent memory task and build its initial context pack.").argument("<goal>", "Task goal to preserve in agent memory").option("--target <target>", "Optional page, node, path, project, or label to anchor the initial context pack").option("--budget <tokens>", "Approximate token budget for the initial context pack", String(8e3)).option("--agent <name>", "Agent name to record on the task").option("--context-pack <id>", "Attach an existing context pack instead of building a new one").action(async (goal, options) => {
|
|
903
|
+
const result = await startMemoryTask(process2.cwd(), {
|
|
904
|
+
goal,
|
|
905
|
+
target: options.target,
|
|
906
|
+
budgetTokens: parsePositiveInt(options.budget, 8e3),
|
|
907
|
+
agent: options.agent,
|
|
908
|
+
contextPackId: options.contextPack
|
|
909
|
+
});
|
|
910
|
+
if (isJson()) {
|
|
911
|
+
emitJson(result);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
log(result.task.id);
|
|
915
|
+
log(`Saved memory task to ${result.markdownPath}`);
|
|
916
|
+
});
|
|
917
|
+
memory.command("update").description("Append a note, decision, path, context pack, or status change to a memory task.").argument("<id>", "Memory task id").option("--note <text>", "Append a task note").option("--decision <text>", "Append a decision").option("--changed-path <path>", "Record a changed file or wiki path").option("--context-pack <id>", "Attach a context pack").option("--session <id>", "Attach a session id").option("--source <id>", "Attach a source id").option("--page <id>", "Attach a page id").option("--node <id>", "Attach a graph node id").option("--git-ref <ref>", "Attach a git ref").addOption(new Option("--status <status>", "Task status").choices(["active", "blocked", "completed", "archived"])).action(
|
|
918
|
+
async (id, options) => {
|
|
919
|
+
const result = await updateMemoryTask(process2.cwd(), id, {
|
|
920
|
+
note: options.note,
|
|
921
|
+
decision: options.decision,
|
|
922
|
+
changedPath: options.changedPath,
|
|
923
|
+
contextPackId: options.contextPack,
|
|
924
|
+
sessionId: options.session,
|
|
925
|
+
sourceId: options.source,
|
|
926
|
+
pageId: options.page,
|
|
927
|
+
nodeId: options.node,
|
|
928
|
+
gitRef: options.gitRef,
|
|
929
|
+
status: options.status
|
|
930
|
+
});
|
|
931
|
+
if (isJson()) {
|
|
932
|
+
emitJson(result);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
log(`Updated memory task ${result.task.id}.`);
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
memory.command("finish").description("Finish a memory task with an outcome and optional follow-up.").argument("<id>", "Memory task id").requiredOption("--outcome <text>", "Outcome to record").option("--follow-up <text>", "Follow-up to preserve for the next agent").action(async (id, options) => {
|
|
939
|
+
const result = await finishMemoryTask(process2.cwd(), id, {
|
|
940
|
+
outcome: options.outcome,
|
|
941
|
+
followUp: options.followUp
|
|
942
|
+
});
|
|
943
|
+
if (isJson()) {
|
|
944
|
+
emitJson(result);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
log(`Finished memory task ${result.task.id}.`);
|
|
948
|
+
});
|
|
949
|
+
memory.command("list").description("List saved agent memory tasks.").action(async () => {
|
|
950
|
+
const tasks = await listMemoryTasks(process2.cwd());
|
|
951
|
+
if (isJson()) {
|
|
952
|
+
emitJson(tasks);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (!tasks.length) {
|
|
956
|
+
log("No memory tasks.");
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
for (const task2 of tasks) {
|
|
960
|
+
log(`${task2.id} \u2014 ${task2.status} \u2014 ${task2.goal}`);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
memory.command("show").description("Print a saved agent memory task.").argument("<id>", "Memory task id").action(async (id) => {
|
|
964
|
+
const task2 = await readMemoryTask(process2.cwd(), id);
|
|
965
|
+
if (!task2) {
|
|
966
|
+
throw new Error(`Memory task not found: ${id}`);
|
|
967
|
+
}
|
|
968
|
+
if (isJson()) {
|
|
969
|
+
emitJson(task2);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
log(`Task: ${task2.title}`);
|
|
973
|
+
log(`Status: ${task2.status}`);
|
|
974
|
+
log(`Goal: ${task2.goal}`);
|
|
975
|
+
if (task2.outcome) log(`Outcome: ${task2.outcome}`);
|
|
976
|
+
if (task2.followUps.length) log(`Follow-ups: ${task2.followUps.join("; ")}`);
|
|
977
|
+
log(`Markdown: ${task2.markdownPath}`);
|
|
978
|
+
});
|
|
979
|
+
memory.command("resume").description("Render a memory task handoff for the next agent.").argument("<id>", "Memory task id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
|
|
980
|
+
const result = await resumeMemoryTask(process2.cwd(), id, { format: options.format });
|
|
981
|
+
if (isJson() || options.format === "json") {
|
|
982
|
+
emitJson(result);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
log(result.rendered);
|
|
986
|
+
});
|
|
987
|
+
var task = program.command("task").description("Manage git-backed agent task ledger entries.");
|
|
988
|
+
task.command("start").description("Start a durable agent task and build its initial context pack.").argument("<goal>", "Task goal to preserve").option("--target <target>", "Optional page, node, path, project, or label to anchor the initial context pack").option("--budget <tokens>", "Approximate token budget for the initial context pack", String(8e3)).option("--agent <name>", "Agent name to record on the task").option("--context-pack <id>", "Attach an existing context pack instead of building a new one").action(async (goal, options) => {
|
|
989
|
+
const result = await startMemoryTask(process2.cwd(), {
|
|
990
|
+
goal,
|
|
991
|
+
target: options.target,
|
|
992
|
+
budgetTokens: parsePositiveInt(options.budget, 8e3),
|
|
993
|
+
agent: options.agent,
|
|
994
|
+
contextPackId: options.contextPack
|
|
995
|
+
});
|
|
996
|
+
if (isJson()) {
|
|
997
|
+
emitJson(result);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
log(result.task.id);
|
|
1001
|
+
log(`Saved task to ${result.markdownPath}`);
|
|
1002
|
+
});
|
|
1003
|
+
task.command("update").description("Append a note, decision, path, context pack, or status change to a task.").argument("<id>", "Task id").option("--note <text>", "Append a task note").option("--decision <text>", "Append a decision").option("--changed-path <path>", "Record a changed file or wiki path").option("--context-pack <id>", "Attach a context pack").option("--session <id>", "Attach a session id").option("--source <id>", "Attach a source id").option("--page <id>", "Attach a page id").option("--node <id>", "Attach a graph node id").option("--git-ref <ref>", "Attach a git ref").addOption(new Option("--status <status>", "Task status").choices(["active", "blocked", "completed", "archived"])).action(
|
|
1004
|
+
async (id, options) => {
|
|
1005
|
+
const result = await updateMemoryTask(process2.cwd(), id, {
|
|
1006
|
+
note: options.note,
|
|
1007
|
+
decision: options.decision,
|
|
1008
|
+
changedPath: options.changedPath,
|
|
1009
|
+
contextPackId: options.contextPack,
|
|
1010
|
+
sessionId: options.session,
|
|
1011
|
+
sourceId: options.source,
|
|
1012
|
+
pageId: options.page,
|
|
1013
|
+
nodeId: options.node,
|
|
1014
|
+
gitRef: options.gitRef,
|
|
1015
|
+
status: options.status
|
|
1016
|
+
});
|
|
1017
|
+
if (isJson()) {
|
|
1018
|
+
emitJson(result);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
log(`Updated task ${result.task.id}.`);
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
task.command("finish").description("Finish a task with an outcome and optional follow-up.").argument("<id>", "Task id").requiredOption("--outcome <text>", "Outcome to record").option("--follow-up <text>", "Follow-up to preserve for the next agent").action(async (id, options) => {
|
|
1025
|
+
const result = await finishMemoryTask(process2.cwd(), id, {
|
|
1026
|
+
outcome: options.outcome,
|
|
1027
|
+
followUp: options.followUp
|
|
1028
|
+
});
|
|
1029
|
+
if (isJson()) {
|
|
1030
|
+
emitJson(result);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
log(`Finished task ${result.task.id}.`);
|
|
1034
|
+
});
|
|
1035
|
+
task.command("list").description("List saved agent tasks.").action(async () => {
|
|
1036
|
+
const tasks = await listMemoryTasks(process2.cwd());
|
|
1037
|
+
if (isJson()) {
|
|
1038
|
+
emitJson(tasks);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (!tasks.length) {
|
|
1042
|
+
log("No tasks.");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
for (const entry of tasks) {
|
|
1046
|
+
log(`${entry.id} \u2014 ${entry.status} \u2014 ${entry.goal}`);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
task.command("show").description("Print a saved agent task.").argument("<id>", "Task id").action(async (id) => {
|
|
1050
|
+
const entry = await readMemoryTask(process2.cwd(), id);
|
|
1051
|
+
if (!entry) {
|
|
1052
|
+
throw new Error(`Task not found: ${id}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (isJson()) {
|
|
1055
|
+
emitJson(entry);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
log(`Task: ${entry.title}`);
|
|
1059
|
+
log(`Status: ${entry.status}`);
|
|
1060
|
+
log(`Goal: ${entry.goal}`);
|
|
1061
|
+
if (entry.outcome) log(`Outcome: ${entry.outcome}`);
|
|
1062
|
+
if (entry.followUps.length) log(`Follow-ups: ${entry.followUps.join("; ")}`);
|
|
1063
|
+
log(`Markdown: ${entry.markdownPath}`);
|
|
1064
|
+
});
|
|
1065
|
+
task.command("resume").description("Render a task handoff for the next agent.").argument("<id>", "Task id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
|
|
1066
|
+
const result = await resumeMemoryTask(process2.cwd(), id, { format: options.format });
|
|
1067
|
+
if (isJson() || options.format === "json") {
|
|
1068
|
+
emitJson(result);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
log(result.rendered);
|
|
1072
|
+
});
|
|
1073
|
+
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").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.exploreProvider).").option("--task <id>", "Attach this exploration to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(
|
|
815
1074
|
new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
|
|
816
1075
|
).action(
|
|
817
1076
|
async (question, options) => {
|
|
@@ -820,7 +1079,8 @@ program.command("explore").description("Run a save-first multi-step exploration
|
|
|
820
1079
|
question,
|
|
821
1080
|
steps: stepCount,
|
|
822
1081
|
format: options.format,
|
|
823
|
-
gapFill: options.gapFill ?? false
|
|
1082
|
+
gapFill: options.gapFill ?? false,
|
|
1083
|
+
memoryTaskId: options.task ?? options.memory
|
|
824
1084
|
});
|
|
825
1085
|
if (isJson()) {
|
|
826
1086
|
emitJson(result);
|
|
@@ -976,9 +1236,10 @@ graph.command("export").description(
|
|
|
976
1236
|
}
|
|
977
1237
|
}
|
|
978
1238
|
);
|
|
979
|
-
graph.command("share").description("Print a shareable summary of the compiled graph.").option("--post", "Print only the short social post text", false).option("--svg [path]", "Write the visual SVG share card, defaulting to wiki/graph/share-card.svg").action(async (options) => {
|
|
980
|
-
|
|
981
|
-
|
|
1239
|
+
graph.command("share").description("Print a shareable summary of the compiled graph.").option("--post", "Print only the short social post text", false).option("--svg [path]", "Write the visual SVG share card, defaulting to wiki/graph/share-card.svg").option("--bundle [dir]", "Write the portable share kit bundle, defaulting to wiki/graph/share-kit").action(async (options) => {
|
|
1240
|
+
const outputModeCount = [options.post, options.svg, options.bundle].filter(Boolean).length;
|
|
1241
|
+
if (outputModeCount > 1) {
|
|
1242
|
+
throw new Error("Choose one graph share output mode: --post, --svg, or --bundle.");
|
|
982
1243
|
}
|
|
983
1244
|
const { paths } = await loadVaultConfig(process2.cwd());
|
|
984
1245
|
const raw = await readFile2(paths.graphPath, "utf-8");
|
|
@@ -1000,6 +1261,16 @@ graph.command("share").description("Print a shareable summary of the compiled gr
|
|
|
1000
1261
|
log(`Wrote SVG share card to ${svgPath}`);
|
|
1001
1262
|
return;
|
|
1002
1263
|
}
|
|
1264
|
+
if (options.bundle) {
|
|
1265
|
+
const bundlePath = typeof options.bundle === "string" ? path2.resolve(process2.cwd(), options.bundle) : path2.join(paths.wikiDir, "graph", "share-kit");
|
|
1266
|
+
const bundleFiles = await writeShareBundle(bundlePath, renderGraphShareBundleFiles(artifact));
|
|
1267
|
+
if (isJson()) {
|
|
1268
|
+
emitJson({ ...artifact, bundlePath, bundleFiles });
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
log(`Wrote share kit to ${bundlePath}`);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1003
1274
|
if (isJson()) {
|
|
1004
1275
|
emitJson(artifact);
|
|
1005
1276
|
return;
|
|
@@ -1568,6 +1839,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
|
|
|
1568
1839
|
const { paths } = await loadVaultConfig(demoDir);
|
|
1569
1840
|
const shareCardPath = path3.join(demoDir, "wiki", "graph", "share-card.md");
|
|
1570
1841
|
const shareCardSvgPath = path3.join(demoDir, "wiki", "graph", "share-card.svg");
|
|
1842
|
+
const shareKitPath = path3.join(demoDir, "wiki", "graph", "share-kit");
|
|
1571
1843
|
let graphStats = "";
|
|
1572
1844
|
try {
|
|
1573
1845
|
const raw = await readFile2(paths.graphPath, "utf-8");
|
|
@@ -1587,6 +1859,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
|
|
|
1587
1859
|
log(`Vault location: ${demoDir}`);
|
|
1588
1860
|
log(`Share card: ${shareCardPath}`);
|
|
1589
1861
|
log(`Visual card: ${shareCardSvgPath}`);
|
|
1862
|
+
log(`Share kit: ${shareKitPath}`);
|
|
1590
1863
|
}
|
|
1591
1864
|
if (options.serve !== false) {
|
|
1592
1865
|
const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
|
|
@@ -1597,6 +1870,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
|
|
|
1597
1870
|
graphStats: graphStats.trim(),
|
|
1598
1871
|
shareCardPath,
|
|
1599
1872
|
shareCardSvgPath,
|
|
1873
|
+
shareKitPath,
|
|
1600
1874
|
port: server.port,
|
|
1601
1875
|
url: `http://localhost:${server.port}`
|
|
1602
1876
|
});
|
|
@@ -1617,7 +1891,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
|
|
|
1617
1891
|
process2.exit(0);
|
|
1618
1892
|
});
|
|
1619
1893
|
} else if (isJson()) {
|
|
1620
|
-
emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath, shareCardSvgPath });
|
|
1894
|
+
emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath, shareCardSvgPath, shareKitPath });
|
|
1621
1895
|
} else {
|
|
1622
1896
|
log("");
|
|
1623
1897
|
log("Try next:");
|
|
@@ -1739,6 +2013,49 @@ program.command("diff").description("Show what changed in the knowledge graph si
|
|
|
1739
2013
|
}
|
|
1740
2014
|
}
|
|
1741
2015
|
});
|
|
2016
|
+
var retrieval = program.command("retrieval").description("Inspect and repair the local retrieval index.");
|
|
2017
|
+
retrieval.command("status").description("Show retrieval index health and configuration.").action(async () => {
|
|
2018
|
+
const status = await getRetrievalStatus(process2.cwd());
|
|
2019
|
+
if (isJson()) {
|
|
2020
|
+
emitJson(status);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
log(`Retrieval backend: ${status.configured.backend}`);
|
|
2024
|
+
log(`Index: ${status.indexExists ? "present" : "missing"} (${status.indexPath})`);
|
|
2025
|
+
log(`Manifest: ${status.manifestExists ? "present" : "missing"} (${status.manifestPath})`);
|
|
2026
|
+
log(`Graph: ${status.graphExists ? "present" : "missing"}`);
|
|
2027
|
+
log(`Pages indexed: ${status.pageCount}`);
|
|
2028
|
+
log(`State: ${status.stale ? "stale" : "fresh"}`);
|
|
2029
|
+
for (const warning of status.warnings) {
|
|
2030
|
+
log(`Warning: ${warning}`);
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
retrieval.command("rebuild").description("Rebuild the local retrieval index from the current graph.").action(async () => {
|
|
2034
|
+
const status = await rebuildRetrievalIndex(process2.cwd());
|
|
2035
|
+
if (isJson()) {
|
|
2036
|
+
emitJson(status);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
log(`Rebuilt retrieval index at ${status.indexPath}`);
|
|
2040
|
+
log(`Pages indexed: ${status.pageCount}`);
|
|
2041
|
+
});
|
|
2042
|
+
retrieval.command("doctor").description("Diagnose retrieval index problems and optionally repair them.").option("--repair", "Rebuild stale or missing retrieval artifacts", false).action(async (options) => {
|
|
2043
|
+
const result = await doctorRetrieval(process2.cwd(), { repair: options.repair });
|
|
2044
|
+
if (isJson()) {
|
|
2045
|
+
emitJson(result);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
log(`Retrieval health: ${result.ok ? "ok" : "needs attention"}`);
|
|
2049
|
+
if (result.repaired) {
|
|
2050
|
+
log("Repaired retrieval index.");
|
|
2051
|
+
}
|
|
2052
|
+
if (result.actions.length) {
|
|
2053
|
+
log(`Suggested action(s): ${result.actions.join(", ")}`);
|
|
2054
|
+
}
|
|
2055
|
+
for (const warning of result.status.warnings) {
|
|
2056
|
+
log(`Warning: ${warning}`);
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
1742
2059
|
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) => {
|
|
1743
2060
|
const rootDir = process2.cwd();
|
|
1744
2061
|
await initVault(rootDir, {});
|
|
@@ -1752,17 +2069,27 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
|
|
|
1752
2069
|
const compiled = await compileVault(rootDir, {});
|
|
1753
2070
|
const shareCardPath = path2.join(rootDir, "wiki", "graph", "share-card.md");
|
|
1754
2071
|
const shareCardSvgPath = path2.join(rootDir, "wiki", "graph", "share-card.svg");
|
|
2072
|
+
const shareKitPath = path2.join(rootDir, "wiki", "graph", "share-kit");
|
|
1755
2073
|
if (!isJson()) {
|
|
1756
2074
|
log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
|
|
1757
2075
|
log(`Share card: ${shareCardPath}`);
|
|
1758
2076
|
log(`Visual card: ${shareCardSvgPath}`);
|
|
2077
|
+
log(`Share kit: ${shareKitPath}`);
|
|
1759
2078
|
log("Post text: swarmvault graph share --post");
|
|
1760
2079
|
}
|
|
1761
2080
|
if (options.serve !== false) {
|
|
1762
2081
|
const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
|
|
1763
2082
|
const server = await startGraphServer(rootDir, port, { full: false });
|
|
1764
2083
|
if (isJson()) {
|
|
1765
|
-
emitJson({
|
|
2084
|
+
emitJson({
|
|
2085
|
+
...result,
|
|
2086
|
+
compiled,
|
|
2087
|
+
shareCardPath,
|
|
2088
|
+
shareCardSvgPath,
|
|
2089
|
+
shareKitPath,
|
|
2090
|
+
port: server.port,
|
|
2091
|
+
url: `http://localhost:${server.port}`
|
|
2092
|
+
});
|
|
1766
2093
|
} else {
|
|
1767
2094
|
log(`Graph viewer running at http://localhost:${server.port}`);
|
|
1768
2095
|
}
|
|
@@ -1774,7 +2101,7 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
|
|
|
1774
2101
|
process2.exit(0);
|
|
1775
2102
|
});
|
|
1776
2103
|
} else if (isJson()) {
|
|
1777
|
-
emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath });
|
|
2104
|
+
emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
|
|
1778
2105
|
}
|
|
1779
2106
|
});
|
|
1780
2107
|
function enableStructuredJsonOnSubcommands(command) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmvaultai/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": "
|
|
47
|
+
"@swarmvaultai/engine": "3.0.0",
|
|
48
48
|
"commander": "^14.0.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|