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,342 +0,0 @@
1
- /**
2
- * Content Counter
3
- *
4
- * Parse page DOM to extract exact content counts for:
5
- * - Grid items, list items, cards
6
- * - Navigation links
7
- * - Sections/containers
8
- * - Images, buttons, forms
9
- *
10
- * Outputs content-counts.json for use in structure analysis.
11
- */
12
-
13
- /**
14
- * Count content items in page DOM
15
- * @param {Page} page - Playwright page
16
- * @returns {Promise<object>} Content counts
17
- */
18
- export async function extractContentCounts(page) {
19
- return await page.evaluate(() => {
20
- const counts = {
21
- extractedAt: new Date().toISOString(),
22
-
23
- // Section counts
24
- sections: {
25
- total: 0,
26
- withBackground: 0,
27
- details: []
28
- },
29
-
30
- // Grid/List containers
31
- grids: {
32
- total: 0,
33
- details: []
34
- },
35
-
36
- // Repeated items (cards, list items)
37
- repeatedItems: {
38
- total: 0,
39
- byType: {}
40
- },
41
-
42
- // Navigation
43
- navigation: {
44
- headerLinks: 0,
45
- footerLinks: 0,
46
- allLinks: 0
47
- },
48
-
49
- // Media
50
- media: {
51
- images: 0,
52
- videos: 0,
53
- svgIcons: 0
54
- },
55
-
56
- // Interactive
57
- interactive: {
58
- buttons: 0,
59
- inputs: 0,
60
- forms: 0
61
- }
62
- };
63
-
64
- // Helper: check if element is visible (not hidden)
65
- const isVisible = (el) => {
66
- if (!el) return false;
67
- const style = getComputedStyle(el);
68
- return style.display !== 'none' &&
69
- style.visibility !== 'hidden' &&
70
- !el.hasAttribute('hidden');
71
- };
72
-
73
- // Helper: get meaningful class name for element
74
- const getSelector = (el) => {
75
- if (el.id) return `#${el.id}`;
76
- const classes = [...el.classList].filter(c =>
77
- !c.match(/^(js-|is-|has-|data-)/) &&
78
- c.length > 2
79
- ).slice(0, 3).join('.');
80
- return classes ? `.${classes}` : el.tagName.toLowerCase();
81
- };
82
-
83
- // 1. Count sections (major containers with padding/background)
84
- const sectionSelectors = [
85
- 'section',
86
- '[class*="section"]',
87
- '[class*="py-lg"]', '[class*="py-xl"]', '[class*="py-2xl"]',
88
- '[class*="py-md"]',
89
- '[class*="bg-background"]',
90
- '[class*="bg-white"]',
91
- '[class*="bg-gray"]'
92
- ];
93
-
94
- const sectionElements = new Set();
95
- const MAX_SECTION_DETAILS = 30;
96
-
97
- sectionSelectors.forEach(sel => {
98
- try {
99
- document.querySelectorAll(sel).forEach(el => {
100
- if (sectionElements.size >= MAX_SECTION_DETAILS) return;
101
-
102
- // Check if element has significant height and width (major section)
103
- const rect = el.getBoundingClientRect();
104
- const isSignificant = rect.height > 100 && rect.width > 200;
105
-
106
- // Count elements that are either:
107
- // 1. Direct children of body/main/root
108
- // 2. Have section-like characteristics (padding, bg, significant size)
109
- const parent = el.parentElement;
110
- const isTopLevel = parent?.tagName === 'BODY' ||
111
- parent?.tagName === 'MAIN' ||
112
- parent?.id === 'root' ||
113
- parent?.id === '__next' ||
114
- parent?.classList.contains('container');
115
-
116
- if (isTopLevel || (isSignificant && isVisible(el))) {
117
- sectionElements.add(el);
118
- }
119
- });
120
- } catch (e) { /* invalid selector */ }
121
- });
122
-
123
- sectionElements.forEach(el => {
124
- const style = getComputedStyle(el);
125
- const hasBg = style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&
126
- style.backgroundColor !== 'transparent';
127
- counts.sections.total++;
128
- if (hasBg) counts.sections.withBackground++;
129
-
130
- counts.sections.details.push({
131
- selector: getSelector(el),
132
- visible: isVisible(el),
133
- hasBackground: hasBg,
134
- childCount: el.children.length
135
- });
136
- });
137
-
138
- // 2. Count grid/flex containers and their items
139
- // Only target meaningful containers (not trivial wrappers)
140
- const gridSelectors = [
141
- '[class*="grid"]',
142
- '[style*="display: grid"]'
143
- ];
144
-
145
- // Separate flex selectors - more selective
146
- const flexSelectors = [
147
- '[class*="flex"][class*="gap"]',
148
- '[class*="flex"][class*="wrap"]',
149
- '[class*="flex"][class*="col"]'
150
- ];
151
-
152
- const processedGrids = new Set();
153
- const MIN_ITEMS_FOR_GRID = 2; // Only count containers with 2+ items
154
- const MAX_GRID_DETAILS = 50; // Limit output size
155
-
156
- [...gridSelectors, ...flexSelectors].forEach(sel => {
157
- try {
158
- document.querySelectorAll(sel).forEach(el => {
159
- if (processedGrids.has(el)) return;
160
- if (counts.grids.details.length >= MAX_GRID_DETAILS) return;
161
-
162
- const style = getComputedStyle(el);
163
- if (style.display === 'grid' || style.display === 'flex' ||
164
- style.display === 'inline-grid' || style.display === 'inline-flex') {
165
- processedGrids.add(el);
166
-
167
- // Count direct children (grid items)
168
- const items = [...el.children].filter(child =>
169
- child.tagName !== 'SCRIPT' &&
170
- child.tagName !== 'STYLE'
171
- );
172
-
173
- // Skip containers with fewer than MIN_ITEMS
174
- if (items.length < MIN_ITEMS_FOR_GRID) return;
175
-
176
- // Count visible vs hidden
177
- const visibleItems = items.filter(isVisible);
178
- const hiddenItems = items.filter(i => !isVisible(i));
179
-
180
- // Only count if has meaningful visible items
181
- if (visibleItems.length >= MIN_ITEMS_FOR_GRID) {
182
- counts.grids.total++;
183
- counts.grids.details.push({
184
- selector: getSelector(el),
185
- display: style.display,
186
- totalItems: items.length,
187
- visibleItems: visibleItems.length,
188
- hiddenItems: hiddenItems.length,
189
- gridCols: style.gridTemplateColumns || null,
190
- visible: isVisible(el)
191
- });
192
- }
193
- }
194
- });
195
- } catch (e) { /* invalid selector */ }
196
- });
197
-
198
- // 3. Count repeated items (cards, list items, etc.)
199
- const repeatPatterns = [
200
- { name: 'cards', selectors: ['[class*="card"]', '[class*="Card"]'] },
201
- { name: 'listItems', selectors: ['li', '[class*="item"]', '[class*="Item"]'] },
202
- { name: 'services', selectors: ['[class*="service"]', '[class*="Service"]'] },
203
- { name: 'features', selectors: ['[class*="feature"]', '[class*="Feature"]'] },
204
- { name: 'testimonials', selectors: ['[class*="testimonial"]', '[class*="review"]'] },
205
- { name: 'teamMembers', selectors: ['[class*="team"]', '[class*="member"]', '[class*="person"]'] },
206
- { name: 'faqItems', selectors: ['[class*="faq"]', '[class*="accordion"]', 'details'] },
207
- { name: 'pricingCards', selectors: ['[class*="pricing"]', '[class*="plan"]'] },
208
- { name: 'blogPosts', selectors: ['[class*="post"]', '[class*="article"]', 'article'] },
209
- { name: 'products', selectors: ['[class*="product"]', '[class*="Product"]'] },
210
- { name: 'categories', selectors: ['[class*="category"]', '[class*="Category"]'] }
211
- ];
212
-
213
- repeatPatterns.forEach(({ name, selectors }) => {
214
- let totalCount = 0;
215
- let visibleCount = 0;
216
-
217
- selectors.forEach(sel => {
218
- try {
219
- const elements = document.querySelectorAll(sel);
220
- elements.forEach(el => {
221
- totalCount++;
222
- if (isVisible(el)) visibleCount++;
223
- });
224
- } catch (e) { /* invalid selector */ }
225
- });
226
-
227
- if (totalCount > 0) {
228
- counts.repeatedItems.byType[name] = {
229
- total: totalCount,
230
- visible: visibleCount,
231
- hidden: totalCount - visibleCount
232
- };
233
- counts.repeatedItems.total += totalCount;
234
- }
235
- });
236
-
237
- // 4. Count navigation links
238
- const header = document.querySelector('header, [class*="header"], nav');
239
- const footer = document.querySelector('footer, [class*="footer"]');
240
-
241
- if (header) {
242
- counts.navigation.headerLinks = header.querySelectorAll('a').length;
243
- }
244
- if (footer) {
245
- counts.navigation.footerLinks = footer.querySelectorAll('a').length;
246
- }
247
- counts.navigation.allLinks = document.querySelectorAll('a').length;
248
-
249
- // 5. Count media elements
250
- counts.media.images = document.querySelectorAll('img, picture').length;
251
- counts.media.videos = document.querySelectorAll('video, iframe[src*="youtube"], iframe[src*="vimeo"]').length;
252
- counts.media.svgIcons = document.querySelectorAll('svg').length;
253
-
254
- // 6. Count interactive elements
255
- counts.interactive.buttons = document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]').length;
256
- counts.interactive.inputs = document.querySelectorAll('input, textarea, select').length;
257
- counts.interactive.forms = document.querySelectorAll('form').length;
258
-
259
- // 7. Calculate summary
260
- counts.summary = {
261
- majorSections: counts.sections.total,
262
- gridContainers: counts.grids.total,
263
- totalRepeatedItems: counts.repeatedItems.total,
264
- totalLinks: counts.navigation.allLinks,
265
- totalImages: counts.media.images,
266
- totalButtons: counts.interactive.buttons,
267
-
268
- // Provide estimates for generation
269
- recommendedItemCounts: {}
270
- };
271
-
272
- // Calculate recommended item counts per grid
273
- counts.grids.details.forEach((grid, idx) => {
274
- if (grid.visibleItems >= 3) {
275
- counts.summary.recommendedItemCounts[grid.selector] = grid.visibleItems;
276
- }
277
- });
278
-
279
- // Add repeated items recommendations
280
- Object.entries(counts.repeatedItems.byType).forEach(([type, data]) => {
281
- if (data.visible >= 2) {
282
- counts.summary.recommendedItemCounts[type] = data.visible;
283
- }
284
- });
285
-
286
- return counts;
287
- });
288
- }
289
-
290
- /**
291
- * Generate concise content summary for prompt injection
292
- * @param {object} counts - Content counts from extractContentCounts
293
- * @returns {string} Summary text
294
- */
295
- export function generateContentSummary(counts) {
296
- const lines = [
297
- '## EXACT CONTENT COUNTS (from DOM parsing)',
298
- ''
299
- ];
300
-
301
- // Sections
302
- lines.push(`### Sections: ${counts.sections.total} total`);
303
- counts.sections.details.slice(0, 10).forEach(s => {
304
- lines.push(`- ${s.selector}: ${s.childCount} children${s.visible ? '' : ' (hidden)'}`);
305
- });
306
- lines.push('');
307
-
308
- // Grids with item counts
309
- lines.push(`### Grid/Flex Containers: ${counts.grids.total} total`);
310
- counts.grids.details.slice(0, 15).forEach(g => {
311
- const visibilityNote = g.hiddenItems > 0 ? ` (+${g.hiddenItems} hidden)` : '';
312
- lines.push(`- ${g.selector}: ${g.visibleItems} visible items${visibilityNote}`);
313
- });
314
- lines.push('');
315
-
316
- // Repeated items
317
- if (Object.keys(counts.repeatedItems.byType).length > 0) {
318
- lines.push('### Repeated Items:');
319
- Object.entries(counts.repeatedItems.byType).forEach(([type, data]) => {
320
- const hiddenNote = data.hidden > 0 ? ` (+${data.hidden} hidden)` : '';
321
- lines.push(`- ${type}: ${data.visible} visible${hiddenNote}`);
322
- });
323
- lines.push('');
324
- }
325
-
326
- // Links and media
327
- lines.push('### Navigation & Media:');
328
- lines.push(`- Header links: ${counts.navigation.headerLinks}`);
329
- lines.push(`- Footer links: ${counts.navigation.footerLinks}`);
330
- lines.push(`- Images: ${counts.media.images}`);
331
- lines.push(`- SVG icons: ${counts.media.svgIcons}`);
332
- lines.push('');
333
-
334
- // Critical instruction
335
- lines.push('### GENERATION INSTRUCTION:');
336
- lines.push('When generating HTML, use EXACTLY these item counts:');
337
- Object.entries(counts.summary.recommendedItemCounts).forEach(([selector, count]) => {
338
- lines.push(`- ${selector}: ${count} items`);
339
- });
340
-
341
- return lines.join('\n');
342
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * Design Tokens Extraction Wrapper
3
- *
4
- * Wraps the Python script for extracting design tokens from screenshots.
5
- *
6
- * Usage:
7
- * import { extractDesignTokens } from './design-tokens.js';
8
- * const result = await extractDesignTokens(outputDir, cssPath);
9
- */
10
-
11
- import { spawn } from 'child_process';
12
- import path from 'path';
13
- import { fileURLToPath } from 'url';
14
-
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = path.dirname(__filename);
17
-
18
- /**
19
- * Extract design tokens from screenshots using Gemini Vision API
20
- *
21
- * @param {string} outputDir - Output directory (contains analysis/desktop/*.png)
22
- * @param {string} cssPath - Path to merged CSS file for reference
23
- * @returns {Promise<Object>} Result with { success, tokens_json, tokens_css }
24
- */
25
- export async function extractDesignTokens(outputDir, cssPath = null) {
26
- const scriptPath = path.resolve(__dirname, '../ai/extract-design-tokens.py');
27
- const screenshotsDir = path.join(outputDir, 'analysis', 'desktop');
28
-
29
- // Build args
30
- const args = [
31
- scriptPath,
32
- '--screenshots', screenshotsDir,
33
- '--output', outputDir
34
- ];
35
-
36
- if (cssPath) {
37
- args.push('--css', cssPath);
38
- }
39
-
40
- return new Promise((resolve) => {
41
- const proc = spawn('python3', args, {
42
- stdio: ['ignore', 'pipe', 'pipe'],
43
- env: { ...process.env }
44
- });
45
-
46
- let stdout = '';
47
- let stderr = '';
48
-
49
- proc.stdout.on('data', (data) => {
50
- stdout += data.toString();
51
- });
52
-
53
- proc.stderr.on('data', (data) => {
54
- stderr += data.toString();
55
- });
56
-
57
- proc.on('close', (code) => {
58
- if (code !== 0) {
59
- // Try to parse error from stdout (script outputs JSON errors)
60
- try {
61
- const errorResult = JSON.parse(stdout);
62
- resolve({
63
- success: false,
64
- error: errorResult.error || 'Unknown error',
65
- hint: errorResult.hint || null
66
- });
67
- } catch {
68
- resolve({
69
- success: false,
70
- error: stderr || `Process exited with code ${code}`
71
- });
72
- }
73
- return;
74
- }
75
-
76
- // Parse success result
77
- try {
78
- const result = JSON.parse(stdout);
79
- resolve(result);
80
- } catch (err) {
81
- resolve({
82
- success: false,
83
- error: `Failed to parse output: ${err.message}`
84
- });
85
- }
86
- });
87
-
88
- proc.on('error', (err) => {
89
- if (err.code === 'ENOENT') {
90
- resolve({
91
- success: false,
92
- error: 'Python3 not found',
93
- hint: 'Install Python 3 to enable AI token extraction'
94
- });
95
- } else {
96
- resolve({
97
- success: false,
98
- error: err.message
99
- });
100
- }
101
- });
102
- });
103
- }