vectorvesper 2.0.6 → 2.1.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/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
|
|
996
|
+
var CLI_VERSION = true ? "2.1.0" : "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
|
|
1294
|
+
var version = true ? "2.1.0" : "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-
|
|
1333
|
+
const { mcpCommand } = await import("./mcp-OURIIFET.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-
|
|
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
|
|
10
|
+
var VERSION = true ? "2.1.0" : "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
|
-
|
|
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-
|
|
104
|
+
const { startMcpServer } = await import("./server-ROPGTSDI.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-
|
|
9
|
+
} from "./chunk-CPZKZO3A.js";
|
|
9
10
|
import {
|
|
10
11
|
getAuthToken
|
|
11
12
|
} from "./chunk-SFP5K3ZO.js";
|
|
@@ -37,8 +38,10 @@ function calleeName(callee) {
|
|
|
37
38
|
}
|
|
38
39
|
return void 0;
|
|
39
40
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
41
|
+
function isStateSetterCall(callee) {
|
|
42
|
+
if (!callee || callee.type !== "Identifier") return false;
|
|
43
|
+
const name = callee.name;
|
|
44
|
+
return /^set[A-Z]/.test(name) && name !== "setInterval" && name !== "setTimeout";
|
|
42
45
|
}
|
|
43
46
|
function collect(ast, traverse) {
|
|
44
47
|
const f = {
|
|
@@ -69,31 +72,50 @@ function collect(ast, traverse) {
|
|
|
69
72
|
const hookByName = /* @__PURE__ */ new Map();
|
|
70
73
|
for (const h of manifest?.hooks ?? []) hookByName.set(h.name, h);
|
|
71
74
|
let fnDepth = 0;
|
|
75
|
+
const resolveFunction = (argPath) => {
|
|
76
|
+
const node = argPath?.node;
|
|
77
|
+
if (!node) return null;
|
|
78
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") return node;
|
|
79
|
+
if (node.type !== "Identifier") return null;
|
|
80
|
+
const binding = argPath.scope?.getBinding(node.name);
|
|
81
|
+
const declaration = binding?.path;
|
|
82
|
+
if (!declaration) return null;
|
|
83
|
+
if (declaration.node?.type === "FunctionDeclaration") return declaration.node;
|
|
84
|
+
const init = declaration.node?.init;
|
|
85
|
+
if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) return init;
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
72
88
|
const scanHotCallback = (cbNode, ctx, fromRaf) => {
|
|
73
|
-
if (!cbNode
|
|
89
|
+
if (!cbNode) return;
|
|
90
|
+
const isExpression = cbNode.type === "ArrowFunctionExpression" || cbNode.type === "FunctionExpression";
|
|
91
|
+
const isDeclaration = cbNode.type === "FunctionDeclaration";
|
|
92
|
+
if (!isExpression && !isDeclaration) return;
|
|
93
|
+
const statement = isDeclaration ? cbNode : { type: "ExpressionStatement", expression: cbNode };
|
|
74
94
|
traverse(
|
|
75
|
-
{ type: "File", program: { type: "Program", body: [
|
|
95
|
+
{ type: "File", program: { type: "Program", body: [statement], directives: [] } },
|
|
76
96
|
{
|
|
77
97
|
CallExpression(p) {
|
|
78
|
-
if (
|
|
98
|
+
if (isStateSetterCall(p.node.callee)) {
|
|
79
99
|
f.setStateInHot.push({ line: cbNode.loc?.start.line ?? 0, ctx, fromRaf });
|
|
80
100
|
}
|
|
81
101
|
},
|
|
82
102
|
OptionalCallExpression(p) {
|
|
83
|
-
if (
|
|
103
|
+
if (isStateSetterCall(p.node.callee)) {
|
|
84
104
|
f.setStateInHot.push({ line: cbNode.loc?.start.line ?? 0, ctx, fromRaf });
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
);
|
|
89
109
|
};
|
|
90
|
-
const handleCall = (
|
|
110
|
+
const handleCall = (p) => {
|
|
111
|
+
const node = p.node;
|
|
91
112
|
const name = calleeName(node.callee);
|
|
92
113
|
const line = node.loc?.start.line ?? 0;
|
|
114
|
+
const argPaths = typeof p.get === "function" ? p.get("arguments") : [];
|
|
93
115
|
switch (name) {
|
|
94
116
|
case "requestAnimationFrame":
|
|
95
117
|
f.raf.push(line);
|
|
96
|
-
scanHotCallback(
|
|
118
|
+
scanHotCallback(resolveFunction(argPaths[0]), "a requestAnimationFrame loop", true);
|
|
97
119
|
break;
|
|
98
120
|
case "cancelAnimationFrame":
|
|
99
121
|
f.hasCancelRaf = true;
|
|
@@ -108,7 +130,7 @@ function collect(ast, traverse) {
|
|
|
108
130
|
f.addEventListener.push(line);
|
|
109
131
|
const ev = node.arguments?.[0];
|
|
110
132
|
if (ev?.type === "StringLiteral" && HOT_EVENTS.has(ev.value)) {
|
|
111
|
-
scanHotCallback(
|
|
133
|
+
scanHotCallback(resolveFunction(argPaths[1]), `a "${ev.value}" listener`, false);
|
|
112
134
|
}
|
|
113
135
|
break;
|
|
114
136
|
}
|
|
@@ -205,10 +227,10 @@ function collect(ast, traverse) {
|
|
|
205
227
|
}
|
|
206
228
|
},
|
|
207
229
|
CallExpression(p) {
|
|
208
|
-
handleCall(p
|
|
230
|
+
handleCall(p);
|
|
209
231
|
},
|
|
210
232
|
OptionalCallExpression(p) {
|
|
211
|
-
handleCall(p
|
|
233
|
+
handleCall(p);
|
|
212
234
|
}
|
|
213
235
|
});
|
|
214
236
|
return f;
|
|
@@ -378,7 +400,7 @@ async function checkMotionFiles(files, cwd) {
|
|
|
378
400
|
}
|
|
379
401
|
|
|
380
402
|
// src/mcp/server.ts
|
|
381
|
-
var VERSION = true ? "2.0
|
|
403
|
+
var VERSION = true ? "2.1.0" : "0.0.0-dev";
|
|
382
404
|
function text(body) {
|
|
383
405
|
return { content: [{ type: "text", text: body }] };
|
|
384
406
|
}
|
|
@@ -563,6 +585,41 @@ ${c.docsUrl}`);
|
|
|
563
585
|
);
|
|
564
586
|
return out.join("\n");
|
|
565
587
|
}
|
|
588
|
+
function formatPatternSummary(p) {
|
|
589
|
+
return `**${p.name}** \u2014 ${p.tagline}
|
|
590
|
+
Composes: ${p.uses.map((u) => `\`${u}\``).join(" + ")}`;
|
|
591
|
+
}
|
|
592
|
+
function formatPatternDetail(p) {
|
|
593
|
+
const out = [];
|
|
594
|
+
out.push(`# ${p.title} \`${p.name}\``);
|
|
595
|
+
out.push(`> ${p.tagline}`);
|
|
596
|
+
out.push("");
|
|
597
|
+
out.push(`**The problem it removes:** ${p.problem}`);
|
|
598
|
+
out.push("");
|
|
599
|
+
out.push(
|
|
600
|
+
`**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.`
|
|
601
|
+
);
|
|
602
|
+
out.push("\n## Wiring order");
|
|
603
|
+
p.wiring.forEach((step, i) => out.push(`${i + 1}. ${step}`));
|
|
604
|
+
out.push("\n## Complete example");
|
|
605
|
+
out.push("```tsx");
|
|
606
|
+
out.push(p.code);
|
|
607
|
+
out.push("```");
|
|
608
|
+
out.push("\n## Pitfalls");
|
|
609
|
+
for (const pit of p.pitfalls) {
|
|
610
|
+
const caught = pit.rule ? ` *(caught by \`check_motion\`: ${pit.rule})*` : "";
|
|
611
|
+
out.push(`- **${pit.mistake}** \u2014 ${pit.consequence}${caught}`);
|
|
612
|
+
}
|
|
613
|
+
out.push("\n## Verify");
|
|
614
|
+
p.verify.forEach((step, i) => out.push(`${i + 1}. ${step}`));
|
|
615
|
+
if (p.docsUrl) out.push(`
|
|
616
|
+
## Docs
|
|
617
|
+
${p.docsUrl}`);
|
|
618
|
+
out.push(
|
|
619
|
+
"\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."
|
|
620
|
+
);
|
|
621
|
+
return out.join("\n");
|
|
622
|
+
}
|
|
566
623
|
function severityMark(s) {
|
|
567
624
|
return s === "error" ? "\u2717 error" : s === "warning" ? "\u25B2 warning" : "\xB7 note";
|
|
568
625
|
}
|
|
@@ -571,11 +628,13 @@ function formatCheckResults(results) {
|
|
|
571
628
|
let errors = 0;
|
|
572
629
|
let warnings = 0;
|
|
573
630
|
let filesWithFindings = 0;
|
|
631
|
+
let unreadable = 0;
|
|
574
632
|
const blocks = [];
|
|
575
633
|
for (const r of results) {
|
|
576
634
|
if (r.error) {
|
|
635
|
+
unreadable++;
|
|
577
636
|
blocks.push(`### \`${r.file}\`
|
|
578
|
-
- could not analyze
|
|
637
|
+
- **could not analyze** \u2014 ${r.error}`);
|
|
579
638
|
continue;
|
|
580
639
|
}
|
|
581
640
|
if (r.findings.length === 0) continue;
|
|
@@ -591,28 +650,25 @@ function formatCheckResults(results) {
|
|
|
591
650
|
blocks.push(lines.join("\n"));
|
|
592
651
|
}
|
|
593
652
|
const total = errors + warnings;
|
|
594
|
-
|
|
653
|
+
const analyzed = results.length - unreadable;
|
|
654
|
+
if (total === 0 && unreadable === 0) {
|
|
595
655
|
return `\u2713 No motion issues found in ${results.length} file${results.length === 1 ? "" : "s"}.
|
|
596
656
|
|
|
597
657
|
` + CHECK_SCOPE_NOTE;
|
|
598
658
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
...blocks,
|
|
604
|
-
"",
|
|
605
|
-
CHECK_SCOPE_NOTE
|
|
606
|
-
].join("\n");
|
|
659
|
+
const headline = total > 0 ? `${total} issue${total === 1 ? "" : "s"} across ${filesWithFindings} file${filesWithFindings === 1 ? "" : "s"} \u2014 ${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}.` : `No issues in the ${analyzed} file${analyzed === 1 ? "" : "s"} that could be read.`;
|
|
660
|
+
const unreadableNote = unreadable > 0 ? `
|
|
661
|
+
\u26A0\uFE0F ${unreadable} file${unreadable === 1 ? "" : "s"} could not be analyzed \u2014 ${unreadable === 1 ? "it was" : "they were"} NOT checked. Fix the path or the syntax and run again.` : "";
|
|
662
|
+
return ["# Motion check", headline + unreadableNote, "", ...blocks, "", CHECK_SCOPE_NOTE].join("\n");
|
|
607
663
|
}
|
|
608
664
|
async function registerTools(server) {
|
|
609
665
|
server.registerTool(
|
|
610
666
|
"list_hooks",
|
|
611
667
|
{
|
|
612
|
-
title: "List motion hooks",
|
|
613
|
-
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.",
|
|
668
|
+
title: "List motion hooks and core primitives",
|
|
669
|
+
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.",
|
|
614
670
|
inputSchema: {
|
|
615
|
-
category: z.enum(["sensor", "governor", "gate", "interaction", "media", "effect"]).optional().describe(
|
|
671
|
+
category: z.enum(["sensor", "governor", "gate", "interaction", "media", "effect", "core"]).optional().describe('Filter to one category. "core" lists the custom-effect primitives.')
|
|
616
672
|
}
|
|
617
673
|
},
|
|
618
674
|
async ({ category }) => {
|
|
@@ -622,16 +678,32 @@ async function registerTools(server) {
|
|
|
622
678
|
"Hook manifest unavailable \u2014 this CLI build is missing dist/hooks.json. Reinstall with `npm i -g vectorvesper@latest`."
|
|
623
679
|
);
|
|
624
680
|
}
|
|
625
|
-
const
|
|
626
|
-
if (!
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
681
|
+
const all = category ? manifest.hooks.filter((h) => h.category === category) : manifest.hooks;
|
|
682
|
+
if (!all.length) return text(`No hooks in category "${category}".`);
|
|
683
|
+
const reactHooks = all.filter((h) => h.category !== "core");
|
|
684
|
+
const core = all.filter((h) => h.category === "core");
|
|
685
|
+
const patternCount = manifest.patterns?.length ?? 0;
|
|
686
|
+
const lines = [];
|
|
687
|
+
if (reactHooks.length) {
|
|
688
|
+
lines.push(
|
|
689
|
+
`${reactHooks.length} hook${reactHooks.length === 1 ? "" : "s"} in @vectorvesper/motion v${manifest.engineVersion}`,
|
|
690
|
+
"",
|
|
691
|
+
...reactHooks.map(formatHookSummary)
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (core.length) {
|
|
695
|
+
if (lines.length) lines.push("");
|
|
696
|
+
lines.push(
|
|
697
|
+
`**Core primitives** \u2014 the layer custom effects are built on. Never start a private requestAnimationFrame loop; subscribe to the conductor instead.`,
|
|
698
|
+
"",
|
|
699
|
+
...core.map(formatHookSummary)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
lines.push(
|
|
631
703
|
"",
|
|
632
|
-
"Call `get_hook` for the full contract of any of these before writing code with it."
|
|
633
|
-
|
|
634
|
-
return text(
|
|
704
|
+
"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).` : "")
|
|
705
|
+
);
|
|
706
|
+
return text(lines.join("\n"));
|
|
635
707
|
}
|
|
636
708
|
);
|
|
637
709
|
server.registerTool(
|
|
@@ -653,6 +725,53 @@ async function registerTools(server) {
|
|
|
653
725
|
return text(formatHookDetail(hook));
|
|
654
726
|
}
|
|
655
727
|
);
|
|
728
|
+
server.registerTool(
|
|
729
|
+
"list_patterns",
|
|
730
|
+
{
|
|
731
|
+
title: "List composition patterns",
|
|
732
|
+
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.",
|
|
733
|
+
inputSchema: {}
|
|
734
|
+
},
|
|
735
|
+
async () => {
|
|
736
|
+
const manifest = loadHookManifest();
|
|
737
|
+
if (!manifest) {
|
|
738
|
+
return text(
|
|
739
|
+
"Hook manifest unavailable \u2014 this CLI build is missing dist/hooks.json. Reinstall with `npm i -g vectorvesper@latest`."
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
const patterns = manifest.patterns ?? [];
|
|
743
|
+
if (!patterns.length) {
|
|
744
|
+
return text("This manifest carries no patterns. Reinstall with `npm i -g vectorvesper@latest`.");
|
|
745
|
+
}
|
|
746
|
+
const body = [
|
|
747
|
+
`${patterns.length} composition pattern${patterns.length === 1 ? "" : "s"} (engine v${manifest.engineVersion})`,
|
|
748
|
+
"",
|
|
749
|
+
...patterns.map(formatPatternSummary),
|
|
750
|
+
"",
|
|
751
|
+
"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."
|
|
752
|
+
].join("\n");
|
|
753
|
+
return text(body);
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
server.registerTool(
|
|
757
|
+
"get_pattern",
|
|
758
|
+
{
|
|
759
|
+
title: "Get a composition pattern",
|
|
760
|
+
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.",
|
|
761
|
+
inputSchema: {
|
|
762
|
+
name: z.string().describe('Pattern name, e.g. "scrollytelling-video" or "custom-frame-effect".')
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
async ({ name }) => {
|
|
766
|
+
const pattern = findPattern(name);
|
|
767
|
+
if (!pattern) {
|
|
768
|
+
const manifest = loadHookManifest();
|
|
769
|
+
const available = manifest?.patterns?.map((p) => p.name).join(", ") ?? "none loaded";
|
|
770
|
+
return text(`No pattern named "${name}". Available: ${available}`);
|
|
771
|
+
}
|
|
772
|
+
return text(formatPatternDetail(pattern));
|
|
773
|
+
}
|
|
774
|
+
);
|
|
656
775
|
server.registerTool(
|
|
657
776
|
"list_components",
|
|
658
777
|
{
|
|
@@ -759,8 +878,8 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
|
|
|
759
878
|
server.registerTool(
|
|
760
879
|
"search",
|
|
761
880
|
{
|
|
762
|
-
title: "Search
|
|
763
|
-
description: 'Keyword search across
|
|
881
|
+
title: "Search primitives, patterns and components",
|
|
882
|
+
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.',
|
|
764
883
|
inputSchema: { query: z.string().describe("What you are trying to build.") }
|
|
765
884
|
},
|
|
766
885
|
async ({ query }) => {
|
|
@@ -774,8 +893,17 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
|
|
|
774
893
|
const results = [];
|
|
775
894
|
const manifest = loadHookManifest();
|
|
776
895
|
for (const h of manifest?.hooks ?? []) {
|
|
777
|
-
const
|
|
778
|
-
|
|
896
|
+
const kind = h.category === "core" ? "core" : "hook";
|
|
897
|
+
const s = score(
|
|
898
|
+
[h.name, h.tagline, h.problem, h.summary, h.category, ...h.aliases ?? [], ...h.exports ?? []].join(" ")
|
|
899
|
+
);
|
|
900
|
+
if (s > 0) results.push({ score: s, line: `**${kind}** \`${h.name}\` \u2014 ${h.tagline}` });
|
|
901
|
+
}
|
|
902
|
+
for (const p of manifest?.patterns ?? []) {
|
|
903
|
+
const s = score(
|
|
904
|
+
[p.name, p.title, p.tagline, p.problem, ...p.aliases ?? [], ...p.uses].join(" ")
|
|
905
|
+
);
|
|
906
|
+
if (s > 0) results.push({ score: s, line: `**pattern** \`${p.name}\` \u2014 ${p.tagline}` });
|
|
779
907
|
}
|
|
780
908
|
try {
|
|
781
909
|
const index = await fetchRegistryIndex();
|
|
@@ -799,7 +927,7 @@ Hook discovery still works offline \u2014 try \`list_hooks\`.`
|
|
|
799
927
|
"",
|
|
800
928
|
...results.slice(0, 12).map((r) => r.line),
|
|
801
929
|
"",
|
|
802
|
-
"Then call `get_hook` or `get_component` for the full contract before writing code."
|
|
930
|
+
"Then call `get_hook`, `get_pattern`, or `get_component` for the full contract before writing code."
|
|
803
931
|
].join("\n")
|
|
804
932
|
);
|
|
805
933
|
}
|
|
@@ -828,7 +956,7 @@ async function startMcpServer() {
|
|
|
828
956
|
const server = new McpServerCtor(
|
|
829
957
|
{ name: "vectorvesper", version: VERSION },
|
|
830
958
|
{
|
|
831
|
-
instructions:
|
|
959
|
+
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.'
|
|
832
960
|
}
|
|
833
961
|
);
|
|
834
962
|
await registerTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vectorvesper",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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
|
},
|