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 CHANGED
@@ -1,24 +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. Render UI components and get screenshots back — in 120ms.
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
- <!-- TODO: demo GIF here -->
8
- <!-- ![demo](docs/demo.gif) -->
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 / VS Code / Other</summary>
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
+ &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>
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
- | `render_component` | Render React/Vue/Svelte/HTML screenshot. Tailwind built-in. |
41
- | `render_responsive` | Render at mobile + tablet + desktop in one call. |
42
- | `render_variants` | Render multiple prop/state variants in one call. |
43
- | `screenshot_url` | Screenshot any URL (e.g. localhost:3000). |
44
- | `audit_a11y` | Run axe-core accessibility audit on your component. |
45
- | `diff_component` | Visual regression: compare before/after code, get pixel diff. |
46
- | `capture_animation` | Capture CSS animation frames over time (multi-screenshot). |
47
-
48
- ### Example
49
-
50
- ```typescript
51
- render_component({
52
- code: `function App() {
53
- return <div className="p-8 bg-blue-500 text-white rounded-xl">Hello</div>
54
- }`,
55
- framework: "react",
56
- darkMode: true,
57
- engines: ["chromium", "firefox", "webkit"]
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
- ## Why frameshot?
136
+ Images display inline in iTerm2, Kitty, and Sixel terminals. Saved to `.frameshot/` by default.
137
+
138
+ ---
62
139
 
63
- | | Storybook | frameshot |
64
- |---|-----------|-----------|
65
- | Setup | .stories.tsx + addons + server | **Zero** |
66
- | Speed | Dev server startup | **~120ms** |
67
- | Cross-browser | Chromatic ($149+/mo) | **Free** |
68
- | AI-native | Needs pre-written stories | **Any code snippet** |
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 | **~120ms** |
75
- | Cold start | ~4s |
76
- | 3 engines parallel | ~300ms |
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
- Browser pool stays warm. Tailwind pre-cached. Sub-200ms after first run.
175
+ ---
79
176
 
80
- ## Recipes
177
+ ## vs. alternatives
81
178
 
82
- - [Claude Code skill setup](examples/claude-code-skill.md) Auto-preview components with `/project:preview`
83
- - [Cursor rules](examples/cursor-rules.md) — Auto-verify UI on every edit
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 && cd frameshot
89
- npm install && npx playwright install chromium && npm run build
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
- ## License
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
+ }