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 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
+ }
@@ -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 # Install skill
20
- design-clone init --force # Reinstall, overwrite existing
21
- design-clone verify # Check if installed correctly
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)
@@ -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 /design:clone or /design:clone-px in Claude Code');
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 |