@weavetab/mcp 2.5.0-beta.5 → 2.5.1

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 (160) hide show
  1. package/AGENTS.md +49 -49
  2. package/ARCHITECTURE.md +590 -590
  3. package/README.md +216 -100
  4. package/SECURITY.md +207 -207
  5. package/TROUBLESHOOTING.md +373 -365
  6. package/dist/anticaptcha/detector.js +395 -395
  7. package/dist/anticaptcha/solver.js +130 -130
  8. package/dist/cdp/bridge.js +73 -73
  9. package/dist/cdp/connector.js +46 -22
  10. package/dist/cdp/extension.js +34 -34
  11. package/dist/cdp/helpers.js +174 -174
  12. package/dist/cdp/pruner.js +195 -195
  13. package/dist/cdp/ref_cache.js +66 -66
  14. package/dist/cdp/security.js +10 -10
  15. package/dist/cdp/verify.js +57 -57
  16. package/dist/cdp/walker.js +563 -525
  17. package/dist/cli/plugin.js +26 -3
  18. package/dist/config/loader.d.ts +0 -3
  19. package/dist/config/loader.js +0 -9
  20. package/dist/index.js +0 -4
  21. package/dist/intelligence/intent.js +81 -81
  22. package/dist/intelligence/sessionMemory.js +2 -2
  23. package/dist/overlay/ghost.js +22 -22
  24. package/dist/overlay/injector.js +89 -89
  25. package/dist/plugin/extension_bridge.js +54 -54
  26. package/dist/plugin/interface.d.ts +7 -0
  27. package/dist/plugin/loader.js +3 -4
  28. package/dist/plugin/schema.d.ts +11 -0
  29. package/dist/plugin/schema.js +59 -0
  30. package/dist/security/guard.js +18 -18
  31. package/dist/sensors/actionability.js +167 -167
  32. package/dist/sensors/guard.js +29 -29
  33. package/dist/sensors/network.js +15 -17
  34. package/dist/server.d.ts +1 -1
  35. package/dist/server.js +173 -144
  36. package/dist/state/delta.js +6 -7
  37. package/dist/tools/automation.js +85 -85
  38. package/dist/tools/canvas.js +48 -25
  39. package/dist/tools/checkpoint.js +94 -35
  40. package/dist/tools/click.js +52 -41
  41. package/dist/tools/clipboard.js +80 -74
  42. package/dist/tools/design_extract.js +441 -441
  43. package/dist/tools/dialog.js +13 -13
  44. package/dist/tools/emulation.d.ts +2 -0
  45. package/dist/tools/emulation.js +7 -0
  46. package/dist/tools/fill.js +344 -347
  47. package/dist/tools/find.js +78 -52
  48. package/dist/tools/highlight.js +67 -67
  49. package/dist/tools/inspect.js +54 -25
  50. package/dist/tools/key.js +42 -35
  51. package/dist/tools/navigate.d.ts +1 -1
  52. package/dist/tools/navigate.js +35 -35
  53. package/dist/tools/office/parse.js +62 -18
  54. package/dist/tools/pdf.d.ts +9 -0
  55. package/dist/tools/pdf.js +7 -4
  56. package/dist/tools/performance.d.ts +11 -4
  57. package/dist/tools/performance.js +94 -42
  58. package/dist/tools/performance_test.d.ts +2 -2
  59. package/dist/tools/performance_test.js +69 -69
  60. package/dist/tools/pointer.js +116 -116
  61. package/dist/tools/read.js +99 -67
  62. package/dist/tools/recording.js +9 -0
  63. package/dist/tools/scrape.js +146 -146
  64. package/dist/tools/screenshot.d.ts +2 -0
  65. package/dist/tools/screenshot.js +46 -4
  66. package/dist/tools/scroll.d.ts +3 -1
  67. package/dist/tools/scroll.js +13 -15
  68. package/dist/tools/select.d.ts +3 -0
  69. package/dist/tools/select.js +131 -124
  70. package/dist/tools/snapshot.js +8 -6
  71. package/dist/tools/storage.d.ts +5 -1
  72. package/dist/tools/storage.js +16 -0
  73. package/dist/tools/tabs.d.ts +7 -5
  74. package/dist/tools/tabs.js +79 -26
  75. package/dist/tools/thought.js +8 -8
  76. package/dist/tools/type.js +274 -274
  77. package/dist/tools/type_secret.js +23 -23
  78. package/dist/tools/upload.js +3 -3
  79. package/dist/tools/video_compiler.js +9 -0
  80. package/dist/tools/wait.js +35 -35
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/docs/DOCUMENTATION.md +583 -585
  84. package/docs/PLUGINS.md +139 -121
  85. package/docs/TOOLS.md +3 -4
  86. package/docs/architecture/blind-injection.md +83 -83
  87. package/docs/architecture/overview.md +49 -49
  88. package/docs/configuration/configuration.md +39 -27
  89. package/docs/configuration/folders.md +62 -55
  90. package/docs/desktop-automation/electron-apps.md +225 -214
  91. package/docs/get-started/installation.md +1 -1
  92. package/docs/get-started/introduction.md +60 -60
  93. package/docs/get-started/quickstart.md +2 -2
  94. package/docs/get-started/setup-wizard.md +1 -1
  95. package/docs/intelligence/site-profiles.md +151 -151
  96. package/docs/tools/tools.md +582 -631
  97. package/docs/troubleshooting/trouble-loop.md +31 -31
  98. package/ext-dist/background.js +415 -415
  99. package/ext-dist/content.js +2527 -2527
  100. package/ext-dist/dashboard.html +583 -583
  101. package/ext-dist/hud/hud.js +3 -3
  102. package/ext-dist/hud/index.html +20 -20
  103. package/ext-dist/manifest.json +73 -73
  104. package/ext-dist/styles.css +223 -223
  105. package/mcp.json +796 -805
  106. package/package.json +8 -8
  107. package/server.json +23 -23
  108. package/smithery.yaml +7 -11
  109. package/dist/cdp/walker.test.d.ts +0 -2
  110. package/dist/cdp/walker.test.js +0 -172
  111. package/dist/intelligence/sessionMemory.test.d.ts +0 -2
  112. package/dist/intelligence/sessionMemory.test.js +0 -62
  113. package/dist/intelligence/trail.test.d.ts +0 -2
  114. package/dist/intelligence/trail.test.js +0 -110
  115. package/dist/state/cache.test.d.ts +0 -2
  116. package/dist/state/cache.test.js +0 -76
  117. package/dist/state/delta.test.d.ts +0 -2
  118. package/dist/state/delta.test.js +0 -279
  119. package/dist/state/groups.d.ts +0 -15
  120. package/dist/state/groups.js +0 -80
  121. package/dist/tools/accessibility.d.ts +0 -14
  122. package/dist/tools/accessibility.js +0 -32
  123. package/dist/tools/ask.d.ts +0 -14
  124. package/dist/tools/ask.js +0 -49
  125. package/dist/tools/click_and_wait.d.ts +0 -33
  126. package/dist/tools/click_and_wait.js +0 -149
  127. package/dist/tools/drag.d.ts +0 -15
  128. package/dist/tools/drag.js +0 -29
  129. package/dist/tools/geo.d.ts +0 -19
  130. package/dist/tools/geo.js +0 -29
  131. package/dist/tools/github/analyze.d.ts +0 -4
  132. package/dist/tools/github/analyze.js +0 -113
  133. package/dist/tools/github/cache.d.ts +0 -21
  134. package/dist/tools/github/cache.js +0 -28
  135. package/dist/tools/github/index.d.ts +0 -19
  136. package/dist/tools/github/index.js +0 -40
  137. package/dist/tools/github/issues.d.ts +0 -9
  138. package/dist/tools/github/issues.js +0 -88
  139. package/dist/tools/github/pr.d.ts +0 -5
  140. package/dist/tools/github/pr.js +0 -88
  141. package/dist/tools/github/rateLimiter.d.ts +0 -4
  142. package/dist/tools/github/rateLimiter.js +0 -46
  143. package/dist/tools/github/read.d.ts +0 -34
  144. package/dist/tools/github/read.js +0 -74
  145. package/dist/tools/github/repoContext.d.ts +0 -20
  146. package/dist/tools/github/repoContext.js +0 -129
  147. package/dist/tools/hover.d.ts +0 -19
  148. package/dist/tools/hover.js +0 -57
  149. package/dist/tools/integration.test.d.ts +0 -2
  150. package/dist/tools/integration.test.js +0 -96
  151. package/dist/tools/macro_compiler.d.ts +0 -7
  152. package/dist/tools/macro_compiler.js +0 -82
  153. package/dist/tools/mouse.d.ts +0 -21
  154. package/dist/tools/mouse.js +0 -62
  155. package/dist/tools/response.d.ts +0 -129
  156. package/dist/tools/response.js +0 -5
  157. package/dist/tools/wait_for.d.ts +0 -23
  158. package/dist/tools/wait_for.js +0 -30
  159. package/dist/tools/windows.d.ts +0 -22
  160. package/dist/tools/windows.js +0 -57
@@ -94,424 +94,424 @@ export async function browserExtractDesign(session, options = {}, config) {
94
94
  }
95
95
  try {
96
96
  const evalRes = await session.Runtime.evaluate({
97
- expression: `(function(targetSelector, includeAssetsFlag) {
98
- // Find target container
99
- let root = document.querySelector(targetSelector);
100
- if (!root && targetSelector !== 'body') {
101
- root = document.querySelector('body') || document.documentElement;
102
- }
103
- if (!root) root = document.documentElement;
104
-
105
- // ── 1. Meta & Environment ──
106
- const isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
107
- || document.documentElement.classList.contains('dark')
108
- || document.body?.classList.contains('dark')
109
- || (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(1') || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(2'));
110
-
111
- const meta = {
112
- url: window.location.href,
113
- title: document.title || '',
114
- extractedAt: new Date().toISOString(),
115
- selector: targetSelector,
116
- viewport: {
117
- width: window.innerWidth,
118
- height: window.innerHeight
119
- },
120
- isDarkMode: !!isDark
121
- };
122
-
123
- // ── 2. CSS Custom Properties (:root / html / body) ──
124
- const cssVars = {};
125
- const rootStyles = window.getComputedStyle(document.documentElement);
126
- const bodyStyles = document.body ? window.getComputedStyle(document.body) : null;
127
-
128
- // Extract declared variables from styleSheets
129
- Array.from(document.styleSheets).forEach(sheet => {
130
- try {
131
- Array.from(sheet.cssRules || []).forEach(rule => {
132
- if (rule.style && (rule.selectorText === ':root' || rule.selectorText === 'html' || rule.selectorText === 'body' || rule.selectorText === '.dark' || rule.selectorText === '[data-theme="dark"]')) {
133
- for (let i = 0; i < rule.style.length; i++) {
134
- const propName = rule.style[i];
135
- if (propName.startsWith('--')) {
136
- const val = rootStyles.getPropertyValue(propName).trim() || (bodyStyles ? bodyStyles.getPropertyValue(propName).trim() : '');
137
- if (val) cssVars[propName] = val;
138
- }
139
- }
140
- }
141
- });
142
- } catch (_) {
143
- // CORS stylesheet security restriction — ignore
144
- }
145
- });
146
-
147
- // ── Helper: Color Parsing & Normalization ──
148
- function normalizeColor(c) {
149
- if (!c || c === 'rgba(0, 0, 0, 0)' || c === 'transparent') return null;
150
- return c.trim();
151
- }
152
-
153
- // ── 3. Token Harvesters ──
154
- const bgColors = new Set();
155
- const textColors = new Set();
156
- const borderColors = new Set();
157
- const fontFamilies = new Set();
158
- const shadows = new Set();
159
- const radii = new Set();
160
- const blurs = new Set();
161
- const displayTypes = new Set();
162
- const gaps = new Set();
163
- const paddings = new Set();
164
-
165
- const headings = {};
166
- let bodyTypography = {
167
- fontFamily: '',
168
- fontSize: '',
169
- fontWeight: '',
170
- lineHeight: '',
171
- color: ''
172
- };
173
-
174
- if (document.body) {
175
- const bs = window.getComputedStyle(document.body);
176
- bodyTypography = {
177
- fontFamily: bs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
178
- fontSize: bs.fontSize,
179
- fontWeight: bs.fontWeight,
180
- lineHeight: bs.lineHeight,
181
- color: bs.color
182
- };
183
- if (bs.fontFamily) fontFamilies.add(bodyTypography.fontFamily);
184
- }
185
-
186
- // Collect heading styles
187
- ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
188
- const el = root.querySelector(tag);
189
- if (el) {
190
- const hs = window.getComputedStyle(el);
191
- headings[tag] = {
192
- fontFamily: hs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
193
- fontSize: hs.fontSize,
194
- fontWeight: hs.fontWeight,
195
- lineHeight: hs.lineHeight,
196
- letterSpacing: hs.letterSpacing,
197
- color: hs.color
198
- };
199
- if (hs.fontFamily) fontFamilies.add(headings[tag].fontFamily);
200
- }
201
- });
202
-
203
- let totalNodes = 0;
204
- let svgCount = 0;
205
-
206
- function walkTokens(node) {
207
- if (!node || node.nodeType !== 1) return;
208
- if (node.id === 'wt-shadow-host' || node.hasAttribute('data-wt-ignore')) return;
209
-
210
- totalNodes++;
211
- const tag = node.tagName.toLowerCase();
212
- if (['script', 'style', 'noscript', 'iframe'].includes(tag)) return;
213
- if (tag === 'svg') svgCount++;
214
-
215
- const s = window.getComputedStyle(node);
216
- if (s.display === 'none' || s.visibility === 'hidden') return;
217
-
218
- const bg = normalizeColor(s.backgroundColor);
219
- if (bg) bgColors.add(bg);
220
-
221
- const col = normalizeColor(s.color);
222
- if (col) textColors.add(col);
223
-
224
- const bc = normalizeColor(s.borderColor);
225
- if (bc && s.borderWidth !== '0px') borderColors.add(bc);
226
-
227
- if (s.fontFamily) {
228
- const f = s.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
229
- if (f && f !== 'inherit') fontFamilies.add(f);
230
- }
231
-
232
- if (s.boxShadow && s.boxShadow !== 'none') shadows.add(s.boxShadow);
233
- if (s.borderRadius && s.borderRadius !== '0px') radii.add(s.borderRadius);
234
- if (s.backdropFilter && s.backdropFilter !== 'none') blurs.add(s.backdropFilter);
235
-
236
- if (s.display) displayTypes.add(s.display);
237
- if (s.gap && s.gap !== 'normal' && s.gap !== '0px') gaps.add(s.gap);
238
- if (s.padding && s.padding !== '0px') paddings.add(s.padding);
239
-
240
- for (let i = 0; i < node.children.length; i++) {
241
- walkTokens(node.children[i]);
242
- }
243
- }
244
-
245
- walkTokens(root);
246
-
247
- // ── 4. Intelligent DOM Normalizer & Beautifier ──
248
- const clone = root.cloneNode(true);
249
-
250
- // A. Remove useless elements
251
- clone.querySelectorAll('script, style, noscript, iframe, link, #wt-shadow-host, [data-wt-ignore]').forEach(el => el.remove());
252
-
253
- // B. Handle Framer Motion / SplitText Spans & Hidden Duplicates
254
- function normalizeDomTree(el) {
255
- if (!el || el.nodeType !== 1) return;
256
-
257
- const tag = el.tagName.toLowerCase();
258
-
259
- // 1. Check for visually hidden / aria-hidden text duplication (Linear / Framer Motion pattern)
260
- const srOnlyEl = el.querySelector('.visuallyHidden, [class*="visuallyHidden"], .sr-only, [class*="sr-only"]');
261
- const ariaHiddenEl = el.querySelector('[aria-hidden="true"]');
262
- if (srOnlyEl && (ariaHiddenEl || el.querySelectorAll('span').length > 2)) {
263
- const canonicalText = srOnlyEl.textContent.trim();
264
- if (canonicalText) {
265
- el.innerHTML = canonicalText;
266
- }
267
- } else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'button', 'a'].includes(tag)) {
268
- // If all children are spans with fragmented text or animation styles, unsplit them
269
- const spans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
270
- const hasOtherContent = Array.from(el.children).some(c => !['span', 'br', 'wbr'].includes(c.tagName.toLowerCase()));
271
-
272
- if (spans.length >= 2 && !hasOtherContent) {
273
- // Check if spans have duplicate text due to responsive breakpoints (e.g. show-mobile vs hide-mobile)
274
- const mobileSpans = spans.filter(s => s.className && s.className.includes('mobile'));
275
- const hideMobileSpans = spans.filter(s => s.className && s.className.includes('hide-mobile'));
276
-
277
- if (hideMobileSpans.length > 0) {
278
- // Desktop active: keep hide-mobile / remove show-mobile
279
- mobileSpans.filter(s => s.className.includes('show-mobile')).forEach(s => s.remove());
280
- }
281
-
282
- // If remaining spans are just split words, join them cleanly
283
- const remainingSpans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
284
- const textContent = remainingSpans.map(s => s.textContent).join(' ').replace(/\\s+/g, ' ').trim();
285
- if (textContent) {
286
- el.innerHTML = textContent;
287
- }
288
- }
289
- }
290
-
291
- // 2. Clean inline styles (remove animation / matrix / opacity: 1 fluff)
292
- if (el.hasAttribute('style')) {
293
- const rawStyle = el.getAttribute('style') || '';
294
- const meaningfulStyles = [];
295
-
296
- rawStyle.split(';').forEach(rule => {
297
- const trimmed = rule.trim();
298
- if (!trimmed) return;
299
- const [prop, val] = trimmed.split(':').map(s => s.trim());
300
- if (!prop || !val) return;
301
-
302
- // Skip animation artifacts
303
- if (
304
- (prop === 'opacity' && val === '1') ||
305
- (prop === 'filter' && (val === 'blur(0px)' || val === 'none')) ||
306
- (prop === 'transform' && (val.includes('translateY(0%)') || val.includes('translate3d(0, 0, 0)') || val === 'none' || val.includes('matrix(1, 0, 0, 1, 0, 0)'))) ||
307
- (prop === 'will-change') ||
308
- (prop === 'perspective') ||
309
- (prop === 'backface-visibility')
310
- ) {
311
- return;
312
- }
313
-
314
- // Keep intentional layout styles / CSS variables / spacer heights
315
- if (prop.startsWith('--height') || prop === 'height') {
316
- meaningfulStyles.push('height: ' + val);
317
- } else if (prop.startsWith('--gap') || prop === 'gap') {
318
- meaningfulStyles.push('gap: ' + val);
319
- } else if (!prop.startsWith('--')) {
320
- meaningfulStyles.push(prop + ': ' + val);
321
- }
322
- });
323
-
324
- if (meaningfulStyles.length > 0) {
325
- el.setAttribute('style', meaningfulStyles.join('; '));
326
- } else {
327
- el.removeAttribute('style');
328
- }
329
- }
330
-
331
- // 3. Clean and normalize class names (strip random hashes, keep semantic names)
332
- if (el.hasAttribute('class')) {
333
- const rawClasses = (el.getAttribute('class') || '').split(/\\s+/);
334
- const cleanClasses = [];
335
-
336
- rawClasses.forEach(cls => {
337
- const c = cls.trim();
338
- if (!c) return;
339
- // Skip internal / ephemeral classes
340
- if (c.startsWith('sc-') || c === 'show-mobile' || c.startsWith('wt-')) return;
341
-
342
- // De-hash CSS module names: e.g. "ImRUSq_justify-space-between" -> "justify-between"
343
- if (c.includes('_')) {
344
- const parts = c.split('_');
345
- const semanticPart = parts[parts.length - 1];
346
- if (semanticPart && semanticPart.length > 2 && !semanticPart.match(/^[a-f0-9]{4,}$/i)) {
347
- let normalized = semanticPart
348
- .replace('justify-space-between', 'justify-between')
349
- .replace('align-center', 'items-center');
350
- if (normalized === 'root') normalized = 'spacer';
351
- cleanClasses.push(normalized);
352
- return;
353
- }
354
- }
355
-
356
- // Keep standard semantic / utility class
357
- cleanClasses.push(c);
358
- });
359
-
360
- const uniqueClasses = Array.from(new Set(cleanClasses));
361
- if (uniqueClasses.length > 0) {
362
- el.setAttribute('class', uniqueClasses.join(' '));
363
- } else {
364
- el.removeAttribute('class');
365
- }
366
- }
367
-
368
- // 4. Remove internal / framework attributes
369
- ['data-wt-id', 'data-wt-secret', 'jsaction', 'jscontroller', 'jsname', 'jsmodel', 'data-sentry-component', 'data-sentry-source-file'].forEach(attr => {
370
- el.removeAttribute(attr);
371
- });
372
-
373
- // 5. Clean SVGs
374
- if (el.tagName.toLowerCase() === 'svg' && !includeAssetsFlag) {
375
- el.innerHTML = '<!-- svg icon -->';
376
- }
377
-
378
- // Recurse children
379
- const children = Array.from(el.children);
380
- for (let i = 0; i < children.length; i++) {
381
- normalizeDomTree(children[i]);
382
- }
383
-
384
- // 6. Remove empty spacer divs with no styles or classes
385
- if (tag === 'div' && el.children.length === 0 && !el.textContent.trim() && !el.hasAttribute('style') && !el.hasAttribute('class')) {
386
- el.remove();
387
- }
388
- }
389
-
390
- normalizeDomTree(clone);
391
-
392
- // ── 5. Full Real CSS Extraction ──
393
- const collectedStyleBlocks = [];
394
- const keyframeMap = new Map();
395
- const mediaQueryMap = new Map();
396
- const fontFaceList = [];
397
- const classRules = [];
398
-
399
- // A. Extract all <style> tags (includes Tailwind, Emotion, styled-components, Next.js CSS)
400
- document.querySelectorAll('style').forEach(st => {
401
- const text = (st.textContent || '').trim();
402
- if (text) {
403
- collectedStyleBlocks.push(text);
404
- }
405
- });
406
-
407
- // B. Extract accessible CSS rules from document.styleSheets
408
- Array.from(document.styleSheets).forEach(sheet => {
409
- try {
410
- Array.from(sheet.cssRules || []).forEach(rule => {
411
- const ruleText = rule.cssText;
412
- if (!ruleText) return;
413
-
414
- if (rule.type === 7 || rule.constructor.name === 'CSSKeyframesRule') {
415
- keyframeMap.set(rule.name || ruleText.slice(0, 30), ruleText);
416
- } else if (rule.type === 5 || rule.constructor.name === 'CSSFontFaceRule') {
417
- fontFaceList.push(ruleText);
418
- } else if (rule.type === 4 || rule.constructor.name === 'CSSMediaRule') {
419
- mediaQueryMap.set(rule.conditionText || ruleText.slice(0, 30), ruleText);
420
- } else if (rule.style) {
421
- const sel = rule.selectorText || '';
422
- if (sel && !sel.includes('wt-') && !sel.includes('chrome-extension')) {
423
- classRules.push(ruleText);
424
- }
425
- }
426
- });
427
- } catch (_) {
428
- // Cross-origin stylesheet security restriction — ignored
429
- }
430
- });
431
-
432
- // C. Build formatted comprehensive styles.css
433
- let fullCss = '/* ==========================================================================\\n';
434
- fullCss += ' Design Blueprint Stylesheet: ' + (document.title || window.location.hostname) + '\\n';
435
- fullCss += ' Extracted via Weavetab browser_extract_design\\n';
436
- fullCss += ' ========================================================================== */\\n\\n';
437
-
438
- // 1. CSS Custom Properties
439
- fullCss += '/* ── 1. CSS Custom Properties (:root / themes) ── */\\n:root {\\n';
440
- Object.entries(cssVars).forEach(([k, v]) => {
441
- fullCss += ' ' + k + ': ' + v + ';\\n';
442
- });
443
- fullCss += '}\\n\\n';
444
-
445
- // 2. Font Faces
446
- if (fontFaceList.length > 0) {
447
- fullCss += '/* ── 2. Font Faces ── */\\n';
448
- Array.from(new Set(fontFaceList)).slice(0, 10).forEach(ff => {
449
- fullCss += ff + '\\n\\n';
450
- });
451
- }
452
-
453
- // 3. Keyframe Animations
454
- if (keyframeMap.size > 0) {
455
- fullCss += '/* ── 3. Keyframe Animations ── */\\n';
456
- Array.from(keyframeMap.values()).forEach(kf => {
457
- fullCss += kf + '\\n\\n';
458
- });
459
- }
460
-
461
- // 4. Stylesheet Rules / Style Blocks
462
- fullCss += '/* ── 4. Layout & Component Styles ── */\\n';
463
- if (collectedStyleBlocks.length > 0) {
464
- collectedStyleBlocks.forEach(sb => {
465
- fullCss += sb + '\\n\\n';
466
- });
467
- } else if (classRules.length > 0) {
468
- Array.from(new Set(classRules)).forEach(cr => {
469
- fullCss += cr + '\\n';
470
- });
471
- }
472
-
473
- // 5. Responsive Media Queries
474
- if (mediaQueryMap.size > 0 && collectedStyleBlocks.length === 0) {
475
- fullCss += '\\n/* ── 5. Responsive Media Queries ── */\\n';
476
- Array.from(mediaQueryMap.values()).forEach(mq => {
477
- fullCss += mq + '\\n\\n';
478
- });
479
- }
480
-
481
- return {
482
- tokens: {
483
- meta,
484
- cssVariables: cssVars,
485
- palette: {
486
- backgrounds: Array.from(bgColors).slice(0, 25),
487
- text: Array.from(textColors).slice(0, 20),
488
- borders: Array.from(borderColors).slice(0, 15),
489
- accents: Array.from(bgColors).filter(c => !c.includes('255, 255, 255') && !c.includes('0, 0, 0') && !c.includes('rgba(0, 0, 0')).slice(0, 10)
490
- },
491
- typography: {
492
- fontFamilies: Array.from(fontFamilies),
493
- headings,
494
- body: bodyTypography
495
- },
496
- effects: {
497
- shadows: Array.from(shadows).slice(0, 10),
498
- radii: Array.from(radii).slice(0, 10),
499
- backdropBlurs: Array.from(blurs).slice(0, 5)
500
- },
501
- layout: {
502
- displayTypes: Array.from(displayTypes),
503
- gapValues: Array.from(gaps).slice(0, 10),
504
- paddingValues: Array.from(paddings).slice(0, 10)
505
- }
506
- },
507
- rawHtml: clone.outerHTML,
508
- css: fullCss,
509
- metrics: {
510
- nodeCount: totalNodes,
511
- svgCount,
512
- htmlBytes: clone.outerHTML.length
513
- }
514
- };
97
+ expression: `(function(targetSelector, includeAssetsFlag) {
98
+ // Find target container
99
+ let root = document.querySelector(targetSelector);
100
+ if (!root && targetSelector !== 'body') {
101
+ root = document.querySelector('body') || document.documentElement;
102
+ }
103
+ if (!root) root = document.documentElement;
104
+
105
+ // ── 1. Meta & Environment ──
106
+ const isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
107
+ || document.documentElement.classList.contains('dark')
108
+ || document.body?.classList.contains('dark')
109
+ || (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(1') || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(2'));
110
+
111
+ const meta = {
112
+ url: window.location.href,
113
+ title: document.title || '',
114
+ extractedAt: new Date().toISOString(),
115
+ selector: targetSelector,
116
+ viewport: {
117
+ width: window.innerWidth,
118
+ height: window.innerHeight
119
+ },
120
+ isDarkMode: !!isDark
121
+ };
122
+
123
+ // ── 2. CSS Custom Properties (:root / html / body) ──
124
+ const cssVars = {};
125
+ const rootStyles = window.getComputedStyle(document.documentElement);
126
+ const bodyStyles = document.body ? window.getComputedStyle(document.body) : null;
127
+
128
+ // Extract declared variables from styleSheets
129
+ Array.from(document.styleSheets).forEach(sheet => {
130
+ try {
131
+ Array.from(sheet.cssRules || []).forEach(rule => {
132
+ if (rule.style && (rule.selectorText === ':root' || rule.selectorText === 'html' || rule.selectorText === 'body' || rule.selectorText === '.dark' || rule.selectorText === '[data-theme="dark"]')) {
133
+ for (let i = 0; i < rule.style.length; i++) {
134
+ const propName = rule.style[i];
135
+ if (propName.startsWith('--')) {
136
+ const val = rootStyles.getPropertyValue(propName).trim() || (bodyStyles ? bodyStyles.getPropertyValue(propName).trim() : '');
137
+ if (val) cssVars[propName] = val;
138
+ }
139
+ }
140
+ }
141
+ });
142
+ } catch (_) {
143
+ // CORS stylesheet security restriction — ignore
144
+ }
145
+ });
146
+
147
+ // ── Helper: Color Parsing & Normalization ──
148
+ function normalizeColor(c) {
149
+ if (!c || c === 'rgba(0, 0, 0, 0)' || c === 'transparent') return null;
150
+ return c.trim();
151
+ }
152
+
153
+ // ── 3. Token Harvesters ──
154
+ const bgColors = new Set();
155
+ const textColors = new Set();
156
+ const borderColors = new Set();
157
+ const fontFamilies = new Set();
158
+ const shadows = new Set();
159
+ const radii = new Set();
160
+ const blurs = new Set();
161
+ const displayTypes = new Set();
162
+ const gaps = new Set();
163
+ const paddings = new Set();
164
+
165
+ const headings = {};
166
+ let bodyTypography = {
167
+ fontFamily: '',
168
+ fontSize: '',
169
+ fontWeight: '',
170
+ lineHeight: '',
171
+ color: ''
172
+ };
173
+
174
+ if (document.body) {
175
+ const bs = window.getComputedStyle(document.body);
176
+ bodyTypography = {
177
+ fontFamily: bs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
178
+ fontSize: bs.fontSize,
179
+ fontWeight: bs.fontWeight,
180
+ lineHeight: bs.lineHeight,
181
+ color: bs.color
182
+ };
183
+ if (bs.fontFamily) fontFamilies.add(bodyTypography.fontFamily);
184
+ }
185
+
186
+ // Collect heading styles
187
+ ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
188
+ const el = root.querySelector(tag);
189
+ if (el) {
190
+ const hs = window.getComputedStyle(el);
191
+ headings[tag] = {
192
+ fontFamily: hs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
193
+ fontSize: hs.fontSize,
194
+ fontWeight: hs.fontWeight,
195
+ lineHeight: hs.lineHeight,
196
+ letterSpacing: hs.letterSpacing,
197
+ color: hs.color
198
+ };
199
+ if (hs.fontFamily) fontFamilies.add(headings[tag].fontFamily);
200
+ }
201
+ });
202
+
203
+ let totalNodes = 0;
204
+ let svgCount = 0;
205
+
206
+ function walkTokens(node) {
207
+ if (!node || node.nodeType !== 1) return;
208
+ if (node.id === 'wt-shadow-host' || node.hasAttribute('data-wt-ignore')) return;
209
+
210
+ totalNodes++;
211
+ const tag = node.tagName.toLowerCase();
212
+ if (['script', 'style', 'noscript', 'iframe'].includes(tag)) return;
213
+ if (tag === 'svg') svgCount++;
214
+
215
+ const s = window.getComputedStyle(node);
216
+ if (s.display === 'none' || s.visibility === 'hidden') return;
217
+
218
+ const bg = normalizeColor(s.backgroundColor);
219
+ if (bg) bgColors.add(bg);
220
+
221
+ const col = normalizeColor(s.color);
222
+ if (col) textColors.add(col);
223
+
224
+ const bc = normalizeColor(s.borderColor);
225
+ if (bc && s.borderWidth !== '0px') borderColors.add(bc);
226
+
227
+ if (s.fontFamily) {
228
+ const f = s.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
229
+ if (f && f !== 'inherit') fontFamilies.add(f);
230
+ }
231
+
232
+ if (s.boxShadow && s.boxShadow !== 'none') shadows.add(s.boxShadow);
233
+ if (s.borderRadius && s.borderRadius !== '0px') radii.add(s.borderRadius);
234
+ if (s.backdropFilter && s.backdropFilter !== 'none') blurs.add(s.backdropFilter);
235
+
236
+ if (s.display) displayTypes.add(s.display);
237
+ if (s.gap && s.gap !== 'normal' && s.gap !== '0px') gaps.add(s.gap);
238
+ if (s.padding && s.padding !== '0px') paddings.add(s.padding);
239
+
240
+ for (let i = 0; i < node.children.length; i++) {
241
+ walkTokens(node.children[i]);
242
+ }
243
+ }
244
+
245
+ walkTokens(root);
246
+
247
+ // ── 4. Intelligent DOM Normalizer & Beautifier ──
248
+ const clone = root.cloneNode(true);
249
+
250
+ // A. Remove useless elements
251
+ clone.querySelectorAll('script, style, noscript, iframe, link, #wt-shadow-host, [data-wt-ignore]').forEach(el => el.remove());
252
+
253
+ // B. Handle Framer Motion / SplitText Spans & Hidden Duplicates
254
+ function normalizeDomTree(el) {
255
+ if (!el || el.nodeType !== 1) return;
256
+
257
+ const tag = el.tagName.toLowerCase();
258
+
259
+ // 1. Check for visually hidden / aria-hidden text duplication (Linear / Framer Motion pattern)
260
+ const srOnlyEl = el.querySelector('.visuallyHidden, [class*="visuallyHidden"], .sr-only, [class*="sr-only"]');
261
+ const ariaHiddenEl = el.querySelector('[aria-hidden="true"]');
262
+ if (srOnlyEl && (ariaHiddenEl || el.querySelectorAll('span').length > 2)) {
263
+ const canonicalText = srOnlyEl.textContent.trim();
264
+ if (canonicalText) {
265
+ el.innerHTML = canonicalText;
266
+ }
267
+ } else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'button', 'a'].includes(tag)) {
268
+ // If all children are spans with fragmented text or animation styles, unsplit them
269
+ const spans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
270
+ const hasOtherContent = Array.from(el.children).some(c => !['span', 'br', 'wbr'].includes(c.tagName.toLowerCase()));
271
+
272
+ if (spans.length >= 2 && !hasOtherContent) {
273
+ // Check if spans have duplicate text due to responsive breakpoints (e.g. show-mobile vs hide-mobile)
274
+ const mobileSpans = spans.filter(s => s.className && s.className.includes('mobile'));
275
+ const hideMobileSpans = spans.filter(s => s.className && s.className.includes('hide-mobile'));
276
+
277
+ if (hideMobileSpans.length > 0) {
278
+ // Desktop active: keep hide-mobile / remove show-mobile
279
+ mobileSpans.filter(s => s.className.includes('show-mobile')).forEach(s => s.remove());
280
+ }
281
+
282
+ // If remaining spans are just split words, join them cleanly
283
+ const remainingSpans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
284
+ const textContent = remainingSpans.map(s => s.textContent).join(' ').replace(/\\s+/g, ' ').trim();
285
+ if (textContent) {
286
+ el.innerHTML = textContent;
287
+ }
288
+ }
289
+ }
290
+
291
+ // 2. Clean inline styles (remove animation / matrix / opacity: 1 fluff)
292
+ if (el.hasAttribute('style')) {
293
+ const rawStyle = el.getAttribute('style') || '';
294
+ const meaningfulStyles = [];
295
+
296
+ rawStyle.split(';').forEach(rule => {
297
+ const trimmed = rule.trim();
298
+ if (!trimmed) return;
299
+ const [prop, val] = trimmed.split(':').map(s => s.trim());
300
+ if (!prop || !val) return;
301
+
302
+ // Skip animation artifacts
303
+ if (
304
+ (prop === 'opacity' && val === '1') ||
305
+ (prop === 'filter' && (val === 'blur(0px)' || val === 'none')) ||
306
+ (prop === 'transform' && (val.includes('translateY(0%)') || val.includes('translate3d(0, 0, 0)') || val === 'none' || val.includes('matrix(1, 0, 0, 1, 0, 0)'))) ||
307
+ (prop === 'will-change') ||
308
+ (prop === 'perspective') ||
309
+ (prop === 'backface-visibility')
310
+ ) {
311
+ return;
312
+ }
313
+
314
+ // Keep intentional layout styles / CSS variables / spacer heights
315
+ if (prop.startsWith('--height') || prop === 'height') {
316
+ meaningfulStyles.push('height: ' + val);
317
+ } else if (prop.startsWith('--gap') || prop === 'gap') {
318
+ meaningfulStyles.push('gap: ' + val);
319
+ } else if (!prop.startsWith('--')) {
320
+ meaningfulStyles.push(prop + ': ' + val);
321
+ }
322
+ });
323
+
324
+ if (meaningfulStyles.length > 0) {
325
+ el.setAttribute('style', meaningfulStyles.join('; '));
326
+ } else {
327
+ el.removeAttribute('style');
328
+ }
329
+ }
330
+
331
+ // 3. Clean and normalize class names (strip random hashes, keep semantic names)
332
+ if (el.hasAttribute('class')) {
333
+ const rawClasses = (el.getAttribute('class') || '').split(/\\s+/);
334
+ const cleanClasses = [];
335
+
336
+ rawClasses.forEach(cls => {
337
+ const c = cls.trim();
338
+ if (!c) return;
339
+ // Skip internal / ephemeral classes
340
+ if (c.startsWith('sc-') || c === 'show-mobile' || c.startsWith('wt-')) return;
341
+
342
+ // De-hash CSS module names: e.g. "ImRUSq_justify-space-between" -> "justify-between"
343
+ if (c.includes('_')) {
344
+ const parts = c.split('_');
345
+ const semanticPart = parts[parts.length - 1];
346
+ if (semanticPart && semanticPart.length > 2 && !semanticPart.match(/^[a-f0-9]{4,}$/i)) {
347
+ let normalized = semanticPart
348
+ .replace('justify-space-between', 'justify-between')
349
+ .replace('align-center', 'items-center');
350
+ if (normalized === 'root') normalized = 'spacer';
351
+ cleanClasses.push(normalized);
352
+ return;
353
+ }
354
+ }
355
+
356
+ // Keep standard semantic / utility class
357
+ cleanClasses.push(c);
358
+ });
359
+
360
+ const uniqueClasses = Array.from(new Set(cleanClasses));
361
+ if (uniqueClasses.length > 0) {
362
+ el.setAttribute('class', uniqueClasses.join(' '));
363
+ } else {
364
+ el.removeAttribute('class');
365
+ }
366
+ }
367
+
368
+ // 4. Remove internal / framework attributes
369
+ ['data-wt-id', 'data-wt-secret', 'jsaction', 'jscontroller', 'jsname', 'jsmodel', 'data-sentry-component', 'data-sentry-source-file'].forEach(attr => {
370
+ el.removeAttribute(attr);
371
+ });
372
+
373
+ // 5. Clean SVGs
374
+ if (el.tagName.toLowerCase() === 'svg' && !includeAssetsFlag) {
375
+ el.innerHTML = '<!-- svg icon -->';
376
+ }
377
+
378
+ // Recurse children
379
+ const children = Array.from(el.children);
380
+ for (let i = 0; i < children.length; i++) {
381
+ normalizeDomTree(children[i]);
382
+ }
383
+
384
+ // 6. Remove empty spacer divs with no styles or classes
385
+ if (tag === 'div' && el.children.length === 0 && !el.textContent.trim() && !el.hasAttribute('style') && !el.hasAttribute('class')) {
386
+ el.remove();
387
+ }
388
+ }
389
+
390
+ normalizeDomTree(clone);
391
+
392
+ // ── 5. Full Real CSS Extraction ──
393
+ const collectedStyleBlocks = [];
394
+ const keyframeMap = new Map();
395
+ const mediaQueryMap = new Map();
396
+ const fontFaceList = [];
397
+ const classRules = [];
398
+
399
+ // A. Extract all <style> tags (includes Tailwind, Emotion, styled-components, Next.js CSS)
400
+ document.querySelectorAll('style').forEach(st => {
401
+ const text = (st.textContent || '').trim();
402
+ if (text) {
403
+ collectedStyleBlocks.push(text);
404
+ }
405
+ });
406
+
407
+ // B. Extract accessible CSS rules from document.styleSheets
408
+ Array.from(document.styleSheets).forEach(sheet => {
409
+ try {
410
+ Array.from(sheet.cssRules || []).forEach(rule => {
411
+ const ruleText = rule.cssText;
412
+ if (!ruleText) return;
413
+
414
+ if (rule.type === 7 || rule.constructor.name === 'CSSKeyframesRule') {
415
+ keyframeMap.set(rule.name || ruleText.slice(0, 30), ruleText);
416
+ } else if (rule.type === 5 || rule.constructor.name === 'CSSFontFaceRule') {
417
+ fontFaceList.push(ruleText);
418
+ } else if (rule.type === 4 || rule.constructor.name === 'CSSMediaRule') {
419
+ mediaQueryMap.set(rule.conditionText || ruleText.slice(0, 30), ruleText);
420
+ } else if (rule.style) {
421
+ const sel = rule.selectorText || '';
422
+ if (sel && !sel.includes('wt-') && !sel.includes('chrome-extension')) {
423
+ classRules.push(ruleText);
424
+ }
425
+ }
426
+ });
427
+ } catch (_) {
428
+ // Cross-origin stylesheet security restriction — ignored
429
+ }
430
+ });
431
+
432
+ // C. Build formatted comprehensive styles.css
433
+ let fullCss = '/* ==========================================================================\\n';
434
+ fullCss += ' Design Blueprint Stylesheet: ' + (document.title || window.location.hostname) + '\\n';
435
+ fullCss += ' Extracted via Weavetab browser_extract_design\\n';
436
+ fullCss += ' ========================================================================== */\\n\\n';
437
+
438
+ // 1. CSS Custom Properties
439
+ fullCss += '/* ── 1. CSS Custom Properties (:root / themes) ── */\\n:root {\\n';
440
+ Object.entries(cssVars).forEach(([k, v]) => {
441
+ fullCss += ' ' + k + ': ' + v + ';\\n';
442
+ });
443
+ fullCss += '}\\n\\n';
444
+
445
+ // 2. Font Faces
446
+ if (fontFaceList.length > 0) {
447
+ fullCss += '/* ── 2. Font Faces ── */\\n';
448
+ Array.from(new Set(fontFaceList)).slice(0, 10).forEach(ff => {
449
+ fullCss += ff + '\\n\\n';
450
+ });
451
+ }
452
+
453
+ // 3. Keyframe Animations
454
+ if (keyframeMap.size > 0) {
455
+ fullCss += '/* ── 3. Keyframe Animations ── */\\n';
456
+ Array.from(keyframeMap.values()).forEach(kf => {
457
+ fullCss += kf + '\\n\\n';
458
+ });
459
+ }
460
+
461
+ // 4. Stylesheet Rules / Style Blocks
462
+ fullCss += '/* ── 4. Layout & Component Styles ── */\\n';
463
+ if (collectedStyleBlocks.length > 0) {
464
+ collectedStyleBlocks.forEach(sb => {
465
+ fullCss += sb + '\\n\\n';
466
+ });
467
+ } else if (classRules.length > 0) {
468
+ Array.from(new Set(classRules)).forEach(cr => {
469
+ fullCss += cr + '\\n';
470
+ });
471
+ }
472
+
473
+ // 5. Responsive Media Queries
474
+ if (mediaQueryMap.size > 0 && collectedStyleBlocks.length === 0) {
475
+ fullCss += '\\n/* ── 5. Responsive Media Queries ── */\\n';
476
+ Array.from(mediaQueryMap.values()).forEach(mq => {
477
+ fullCss += mq + '\\n\\n';
478
+ });
479
+ }
480
+
481
+ return {
482
+ tokens: {
483
+ meta,
484
+ cssVariables: cssVars,
485
+ palette: {
486
+ backgrounds: Array.from(bgColors).slice(0, 25),
487
+ text: Array.from(textColors).slice(0, 20),
488
+ borders: Array.from(borderColors).slice(0, 15),
489
+ accents: Array.from(bgColors).filter(c => !c.includes('255, 255, 255') && !c.includes('0, 0, 0') && !c.includes('rgba(0, 0, 0')).slice(0, 10)
490
+ },
491
+ typography: {
492
+ fontFamilies: Array.from(fontFamilies),
493
+ headings,
494
+ body: bodyTypography
495
+ },
496
+ effects: {
497
+ shadows: Array.from(shadows).slice(0, 10),
498
+ radii: Array.from(radii).slice(0, 10),
499
+ backdropBlurs: Array.from(blurs).slice(0, 5)
500
+ },
501
+ layout: {
502
+ displayTypes: Array.from(displayTypes),
503
+ gapValues: Array.from(gaps).slice(0, 10),
504
+ paddingValues: Array.from(paddings).slice(0, 10)
505
+ }
506
+ },
507
+ rawHtml: clone.outerHTML,
508
+ css: fullCss,
509
+ metrics: {
510
+ nodeCount: totalNodes,
511
+ svgCount,
512
+ htmlBytes: clone.outerHTML.length
513
+ }
514
+ };
515
515
  })(${JSON.stringify(selector)}, ${JSON.stringify(includeAssets)})`,
516
516
  returnByValue: true
517
517
  });
@@ -581,29 +581,29 @@ export async function browserExtractDesign(session, options = {}, config) {
581
581
  }
582
582
  // Generate Markdown README Guide for AI Agent
583
583
  const readmePath = path.join(targetDir, "README.md");
584
- const readmeContent = `# Design Blueprint: ${tokens.meta.title || tokens.meta.url}
585
-
586
- - **Extracted At**: ${tokens.meta.extractedAt}
587
- - **Source URL**: ${tokens.meta.url}
588
- - **Theme**: ${tokens.meta.isDarkMode ? "Dark Mode" : "Light Mode"}
589
- - **Selector**: \`${tokens.meta.selector}\`
590
-
591
- ## Design System Tokens
592
- - **Font Families**: ${tokens.typography.fontFamilies.join(", ") || "System Default"}
593
- - **Primary Background Colors**: \`${tokens.palette.backgrounds.slice(0, 5).join("`, `")}\`
594
- - **Text Colors**: \`${tokens.palette.text.slice(0, 5).join("`, `")}\`
595
- - **Border Radii**: \`${tokens.effects.radii.slice(0, 5).join("`, `")}\`
596
- - **Shadows**: ${tokens.effects.shadows.length} extracted shadow styles
597
-
598
- ## Included Files
599
- 1. \`tokens.json\` — Full machine-readable design tokens, CSS variables, typography hierarchy, and colors.
600
- 2. \`template.html\` — Clean, semantic, beautified HTML skeleton (${metrics.nodeCount} elements, ${metrics.svgCount} SVGs).
601
- 3. \`styles.css\` — Extracted CSS variables and scoped styles.
602
- ${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` configuration.\n" : ""}
603
- ## Agent Implementation Instructions
604
- - Open \`tokens.json\` to copy exact hex colors, typography weights, and border radius tokens.
605
- - Open \`template.html\` to review semantic structure, SVGs, and component layout hierarchy.
606
- - Recreate the target component in your project using these exact tokens.
584
+ const readmeContent = `# Design Blueprint: ${tokens.meta.title || tokens.meta.url}
585
+
586
+ - **Extracted At**: ${tokens.meta.extractedAt}
587
+ - **Source URL**: ${tokens.meta.url}
588
+ - **Theme**: ${tokens.meta.isDarkMode ? "Dark Mode" : "Light Mode"}
589
+ - **Selector**: \`${tokens.meta.selector}\`
590
+
591
+ ## Design System Tokens
592
+ - **Font Families**: ${tokens.typography.fontFamilies.join(", ") || "System Default"}
593
+ - **Primary Background Colors**: \`${tokens.palette.backgrounds.slice(0, 5).join("`, `")}\`
594
+ - **Text Colors**: \`${tokens.palette.text.slice(0, 5).join("`, `")}\`
595
+ - **Border Radii**: \`${tokens.effects.radii.slice(0, 5).join("`, `")}\`
596
+ - **Shadows**: ${tokens.effects.shadows.length} extracted shadow styles
597
+
598
+ ## Included Files
599
+ 1. \`tokens.json\` — Full machine-readable design tokens, CSS variables, typography hierarchy, and colors.
600
+ 2. \`template.html\` — Clean, semantic, beautified HTML skeleton (${metrics.nodeCount} elements, ${metrics.svgCount} SVGs).
601
+ 3. \`styles.css\` — Extracted CSS variables and scoped styles.
602
+ ${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` configuration.\n" : ""}
603
+ ## Agent Implementation Instructions
604
+ - Open \`tokens.json\` to copy exact hex colors, typography weights, and border radius tokens.
605
+ - Open \`template.html\` to review semantic structure, SVGs, and component layout hierarchy.
606
+ - Recreate the target component in your project using these exact tokens.
607
607
  `;
608
608
  await fs.writeFile(readmePath, readmeContent, "utf-8");
609
609
  return {