@swarmvaultai/cli 3.15.0 → 3.16.1

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.
Files changed (3) hide show
  1. package/README.md +29 -3
  2. package/dist/index.js +203 -24
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -672,7 +672,15 @@ Trace the reverse-import blast radius of changing a file or module.
672
672
  - follows reverse `imports` edges through the compiled graph
673
673
  - reports affected modules by depth so you can estimate downstream impact before editing
674
674
 
675
- ### `swarmvault graph export --html|--html-standalone|--report|--svg|--graphml|--cypher|--json|--obsidian|--canvas <output>`
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>`
676
684
 
677
685
  Export the current graph as one or more shareable formats:
678
686
 
@@ -683,6 +691,7 @@ Export the current graph as one or more shareable formats:
683
691
  - `--graphml` for graph-tool interoperability
684
692
  - `--cypher` for Neo4j-style import scripts
685
693
  - `--json` for a deterministic machine-readable graph package
694
+ - `--callflow` for compact directed relationship HTML
686
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
687
696
  - `--canvas` for an Obsidian canvas grouped by community
688
697
 
@@ -713,11 +722,11 @@ Defaults:
713
722
  - namespaces every remote record by `vaultId` so multiple vaults can safely share one Neo4j database
714
723
  - upserts current graph records and does not prune stale remote data yet
715
724
 
716
- ### `swarmvault install --agent <agent>`
725
+ ### `swarmvault install --agent <agent> [--scope project|user]`
717
726
 
718
727
  Install agent-specific rules into the current project so an agent understands the SwarmVault workspace contract and workflow.
719
728
 
720
- `init`, `quickstart`, `scan`, and `clone` do not write project-local agent rule files by default. Run `swarmvault install --agent <agent>` for one target at a time, 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.
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.
721
730
 
722
731
  Hook-capable installs:
723
732
 
@@ -727,6 +736,7 @@ swarmvault install --agent claude --hook
727
736
  swarmvault install --agent gemini --hook
728
737
  swarmvault install --agent opencode --hook
729
738
  swarmvault install --agent copilot --hook
739
+ swarmvault install --agent kilo --hook
730
740
  ```
731
741
 
732
742
  Agent target mapping:
@@ -741,9 +751,13 @@ Agent target mapping:
741
751
  - `claw` writes `.claw/skills/swarmvault/SKILL.md`
742
752
  - `droid` writes `.factory/rules/swarmvault.md`
743
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`
744
755
  - `hermes` writes `~/.hermes/skills/swarmvault/SKILL.md` plus `AGENTS.md`
745
756
  - `antigravity` writes `.agents/rules/swarmvault.md` and `.agents/workflows/swarmvault.md`, and removes older fully managed `.agent/` files during reinstall
746
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.
747
761
 
748
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.
749
763
 
@@ -754,6 +768,7 @@ Hook semantics:
754
768
  - `gemini --hook` writes `.gemini/settings.json` plus `.gemini/hooks/swarmvault-graph-first.js` and stays advisory/model-visible
755
769
  - `opencode --hook` writes `.opencode/plugins/swarmvault-graph-first.js` and stays advisory/log-only
756
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`
757
772
 
758
773
  `aider` is intentionally file/config-based in this release rather than hook-based.
759
774
 
@@ -775,6 +790,17 @@ npm install -g @swarmvaultai/cli
775
790
 
776
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.
777
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
+
778
804
  Example:
779
805
 
780
806
  ```json
package/dist/index.js CHANGED
@@ -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.15.0";
339
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.16.1";
334
340
  } catch {
335
- return "3.15.0";
341
+ return "3.16.1";
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);
@@ -1853,8 +1913,8 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
1853
1913
  });
1854
1914
  });
1855
1915
  graph.command("export").description(
1856
- "Export the graph as HTML, report, SVG, GraphML, Cypher, JSON, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
1857
- ).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(
1858
1918
  async (options) => {
1859
1919
  const useFullGraph = options.overview ? false : options.full ?? true;
1860
1920
  const targets = [
@@ -1866,12 +1926,13 @@ graph.command("export").description(
1866
1926
  options.cypher ? { format: "cypher", outputPath: options.cypher } : null,
1867
1927
  options.neo4j ? { format: "cypher", outputPath: options.neo4j } : null,
1868
1928
  options.json ? { format: "json", outputPath: options.json } : null,
1929
+ options.callflow ? { format: "callflow", outputPath: options.callflow } : null,
1869
1930
  options.obsidian ? { format: "obsidian", outputPath: options.obsidian } : null,
1870
1931
  options.canvas ? { format: "canvas", outputPath: options.canvas } : null
1871
1932
  ].filter((target) => Boolean(target));
1872
1933
  if (targets.length === 0) {
1873
1934
  throw new Error(
1874
- "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."
1875
1936
  );
1876
1937
  }
1877
1938
  const results = [];
@@ -2010,6 +2071,24 @@ graph.command("blast").description("Show the blast radius of changing a file or
2010
2071
  log(` ${" ".repeat(mod.depth - 1)}${mod.label} (depth ${mod.depth})`);
2011
2072
  }
2012
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
+ });
2013
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) => {
2014
2093
  const result = await createSupersessionEdge(process2.cwd(), pageId, replacedById);
2015
2094
  if (isJson()) {
@@ -2193,6 +2272,91 @@ provider.command("setup").description("Interactive setup for a provider (current
2193
2272
  log(`Left tasks.audioProvider = "${registration.previousAudioProvider}" untouched (use --set-audio-provider to override).`);
2194
2273
  }
2195
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.reassignedTasks.length) {
2354
+ log(`Reassigned tasks to ${result.fallbackProviderId}: ${result.reassignedTasks.join(", ")}`);
2355
+ }
2356
+ if (result.clearedTasks.length) {
2357
+ log(`Cleared task assignments: ${result.clearedTasks.join(", ")}`);
2358
+ }
2359
+ });
2196
2360
  async function confirmInteractive(message) {
2197
2361
  if (!process2.stdin.isTTY) return false;
2198
2362
  const rl = createInterface({ input: process2.stdin, output: process2.stderr });
@@ -2406,29 +2570,44 @@ program.command("mcp").description("Run SwarmVault as a local MCP server over st
2406
2570
  process2.exit(0);
2407
2571
  });
2408
2572
  });
2409
- program.command("install").description("Install SwarmVault instructions for an agent in the current project.").requiredOption(
2573
+ var install = program.command("install").description("Install SwarmVault instructions for an agent in the current project.");
2574
+ 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) => {
2575
+ const scope = options.scope === "user" ? "user" : "project";
2576
+ const result = await getAgentInstallStatus(process2.cwd(), options.agent, { hook: options.hook ?? false, scope });
2577
+ if (isJson()) {
2578
+ emitJson(result);
2579
+ return;
2580
+ }
2581
+ log(`${result.agent} ${result.installed ? "installed" : "not installed"} (${result.scope}${result.hook ? ", hook" : ""})`);
2582
+ for (const target of result.targets) {
2583
+ log(`${target.exists ? "ok" : "missing"} ${target.path}`);
2584
+ }
2585
+ });
2586
+ install.option(
2410
2587
  "--agent <agent>",
2411
- "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"
2412
- ).option("--hook", "Also install hook/plugin guidance when the target agent supports it", false).action(
2413
- async (options) => {
2414
- const hookCapableAgents = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "gemini", "copilot"]);
2415
- if (options.hook && !hookCapableAgents.has(options.agent)) {
2416
- throw new Error("--hook is only supported for --agent codex, claude, opencode, gemini, or copilot");
2588
+ "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"
2589
+ ).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) => {
2590
+ if (!options.agent) {
2591
+ throw new Error("Specify --agent <agent>.");
2592
+ }
2593
+ const hookCapableAgents = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "gemini", "copilot", "kilo"]);
2594
+ if (options.hook && !hookCapableAgents.has(options.agent)) {
2595
+ throw new Error("--hook is only supported for --agent codex, claude, opencode, gemini, copilot, or kilo");
2596
+ }
2597
+ const scope = options.scope === "user" ? "user" : "project";
2598
+ const result = await installAgent(process2.cwd(), options.agent, { hook: options.hook ?? false, scope });
2599
+ if (isJson()) {
2600
+ emitJson({ ...result, hook: options.hook ?? false, scope });
2601
+ } else {
2602
+ log(`Installed rules into ${result.target}`);
2603
+ if (result.targets.length > 1) {
2604
+ log(`Also wrote: ${result.targets.filter((entry) => entry !== result.target).join(", ")}`);
2417
2605
  }
2418
- const result = await installAgent(process2.cwd(), options.agent, { hook: options.hook ?? false });
2419
- if (isJson()) {
2420
- emitJson({ ...result, hook: options.hook ?? false });
2421
- } else {
2422
- log(`Installed rules into ${result.target}`);
2423
- if (result.targets.length > 1) {
2424
- log(`Also wrote: ${result.targets.filter((entry) => entry !== result.target).join(", ")}`);
2425
- }
2426
- for (const warning of result.warnings ?? []) {
2427
- emitNotice(warning);
2428
- }
2606
+ for (const warning of result.warnings ?? []) {
2607
+ emitNotice(warning);
2429
2608
  }
2430
2609
  }
2431
- );
2610
+ });
2432
2611
  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) => {
2433
2612
  const { mkdtemp, writeFile: writeFile3, mkdir: mkdir3 } = await import("fs/promises");
2434
2613
  const { tmpdir } = await import("os");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "3.15.0",
3
+ "version": "3.16.1",
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.15.0",
47
+ "@swarmvaultai/engine": "3.16.1",
48
48
  "commander": "^14.0.1"
49
49
  },
50
50
  "devDependencies": {