vectorvesper 2.0.7 → 2.1.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.
package/dist/index.js CHANGED
@@ -993,7 +993,7 @@ Remove ${slug}
993
993
  import fs6 from "fs";
994
994
  import path6 from "path";
995
995
  import pc8 from "picocolors";
996
- var CLI_VERSION = true ? "2.0.7" : "0.0.0-dev";
996
+ var CLI_VERSION = true ? "2.1.1" : "0.0.0-dev";
997
997
  async function infoCommand() {
998
998
  console.log(pc8.bold(pc8.cyan("\nVector Vesper Diagnostics\n")));
999
999
  const projectInfo = detectProject();
@@ -1291,7 +1291,7 @@ async function whoamiCommand() {
1291
1291
  }
1292
1292
 
1293
1293
  // src/index.ts
1294
- var version = true ? "2.0.7" : "0.0.0-dev";
1294
+ var version = true ? "2.1.1" : "0.0.0-dev";
1295
1295
  var program = new Command();
1296
1296
  program.name("vv").description("Vector Vesper CLI \u2014 add visual components to your React project").version(version).option("--verbose", "Show detailed debug output").hook("preAction", (thisCommand) => {
1297
1297
  const opts = thisCommand.opts();
@@ -1330,7 +1330,7 @@ program.command("whoami", { hidden: true }).description("Show current authentica
1330
1330
  await whoamiCommand();
1331
1331
  });
1332
1332
  program.command("mcp [action]").description("Run as an MCP server (stdio) for AI coding agents. `vv mcp status` checks the setup.").option("--stdio", "Force server mode even from an interactive terminal").action(async (action, options) => {
1333
- const { mcpCommand } = await import("./mcp-NVM7DS5P.js");
1333
+ const { mcpCommand } = await import("./mcp-FCM3FZSK.js");
1334
1334
  await mcpCommand(action, options);
1335
1335
  });
1336
1336
  process.on("unhandledRejection", (error) => {
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  loadHookManifest
3
- } from "./chunk-XN3PPCIM.js";
3
+ } from "./chunk-CPZKZO3A.js";
4
4
  import {
5
5
  getAuthToken
6
6
  } from "./chunk-SFP5K3ZO.js";
7
7
 
8
8
  // src/commands/mcp.ts
9
9
  import pc from "picocolors";
10
- var VERSION = true ? "2.0.7" : "0.0.0-dev";
10
+ var VERSION = true ? "2.1.1" : "0.0.0-dev";
11
11
  var CONFIG_SNIPPET = `{
12
12
  "mcpServers": {
13
13
  "vectorvesper": {
@@ -33,7 +33,14 @@ function printStatus() {
33
33
  console.log(pc.bold(pc.cyan(" Vector Vesper MCP\n")));
34
34
  console.log(` ${pc.bold("CLI version:")} ${VERSION}`);
35
35
  if (manifest) {
36
- console.log(` ${ok(`Hook manifest: ${manifest.hooks.length} hooks, engine v${manifest.engineVersion}`)}`);
36
+ const hookCount = manifest.hooks.filter((h) => h.category !== "core").length;
37
+ const coreCount = manifest.hooks.length - hookCount;
38
+ const patternCount = manifest.patterns?.length ?? 0;
39
+ console.log(
40
+ ` ${ok(
41
+ `Hook manifest: ${hookCount} hooks, ${coreCount} core primitives, ${patternCount} patterns, engine v${manifest.engineVersion}`
42
+ )}`
43
+ );
37
44
  console.log(` ${pc.dim(`Contract level: ${manifest.contractLevel}`)}`);
38
45
  } else {
39
46
  console.log(` ${bad("Hook manifest: MISSING")}`);
@@ -53,7 +60,7 @@ function printStatus() {
53
60
  console.log("");
54
61
  console.log(
55
62
  pc.dim(
56
- " Tools: list_hooks \xB7 get_hook \xB7 list_components \xB7 get_component \xB7 get_setup_guidance \xB7 search \xB7 check_motion"
63
+ " Tools: list_hooks \xB7 get_hook \xB7 list_patterns \xB7 get_pattern \xB7 list_components \xB7 get_component \xB7 get_setup_guidance \xB7 search \xB7 check_motion"
57
64
  )
58
65
  );
59
66
  console.log("");
@@ -67,7 +74,7 @@ function printInteractiveHelp() {
67
74
  console.log(" started from a terminal it would just wait for a client that never connects.");
68
75
  console.log("");
69
76
  console.log(` ${pc.bold("Check your setup:")} ${pc.cyan("vv mcp status")}`);
70
- console.log(` ${pc.bold("Add to Claude Code:")} ${pc.cyan("claude mcp add vectorvesper -- npx -y vectorvesper mcp")}`);
77
+ console.log(` ${pc.bold("Add to Claude Code:")} ${pc.cyan("claude mcp add vectorvesper -- npx -y vectorvesper@latest mcp")}`);
71
78
  console.log("");
72
79
  console.log(pc.dim(" To run the server here anyway (for debugging): vv mcp --stdio"));
73
80
  console.log("");
@@ -94,7 +101,7 @@ ${pc.red("\u2717")} Unknown argument "${action}" for \`vv mcp\`.
94
101
  return;
95
102
  }
96
103
  protectStdout();
97
- const { startMcpServer } = await import("./server-LJL6ZBE3.js");
104
+ const { startMcpServer } = await import("./server-WQ564YNA.js");
98
105
  try {
99
106
  await startMcpServer();
100
107
  } catch (error) {
@@ -4,8 +4,9 @@ import {
4
4
  } from "./chunk-5YSVDVLK.js";
5
5
  import {
6
6
  findHook,
7
+ findPattern,
7
8
  loadHookManifest
8
- } from "./chunk-XN3PPCIM.js";
9
+ } from "./chunk-CPZKZO3A.js";
9
10
  import {
10
11
  getAuthToken
11
12
  } from "./chunk-SFP5K3ZO.js";
@@ -50,6 +51,8 @@ function collect(ast, traverse) {
50
51
  usesUseGSAP: false,
51
52
  usesR3F: false,
52
53
  raf: [],
54
+ rafSelfScheduling: [],
55
+ rafUnresolved: 0,
53
56
  hasCancelRaf: false,
54
57
  addEventListener: [],
55
58
  hasRemoveEventListener: false,
@@ -112,10 +115,14 @@ function collect(ast, traverse) {
112
115
  const line = node.loc?.start.line ?? 0;
113
116
  const argPaths = typeof p.get === "function" ? p.get("arguments") : [];
114
117
  switch (name) {
115
- case "requestAnimationFrame":
118
+ case "requestAnimationFrame": {
116
119
  f.raf.push(line);
117
- scanHotCallback(resolveFunction(argPaths[0]), "a requestAnimationFrame loop", true);
120
+ const cb = resolveFunction(argPaths[0]);
121
+ if (!cb) f.rafUnresolved++;
122
+ else if (callbackSchedulesAnotherFrame(cb, traverse)) f.rafSelfScheduling.push(line);
123
+ scanHotCallback(cb, "a requestAnimationFrame loop", true);
118
124
  break;
125
+ }
119
126
  case "cancelAnimationFrame":
120
127
  f.hasCancelRaf = true;
121
128
  break;
@@ -210,6 +217,9 @@ function collect(ast, traverse) {
210
217
  },
211
218
  Identifier(p) {
212
219
  const name = p.node.name;
220
+ if (/^(reducedMotion|prefersReducedMotion|prefersReduced)$/.test(name)) {
221
+ f.hasReducedMotionGuard = true;
222
+ }
213
223
  if (!BROWSER_GLOBALS.has(name)) return;
214
224
  if (fnDepth !== 0) return;
215
225
  const parent = p.parent;
@@ -264,8 +274,27 @@ function checkClientBoundary(f, file, framework) {
264
274
  }
265
275
  return out;
266
276
  }
277
+ function callbackSchedulesAnotherFrame(cbNode, traverse) {
278
+ const isDeclaration = cbNode.type === "FunctionDeclaration";
279
+ const isExpression = cbNode.type === "ArrowFunctionExpression" || cbNode.type === "FunctionExpression";
280
+ if (!isDeclaration && !isExpression) return false;
281
+ const statement = isDeclaration ? cbNode : { type: "ExpressionStatement", expression: cbNode };
282
+ let found = false;
283
+ traverse(
284
+ { type: "File", program: { type: "Program", body: [statement], directives: [] } },
285
+ {
286
+ CallExpression(p) {
287
+ if (calleeName(p.node.callee) === "requestAnimationFrame") found = true;
288
+ },
289
+ OptionalCallExpression(p) {
290
+ if (calleeName(p.node.callee) === "requestAnimationFrame") found = true;
291
+ }
292
+ }
293
+ );
294
+ return found;
295
+ }
267
296
  function isRafLoop(f) {
268
- return f.raf.length >= 2;
297
+ return f.rafSelfScheduling.length > 0 || f.rafUnresolved >= 2;
269
298
  }
270
299
  function checkOrphanRaf(f, file) {
271
300
  if (!isRafLoop(f)) return [];
@@ -274,8 +303,10 @@ function checkOrphanRaf(f, file) {
274
303
  rule: "orphan-raf",
275
304
  severity: "warning",
276
305
  file,
277
- line: f.raf[0],
278
- message: `This file runs its own \`requestAnimationFrame\` loop${f.raf.length > 1 ? ` (\xD7${f.raf.length})` : ""}. Independent loops each schedule their own frame and cannot shed work under load \u2014 the coordination the runtime exists to provide is lost.`,
306
+ // Point at the self-scheduling call when we found one — that is the loop
307
+ // itself, not merely the first rAF in the file.
308
+ line: f.rafSelfScheduling[0] ?? f.raf[0],
309
+ message: `This file runs its own \`requestAnimationFrame\` loop${f.rafSelfScheduling.length > 1 ? ` (\xD7${f.rafSelfScheduling.length})` : ""}. Independent loops each schedule their own frame and cannot shed work under load \u2014 the coordination the runtime exists to provide is lost.`,
279
310
  fix: "Drive the animation from the shared conductor (`getConductor().subscribe(...)` in `@vectorvesper/motion`) or a hook that already joins it, so it shares one frame budget.",
280
311
  nudge: "Call `list_hooks` \u2014 a sensor or governor primitive may already cover this without a hand-written loop."
281
312
  }
@@ -399,7 +430,7 @@ async function checkMotionFiles(files, cwd) {
399
430
  }
400
431
 
401
432
  // src/mcp/server.ts
402
- var VERSION = true ? "2.0.7" : "0.0.0-dev";
433
+ var VERSION = true ? "2.1.1" : "0.0.0-dev";
403
434
  function text(body) {
404
435
  return { content: [{ type: "text", text: body }] };
405
436
  }
@@ -584,6 +615,41 @@ ${c.docsUrl}`);
584
615
  );
585
616
  return out.join("\n");
586
617
  }
618
+ function formatPatternSummary(p) {
619
+ return `**${p.name}** \u2014 ${p.tagline}
620
+ Composes: ${p.uses.map((u) => `\`${u}\``).join(" + ")}`;
621
+ }
622
+ function formatPatternDetail(p) {
623
+ const out = [];
624
+ out.push(`# ${p.title} \`${p.name}\``);
625
+ out.push(`> ${p.tagline}`);
626
+ out.push("");
627
+ out.push(`**The problem it removes:** ${p.problem}`);
628
+ out.push("");
629
+ out.push(
630
+ `**Composes:** ${p.uses.map((u) => `\`${u}\``).join(" + ")} \u2014 call \`get_hook\` on any of these you have not already read; the pattern assumes their contracts.`
631
+ );
632
+ out.push("\n## Wiring order");
633
+ p.wiring.forEach((step, i) => out.push(`${i + 1}. ${step}`));
634
+ out.push("\n## Complete example");
635
+ out.push("```tsx");
636
+ out.push(p.code);
637
+ out.push("```");
638
+ out.push("\n## Pitfalls");
639
+ for (const pit of p.pitfalls) {
640
+ const caught = pit.rule ? ` *(caught by \`check_motion\`: ${pit.rule})*` : "";
641
+ out.push(`- **${pit.mistake}** \u2014 ${pit.consequence}${caught}`);
642
+ }
643
+ out.push("\n## Verify");
644
+ p.verify.forEach((step, i) => out.push(`${i + 1}. ${step}`));
645
+ if (p.docsUrl) out.push(`
646
+ ## Docs
647
+ ${p.docsUrl}`);
648
+ out.push(
649
+ "\n---\n**After writing your version, run `check_motion` on the file(s).** The pitfalls above are the findings it looks for; a clean report is how you confirm the composition held."
650
+ );
651
+ return out.join("\n");
652
+ }
587
653
  function severityMark(s) {
588
654
  return s === "error" ? "\u2717 error" : s === "warning" ? "\u25B2 warning" : "\xB7 note";
589
655
  }
@@ -629,10 +695,10 @@ async function registerTools(server) {
629
695
  server.registerTool(
630
696
  "list_hooks",
631
697
  {
632
- title: "List motion hooks",
633
- description: "List every React hook in the @vectorvesper/motion runtime, with the problem each one solves. Start here when deciding which primitive fits a motion task.",
698
+ title: "List motion hooks and core primitives",
699
+ description: "List every React hook in the @vectorvesper/motion runtime, with the problem each one solves, plus the core primitives (frame conductor, sensor bus, math, devtools) that custom effects are built on. Start here when deciding which primitive fits a motion task.",
634
700
  inputSchema: {
635
- category: z.enum(["sensor", "governor", "gate", "interaction", "media", "effect"]).optional().describe("Filter to one category.")
701
+ category: z.enum(["sensor", "governor", "gate", "interaction", "media", "effect", "core"]).optional().describe('Filter to one category. "core" lists the custom-effect primitives.')
636
702
  }
637
703
  },
638
704
  async ({ category }) => {
@@ -642,16 +708,32 @@ async function registerTools(server) {
642
708
  "Hook manifest unavailable \u2014 this CLI build is missing dist/hooks.json. Reinstall with `npm i -g vectorvesper@latest`."
643
709
  );
644
710
  }
645
- const hooks = category ? manifest.hooks.filter((h) => h.category === category) : manifest.hooks;
646
- if (!hooks.length) return text(`No hooks in category "${category}".`);
647
- const body = [
648
- `${hooks.length} hook${hooks.length === 1 ? "" : "s"} in @vectorvesper/motion v${manifest.engineVersion}`,
649
- "",
650
- ...hooks.map(formatHookSummary),
711
+ const all = category ? manifest.hooks.filter((h) => h.category === category) : manifest.hooks;
712
+ if (!all.length) return text(`No hooks in category "${category}".`);
713
+ const reactHooks = all.filter((h) => h.category !== "core");
714
+ const core = all.filter((h) => h.category === "core");
715
+ const patternCount = manifest.patterns?.length ?? 0;
716
+ const lines = [];
717
+ if (reactHooks.length) {
718
+ lines.push(
719
+ `${reactHooks.length} hook${reactHooks.length === 1 ? "" : "s"} in @vectorvesper/motion v${manifest.engineVersion}`,
720
+ "",
721
+ ...reactHooks.map(formatHookSummary)
722
+ );
723
+ }
724
+ if (core.length) {
725
+ if (lines.length) lines.push("");
726
+ lines.push(
727
+ `**Core primitives** \u2014 the layer custom effects are built on. Never start a private requestAnimationFrame loop; subscribe to the conductor instead.`,
728
+ "",
729
+ ...core.map(formatHookSummary)
730
+ );
731
+ }
732
+ lines.push(
651
733
  "",
652
- "Call `get_hook` for the full contract of any of these before writing code with it."
653
- ].join("\n");
654
- return text(body);
734
+ "Call `get_hook` for the full contract of any of these before writing code with it." + (patternCount > 0 ? ` For compositions \u2014 scrollytelling, predictive cards, custom effects \u2014 call \`list_patterns\` (${patternCount} available).` : "")
735
+ );
736
+ return text(lines.join("\n"));
655
737
  }
656
738
  );
657
739
  server.registerTool(
@@ -673,6 +755,53 @@ async function registerTools(server) {
673
755
  return text(formatHookDetail(hook));
674
756
  }
675
757
  );
758
+ server.registerTool(
759
+ "list_patterns",
760
+ {
761
+ title: "List composition patterns",
762
+ description: "List the composition patterns \u2014 hooks and core primitives wired together into things people build: scrollytelling video, predictive media cards, adaptive shader heroes, custom frame effects. Use these when a task combines primitives, or when no single hook covers it; a pattern carries the wiring order, complete code, the pitfalls (each mapped to the check_motion rule that catches it), and how to verify the result.",
763
+ inputSchema: {}
764
+ },
765
+ async () => {
766
+ const manifest = loadHookManifest();
767
+ if (!manifest) {
768
+ return text(
769
+ "Hook manifest unavailable \u2014 this CLI build is missing dist/hooks.json. Reinstall with `npm i -g vectorvesper@latest`."
770
+ );
771
+ }
772
+ const patterns = manifest.patterns ?? [];
773
+ if (!patterns.length) {
774
+ return text("This manifest carries no patterns. Reinstall with `npm i -g vectorvesper@latest`.");
775
+ }
776
+ const body = [
777
+ `${patterns.length} composition pattern${patterns.length === 1 ? "" : "s"} (engine v${manifest.engineVersion})`,
778
+ "",
779
+ ...patterns.map(formatPatternSummary),
780
+ "",
781
+ "Call `get_pattern` for the wiring order, complete code, pitfalls, and verification steps. Building something none of these cover? Start from `custom-frame-effect` \u2014 it is the canonical skeleton every custom effect follows."
782
+ ].join("\n");
783
+ return text(body);
784
+ }
785
+ );
786
+ server.registerTool(
787
+ "get_pattern",
788
+ {
789
+ title: "Get a composition pattern",
790
+ description: "Full recipe for one composition: the primitives it combines, the wiring order, one complete runnable example, the pitfalls (each naming the check_motion rule that catches it), and the verification steps. Read this BEFORE writing a multi-primitive feature \u2014 and read `custom-frame-effect` before building any effect the hooks do not cover.",
791
+ inputSchema: {
792
+ name: z.string().describe('Pattern name, e.g. "scrollytelling-video" or "custom-frame-effect".')
793
+ }
794
+ },
795
+ async ({ name }) => {
796
+ const pattern = findPattern(name);
797
+ if (!pattern) {
798
+ const manifest = loadHookManifest();
799
+ const available = manifest?.patterns?.map((p) => p.name).join(", ") ?? "none loaded";
800
+ return text(`No pattern named "${name}". Available: ${available}`);
801
+ }
802
+ return text(formatPatternDetail(pattern));
803
+ }
804
+ );
676
805
  server.registerTool(
677
806
  "list_components",
678
807
  {
@@ -779,8 +908,8 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
779
908
  server.registerTool(
780
909
  "search",
781
910
  {
782
- title: "Search hooks and components",
783
- description: 'Keyword search across both motion hooks and installable components. Use this when you know the effect you want ("magnetic button", "scroll video", "lazy 3d scene") but not which primitive provides it.',
911
+ title: "Search primitives, patterns and components",
912
+ description: 'Keyword search across everything: React hooks, the core primitives, multi-hook composition patterns, and installable components. Use this when you know the effect you want ("magnetic button", "scroll video", "lazy 3d scene") but not which primitive provides it. A **pattern** result is usually the one to follow for anything combining two or more primitives \u2014 it carries the wiring order and the failure modes that using each hook correctly in isolation still gets wrong.',
784
913
  inputSchema: { query: z.string().describe("What you are trying to build.") }
785
914
  },
786
915
  async ({ query }) => {
@@ -794,8 +923,17 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
794
923
  const results = [];
795
924
  const manifest = loadHookManifest();
796
925
  for (const h of manifest?.hooks ?? []) {
797
- const s = score([h.name, h.tagline, h.problem, h.summary, h.category].join(" "));
798
- if (s > 0) results.push({ score: s, line: `**hook** \`${h.name}\` \u2014 ${h.tagline}` });
926
+ const kind = h.category === "core" ? "core" : "hook";
927
+ const s = score(
928
+ [h.name, h.tagline, h.problem, h.summary, h.category, ...h.aliases ?? [], ...h.exports ?? []].join(" ")
929
+ );
930
+ if (s > 0) results.push({ score: s, line: `**${kind}** \`${h.name}\` \u2014 ${h.tagline}` });
931
+ }
932
+ for (const p of manifest?.patterns ?? []) {
933
+ const s = score(
934
+ [p.name, p.title, p.tagline, p.problem, ...p.aliases ?? [], ...p.uses].join(" ")
935
+ );
936
+ if (s > 0) results.push({ score: s, line: `**pattern** \`${p.name}\` \u2014 ${p.tagline}` });
799
937
  }
800
938
  try {
801
939
  const index = await fetchRegistryIndex();
@@ -819,7 +957,7 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
819
957
  "",
820
958
  ...results.slice(0, 12).map((r) => r.line),
821
959
  "",
822
- "Then call `get_hook` or `get_component` for the full contract before writing code."
960
+ "Then call `get_hook`, `get_pattern`, or `get_component` for the full contract before writing code."
823
961
  ].join("\n")
824
962
  );
825
963
  }
@@ -848,7 +986,7 @@ async function startMcpServer() {
848
986
  const server = new McpServerCtor(
849
987
  { name: "vectorvesper", version: VERSION },
850
988
  {
851
- instructions: "Vector Vesper's motion runtime and component registry.\n\nBefore writing any animation, scroll, pointer or WebGL code in this project, call `search` or `list_hooks` to check whether a Vector Vesper primitive already covers it, then `get_hook` / `get_component` for the contract. The contract states whether something needs a client boundary, whether it owns an element's transform, and what it conflicts with \u2014 details that do not surface as compile errors but do surface as jank.\n\nAfter writing animation, scroll, pointer or WebGL code, call `check_motion` on the file(s) you changed. It reports the same failure class the contracts warn about \u2014 an unmanaged frame loop, missing cleanup, state written every frame, a transform animated by two owners, motion with no reduced-motion guard, a browser API at module scope \u2014 checked against what you actually wrote. This is how you confirm the contract was followed; none of it shows up as a type or build error.\n\nHooks are consumed by importing `@vectorvesper/motion`. Never reimplement or inline a hook: a copy runs its own requestAnimationFrame loop instead of joining the shared frame conductor, which removes the coordination the runtime exists to provide, and nothing errors when that happens.\n\nIf asked how a hook works internally, call `get_hook` \u2014 its `How it works inside` section explains the architecture, the frame lane, and the smoothing constants directly. Prefer that over reading `node_modules/@vectorvesper/motion`: it is the same design stated at the level the question is actually about, and it stays correct as the package is bundled and rebundled."
989
+ instructions: 'Vector Vesper\'s motion runtime and component registry.\n\nBefore writing any animation, scroll, pointer or WebGL code in this project, call `search` or `list_hooks` to check whether a Vector Vesper primitive already covers it, then `get_hook` / `get_component` for the contract. The contract states whether something needs a client boundary, whether it owns an element\'s transform, and what it conflicts with \u2014 details that do not surface as compile errors but do surface as jank.\n\nWhen the task COMBINES primitives (scrollytelling, a predictive media card, an adaptive hero, a marquee) call `list_patterns` / `get_pattern` first: a pattern carries the wiring order, one complete example, the pitfalls with the `check_motion` rule that catches each, and how to verify. When no hook or pattern covers the task, build a custom effect on the runtime: `get_pattern("custom-frame-effect")` is the canonical skeleton, and `get_hook("FrameConductor")`, `get_hook("SensorBus")` and `get_hook("damp")` are the core primitives it rides on. Never start a private requestAnimationFrame loop \u2014 subscribing to the shared conductor is always the answer.\n\nAfter writing animation, scroll, pointer or WebGL code, call `check_motion` on the file(s) you changed. It reports the same failure class the contracts warn about \u2014 an unmanaged frame loop, missing cleanup, state written every frame, a transform animated by two owners, motion with no reduced-motion guard, a browser API at module scope \u2014 checked against what you actually wrote. This is how you confirm the contract was followed; none of it shows up as a type or build error.\n\nHooks are consumed by importing `@vectorvesper/motion`. Never reimplement or inline a hook: a copy runs its own requestAnimationFrame loop instead of joining the shared frame conductor, which removes the coordination the runtime exists to provide, and nothing errors when that happens.\n\nIf asked how a hook works internally, call `get_hook` \u2014 its `How it works inside` section explains the architecture, the frame lane, and the smoothing constants directly. Prefer that over reading `node_modules/@vectorvesper/motion`: it is the same design stated at the level the question is actually about, and it stays correct as the package is bundled and rebundled.'
852
990
  }
853
991
  );
854
992
  await registerTools(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vectorvesper",
3
- "version": "2.0.7",
3
+ "version": "2.1.1",
4
4
  "description": "Add WebGL, React Three Fiber & advanced motion components to your project via CLI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,6 +44,8 @@
44
44
  "typecheck": "tsc --noEmit",
45
45
  "test": "vitest run",
46
46
  "test:watch": "vitest",
47
+ "test:mcp": "node scripts/mcp-e2e/harness.mjs --local",
48
+ "test:mcp:published": "node scripts/mcp-e2e/harness.mjs",
47
49
  "postbuild": "node scripts/bundle-manifest.mjs",
48
50
  "prepublishOnly": "npm run build"
49
51
  },