real-prototypes-skill 0.1.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 (60) hide show
  1. package/.claude/skills/agent-browser-skill/SKILL.md +252 -0
  2. package/.claude/skills/real-prototypes-skill/.gitignore +188 -0
  3. package/.claude/skills/real-prototypes-skill/ACCESSIBILITY.md +668 -0
  4. package/.claude/skills/real-prototypes-skill/INSTALL.md +259 -0
  5. package/.claude/skills/real-prototypes-skill/LICENSE +21 -0
  6. package/.claude/skills/real-prototypes-skill/PUBLISH.md +310 -0
  7. package/.claude/skills/real-prototypes-skill/QUICKSTART.md +240 -0
  8. package/.claude/skills/real-prototypes-skill/README.md +442 -0
  9. package/.claude/skills/real-prototypes-skill/SKILL.md +375 -0
  10. package/.claude/skills/real-prototypes-skill/capture/capture-engine.js +1153 -0
  11. package/.claude/skills/real-prototypes-skill/capture/config.schema.json +170 -0
  12. package/.claude/skills/real-prototypes-skill/cli.js +596 -0
  13. package/.claude/skills/real-prototypes-skill/docs/TROUBLESHOOTING.md +278 -0
  14. package/.claude/skills/real-prototypes-skill/docs/schemas/capture-config.md +167 -0
  15. package/.claude/skills/real-prototypes-skill/docs/schemas/design-tokens.md +183 -0
  16. package/.claude/skills/real-prototypes-skill/docs/schemas/manifest.md +169 -0
  17. package/.claude/skills/real-prototypes-skill/examples/CLAUDE.md.example +73 -0
  18. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/CLAUDE.md +136 -0
  19. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/FEATURES.md +222 -0
  20. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/README.md +82 -0
  21. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/design-tokens.json +87 -0
  22. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/homepage-viewport.png +0 -0
  23. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-chatbot-final.png +0 -0
  24. package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-fullpage-v2.png +0 -0
  25. package/.claude/skills/real-prototypes-skill/references/accessibility-fixes.md +298 -0
  26. package/.claude/skills/real-prototypes-skill/references/accessibility-report.json +253 -0
  27. package/.claude/skills/real-prototypes-skill/scripts/CAPTURE-ENHANCEMENTS.md +344 -0
  28. package/.claude/skills/real-prototypes-skill/scripts/IMPLEMENTATION-SUMMARY.md +517 -0
  29. package/.claude/skills/real-prototypes-skill/scripts/QUICK-START.md +229 -0
  30. package/.claude/skills/real-prototypes-skill/scripts/QUICKSTART-layout-analysis.md +148 -0
  31. package/.claude/skills/real-prototypes-skill/scripts/README-analyze-layout.md +407 -0
  32. package/.claude/skills/real-prototypes-skill/scripts/analyze-layout.js +880 -0
  33. package/.claude/skills/real-prototypes-skill/scripts/capture-platform.js +203 -0
  34. package/.claude/skills/real-prototypes-skill/scripts/comprehensive-capture.js +597 -0
  35. package/.claude/skills/real-prototypes-skill/scripts/create-manifest.js +338 -0
  36. package/.claude/skills/real-prototypes-skill/scripts/enterprise-pipeline.js +428 -0
  37. package/.claude/skills/real-prototypes-skill/scripts/extract-tokens.js +468 -0
  38. package/.claude/skills/real-prototypes-skill/scripts/full-site-capture.js +738 -0
  39. package/.claude/skills/real-prototypes-skill/scripts/generate-tailwind-config.js +296 -0
  40. package/.claude/skills/real-prototypes-skill/scripts/integrate-accessibility.sh +161 -0
  41. package/.claude/skills/real-prototypes-skill/scripts/manifest-schema.json +302 -0
  42. package/.claude/skills/real-prototypes-skill/scripts/setup-prototype.sh +167 -0
  43. package/.claude/skills/real-prototypes-skill/scripts/test-analyze-layout.js +338 -0
  44. package/.claude/skills/real-prototypes-skill/scripts/test-validation.js +307 -0
  45. package/.claude/skills/real-prototypes-skill/scripts/validate-accessibility.js +598 -0
  46. package/.claude/skills/real-prototypes-skill/scripts/validate-manifest.js +499 -0
  47. package/.claude/skills/real-prototypes-skill/scripts/validate-output.js +361 -0
  48. package/.claude/skills/real-prototypes-skill/scripts/validate-prerequisites.js +319 -0
  49. package/.claude/skills/real-prototypes-skill/scripts/verify-layout-analysis.sh +77 -0
  50. package/.claude/skills/real-prototypes-skill/templates/dashboard-widget.tsx.template +91 -0
  51. package/.claude/skills/real-prototypes-skill/templates/data-table.tsx.template +193 -0
  52. package/.claude/skills/real-prototypes-skill/templates/form-section.tsx.template +250 -0
  53. package/.claude/skills/real-prototypes-skill/templates/modal-dialog.tsx.template +239 -0
  54. package/.claude/skills/real-prototypes-skill/templates/nav-item.tsx.template +265 -0
  55. package/.claude/skills/real-prototypes-skill/validation/validation-engine.js +559 -0
  56. package/.env.example +74 -0
  57. package/LICENSE +21 -0
  58. package/README.md +444 -0
  59. package/bin/cli.js +319 -0
  60. package/package.json +59 -0
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Design Token Extractor
5
+ *
6
+ * Parses HTML (and optionally CSS) files to extract design tokens including:
7
+ * - Colors (hex, rgb, rgba, hsl)
8
+ * - Font families
9
+ * - Font sizes
10
+ * - Spacing values (margin, padding)
11
+ * - Border radius values
12
+ *
13
+ * Usage: node extract-tokens.js <html-file> [css-file]
14
+ * Output: JSON object with categorized design tokens
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ // Regular expressions for extracting values
21
+ const PATTERNS = {
22
+ // Color patterns
23
+ hex: /#([0-9a-fA-F]{3,8})\b/g,
24
+ rgb: /rgba?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([\d.]+))?\s*\)/gi,
25
+ hsl: /hsla?\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%(?:\s*,\s*([\d.]+))?\s*\)/gi,
26
+
27
+ // Font patterns
28
+ fontFamily: /font-family\s*:\s*([^;}"']+)/gi,
29
+ fontSize: /font-size\s*:\s*([^;}"']+)/gi,
30
+
31
+ // Spacing patterns
32
+ margin: /margin(?:-(?:top|right|bottom|left))?\s*:\s*([^;}"']+)/gi,
33
+ padding: /padding(?:-(?:top|right|bottom|left))?\s*:\s*([^;}"']+)/gi,
34
+ gap: /gap\s*:\s*([^;}"']+)/gi,
35
+
36
+ // Border radius patterns
37
+ borderRadius: /border-radius\s*:\s*([^;}"']+)/gi,
38
+
39
+ // Style attribute pattern
40
+ styleAttr: /style\s*=\s*["']([^"']+)["']/gi,
41
+
42
+ // Inline style tag content
43
+ styleTag: /<style[^>]*>([\s\S]*?)<\/style>/gi
44
+ };
45
+
46
+ /**
47
+ * Extract all style content from HTML
48
+ */
49
+ function extractStyles(htmlContent, cssContent = '') {
50
+ let allStyles = cssContent;
51
+
52
+ // Extract inline style tags
53
+ let match;
54
+ while ((match = PATTERNS.styleTag.exec(htmlContent)) !== null) {
55
+ allStyles += '\n' + match[1];
56
+ }
57
+
58
+ // Extract style attributes
59
+ PATTERNS.styleAttr.lastIndex = 0;
60
+ while ((match = PATTERNS.styleAttr.exec(htmlContent)) !== null) {
61
+ allStyles += '\n' + match[1];
62
+ }
63
+
64
+ return allStyles;
65
+ }
66
+
67
+ /**
68
+ * Extract color values from styles
69
+ */
70
+ function extractColors(styles) {
71
+ const colors = new Set();
72
+
73
+ // Extract hex colors
74
+ let match;
75
+ PATTERNS.hex.lastIndex = 0;
76
+ while ((match = PATTERNS.hex.exec(styles)) !== null) {
77
+ let hex = match[0].toLowerCase();
78
+ // Normalize 3-digit hex to 6-digit
79
+ if (hex.length === 4) {
80
+ hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
81
+ }
82
+ colors.add(hex);
83
+ }
84
+
85
+ // Extract rgb/rgba colors
86
+ PATTERNS.rgb.lastIndex = 0;
87
+ while ((match = PATTERNS.rgb.exec(styles)) !== null) {
88
+ colors.add(match[0].toLowerCase().replace(/\s+/g, ''));
89
+ }
90
+
91
+ // Extract hsl/hsla colors
92
+ PATTERNS.hsl.lastIndex = 0;
93
+ while ((match = PATTERNS.hsl.exec(styles)) !== null) {
94
+ colors.add(match[0].toLowerCase().replace(/\s+/g, ''));
95
+ }
96
+
97
+ return Array.from(colors);
98
+ }
99
+
100
+ /**
101
+ * Extract font families from styles
102
+ */
103
+ function extractFontFamilies(styles) {
104
+ const fonts = new Set();
105
+ let match;
106
+
107
+ PATTERNS.fontFamily.lastIndex = 0;
108
+ while ((match = PATTERNS.fontFamily.exec(styles)) !== null) {
109
+ const fontValue = match[1].trim();
110
+ // Split by comma and get primary font
111
+ const primaryFont = fontValue.split(',')[0].trim().replace(/["']/g, '');
112
+ if (primaryFont && !primaryFont.startsWith('var(')) {
113
+ fonts.add(primaryFont);
114
+ }
115
+ }
116
+
117
+ return Array.from(fonts);
118
+ }
119
+
120
+ /**
121
+ * Extract font sizes from styles
122
+ */
123
+ function extractFontSizes(styles) {
124
+ const sizes = new Set();
125
+ let match;
126
+
127
+ PATTERNS.fontSize.lastIndex = 0;
128
+ while ((match = PATTERNS.fontSize.exec(styles)) !== null) {
129
+ const sizeValue = match[1].trim();
130
+ if (!sizeValue.startsWith('var(')) {
131
+ sizes.add(sizeValue);
132
+ }
133
+ }
134
+
135
+ return Array.from(sizes);
136
+ }
137
+
138
+ /**
139
+ * Extract spacing values from styles
140
+ */
141
+ function extractSpacing(styles) {
142
+ const spacing = new Set();
143
+ let match;
144
+
145
+ // Extract margin values
146
+ PATTERNS.margin.lastIndex = 0;
147
+ while ((match = PATTERNS.margin.exec(styles)) !== null) {
148
+ parseSpacingValue(match[1]).forEach(v => spacing.add(v));
149
+ }
150
+
151
+ // Extract padding values
152
+ PATTERNS.padding.lastIndex = 0;
153
+ while ((match = PATTERNS.padding.exec(styles)) !== null) {
154
+ parseSpacingValue(match[1]).forEach(v => spacing.add(v));
155
+ }
156
+
157
+ // Extract gap values
158
+ PATTERNS.gap.lastIndex = 0;
159
+ while ((match = PATTERNS.gap.exec(styles)) !== null) {
160
+ parseSpacingValue(match[1]).forEach(v => spacing.add(v));
161
+ }
162
+
163
+ return Array.from(spacing).filter(v => v !== '0' && v !== 'auto' && !v.startsWith('var('));
164
+ }
165
+
166
+ /**
167
+ * Parse spacing value (handles shorthand like "10px 20px 10px 20px")
168
+ */
169
+ function parseSpacingValue(value) {
170
+ const values = value.trim().split(/\s+/);
171
+ return values.filter(v => v && v !== '0' && v !== 'auto' && !v.startsWith('var('));
172
+ }
173
+
174
+ /**
175
+ * Extract border radius values from styles
176
+ */
177
+ function extractBorderRadius(styles) {
178
+ const radii = new Set();
179
+ let match;
180
+
181
+ PATTERNS.borderRadius.lastIndex = 0;
182
+ while ((match = PATTERNS.borderRadius.exec(styles)) !== null) {
183
+ const radiusValue = match[1].trim();
184
+ if (!radiusValue.startsWith('var(')) {
185
+ // Handle shorthand notation
186
+ const values = radiusValue.split(/\s+/);
187
+ values.forEach(v => {
188
+ if (v && v !== '0') {
189
+ radii.add(v);
190
+ }
191
+ });
192
+ }
193
+ }
194
+
195
+ return Array.from(radii);
196
+ }
197
+
198
+ /**
199
+ * Convert numeric value to comparable number (in px)
200
+ */
201
+ function toPixels(value) {
202
+ if (!value) return 0;
203
+ const num = parseFloat(value);
204
+ if (value.includes('rem')) return num * 16;
205
+ if (value.includes('em')) return num * 16;
206
+ if (value.includes('px')) return num;
207
+ if (value.includes('%')) return num;
208
+ return num;
209
+ }
210
+
211
+ /**
212
+ * Sort values by size
213
+ */
214
+ function sortBySize(values) {
215
+ return [...values].sort((a, b) => toPixels(a) - toPixels(b));
216
+ }
217
+
218
+ /**
219
+ * Categorize colors based on lightness/usage patterns
220
+ */
221
+ function categorizeColors(colors) {
222
+ const result = {};
223
+
224
+ if (colors.length === 0) return result;
225
+
226
+ // Sort colors by luminance
227
+ const sortedColors = [...colors].sort((a, b) => {
228
+ return getLuminance(a) - getLuminance(b);
229
+ });
230
+
231
+ // Assign categories based on position
232
+ if (sortedColors.length >= 1) {
233
+ result.text = sortedColors[0]; // Darkest
234
+ }
235
+ if (sortedColors.length >= 2) {
236
+ result.background = sortedColors[sortedColors.length - 1]; // Lightest
237
+ }
238
+ if (sortedColors.length >= 3) {
239
+ const midIndex = Math.floor(sortedColors.length / 2);
240
+ result.primary = sortedColors[midIndex];
241
+ }
242
+ if (sortedColors.length >= 4) {
243
+ const quarterIndex = Math.floor(sortedColors.length / 4);
244
+ result.secondary = sortedColors[quarterIndex];
245
+ }
246
+
247
+ // Add remaining colors as numbered entries
248
+ sortedColors.forEach((color, index) => {
249
+ if (!Object.values(result).includes(color)) {
250
+ result[`color${index + 1}`] = color;
251
+ }
252
+ });
253
+
254
+ return result;
255
+ }
256
+
257
+ /**
258
+ * Get luminance of a color for sorting
259
+ */
260
+ function getLuminance(color) {
261
+ let r, g, b;
262
+
263
+ if (color.startsWith('#')) {
264
+ const hex = color.slice(1);
265
+ if (hex.length === 3) {
266
+ r = parseInt(hex[0] + hex[0], 16);
267
+ g = parseInt(hex[1] + hex[1], 16);
268
+ b = parseInt(hex[2] + hex[2], 16);
269
+ } else {
270
+ r = parseInt(hex.slice(0, 2), 16);
271
+ g = parseInt(hex.slice(2, 4), 16);
272
+ b = parseInt(hex.slice(4, 6), 16);
273
+ }
274
+ } else if (color.startsWith('rgb')) {
275
+ const match = color.match(/(\d+)/g);
276
+ if (match) {
277
+ [r, g, b] = match.map(Number);
278
+ }
279
+ } else {
280
+ return 0.5; // Default for hsl and others
281
+ }
282
+
283
+ // Calculate relative luminance
284
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
285
+ }
286
+
287
+ /**
288
+ * Categorize fonts
289
+ */
290
+ function categorizeFonts(fonts) {
291
+ const result = {};
292
+
293
+ if (fonts.length === 0) return result;
294
+
295
+ // Common heading font indicators
296
+ const headingKeywords = ['heading', 'display', 'title', 'serif', 'playfair', 'merriweather', 'georgia'];
297
+ const bodyKeywords = ['body', 'sans', 'inter', 'roboto', 'arial', 'helvetica', 'system'];
298
+
299
+ fonts.forEach(font => {
300
+ const lowerFont = font.toLowerCase();
301
+
302
+ if (!result.heading && headingKeywords.some(kw => lowerFont.includes(kw))) {
303
+ result.heading = font;
304
+ } else if (!result.body && bodyKeywords.some(kw => lowerFont.includes(kw))) {
305
+ result.body = font;
306
+ }
307
+ });
308
+
309
+ // Fallback assignments
310
+ if (!result.body && fonts.length >= 1) {
311
+ result.body = fonts[0];
312
+ }
313
+ if (!result.heading && fonts.length >= 2) {
314
+ result.heading = fonts[1];
315
+ } else if (!result.heading && fonts.length >= 1) {
316
+ result.heading = fonts[0];
317
+ }
318
+
319
+ // Add remaining fonts
320
+ fonts.forEach((font, index) => {
321
+ if (!Object.values(result).includes(font)) {
322
+ result[`font${index + 1}`] = font;
323
+ }
324
+ });
325
+
326
+ return result;
327
+ }
328
+
329
+ /**
330
+ * Categorize spacing values
331
+ */
332
+ function categorizeSpacing(spacingValues) {
333
+ const result = {};
334
+
335
+ if (spacingValues.length === 0) return result;
336
+
337
+ const sorted = sortBySize(spacingValues);
338
+ const categories = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];
339
+
340
+ // Distribute values across categories
341
+ const step = Math.max(1, Math.ceil(sorted.length / categories.length));
342
+
343
+ sorted.forEach((value, index) => {
344
+ const categoryIndex = Math.min(Math.floor(index / step), categories.length - 1);
345
+ const category = categories[categoryIndex];
346
+ if (!result[category]) {
347
+ result[category] = value;
348
+ }
349
+ });
350
+
351
+ return result;
352
+ }
353
+
354
+ /**
355
+ * Categorize border radius values
356
+ */
357
+ function categorizeBorderRadius(radii) {
358
+ const result = {};
359
+
360
+ if (radii.length === 0) return result;
361
+
362
+ const sorted = sortBySize(radii);
363
+ const categories = ['sm', 'md', 'lg', 'xl', 'full'];
364
+
365
+ // Check for full/rounded values
366
+ const fullIndex = sorted.findIndex(v =>
367
+ v === '50%' || v === '9999px' || v === '100%' || parseFloat(v) >= 999
368
+ );
369
+
370
+ if (fullIndex !== -1) {
371
+ result.full = sorted[fullIndex];
372
+ sorted.splice(fullIndex, 1);
373
+ }
374
+
375
+ // Distribute remaining values
376
+ const remainingCategories = categories.filter(c => c !== 'full');
377
+ const step = Math.max(1, Math.ceil(sorted.length / remainingCategories.length));
378
+
379
+ sorted.forEach((value, index) => {
380
+ const categoryIndex = Math.min(Math.floor(index / step), remainingCategories.length - 1);
381
+ const category = remainingCategories[categoryIndex];
382
+ if (!result[category]) {
383
+ result[category] = value;
384
+ }
385
+ });
386
+
387
+ return result;
388
+ }
389
+
390
+ /**
391
+ * Main extraction function
392
+ */
393
+ function extractTokens(htmlPath, cssPath = null) {
394
+ // Read HTML file
395
+ if (!fs.existsSync(htmlPath)) {
396
+ console.error(`Error: HTML file not found: ${htmlPath}`);
397
+ process.exit(1);
398
+ }
399
+
400
+ const htmlContent = fs.readFileSync(htmlPath, 'utf8');
401
+
402
+ // Read CSS file if provided
403
+ let cssContent = '';
404
+ if (cssPath) {
405
+ if (!fs.existsSync(cssPath)) {
406
+ console.error(`Warning: CSS file not found: ${cssPath}`);
407
+ } else {
408
+ cssContent = fs.readFileSync(cssPath, 'utf8');
409
+ }
410
+ }
411
+
412
+ // Extract all styles
413
+ const allStyles = extractStyles(htmlContent, cssContent);
414
+
415
+ // Extract raw values
416
+ const rawColors = extractColors(allStyles);
417
+ const rawFonts = extractFontFamilies(allStyles);
418
+ const rawFontSizes = extractFontSizes(allStyles);
419
+ const rawSpacing = extractSpacing(allStyles);
420
+ const rawBorderRadius = extractBorderRadius(allStyles);
421
+
422
+ // Build tokens object
423
+ const tokens = {
424
+ colors: categorizeColors(rawColors),
425
+ fonts: categorizeFonts(rawFonts),
426
+ fontSizes: categorizeSpacing(rawFontSizes),
427
+ spacing: categorizeSpacing(rawSpacing),
428
+ borderRadius: categorizeBorderRadius(rawBorderRadius)
429
+ };
430
+
431
+ // Add raw values for reference
432
+ tokens._raw = {
433
+ colors: rawColors,
434
+ fonts: rawFonts,
435
+ fontSizes: rawFontSizes,
436
+ spacing: rawSpacing,
437
+ borderRadius: rawBorderRadius
438
+ };
439
+
440
+ return tokens;
441
+ }
442
+
443
+ // CLI execution
444
+ if (require.main === module) {
445
+ const args = process.argv.slice(2);
446
+
447
+ if (args.length === 0) {
448
+ console.log('Usage: node extract-tokens.js <html-file> [css-file]');
449
+ console.log('');
450
+ console.log('Extracts design tokens from HTML and CSS files.');
451
+ console.log('');
452
+ console.log('Arguments:');
453
+ console.log(' html-file Path to the HTML file to parse');
454
+ console.log(' css-file Optional path to additional CSS file');
455
+ console.log('');
456
+ console.log('Output: JSON object with categorized design tokens');
457
+ process.exit(0);
458
+ }
459
+
460
+ const htmlPath = path.resolve(args[0]);
461
+ const cssPath = args[1] ? path.resolve(args[1]) : null;
462
+
463
+ const tokens = extractTokens(htmlPath, cssPath);
464
+
465
+ console.log(JSON.stringify(tokens, null, 2));
466
+ }
467
+
468
+ module.exports = { extractTokens };