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 CHANGED
@@ -2,58 +2,34 @@
2
2
 
3
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)
4
4
 
5
- > Instant cross-browser component preview for AI agents. One call, one screenshot — no dev server, no Storybook, no ceremony.
5
+ > **Give your AI agent eyes.** One MCP call, one screenshot — 120ms. Stop coding blind.
6
6
 
7
- **Works with:** Claude Code · Cursor · Codex · Windsurf · any MCP-compatible tool
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
- You're building UI with an AI agent. The agent writes a component but can't see it. You have to:
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
- **frameshot** skips all of that. The agent renders the component itself, sees the result, and fixes it.
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
- ## Quick Start
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
- ### VS Code Copilot (`.vscode/mcp.json`)
25
+ ## Install
44
26
 
45
- ```json
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
- ### Manual setup
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
- > **Note:** On first run, Playwright will download Chromium (~150MB). For additional engines: `npx playwright install firefox webkit`
70
-
71
- ## Tools
72
-
73
- ### `render_component`
74
-
75
- Render isolated component code → get screenshot back instantly.
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
- function App() {
81
- return (
82
- <div className="p-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl">
83
- <h1 className="text-4xl font-bold text-white">Hello World</h1>
84
- </div>
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
- // Returns 3 screenshots one per browser engine
85
+ // Screenshot returned in 118ms. AI can see it looks correct.
92
86
  ```
93
87
 
94
- | Parameter | Default | Description |
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
- ## Why Not Storybook?
115
-
116
- | | Storybook | frameshot |
117
- |---|-----------|-----------|
118
- | Setup | Write .stories.tsx, configure addons, run server | **Zero** pass code, get image |
119
- | Speed | Heavy dev server startup | **~120ms** warm render |
120
- | Cross-browser | Chromatic ($149+/mo) | **Free** Playwright built-in |
121
- | AI-native | Requires pre-written stories | **Works with any code snippet** |
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 (browser reused) | **~120ms** |
131
- | Cold start (first render) | ~4s |
132
- | Cross-browser (3 engines parallel) | ~300ms warm |
133
-
134
- The browser pool stays warm with Tailwind pre-cached. After cold start, renders are **sub-200ms**.
135
-
136
- ## Framework Support
137
-
138
- ### React (JSX + Tailwind)
139
- ```jsx
140
- function App() {
141
- const [count, setCount] = React.useState(0)
142
- return <button onClick={() => setCount(c => c+1)} className="btn">{count}</button>
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
- ### Vue 3 (Composition API + Tailwind)
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
- ### HTML (+ Tailwind)
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
- ## Environment Variables
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
- cd frameshot
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
+ }