@weavetab/mcp 2.5.0 → 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 (168) hide show
  1. package/AGENTS.md +49 -49
  2. package/ARCHITECTURE.md +590 -590
  3. package/LICENSE +216 -216
  4. package/README.md +269 -269
  5. package/SECURITY.md +207 -207
  6. package/TROUBLESHOOTING.md +373 -365
  7. package/dist/anticaptcha/detector.js +395 -395
  8. package/dist/anticaptcha/solver.js +130 -130
  9. package/dist/cdp/bridge.js +73 -73
  10. package/dist/cdp/connector.js +46 -22
  11. package/dist/cdp/extension.js +34 -34
  12. package/dist/cdp/helpers.js +174 -174
  13. package/dist/cdp/pruner.js +195 -195
  14. package/dist/cdp/ref_cache.js +66 -66
  15. package/dist/cdp/security.js +10 -10
  16. package/dist/cdp/verify.js +57 -57
  17. package/dist/cdp/walker.js +563 -525
  18. package/dist/cli/plugin.js +26 -3
  19. package/dist/config/loader.d.ts +0 -3
  20. package/dist/config/loader.js +0 -9
  21. package/dist/index.js +0 -4
  22. package/dist/intelligence/intent.js +81 -81
  23. package/dist/intelligence/sessionMemory.js +2 -2
  24. package/dist/overlay/ghost.js +22 -22
  25. package/dist/overlay/injector.js +89 -89
  26. package/dist/plugin/extension_bridge.js +54 -54
  27. package/dist/plugin/interface.d.ts +7 -0
  28. package/dist/plugin/loader.js +3 -4
  29. package/dist/plugin/schema.d.ts +11 -0
  30. package/dist/plugin/schema.js +59 -0
  31. package/dist/security/guard.js +18 -18
  32. package/dist/sensors/actionability.js +167 -167
  33. package/dist/sensors/guard.js +29 -29
  34. package/dist/sensors/network.js +15 -17
  35. package/dist/server.d.ts +1 -1
  36. package/dist/server.js +173 -144
  37. package/dist/state/delta.js +6 -7
  38. package/dist/tools/automation.js +85 -85
  39. package/dist/tools/canvas.js +48 -25
  40. package/dist/tools/checkpoint.js +94 -35
  41. package/dist/tools/click.js +52 -41
  42. package/dist/tools/clipboard.js +80 -74
  43. package/dist/tools/design_extract.js +441 -441
  44. package/dist/tools/dialog.js +13 -13
  45. package/dist/tools/emulation.d.ts +2 -0
  46. package/dist/tools/emulation.js +7 -0
  47. package/dist/tools/fill.js +344 -347
  48. package/dist/tools/find.js +78 -52
  49. package/dist/tools/highlight.js +67 -67
  50. package/dist/tools/inspect.js +54 -25
  51. package/dist/tools/key.js +42 -35
  52. package/dist/tools/navigate.d.ts +1 -1
  53. package/dist/tools/navigate.js +35 -35
  54. package/dist/tools/office/parse.js +62 -18
  55. package/dist/tools/pdf.d.ts +9 -0
  56. package/dist/tools/pdf.js +7 -4
  57. package/dist/tools/performance.d.ts +11 -4
  58. package/dist/tools/performance.js +94 -42
  59. package/dist/tools/performance_test.d.ts +2 -2
  60. package/dist/tools/performance_test.js +69 -69
  61. package/dist/tools/pointer.js +116 -116
  62. package/dist/tools/read.js +99 -67
  63. package/dist/tools/recording.js +9 -0
  64. package/dist/tools/scrape.js +146 -146
  65. package/dist/tools/screenshot.d.ts +2 -0
  66. package/dist/tools/screenshot.js +46 -4
  67. package/dist/tools/scroll.d.ts +3 -1
  68. package/dist/tools/scroll.js +13 -15
  69. package/dist/tools/select.d.ts +3 -0
  70. package/dist/tools/select.js +131 -124
  71. package/dist/tools/snapshot.js +8 -6
  72. package/dist/tools/storage.d.ts +5 -1
  73. package/dist/tools/storage.js +16 -0
  74. package/dist/tools/tabs.d.ts +7 -5
  75. package/dist/tools/tabs.js +79 -26
  76. package/dist/tools/thought.js +8 -8
  77. package/dist/tools/type.js +274 -274
  78. package/dist/tools/type_secret.js +23 -23
  79. package/dist/tools/upload.js +3 -3
  80. package/dist/tools/video_compiler.js +9 -0
  81. package/dist/tools/wait.js +35 -35
  82. package/dist/version.d.ts +1 -1
  83. package/dist/version.js +1 -1
  84. package/docs/DOCUMENTATION.md +583 -585
  85. package/docs/PLUGINS.md +139 -121
  86. package/docs/TOOLS.md +3 -4
  87. package/docs/architecture/blind-injection.md +83 -83
  88. package/docs/architecture/overview.md +49 -49
  89. package/docs/architecture/security.md +36 -36
  90. package/docs/architecture/transport.md +37 -37
  91. package/docs/cli/cli-commands.md +50 -50
  92. package/docs/cli/cli-flags.md +28 -28
  93. package/docs/cli/cli-scripting.md +35 -35
  94. package/docs/configuration/config-locations.md +23 -23
  95. package/docs/configuration/configuration.md +39 -27
  96. package/docs/configuration/env-variables.md +19 -19
  97. package/docs/configuration/folders.md +62 -55
  98. package/docs/desktop-automation/electron-apps.md +225 -214
  99. package/docs/get-started/installation.md +1 -1
  100. package/docs/get-started/introduction.md +60 -60
  101. package/docs/get-started/quickstart.md +2 -2
  102. package/docs/get-started/setup-wizard.md +27 -27
  103. package/docs/intelligence/site-profiles.md +151 -151
  104. package/docs/tools/tools.md +582 -631
  105. package/docs/troubleshooting/trouble-browser.md +27 -27
  106. package/docs/troubleshooting/trouble-loop.md +31 -31
  107. package/ext-dist/background.js +415 -415
  108. package/ext-dist/content.js +2527 -2527
  109. package/ext-dist/dashboard.html +583 -583
  110. package/ext-dist/hud/hud.js +3 -3
  111. package/ext-dist/hud/index.html +20 -20
  112. package/ext-dist/manifest.json +73 -73
  113. package/ext-dist/styles.css +223 -223
  114. package/mcp.json +796 -805
  115. package/package.json +118 -118
  116. package/server.json +23 -23
  117. package/dist/cdp/walker.test.d.ts +0 -2
  118. package/dist/cdp/walker.test.js +0 -172
  119. package/dist/intelligence/sessionMemory.test.d.ts +0 -2
  120. package/dist/intelligence/sessionMemory.test.js +0 -62
  121. package/dist/intelligence/trail.test.d.ts +0 -2
  122. package/dist/intelligence/trail.test.js +0 -110
  123. package/dist/state/cache.test.d.ts +0 -2
  124. package/dist/state/cache.test.js +0 -76
  125. package/dist/state/delta.test.d.ts +0 -2
  126. package/dist/state/delta.test.js +0 -279
  127. package/dist/state/groups.d.ts +0 -15
  128. package/dist/state/groups.js +0 -80
  129. package/dist/tools/accessibility.d.ts +0 -14
  130. package/dist/tools/accessibility.js +0 -32
  131. package/dist/tools/ask.d.ts +0 -14
  132. package/dist/tools/ask.js +0 -49
  133. package/dist/tools/click_and_wait.d.ts +0 -33
  134. package/dist/tools/click_and_wait.js +0 -149
  135. package/dist/tools/drag.d.ts +0 -15
  136. package/dist/tools/drag.js +0 -29
  137. package/dist/tools/geo.d.ts +0 -19
  138. package/dist/tools/geo.js +0 -29
  139. package/dist/tools/github/analyze.d.ts +0 -4
  140. package/dist/tools/github/analyze.js +0 -113
  141. package/dist/tools/github/cache.d.ts +0 -21
  142. package/dist/tools/github/cache.js +0 -28
  143. package/dist/tools/github/index.d.ts +0 -19
  144. package/dist/tools/github/index.js +0 -40
  145. package/dist/tools/github/issues.d.ts +0 -9
  146. package/dist/tools/github/issues.js +0 -88
  147. package/dist/tools/github/pr.d.ts +0 -5
  148. package/dist/tools/github/pr.js +0 -88
  149. package/dist/tools/github/rateLimiter.d.ts +0 -4
  150. package/dist/tools/github/rateLimiter.js +0 -46
  151. package/dist/tools/github/read.d.ts +0 -34
  152. package/dist/tools/github/read.js +0 -74
  153. package/dist/tools/github/repoContext.d.ts +0 -20
  154. package/dist/tools/github/repoContext.js +0 -129
  155. package/dist/tools/hover.d.ts +0 -19
  156. package/dist/tools/hover.js +0 -57
  157. package/dist/tools/integration.test.d.ts +0 -2
  158. package/dist/tools/integration.test.js +0 -96
  159. package/dist/tools/macro_compiler.d.ts +0 -7
  160. package/dist/tools/macro_compiler.js +0 -82
  161. package/dist/tools/mouse.d.ts +0 -21
  162. package/dist/tools/mouse.js +0 -62
  163. package/dist/tools/response.d.ts +0 -129
  164. package/dist/tools/response.js +0 -5
  165. package/dist/tools/wait_for.d.ts +0 -23
  166. package/dist/tools/wait_for.js +0 -30
  167. package/dist/tools/windows.d.ts +0 -22
  168. 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 {