design-clone 1.0.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 (47) hide show
  1. package/.env.example +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/SKILL.md +239 -0
  5. package/bin/cli.js +45 -0
  6. package/bin/commands/help.js +29 -0
  7. package/bin/commands/init.js +126 -0
  8. package/bin/commands/verify.js +99 -0
  9. package/bin/utils/copy.js +65 -0
  10. package/bin/utils/validate.js +122 -0
  11. package/docs/basic-clone.md +63 -0
  12. package/docs/cli-reference.md +94 -0
  13. package/docs/design-clone-architecture.md +247 -0
  14. package/docs/pixel-perfect.md +86 -0
  15. package/docs/troubleshooting.md +97 -0
  16. package/package.json +57 -0
  17. package/requirements.txt +5 -0
  18. package/src/ai/analyze-structure.py +305 -0
  19. package/src/ai/extract-design-tokens.py +439 -0
  20. package/src/ai/prompts/__init__.py +2 -0
  21. package/src/ai/prompts/design_tokens.py +183 -0
  22. package/src/ai/prompts/structure_analysis.py +273 -0
  23. package/src/core/cookie-handler.js +76 -0
  24. package/src/core/css-extractor.js +107 -0
  25. package/src/core/dimension-extractor.js +366 -0
  26. package/src/core/dimension-output.js +208 -0
  27. package/src/core/extract-assets.js +468 -0
  28. package/src/core/filter-css.js +499 -0
  29. package/src/core/html-extractor.js +102 -0
  30. package/src/core/lazy-loader.js +188 -0
  31. package/src/core/page-readiness.js +161 -0
  32. package/src/core/screenshot.js +380 -0
  33. package/src/post-process/enhance-assets.js +157 -0
  34. package/src/post-process/fetch-images.js +398 -0
  35. package/src/post-process/inject-icons.js +311 -0
  36. package/src/utils/__init__.py +16 -0
  37. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  38. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  39. package/src/utils/browser.js +103 -0
  40. package/src/utils/env.js +153 -0
  41. package/src/utils/env.py +134 -0
  42. package/src/utils/helpers.js +71 -0
  43. package/src/utils/puppeteer.js +281 -0
  44. package/src/verification/verify-layout.js +424 -0
  45. package/src/verification/verify-menu.js +422 -0
  46. package/templates/base.css +705 -0
  47. package/templates/base.html +293 -0
@@ -0,0 +1,86 @@
1
+ # Pixel-Perfect Clone Workflow
2
+
3
+ Full design replication with asset extraction, AI analysis, and verification.
4
+
5
+ ## When to Use
6
+
7
+ - Production-ready design implementation
8
+ - Complete asset and structure extraction
9
+ - Sites with complex navigation or interactions
10
+
11
+ ## Full Workflow
12
+
13
+ ### Step 1: Capture + Extract
14
+
15
+ ```bash
16
+ node src/core/screenshot.js \
17
+ --url "https://example.com" \
18
+ --output ./clone \
19
+ --extract-html --extract-css \
20
+ --full-page
21
+ ```
22
+
23
+ ### Step 2: Filter CSS
24
+
25
+ ```bash
26
+ node src/core/filter-css.js \
27
+ --html ./clone/source.html \
28
+ --css ./clone/source-raw.css \
29
+ --output ./clone/source.css
30
+ ```
31
+
32
+ ### Step 3: Extract Assets
33
+
34
+ ```bash
35
+ node src/core/extract-assets.js \
36
+ --url "https://example.com" \
37
+ --output ./clone
38
+ ```
39
+
40
+ Downloads: images, fonts, icons, SVGs to `./clone/assets/`
41
+
42
+ ### Step 4: AI Structure Analysis
43
+
44
+ Requires `GEMINI_API_KEY` in environment.
45
+
46
+ ```bash
47
+ python src/ai/analyze-structure.py \
48
+ -s ./clone/desktop.png \
49
+ -o ./clone \
50
+ --html ./clone/source.html \
51
+ --css ./clone/source.css
52
+ ```
53
+
54
+ Output: `./clone/structure.md` with section breakdown and BEM suggestions.
55
+
56
+ ### Step 5: Extract Design Tokens
57
+
58
+ ```bash
59
+ python src/ai/extract-design-tokens.py \
60
+ -s ./clone/desktop.png \
61
+ -o ./clone
62
+ ```
63
+
64
+ Output: `./clone/tokens.json` with colors, typography, spacing.
65
+
66
+ ### Step 6: Verify Menu
67
+
68
+ ```bash
69
+ node src/verification/verify-menu.js --html ./clone/source.html
70
+ ```
71
+
72
+ Reports: missing links, broken structure, accessibility issues.
73
+
74
+ ## Output Structure
75
+
76
+ ```
77
+ ./clone/
78
+ ├── desktop.png, tablet.png, mobile.png
79
+ ├── source.html, source.css
80
+ ├── structure.md # AI analysis
81
+ ├── tokens.json # Design tokens
82
+ └── assets/
83
+ ├── images/
84
+ ├── fonts/
85
+ └── icons/
86
+ ```
@@ -0,0 +1,97 @@
1
+ # Troubleshooting
2
+
3
+ Common issues and solutions.
4
+
5
+ ## Browser Issues
6
+
7
+ ### Chrome/Chromium not found
8
+
9
+ **Error:** `Could not find Chrome`
10
+
11
+ **Solution:**
12
+ ```bash
13
+ # macOS
14
+ brew install --cask google-chrome
15
+
16
+ # Ubuntu
17
+ sudo apt install chromium-browser
18
+
19
+ # Or set path manually
20
+ export CHROME_PATH="/path/to/chrome"
21
+ ```
22
+
23
+ ### Puppeteer launch fails
24
+
25
+ **Error:** `Failed to launch browser`
26
+
27
+ **Solutions:**
28
+ 1. Install dependencies: `npm install puppeteer`
29
+ 2. Linux: Install required libs: `apt install libnss3 libatk1.0-0 libatk-bridge2.0-0`
30
+ 3. Run with `--no-sandbox` (Docker): Set `PUPPETEER_NO_SANDBOX=1`
31
+
32
+ ## CSS Issues
33
+
34
+ ### CORS-blocked stylesheets
35
+
36
+ **Symptom:** CSS extraction returns empty or partial styles.
37
+
38
+ **Solution:** Some sites block cross-origin stylesheet access. Use browser DevTools manually or try `--wait 3000` for dynamic CSS.
39
+
40
+ ### CSS too large
41
+
42
+ **Error:** `CSS file exceeds 10MB limit`
43
+
44
+ **Solution:** filter-css.js has 10MB limit. Split manually or increase `MAX_CSS_INPUT_SIZE` in filter-css.js.
45
+
46
+ ## Python Issues
47
+
48
+ ### Module not found
49
+
50
+ **Error:** `ModuleNotFoundError: google.genai`
51
+
52
+ **Solution:**
53
+ ```bash
54
+ pip install -r requirements.txt
55
+ # Or: pip install google-genai
56
+ ```
57
+
58
+ ### Wrong Python version
59
+
60
+ **Error:** `SyntaxError` on type hints
61
+
62
+ **Solution:** Requires Python 3.9+. Check: `python3 --version`
63
+
64
+ ## Gemini API Issues
65
+
66
+ ### API key not set
67
+
68
+ **Error:** `GEMINI_API_KEY not found`
69
+
70
+ **Solution:** Create `.env` file:
71
+ ```bash
72
+ GEMINI_API_KEY=your-api-key-here
73
+ ```
74
+
75
+ ### Rate limit exceeded
76
+
77
+ **Error:** `429 Too Many Requests`
78
+
79
+ **Solution:** Wait 1 minute, or use `--model gemini-1.5-flash` for lower limits.
80
+
81
+ ## Screenshot Issues
82
+
83
+ ### Incomplete page capture
84
+
85
+ **Symptom:** Missing sections in screenshot.
86
+
87
+ **Solutions:**
88
+ 1. Increase wait: `--wait 5000`
89
+ 2. Use full page: `--full-page`
90
+ 3. Check for lazy-loaded content
91
+
92
+ ### Wrong viewport size
93
+
94
+ **Solution:** Specify custom viewports:
95
+ ```bash
96
+ --viewports '[{"width":1440,"height":900,"name":"custom"}]'
97
+ ```
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "design-clone",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Claude Code skill for cloning website designs via multi-viewport screenshots, HTML/CSS extraction, and Gemini AI analysis",
6
+ "bin": {
7
+ "design-clone": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "templates/",
13
+ "docs/",
14
+ "SKILL.md",
15
+ "requirements.txt",
16
+ ".env.example"
17
+ ],
18
+ "scripts": {
19
+ "screenshot": "node src/core/screenshot.js",
20
+ "filter-css": "node src/core/filter-css.js",
21
+ "extract-assets": "node src/core/extract-assets.js",
22
+ "verify-menu": "node src/verification/verify-menu.js",
23
+ "verify-layout": "node src/verification/verify-layout.js",
24
+ "test": "node tests/run-all-tests.js"
25
+ },
26
+ "keywords": [
27
+ "claude",
28
+ "claude-code",
29
+ "skill",
30
+ "design",
31
+ "clone",
32
+ "screenshot",
33
+ "puppeteer",
34
+ "gemini",
35
+ "website"
36
+ ],
37
+ "author": "",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/bienhoang/design-clone"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ },
46
+ "dependencies": {
47
+ "css-tree": "^2.3.1"
48
+ },
49
+ "peerDependencies": {
50
+ "puppeteer": ">=21.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "puppeteer": {
54
+ "optional": true
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,5 @@
1
+ # Design Clone Skill - Python Dependencies
2
+ # Install: pip install -r requirements.txt
3
+
4
+ # Gemini AI for structure analysis and token extraction
5
+ google-genai>=1.0.0
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Analyze website screenshot structure using Gemini Vision API.
4
+
5
+ Usage:
6
+ python analyze-structure.py --screenshot ./analysis/desktop.png --output ./analysis
7
+ python analyze-structure.py -s desktop.png -o ./out --html source.html --css source.css
8
+
9
+ Options:
10
+ --screenshot Path to desktop screenshot
11
+ --output Output directory for structure.md
12
+ --html Path to source HTML file (optional, improves accuracy)
13
+ --css Path to filtered CSS file (optional, improves accuracy)
14
+ --model Gemini model (default: gemini-2.5-flash)
15
+ --verbose Enable verbose output
16
+
17
+ Output:
18
+ - structure.md: Markdown description of page structure
19
+
20
+ When HTML/CSS provided, extracts EXACT values from source instead of estimating.
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ # Add src directory to path for local imports
30
+ SCRIPT_DIR = Path(__file__).parent.resolve()
31
+ SRC_DIR = SCRIPT_DIR.parent
32
+ sys.path.insert(0, str(SRC_DIR))
33
+
34
+ # Import local env resolver (portable)
35
+ try:
36
+ from utils.env import resolve_env, load_env
37
+ load_env() # Load .env files on startup
38
+ except ImportError:
39
+ # Fallback: simple env getter
40
+ def resolve_env(key, default=None):
41
+ return os.environ.get(key, default)
42
+
43
+ # Check for google-genai dependency
44
+ try:
45
+ from google import genai
46
+ from google.genai import types
47
+ except ImportError:
48
+ print(json.dumps({
49
+ "success": False,
50
+ "error": "google-genai not installed",
51
+ "hint": "Run: pip install google-genai"
52
+ }, indent=2))
53
+ sys.exit(1)
54
+
55
+ # Import prompts from extracted module
56
+ from prompts.structure_analysis import build_structure_prompt
57
+
58
+
59
+ # Fallback structure when analysis fails
60
+ DEFAULT_STRUCTURE = """# Page Structure Analysis
61
+
62
+ ## 1. Header Section
63
+ - Logo: Left-aligned, text-based
64
+ - Navigation: Horizontal, 4-5 items
65
+ - CTA Button: Right-aligned, primary color
66
+ - Mobile menu: Hamburger icon for small screens
67
+
68
+ ## 2. Hero Section
69
+ - Layout: Centered
70
+ - Headline: Large (36-48px), bold, dark text
71
+ - Subheadline: Medium (18-20px), lighter text
72
+ - Primary CTA: Prominent button, primary color
73
+ - Background: Solid light color
74
+
75
+ ## 3. Content Sections
76
+ ### Features Section
77
+ - Layout: 3-column grid
78
+ - Items: 3 feature cards with icons
79
+ - Components: Icon + heading + description
80
+
81
+ ### About/Info Section
82
+ - Layout: 2-column (text + image)
83
+ - Alternating left-right pattern
84
+
85
+ ## 4. Footer Section
86
+ - Layout: 4-column grid
87
+ - Content: Logo, nav links, contact, social icons
88
+ - Copyright: Centered at bottom
89
+
90
+ ## 5. Global Patterns
91
+ - Container max-width: 1200px
92
+ - Section padding: 64px vertical
93
+ - Card style: Subtle shadows, 8px border-radius
94
+ - Color scheme: Light mode
95
+ - Typography: Sans-serif (modern)
96
+
97
+ ## 6. Responsive Hints
98
+ - Navigation collapses to hamburger on mobile
99
+ - Grid sections stack vertically
100
+ - Padding reduces on smaller screens
101
+
102
+ ## 7. BEM Class Suggestions
103
+ - header, header__container, header__logo, header__nav, header__cta
104
+ - hero, hero__container, hero__title, hero__subtitle, hero__cta
105
+ - features, features__grid, feature-card, feature-card__icon, feature-card__title
106
+ - footer, footer__container, footer__column, footer__links
107
+ """
108
+
109
+
110
+ def get_api_key():
111
+ """Get Gemini API key from environment (supports GEMINI_API_KEY or GOOGLE_API_KEY)."""
112
+ return resolve_env('GEMINI_API_KEY') or resolve_env('GOOGLE_API_KEY')
113
+
114
+
115
+ def load_dimensions(output_dir: str) -> dict:
116
+ """Load extracted dimensions summary if available.
117
+
118
+ Args:
119
+ output_dir: Directory containing dimensions-summary.json
120
+
121
+ Returns:
122
+ Dimensions dict or None if not found
123
+ """
124
+ summary_path = Path(output_dir) / "dimensions-summary.json"
125
+ if summary_path.exists():
126
+ try:
127
+ with open(summary_path, 'r', encoding='utf-8') as f:
128
+ return json.load(f)
129
+ except (json.JSONDecodeError, IOError) as e:
130
+ print(f"Warning: Failed to load dimensions: {e}", file=sys.stderr)
131
+ return None
132
+
133
+
134
+ def analyze_structure(
135
+ screenshot_path: str,
136
+ output_dir: str = None,
137
+ html_path: str = None,
138
+ css_path: str = None,
139
+ model: str = "gemini-2.5-flash",
140
+ verbose: bool = False
141
+ ) -> str:
142
+ """Analyze screenshot structure using Gemini Vision.
143
+
144
+ Args:
145
+ screenshot_path: Path to desktop screenshot
146
+ output_dir: Output directory (also reads dimensions-summary.json if present)
147
+ html_path: Optional path to source HTML (improves accuracy)
148
+ css_path: Optional path to filtered CSS (improves accuracy)
149
+ model: Gemini model to use
150
+ verbose: Enable verbose output
151
+
152
+ Returns:
153
+ Markdown structure analysis
154
+ """
155
+
156
+ api_key = get_api_key()
157
+ if not api_key:
158
+ if verbose:
159
+ print("Warning: GEMINI_API_KEY not found, using default structure")
160
+ return DEFAULT_STRUCTURE
161
+
162
+ screenshot = Path(screenshot_path)
163
+ if not screenshot.exists():
164
+ if verbose:
165
+ print(f"Warning: Screenshot not found: {screenshot_path}")
166
+ return DEFAULT_STRUCTURE
167
+
168
+ # Load extracted dimensions if available (highest priority)
169
+ dimensions = None
170
+ if output_dir:
171
+ dimensions = load_dimensions(output_dir)
172
+ if dimensions and verbose:
173
+ print(f"Loaded extracted dimensions from {output_dir}/dimensions-summary.json")
174
+
175
+ # Load HTML/CSS if provided
176
+ html_content = None
177
+ css_content = None
178
+
179
+ if html_path and Path(html_path).exists():
180
+ with open(html_path, 'r', encoding='utf-8') as f:
181
+ html_content = f.read()
182
+ if verbose:
183
+ print(f"Loaded HTML: {len(html_content)} chars")
184
+
185
+ if css_path and Path(css_path).exists():
186
+ with open(css_path, 'r', encoding='utf-8') as f:
187
+ css_content = f.read()
188
+ if verbose:
189
+ print(f"Loaded CSS: {len(css_content)} chars")
190
+
191
+ # Build prompt with context (dimensions have highest priority)
192
+ prompt = build_structure_prompt(html_content, css_content, dimensions)
193
+
194
+ if verbose:
195
+ if dimensions:
196
+ print("Using ENHANCED prompt with EXACT extracted dimensions")
197
+ elif html_content and css_content:
198
+ print("Using prompt with HTML/CSS context")
199
+
200
+ try:
201
+ client = genai.Client(api_key=api_key)
202
+
203
+ # Load image
204
+ with open(screenshot, 'rb') as f:
205
+ img_bytes = f.read()
206
+
207
+ content = [
208
+ prompt,
209
+ types.Part.from_bytes(data=img_bytes, mime_type='image/png')
210
+ ]
211
+
212
+ if verbose:
213
+ print(f"Analyzing structure with {model}...")
214
+
215
+ response = client.models.generate_content(
216
+ model=model,
217
+ contents=content
218
+ )
219
+
220
+ if hasattr(response, 'text') and response.text:
221
+ if verbose:
222
+ print("Structure analysis complete")
223
+ return response.text
224
+ else:
225
+ if verbose:
226
+ print("Warning: Empty response, using default structure")
227
+ return DEFAULT_STRUCTURE
228
+
229
+ except Exception as e:
230
+ if verbose:
231
+ print(f"Error during analysis: {e}")
232
+ return DEFAULT_STRUCTURE
233
+
234
+
235
+ def main():
236
+ parser = argparse.ArgumentParser(
237
+ description="Analyze website screenshot structure using Gemini Vision"
238
+ )
239
+ parser.add_argument(
240
+ '--screenshot', '-s',
241
+ required=True,
242
+ help='Path to desktop screenshot'
243
+ )
244
+ parser.add_argument(
245
+ '--output', '-o',
246
+ required=True,
247
+ help='Output directory for structure.md (also reads dimensions-summary.json if present)'
248
+ )
249
+ parser.add_argument(
250
+ '--html',
251
+ default=None,
252
+ help='Path to source HTML file (optional, improves accuracy)'
253
+ )
254
+ parser.add_argument(
255
+ '--css',
256
+ default=None,
257
+ help='Path to filtered CSS file (optional, improves accuracy)'
258
+ )
259
+ parser.add_argument(
260
+ '--model', '-m',
261
+ default='gemini-2.5-flash',
262
+ help='Gemini model to use (default: gemini-2.5-flash)'
263
+ )
264
+ parser.add_argument(
265
+ '--verbose', '-v',
266
+ action='store_true',
267
+ help='Enable verbose output'
268
+ )
269
+
270
+ args = parser.parse_args()
271
+
272
+ # Create output directory
273
+ output_path = Path(args.output)
274
+ output_path.mkdir(parents=True, exist_ok=True)
275
+
276
+ # Analyze structure (dimensions loaded automatically from output_dir)
277
+ structure = analyze_structure(
278
+ screenshot_path=args.screenshot,
279
+ output_dir=args.output,
280
+ html_path=args.html,
281
+ css_path=args.css,
282
+ model=args.model,
283
+ verbose=args.verbose
284
+ )
285
+
286
+ # Save structure.md
287
+ md_path = output_path / "structure.md"
288
+ with open(md_path, 'w') as f:
289
+ f.write(structure)
290
+
291
+ if args.verbose:
292
+ print(f"Saved: {md_path}")
293
+
294
+ # Output result as JSON
295
+ result = {
296
+ "success": True,
297
+ "structure_file": str(md_path),
298
+ "model": args.model
299
+ }
300
+
301
+ print(json.dumps(result, indent=2))
302
+
303
+
304
+ if __name__ == '__main__':
305
+ main()