design-clone 1.1.1 → 2.1.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.
Files changed (70) hide show
  1. package/README.md +42 -20
  2. package/SKILL.md +74 -0
  3. package/bin/commands/clone-site.js +75 -10
  4. package/bin/commands/init.js +33 -1
  5. package/bin/commands/verify.js +5 -3
  6. package/bin/utils/validate.js +24 -8
  7. package/docs/cli-reference.md +224 -2
  8. package/docs/codebase-summary.md +309 -0
  9. package/docs/design-clone-architecture.md +290 -45
  10. package/docs/pixel-perfect.md +35 -4
  11. package/docs/project-roadmap.md +382 -0
  12. package/docs/troubleshooting.md +5 -4
  13. package/package.json +12 -6
  14. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  15. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  16. package/src/ai/analyze-structure.py +73 -3
  17. package/src/ai/extract-design-tokens.py +356 -13
  18. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  19. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  20. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  21. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  22. package/src/ai/prompts/design_tokens.py +133 -0
  23. package/src/ai/prompts/structure_analysis.py +329 -10
  24. package/src/ai/prompts/ux_audit.py +198 -0
  25. package/src/ai/ux-audit.js +596 -0
  26. package/src/core/animation-extractor.js +526 -0
  27. package/src/core/app-state-snapshot.js +511 -0
  28. package/src/core/content-counter.js +342 -0
  29. package/src/core/cookie-handler.js +1 -1
  30. package/src/core/css-extractor.js +4 -4
  31. package/src/core/dimension-extractor.js +93 -21
  32. package/src/core/dimension-output.js +103 -6
  33. package/src/core/discover-pages.js +242 -14
  34. package/src/core/dom-tree-analyzer.js +298 -0
  35. package/src/core/extract-assets.js +1 -1
  36. package/src/core/framework-detector.js +538 -0
  37. package/src/core/html-extractor.js +45 -4
  38. package/src/core/lazy-loader.js +7 -7
  39. package/src/core/multi-page-screenshot.js +9 -6
  40. package/src/core/page-readiness.js +8 -8
  41. package/src/core/screenshot.js +311 -7
  42. package/src/core/section-cropper.js +209 -0
  43. package/src/core/section-detector.js +386 -0
  44. package/src/core/semantic-enhancer.js +492 -0
  45. package/src/core/state-capture.js +598 -0
  46. package/src/core/tests/test-section-cropper.js +177 -0
  47. package/src/core/tests/test-section-detector.js +55 -0
  48. package/src/core/video-capture.js +546 -0
  49. package/src/route-discoverers/angular-discoverer.js +157 -0
  50. package/src/route-discoverers/astro-discoverer.js +123 -0
  51. package/src/route-discoverers/base-discoverer.js +242 -0
  52. package/src/route-discoverers/index.js +106 -0
  53. package/src/route-discoverers/next-discoverer.js +130 -0
  54. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  55. package/src/route-discoverers/react-discoverer.js +139 -0
  56. package/src/route-discoverers/svelte-discoverer.js +109 -0
  57. package/src/route-discoverers/universal-discoverer.js +227 -0
  58. package/src/route-discoverers/vue-discoverer.js +118 -0
  59. package/src/utils/__init__.py +1 -1
  60. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  61. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  62. package/src/utils/browser.js +11 -37
  63. package/src/utils/playwright.js +213 -0
  64. package/src/verification/generate-audit-report.js +398 -0
  65. package/src/verification/verify-footer.js +493 -0
  66. package/src/verification/verify-header.js +486 -0
  67. package/src/verification/verify-layout.js +2 -2
  68. package/src/verification/verify-menu.js +4 -20
  69. package/src/verification/verify-slider.js +533 -0
  70. package/src/utils/puppeteer.js +0 -281
package/README.md CHANGED
@@ -1,15 +1,21 @@
1
- # Design Clone Skill for Claude Code
2
-
3
- Clone website designs with multi-viewport screenshots, HTML/CSS extraction, and Gemini AI analysis.
4
-
5
- [![npm](https://img.shields.io/npm/v/design-clone)](https://www.npmjs.com/package/design-clone)
6
- [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
1
+ <div align="center">
2
+ <img src="logo.svg" alt="design-clone" width="120" height="120">
3
+ <h1>Design Clone</h1>
4
+ <p><strong>Clone website designs with multi-viewport screenshots, HTML/CSS extraction, and Gemini AI analysis.</strong></p>
5
+ <p>
6
+ <a href="https://www.npmjs.com/package/design-clone"><img src="https://img.shields.io/npm/v/design-clone" alt="npm"></a>
7
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
8
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node"></a>
9
+ </p>
10
+ </div>
11
+
12
+ ---
8
13
 
9
14
  ## Features
10
15
 
11
16
  - **Multi-viewport screenshots**: Desktop (1920px), Tablet (768px), Mobile (375px)
12
17
  - **HTML/CSS extraction**: Clean source files with unused CSS removal
18
+ - **Hover state capture**: Screenshots and CSS for interactive element states (phase 2)
13
19
  - **AI structure analysis**: Gemini Vision analyzes page layout (optional)
14
20
  - **Asset extraction**: Downloads images, fonts, icons
15
21
  - **Menu verification**: Tests responsive navigation functionality
@@ -75,14 +81,21 @@ Full pixel-perfect clone:
75
81
 
76
82
  ```
77
83
  cloned-design/
78
- ├── desktop.png # 1920x1080 screenshot
79
- ├── tablet.png # 768x1024 screenshot
80
- ├── mobile.png # 375x812 screenshot
81
- ├── source.html # Cleaned HTML
82
- ├── source.css # Filtered CSS
83
- ├── source-raw.css # Original CSS (unfiltered)
84
- ├── structure.md # AI analysis (if GEMINI_API_KEY set)
85
- ├── tokens.json # Extracted design tokens
84
+ ├── desktop.png # 1920x1080 screenshot
85
+ ├── tablet.png # 768x1024 screenshot
86
+ ├── mobile.png # 375x812 screenshot
87
+ ├── source.html # Cleaned HTML
88
+ ├── source.css # Filtered CSS
89
+ ├── source-raw.css # Original CSS (unfiltered)
90
+ ├── animations.css # Extracted @keyframes definitions
91
+ ├── animation-tokens.json # Animation metadata (keyframes, transitions, timings)
92
+ ├── hover.css # Generated :hover CSS rules (with --capture-hover)
93
+ ├── structure.md # AI analysis (if GEMINI_API_KEY set)
94
+ ├── tokens.json # Extracted design tokens
95
+ ├── hover-states/ # Hover state captures (with --capture-hover)
96
+ │ ├── hover-N-normal.png # Element before hover
97
+ │ ├── hover-N-hover.png # Element during hover
98
+ │ └── hover-diff.json # Captured element summary
86
99
  └── assets/
87
100
  ├── images/
88
101
  ├── fonts/
@@ -104,7 +117,7 @@ Get your API key at: https://aistudio.google.com/apikey
104
117
 
105
118
  - Node.js 18+
106
119
  - Python 3.9+ (for AI analysis)
107
- - Chrome or Chromium (auto-detected)
120
+ - Playwright (auto-installed with browsers)
108
121
 
109
122
  ## CLI Commands
110
123
 
@@ -137,14 +150,17 @@ pip install google-genai
137
150
  pip3 install -r requirements.txt
138
151
  ```
139
152
 
140
- ### Puppeteer issues
153
+ ### Playwright issues
141
154
 
142
155
  ```bash
143
- # Install Puppeteer if not present
144
- npm install puppeteer
156
+ # Install Playwright if not present
157
+ npm install playwright
158
+
159
+ # Install browsers
160
+ npx playwright install chromium
145
161
 
146
162
  # For Docker/CI environments
147
- export PUPPETEER_NO_SANDBOX=1
163
+ export PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers
148
164
  ```
149
165
 
150
166
  See full troubleshooting guide: [docs/troubleshooting.md](docs/troubleshooting.md)
@@ -164,3 +180,9 @@ MIT - See [LICENSE](LICENSE)
164
180
  ## Credits
165
181
 
166
182
  Built for use with [Claude Code](https://claude.ai/code) by Anthropic.
183
+
184
+ ---
185
+
186
+ <div align="center">
187
+ <sub>Made with ❤️ for the Claude Code community</sub>
188
+ </div>
package/SKILL.md CHANGED
@@ -13,6 +13,7 @@ Clone website designs with multi-viewport screenshots, HTML/CSS extraction, CSS
13
13
  - **Direct Unsplash Images** - Real images without API key needed
14
14
  - **Japanese Design Principles** - Ma, Kanso, Shibui, Seijaku for elegant designs
15
15
  - **Multi-viewport Screenshots** - Desktop, tablet, mobile captures
16
+ - **Hover State Capture** - Interactive element screenshots and :hover CSS generation
16
17
  - **Gemini Vision Analysis** - AI-powered design token extraction
17
18
  - **ui-ux-pro-max Quality Check** - Accessibility, hover states, contrast validation
18
19
 
@@ -164,6 +165,7 @@ node src/core/screenshot.js \
164
165
  --url "URL" \
165
166
  --output ./output \
166
167
  --extract-html --extract-css \
168
+ --capture-hover true \
167
169
  --full-page
168
170
 
169
171
  # Step 2: Filter CSS
@@ -199,6 +201,8 @@ python3 $HOME/.claude/skills/ui-ux-pro-max/scripts/search.py "animation hover" -
199
201
  python3 $HOME/.claude/skills/ui-ux-pro-max/scripts/search.py "z-index" --domain ux
200
202
  ```
201
203
 
204
+ **Note:** Step 1 includes `--capture-hover true` to capture interactive element states and generate `:hover` CSS rules. Outputs include `hover-states/` directory and `hover.css`.
205
+
202
206
  ## Quality Checklist (ui-ux-pro-max)
203
207
 
204
208
  After generating HTML/CSS, verify these items using `ui-ux-pro-max` skill:
@@ -256,6 +260,73 @@ After generating HTML/CSS, verify these items using `ui-ux-pro-max` skill:
256
260
  | Shibui (渋い) | Subtle elegance | Soft shadows, gentle transitions |
257
261
  | Seijaku (静寂) | Tranquility | Calm colors, visual harmony |
258
262
 
263
+ ## Animation & Interaction Capture (v1.2+)
264
+
265
+ ### CSS Animations
266
+
267
+ Automatically extracts @keyframes and transition properties when using `--extract-css`:
268
+
269
+ ```bash
270
+ node src/core/screenshot.js --url https://example.com --output ./out --extract-css true
271
+ ```
272
+
273
+ **Output:**
274
+ - `animations.css` - All @keyframes definitions with frame data
275
+ - `animation-tokens.json` - Detailed animation metadata (durations, timing functions)
276
+
277
+ ### Hover State Capture
278
+
279
+ Capture interactive element hover states:
280
+
281
+ ```bash
282
+ node src/core/screenshot.js --url https://example.com --output ./out --capture-hover
283
+ ```
284
+
285
+ **Output:**
286
+ - `hover-states/` - Before/after screenshots for each interactive element
287
+ - `hover.css` - Generated :hover rules from computed style differences
288
+ - `hover-diff.json` - Style diff data
289
+
290
+ **Detection Methods:**
291
+ 1. CSS-based: Parses :hover selectors from extracted CSS
292
+ 2. DOM-based: Queries buttons, links, and interactive elements
293
+
294
+ ### Video Recording
295
+
296
+ Record scroll preview video (opt-in due to 3-5x capture time increase):
297
+
298
+ ```bash
299
+ # WebM (native, no extra deps)
300
+ node src/core/screenshot.js --url https://example.com --output ./out --video
301
+
302
+ # MP4 (requires ffmpeg)
303
+ node src/core/screenshot.js --url https://example.com --output ./out --video --video-format mp4
304
+
305
+ # GIF (requires ffmpeg)
306
+ node src/core/screenshot.js --url https://example.com --output ./out --video --video-format gif
307
+
308
+ # Custom duration (default: 12000ms)
309
+ node src/core/screenshot.js --url https://example.com --output ./out --video --video-duration 8000
310
+ ```
311
+
312
+ **Output:**
313
+ - `preview.webm` (default) or `preview.mp4` / `preview.gif`
314
+
315
+ **ffmpeg Setup (for MP4/GIF):**
316
+ ```bash
317
+ npm install fluent-ffmpeg @ffmpeg-installer/ffmpeg
318
+ ```
319
+
320
+ ### Feature Flags Reference
321
+
322
+ | Flag | Default | Description |
323
+ |------|---------|-------------|
324
+ | `--extract-animations` | true (with --extract-css) | Extract @keyframes and transitions |
325
+ | `--capture-hover` | false | Capture hover state screenshots |
326
+ | `--video` | false | Record scroll preview video |
327
+ | `--video-format` | webm | Video format: webm, mp4, gif |
328
+ | `--video-duration` | 12000 | Video duration in ms |
329
+
259
330
  ## Environment Variables
260
331
 
261
332
  Create `.env` file (see `.env.example`):
@@ -270,6 +341,9 @@ GEMINI_API_KEY=your-key # Optional: enables AI structure analysis
270
341
  |--------|----------|---------|
271
342
  | screenshot.js | src/core/ | Capture screenshots + extract HTML/CSS |
272
343
  | filter-css.js | src/core/ | Filter unused CSS rules |
344
+ | animation-extractor.js | src/core/ | Extract @keyframes and transitions from CSS |
345
+ | state-capture.js | src/core/ | Capture hover states for interactive elements |
346
+ | video-capture.js | src/core/ | Record scroll preview video with optional ffmpeg conversion |
273
347
  | extract-assets.js | src/core/ | Download images, fonts, icons |
274
348
  | discover-pages.js | src/core/ | Discover navigation links |
275
349
  | multi-page-screenshot.js | src/core/ | Capture multiple pages |
@@ -12,6 +12,7 @@
12
12
  * --viewports <list> Viewport list (default: desktop,tablet,mobile)
13
13
  * --yes Skip confirmation prompt
14
14
  * --output <dir> Custom output directory
15
+ * --ux-audit Run UX audit using Gemini Vision (requires GEMINI_API_KEY)
15
16
  */
16
17
 
17
18
  import fs from 'fs/promises';
@@ -23,6 +24,7 @@ import { captureMultiplePages } from '../../src/core/multi-page-screenshot.js';
23
24
  import { mergeCssFiles } from '../../src/core/merge-css.js';
24
25
  import { rewriteLinks, createPageManifest, rewriteAllLinks } from '../../src/core/rewrite-links.js';
25
26
  import { extractDesignTokens } from '../../src/core/design-tokens.js';
27
+ import { runUXAudit } from '../../src/ai/ux-audit.js';
26
28
 
27
29
  /**
28
30
  * Generate output directory name
@@ -53,7 +55,8 @@ export function parseArgs(args) {
53
55
  viewports: ['desktop', 'tablet', 'mobile'],
54
56
  skipConfirm: false,
55
57
  output: null,
56
- ai: false
58
+ ai: false,
59
+ uxAudit: false
57
60
  };
58
61
 
59
62
  for (let i = 0; i < args.length; i++) {
@@ -71,6 +74,8 @@ export function parseArgs(args) {
71
74
  options.output = args[++i];
72
75
  } else if (arg === '--ai') {
73
76
  options.ai = true;
77
+ } else if (arg === '--ux-audit') {
78
+ options.uxAudit = true;
74
79
  } else if (!arg.startsWith('--') && !options.url) {
75
80
  options.url = arg;
76
81
  }
@@ -93,7 +98,8 @@ export async function cloneSite(url, options = {}) {
93
98
  viewports = ['desktop', 'tablet', 'mobile'],
94
99
  skipConfirm = false,
95
100
  output,
96
- ai = false
101
+ ai = false,
102
+ uxAudit = false
97
103
  } = options;
98
104
 
99
105
  // Validate URL
@@ -111,7 +117,7 @@ export async function cloneSite(url, options = {}) {
111
117
  console.error(`[clone-site] Output: ${outputDir}`);
112
118
 
113
119
  // Step 1: Discover or use manual pages
114
- console.error('\n[1/6] Discovering pages...');
120
+ console.error('\n[1/7] Discovering pages...');
115
121
 
116
122
  let pageList;
117
123
  if (manualPages && manualPages.length > 0) {
@@ -141,7 +147,7 @@ export async function cloneSite(url, options = {}) {
141
147
  }
142
148
 
143
149
  // Step 2: Capture all pages
144
- console.error('\n[2/6] Capturing pages...');
150
+ console.error('\n[2/7] Capturing pages...');
145
151
 
146
152
  const captureResult = await captureMultiplePages(pageList.pages, {
147
153
  outputDir,
@@ -159,7 +165,7 @@ export async function cloneSite(url, options = {}) {
159
165
  console.error(` Screenshots: ${captureResult.stats.totalScreenshots}`);
160
166
 
161
167
  // Step 3: Merge CSS files (prefer filtered CSS)
162
- console.error('\n[3/6] Merging CSS...');
168
+ console.error('\n[3/7] Merging CSS...');
163
169
 
164
170
  const mergedCssPath = path.join(outputDir, 'styles.css');
165
171
  let mergeResult = { success: false };
@@ -184,7 +190,7 @@ export async function cloneSite(url, options = {}) {
184
190
  }
185
191
 
186
192
  // Step 4: Extract design tokens (if --ai flag)
187
- console.error('\n[4/6] Extracting design tokens...');
193
+ console.error('\n[4/7] Extracting design tokens...');
188
194
 
189
195
  let hasTokens = false;
190
196
  if (ai) {
@@ -207,8 +213,64 @@ export async function cloneSite(url, options = {}) {
207
213
  console.error(' Skipped (use --ai flag to enable)');
208
214
  }
209
215
 
210
- // Step 5: Rewrite links
211
- console.error('\n[5/6] Rewriting links...');
216
+ // Step 5: UX Audit (if --ux-audit flag)
217
+ console.error('\n[5/7] Running UX audit...');
218
+
219
+ let uxAuditResult = null;
220
+ if (uxAudit) {
221
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
222
+ // Find homepage screenshots for audit
223
+ const screenshotDir = path.join(outputDir, 'screenshots');
224
+ const screenshotPaths = {};
225
+
226
+ for (const viewport of viewports) {
227
+ const screenshotPath = path.join(screenshotDir, `index-${viewport}.png`);
228
+ try {
229
+ await fs.access(screenshotPath);
230
+ screenshotPaths[viewport] = screenshotPath;
231
+ } catch {
232
+ // Try alternative naming
233
+ const altPath = path.join(screenshotDir, `${viewport}.png`);
234
+ try {
235
+ await fs.access(altPath);
236
+ screenshotPaths[viewport] = altPath;
237
+ } catch {
238
+ // Skip this viewport
239
+ }
240
+ }
241
+ }
242
+
243
+ if (Object.keys(screenshotPaths).length > 0) {
244
+ const analysisDir = path.join(outputDir, 'analysis');
245
+ await fs.mkdir(analysisDir, { recursive: true });
246
+
247
+ uxAuditResult = await runUXAudit(screenshotPaths, {
248
+ output: analysisDir,
249
+ verbose: true,
250
+ url
251
+ });
252
+
253
+ if (uxAuditResult.success) {
254
+ console.error(` UX Score: ${uxAuditResult.summary.uxScore}%`);
255
+ console.error(` Accessibility: ${uxAuditResult.summary.accessibilityScore}%`);
256
+ console.error(` Issues: ${uxAuditResult.summary.issueCount} (${uxAuditResult.summary.criticalCount} critical)`);
257
+ console.error(` Report: analysis/ux-audit.md`);
258
+ } else {
259
+ console.error(` Warning: UX audit failed - ${uxAuditResult.error}`);
260
+ }
261
+ } else {
262
+ console.error(' Skipped: No screenshots found for audit');
263
+ }
264
+ } else {
265
+ console.error(' Skipped: GEMINI_API_KEY not set');
266
+ console.error(' Hint: Set GEMINI_API_KEY in ~/.claude/.env for UX audit');
267
+ }
268
+ } else {
269
+ console.error(' Skipped (use --ux-audit flag to enable)');
270
+ }
271
+
272
+ // Step 6: Rewrite links
273
+ console.error('\n[6/7] Rewriting links...');
212
274
 
213
275
  const manifest = createPageManifest(pageList.pages, {
214
276
  hasTokens,
@@ -241,8 +303,8 @@ export async function cloneSite(url, options = {}) {
241
303
  }
242
304
  }
243
305
 
244
- // Step 6: Generate manifest
245
- console.error('\n[6/6] Generating manifest...');
306
+ // Step 7: Generate manifest
307
+ console.error('\n[7/7] Generating manifest...');
246
308
 
247
309
  const manifestPath = path.join(outputDir, 'manifest.json');
248
310
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
@@ -261,6 +323,7 @@ export async function cloneSite(url, options = {}) {
261
323
  manifest,
262
324
  captureResult,
263
325
  mergeResult,
326
+ uxAuditResult,
264
327
  totalTimeMs: totalTime
265
328
  };
266
329
  }
@@ -281,12 +344,14 @@ Options:
281
344
  --yes Skip confirmation prompt
282
345
  --output <dir> Custom output directory
283
346
  --ai Extract design tokens using Gemini AI (requires GEMINI_API_KEY)
347
+ --ux-audit Run UX audit using Gemini Vision (requires GEMINI_API_KEY)
284
348
 
285
349
  Examples:
286
350
  design-clone clone-site https://example.com
287
351
  design-clone clone-site https://example.com --max-pages 5
288
352
  design-clone clone-site https://example.com --pages /,/about,/contact
289
353
  design-clone clone-site https://example.com --ai
354
+ design-clone clone-site https://example.com --ux-audit
290
355
  `);
291
356
  }
292
357
 
@@ -50,7 +50,7 @@ export async function init(args) {
50
50
  }
51
51
 
52
52
  if (!checks.chrome.ok) {
53
- console.warn('Warning: Chrome not found. Screenshots may not work without Puppeteer\'s bundled Chromium.');
53
+ console.warn('Warning: Chrome not found. Playwright will download Chromium during installation.');
54
54
  }
55
55
 
56
56
  // Check existing installation
@@ -129,6 +129,38 @@ export async function init(args) {
129
129
  console.warn(` Warning: npm install failed: ${error.message}`);
130
130
  }
131
131
 
132
+ // Install Playwright
133
+ console.log('Installing Playwright...');
134
+ try {
135
+ // Check if playwright is already installed
136
+ let playwrightInstalled = false;
137
+ try {
138
+ await exec('node -e "require.resolve(\'playwright\')"', { cwd: SKILL_DEST });
139
+ playwrightInstalled = true;
140
+ console.log(' Playwright already installed');
141
+ } catch {
142
+ // Need to install
143
+ }
144
+
145
+ if (!playwrightInstalled) {
146
+ await exec('npm install playwright', { cwd: SKILL_DEST });
147
+ console.log(' Playwright installed');
148
+ }
149
+
150
+ // Install Playwright browsers (chromium only for smaller footprint)
151
+ console.log('Installing Playwright browsers...');
152
+ try {
153
+ await exec('npx playwright install chromium', { cwd: SKILL_DEST, timeout: 300000 });
154
+ console.log(' Chromium browser installed');
155
+ } catch (browserError) {
156
+ console.warn(` Warning: Browser install failed: ${browserError.message}`);
157
+ console.warn(' Run manually: npx playwright install chromium');
158
+ }
159
+ } catch (error) {
160
+ console.warn(` Warning: Playwright install failed: ${error.message}`);
161
+ console.warn(' Run manually: npm install playwright && npx playwright install chromium');
162
+ }
163
+
132
164
  // Python dependencies
133
165
  if (checks.python.ok) {
134
166
  console.log('Installing Python dependencies...');
@@ -63,11 +63,13 @@ export async function verify() {
63
63
  console.log('\nEnvironment:');
64
64
  const checks = await runAllChecks();
65
65
 
66
- console.log(` Node.js: ${checks.node.ok ? '✓' : '✗'} ${checks.node.message}`);
67
- console.log(` Python: ${checks.python.ok ? '✓' : '✗'} ${checks.python.message}`);
68
- console.log(` Chrome: ${checks.chrome.ok ? '✓' : '✗'} ${checks.chrome.message}`);
66
+ console.log(` Node.js: ${checks.node.ok ? '✓' : '✗'} ${checks.node.message}`);
67
+ console.log(` Python: ${checks.python.ok ? '✓' : '✗'} ${checks.python.message}`);
68
+ console.log(` Playwright: ${checks.playwright.ok ? '✓' : '✗'} ${checks.playwright.message}`);
69
+ console.log(` Chrome: ${checks.chrome.ok ? '✓' : '○'} ${checks.chrome.message}${checks.playwright.ok ? ' (optional with Playwright)' : ''}`);
69
70
 
70
71
  if (!checks.node.ok) allOk = false;
72
+ if (!checks.playwright.ok && !checks.chrome.ok) allOk = false;
71
73
 
72
74
  // Check Gemini API key
73
75
  console.log('\nOptional:');
@@ -94,15 +94,31 @@ export async function checkChrome() {
94
94
  }
95
95
 
96
96
  /**
97
- * Check Puppeteer
97
+ * Check Playwright
98
98
  * @returns {Promise<{ok: boolean, message: string}>}
99
99
  */
100
- export async function checkPuppeteer() {
100
+ export async function checkPlaywright() {
101
101
  try {
102
- await import('puppeteer');
103
- return { ok: true, message: 'Puppeteer installed' };
102
+ const playwright = await import('playwright');
103
+ // Check if browsers are installed by checking chromium executable
104
+ if (playwright.chromium?.executablePath) {
105
+ try {
106
+ const fs = await import('fs/promises');
107
+ await fs.access(playwright.chromium.executablePath());
108
+ return { ok: true, message: 'Playwright installed with browsers' };
109
+ } catch {
110
+ return { ok: true, message: 'Playwright installed (browsers may need install)' };
111
+ }
112
+ }
113
+ return { ok: true, message: 'Playwright installed' };
104
114
  } catch {
105
- return { ok: false, message: 'Puppeteer not installed (optional)' };
115
+ // Try playwright-core
116
+ try {
117
+ await import('playwright-core');
118
+ return { ok: true, message: 'playwright-core installed (needs Chrome)' };
119
+ } catch {
120
+ return { ok: false, message: 'Playwright not installed' };
121
+ }
106
122
  }
107
123
  }
108
124
 
@@ -111,12 +127,12 @@ export async function checkPuppeteer() {
111
127
  * @returns {Promise<Object>}
112
128
  */
113
129
  export async function runAllChecks() {
114
- const [node, python, chrome, puppeteer] = await Promise.all([
130
+ const [node, python, chrome, playwright] = await Promise.all([
115
131
  checkNode(),
116
132
  checkPython(),
117
133
  checkChrome(),
118
- checkPuppeteer()
134
+ checkPlaywright()
119
135
  ]);
120
136
 
121
- return { node, python, chrome, puppeteer };
137
+ return { node, python, chrome, playwright };
122
138
  }