design-clone 1.0.2 → 1.1.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.
- package/SKILL.md +53 -0
- package/bin/cli.js +16 -0
- package/bin/commands/clone-site.js +324 -0
- package/bin/commands/help.js +16 -4
- package/bin/commands/init.js +29 -1
- package/commands/design/clone-site.md +135 -0
- package/docs/troubleshooting.md +72 -0
- package/package.json +2 -1
- package/src/core/css-extractor.js +38 -13
- package/src/core/design-tokens.js +103 -0
- package/src/core/discover-pages.js +314 -0
- package/src/core/html-extractor.js +72 -3
- package/src/core/merge-css.js +407 -0
- package/src/core/multi-page-screenshot.js +377 -0
- package/src/core/rewrite-links.js +226 -0
- package/src/core/screenshot.js +18 -1
package/SKILL.md
CHANGED
|
@@ -104,6 +104,54 @@ python3 $HOME/.claude/skills/ui-ux-pro-max/scripts/search.py "animation hover" -
|
|
|
104
104
|
|
|
105
105
|
**Output:** desktop.png, tablet.png, mobile.png, source.html, source.css, source-raw.css
|
|
106
106
|
|
|
107
|
+
### design:clone-site
|
|
108
|
+
|
|
109
|
+
Multi-page site cloning with shared CSS and working navigation.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
/design:clone-site https://example.com
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Workflow:**
|
|
116
|
+
```bash
|
|
117
|
+
# Auto-discover pages from navigation
|
|
118
|
+
design-clone clone-site https://example.com
|
|
119
|
+
|
|
120
|
+
# Or specify pages manually
|
|
121
|
+
design-clone clone-site https://example.com --pages /,/about,/contact
|
|
122
|
+
|
|
123
|
+
# Options:
|
|
124
|
+
# --pages <paths> Comma-separated paths
|
|
125
|
+
# --max-pages <n> Limit pages (default: 10)
|
|
126
|
+
# --viewports <list> Viewports (default: desktop,tablet,mobile)
|
|
127
|
+
# --yes Skip confirmation
|
|
128
|
+
# --output <dir> Custom output directory
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Output Structure:**
|
|
132
|
+
```
|
|
133
|
+
cloned-designs/{timestamp}-{domain}/
|
|
134
|
+
├── analysis/ # Screenshots by viewport
|
|
135
|
+
│ ├── desktop/*.png
|
|
136
|
+
│ ├── tablet/*.png
|
|
137
|
+
│ └── mobile/*.png
|
|
138
|
+
├── pages/ # HTML with rewritten links
|
|
139
|
+
│ ├── index.html
|
|
140
|
+
│ ├── about.html
|
|
141
|
+
│ └── contact.html
|
|
142
|
+
├── styles.css # Merged + deduplicated CSS
|
|
143
|
+
└── manifest.json # Page metadata + mapping
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Features:**
|
|
147
|
+
- Auto-discovers pages from navigation (SPA-aware)
|
|
148
|
+
- Shared CSS with deduplication (15-30% reduction)
|
|
149
|
+
- Working internal links
|
|
150
|
+
- Progress reporting
|
|
151
|
+
- Graceful error handling (continues on page failures)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
107
155
|
### design:clone-px
|
|
108
156
|
|
|
109
157
|
Pixel-perfect clone with full asset extraction and AI analysis.
|
|
@@ -223,6 +271,11 @@ GEMINI_API_KEY=your-key # Optional: enables AI structure analysis
|
|
|
223
271
|
| screenshot.js | src/core/ | Capture screenshots + extract HTML/CSS |
|
|
224
272
|
| filter-css.js | src/core/ | Filter unused CSS rules |
|
|
225
273
|
| extract-assets.js | src/core/ | Download images, fonts, icons |
|
|
274
|
+
| discover-pages.js | src/core/ | Discover navigation links |
|
|
275
|
+
| multi-page-screenshot.js | src/core/ | Capture multiple pages |
|
|
276
|
+
| merge-css.js | src/core/ | Merge + deduplicate CSS |
|
|
277
|
+
| rewrite-links.js | src/core/ | Rewrite internal links |
|
|
278
|
+
| clone-site.js | bin/commands/ | Multi-page clone CLI |
|
|
226
279
|
| analyze-structure.py | src/ai/ | AI-powered structure analysis |
|
|
227
280
|
| extract-design-tokens.py | src/ai/ | Extract colors, typography, spacing |
|
|
228
281
|
| verify-menu.js | src/verification/ | Validate navigation structure |
|
package/bin/cli.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* design-clone init [--force] Install skill to ~/.claude/skills/
|
|
7
7
|
* design-clone verify Check installation status
|
|
8
|
+
* design-clone clone-site <url> [options] Clone multiple pages
|
|
8
9
|
* design-clone help Show help
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { init } from './commands/init.js';
|
|
12
13
|
import { verify } from './commands/verify.js';
|
|
13
14
|
import { help } from './commands/help.js';
|
|
15
|
+
import { cloneSite, parseArgs as parseCloneSiteArgs, showHelp as showCloneSiteHelp } from './commands/clone-site.js';
|
|
14
16
|
|
|
15
17
|
const [,, command, ...args] = process.argv;
|
|
16
18
|
|
|
@@ -25,6 +27,20 @@ async function main() {
|
|
|
25
27
|
case 'check':
|
|
26
28
|
await verify();
|
|
27
29
|
break;
|
|
30
|
+
case 'clone-site':
|
|
31
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
32
|
+
showCloneSiteHelp();
|
|
33
|
+
} else {
|
|
34
|
+
const options = parseCloneSiteArgs(args);
|
|
35
|
+
if (!options.url) {
|
|
36
|
+
console.error('Error: URL is required');
|
|
37
|
+
showCloneSiteHelp();
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const result = await cloneSite(options.url, options);
|
|
41
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
28
44
|
case 'help':
|
|
29
45
|
case '--help':
|
|
30
46
|
case '-h':
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone Site Command
|
|
3
|
+
*
|
|
4
|
+
* Clone multiple pages from a website with shared CSS and working navigation.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* design-clone clone-site <url> [options]
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --pages <paths> Comma-separated paths (e.g., /,/about,/contact)
|
|
11
|
+
* --max-pages <n> Maximum pages to clone (default: 10)
|
|
12
|
+
* --viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
13
|
+
* --yes Skip confirmation prompt
|
|
14
|
+
* --output <dir> Custom output directory
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs/promises';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
import { discoverPages } from '../../src/core/discover-pages.js';
|
|
22
|
+
import { captureMultiplePages } from '../../src/core/multi-page-screenshot.js';
|
|
23
|
+
import { mergeCssFiles } from '../../src/core/merge-css.js';
|
|
24
|
+
import { rewriteLinks, createPageManifest, rewriteAllLinks } from '../../src/core/rewrite-links.js';
|
|
25
|
+
import { extractDesignTokens } from '../../src/core/design-tokens.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate output directory name
|
|
29
|
+
* @param {string} url - Target URL
|
|
30
|
+
* @returns {string} Output directory path
|
|
31
|
+
*/
|
|
32
|
+
function generateOutputDir(url) {
|
|
33
|
+
const urlObj = new URL(url);
|
|
34
|
+
const domain = urlObj.hostname.replace(/^www\./, '');
|
|
35
|
+
const timestamp = new Date().toISOString()
|
|
36
|
+
.replace(/[-:]/g, '')
|
|
37
|
+
.replace('T', '-')
|
|
38
|
+
.slice(0, 13);
|
|
39
|
+
|
|
40
|
+
return `./cloned-designs/${timestamp}-${domain}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse CLI arguments
|
|
45
|
+
* @param {string[]} args - CLI arguments
|
|
46
|
+
* @returns {Object} Parsed options
|
|
47
|
+
*/
|
|
48
|
+
export function parseArgs(args) {
|
|
49
|
+
const options = {
|
|
50
|
+
url: null,
|
|
51
|
+
pages: null,
|
|
52
|
+
maxPages: 10,
|
|
53
|
+
viewports: ['desktop', 'tablet', 'mobile'],
|
|
54
|
+
skipConfirm: false,
|
|
55
|
+
output: null,
|
|
56
|
+
ai: false
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < args.length; i++) {
|
|
60
|
+
const arg = args[i];
|
|
61
|
+
|
|
62
|
+
if (arg === '--pages' && args[i + 1]) {
|
|
63
|
+
options.pages = args[++i].split(',').map(p => p.trim());
|
|
64
|
+
} else if (arg === '--max-pages' && args[i + 1]) {
|
|
65
|
+
options.maxPages = parseInt(args[++i], 10);
|
|
66
|
+
} else if (arg === '--viewports' && args[i + 1]) {
|
|
67
|
+
options.viewports = args[++i].split(',').map(v => v.trim());
|
|
68
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
69
|
+
options.skipConfirm = true;
|
|
70
|
+
} else if (arg === '--output' && args[i + 1]) {
|
|
71
|
+
options.output = args[++i];
|
|
72
|
+
} else if (arg === '--ai') {
|
|
73
|
+
options.ai = true;
|
|
74
|
+
} else if (!arg.startsWith('--') && !options.url) {
|
|
75
|
+
options.url = arg;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Clone multiple pages from a website
|
|
84
|
+
* @param {string} url - Target URL
|
|
85
|
+
* @param {Object} options - Clone options
|
|
86
|
+
* @returns {Promise<Object>} Clone result
|
|
87
|
+
*/
|
|
88
|
+
export async function cloneSite(url, options = {}) {
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
const {
|
|
91
|
+
pages: manualPages,
|
|
92
|
+
maxPages = 10,
|
|
93
|
+
viewports = ['desktop', 'tablet', 'mobile'],
|
|
94
|
+
skipConfirm = false,
|
|
95
|
+
output,
|
|
96
|
+
ai = false
|
|
97
|
+
} = options;
|
|
98
|
+
|
|
99
|
+
// Validate URL
|
|
100
|
+
let baseUrl;
|
|
101
|
+
try {
|
|
102
|
+
baseUrl = new URL(url);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Generate output directory
|
|
108
|
+
const outputDir = output || generateOutputDir(url);
|
|
109
|
+
|
|
110
|
+
console.error(`\n[clone-site] Target: ${url}`);
|
|
111
|
+
console.error(`[clone-site] Output: ${outputDir}`);
|
|
112
|
+
|
|
113
|
+
// Step 1: Discover or use manual pages
|
|
114
|
+
console.error('\n[1/6] Discovering pages...');
|
|
115
|
+
|
|
116
|
+
let pageList;
|
|
117
|
+
if (manualPages && manualPages.length > 0) {
|
|
118
|
+
// Use manual page list
|
|
119
|
+
pageList = {
|
|
120
|
+
success: true,
|
|
121
|
+
pages: manualPages.map(p => ({
|
|
122
|
+
path: p,
|
|
123
|
+
name: p === '/' ? 'Home' : p.replace(/^\//, '').replace(/-/g, ' '),
|
|
124
|
+
url: new URL(p, url).href
|
|
125
|
+
}))
|
|
126
|
+
};
|
|
127
|
+
console.error(` Using ${pageList.pages.length} manual pages`);
|
|
128
|
+
} else {
|
|
129
|
+
// Auto-discover
|
|
130
|
+
pageList = await discoverPages(url, { maxPages });
|
|
131
|
+
if (!pageList.success) {
|
|
132
|
+
console.error(` Warning: Discovery failed - ${pageList.error}`);
|
|
133
|
+
console.error(' Falling back to homepage only');
|
|
134
|
+
}
|
|
135
|
+
console.error(` Found ${pageList.pages.length} pages`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Show discovered pages
|
|
139
|
+
for (const page of pageList.pages) {
|
|
140
|
+
console.error(` - ${page.path} (${page.name})`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Step 2: Capture all pages
|
|
144
|
+
console.error('\n[2/6] Capturing pages...');
|
|
145
|
+
|
|
146
|
+
const captureResult = await captureMultiplePages(pageList.pages, {
|
|
147
|
+
outputDir,
|
|
148
|
+
viewports,
|
|
149
|
+
onProgress: (current, total, info) => {
|
|
150
|
+
console.error(` [${current}/${total}] ${info.status}: ${info.name}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!captureResult.success) {
|
|
155
|
+
throw new Error(`Capture failed: ${captureResult.error}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.error(` Captured ${captureResult.stats.successfulPages}/${captureResult.stats.totalPages} pages`);
|
|
159
|
+
console.error(` Screenshots: ${captureResult.stats.totalScreenshots}`);
|
|
160
|
+
|
|
161
|
+
// Step 3: Merge CSS files (prefer filtered CSS)
|
|
162
|
+
console.error('\n[3/6] Merging CSS...');
|
|
163
|
+
|
|
164
|
+
const mergedCssPath = path.join(outputDir, 'styles.css');
|
|
165
|
+
let mergeResult = { success: false };
|
|
166
|
+
|
|
167
|
+
// Use filtered CSS if available, fallback to raw CSS
|
|
168
|
+
const cssToMerge = captureResult.cssFilesFiltered?.length > 0
|
|
169
|
+
? captureResult.cssFilesFiltered
|
|
170
|
+
: captureResult.cssFiles;
|
|
171
|
+
|
|
172
|
+
const cssType = captureResult.cssFilesFiltered?.length > 0 ? 'filtered' : 'raw';
|
|
173
|
+
|
|
174
|
+
if (cssToMerge.length > 0) {
|
|
175
|
+
mergeResult = await mergeCssFiles(cssToMerge, mergedCssPath);
|
|
176
|
+
if (mergeResult.success) {
|
|
177
|
+
console.error(` Merged ${mergeResult.input.fileCount} ${cssType} files`);
|
|
178
|
+
console.error(` Reduction: ${mergeResult.stats.reduction}`);
|
|
179
|
+
} else {
|
|
180
|
+
console.error(` Warning: Merge failed - ${mergeResult.error}`);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
console.error(' No CSS files to merge');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Step 4: Extract design tokens (if --ai flag)
|
|
187
|
+
console.error('\n[4/6] Extracting design tokens...');
|
|
188
|
+
|
|
189
|
+
let hasTokens = false;
|
|
190
|
+
if (ai) {
|
|
191
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
192
|
+
const tokenResult = await extractDesignTokens(outputDir, mergedCssPath);
|
|
193
|
+
if (tokenResult.success) {
|
|
194
|
+
hasTokens = true;
|
|
195
|
+
console.error(` Created: tokens.css, design-tokens.json`);
|
|
196
|
+
} else {
|
|
197
|
+
console.error(` Warning: Token extraction failed - ${tokenResult.error}`);
|
|
198
|
+
if (tokenResult.hint) {
|
|
199
|
+
console.error(` Hint: ${tokenResult.hint}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
console.error(' Skipped: GEMINI_API_KEY not set');
|
|
204
|
+
console.error(' Hint: Set GEMINI_API_KEY in ~/.claude/.env for AI token extraction');
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
console.error(' Skipped (use --ai flag to enable)');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Step 5: Rewrite links
|
|
211
|
+
console.error('\n[5/6] Rewriting links...');
|
|
212
|
+
|
|
213
|
+
const manifest = createPageManifest(pageList.pages, {
|
|
214
|
+
hasTokens,
|
|
215
|
+
stats: {
|
|
216
|
+
totalPages: pageList.pages.length,
|
|
217
|
+
totalScreenshots: captureResult.stats.totalScreenshots,
|
|
218
|
+
cssReduction: mergeResult.stats?.reduction || '0%',
|
|
219
|
+
captureTimeMs: captureResult.stats.totalTimeMs
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Copy HTML files to pages/ directory and rewrite links
|
|
224
|
+
const pagesDir = path.join(outputDir, 'pages');
|
|
225
|
+
await fs.mkdir(pagesDir, { recursive: true });
|
|
226
|
+
|
|
227
|
+
for (const page of manifest.pages) {
|
|
228
|
+
const sourceHtml = path.join(outputDir, 'html', page.file);
|
|
229
|
+
const destHtml = path.join(pagesDir, page.file);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
let html = await fs.readFile(sourceHtml, 'utf-8');
|
|
233
|
+
html = rewriteLinks(html, manifest, {
|
|
234
|
+
baseUrl: url,
|
|
235
|
+
injectTokensCss: hasTokens
|
|
236
|
+
});
|
|
237
|
+
await fs.writeFile(destHtml, html, 'utf-8');
|
|
238
|
+
console.error(` Rewritten: ${page.file}`);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error(` Warning: Failed to rewrite ${page.file}: ${err.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Step 6: Generate manifest
|
|
245
|
+
console.error('\n[6/6] Generating manifest...');
|
|
246
|
+
|
|
247
|
+
const manifestPath = path.join(outputDir, 'manifest.json');
|
|
248
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
249
|
+
console.error(` Created: manifest.json`);
|
|
250
|
+
|
|
251
|
+
// Summary
|
|
252
|
+
const totalTime = Date.now() - startTime;
|
|
253
|
+
console.error(`\n[clone-site] Complete!`);
|
|
254
|
+
console.error(` Output: ${path.resolve(outputDir)}`);
|
|
255
|
+
console.error(` Pages: ${manifest.pages.length}`);
|
|
256
|
+
console.error(` Time: ${(totalTime / 1000).toFixed(1)}s`);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
success: true,
|
|
260
|
+
outputDir: path.resolve(outputDir),
|
|
261
|
+
manifest,
|
|
262
|
+
captureResult,
|
|
263
|
+
mergeResult,
|
|
264
|
+
totalTimeMs: totalTime
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Show help message
|
|
270
|
+
*/
|
|
271
|
+
export function showHelp() {
|
|
272
|
+
console.log(`
|
|
273
|
+
Usage: design-clone clone-site <url> [options]
|
|
274
|
+
|
|
275
|
+
Clone multiple pages from a website with shared CSS and working navigation.
|
|
276
|
+
|
|
277
|
+
Options:
|
|
278
|
+
--pages <paths> Comma-separated paths (e.g., /,/about,/contact)
|
|
279
|
+
--max-pages <n> Maximum pages to auto-discover (default: 10)
|
|
280
|
+
--viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
281
|
+
--yes Skip confirmation prompt
|
|
282
|
+
--output <dir> Custom output directory
|
|
283
|
+
--ai Extract design tokens using Gemini AI (requires GEMINI_API_KEY)
|
|
284
|
+
|
|
285
|
+
Examples:
|
|
286
|
+
design-clone clone-site https://example.com
|
|
287
|
+
design-clone clone-site https://example.com --max-pages 5
|
|
288
|
+
design-clone clone-site https://example.com --pages /,/about,/contact
|
|
289
|
+
design-clone clone-site https://example.com --ai
|
|
290
|
+
`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// CLI entry point
|
|
294
|
+
const isMainModule = process.argv[1] && (
|
|
295
|
+
process.argv[1].endsWith('clone-site.js') ||
|
|
296
|
+
process.argv[1].includes('clone-site')
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
if (isMainModule) {
|
|
300
|
+
const args = process.argv.slice(2);
|
|
301
|
+
|
|
302
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
303
|
+
showHelp();
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const options = parseArgs(args);
|
|
308
|
+
|
|
309
|
+
if (!options.url) {
|
|
310
|
+
console.error('Error: URL is required');
|
|
311
|
+
showHelp();
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
cloneSite(options.url, options)
|
|
316
|
+
.then(result => {
|
|
317
|
+
console.log(JSON.stringify(result, null, 2));
|
|
318
|
+
process.exit(0);
|
|
319
|
+
})
|
|
320
|
+
.catch(err => {
|
|
321
|
+
console.error(`\n[ERROR] ${err.message}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
});
|
|
324
|
+
}
|
package/bin/commands/help.js
CHANGED
|
@@ -9,16 +9,28 @@ design-clone - Claude Code skill for website design cloning
|
|
|
9
9
|
Usage:
|
|
10
10
|
design-clone init [options] Install skill to ~/.claude/skills/
|
|
11
11
|
design-clone verify Check installation status
|
|
12
|
+
design-clone clone-site <url> Clone multiple pages from a website
|
|
12
13
|
design-clone help Show this help
|
|
13
14
|
|
|
14
|
-
Options:
|
|
15
|
+
Init Options:
|
|
15
16
|
--force, -f Overwrite existing installation
|
|
16
17
|
--skip-deps Skip dependency installation
|
|
17
18
|
|
|
19
|
+
Clone-site Options:
|
|
20
|
+
--pages <paths> Comma-separated paths (e.g., /,/about,/contact)
|
|
21
|
+
--max-pages <n> Maximum pages to auto-discover (default: 10)
|
|
22
|
+
--viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
23
|
+
--yes Skip confirmation prompt
|
|
24
|
+
--output <dir> Custom output directory
|
|
25
|
+
--ai Extract design tokens using Gemini AI (requires GEMINI_API_KEY)
|
|
26
|
+
|
|
18
27
|
Examples:
|
|
19
|
-
design-clone init
|
|
20
|
-
design-clone init --force
|
|
21
|
-
design-clone verify
|
|
28
|
+
design-clone init # Install skill
|
|
29
|
+
design-clone init --force # Reinstall, overwrite existing
|
|
30
|
+
design-clone verify # Check if installed correctly
|
|
31
|
+
design-clone clone-site https://example.com
|
|
32
|
+
design-clone clone-site https://example.com --max-pages 5
|
|
33
|
+
design-clone clone-site https://example.com --pages /,/about,/contact
|
|
22
34
|
|
|
23
35
|
After installation:
|
|
24
36
|
1. Set GEMINI_API_KEY in ~/.claude/.env (optional, for AI analysis)
|
package/bin/commands/init.js
CHANGED
|
@@ -17,6 +17,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
17
17
|
const SKILL_SOURCE = path.resolve(__dirname, '../..');
|
|
18
18
|
// Destination: ~/.claude/skills/design-clone
|
|
19
19
|
const getSkillDest = () => path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude/skills/design-clone');
|
|
20
|
+
// Commands destination: ~/.claude/commands/design/
|
|
21
|
+
const getCommandsDest = () => path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude/commands/design');
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Install skill to Claude Code skills directory
|
|
@@ -93,6 +95,29 @@ export async function init(args) {
|
|
|
93
95
|
process.exit(1);
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
// Copy slash commands to ~/.claude/commands/design/
|
|
99
|
+
console.log('Installing slash commands...');
|
|
100
|
+
try {
|
|
101
|
+
const COMMANDS_DEST = getCommandsDest();
|
|
102
|
+
const COMMANDS_SOURCE = path.join(SKILL_SOURCE, 'commands/design');
|
|
103
|
+
|
|
104
|
+
// Ensure commands directory exists
|
|
105
|
+
await fs.mkdir(COMMANDS_DEST, { recursive: true });
|
|
106
|
+
|
|
107
|
+
// Copy command files
|
|
108
|
+
const commandFiles = await fs.readdir(COMMANDS_SOURCE).catch(() => []);
|
|
109
|
+
for (const file of commandFiles) {
|
|
110
|
+
if (file.endsWith('.md')) {
|
|
111
|
+
const src = path.join(COMMANDS_SOURCE, file);
|
|
112
|
+
const dest = path.join(COMMANDS_DEST, file);
|
|
113
|
+
await fs.copyFile(src, dest);
|
|
114
|
+
console.log(` Installed: /design:${file.replace('.md', '')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn(` Warning: Could not install slash commands: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
96
121
|
// Install dependencies
|
|
97
122
|
if (!skipDeps) {
|
|
98
123
|
// Node.js dependencies
|
|
@@ -156,6 +181,9 @@ export async function init(args) {
|
|
|
156
181
|
console.log('\n✓ design-clone skill installed successfully!\n');
|
|
157
182
|
console.log('Next steps:');
|
|
158
183
|
console.log(' 1. (Optional) Set GEMINI_API_KEY in ~/.claude/.env for AI analysis');
|
|
159
|
-
console.log(' 2. Use
|
|
184
|
+
console.log(' 2. Use slash commands in Claude Code:');
|
|
185
|
+
console.log(' /design:clone - Clone single page');
|
|
186
|
+
console.log(' /design:clone-site - Clone multiple pages');
|
|
187
|
+
console.log(' /design:clone-px - Pixel-perfect clone');
|
|
160
188
|
console.log('\nRun "design-clone verify" to check installation status.');
|
|
161
189
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Clone multiple pages from a website with shared CSS and navigation
|
|
3
|
+
argument-hint: [url] [--max-pages N] [--ai]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Clone multiple pages from this website with shared CSS and working navigation:
|
|
7
|
+
<url>$ARGUMENTS</url>
|
|
8
|
+
|
|
9
|
+
## Required Skills (Priority Order)
|
|
10
|
+
1. **`chrome-devtools`** - Multi-viewport screenshot capture
|
|
11
|
+
2. **`ai-multimodal`** - Gemini Vision for design token extraction (with --ai flag)
|
|
12
|
+
|
|
13
|
+
## Pipeline Overview
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
URL -> Page Discovery -> Multi-page Capture -> CSS Filtering & Merge -> Link Rewriting -> [AI Tokens] -> Output
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
### STEP 1: Run Clone-Site Command
|
|
22
|
+
|
|
23
|
+
Use the design-clone CLI to clone multiple pages:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Basic usage - auto-discovers pages from navigation
|
|
27
|
+
design-clone clone-site "$ARGUMENTS"
|
|
28
|
+
|
|
29
|
+
# With options
|
|
30
|
+
design-clone clone-site "$ARGUMENTS" --max-pages 5
|
|
31
|
+
|
|
32
|
+
# Specific pages
|
|
33
|
+
design-clone clone-site "$ARGUMENTS" --pages /,/about,/contact
|
|
34
|
+
|
|
35
|
+
# With AI design token extraction (requires GEMINI_API_KEY)
|
|
36
|
+
design-clone clone-site "$ARGUMENTS" --ai
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CLI Options
|
|
40
|
+
|
|
41
|
+
| Option | Default | Description |
|
|
42
|
+
|--------|---------|-------------|
|
|
43
|
+
| `--pages <paths>` | auto | Comma-separated paths (e.g., /,/about,/contact) |
|
|
44
|
+
| `--max-pages <n>` | 10 | Maximum pages to auto-discover |
|
|
45
|
+
| `--viewports <list>` | desktop,tablet,mobile | Viewport list |
|
|
46
|
+
| `--yes` | false | Skip confirmation prompt |
|
|
47
|
+
| `--output <dir>` | ./cloned-designs/{timestamp}-{domain} | Custom output directory |
|
|
48
|
+
| `--ai` | false | Extract design tokens using Gemini AI |
|
|
49
|
+
|
|
50
|
+
### STEP 2: Process Flow (Automatic)
|
|
51
|
+
|
|
52
|
+
The command executes these steps automatically:
|
|
53
|
+
|
|
54
|
+
1. **Page Discovery** - Crawls navigation links, respects same-domain
|
|
55
|
+
2. **Multi-page Capture** - Screenshots + HTML/CSS for each page
|
|
56
|
+
3. **CSS Merge** - Combines filtered CSS with deduplication (15-30% reduction)
|
|
57
|
+
4. **Link Rewriting** - Updates internal links to local .html files
|
|
58
|
+
5. **Token Extraction** (if --ai) - Gemini Vision extracts design tokens
|
|
59
|
+
6. **Manifest Generation** - Creates manifest.json with page metadata
|
|
60
|
+
|
|
61
|
+
### Output Structure
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
cloned-designs/{timestamp}-{domain}/
|
|
65
|
+
├── analysis/ # Screenshots by viewport
|
|
66
|
+
│ ├── desktop/
|
|
67
|
+
│ │ ├── index.png
|
|
68
|
+
│ │ ├── about.png
|
|
69
|
+
│ │ └── contact.png
|
|
70
|
+
│ ├── tablet/
|
|
71
|
+
│ └── mobile/
|
|
72
|
+
├── html/ # Raw extracted HTML (source)
|
|
73
|
+
├── css/ # Per-page CSS (raw + filtered)
|
|
74
|
+
├── pages/ # HTML with rewritten links
|
|
75
|
+
│ ├── index.html # Links to ../styles.css
|
|
76
|
+
│ ├── about.html
|
|
77
|
+
│ └── contact.html
|
|
78
|
+
├── styles.css # Merged + deduplicated CSS
|
|
79
|
+
├── tokens.css # Design tokens (if --ai)
|
|
80
|
+
├── design-tokens.json # Token data (if --ai)
|
|
81
|
+
├── manifest.json # Page metadata + mapping
|
|
82
|
+
└── capture-results.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### STEP 3: Review & Edit
|
|
86
|
+
|
|
87
|
+
After cloning:
|
|
88
|
+
|
|
89
|
+
1. **Test navigation** - Open `pages/index.html` in browser
|
|
90
|
+
2. **Verify CSS** - Check that styles.css covers all pages
|
|
91
|
+
3. **Check screenshots** - Review analysis/ for visual reference
|
|
92
|
+
4. **Edit tokens** (if --ai) - Modify tokens.css to customize design
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
- **Auto-discovers pages** from navigation (SPA-aware)
|
|
97
|
+
- **Shared CSS** with deduplication (15-30% reduction)
|
|
98
|
+
- **Filtered CSS** - Uses per-page filtered CSS, not raw
|
|
99
|
+
- **Working internal links** - Rewrites to local .html files
|
|
100
|
+
- **Progress reporting** - Shows capture progress
|
|
101
|
+
- **Graceful errors** - Continues on individual page failures
|
|
102
|
+
- **AI tokens** (optional) - Gemini Vision design token extraction
|
|
103
|
+
|
|
104
|
+
## Environment Variables
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Optional: For AI token extraction with --ai flag
|
|
108
|
+
GEMINI_API_KEY=your-key
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Examples
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Clone with auto-discovery (up to 10 pages)
|
|
115
|
+
/design:clone-site https://example.com
|
|
116
|
+
|
|
117
|
+
# Clone specific pages only
|
|
118
|
+
/design:clone-site https://example.com --pages /,/about,/pricing
|
|
119
|
+
|
|
120
|
+
# Clone with AI token extraction
|
|
121
|
+
/design:clone-site https://example.com --ai --max-pages 5
|
|
122
|
+
|
|
123
|
+
# Clone to custom directory
|
|
124
|
+
/design:clone-site https://example.com --output ./my-clone
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Error Handling
|
|
128
|
+
|
|
129
|
+
| Scenario | Behavior |
|
|
130
|
+
|----------|----------|
|
|
131
|
+
| Page fails to load | Continues with other pages, logs warning |
|
|
132
|
+
| No navigation found | Falls back to homepage only |
|
|
133
|
+
| CSS extraction fails | Uses raw CSS fallback |
|
|
134
|
+
| No GEMINI_API_KEY | Skips token extraction, logs hint |
|
|
135
|
+
| Python not found | Skips AI features, continues |
|