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.
- package/README.md +42 -20
- package/SKILL.md +74 -0
- package/bin/commands/clone-site.js +75 -10
- package/bin/commands/init.js +33 -1
- package/bin/commands/verify.js +5 -3
- package/bin/utils/validate.js +24 -8
- package/docs/cli-reference.md +224 -2
- package/docs/codebase-summary.md +309 -0
- package/docs/design-clone-architecture.md +290 -45
- package/docs/pixel-perfect.md +35 -4
- package/docs/project-roadmap.md +382 -0
- package/docs/troubleshooting.md +5 -4
- package/package.json +12 -6
- package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
- package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
- package/src/ai/analyze-structure.py +73 -3
- package/src/ai/extract-design-tokens.py +356 -13
- package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
- package/src/ai/prompts/design_tokens.py +133 -0
- package/src/ai/prompts/structure_analysis.py +329 -10
- package/src/ai/prompts/ux_audit.py +198 -0
- package/src/ai/ux-audit.js +596 -0
- package/src/core/animation-extractor.js +526 -0
- package/src/core/app-state-snapshot.js +511 -0
- package/src/core/content-counter.js +342 -0
- package/src/core/cookie-handler.js +1 -1
- package/src/core/css-extractor.js +4 -4
- package/src/core/dimension-extractor.js +93 -21
- package/src/core/dimension-output.js +103 -6
- package/src/core/discover-pages.js +242 -14
- package/src/core/dom-tree-analyzer.js +298 -0
- package/src/core/extract-assets.js +1 -1
- package/src/core/framework-detector.js +538 -0
- package/src/core/html-extractor.js +45 -4
- package/src/core/lazy-loader.js +7 -7
- package/src/core/multi-page-screenshot.js +9 -6
- package/src/core/page-readiness.js +8 -8
- package/src/core/screenshot.js +311 -7
- package/src/core/section-cropper.js +209 -0
- package/src/core/section-detector.js +386 -0
- package/src/core/semantic-enhancer.js +492 -0
- package/src/core/state-capture.js +598 -0
- package/src/core/tests/test-section-cropper.js +177 -0
- package/src/core/tests/test-section-detector.js +55 -0
- package/src/core/video-capture.js +546 -0
- package/src/route-discoverers/angular-discoverer.js +157 -0
- package/src/route-discoverers/astro-discoverer.js +123 -0
- package/src/route-discoverers/base-discoverer.js +242 -0
- package/src/route-discoverers/index.js +106 -0
- package/src/route-discoverers/next-discoverer.js +130 -0
- package/src/route-discoverers/nuxt-discoverer.js +138 -0
- package/src/route-discoverers/react-discoverer.js +139 -0
- package/src/route-discoverers/svelte-discoverer.js +109 -0
- package/src/route-discoverers/universal-discoverer.js +227 -0
- package/src/route-discoverers/vue-discoverer.js +118 -0
- package/src/utils/__init__.py +1 -1
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/browser.js +11 -37
- package/src/utils/playwright.js +213 -0
- package/src/verification/generate-audit-report.js +398 -0
- package/src/verification/verify-footer.js +493 -0
- package/src/verification/verify-header.js +486 -0
- package/src/verification/verify-layout.js +2 -2
- package/src/verification/verify-menu.js +4 -20
- package/src/verification/verify-slider.js +533 -0
- package/src/utils/puppeteer.js +0 -281
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Clone
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
79
|
-
├── tablet.png
|
|
80
|
-
├── mobile.png
|
|
81
|
-
├── source.html
|
|
82
|
-
├── source.css
|
|
83
|
-
├── source-raw.css
|
|
84
|
-
├──
|
|
85
|
-
├── tokens.json
|
|
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
|
-
-
|
|
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
|
-
###
|
|
153
|
+
### Playwright issues
|
|
141
154
|
|
|
142
155
|
```bash
|
|
143
|
-
# Install
|
|
144
|
-
npm install
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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:
|
|
211
|
-
console.error('\n[5/
|
|
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
|
|
245
|
-
console.error('\n[
|
|
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
|
|
package/bin/commands/init.js
CHANGED
|
@@ -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.
|
|
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...');
|
package/bin/commands/verify.js
CHANGED
|
@@ -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:
|
|
67
|
-
console.log(` Python:
|
|
68
|
-
console.log(`
|
|
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:');
|
package/bin/utils/validate.js
CHANGED
|
@@ -94,15 +94,31 @@ export async function checkChrome() {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Check
|
|
97
|
+
* Check Playwright
|
|
98
98
|
* @returns {Promise<{ok: boolean, message: string}>}
|
|
99
99
|
*/
|
|
100
|
-
export async function
|
|
100
|
+
export async function checkPlaywright() {
|
|
101
101
|
try {
|
|
102
|
-
await import('
|
|
103
|
-
|
|
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
|
-
|
|
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,
|
|
130
|
+
const [node, python, chrome, playwright] = await Promise.all([
|
|
115
131
|
checkNode(),
|
|
116
132
|
checkPython(),
|
|
117
133
|
checkChrome(),
|
|
118
|
-
|
|
134
|
+
checkPlaywright()
|
|
119
135
|
]);
|
|
120
136
|
|
|
121
|
-
return { node, python, chrome,
|
|
137
|
+
return { node, python, chrome, playwright };
|
|
122
138
|
}
|