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.
- package/.env.example +14 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/SKILL.md +239 -0
- package/bin/cli.js +45 -0
- package/bin/commands/help.js +29 -0
- package/bin/commands/init.js +126 -0
- package/bin/commands/verify.js +99 -0
- package/bin/utils/copy.js +65 -0
- package/bin/utils/validate.js +122 -0
- package/docs/basic-clone.md +63 -0
- package/docs/cli-reference.md +94 -0
- package/docs/design-clone-architecture.md +247 -0
- package/docs/pixel-perfect.md +86 -0
- package/docs/troubleshooting.md +97 -0
- package/package.json +57 -0
- package/requirements.txt +5 -0
- package/src/ai/analyze-structure.py +305 -0
- package/src/ai/extract-design-tokens.py +439 -0
- package/src/ai/prompts/__init__.py +2 -0
- package/src/ai/prompts/design_tokens.py +183 -0
- package/src/ai/prompts/structure_analysis.py +273 -0
- package/src/core/cookie-handler.js +76 -0
- package/src/core/css-extractor.js +107 -0
- package/src/core/dimension-extractor.js +366 -0
- package/src/core/dimension-output.js +208 -0
- package/src/core/extract-assets.js +468 -0
- package/src/core/filter-css.js +499 -0
- package/src/core/html-extractor.js +102 -0
- package/src/core/lazy-loader.js +188 -0
- package/src/core/page-readiness.js +161 -0
- package/src/core/screenshot.js +380 -0
- package/src/post-process/enhance-assets.js +157 -0
- package/src/post-process/fetch-images.js +398 -0
- package/src/post-process/inject-icons.js +311 -0
- package/src/utils/__init__.py +16 -0
- 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 +103 -0
- package/src/utils/env.js +153 -0
- package/src/utils/env.py +134 -0
- package/src/utils/helpers.js +71 -0
- package/src/utils/puppeteer.js +281 -0
- package/src/verification/verify-layout.js +424 -0
- package/src/verification/verify-menu.js +422 -0
- package/templates/base.css +705 -0
- 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
|
+
}
|
package/requirements.txt
ADDED
|
@@ -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()
|