design-clone 2.1.0 → 2.3.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 (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
@@ -1,375 +0,0 @@
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 load_dom_hierarchy(output_dir: str) -> dict:
135
- """Load extracted DOM hierarchy if available.
136
-
137
- Args:
138
- output_dir: Directory containing dom-hierarchy.json
139
-
140
- Returns:
141
- Hierarchy dict or None if not found
142
- """
143
- hierarchy_path = Path(output_dir) / "dom-hierarchy.json"
144
- if hierarchy_path.exists():
145
- try:
146
- with open(hierarchy_path, 'r', encoding='utf-8') as f:
147
- return json.load(f)
148
- except (json.JSONDecodeError, IOError) as e:
149
- print(f"Warning: Failed to load DOM hierarchy: {e}", file=sys.stderr)
150
- return None
151
-
152
-
153
- def load_content_counts(output_dir: str) -> tuple:
154
- """Load content counts and summary if available.
155
-
156
- Args:
157
- output_dir: Directory containing content-counts.json and content-summary.md
158
-
159
- Returns:
160
- Tuple of (counts_dict, summary_text) or (None, None) if not found
161
- """
162
- counts_path = Path(output_dir) / "content-counts.json"
163
- summary_path = Path(output_dir) / "content-summary.md"
164
-
165
- counts = None
166
- summary = None
167
-
168
- if counts_path.exists():
169
- try:
170
- with open(counts_path, 'r', encoding='utf-8') as f:
171
- counts = json.load(f)
172
- except (json.JSONDecodeError, IOError) as e:
173
- print(f"Warning: Failed to load content counts: {e}", file=sys.stderr)
174
-
175
- if summary_path.exists():
176
- try:
177
- with open(summary_path, 'r', encoding='utf-8') as f:
178
- summary = f.read()
179
- except IOError as e:
180
- print(f"Warning: Failed to load content summary: {e}", file=sys.stderr)
181
-
182
- return counts, summary
183
-
184
-
185
- def analyze_structure(
186
- screenshot_path: str,
187
- output_dir: str = None,
188
- html_path: str = None,
189
- css_path: str = None,
190
- model: str = "gemini-2.5-flash",
191
- verbose: bool = False
192
- ) -> str:
193
- """Analyze screenshot structure using Gemini Vision.
194
-
195
- Args:
196
- screenshot_path: Path to desktop screenshot
197
- output_dir: Output directory (also reads dimensions-summary.json if present)
198
- html_path: Optional path to source HTML (improves accuracy)
199
- css_path: Optional path to filtered CSS (improves accuracy)
200
- model: Gemini model to use
201
- verbose: Enable verbose output
202
-
203
- Returns:
204
- Markdown structure analysis
205
- """
206
-
207
- api_key = get_api_key()
208
- if not api_key:
209
- if verbose:
210
- print("Warning: GEMINI_API_KEY not found, using default structure")
211
- return DEFAULT_STRUCTURE
212
-
213
- screenshot = Path(screenshot_path)
214
- if not screenshot.exists():
215
- if verbose:
216
- print(f"Warning: Screenshot not found: {screenshot_path}")
217
- return DEFAULT_STRUCTURE
218
-
219
- # Load extracted dimensions if available (highest priority)
220
- dimensions = None
221
- if output_dir:
222
- dimensions = load_dimensions(output_dir)
223
- if dimensions and verbose:
224
- print(f"Loaded extracted dimensions from {output_dir}/dimensions-summary.json")
225
-
226
- # Load DOM hierarchy if available (enhances dimensions)
227
- hierarchy = None
228
- if output_dir:
229
- hierarchy = load_dom_hierarchy(output_dir)
230
- if hierarchy and verbose:
231
- stats = hierarchy.get('stats', {})
232
- print(f"Loaded DOM hierarchy: {stats.get('totalNodes', 0)} nodes, depth {stats.get('maxDepth', 0)}")
233
-
234
- # Load content counts if available
235
- content_counts, content_summary = None, None
236
- if output_dir:
237
- content_counts, content_summary = load_content_counts(output_dir)
238
- if content_counts and verbose:
239
- print(f"Loaded content counts: {content_counts.get('summary', {}).get('totalRepeatedItems', 0)} items")
240
-
241
- # Load HTML/CSS if provided
242
- html_content = None
243
- css_content = None
244
-
245
- if html_path and Path(html_path).exists():
246
- with open(html_path, 'r', encoding='utf-8') as f:
247
- html_content = f.read()
248
- if verbose:
249
- print(f"Loaded HTML: {len(html_content)} chars")
250
-
251
- if css_path and Path(css_path).exists():
252
- with open(css_path, 'r', encoding='utf-8') as f:
253
- css_content = f.read()
254
- if verbose:
255
- print(f"Loaded CSS: {len(css_content)} chars")
256
-
257
- # Build prompt with context (hierarchy+dimensions have highest priority)
258
- prompt = build_structure_prompt(html_content, css_content, dimensions, content_summary, hierarchy)
259
-
260
- if verbose:
261
- if hierarchy and dimensions:
262
- print("Using HIERARCHY prompt with DOM structure + EXACT dimensions")
263
- elif dimensions:
264
- print("Using ENHANCED prompt with EXACT extracted dimensions")
265
- if content_summary:
266
- print("Using prompt with EXACT content counts")
267
- elif html_content and css_content:
268
- print("Using prompt with HTML/CSS context")
269
-
270
- try:
271
- client = genai.Client(api_key=api_key)
272
-
273
- # Load image
274
- with open(screenshot, 'rb') as f:
275
- img_bytes = f.read()
276
-
277
- content = [
278
- prompt,
279
- types.Part.from_bytes(data=img_bytes, mime_type='image/png')
280
- ]
281
-
282
- if verbose:
283
- print(f"Analyzing structure with {model}...")
284
-
285
- response = client.models.generate_content(
286
- model=model,
287
- contents=content
288
- )
289
-
290
- if hasattr(response, 'text') and response.text:
291
- if verbose:
292
- print("Structure analysis complete")
293
- return response.text
294
- else:
295
- if verbose:
296
- print("Warning: Empty response, using default structure")
297
- return DEFAULT_STRUCTURE
298
-
299
- except Exception as e:
300
- if verbose:
301
- print(f"Error during analysis: {e}")
302
- return DEFAULT_STRUCTURE
303
-
304
-
305
- def main():
306
- parser = argparse.ArgumentParser(
307
- description="Analyze website screenshot structure using Gemini Vision"
308
- )
309
- parser.add_argument(
310
- '--screenshot', '-s',
311
- required=True,
312
- help='Path to desktop screenshot'
313
- )
314
- parser.add_argument(
315
- '--output', '-o',
316
- required=True,
317
- help='Output directory for structure.md (also reads dimensions-summary.json if present)'
318
- )
319
- parser.add_argument(
320
- '--html',
321
- default=None,
322
- help='Path to source HTML file (optional, improves accuracy)'
323
- )
324
- parser.add_argument(
325
- '--css',
326
- default=None,
327
- help='Path to filtered CSS file (optional, improves accuracy)'
328
- )
329
- parser.add_argument(
330
- '--model', '-m',
331
- default='gemini-2.5-flash',
332
- help='Gemini model to use (default: gemini-2.5-flash)'
333
- )
334
- parser.add_argument(
335
- '--verbose', '-v',
336
- action='store_true',
337
- help='Enable verbose output'
338
- )
339
-
340
- args = parser.parse_args()
341
-
342
- # Create output directory
343
- output_path = Path(args.output)
344
- output_path.mkdir(parents=True, exist_ok=True)
345
-
346
- # Analyze structure (dimensions loaded automatically from output_dir)
347
- structure = analyze_structure(
348
- screenshot_path=args.screenshot,
349
- output_dir=args.output,
350
- html_path=args.html,
351
- css_path=args.css,
352
- model=args.model,
353
- verbose=args.verbose
354
- )
355
-
356
- # Save structure.md
357
- md_path = output_path / "structure.md"
358
- with open(md_path, 'w') as f:
359
- f.write(structure)
360
-
361
- if args.verbose:
362
- print(f"Saved: {md_path}")
363
-
364
- # Output result as JSON
365
- result = {
366
- "success": True,
367
- "structure_file": str(md_path),
368
- "model": args.model
369
- }
370
-
371
- print(json.dumps(result, indent=2))
372
-
373
-
374
- if __name__ == '__main__':
375
- main()