frameshot-mcp 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -86
- package/action.yml +46 -32
- package/dist/chunk-3LVWVDET.js +849 -0
- package/dist/chunk-67JZQ6OI.js +819 -0
- package/dist/chunk-AZCGKIMU.js +850 -0
- package/dist/chunk-B3CLIGWU.js +786 -0
- package/dist/chunk-C6QSY4WR.js +811 -0
- package/dist/chunk-DX54PJKO.js +603 -0
- package/dist/chunk-EMCJGIMY.js +984 -0
- package/dist/chunk-FQNWGR62.js +849 -0
- package/dist/chunk-FTYTZW6D.js +203 -0
- package/dist/chunk-JGVKYXY2.js +857 -0
- package/dist/chunk-JYPEA4P2.js +846 -0
- package/dist/chunk-KHK35HDD.js +855 -0
- package/dist/chunk-Q7A3DLED.js +848 -0
- package/dist/chunk-SIA6XEHM.js +811 -0
- package/dist/chunk-ST35YDI6.js +834 -0
- package/dist/chunk-T5OBJK35.js +855 -0
- package/dist/chunk-U3GHS7KO.js +837 -0
- package/dist/chunk-WS2ASCD6.js +683 -0
- package/dist/chunk-WZMHVSUA.js +847 -0
- package/dist/chunk-ZZST6K7Y.js +987 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +407 -0
- package/dist/index.js +34 -15
- package/dist/renderer.d.ts +65 -9
- package/dist/renderer.js +11 -5
- package/package.json +15 -5
- package/scripts/render-changed.mjs +183 -40
package/README.md
CHANGED
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
# frameshot
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/frameshot-mcp) [](https://www.npmjs.com/package/frameshot-mcp) [](https://github.com/kamegoro/frameshot) [](https://www.npmjs.com/package/frameshot-mcp) [](https://www.npmjs.com/package/frameshot-mcp) [](https://github.com/kamegoro/frameshot) [](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Give your AI agent eyes.** Render any component — with real imports, Tailwind, and CSS Modules resolved via Vite — directly in your terminal or AI chat. No stories. No config. Just point at a file.
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<img src="docs/demo.
|
|
8
|
+
<img src="docs/demo.gif" alt="frameshot demo" width="720" />
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
AI coding agents (Claude Code, Cursor, Copilot, Cline) write UI code **blind**. They generate HTML/CSS/React but never see the rendered result. You end up:
|
|
14
|
-
|
|
15
|
-
- Switching to the browser to check every change
|
|
16
|
-
- Screenshotting and pasting back into chat
|
|
17
|
-
- Burning tokens on fix loops that never converge
|
|
18
|
-
|
|
19
|
-
**frameshot closes this loop.** The agent calls one tool, gets a screenshot back in 120ms, and self-corrects.
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
AI writes code → frameshot renders → AI sees result → AI self-corrects → ships
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Install
|
|
11
|
+
**Claude Code**
|
|
26
12
|
|
|
27
13
|
```bash
|
|
28
14
|
claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
29
15
|
```
|
|
30
16
|
|
|
31
17
|
<details>
|
|
32
|
-
<summary>Cursor
|
|
18
|
+
<summary>Cursor · VS Code · Windsurf · Cline</summary>
|
|
33
19
|
|
|
34
20
|
```json
|
|
35
21
|
{
|
|
@@ -44,72 +30,116 @@ claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
|
44
30
|
|
|
45
31
|
</details>
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| `render_interaction` | Simulate click/hover/type, then screenshot result. |
|
|
58
|
-
| `render_grid` | Multiple snippets in a labeled grid image. |
|
|
59
|
-
| `diff_component` | Before/after pixel diff with % changed. |
|
|
60
|
-
| `audit_a11y` | axe-core accessibility audit (WCAG violations). |
|
|
61
|
-
| `perf_audit` | DOM element count, depth, render timing. |
|
|
62
|
-
| `render_matrix` | Viewport × theme matrix in one call. |
|
|
63
|
-
| `capture_animation` | Multi-frame CSS animation capture. |
|
|
64
|
-
| `diff_reference` | Compare render against a reference image (Figma QA). |
|
|
65
|
-
| `render_catalog` | Render all components in a directory (zero-config Storybook). |
|
|
66
|
-
| `snapshot_save` | Save render as named baseline snapshot. |
|
|
67
|
-
| `snapshot_check` | Compare current render against saved snapshot. |
|
|
68
|
-
| `snapshot_list` | List all saved snapshots. |
|
|
69
|
-
|
|
70
|
-
### Example
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// AI renders its own output to verify
|
|
74
|
-
render_component({
|
|
75
|
-
code: `function App() {
|
|
76
|
-
return (
|
|
77
|
-
<div className="p-8 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-2xl shadow-xl">
|
|
78
|
-
<h1 className="text-3xl font-bold">Pricing</h1>
|
|
79
|
-
<p className="text-5xl mt-4">$29<span className="text-lg">/mo</span></p>
|
|
80
|
-
</div>
|
|
81
|
-
)
|
|
82
|
-
}`,
|
|
83
|
-
framework: "react"
|
|
84
|
-
})
|
|
85
|
-
// → Screenshot returned in 118ms. AI can see it looks correct.
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Using with Claude
|
|
36
|
+
|
|
37
|
+
Once installed, just tell Claude what you want:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
"Render src/components/PricingCard.tsx and check the layout looks right"
|
|
41
|
+
"Refactor the dashboard to use CSS grid — render it before and after and make sure nothing broke"
|
|
42
|
+
"Show me all components in src/components/ as screenshots"
|
|
86
43
|
```
|
|
87
44
|
|
|
88
|
-
|
|
45
|
+
Claude will automatically call `render_file`, `diff_component`, or `render_catalog` as needed. No manual tool calls required.
|
|
89
46
|
|
|
90
|
-
|
|
91
|
-
|---|-----------|-----------------|-------------|---------------|
|
|
92
|
-
| Setup | stories + addons + server | SaaS signup + billing | Browser install | **`npx` — done** |
|
|
93
|
-
| Speed | Dev server startup | Cloud round-trip | 2-5s | **~120ms** |
|
|
94
|
-
| Cost | Free (stories = labor) | $149-800+/mo | Free | **Free forever** |
|
|
95
|
-
| AI-native | Needs pre-written stories | No MCP support | Full-page only | **Any code snippet** |
|
|
96
|
-
| Frameworks | React (+ addons) | Whatever Storybook supports | HTML only | **React/Vue/Svelte/HTML** |
|
|
97
|
-
| Cross-browser | Chromatic ($$$) | Per-snapshot pricing | Single browser | **3 engines, free** |
|
|
47
|
+
---
|
|
98
48
|
|
|
99
|
-
##
|
|
49
|
+
## Render
|
|
100
50
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
51
|
+
Point to a file. Get a screenshot. All imports resolved.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
render_file("src/components/Dashboard.tsx")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<img src="docs/example-output.png" alt="Rendered PricingCard with full Tailwind styling" width="280" />
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
Real Tailwind. Real CSS Modules. Real dependencies — not a CDN polyfill.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Visual diff
|
|
66
|
+
|
|
67
|
+
Catch regressions before you ship. Before / after / changed pixels — all at once.
|
|
68
|
+
|
|
69
|
+
<p align="center">
|
|
70
|
+
<img src="docs/diff-before.png" alt="Before" width="210" />
|
|
71
|
+
|
|
72
|
+
<img src="docs/diff-after.png" alt="After" width="210" />
|
|
73
|
+
|
|
74
|
+
<img src="docs/diff-result.png" alt="Diff" width="210" />
|
|
75
|
+
</p>
|
|
107
76
|
|
|
108
|
-
|
|
77
|
+
```
|
|
78
|
+
diff_component("src/components/MetricCard.tsx")
|
|
79
|
+
→ 0.7% changed
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Tools
|
|
85
|
+
|
|
86
|
+
| Tool | What it does |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `render_file` | **Render a file via Vite** — full dep resolution, props support |
|
|
89
|
+
| `render_component` | Render a self-contained snippet (React / Vue / Svelte / HTML) |
|
|
90
|
+
| `screenshot_url` | Screenshot any URL — localhost, staging, prod |
|
|
91
|
+
| `diff_component` | Pixel diff before/after with % changed |
|
|
92
|
+
| `render_responsive` | Mobile + tablet + desktop in one call |
|
|
93
|
+
| `render_theme` | Light + dark mode side by side |
|
|
94
|
+
| `audit_a11y` | axe-core accessibility audit (WCAG) |
|
|
95
|
+
| `render_catalog` | Render every component in a directory |
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>All 18 tools</summary>
|
|
99
|
+
|
|
100
|
+
| Tool | What it does |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `render_file` | Render a project file with full Vite dependency resolution |
|
|
103
|
+
| `render_component` | Render a self-contained snippet → screenshot |
|
|
104
|
+
| `screenshot_url` | Screenshot any URL with retry and network idle wait |
|
|
105
|
+
| `render_responsive` | Mobile + tablet + desktop in one call |
|
|
106
|
+
| `render_variants` | Multiple prop/state variants at once |
|
|
107
|
+
| `render_theme` | Light + dark mode side by side |
|
|
108
|
+
| `render_interaction` | Simulate click/hover/type, then screenshot |
|
|
109
|
+
| `render_grid` | Multiple snippets in a labeled grid |
|
|
110
|
+
| `render_matrix` | Viewport × theme matrix in one call |
|
|
111
|
+
| `capture_animation` | Multi-frame CSS animation capture |
|
|
112
|
+
| `diff_component` | Before/after pixel diff with % changed |
|
|
113
|
+
| `diff_reference` | Compare render against a reference image (Figma QA) |
|
|
114
|
+
| `audit_a11y` | axe-core accessibility audit |
|
|
115
|
+
| `perf_audit` | DOM count, depth, render timing |
|
|
116
|
+
| `render_catalog` | Render all components in a directory |
|
|
117
|
+
| `snapshot_save` | Save a render as named baseline |
|
|
118
|
+
| `snapshot_check` | Compare current render against saved baseline |
|
|
119
|
+
| `snapshot_list` | List all saved snapshots |
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## CLI
|
|
126
|
+
|
|
127
|
+
No AI client required. Use frameshot standalone:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx frameshot render src/components/Button.tsx
|
|
131
|
+
npx frameshot render src/Card.tsx --props '{"title":"Hello"}'
|
|
132
|
+
npx frameshot catalog src/components/ --recursive
|
|
133
|
+
npx frameshot diff src/components/Header.tsx
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
|
|
137
|
+
|
|
138
|
+
---
|
|
109
139
|
|
|
110
140
|
## GitHub Action
|
|
111
141
|
|
|
112
|
-
|
|
142
|
+
Free Chromatic alternative. Auto-posts before/after screenshots with diff on every PR:
|
|
113
143
|
|
|
114
144
|
```yaml
|
|
115
145
|
# .github/workflows/visual-preview.yml
|
|
@@ -120,28 +150,55 @@ jobs:
|
|
|
120
150
|
runs-on: ubuntu-latest
|
|
121
151
|
steps:
|
|
122
152
|
- uses: actions/checkout@v4
|
|
153
|
+
with:
|
|
154
|
+
fetch-depth: 0
|
|
123
155
|
- uses: kamegoro/frameshot@main
|
|
124
156
|
with:
|
|
125
157
|
paths: "./src/components/*.tsx"
|
|
126
|
-
framework: react
|
|
127
158
|
```
|
|
128
159
|
|
|
129
|
-
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Performance
|
|
163
|
+
|
|
164
|
+
Typical measured times (varies by machine and project size):
|
|
165
|
+
|
|
166
|
+
| Scenario | Time |
|
|
167
|
+
|----------|------|
|
|
168
|
+
| Warm render (Vite server cached) | **~200–500ms** |
|
|
169
|
+
| CDN fallback (no Vite) | **~120ms** |
|
|
170
|
+
| Cold start (first render) | ~1–3s |
|
|
171
|
+
| Subsequent renders (same session) | ~200ms |
|
|
172
|
+
|
|
173
|
+
Vite server is cached per project root. Browser pool stays warm between MCP calls.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## vs. alternatives
|
|
130
178
|
|
|
131
|
-
|
|
179
|
+
| | Storybook | Chromatic | Browser MCP | **frameshot** |
|
|
180
|
+
|---|-----------|-----------|-------------|---------------|
|
|
181
|
+
| Setup | Stories + config | SaaS signup | Browser install | **`npx` — done** |
|
|
182
|
+
| Speed | Dev server startup | Cloud round-trip | 2–5s | **~200ms warm** |
|
|
183
|
+
| Cost | Free (labor cost) | $149–800+/mo | Free | **Free forever** |
|
|
184
|
+
| Stories needed | Yes | Yes | No | **No** |
|
|
185
|
+
| Real imports | Via Storybook | Via Storybook | No | **Via Vite** |
|
|
186
|
+
| Works offline | Yes | No | Yes | **Yes** |
|
|
187
|
+
| AI-native (MCP) | No | No | Full-page only | **Component-level** |
|
|
132
188
|
|
|
133
|
-
|
|
134
|
-
- [Cursor rules](examples/cursor-rules.md) — Verify UI on every edit
|
|
189
|
+
---
|
|
135
190
|
|
|
136
191
|
## Development
|
|
137
192
|
|
|
138
193
|
```bash
|
|
139
|
-
git clone https://github.com/kamegoro/frameshot.git
|
|
140
|
-
|
|
194
|
+
git clone https://github.com/kamegoro/frameshot.git
|
|
195
|
+
cd frameshot && npm install
|
|
196
|
+
npx playwright install chromium
|
|
197
|
+
npm run build && npm test
|
|
141
198
|
```
|
|
142
199
|
|
|
143
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
200
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for architecture details.
|
|
144
201
|
|
|
145
|
-
|
|
202
|
+
---
|
|
146
203
|
|
|
147
|
-
MIT
|
|
204
|
+
MIT © [kamegoro](https://github.com/kamegoro)
|
package/action.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: "frameshot — Visual Preview"
|
|
2
|
-
description: "Render components and attach before/after screenshots to PRs.
|
|
2
|
+
description: "Render changed components with full Vite dependency resolution and attach before/after screenshots to PRs."
|
|
3
3
|
branding:
|
|
4
4
|
icon: "camera"
|
|
5
5
|
color: "blue"
|
|
@@ -8,26 +8,14 @@ inputs:
|
|
|
8
8
|
paths:
|
|
9
9
|
description: "Glob patterns for component files to render (newline or comma separated)"
|
|
10
10
|
required: true
|
|
11
|
-
framework:
|
|
12
|
-
description: "Framework: react, vue, svelte, or html"
|
|
13
|
-
required: false
|
|
14
|
-
default: "react"
|
|
15
11
|
width:
|
|
16
|
-
description: "Viewport width (
|
|
12
|
+
description: "Viewport width in px. Leave empty for auto-fit (recommended)."
|
|
17
13
|
required: false
|
|
18
|
-
default: "
|
|
14
|
+
default: ""
|
|
19
15
|
height:
|
|
20
|
-
description: "Viewport height (
|
|
21
|
-
required: false
|
|
22
|
-
default: "800"
|
|
23
|
-
dark-mode:
|
|
24
|
-
description: "Render with dark mode"
|
|
16
|
+
description: "Viewport height in px. Leave empty for auto-fit (recommended)."
|
|
25
17
|
required: false
|
|
26
|
-
default: "
|
|
27
|
-
tailwind-version:
|
|
28
|
-
description: "Tailwind CSS version (3 or 4)"
|
|
29
|
-
required: false
|
|
30
|
-
default: "3"
|
|
18
|
+
default: ""
|
|
31
19
|
comment:
|
|
32
20
|
description: "Post a PR comment with screenshots"
|
|
33
21
|
required: false
|
|
@@ -41,7 +29,7 @@ runs:
|
|
|
41
29
|
using: "composite"
|
|
42
30
|
steps:
|
|
43
31
|
- name: Setup Node.js
|
|
44
|
-
uses: actions/setup-node@
|
|
32
|
+
uses: actions/setup-node@v6
|
|
45
33
|
with:
|
|
46
34
|
node-version: "20"
|
|
47
35
|
|
|
@@ -58,11 +46,9 @@ runs:
|
|
|
58
46
|
shell: bash
|
|
59
47
|
env:
|
|
60
48
|
INPUT_PATHS: ${{ inputs.paths }}
|
|
61
|
-
INPUT_FRAMEWORK: ${{ inputs.framework }}
|
|
62
49
|
INPUT_WIDTH: ${{ inputs.width }}
|
|
63
50
|
INPUT_HEIGHT: ${{ inputs.height }}
|
|
64
|
-
|
|
65
|
-
INPUT_TAILWIND_VERSION: ${{ inputs.tailwind-version }}
|
|
51
|
+
INPUT_BASE_REF: ${{ github.event.pull_request.base.sha }}
|
|
66
52
|
run: |
|
|
67
53
|
node "${{ github.action_path }}/scripts/render-changed.mjs"
|
|
68
54
|
|
|
@@ -83,26 +69,54 @@ runs:
|
|
|
83
69
|
return;
|
|
84
70
|
}
|
|
85
71
|
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
console.log('No
|
|
72
|
+
const manifestPath = path.join(dir, 'manifest.json');
|
|
73
|
+
if (!fs.existsSync(manifestPath)) {
|
|
74
|
+
console.log('No manifest found');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
79
|
+
if (manifest.length === 0) {
|
|
80
|
+
console.log('No components rendered');
|
|
89
81
|
return;
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
// Upload screenshots as artifacts and build comment
|
|
93
84
|
let body = '## 📸 frameshot — Visual Preview\n\n';
|
|
94
|
-
body += '| Component | Screenshot |\n|---|---|\n';
|
|
95
85
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
const hasBeforeAfter = manifest.some(m => m.hasBefore);
|
|
87
|
+
|
|
88
|
+
if (hasBeforeAfter) {
|
|
89
|
+
body += '| Component | Before | After | Diff |\n|---|---|---|---|\n';
|
|
90
|
+
} else {
|
|
91
|
+
body += '| Component | Screenshot |\n|---|---|\n';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const entry of manifest) {
|
|
95
|
+
const afterFile = path.join(dir, `${entry.name}_after.png`);
|
|
96
|
+
if (!fs.existsSync(afterFile)) continue;
|
|
97
|
+
|
|
98
|
+
const afterData = fs.readFileSync(afterFile, 'base64');
|
|
99
|
+
|
|
100
|
+
if (hasBeforeAfter) {
|
|
101
|
+
const beforeFile = path.join(dir, `${entry.name}_before.png`);
|
|
102
|
+
const diffFile = path.join(dir, `${entry.name}_diff.png`);
|
|
103
|
+
|
|
104
|
+
const beforeImg = fs.existsSync(beforeFile)
|
|
105
|
+
? `<img src="data:image/png;base64,${fs.readFileSync(beforeFile, 'base64')}" width="250" />`
|
|
106
|
+
: '*(new)*';
|
|
107
|
+
|
|
108
|
+
const diffImg = fs.existsSync(diffFile) && entry.diffPercentage > 0
|
|
109
|
+
? `<img src="data:image/png;base64,${fs.readFileSync(diffFile, 'base64')}" width="250" /><br/>${entry.diffPercentage.toFixed(1)}% changed`
|
|
110
|
+
: entry.hasBefore ? '✅ No change' : '—';
|
|
111
|
+
|
|
112
|
+
body += `| \`${entry.name}\` | ${beforeImg} | <img src="data:image/png;base64,${afterData}" width="250" /> | ${diffImg} |\n`;
|
|
113
|
+
} else {
|
|
114
|
+
body += `| \`${entry.name}\` | <img src="data:image/png;base64,${afterData}" width="400" /> |\n`;
|
|
115
|
+
}
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
body += '\n---\n*Generated by [frameshot](https://github.com/kamegoro/frameshot)*';
|
|
104
119
|
|
|
105
|
-
// Find existing comment
|
|
106
120
|
const { data: comments } = await github.rest.issues.listComments({
|
|
107
121
|
owner: context.repo.owner,
|
|
108
122
|
repo: context.repo.repo,
|