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 CHANGED
@@ -1,35 +1,21 @@
1
1
  # frameshot
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/frameshot-mcp)](https://www.npmjs.com/package/frameshot-mcp) [![npm downloads](https://img.shields.io/npm/dm/frameshot-mcp)](https://www.npmjs.com/package/frameshot-mcp) [![GitHub stars](https://img.shields.io/github/stars/kamegoro/frameshot)](https://github.com/kamegoro/frameshot) [![CI](https://github.com/kamegoro/frameshot/actions/workflows/ci.yml/badge.svg)](https://github.com/kamegoro/frameshot/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ [![npm version](https://img.shields.io/npm/v/frameshot-mcp)](https://www.npmjs.com/package/frameshot-mcp) [![npm downloads](https://img.shields.io/npm/dm/frameshot-mcp)](https://www.npmjs.com/package/frameshot-mcp) [![GitHub stars](https://img.shields.io/github/stars/kamegoro/frameshot)](https://github.com/kamegoro/frameshot) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- > **Give your AI agent eyes.** One MCP call, one screenshot120ms. Stop coding blind.
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.svg" alt="frameshot demo — AI writes code, frameshot renders it, AI sees and self-corrects" width="720" />
8
+ <img src="docs/demo.gif" alt="frameshot demo" width="720" />
9
9
  </p>
10
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.
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 / VS Code / Windsurf / Other MCP clients</summary>
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
- ## 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
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
- ## Why frameshot?
45
+ Claude will automatically call `render_file`, `diff_component`, or `render_catalog` as needed. No manual tool calls required.
89
46
 
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** |
47
+ ---
98
48
 
99
- ## Performance
49
+ ## Render
100
50
 
101
- | Scenario | Time |
102
- |----------|------|
103
- | Warm render | **~120ms** |
104
- | Cold start (first run) | ~4s |
105
- | 3 browsers in parallel | ~300ms |
106
- | Responsive (3 viewports) | ~350ms |
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
+ &nbsp;
72
+ <img src="docs/diff-after.png" alt="After" width="210" />
73
+ &nbsp;
74
+ <img src="docs/diff-result.png" alt="Diff" width="210" />
75
+ </p>
107
76
 
108
- Browser pool stays warm between calls. Tailwind CSS is pre-cached. Sub-200ms after first run.
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
- Add visual previews to every PR free Chromatic alternative:
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
- Screenshots are posted as a PR comment automatically.
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
- ## Recipes
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
- - [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
189
+ ---
135
190
 
136
191
  ## Development
137
192
 
138
193
  ```bash
139
- git clone https://github.com/kamegoro/frameshot.git && cd frameshot
140
- npm install && npx playwright install chromium && npm run build && npm test
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
- ## License
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. Free visual regression for every pull request."
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 (px)"
12
+ description: "Viewport width in px. Leave empty for auto-fit (recommended)."
17
13
  required: false
18
- default: "1280"
14
+ default: ""
19
15
  height:
20
- description: "Viewport height (px)"
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: "false"
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@v4
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
- INPUT_DARK_MODE: ${{ inputs.dark-mode }}
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 files = fs.readdirSync(dir).filter(f => f.endsWith('.png'));
87
- if (files.length === 0) {
88
- console.log('No screenshot files found');
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
- 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`;
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,