frameshot-mcp 0.3.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 +157 -47
- package/action.yml +144 -0
- package/dist/chunk-3LVWVDET.js +849 -0
- package/dist/chunk-47YJG5HR.js +690 -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 +1077 -653
- package/dist/renderer.d.ts +297 -0
- package/dist/renderer.js +34 -0
- package/package.json +32 -7
- package/scripts/render-changed.mjs +233 -0
- package/scripts/setup-labels.sh +48 -0
package/README.md
CHANGED
|
@@ -1,24 +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
|
-
|
|
8
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/demo.gif" alt="frameshot demo" width="720" />
|
|
9
|
+
</p>
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
AI writes code → AI calls frameshot → AI sees the result → AI self-corrects
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Install
|
|
11
|
+
**Claude Code**
|
|
15
12
|
|
|
16
13
|
```bash
|
|
17
14
|
claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
18
15
|
```
|
|
19
16
|
|
|
20
17
|
<details>
|
|
21
|
-
<summary>Cursor
|
|
18
|
+
<summary>Cursor · VS Code · Windsurf · Cline</summary>
|
|
22
19
|
|
|
23
20
|
```json
|
|
24
21
|
{
|
|
@@ -33,62 +30,175 @@ claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
|
33
30
|
|
|
34
31
|
</details>
|
|
35
32
|
|
|
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"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Claude will automatically call `render_file`, `diff_component`, or `render_catalog` as needed. No manual tool calls required.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Render
|
|
50
|
+
|
|
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>
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
diff_component("src/components/MetricCard.tsx")
|
|
79
|
+
→ 0.7% changed
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
36
84
|
## Tools
|
|
37
85
|
|
|
38
86
|
| Tool | What it does |
|
|
39
87
|
|------|-------------|
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
59
134
|
```
|
|
60
135
|
|
|
61
|
-
|
|
136
|
+
Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
|
|
137
|
+
|
|
138
|
+
---
|
|
62
139
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
140
|
+
## GitHub Action
|
|
141
|
+
|
|
142
|
+
Free Chromatic alternative. Auto-posts before/after screenshots with diff on every PR:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
# .github/workflows/visual-preview.yml
|
|
146
|
+
name: Visual Preview
|
|
147
|
+
on: [pull_request]
|
|
148
|
+
jobs:
|
|
149
|
+
preview:
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
steps:
|
|
152
|
+
- uses: actions/checkout@v4
|
|
153
|
+
with:
|
|
154
|
+
fetch-depth: 0
|
|
155
|
+
- uses: kamegoro/frameshot@main
|
|
156
|
+
with:
|
|
157
|
+
paths: "./src/components/*.tsx"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
69
161
|
|
|
70
162
|
## Performance
|
|
71
163
|
|
|
164
|
+
Typical measured times (varies by machine and project size):
|
|
165
|
+
|
|
72
166
|
| Scenario | Time |
|
|
73
167
|
|----------|------|
|
|
74
|
-
| Warm render | **~
|
|
75
|
-
|
|
|
76
|
-
|
|
|
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.
|
|
77
174
|
|
|
78
|
-
|
|
175
|
+
---
|
|
79
176
|
|
|
80
|
-
##
|
|
177
|
+
## vs. alternatives
|
|
81
178
|
|
|
82
|
-
|
|
83
|
-
|
|
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** |
|
|
188
|
+
|
|
189
|
+
---
|
|
84
190
|
|
|
85
191
|
## Development
|
|
86
192
|
|
|
87
193
|
```bash
|
|
88
|
-
git clone https://github.com/kamegoro/frameshot.git
|
|
89
|
-
|
|
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
|
|
90
198
|
```
|
|
91
199
|
|
|
92
|
-
|
|
200
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for architecture details.
|
|
201
|
+
|
|
202
|
+
---
|
|
93
203
|
|
|
94
|
-
MIT
|
|
204
|
+
MIT © [kamegoro](https://github.com/kamegoro)
|
package/action.yml
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
name: "frameshot — Visual Preview"
|
|
2
|
+
description: "Render changed components with full Vite dependency resolution and attach before/after screenshots to PRs."
|
|
3
|
+
branding:
|
|
4
|
+
icon: "camera"
|
|
5
|
+
color: "blue"
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
paths:
|
|
9
|
+
description: "Glob patterns for component files to render (newline or comma separated)"
|
|
10
|
+
required: true
|
|
11
|
+
width:
|
|
12
|
+
description: "Viewport width in px. Leave empty for auto-fit (recommended)."
|
|
13
|
+
required: false
|
|
14
|
+
default: ""
|
|
15
|
+
height:
|
|
16
|
+
description: "Viewport height in px. Leave empty for auto-fit (recommended)."
|
|
17
|
+
required: false
|
|
18
|
+
default: ""
|
|
19
|
+
comment:
|
|
20
|
+
description: "Post a PR comment with screenshots"
|
|
21
|
+
required: false
|
|
22
|
+
default: "true"
|
|
23
|
+
token:
|
|
24
|
+
description: "GitHub token for PR comments"
|
|
25
|
+
required: false
|
|
26
|
+
default: ${{ github.token }}
|
|
27
|
+
|
|
28
|
+
runs:
|
|
29
|
+
using: "composite"
|
|
30
|
+
steps:
|
|
31
|
+
- name: Setup Node.js
|
|
32
|
+
uses: actions/setup-node@v6
|
|
33
|
+
with:
|
|
34
|
+
node-version: "20"
|
|
35
|
+
|
|
36
|
+
- name: Install frameshot
|
|
37
|
+
shell: bash
|
|
38
|
+
run: npm install -g frameshot-mcp@latest
|
|
39
|
+
|
|
40
|
+
- name: Install Playwright Chromium
|
|
41
|
+
shell: bash
|
|
42
|
+
run: npx playwright install chromium
|
|
43
|
+
|
|
44
|
+
- name: Render changed components
|
|
45
|
+
id: render
|
|
46
|
+
shell: bash
|
|
47
|
+
env:
|
|
48
|
+
INPUT_PATHS: ${{ inputs.paths }}
|
|
49
|
+
INPUT_WIDTH: ${{ inputs.width }}
|
|
50
|
+
INPUT_HEIGHT: ${{ inputs.height }}
|
|
51
|
+
INPUT_BASE_REF: ${{ github.event.pull_request.base.sha }}
|
|
52
|
+
run: |
|
|
53
|
+
node "${{ github.action_path }}/scripts/render-changed.mjs"
|
|
54
|
+
|
|
55
|
+
- name: Comment on PR
|
|
56
|
+
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
57
|
+
uses: actions/github-script@v9
|
|
58
|
+
env:
|
|
59
|
+
SCREENSHOTS_DIR: ${{ github.workspace }}/.frameshot-screenshots
|
|
60
|
+
with:
|
|
61
|
+
github-token: ${{ inputs.token }}
|
|
62
|
+
script: |
|
|
63
|
+
const fs = require('fs');
|
|
64
|
+
const path = require('path');
|
|
65
|
+
const dir = process.env.SCREENSHOTS_DIR;
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(dir)) {
|
|
68
|
+
console.log('No screenshots generated');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
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');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let body = '## 📸 frameshot — Visual Preview\n\n';
|
|
85
|
+
|
|
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
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
body += '\n---\n*Generated by [frameshot](https://github.com/kamegoro/frameshot)*';
|
|
119
|
+
|
|
120
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
121
|
+
owner: context.repo.owner,
|
|
122
|
+
repo: context.repo.repo,
|
|
123
|
+
issue_number: context.issue.number,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const existing = comments.find(c =>
|
|
127
|
+
c.body?.includes('📸 frameshot — Visual Preview')
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (existing) {
|
|
131
|
+
await github.rest.issues.updateComment({
|
|
132
|
+
owner: context.repo.owner,
|
|
133
|
+
repo: context.repo.repo,
|
|
134
|
+
comment_id: existing.id,
|
|
135
|
+
body,
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
await github.rest.issues.createComment({
|
|
139
|
+
owner: context.repo.owner,
|
|
140
|
+
repo: context.repo.repo,
|
|
141
|
+
issue_number: context.issue.number,
|
|
142
|
+
body,
|
|
143
|
+
});
|
|
144
|
+
}
|