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.
- package/.claude/skills/agent-browser-skill/SKILL.md +252 -0
- package/.claude/skills/real-prototypes-skill/.gitignore +188 -0
- package/.claude/skills/real-prototypes-skill/ACCESSIBILITY.md +668 -0
- package/.claude/skills/real-prototypes-skill/INSTALL.md +259 -0
- package/.claude/skills/real-prototypes-skill/LICENSE +21 -0
- package/.claude/skills/real-prototypes-skill/PUBLISH.md +310 -0
- package/.claude/skills/real-prototypes-skill/QUICKSTART.md +240 -0
- package/.claude/skills/real-prototypes-skill/README.md +442 -0
- package/.claude/skills/real-prototypes-skill/SKILL.md +375 -0
- package/.claude/skills/real-prototypes-skill/capture/capture-engine.js +1153 -0
- package/.claude/skills/real-prototypes-skill/capture/config.schema.json +170 -0
- package/.claude/skills/real-prototypes-skill/cli.js +596 -0
- package/.claude/skills/real-prototypes-skill/docs/TROUBLESHOOTING.md +278 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/capture-config.md +167 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/design-tokens.md +183 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/manifest.md +169 -0
- package/.claude/skills/real-prototypes-skill/examples/CLAUDE.md.example +73 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/CLAUDE.md +136 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/FEATURES.md +222 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/README.md +82 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/design-tokens.json +87 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/homepage-viewport.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-chatbot-final.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-fullpage-v2.png +0 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-fixes.md +298 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-report.json +253 -0
- package/.claude/skills/real-prototypes-skill/scripts/CAPTURE-ENHANCEMENTS.md +344 -0
- package/.claude/skills/real-prototypes-skill/scripts/IMPLEMENTATION-SUMMARY.md +517 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICK-START.md +229 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICKSTART-layout-analysis.md +148 -0
- package/.claude/skills/real-prototypes-skill/scripts/README-analyze-layout.md +407 -0
- package/.claude/skills/real-prototypes-skill/scripts/analyze-layout.js +880 -0
- package/.claude/skills/real-prototypes-skill/scripts/capture-platform.js +203 -0
- package/.claude/skills/real-prototypes-skill/scripts/comprehensive-capture.js +597 -0
- package/.claude/skills/real-prototypes-skill/scripts/create-manifest.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/enterprise-pipeline.js +428 -0
- package/.claude/skills/real-prototypes-skill/scripts/extract-tokens.js +468 -0
- package/.claude/skills/real-prototypes-skill/scripts/full-site-capture.js +738 -0
- package/.claude/skills/real-prototypes-skill/scripts/generate-tailwind-config.js +296 -0
- package/.claude/skills/real-prototypes-skill/scripts/integrate-accessibility.sh +161 -0
- package/.claude/skills/real-prototypes-skill/scripts/manifest-schema.json +302 -0
- package/.claude/skills/real-prototypes-skill/scripts/setup-prototype.sh +167 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-analyze-layout.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-validation.js +307 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-accessibility.js +598 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-manifest.js +499 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-output.js +361 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-prerequisites.js +319 -0
- package/.claude/skills/real-prototypes-skill/scripts/verify-layout-analysis.sh +77 -0
- package/.claude/skills/real-prototypes-skill/templates/dashboard-widget.tsx.template +91 -0
- package/.claude/skills/real-prototypes-skill/templates/data-table.tsx.template +193 -0
- package/.claude/skills/real-prototypes-skill/templates/form-section.tsx.template +250 -0
- package/.claude/skills/real-prototypes-skill/templates/modal-dialog.tsx.template +239 -0
- package/.claude/skills/real-prototypes-skill/templates/nav-item.tsx.template +265 -0
- package/.claude/skills/real-prototypes-skill/validation/validation-engine.js +559 -0
- package/.env.example +74 -0
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/bin/cli.js +319 -0
- package/package.json +59 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create-manifest.js
|
|
5
|
+
*
|
|
6
|
+
* Generates a manifest.json file for platform prototype references.
|
|
7
|
+
* Scans references/screenshots and references/html directories to auto-discover files.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node create-manifest.js <platform-name> <platform-url> [output-dir]
|
|
11
|
+
*
|
|
12
|
+
* Arguments:
|
|
13
|
+
* platform-name Name of the platform (e.g., "Linear", "Notion")
|
|
14
|
+
* platform-url Base URL of the platform (e.g., "https://linear.app")
|
|
15
|
+
* output-dir Optional output directory (defaults to current directory)
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* node create-manifest.js "Linear" "https://linear.app" ./references
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
// Supported file extensions
|
|
25
|
+
const SCREENSHOT_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.gif'];
|
|
26
|
+
const HTML_EXTENSIONS = ['.html', '.htm'];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse command line arguments
|
|
30
|
+
*/
|
|
31
|
+
function parseArgs() {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
|
|
34
|
+
if (args.length < 2) {
|
|
35
|
+
console.error('Usage: node create-manifest.js <platform-name> <platform-url> [output-dir]');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error('Arguments:');
|
|
38
|
+
console.error(' platform-name Name of the platform (e.g., "Linear")');
|
|
39
|
+
console.error(' platform-url Base URL of the platform (e.g., "https://linear.app")');
|
|
40
|
+
console.error(' output-dir Optional output directory (defaults to current directory)');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
platformName: args[0],
|
|
46
|
+
platformUrl: args[1],
|
|
47
|
+
outputDir: args[2] || process.cwd()
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a file has a matching extension
|
|
53
|
+
*/
|
|
54
|
+
function hasExtension(filename, extensions) {
|
|
55
|
+
const ext = path.extname(filename).toLowerCase();
|
|
56
|
+
return extensions.includes(ext);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Scan a directory for files with specific extensions
|
|
61
|
+
*/
|
|
62
|
+
function scanDirectory(dirPath, extensions) {
|
|
63
|
+
const files = [];
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(dirPath)) {
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (entry.isFile() && hasExtension(entry.name, extensions)) {
|
|
74
|
+
const filePath = path.join(dirPath, entry.name);
|
|
75
|
+
const stats = fs.statSync(filePath);
|
|
76
|
+
files.push({
|
|
77
|
+
name: entry.name,
|
|
78
|
+
path: filePath,
|
|
79
|
+
mtime: stats.mtime
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.warn(`Warning: Could not read directory ${dirPath}: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return files.sort((a, b) => a.name.localeCompare(b.name));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract page ID from filename
|
|
92
|
+
* Converts "my-page-name.png" to "my-page-name"
|
|
93
|
+
* Handles patterns like "page-name-desktop.png" or "page-name-1.png"
|
|
94
|
+
*/
|
|
95
|
+
function extractPageId(filename) {
|
|
96
|
+
// Remove extension
|
|
97
|
+
let id = path.basename(filename, path.extname(filename));
|
|
98
|
+
|
|
99
|
+
// Normalize to lowercase with hyphens
|
|
100
|
+
id = id.toLowerCase().replace(/[_\s]+/g, '-');
|
|
101
|
+
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert page ID to human-readable name
|
|
107
|
+
* Converts "my-page-name" to "My Page Name"
|
|
108
|
+
*/
|
|
109
|
+
function pageIdToName(pageId) {
|
|
110
|
+
return pageId
|
|
111
|
+
.split('-')
|
|
112
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
113
|
+
.join(' ');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detect viewport/variant from filename
|
|
118
|
+
* Looks for patterns like "-desktop", "-mobile", "-hover", "-1920x1080"
|
|
119
|
+
*/
|
|
120
|
+
function detectScreenshotMeta(filename) {
|
|
121
|
+
const basename = path.basename(filename, path.extname(filename)).toLowerCase();
|
|
122
|
+
const meta = {};
|
|
123
|
+
|
|
124
|
+
// Check for viewport indicators
|
|
125
|
+
if (basename.includes('desktop') || basename.includes('-lg') || basename.includes('-large')) {
|
|
126
|
+
meta.viewport = 'desktop';
|
|
127
|
+
} else if (basename.includes('mobile') || basename.includes('-sm') || basename.includes('-small')) {
|
|
128
|
+
meta.viewport = 'mobile';
|
|
129
|
+
} else if (basename.includes('tablet') || basename.includes('-md') || basename.includes('-medium')) {
|
|
130
|
+
meta.viewport = 'tablet';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for resolution pattern (e.g., 1920x1080)
|
|
134
|
+
const resMatch = basename.match(/(\d{3,4})x(\d{3,4})/);
|
|
135
|
+
if (resMatch) {
|
|
136
|
+
meta.viewport = `${resMatch[1]}x${resMatch[2]}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for state indicators
|
|
140
|
+
if (basename.includes('hover')) {
|
|
141
|
+
meta.state = 'hover';
|
|
142
|
+
} else if (basename.includes('active') || basename.includes('pressed')) {
|
|
143
|
+
meta.state = 'active';
|
|
144
|
+
} else if (basename.includes('focus')) {
|
|
145
|
+
meta.state = 'focus';
|
|
146
|
+
} else if (basename.includes('expanded')) {
|
|
147
|
+
meta.state = 'expanded';
|
|
148
|
+
} else if (basename.includes('collapsed')) {
|
|
149
|
+
meta.state = 'collapsed';
|
|
150
|
+
} else if (basename.includes('loading')) {
|
|
151
|
+
meta.state = 'loading';
|
|
152
|
+
} else if (basename.includes('empty')) {
|
|
153
|
+
meta.state = 'empty';
|
|
154
|
+
} else if (basename.includes('error')) {
|
|
155
|
+
meta.state = 'error';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return meta;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Group screenshots by base page name
|
|
163
|
+
* Handles multiple screenshots per page (different viewports, states)
|
|
164
|
+
*/
|
|
165
|
+
function groupScreenshots(files) {
|
|
166
|
+
const groups = new Map();
|
|
167
|
+
|
|
168
|
+
for (const file of files) {
|
|
169
|
+
const basename = path.basename(file.name, path.extname(file.name)).toLowerCase();
|
|
170
|
+
|
|
171
|
+
// Try to extract base page name (remove viewport/state suffixes)
|
|
172
|
+
let baseName = basename
|
|
173
|
+
.replace(/[-_](desktop|mobile|tablet|lg|md|sm|large|medium|small)$/i, '')
|
|
174
|
+
.replace(/[-_](hover|active|pressed|focus|expanded|collapsed|loading|empty|error)$/i, '')
|
|
175
|
+
.replace(/[-_]\d{3,4}x\d{3,4}$/i, '')
|
|
176
|
+
.replace(/[-_]\d+$/i, ''); // Remove trailing numbers
|
|
177
|
+
|
|
178
|
+
if (!groups.has(baseName)) {
|
|
179
|
+
groups.set(baseName, []);
|
|
180
|
+
}
|
|
181
|
+
groups.get(baseName).push(file);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return groups;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Match HTML files to screenshot groups
|
|
189
|
+
*/
|
|
190
|
+
function matchHtmlFiles(htmlFiles, screenshotGroups) {
|
|
191
|
+
const matches = new Map();
|
|
192
|
+
|
|
193
|
+
for (const html of htmlFiles) {
|
|
194
|
+
const basename = path.basename(html.name, path.extname(html.name)).toLowerCase();
|
|
195
|
+
|
|
196
|
+
// Look for exact or partial match in screenshot groups
|
|
197
|
+
for (const [groupName] of screenshotGroups) {
|
|
198
|
+
if (basename === groupName || basename.includes(groupName) || groupName.includes(basename)) {
|
|
199
|
+
matches.set(groupName, html);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If no match found, create standalone entry
|
|
205
|
+
if (!matches.has(basename)) {
|
|
206
|
+
matches.set(basename, html);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return matches;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate manifest from discovered files
|
|
215
|
+
*/
|
|
216
|
+
function generateManifest(platformName, platformUrl, outputDir) {
|
|
217
|
+
const screenshotsDir = path.join(outputDir, 'references', 'screenshots');
|
|
218
|
+
const htmlDir = path.join(outputDir, 'references', 'html');
|
|
219
|
+
|
|
220
|
+
// Scan directories
|
|
221
|
+
const screenshotFiles = scanDirectory(screenshotsDir, SCREENSHOT_EXTENSIONS);
|
|
222
|
+
const htmlFiles = scanDirectory(htmlDir, HTML_EXTENSIONS);
|
|
223
|
+
|
|
224
|
+
console.log(`Found ${screenshotFiles.length} screenshot(s) in ${screenshotsDir}`);
|
|
225
|
+
console.log(`Found ${htmlFiles.length} HTML file(s) in ${htmlDir}`);
|
|
226
|
+
|
|
227
|
+
// Group screenshots by page
|
|
228
|
+
const screenshotGroups = groupScreenshots(screenshotFiles);
|
|
229
|
+
|
|
230
|
+
// Match HTML files to groups
|
|
231
|
+
const htmlMatches = matchHtmlFiles(htmlFiles, screenshotGroups);
|
|
232
|
+
|
|
233
|
+
// Collect all unique page identifiers
|
|
234
|
+
const pageIds = new Set([...screenshotGroups.keys(), ...htmlMatches.keys()]);
|
|
235
|
+
|
|
236
|
+
// Build pages array
|
|
237
|
+
const pages = [];
|
|
238
|
+
|
|
239
|
+
for (const pageId of pageIds) {
|
|
240
|
+
const page = {
|
|
241
|
+
id: pageId,
|
|
242
|
+
name: pageIdToName(pageId),
|
|
243
|
+
path: `/${pageId}`
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Add screenshots
|
|
247
|
+
const screenshots = screenshotGroups.get(pageId);
|
|
248
|
+
if (screenshots && screenshots.length > 0) {
|
|
249
|
+
page.screenshots = screenshots.map(file => {
|
|
250
|
+
const meta = detectScreenshotMeta(file.name);
|
|
251
|
+
return {
|
|
252
|
+
file: file.name,
|
|
253
|
+
...meta
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add HTML reference
|
|
259
|
+
const html = htmlMatches.get(pageId);
|
|
260
|
+
if (html) {
|
|
261
|
+
page.html = {
|
|
262
|
+
file: html.name,
|
|
263
|
+
capturedAt: html.mtime.toISOString()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
pages.push(page);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Sort pages alphabetically by ID
|
|
271
|
+
pages.sort((a, b) => a.id.localeCompare(b.id));
|
|
272
|
+
|
|
273
|
+
// Build manifest
|
|
274
|
+
const manifest = {
|
|
275
|
+
platform: {
|
|
276
|
+
name: platformName,
|
|
277
|
+
url: platformUrl,
|
|
278
|
+
capturedAt: new Date().toISOString()
|
|
279
|
+
},
|
|
280
|
+
pages: pages,
|
|
281
|
+
designTokens: {
|
|
282
|
+
colors: {},
|
|
283
|
+
typography: {},
|
|
284
|
+
spacing: {}
|
|
285
|
+
},
|
|
286
|
+
metadata: {
|
|
287
|
+
generatedAt: new Date().toISOString(),
|
|
288
|
+
generatedBy: 'create-manifest.js'
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
return manifest;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Main execution
|
|
297
|
+
*/
|
|
298
|
+
function main() {
|
|
299
|
+
const { platformName, platformUrl, outputDir } = parseArgs();
|
|
300
|
+
|
|
301
|
+
// Resolve output directory to absolute path
|
|
302
|
+
const resolvedOutputDir = path.resolve(outputDir);
|
|
303
|
+
|
|
304
|
+
console.log(`Generating manifest for: ${platformName}`);
|
|
305
|
+
console.log(`Platform URL: ${platformUrl}`);
|
|
306
|
+
console.log(`Output directory: ${resolvedOutputDir}`);
|
|
307
|
+
console.log('');
|
|
308
|
+
|
|
309
|
+
// Generate manifest
|
|
310
|
+
const manifest = generateManifest(platformName, platformUrl, resolvedOutputDir);
|
|
311
|
+
|
|
312
|
+
// Write manifest file
|
|
313
|
+
const manifestPath = path.join(resolvedOutputDir, 'manifest.json');
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
317
|
+
console.log('');
|
|
318
|
+
console.log(`Manifest written to: ${manifestPath}`);
|
|
319
|
+
console.log(`Total pages: ${manifest.pages.length}`);
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error(`Error writing manifest: ${err.message}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Run if executed directly
|
|
327
|
+
if (require.main === module) {
|
|
328
|
+
main();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Export for testing
|
|
332
|
+
module.exports = {
|
|
333
|
+
generateManifest,
|
|
334
|
+
extractPageId,
|
|
335
|
+
pageIdToName,
|
|
336
|
+
detectScreenshotMeta,
|
|
337
|
+
groupScreenshots
|
|
338
|
+
};
|