frameshot-mcp 0.2.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 +93 -128
- package/action.yml +130 -0
- package/dist/chunk-47YJG5HR.js +690 -0
- package/dist/index.js +1091 -214
- package/dist/renderer.d.ts +241 -0
- package/dist/renderer.js +28 -0
- package/package.json +28 -5
- package/scripts/render-changed.mjs +90 -0
- package/scripts/setup-labels.sh +48 -0
package/README.md
CHANGED
|
@@ -2,58 +2,34 @@
|
|
|
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
|
-
>
|
|
5
|
+
> **Give your AI agent eyes.** One MCP call, one screenshot — 120ms. Stop coding blind.
|
|
6
6
|
|
|
7
|
-
|
|
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>
|
|
8
10
|
|
|
9
11
|
## The Problem
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
1. Start the dev server
|
|
13
|
-
2. Log in
|
|
14
|
-
3. Navigate to the right page
|
|
15
|
-
4. Scroll to the component
|
|
16
|
-
5. Tell the agent what's wrong
|
|
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:
|
|
17
14
|
|
|
18
|
-
|
|
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
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
**frameshot closes this loop.** The agent calls one tool, gets a screenshot back in 120ms, and self-corrects.
|
|
21
20
|
|
|
22
|
-
### Claude Code
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
26
21
|
```
|
|
27
|
-
|
|
28
|
-
That's it. Now your AI agent can see UI.
|
|
29
|
-
|
|
30
|
-
### Cursor (`.cursor/mcp.json`)
|
|
31
|
-
|
|
32
|
-
```json
|
|
33
|
-
{
|
|
34
|
-
"mcpServers": {
|
|
35
|
-
"frameshot": {
|
|
36
|
-
"command": "npx",
|
|
37
|
-
"args": ["frameshot-mcp@latest"]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
22
|
+
AI writes code → frameshot renders → AI sees result → AI self-corrects → ships
|
|
41
23
|
```
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
## Install
|
|
44
26
|
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
"servers": {
|
|
48
|
-
"frameshot": {
|
|
49
|
-
"command": "npx",
|
|
50
|
-
"args": ["frameshot-mcp@latest"]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add frameshot -- npx frameshot-mcp@latest
|
|
54
29
|
```
|
|
55
30
|
|
|
56
|
-
|
|
31
|
+
<details>
|
|
32
|
+
<summary>Cursor / VS Code / Windsurf / Other MCP clients</summary>
|
|
57
33
|
|
|
58
34
|
```json
|
|
59
35
|
{
|
|
@@ -66,117 +42,106 @@ That's it. Now your AI agent can see UI.
|
|
|
66
42
|
}
|
|
67
43
|
```
|
|
68
44
|
|
|
69
|
-
>
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Render
|
|
45
|
+
</details>
|
|
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. |
|
|
69
|
+
|
|
70
|
+
### Example
|
|
76
71
|
|
|
77
72
|
```typescript
|
|
73
|
+
// AI renders its own output to verify
|
|
78
74
|
render_component({
|
|
79
|
-
code: `
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
framework: "react",
|
|
89
|
-
engines: ["chromium", "firefox", "webkit"]
|
|
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"
|
|
90
84
|
})
|
|
91
|
-
//
|
|
85
|
+
// → Screenshot returned in 118ms. AI can see it looks correct.
|
|
92
86
|
```
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|-----------|---------|-------------|
|
|
96
|
-
| `code` | required | Component source code |
|
|
97
|
-
| `framework` | `"react"` | `"react"` · `"vue"` · `"html"` |
|
|
98
|
-
| `engines` | `["chromium"]` | Browser engines to render in |
|
|
99
|
-
| `width` | `1280` | Viewport width |
|
|
100
|
-
| `height` | `800` | Viewport height |
|
|
101
|
-
| `fullPage` | `true` | Capture full scroll height |
|
|
102
|
-
|
|
103
|
-
### `screenshot_url`
|
|
104
|
-
|
|
105
|
-
Screenshot a running app (e.g. localhost) across browsers.
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
screenshot_url({
|
|
109
|
-
url: "http://localhost:3000/components/button",
|
|
110
|
-
engines: ["chromium", "firefox", "webkit"]
|
|
111
|
-
})
|
|
112
|
-
```
|
|
88
|
+
## Why frameshot?
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
| |
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
| Auth required? | Need full app context | **No** — isolated component render |
|
|
123
|
-
|
|
124
|
-
frameshot is not a Storybook replacement. Storybook is for documentation and design systems. frameshot is for **seeing what you just wrote, right now**.
|
|
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** |
|
|
125
98
|
|
|
126
99
|
## Performance
|
|
127
100
|
|
|
128
101
|
| Scenario | Time |
|
|
129
102
|
|----------|------|
|
|
130
|
-
| Warm render
|
|
131
|
-
| Cold start (first
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
103
|
+
| Warm render | **~120ms** |
|
|
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
|
|
144
127
|
```
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
```javascript
|
|
148
|
-
const App = {
|
|
149
|
-
setup() {
|
|
150
|
-
const count = ref(0)
|
|
151
|
-
return { count }
|
|
152
|
-
},
|
|
153
|
-
template: `<button @click="count++">{{ count }}</button>`
|
|
154
|
-
}
|
|
155
|
-
```
|
|
129
|
+
Screenshots are posted as a PR comment automatically.
|
|
156
130
|
|
|
157
|
-
|
|
158
|
-
```html
|
|
159
|
-
<div class="flex gap-4 p-8">
|
|
160
|
-
<button class="px-4 py-2 bg-blue-500 text-white rounded">Click me</button>
|
|
161
|
-
</div>
|
|
162
|
-
```
|
|
131
|
+
## Recipes
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| Variable | Description |
|
|
167
|
-
|----------|-------------|
|
|
168
|
-
| `FRAMESHOT_BROWSER_PATH` | Custom Chromium executable path |
|
|
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
|
|
169
135
|
|
|
170
136
|
## Development
|
|
171
137
|
|
|
172
138
|
```bash
|
|
173
|
-
git clone https://github.com/kamegoro/frameshot.git
|
|
174
|
-
|
|
175
|
-
npm install
|
|
176
|
-
npx playwright install chromium
|
|
177
|
-
npm run build
|
|
139
|
+
git clone https://github.com/kamegoro/frameshot.git && cd frameshot
|
|
140
|
+
npm install && npx playwright install chromium && npm run build && npm test
|
|
178
141
|
```
|
|
179
142
|
|
|
143
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
144
|
+
|
|
180
145
|
## License
|
|
181
146
|
|
|
182
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
|
+
}
|