frameshot-mcp 0.7.0 → 0.9.7
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 +83 -69
- package/action.yml +114 -16
- package/dist/chunk-MEBQ7ZWA.js +1774 -0
- package/dist/chunk-VUYZHZBH.js +157 -0
- package/dist/cli.js +131 -133
- package/dist/index.js +519 -572
- package/dist/renderer.d.ts +17 -7
- package/dist/renderer.js +4 -6
- package/dist/stubs/gatsby.js +20 -0
- package/dist/stubs/next-font.js +9 -0
- package/dist/stubs/next-headers.js +20 -0
- package/dist/stubs/next-image.js +34 -0
- package/dist/stubs/next-link.js +25 -0
- package/dist/stubs/next-navigation.js +17 -0
- package/dist/stubs/next-router.js +19 -0
- package/dist/stubs/nuxt-app.js +37 -0
- package/dist/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/qwik-city.js +33 -0
- package/dist/stubs/react-router.js +67 -0
- package/dist/stubs/server-only.js +2 -0
- package/dist/stubs/solid-router.js +27 -0
- package/dist/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/gatsby.js +20 -0
- package/dist/stubs/stubs/next-font.js +9 -0
- package/dist/stubs/stubs/next-headers.js +20 -0
- package/dist/stubs/stubs/next-image.js +34 -0
- package/dist/stubs/stubs/next-link.js +25 -0
- package/dist/stubs/stubs/next-navigation.js +17 -0
- package/dist/stubs/stubs/next-router.js +19 -0
- package/dist/stubs/stubs/nuxt-app.js +37 -0
- package/dist/stubs/stubs/nuxt-imports.js +13 -0
- package/dist/stubs/stubs/qwik-city.js +33 -0
- package/dist/stubs/stubs/react-router.js +67 -0
- package/dist/stubs/stubs/server-only.js +2 -0
- package/dist/stubs/stubs/solid-router.js +27 -0
- package/dist/stubs/stubs/solid-start.js +18 -0
- package/dist/stubs/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/stubs/vike.js +11 -0
- package/dist/stubs/sveltekit-environment.js +5 -0
- package/dist/stubs/sveltekit-navigation.js +11 -0
- package/dist/stubs/sveltekit-stores.js +15 -0
- package/dist/stubs/vike.js +11 -0
- package/package.json +10 -4
- package/scripts/render-changed.mjs +140 -18
- package/dist/chunk-3LVWVDET.js +0 -849
- package/dist/chunk-47YJG5HR.js +0 -690
- package/dist/chunk-67JZQ6OI.js +0 -819
- package/dist/chunk-AZCGKIMU.js +0 -850
- package/dist/chunk-B3CLIGWU.js +0 -786
- package/dist/chunk-C6QSY4WR.js +0 -811
- package/dist/chunk-DX54PJKO.js +0 -603
- package/dist/chunk-EMCJGIMY.js +0 -984
- package/dist/chunk-FQNWGR62.js +0 -849
- package/dist/chunk-FTYTZW6D.js +0 -203
- package/dist/chunk-JGVKYXY2.js +0 -857
- package/dist/chunk-JYPEA4P2.js +0 -846
- package/dist/chunk-KHK35HDD.js +0 -855
- package/dist/chunk-Q7A3DLED.js +0 -848
- package/dist/chunk-SIA6XEHM.js +0 -811
- package/dist/chunk-ST35YDI6.js +0 -834
- package/dist/chunk-T5OBJK35.js +0 -855
- package/dist/chunk-U3GHS7KO.js +0 -837
- package/dist/chunk-WS2ASCD6.js +0 -683
- package/dist/chunk-WZMHVSUA.js +0 -847
- package/dist/chunk-ZZST6K7Y.js +0 -987
package/dist/index.js
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
SnapshotStore,
|
|
6
|
-
SnapshotUseCase
|
|
7
|
-
} from "./chunk-FTYTZW6D.js";
|
|
3
|
+
createContainer
|
|
4
|
+
} from "./chunk-VUYZHZBH.js";
|
|
8
5
|
import {
|
|
9
|
-
BrowserPool,
|
|
10
|
-
CatalogUseCase,
|
|
11
6
|
DEVICE_PRESETS,
|
|
12
|
-
DiffUseCase,
|
|
13
7
|
EXT_TO_FRAMEWORK,
|
|
14
|
-
HtmlBuilder,
|
|
15
|
-
ImageComparator,
|
|
16
|
-
RenderUseCase,
|
|
17
|
-
ViteBundler,
|
|
18
8
|
__export
|
|
19
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MEBQ7ZWA.js";
|
|
20
10
|
|
|
21
11
|
// src/index.ts
|
|
22
12
|
import { execSync } from "child_process";
|
|
@@ -14537,6 +14527,25 @@ function date4(params) {
|
|
|
14537
14527
|
// node_modules/zod/v4/classic/external.js
|
|
14538
14528
|
config(en_default());
|
|
14539
14529
|
|
|
14530
|
+
// src/tools/tool-utils.ts
|
|
14531
|
+
function wrapHandler(handler) {
|
|
14532
|
+
return async (args) => {
|
|
14533
|
+
try {
|
|
14534
|
+
return await handler(args);
|
|
14535
|
+
} catch (e) {
|
|
14536
|
+
return {
|
|
14537
|
+
content: [
|
|
14538
|
+
{
|
|
14539
|
+
type: "text",
|
|
14540
|
+
text: `Error: ${e instanceof Error ? e.message : String(e)}`
|
|
14541
|
+
}
|
|
14542
|
+
],
|
|
14543
|
+
isError: true
|
|
14544
|
+
};
|
|
14545
|
+
}
|
|
14546
|
+
};
|
|
14547
|
+
}
|
|
14548
|
+
|
|
14540
14549
|
// src/tools/audit-tools.ts
|
|
14541
14550
|
function registerAuditTools(server2, useCase) {
|
|
14542
14551
|
server2.tool(
|
|
@@ -14548,51 +14557,41 @@ function registerAuditTools(server2, useCase) {
|
|
|
14548
14557
|
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
14549
14558
|
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
14550
14559
|
},
|
|
14551
|
-
async ({ code, framework, width, height }) => {
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
if (result.violations.length === 0) {
|
|
14557
|
-
return {
|
|
14558
|
-
content: [
|
|
14559
|
-
{
|
|
14560
|
-
type: "text",
|
|
14561
|
-
text: `\u2705 No accessibility violations found. (${result.passes} rules passed, ${result.incomplete} need review)`
|
|
14562
|
-
}
|
|
14563
|
-
]
|
|
14564
|
-
};
|
|
14565
|
-
}
|
|
14566
|
-
const report = result.violations.map((v) => {
|
|
14567
|
-
const nodes = v.nodes.slice(0, 3).map((n) => ` ${n.target.join(" > ")}
|
|
14568
|
-
${n.html}`).join("\n");
|
|
14569
|
-
return `[${v.impact?.toUpperCase()}] ${v.id}
|
|
14570
|
-
${v.description}
|
|
14571
|
-
${v.helpUrl}
|
|
14572
|
-
${nodes}`;
|
|
14573
|
-
}).join("\n\n");
|
|
14560
|
+
wrapHandler(async ({ code, framework, width, height }) => {
|
|
14561
|
+
const result = await useCase.auditA11y(code, framework, {
|
|
14562
|
+
viewport: { width, height }
|
|
14563
|
+
});
|
|
14564
|
+
if (result.violations.length === 0) {
|
|
14574
14565
|
return {
|
|
14575
14566
|
content: [
|
|
14576
14567
|
{
|
|
14577
14568
|
type: "text",
|
|
14578
|
-
text:
|
|
14579
|
-
|
|
14580
|
-
${report}
|
|
14581
|
-
|
|
14582
|
-
(${result.passes} rules passed, ${result.incomplete} need review)`
|
|
14569
|
+
text: `\u2705 No accessibility violations found. (${result.passes} rules passed, ${result.incomplete} need review)`
|
|
14583
14570
|
}
|
|
14584
14571
|
]
|
|
14585
14572
|
};
|
|
14586
|
-
} catch (error51) {
|
|
14587
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14588
|
-
return {
|
|
14589
|
-
content: [
|
|
14590
|
-
{ type: "text", text: `A11y audit failed: ${msg}` }
|
|
14591
|
-
],
|
|
14592
|
-
isError: true
|
|
14593
|
-
};
|
|
14594
14573
|
}
|
|
14595
|
-
|
|
14574
|
+
const report = result.violations.map((v) => {
|
|
14575
|
+
const nodes = v.nodes.slice(0, 3).map((n) => ` ${n.target.join(" > ")}
|
|
14576
|
+
${n.html}`).join("\n");
|
|
14577
|
+
return `[${v.impact?.toUpperCase()}] ${v.id}
|
|
14578
|
+
${v.description}
|
|
14579
|
+
${v.helpUrl}
|
|
14580
|
+
${nodes}`;
|
|
14581
|
+
}).join("\n\n");
|
|
14582
|
+
return {
|
|
14583
|
+
content: [
|
|
14584
|
+
{
|
|
14585
|
+
type: "text",
|
|
14586
|
+
text: `Found ${result.violations.length} accessibility violation(s):
|
|
14587
|
+
|
|
14588
|
+
${report}
|
|
14589
|
+
|
|
14590
|
+
(${result.passes} rules passed, ${result.incomplete} need review)`
|
|
14591
|
+
}
|
|
14592
|
+
]
|
|
14593
|
+
};
|
|
14594
|
+
})
|
|
14596
14595
|
);
|
|
14597
14596
|
server2.tool(
|
|
14598
14597
|
"perf_audit",
|
|
@@ -14603,33 +14602,23 @@ ${report}
|
|
|
14603
14602
|
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
14604
14603
|
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
14605
14604
|
},
|
|
14606
|
-
async ({ code, framework, width, height }) => {
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
} catch (error51) {
|
|
14624
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14625
|
-
return {
|
|
14626
|
-
content: [
|
|
14627
|
-
{ type: "text", text: `Perf audit failed: ${msg}` }
|
|
14628
|
-
],
|
|
14629
|
-
isError: true
|
|
14630
|
-
};
|
|
14631
|
-
}
|
|
14632
|
-
}
|
|
14605
|
+
wrapHandler(async ({ code, framework, width, height }) => {
|
|
14606
|
+
const metrics = await useCase.perfAudit(code, framework, {
|
|
14607
|
+
viewport: { width, height }
|
|
14608
|
+
});
|
|
14609
|
+
const lines = [
|
|
14610
|
+
`\u23F1\uFE0F Render time: ${metrics.renderTimeMs}ms`,
|
|
14611
|
+
`\u{1F4E6} DOM elements: ${metrics.domElements}`,
|
|
14612
|
+
`\u{1F333} DOM depth: ${metrics.domDepth}`,
|
|
14613
|
+
`\u{1F4DC} Scripts: ${metrics.scriptCount}`,
|
|
14614
|
+
`\u{1F3A8} Stylesheets: ${metrics.styleSheetCount}`,
|
|
14615
|
+
`\u{1F5BC}\uFE0F Images: ${metrics.imageCount}`,
|
|
14616
|
+
`\u{1F4D0} Total DOM size: ${(metrics.totalDomSize / 1024).toFixed(1)}KB`
|
|
14617
|
+
];
|
|
14618
|
+
return {
|
|
14619
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
14620
|
+
};
|
|
14621
|
+
})
|
|
14633
14622
|
);
|
|
14634
14623
|
}
|
|
14635
14624
|
|
|
@@ -14646,15 +14635,15 @@ function registerCatalogTools(server2, useCase) {
|
|
|
14646
14635
|
darkMode: external_exports.boolean().optional().default(false).describe("Render with dark mode"),
|
|
14647
14636
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
14648
14637
|
},
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14638
|
+
wrapHandler(
|
|
14639
|
+
async ({
|
|
14640
|
+
directory,
|
|
14641
|
+
recursive,
|
|
14642
|
+
width,
|
|
14643
|
+
height,
|
|
14644
|
+
darkMode,
|
|
14645
|
+
tailwindVersion
|
|
14646
|
+
}) => {
|
|
14658
14647
|
const results = await useCase.renderCatalog(directory, {
|
|
14659
14648
|
recursive,
|
|
14660
14649
|
viewport: { width, height },
|
|
@@ -14698,16 +14687,8 @@ function registerCatalogTools(server2, useCase) {
|
|
|
14698
14687
|
text: `\u{1F4E6} Component catalog: ${results.length} files from ${directory}`
|
|
14699
14688
|
});
|
|
14700
14689
|
return { content };
|
|
14701
|
-
} catch (error51) {
|
|
14702
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14703
|
-
return {
|
|
14704
|
-
content: [
|
|
14705
|
-
{ type: "text", text: `Catalog render failed: ${msg}` }
|
|
14706
|
-
],
|
|
14707
|
-
isError: true
|
|
14708
|
-
};
|
|
14709
14690
|
}
|
|
14710
|
-
|
|
14691
|
+
)
|
|
14711
14692
|
);
|
|
14712
14693
|
}
|
|
14713
14694
|
|
|
@@ -14723,44 +14704,36 @@ function registerDiffTools(server2, useCase) {
|
|
|
14723
14704
|
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
14724
14705
|
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
14725
14706
|
},
|
|
14726
|
-
async ({ before, after, framework, width, height }) => {
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
} catch (error51) {
|
|
14757
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14758
|
-
return {
|
|
14759
|
-
content: [{ type: "text", text: `Diff failed: ${msg}` }],
|
|
14760
|
-
isError: true
|
|
14761
|
-
};
|
|
14762
|
-
}
|
|
14763
|
-
}
|
|
14707
|
+
wrapHandler(async ({ before, after, framework, width, height }) => {
|
|
14708
|
+
const result = await useCase.diffComponent(before, after, framework, {
|
|
14709
|
+
viewport: { width, height }
|
|
14710
|
+
});
|
|
14711
|
+
const content = [
|
|
14712
|
+
{
|
|
14713
|
+
type: "text",
|
|
14714
|
+
text: `Visual diff: ${result.diffPercentage}% pixels changed (${result.diffPixels}/${result.totalPixels})`
|
|
14715
|
+
},
|
|
14716
|
+
{ type: "text", text: "Before:" },
|
|
14717
|
+
{
|
|
14718
|
+
type: "image",
|
|
14719
|
+
data: result.before,
|
|
14720
|
+
mimeType: "image/png"
|
|
14721
|
+
},
|
|
14722
|
+
{ type: "text", text: "After:" },
|
|
14723
|
+
{
|
|
14724
|
+
type: "image",
|
|
14725
|
+
data: result.after,
|
|
14726
|
+
mimeType: "image/png"
|
|
14727
|
+
},
|
|
14728
|
+
{ type: "text", text: "Diff (red = changed pixels):" },
|
|
14729
|
+
{
|
|
14730
|
+
type: "image",
|
|
14731
|
+
data: result.diff,
|
|
14732
|
+
mimeType: "image/png"
|
|
14733
|
+
}
|
|
14734
|
+
];
|
|
14735
|
+
return { content };
|
|
14736
|
+
})
|
|
14764
14737
|
);
|
|
14765
14738
|
server2.tool(
|
|
14766
14739
|
"diff_reference",
|
|
@@ -14777,17 +14750,17 @@ function registerDiffTools(server2, useCase) {
|
|
|
14777
14750
|
darkMode: external_exports.boolean().optional().default(false).describe("Render with dark mode"),
|
|
14778
14751
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
14779
14752
|
},
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14753
|
+
wrapHandler(
|
|
14754
|
+
async ({
|
|
14755
|
+
code,
|
|
14756
|
+
framework,
|
|
14757
|
+
referenceImage,
|
|
14758
|
+
width,
|
|
14759
|
+
height,
|
|
14760
|
+
threshold,
|
|
14761
|
+
darkMode,
|
|
14762
|
+
tailwindVersion
|
|
14763
|
+
}) => {
|
|
14791
14764
|
const result = await useCase.diffFromReference(
|
|
14792
14765
|
code,
|
|
14793
14766
|
framework,
|
|
@@ -14819,22 +14792,91 @@ function registerDiffTools(server2, useCase) {
|
|
|
14819
14792
|
}
|
|
14820
14793
|
];
|
|
14821
14794
|
return { content };
|
|
14822
|
-
} catch (error51) {
|
|
14823
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14824
|
-
return {
|
|
14825
|
-
content: [
|
|
14826
|
-
{ type: "text", text: `Reference diff failed: ${msg}` }
|
|
14827
|
-
],
|
|
14828
|
-
isError: true
|
|
14829
|
-
};
|
|
14830
14795
|
}
|
|
14831
|
-
|
|
14796
|
+
)
|
|
14832
14797
|
);
|
|
14833
14798
|
}
|
|
14834
14799
|
|
|
14835
|
-
// src/tools/render-tools.ts
|
|
14800
|
+
// src/tools/render-animation-tools.ts
|
|
14801
|
+
function registerRenderAnimationTools(server2, useCase) {
|
|
14802
|
+
server2.tool(
|
|
14803
|
+
"capture_animation",
|
|
14804
|
+
"Capture multiple frames of a CSS animation or transition over time. Returns sequential screenshots to verify animation behavior, timing, and smoothness.",
|
|
14805
|
+
{
|
|
14806
|
+
code: external_exports.string().describe("Component code with CSS animations/transitions"),
|
|
14807
|
+
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework: html, react, vue, or svelte"),
|
|
14808
|
+
frames: external_exports.number().optional().default(5).describe("Number of frames to capture"),
|
|
14809
|
+
duration: external_exports.number().optional().default(1e3).describe("Total capture duration in ms"),
|
|
14810
|
+
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
14811
|
+
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
14812
|
+
},
|
|
14813
|
+
wrapHandler(
|
|
14814
|
+
async ({ code, framework, frames, duration: duration3, width, height }) => {
|
|
14815
|
+
const results = await useCase.captureAnimation(code, framework, {
|
|
14816
|
+
frames,
|
|
14817
|
+
duration: duration3,
|
|
14818
|
+
viewport: { width, height }
|
|
14819
|
+
});
|
|
14820
|
+
const content = results.flatMap((r) => [
|
|
14821
|
+
{
|
|
14822
|
+
type: "image",
|
|
14823
|
+
data: r.image,
|
|
14824
|
+
mimeType: "image/png"
|
|
14825
|
+
},
|
|
14826
|
+
{
|
|
14827
|
+
type: "text",
|
|
14828
|
+
text: `[${r.timestamp}ms]`
|
|
14829
|
+
}
|
|
14830
|
+
]);
|
|
14831
|
+
return { content };
|
|
14832
|
+
}
|
|
14833
|
+
)
|
|
14834
|
+
);
|
|
14835
|
+
server2.tool(
|
|
14836
|
+
"render_interaction",
|
|
14837
|
+
"Render a component, simulate user interactions (click, hover, focus, type), then screenshot the result. Use this to verify hover states, dropdowns, modals, form inputs.",
|
|
14838
|
+
{
|
|
14839
|
+
code: external_exports.string().describe("Component code to render"),
|
|
14840
|
+
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework"),
|
|
14841
|
+
interactions: external_exports.array(
|
|
14842
|
+
external_exports.object({
|
|
14843
|
+
action: external_exports.enum(["click", "hover", "focus", "type", "wait"]).describe("Interaction type"),
|
|
14844
|
+
selector: external_exports.string().optional().describe("CSS selector for the target element"),
|
|
14845
|
+
value: external_exports.string().optional().describe("Text to type (for 'type' action)"),
|
|
14846
|
+
ms: external_exports.number().optional().describe("Wait duration in ms (for 'wait' action)")
|
|
14847
|
+
})
|
|
14848
|
+
).describe("Sequence of interactions to perform"),
|
|
14849
|
+
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
14850
|
+
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
14851
|
+
},
|
|
14852
|
+
wrapHandler(async ({ code, framework, interactions, width, height }) => {
|
|
14853
|
+
const result = await useCase.renderInteraction(
|
|
14854
|
+
code,
|
|
14855
|
+
framework,
|
|
14856
|
+
interactions,
|
|
14857
|
+
{
|
|
14858
|
+
viewport: { width, height }
|
|
14859
|
+
}
|
|
14860
|
+
);
|
|
14861
|
+
const content = [
|
|
14862
|
+
{
|
|
14863
|
+
type: "image",
|
|
14864
|
+
data: result.image,
|
|
14865
|
+
mimeType: "image/png"
|
|
14866
|
+
},
|
|
14867
|
+
{
|
|
14868
|
+
type: "text",
|
|
14869
|
+
text: `After ${interactions.length} interaction(s) \u2014 ${result.width}x${result.height}`
|
|
14870
|
+
}
|
|
14871
|
+
];
|
|
14872
|
+
return { content };
|
|
14873
|
+
})
|
|
14874
|
+
);
|
|
14875
|
+
}
|
|
14876
|
+
|
|
14877
|
+
// src/tools/render-file-tools.ts
|
|
14836
14878
|
import { extname } from "path";
|
|
14837
|
-
function
|
|
14879
|
+
function registerRenderFileTools(server2, useCase) {
|
|
14838
14880
|
server2.tool(
|
|
14839
14881
|
"render_file",
|
|
14840
14882
|
"Render a component file with full dependency resolution. Uses your project's Vite config to resolve imports, path aliases, CSS modules, and node_modules. Falls back to CDN-based rendering if Vite is unavailable. Auto-detects framework from file extension.",
|
|
@@ -14849,16 +14891,16 @@ function registerRenderTools(server2, useCase) {
|
|
|
14849
14891
|
"Project root directory override (defaults to auto-detect from file path)"
|
|
14850
14892
|
)
|
|
14851
14893
|
},
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14894
|
+
wrapHandler(
|
|
14895
|
+
async ({
|
|
14896
|
+
path,
|
|
14897
|
+
props,
|
|
14898
|
+
width,
|
|
14899
|
+
height,
|
|
14900
|
+
darkMode,
|
|
14901
|
+
tailwindVersion,
|
|
14902
|
+
projectRoot
|
|
14903
|
+
}) => {
|
|
14862
14904
|
const start = performance.now();
|
|
14863
14905
|
const { results, mode } = await useCase.renderFile(path, {
|
|
14864
14906
|
props,
|
|
@@ -14886,16 +14928,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14886
14928
|
}
|
|
14887
14929
|
]);
|
|
14888
14930
|
return { content };
|
|
14889
|
-
} catch (error51) {
|
|
14890
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14891
|
-
return {
|
|
14892
|
-
content: [
|
|
14893
|
-
{ type: "text", text: `File render failed: ${msg}` }
|
|
14894
|
-
],
|
|
14895
|
-
isError: true
|
|
14896
|
-
};
|
|
14897
14931
|
}
|
|
14898
|
-
|
|
14932
|
+
)
|
|
14899
14933
|
);
|
|
14900
14934
|
server2.tool(
|
|
14901
14935
|
"render_component",
|
|
@@ -14916,19 +14950,19 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14916
14950
|
css: external_exports.string().optional().describe("Custom CSS to inject (design tokens, variables, etc)"),
|
|
14917
14951
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
14918
14952
|
},
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14953
|
+
wrapHandler(
|
|
14954
|
+
async ({
|
|
14955
|
+
code,
|
|
14956
|
+
framework,
|
|
14957
|
+
engines,
|
|
14958
|
+
width,
|
|
14959
|
+
height,
|
|
14960
|
+
fullPage,
|
|
14961
|
+
darkMode,
|
|
14962
|
+
colorSchemes,
|
|
14963
|
+
css,
|
|
14964
|
+
tailwindVersion
|
|
14965
|
+
}) => {
|
|
14932
14966
|
const start = performance.now();
|
|
14933
14967
|
const schemes = colorSchemes ?? (darkMode ? ["dark"] : ["light"]);
|
|
14934
14968
|
const allResults = await Promise.all(
|
|
@@ -14959,15 +14993,19 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14959
14993
|
}
|
|
14960
14994
|
]);
|
|
14961
14995
|
return { content };
|
|
14962
|
-
} catch (error51) {
|
|
14963
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
14964
|
-
return {
|
|
14965
|
-
content: [{ type: "text", text: `Render failed: ${msg}` }],
|
|
14966
|
-
isError: true
|
|
14967
|
-
};
|
|
14968
14996
|
}
|
|
14969
|
-
|
|
14997
|
+
)
|
|
14970
14998
|
);
|
|
14999
|
+
}
|
|
15000
|
+
|
|
15001
|
+
// src/tools/render-visual-tools.ts
|
|
15002
|
+
function imageTextContent(image, text) {
|
|
15003
|
+
return [
|
|
15004
|
+
{ type: "image", data: image, mimeType: "image/png" },
|
|
15005
|
+
{ type: "text", text }
|
|
15006
|
+
];
|
|
15007
|
+
}
|
|
15008
|
+
function registerRenderVisualTools(server2, useCase) {
|
|
14971
15009
|
server2.tool(
|
|
14972
15010
|
"render_responsive",
|
|
14973
15011
|
"Render a component at mobile, tablet, and desktop sizes in one call. Returns 3 screenshots for responsive verification.",
|
|
@@ -14976,44 +15014,70 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
14976
15014
|
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework: html, react, vue, or svelte"),
|
|
14977
15015
|
devices: external_exports.array(external_exports.enum(["mobile", "tablet", "desktop"])).optional().default(["mobile", "tablet", "desktop"]).describe("Device sizes to render")
|
|
14978
15016
|
},
|
|
14979
|
-
async ({ code, framework, devices }) => {
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
],
|
|
15013
|
-
|
|
15014
|
-
}
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
+
wrapHandler(async ({ code, framework, devices }) => {
|
|
15018
|
+
const results = await Promise.all(
|
|
15019
|
+
devices.map(async (device) => {
|
|
15020
|
+
const preset = DEVICE_PRESETS[device];
|
|
15021
|
+
const [result] = await useCase.render(code, framework, {
|
|
15022
|
+
viewport: { width: preset.width, height: preset.height },
|
|
15023
|
+
fullPage: true,
|
|
15024
|
+
engines: ["chromium"]
|
|
15025
|
+
});
|
|
15026
|
+
return { device, ...result };
|
|
15027
|
+
})
|
|
15028
|
+
);
|
|
15029
|
+
const content = results.flatMap(
|
|
15030
|
+
(r) => imageTextContent(r.image, `[${r.device}] ${r.width}x${r.height}`)
|
|
15031
|
+
);
|
|
15032
|
+
return { content };
|
|
15033
|
+
})
|
|
15034
|
+
);
|
|
15035
|
+
server2.tool(
|
|
15036
|
+
"render_theme",
|
|
15037
|
+
"Render a component in both light and dark mode side-by-side. Returns 2 labeled screenshots for quick theme verification.",
|
|
15038
|
+
{
|
|
15039
|
+
code: external_exports.string().describe("Component code to render"),
|
|
15040
|
+
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework"),
|
|
15041
|
+
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
15042
|
+
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
15043
|
+
},
|
|
15044
|
+
wrapHandler(async ({ code, framework, width, height }) => {
|
|
15045
|
+
const start = performance.now();
|
|
15046
|
+
const [lightResults, darkResults] = await Promise.all([
|
|
15047
|
+
useCase.render(code, framework, {
|
|
15048
|
+
viewport: { width, height },
|
|
15049
|
+
fullPage: true,
|
|
15050
|
+
engines: ["chromium"],
|
|
15051
|
+
darkMode: false
|
|
15052
|
+
}),
|
|
15053
|
+
useCase.render(code, framework, {
|
|
15054
|
+
viewport: { width, height },
|
|
15055
|
+
fullPage: true,
|
|
15056
|
+
engines: ["chromium"],
|
|
15057
|
+
darkMode: true
|
|
15058
|
+
})
|
|
15059
|
+
]);
|
|
15060
|
+
const elapsed = Math.round(performance.now() - start);
|
|
15061
|
+
const content = [
|
|
15062
|
+
{ type: "text", text: "\u2600\uFE0F Light mode:" },
|
|
15063
|
+
{
|
|
15064
|
+
type: "image",
|
|
15065
|
+
data: lightResults[0].image,
|
|
15066
|
+
mimeType: "image/png"
|
|
15067
|
+
},
|
|
15068
|
+
{ type: "text", text: "\u{1F319} Dark mode:" },
|
|
15069
|
+
{
|
|
15070
|
+
type: "image",
|
|
15071
|
+
data: darkResults[0].image,
|
|
15072
|
+
mimeType: "image/png"
|
|
15073
|
+
},
|
|
15074
|
+
{
|
|
15075
|
+
type: "text",
|
|
15076
|
+
text: `${lightResults[0].width}x${lightResults[0].height} (${elapsed}ms)`
|
|
15077
|
+
}
|
|
15078
|
+
];
|
|
15079
|
+
return { content };
|
|
15080
|
+
})
|
|
15017
15081
|
);
|
|
15018
15082
|
server2.tool(
|
|
15019
15083
|
"render_variants",
|
|
@@ -15030,110 +15094,35 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
15030
15094
|
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
15031
15095
|
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
15032
15096
|
},
|
|
15033
|
-
async ({ code, variants, framework, width, height }) => {
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
const wrappedCode = framework === "react" ? `${code}
|
|
15097
|
+
wrapHandler(async ({ code, variants, framework, width, height }) => {
|
|
15098
|
+
const results = await Promise.all(
|
|
15099
|
+
variants.map(async (variant) => {
|
|
15100
|
+
const wrappedCode = framework === "react" ? `${code}
|
|
15038
15101
|
const _VARIANT_PROPS = ${variant.props};
|
|
15039
15102
|
function _VariantWrapper() { return <App {..._VARIANT_PROPS} />; }` : code;
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
const content = results.flatMap((r) => [
|
|
15058
|
-
{
|
|
15059
|
-
type: "image",
|
|
15060
|
-
data: r.image,
|
|
15061
|
-
mimeType: "image/png"
|
|
15062
|
-
},
|
|
15063
|
-
{
|
|
15064
|
-
type: "text",
|
|
15065
|
-
text: `[${r.label}] ${r.width}x${r.height}${r.consoleErrors.length ? `
|
|
15103
|
+
const renderFramework = framework;
|
|
15104
|
+
const overrideCode = framework === "react" ? wrappedCode.replace(
|
|
15105
|
+
/const _C = typeof App/,
|
|
15106
|
+
"const _C = typeof _VariantWrapper!=='undefined'?_VariantWrapper:typeof App"
|
|
15107
|
+
) : wrappedCode;
|
|
15108
|
+
const [result] = await useCase.render(overrideCode, renderFramework, {
|
|
15109
|
+
viewport: { width, height },
|
|
15110
|
+
fullPage: true,
|
|
15111
|
+
engines: ["chromium"]
|
|
15112
|
+
});
|
|
15113
|
+
return { label: variant.label, ...result };
|
|
15114
|
+
})
|
|
15115
|
+
);
|
|
15116
|
+
const content = results.flatMap(
|
|
15117
|
+
(r) => imageTextContent(
|
|
15118
|
+
r.image,
|
|
15119
|
+
`[${r.label}] ${r.width}x${r.height}${r.consoleErrors.length ? `
|
|
15066
15120
|
\u26A0\uFE0F Console errors:
|
|
15067
15121
|
${r.consoleErrors.join("\n")}` : ""}`
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15073
|
-
return {
|
|
15074
|
-
content: [
|
|
15075
|
-
{
|
|
15076
|
-
type: "text",
|
|
15077
|
-
text: `Variants render failed: ${msg}`
|
|
15078
|
-
}
|
|
15079
|
-
],
|
|
15080
|
-
isError: true
|
|
15081
|
-
};
|
|
15082
|
-
}
|
|
15083
|
-
}
|
|
15084
|
-
);
|
|
15085
|
-
server2.tool(
|
|
15086
|
-
"render_interaction",
|
|
15087
|
-
"Render a component, simulate user interactions (click, hover, focus, type), then screenshot the result. Use this to verify hover states, dropdowns, modals, form inputs.",
|
|
15088
|
-
{
|
|
15089
|
-
code: external_exports.string().describe("Component code to render"),
|
|
15090
|
-
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework"),
|
|
15091
|
-
interactions: external_exports.array(
|
|
15092
|
-
external_exports.object({
|
|
15093
|
-
action: external_exports.enum(["click", "hover", "focus", "type", "wait"]).describe("Interaction type"),
|
|
15094
|
-
selector: external_exports.string().optional().describe("CSS selector for the target element"),
|
|
15095
|
-
value: external_exports.string().optional().describe("Text to type (for 'type' action)"),
|
|
15096
|
-
ms: external_exports.number().optional().describe("Wait duration in ms (for 'wait' action)")
|
|
15097
|
-
})
|
|
15098
|
-
).describe("Sequence of interactions to perform"),
|
|
15099
|
-
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
15100
|
-
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
15101
|
-
},
|
|
15102
|
-
async ({ code, framework, interactions, width, height }) => {
|
|
15103
|
-
try {
|
|
15104
|
-
const result = await useCase.renderInteraction(
|
|
15105
|
-
code,
|
|
15106
|
-
framework,
|
|
15107
|
-
interactions,
|
|
15108
|
-
{
|
|
15109
|
-
viewport: { width, height }
|
|
15110
|
-
}
|
|
15111
|
-
);
|
|
15112
|
-
const content = [
|
|
15113
|
-
{
|
|
15114
|
-
type: "image",
|
|
15115
|
-
data: result.image,
|
|
15116
|
-
mimeType: "image/png"
|
|
15117
|
-
},
|
|
15118
|
-
{
|
|
15119
|
-
type: "text",
|
|
15120
|
-
text: `After ${interactions.length} interaction(s) \u2014 ${result.width}x${result.height}`
|
|
15121
|
-
}
|
|
15122
|
-
];
|
|
15123
|
-
return { content };
|
|
15124
|
-
} catch (error51) {
|
|
15125
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15126
|
-
return {
|
|
15127
|
-
content: [
|
|
15128
|
-
{
|
|
15129
|
-
type: "text",
|
|
15130
|
-
text: `Interaction render failed: ${msg}`
|
|
15131
|
-
}
|
|
15132
|
-
],
|
|
15133
|
-
isError: true
|
|
15134
|
-
};
|
|
15135
|
-
}
|
|
15136
|
-
}
|
|
15122
|
+
)
|
|
15123
|
+
);
|
|
15124
|
+
return { content };
|
|
15125
|
+
})
|
|
15137
15126
|
);
|
|
15138
15127
|
server2.tool(
|
|
15139
15128
|
"render_grid",
|
|
@@ -15150,91 +15139,19 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
15150
15139
|
cellWidth: external_exports.number().optional().default(400).describe("Width of each cell (px)"),
|
|
15151
15140
|
cellHeight: external_exports.number().optional().default(300).describe("Height of each cell (px)")
|
|
15152
15141
|
},
|
|
15153
|
-
|
|
15154
|
-
|
|
15142
|
+
wrapHandler(
|
|
15143
|
+
async ({ cells, framework, columns, cellWidth, cellHeight }) => {
|
|
15155
15144
|
const result = await useCase.renderGrid(cells, framework, {
|
|
15156
15145
|
columns,
|
|
15157
15146
|
viewport: { width: cellWidth, height: cellHeight }
|
|
15158
15147
|
});
|
|
15159
|
-
const content =
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
mimeType: "image/png"
|
|
15164
|
-
},
|
|
15165
|
-
{
|
|
15166
|
-
type: "text",
|
|
15167
|
-
text: `Grid: ${result.cells} cells, ${columns} columns \u2014 ${result.width}x${result.height}`
|
|
15168
|
-
}
|
|
15169
|
-
];
|
|
15170
|
-
return { content };
|
|
15171
|
-
} catch (error51) {
|
|
15172
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15173
|
-
return {
|
|
15174
|
-
content: [
|
|
15175
|
-
{ type: "text", text: `Grid render failed: ${msg}` }
|
|
15176
|
-
],
|
|
15177
|
-
isError: true
|
|
15178
|
-
};
|
|
15179
|
-
}
|
|
15180
|
-
}
|
|
15181
|
-
);
|
|
15182
|
-
server2.tool(
|
|
15183
|
-
"render_theme",
|
|
15184
|
-
"Render a component in both light and dark mode side-by-side. Returns 2 labeled screenshots for quick theme verification.",
|
|
15185
|
-
{
|
|
15186
|
-
code: external_exports.string().describe("Component code to render"),
|
|
15187
|
-
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework"),
|
|
15188
|
-
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
15189
|
-
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
15190
|
-
},
|
|
15191
|
-
async ({ code, framework, width, height }) => {
|
|
15192
|
-
try {
|
|
15193
|
-
const start = performance.now();
|
|
15194
|
-
const [lightResults, darkResults] = await Promise.all([
|
|
15195
|
-
useCase.render(code, framework, {
|
|
15196
|
-
viewport: { width, height },
|
|
15197
|
-
fullPage: true,
|
|
15198
|
-
engines: ["chromium"],
|
|
15199
|
-
darkMode: false
|
|
15200
|
-
}),
|
|
15201
|
-
useCase.render(code, framework, {
|
|
15202
|
-
viewport: { width, height },
|
|
15203
|
-
fullPage: true,
|
|
15204
|
-
engines: ["chromium"],
|
|
15205
|
-
darkMode: true
|
|
15206
|
-
})
|
|
15207
|
-
]);
|
|
15208
|
-
const elapsed = Math.round(performance.now() - start);
|
|
15209
|
-
const content = [
|
|
15210
|
-
{ type: "text", text: "\u2600\uFE0F Light mode:" },
|
|
15211
|
-
{
|
|
15212
|
-
type: "image",
|
|
15213
|
-
data: lightResults[0].image,
|
|
15214
|
-
mimeType: "image/png"
|
|
15215
|
-
},
|
|
15216
|
-
{ type: "text", text: "\u{1F319} Dark mode:" },
|
|
15217
|
-
{
|
|
15218
|
-
type: "image",
|
|
15219
|
-
data: darkResults[0].image,
|
|
15220
|
-
mimeType: "image/png"
|
|
15221
|
-
},
|
|
15222
|
-
{
|
|
15223
|
-
type: "text",
|
|
15224
|
-
text: `${lightResults[0].width}x${lightResults[0].height} (${elapsed}ms)`
|
|
15225
|
-
}
|
|
15226
|
-
];
|
|
15148
|
+
const content = imageTextContent(
|
|
15149
|
+
result.image,
|
|
15150
|
+
`Grid: ${result.cells} cells, ${columns} columns \u2014 ${result.width}x${result.height}`
|
|
15151
|
+
);
|
|
15227
15152
|
return { content };
|
|
15228
|
-
} catch (error51) {
|
|
15229
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15230
|
-
return {
|
|
15231
|
-
content: [
|
|
15232
|
-
{ type: "text", text: `Theme render failed: ${msg}` }
|
|
15233
|
-
],
|
|
15234
|
-
isError: true
|
|
15235
|
-
};
|
|
15236
15153
|
}
|
|
15237
|
-
|
|
15154
|
+
)
|
|
15238
15155
|
);
|
|
15239
15156
|
server2.tool(
|
|
15240
15157
|
"render_matrix",
|
|
@@ -15257,8 +15174,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
15257
15174
|
css: external_exports.string().optional().describe("Custom CSS to inject (design tokens, variables, etc)"),
|
|
15258
15175
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
15259
15176
|
},
|
|
15260
|
-
|
|
15261
|
-
|
|
15177
|
+
wrapHandler(
|
|
15178
|
+
async ({ code, framework, viewports, themes, css, tailwindVersion }) => {
|
|
15262
15179
|
const start = performance.now();
|
|
15263
15180
|
const resolvedViewports = viewports.map((v) => {
|
|
15264
15181
|
if (typeof v === "string") {
|
|
@@ -15282,81 +15199,31 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
15282
15199
|
}
|
|
15283
15200
|
);
|
|
15284
15201
|
const elapsed = Math.round(performance.now() - start);
|
|
15285
|
-
const content = results.flatMap(
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
mimeType: "image/png"
|
|
15290
|
-
},
|
|
15291
|
-
{
|
|
15292
|
-
type: "text",
|
|
15293
|
-
text: `[${r.viewport}/${r.theme}] ${r.width}x${r.height}${r.consoleErrors.length ? `
|
|
15202
|
+
const content = results.flatMap(
|
|
15203
|
+
(r) => imageTextContent(
|
|
15204
|
+
r.image,
|
|
15205
|
+
`[${r.viewport}/${r.theme}] ${r.width}x${r.height}${r.consoleErrors.length ? `
|
|
15294
15206
|
\u26A0\uFE0F Console errors:
|
|
15295
15207
|
${r.consoleErrors.join("\n")}` : ""}`
|
|
15296
|
-
|
|
15297
|
-
|
|
15208
|
+
)
|
|
15209
|
+
);
|
|
15298
15210
|
content.push({
|
|
15299
15211
|
type: "text",
|
|
15300
15212
|
text: `Matrix: ${resolvedViewports.length} viewports x ${themes.length} themes = ${results.length} renders (${elapsed}ms)`
|
|
15301
15213
|
});
|
|
15302
15214
|
return { content };
|
|
15303
|
-
} catch (error51) {
|
|
15304
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15305
|
-
return {
|
|
15306
|
-
content: [
|
|
15307
|
-
{ type: "text", text: `Matrix render failed: ${msg}` }
|
|
15308
|
-
],
|
|
15309
|
-
isError: true
|
|
15310
|
-
};
|
|
15311
15215
|
}
|
|
15312
|
-
|
|
15313
|
-
);
|
|
15314
|
-
server2.tool(
|
|
15315
|
-
"capture_animation",
|
|
15316
|
-
"Capture multiple frames of a CSS animation or transition over time. Returns sequential screenshots to verify animation behavior, timing, and smoothness.",
|
|
15317
|
-
{
|
|
15318
|
-
code: external_exports.string().describe("Component code with CSS animations/transitions"),
|
|
15319
|
-
framework: external_exports.enum(["html", "react", "vue", "svelte"]).default("react").describe("Framework: html, react, vue, or svelte"),
|
|
15320
|
-
frames: external_exports.number().optional().default(5).describe("Number of frames to capture"),
|
|
15321
|
-
duration: external_exports.number().optional().default(1e3).describe("Total capture duration in ms"),
|
|
15322
|
-
width: external_exports.number().optional().default(1280).describe("Viewport width (px)"),
|
|
15323
|
-
height: external_exports.number().optional().default(800).describe("Viewport height (px)")
|
|
15324
|
-
},
|
|
15325
|
-
async ({ code, framework, frames, duration: duration3, width, height }) => {
|
|
15326
|
-
try {
|
|
15327
|
-
const results = await useCase.captureAnimation(code, framework, {
|
|
15328
|
-
frames,
|
|
15329
|
-
duration: duration3,
|
|
15330
|
-
viewport: { width, height }
|
|
15331
|
-
});
|
|
15332
|
-
const content = results.flatMap((r) => [
|
|
15333
|
-
{
|
|
15334
|
-
type: "image",
|
|
15335
|
-
data: r.image,
|
|
15336
|
-
mimeType: "image/png"
|
|
15337
|
-
},
|
|
15338
|
-
{
|
|
15339
|
-
type: "text",
|
|
15340
|
-
text: `[${r.timestamp}ms]`
|
|
15341
|
-
}
|
|
15342
|
-
]);
|
|
15343
|
-
return { content };
|
|
15344
|
-
} catch (error51) {
|
|
15345
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15346
|
-
return {
|
|
15347
|
-
content: [
|
|
15348
|
-
{
|
|
15349
|
-
type: "text",
|
|
15350
|
-
text: `Animation capture failed: ${msg}`
|
|
15351
|
-
}
|
|
15352
|
-
],
|
|
15353
|
-
isError: true
|
|
15354
|
-
};
|
|
15355
|
-
}
|
|
15356
|
-
}
|
|
15216
|
+
)
|
|
15357
15217
|
);
|
|
15358
15218
|
}
|
|
15359
15219
|
|
|
15220
|
+
// src/tools/render-tools.ts
|
|
15221
|
+
function registerRenderTools(server2, useCase) {
|
|
15222
|
+
registerRenderFileTools(server2, useCase);
|
|
15223
|
+
registerRenderVisualTools(server2, useCase);
|
|
15224
|
+
registerRenderAnimationTools(server2, useCase);
|
|
15225
|
+
}
|
|
15226
|
+
|
|
15360
15227
|
// src/tools/screenshot-tools.ts
|
|
15361
15228
|
function registerScreenshotTools(server2, useCase) {
|
|
15362
15229
|
server2.tool(
|
|
@@ -15376,17 +15243,17 @@ function registerScreenshotTools(server2, useCase) {
|
|
|
15376
15243
|
"Number of retries with exponential backoff (300ms, 600ms, 1200ms...) if the page fails to load"
|
|
15377
15244
|
)
|
|
15378
15245
|
},
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15246
|
+
wrapHandler(
|
|
15247
|
+
async ({
|
|
15248
|
+
url: url2,
|
|
15249
|
+
engines,
|
|
15250
|
+
width,
|
|
15251
|
+
height,
|
|
15252
|
+
fullPage,
|
|
15253
|
+
waitForSelector,
|
|
15254
|
+
waitForNetworkIdle,
|
|
15255
|
+
retryOnError
|
|
15256
|
+
}) => {
|
|
15390
15257
|
const results = await useCase.screenshotUrlWithRetry(url2, {
|
|
15391
15258
|
viewport: { width, height },
|
|
15392
15259
|
engines,
|
|
@@ -15409,20 +15276,8 @@ ${r.consoleErrors.join("\n")}` : ""}`
|
|
|
15409
15276
|
}
|
|
15410
15277
|
]);
|
|
15411
15278
|
return { content };
|
|
15412
|
-
} catch (error51) {
|
|
15413
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15414
|
-
const retryInfo = retryOnError > 0 ? ` (after ${retryOnError + 1} attempts)` : "";
|
|
15415
|
-
return {
|
|
15416
|
-
content: [
|
|
15417
|
-
{
|
|
15418
|
-
type: "text",
|
|
15419
|
-
text: `Screenshot failed${retryInfo}: ${msg}`
|
|
15420
|
-
}
|
|
15421
|
-
],
|
|
15422
|
-
isError: true
|
|
15423
|
-
};
|
|
15424
15279
|
}
|
|
15425
|
-
|
|
15280
|
+
)
|
|
15426
15281
|
);
|
|
15427
15282
|
}
|
|
15428
15283
|
|
|
@@ -15442,16 +15297,16 @@ function registerSnapshotTools(server2, useCase) {
|
|
|
15442
15297
|
darkMode: external_exports.boolean().optional().default(false).describe("Render with dark mode"),
|
|
15443
15298
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
15444
15299
|
},
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15300
|
+
wrapHandler(
|
|
15301
|
+
async ({
|
|
15302
|
+
name,
|
|
15303
|
+
code,
|
|
15304
|
+
framework,
|
|
15305
|
+
width,
|
|
15306
|
+
height,
|
|
15307
|
+
darkMode,
|
|
15308
|
+
tailwindVersion
|
|
15309
|
+
}) => {
|
|
15455
15310
|
const result = await useCase.save(name, code, framework, {
|
|
15456
15311
|
viewport: { width, height },
|
|
15457
15312
|
darkMode,
|
|
@@ -15470,16 +15325,8 @@ function registerSnapshotTools(server2, useCase) {
|
|
|
15470
15325
|
}
|
|
15471
15326
|
]
|
|
15472
15327
|
};
|
|
15473
|
-
} catch (error51) {
|
|
15474
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15475
|
-
return {
|
|
15476
|
-
content: [
|
|
15477
|
-
{ type: "text", text: `Snapshot save failed: ${msg}` }
|
|
15478
|
-
],
|
|
15479
|
-
isError: true
|
|
15480
|
-
};
|
|
15481
15328
|
}
|
|
15482
|
-
|
|
15329
|
+
)
|
|
15483
15330
|
);
|
|
15484
15331
|
server2.tool(
|
|
15485
15332
|
"snapshot_check",
|
|
@@ -15493,16 +15340,16 @@ function registerSnapshotTools(server2, useCase) {
|
|
|
15493
15340
|
darkMode: external_exports.boolean().optional().default(false).describe("Render with dark mode"),
|
|
15494
15341
|
tailwindVersion: external_exports.enum(["3", "4"]).optional().default("3").describe("Tailwind CSS version (3 or 4)")
|
|
15495
15342
|
},
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15343
|
+
wrapHandler(
|
|
15344
|
+
async ({
|
|
15345
|
+
name,
|
|
15346
|
+
code,
|
|
15347
|
+
framework,
|
|
15348
|
+
width,
|
|
15349
|
+
height,
|
|
15350
|
+
darkMode,
|
|
15351
|
+
tailwindVersion
|
|
15352
|
+
}) => {
|
|
15506
15353
|
const result = await useCase.check(name, code, framework, {
|
|
15507
15354
|
viewport: { width, height },
|
|
15508
15355
|
darkMode,
|
|
@@ -15538,16 +15385,8 @@ function registerSnapshotTools(server2, useCase) {
|
|
|
15538
15385
|
}
|
|
15539
15386
|
];
|
|
15540
15387
|
return { content };
|
|
15541
|
-
} catch (error51) {
|
|
15542
|
-
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
15543
|
-
return {
|
|
15544
|
-
content: [
|
|
15545
|
-
{ type: "text", text: `Snapshot check failed: ${msg}` }
|
|
15546
|
-
],
|
|
15547
|
-
isError: true
|
|
15548
|
-
};
|
|
15549
15388
|
}
|
|
15550
|
-
|
|
15389
|
+
)
|
|
15551
15390
|
);
|
|
15552
15391
|
server2.tool(
|
|
15553
15392
|
"snapshot_list",
|
|
@@ -15581,6 +15420,133 @@ ${lines.join("\n")}`
|
|
|
15581
15420
|
);
|
|
15582
15421
|
}
|
|
15583
15422
|
|
|
15423
|
+
// src/tools/watch-tools.ts
|
|
15424
|
+
function registerWatchTools(server2, useCase) {
|
|
15425
|
+
server2.tool(
|
|
15426
|
+
"watch_start",
|
|
15427
|
+
"Start watching component files for changes. On every save, the component is automatically rendered and you will receive a notification \u2014 call watch_get_latest(id) to retrieve the rendered screenshot. Use this to create a live feedback loop while editing UI \u2014 the AI sees each change without you needing to call render_file manually.",
|
|
15428
|
+
{
|
|
15429
|
+
patterns: external_exports.array(external_exports.string()).describe(
|
|
15430
|
+
"Glob patterns or absolute file paths to watch (e.g. ['src/components/Button.tsx'])"
|
|
15431
|
+
),
|
|
15432
|
+
props: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Props to pass to the component on each render")
|
|
15433
|
+
},
|
|
15434
|
+
async ({ patterns, props }) => {
|
|
15435
|
+
const session = useCase.start(patterns, props, async (event) => {
|
|
15436
|
+
if (event.error) {
|
|
15437
|
+
await server2.server.notification({
|
|
15438
|
+
method: "notifications/message",
|
|
15439
|
+
params: {
|
|
15440
|
+
level: "error",
|
|
15441
|
+
logger: "frameshot/watch",
|
|
15442
|
+
data: `[${event.sessionId}] \u274C ${event.filePath}: ${event.error}`
|
|
15443
|
+
}
|
|
15444
|
+
});
|
|
15445
|
+
return;
|
|
15446
|
+
}
|
|
15447
|
+
const summary = `[${event.sessionId}] \u2713 ${event.filePath} \u2014 ${event.width}\xD7${event.height} \xB7 ${event.elapsedMs}ms \xB7 ${event.mode}` + (event.consoleErrors.length ? `
|
|
15448
|
+
\u26A0\uFE0F ${event.consoleErrors.join("\n")}` : "") + `
|
|
15449
|
+
Call watch_get_latest("${event.sessionId}") to see the render.`;
|
|
15450
|
+
await server2.server.notification({
|
|
15451
|
+
method: "notifications/message",
|
|
15452
|
+
params: {
|
|
15453
|
+
level: "info",
|
|
15454
|
+
logger: "frameshot/watch",
|
|
15455
|
+
data: summary
|
|
15456
|
+
}
|
|
15457
|
+
});
|
|
15458
|
+
});
|
|
15459
|
+
return {
|
|
15460
|
+
content: [
|
|
15461
|
+
{
|
|
15462
|
+
type: "text",
|
|
15463
|
+
text: `\u2713 Watch started (${session.id})
|
|
15464
|
+
Watching: ${patterns.join(", ")}
|
|
15465
|
+
|
|
15466
|
+
Every time a matched file is saved, it will be rendered automatically and a notification will appear in your MCP log stream. Call \`watch_get_latest\` with id "${session.id}" to retrieve the screenshot.
|
|
15467
|
+
|
|
15468
|
+
Call \`watch_stop\` with id "${session.id}" to stop.`
|
|
15469
|
+
}
|
|
15470
|
+
]
|
|
15471
|
+
};
|
|
15472
|
+
}
|
|
15473
|
+
);
|
|
15474
|
+
server2.tool(
|
|
15475
|
+
"watch_stop",
|
|
15476
|
+
"Stop a running file watcher started with watch_start.",
|
|
15477
|
+
{
|
|
15478
|
+
id: external_exports.string().describe("Watch session ID returned by watch_start (e.g. 'watch-1')")
|
|
15479
|
+
},
|
|
15480
|
+
async ({ id }) => {
|
|
15481
|
+
const stopped = useCase.stop(id);
|
|
15482
|
+
return {
|
|
15483
|
+
content: [
|
|
15484
|
+
{
|
|
15485
|
+
type: "text",
|
|
15486
|
+
text: stopped ? `\u2713 Watch session "${id}" stopped.` : `No active watch session with id "${id}".`
|
|
15487
|
+
}
|
|
15488
|
+
]
|
|
15489
|
+
};
|
|
15490
|
+
}
|
|
15491
|
+
);
|
|
15492
|
+
server2.tool(
|
|
15493
|
+
"watch_list",
|
|
15494
|
+
"List all currently active file watch sessions.",
|
|
15495
|
+
{},
|
|
15496
|
+
async () => {
|
|
15497
|
+
const sessions = useCase.activeSessions();
|
|
15498
|
+
if (sessions.length === 0) {
|
|
15499
|
+
return {
|
|
15500
|
+
content: [{ type: "text", text: "No active watch sessions." }]
|
|
15501
|
+
};
|
|
15502
|
+
}
|
|
15503
|
+
const lines = sessions.map(
|
|
15504
|
+
(s) => `${s.id}: watching ${s.patterns.join(", ")}`
|
|
15505
|
+
);
|
|
15506
|
+
return {
|
|
15507
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
15508
|
+
};
|
|
15509
|
+
}
|
|
15510
|
+
);
|
|
15511
|
+
server2.tool(
|
|
15512
|
+
"watch_get_latest",
|
|
15513
|
+
"Get the latest rendered screenshot from a watch session. Call this after receiving a watch notification to see the current state of the component.",
|
|
15514
|
+
{
|
|
15515
|
+
id: external_exports.string().describe("Watch session ID from watch_start")
|
|
15516
|
+
},
|
|
15517
|
+
async ({ id }) => {
|
|
15518
|
+
const render = useCase.getLatestRender(id);
|
|
15519
|
+
if (!render) {
|
|
15520
|
+
return {
|
|
15521
|
+
content: [
|
|
15522
|
+
{ type: "text", text: `No render yet for session "${id}".` }
|
|
15523
|
+
]
|
|
15524
|
+
};
|
|
15525
|
+
}
|
|
15526
|
+
if (render.error) {
|
|
15527
|
+
return {
|
|
15528
|
+
content: [
|
|
15529
|
+
{ type: "text", text: `Last render failed: ${render.error}` }
|
|
15530
|
+
]
|
|
15531
|
+
};
|
|
15532
|
+
}
|
|
15533
|
+
return {
|
|
15534
|
+
content: [
|
|
15535
|
+
{
|
|
15536
|
+
type: "image",
|
|
15537
|
+
data: render.image,
|
|
15538
|
+
mimeType: "image/png"
|
|
15539
|
+
},
|
|
15540
|
+
{
|
|
15541
|
+
type: "text",
|
|
15542
|
+
text: `${render.filePath} \u2014 ${render.width}\xD7${render.height} \xB7 ${render.elapsedMs}ms \xB7 ${render.mode}`
|
|
15543
|
+
}
|
|
15544
|
+
]
|
|
15545
|
+
};
|
|
15546
|
+
}
|
|
15547
|
+
);
|
|
15548
|
+
}
|
|
15549
|
+
|
|
15584
15550
|
// src/tools/register.ts
|
|
15585
15551
|
function registerAllTools(server2, useCases) {
|
|
15586
15552
|
registerRenderTools(server2, useCases.render);
|
|
@@ -15589,36 +15555,18 @@ function registerAllTools(server2, useCases) {
|
|
|
15589
15555
|
registerAuditTools(server2, useCases.audit);
|
|
15590
15556
|
registerSnapshotTools(server2, useCases.snapshot);
|
|
15591
15557
|
registerCatalogTools(server2, useCases.catalog);
|
|
15558
|
+
registerWatchTools(server2, useCases.watch);
|
|
15592
15559
|
}
|
|
15593
15560
|
|
|
15594
15561
|
// src/index.ts
|
|
15595
|
-
var
|
|
15596
|
-
var htmlBuilder = new HtmlBuilder();
|
|
15597
|
-
var imageComparator = new ImageComparator();
|
|
15598
|
-
var snapshotStore = new SnapshotStore();
|
|
15599
|
-
var viteBundler = new ViteBundler();
|
|
15600
|
-
var renderUseCase = new RenderUseCase(
|
|
15601
|
-
browserPool,
|
|
15602
|
-
htmlBuilder,
|
|
15603
|
-
imageComparator,
|
|
15604
|
-
viteBundler
|
|
15605
|
-
);
|
|
15606
|
-
var screenshotUseCase = new ScreenshotUseCase(browserPool);
|
|
15607
|
-
var diffUseCase = new DiffUseCase(renderUseCase, imageComparator);
|
|
15608
|
-
var auditUseCase = new AuditUseCase(browserPool, htmlBuilder);
|
|
15609
|
-
var snapshotUseCase = new SnapshotUseCase(
|
|
15610
|
-
snapshotStore,
|
|
15611
|
-
renderUseCase,
|
|
15612
|
-
diffUseCase
|
|
15613
|
-
);
|
|
15614
|
-
var catalogUseCase = new CatalogUseCase(renderUseCase);
|
|
15562
|
+
var container = createContainer();
|
|
15615
15563
|
async function ensureBrowser() {
|
|
15616
15564
|
try {
|
|
15617
|
-
await
|
|
15565
|
+
await container.pool.warmup(["chromium"]);
|
|
15618
15566
|
} catch {
|
|
15619
15567
|
try {
|
|
15620
15568
|
execSync("npx playwright install chromium", { stdio: "pipe" });
|
|
15621
|
-
await
|
|
15569
|
+
await container.pool.warmup(["chromium"]);
|
|
15622
15570
|
} catch {
|
|
15623
15571
|
}
|
|
15624
15572
|
}
|
|
@@ -15626,25 +15574,24 @@ async function ensureBrowser() {
|
|
|
15626
15574
|
await ensureBrowser();
|
|
15627
15575
|
var server = new McpServer({
|
|
15628
15576
|
name: "frameshot",
|
|
15629
|
-
version: "0.
|
|
15577
|
+
version: "0.8.0"
|
|
15630
15578
|
});
|
|
15631
15579
|
registerAllTools(server, {
|
|
15632
|
-
render: renderUseCase,
|
|
15633
|
-
screenshot: screenshotUseCase,
|
|
15634
|
-
diff: diffUseCase,
|
|
15635
|
-
audit: auditUseCase,
|
|
15636
|
-
snapshot: snapshotUseCase,
|
|
15637
|
-
catalog: catalogUseCase
|
|
15580
|
+
render: container.renderUseCase,
|
|
15581
|
+
screenshot: container.screenshotUseCase,
|
|
15582
|
+
diff: container.diffUseCase,
|
|
15583
|
+
audit: container.auditUseCase,
|
|
15584
|
+
snapshot: container.snapshotUseCase,
|
|
15585
|
+
catalog: container.catalogUseCase,
|
|
15586
|
+
watch: container.watchUseCase
|
|
15638
15587
|
});
|
|
15639
15588
|
var transport = new StdioServerTransport();
|
|
15640
15589
|
await server.connect(transport);
|
|
15641
15590
|
process.on("SIGINT", async () => {
|
|
15642
|
-
await
|
|
15643
|
-
await browserPool.shutdown();
|
|
15591
|
+
await container.shutdown();
|
|
15644
15592
|
process.exit(0);
|
|
15645
15593
|
});
|
|
15646
15594
|
process.on("SIGTERM", async () => {
|
|
15647
|
-
await
|
|
15648
|
-
await browserPool.shutdown();
|
|
15595
|
+
await container.shutdown();
|
|
15649
15596
|
process.exit(0);
|
|
15650
15597
|
});
|