frameshot-mcp 0.3.0 → 0.6.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 +85 -32
- package/action.yml +130 -0
- package/dist/chunk-47YJG5HR.js +690 -0
- package/dist/index.js +1058 -653
- package/dist/renderer.d.ts +241 -0
- package/dist/renderer.js +28 -0
- package/package.json +19 -4
- package/scripts/render-changed.mjs +90 -0
- package/scripts/setup-labels.sh +48 -0
package/README.md
CHANGED
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/frameshot-mcp) [](https://www.npmjs.com/package/frameshot-mcp) [](https://github.com/kamegoro/frameshot) [](https://github.com/kamegoro/frameshot/actions/workflows/ci.yml) [](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
> Give your AI agent eyes
|
|
5
|
+
> **Give your AI agent eyes.** One MCP call, one screenshot — 120ms. Stop coding blind.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/demo.svg" alt="frameshot demo — AI writes code, frameshot renders it, AI sees and self-corrects" width="720" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
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.
|
|
9
20
|
|
|
10
21
|
```
|
|
11
|
-
AI writes code →
|
|
22
|
+
AI writes code → frameshot renders → AI sees result → AI self-corrects → ships
|
|
12
23
|
```
|
|
13
24
|
|
|
14
25
|
## Install
|
|
@@ -18,7 +29,7 @@ claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
|
18
29
|
```
|
|
19
30
|
|
|
20
31
|
<details>
|
|
21
|
-
<summary>Cursor / VS Code / Other</summary>
|
|
32
|
+
<summary>Cursor / VS Code / Windsurf / Other MCP clients</summary>
|
|
22
33
|
|
|
23
34
|
```json
|
|
24
35
|
{
|
|
@@ -33,62 +44,104 @@ claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
|
33
44
|
|
|
34
45
|
</details>
|
|
35
46
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
| Tool |
|
|
39
|
-
|
|
40
|
-
| `render_component` | Render React/Vue/Svelte/HTML → screenshot. Tailwind built-in. |
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
+
## What it does
|
|
48
|
+
|
|
49
|
+
| Tool | Purpose |
|
|
50
|
+
|------|---------|
|
|
51
|
+
| `render_component` | Render React/Vue/Svelte/HTML code → screenshot. Tailwind built-in. |
|
|
52
|
+
| `render_file` | Render a file from disk (auto-detects framework from extension). |
|
|
53
|
+
| `screenshot_url` | Screenshot any URL (localhost:3000, staging, prod). |
|
|
54
|
+
| `render_responsive` | Mobile + tablet + desktop in one call. |
|
|
55
|
+
| `render_variants` | Multiple prop/state variants at once. |
|
|
56
|
+
| `render_theme` | Light + dark mode side-by-side. |
|
|
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. |
|
|
47
69
|
|
|
48
70
|
### Example
|
|
49
71
|
|
|
50
72
|
```typescript
|
|
73
|
+
// AI renders its own output to verify
|
|
51
74
|
render_component({
|
|
52
75
|
code: `function App() {
|
|
53
|
-
return
|
|
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
|
+
)
|
|
54
82
|
}`,
|
|
55
|
-
framework: "react"
|
|
56
|
-
darkMode: true,
|
|
57
|
-
engines: ["chromium", "firefox", "webkit"]
|
|
83
|
+
framework: "react"
|
|
58
84
|
})
|
|
85
|
+
// → Screenshot returned in 118ms. AI can see it looks correct.
|
|
59
86
|
```
|
|
60
87
|
|
|
61
88
|
## Why frameshot?
|
|
62
89
|
|
|
63
|
-
| | Storybook | frameshot |
|
|
64
|
-
|
|
65
|
-
| Setup |
|
|
66
|
-
| Speed | Dev server startup | **~120ms** |
|
|
67
|
-
|
|
|
68
|
-
| AI-native | Needs pre-written stories | **Any code snippet** |
|
|
90
|
+
| | Storybook | Chromatic/Percy | Browser MCP | **frameshot** |
|
|
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** |
|
|
69
98
|
|
|
70
99
|
## Performance
|
|
71
100
|
|
|
72
101
|
| Scenario | Time |
|
|
73
102
|
|----------|------|
|
|
74
103
|
| Warm render | **~120ms** |
|
|
75
|
-
| Cold start | ~4s |
|
|
76
|
-
| 3
|
|
104
|
+
| Cold start (first run) | ~4s |
|
|
105
|
+
| 3 browsers in parallel | ~300ms |
|
|
106
|
+
| Responsive (3 viewports) | ~350ms |
|
|
107
|
+
|
|
108
|
+
Browser pool stays warm between calls. Tailwind CSS is pre-cached. Sub-200ms after first run.
|
|
109
|
+
|
|
110
|
+
## GitHub Action
|
|
111
|
+
|
|
112
|
+
Add visual previews to every PR — free Chromatic alternative:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
# .github/workflows/visual-preview.yml
|
|
116
|
+
name: Visual Preview
|
|
117
|
+
on: [pull_request]
|
|
118
|
+
jobs:
|
|
119
|
+
preview:
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v4
|
|
123
|
+
- uses: kamegoro/frameshot@main
|
|
124
|
+
with:
|
|
125
|
+
paths: "./src/components/*.tsx"
|
|
126
|
+
framework: react
|
|
127
|
+
```
|
|
77
128
|
|
|
78
|
-
|
|
129
|
+
Screenshots are posted as a PR comment automatically.
|
|
79
130
|
|
|
80
131
|
## Recipes
|
|
81
132
|
|
|
82
|
-
- [Claude Code skill
|
|
83
|
-
- [Cursor rules](examples/cursor-rules.md) —
|
|
133
|
+
- [Claude Code skill](examples/claude-code-skill.md) — Auto-preview on `/project:preview`
|
|
134
|
+
- [Cursor rules](examples/cursor-rules.md) — Verify UI on every edit
|
|
84
135
|
|
|
85
136
|
## Development
|
|
86
137
|
|
|
87
138
|
```bash
|
|
88
139
|
git clone https://github.com/kamegoro/frameshot.git && cd frameshot
|
|
89
|
-
npm install && npx playwright install chromium && npm run build
|
|
140
|
+
npm install && npx playwright install chromium && npm run build && npm test
|
|
90
141
|
```
|
|
91
142
|
|
|
143
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
144
|
+
|
|
92
145
|
## License
|
|
93
146
|
|
|
94
147
|
MIT
|
package/action.yml
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
name: "frameshot — Visual Preview"
|
|
2
|
+
description: "Render components and attach before/after screenshots to PRs. Free visual regression for every pull request."
|
|
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
|
+
framework:
|
|
12
|
+
description: "Framework: react, vue, svelte, or html"
|
|
13
|
+
required: false
|
|
14
|
+
default: "react"
|
|
15
|
+
width:
|
|
16
|
+
description: "Viewport width (px)"
|
|
17
|
+
required: false
|
|
18
|
+
default: "1280"
|
|
19
|
+
height:
|
|
20
|
+
description: "Viewport height (px)"
|
|
21
|
+
required: false
|
|
22
|
+
default: "800"
|
|
23
|
+
dark-mode:
|
|
24
|
+
description: "Render with dark mode"
|
|
25
|
+
required: false
|
|
26
|
+
default: "false"
|
|
27
|
+
tailwind-version:
|
|
28
|
+
description: "Tailwind CSS version (3 or 4)"
|
|
29
|
+
required: false
|
|
30
|
+
default: "3"
|
|
31
|
+
comment:
|
|
32
|
+
description: "Post a PR comment with screenshots"
|
|
33
|
+
required: false
|
|
34
|
+
default: "true"
|
|
35
|
+
token:
|
|
36
|
+
description: "GitHub token for PR comments"
|
|
37
|
+
required: false
|
|
38
|
+
default: ${{ github.token }}
|
|
39
|
+
|
|
40
|
+
runs:
|
|
41
|
+
using: "composite"
|
|
42
|
+
steps:
|
|
43
|
+
- name: Setup Node.js
|
|
44
|
+
uses: actions/setup-node@v4
|
|
45
|
+
with:
|
|
46
|
+
node-version: "20"
|
|
47
|
+
|
|
48
|
+
- name: Install frameshot
|
|
49
|
+
shell: bash
|
|
50
|
+
run: npm install -g frameshot-mcp@latest
|
|
51
|
+
|
|
52
|
+
- name: Install Playwright Chromium
|
|
53
|
+
shell: bash
|
|
54
|
+
run: npx playwright install chromium
|
|
55
|
+
|
|
56
|
+
- name: Render changed components
|
|
57
|
+
id: render
|
|
58
|
+
shell: bash
|
|
59
|
+
env:
|
|
60
|
+
INPUT_PATHS: ${{ inputs.paths }}
|
|
61
|
+
INPUT_FRAMEWORK: ${{ inputs.framework }}
|
|
62
|
+
INPUT_WIDTH: ${{ inputs.width }}
|
|
63
|
+
INPUT_HEIGHT: ${{ inputs.height }}
|
|
64
|
+
INPUT_DARK_MODE: ${{ inputs.dark-mode }}
|
|
65
|
+
INPUT_TAILWIND_VERSION: ${{ inputs.tailwind-version }}
|
|
66
|
+
run: |
|
|
67
|
+
node "${{ github.action_path }}/scripts/render-changed.mjs"
|
|
68
|
+
|
|
69
|
+
- name: Comment on PR
|
|
70
|
+
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
71
|
+
uses: actions/github-script@v9
|
|
72
|
+
env:
|
|
73
|
+
SCREENSHOTS_DIR: ${{ github.workspace }}/.frameshot-screenshots
|
|
74
|
+
with:
|
|
75
|
+
github-token: ${{ inputs.token }}
|
|
76
|
+
script: |
|
|
77
|
+
const fs = require('fs');
|
|
78
|
+
const path = require('path');
|
|
79
|
+
const dir = process.env.SCREENSHOTS_DIR;
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(dir)) {
|
|
82
|
+
console.log('No screenshots generated');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.png'));
|
|
87
|
+
if (files.length === 0) {
|
|
88
|
+
console.log('No screenshot files found');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Upload screenshots as artifacts and build comment
|
|
93
|
+
let body = '## 📸 frameshot — Visual Preview\n\n';
|
|
94
|
+
body += '| Component | Screenshot |\n|---|---|\n';
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const name = file.replace('.png', '').replace(/_/g, '/');
|
|
98
|
+
const imgPath = path.join(dir, file);
|
|
99
|
+
const imgData = fs.readFileSync(imgPath, 'base64');
|
|
100
|
+
body += `| \`${name}\` | <img src="data:image/png;base64,${imgData}" width="400" /> |\n`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
body += '\n---\n*Generated by [frameshot](https://github.com/kamegoro/frameshot)*';
|
|
104
|
+
|
|
105
|
+
// Find existing comment
|
|
106
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
107
|
+
owner: context.repo.owner,
|
|
108
|
+
repo: context.repo.repo,
|
|
109
|
+
issue_number: context.issue.number,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const existing = comments.find(c =>
|
|
113
|
+
c.body?.includes('📸 frameshot — Visual Preview')
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (existing) {
|
|
117
|
+
await github.rest.issues.updateComment({
|
|
118
|
+
owner: context.repo.owner,
|
|
119
|
+
repo: context.repo.repo,
|
|
120
|
+
comment_id: existing.id,
|
|
121
|
+
body,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
await github.rest.issues.createComment({
|
|
125
|
+
owner: context.repo.owner,
|
|
126
|
+
repo: context.repo.repo,
|
|
127
|
+
issue_number: context.issue.number,
|
|
128
|
+
body,
|
|
129
|
+
});
|
|
130
|
+
}
|