design-clone 1.0.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/.env.example +14 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/SKILL.md +239 -0
- package/bin/cli.js +45 -0
- package/bin/commands/help.js +29 -0
- package/bin/commands/init.js +126 -0
- package/bin/commands/verify.js +99 -0
- package/bin/utils/copy.js +65 -0
- package/bin/utils/validate.js +122 -0
- package/docs/basic-clone.md +63 -0
- package/docs/cli-reference.md +94 -0
- package/docs/design-clone-architecture.md +247 -0
- package/docs/pixel-perfect.md +86 -0
- package/docs/troubleshooting.md +97 -0
- package/package.json +57 -0
- package/requirements.txt +5 -0
- package/src/ai/analyze-structure.py +305 -0
- package/src/ai/extract-design-tokens.py +439 -0
- package/src/ai/prompts/__init__.py +2 -0
- package/src/ai/prompts/design_tokens.py +183 -0
- package/src/ai/prompts/structure_analysis.py +273 -0
- package/src/core/cookie-handler.js +76 -0
- package/src/core/css-extractor.js +107 -0
- package/src/core/dimension-extractor.js +366 -0
- package/src/core/dimension-output.js +208 -0
- package/src/core/extract-assets.js +468 -0
- package/src/core/filter-css.js +499 -0
- package/src/core/html-extractor.js +102 -0
- package/src/core/lazy-loader.js +188 -0
- package/src/core/page-readiness.js +161 -0
- package/src/core/screenshot.js +380 -0
- package/src/post-process/enhance-assets.js +157 -0
- package/src/post-process/fetch-images.js +398 -0
- package/src/post-process/inject-icons.js +311 -0
- package/src/utils/__init__.py +16 -0
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/browser.js +103 -0
- package/src/utils/env.js +153 -0
- package/src/utils/env.py +134 -0
- package/src/utils/helpers.js +71 -0
- package/src/utils/puppeteer.js +281 -0
- package/src/verification/verify-layout.js +424 -0
- package/src/verification/verify-menu.js +422 -0
- package/templates/base.css +705 -0
- package/templates/base.html +293 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify command - check installation status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { exists } from '../utils/copy.js';
|
|
8
|
+
import { runAllChecks } from '../utils/validate.js';
|
|
9
|
+
|
|
10
|
+
const getSkillDir = () => path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude/skills/design-clone');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Verify skill installation
|
|
14
|
+
*/
|
|
15
|
+
export async function verify() {
|
|
16
|
+
const SKILL_DIR = getSkillDir();
|
|
17
|
+
let allOk = true;
|
|
18
|
+
|
|
19
|
+
console.log('design-clone skill verification\n');
|
|
20
|
+
|
|
21
|
+
// Check skill directory
|
|
22
|
+
console.log('Installation:');
|
|
23
|
+
const skillExists = await exists(SKILL_DIR);
|
|
24
|
+
console.log(` Skill directory: ${skillExists ? '✓' : '✗'} ${SKILL_DIR}`);
|
|
25
|
+
if (!skillExists) {
|
|
26
|
+
console.log('\n Skill not installed. Run: design-clone init');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check required files
|
|
31
|
+
const requiredFiles = [
|
|
32
|
+
'SKILL.md',
|
|
33
|
+
'multi-screenshot.js',
|
|
34
|
+
'filter-css.js',
|
|
35
|
+
'analyze-structure.py',
|
|
36
|
+
'lib/browser.js',
|
|
37
|
+
'lib/env.js',
|
|
38
|
+
'lib/env.py',
|
|
39
|
+
'requirements.txt'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
let filesOk = true;
|
|
43
|
+
for (const file of requiredFiles) {
|
|
44
|
+
const filePath = path.join(SKILL_DIR, file);
|
|
45
|
+
const fileExists = await exists(filePath);
|
|
46
|
+
if (!fileExists) {
|
|
47
|
+
console.log(` ${file}: ✗ missing`);
|
|
48
|
+
filesOk = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (filesOk) {
|
|
52
|
+
console.log(` Required files: ✓ all present`);
|
|
53
|
+
} else {
|
|
54
|
+
allOk = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check node_modules
|
|
58
|
+
const nodeModulesExists = await exists(path.join(SKILL_DIR, 'node_modules'));
|
|
59
|
+
console.log(` Node modules: ${nodeModulesExists ? '✓' : '✗'} ${nodeModulesExists ? 'installed' : 'not installed'}`);
|
|
60
|
+
if (!nodeModulesExists) allOk = false;
|
|
61
|
+
|
|
62
|
+
// Check environment
|
|
63
|
+
console.log('\nEnvironment:');
|
|
64
|
+
const checks = await runAllChecks();
|
|
65
|
+
|
|
66
|
+
console.log(` Node.js: ${checks.node.ok ? '✓' : '✗'} ${checks.node.message}`);
|
|
67
|
+
console.log(` Python: ${checks.python.ok ? '✓' : '✗'} ${checks.python.message}`);
|
|
68
|
+
console.log(` Chrome: ${checks.chrome.ok ? '✓' : '✗'} ${checks.chrome.message}`);
|
|
69
|
+
|
|
70
|
+
if (!checks.node.ok) allOk = false;
|
|
71
|
+
|
|
72
|
+
// Check Gemini API key
|
|
73
|
+
console.log('\nOptional:');
|
|
74
|
+
const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
75
|
+
console.log(` GEMINI_API_KEY: ${geminiKey ? '✓ set' : '○ not set (AI analysis disabled)'}`);
|
|
76
|
+
|
|
77
|
+
// Check .env files
|
|
78
|
+
const envLocations = [
|
|
79
|
+
path.join(SKILL_DIR, '.env'),
|
|
80
|
+
path.join(process.env.HOME || '', '.claude/skills/.env'),
|
|
81
|
+
path.join(process.env.HOME || '', '.claude/.env')
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const envPath of envLocations) {
|
|
85
|
+
const envExists = await exists(envPath);
|
|
86
|
+
if (envExists) {
|
|
87
|
+
console.log(` .env found: ${envPath}`);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Summary
|
|
93
|
+
console.log('\nStatus:');
|
|
94
|
+
if (allOk) {
|
|
95
|
+
console.log(' ✓ Ready to use! Try /design:clone in Claude Code');
|
|
96
|
+
} else {
|
|
97
|
+
console.log(' ✗ Some issues found. Run: design-clone init --force');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File copy utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Copy directory recursively
|
|
10
|
+
* @param {string} src - Source directory
|
|
11
|
+
* @param {string} dest - Destination directory
|
|
12
|
+
* @param {Object} options - Options
|
|
13
|
+
* @param {string[]} options.exclude - Patterns to exclude
|
|
14
|
+
*/
|
|
15
|
+
export async function copyRecursive(src, dest, options = {}) {
|
|
16
|
+
const exclude = options.exclude || [
|
|
17
|
+
'node_modules',
|
|
18
|
+
'.git',
|
|
19
|
+
'__pycache__',
|
|
20
|
+
'test-*.js',
|
|
21
|
+
'run-all-tests.js',
|
|
22
|
+
'.DS_Store',
|
|
23
|
+
'*.log'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
await fs.mkdir(dest, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const srcPath = path.join(src, entry.name);
|
|
32
|
+
const destPath = path.join(dest, entry.name);
|
|
33
|
+
|
|
34
|
+
// Check exclusions
|
|
35
|
+
const shouldExclude = exclude.some(pattern => {
|
|
36
|
+
if (pattern.includes('*')) {
|
|
37
|
+
const regex = new RegExp('^' + pattern.replace('*', '.*') + '$');
|
|
38
|
+
return regex.test(entry.name);
|
|
39
|
+
}
|
|
40
|
+
return entry.name === pattern;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (shouldExclude) continue;
|
|
44
|
+
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
await copyRecursive(srcPath, destPath, options);
|
|
47
|
+
} else {
|
|
48
|
+
await fs.copyFile(srcPath, destPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if path exists
|
|
55
|
+
* @param {string} filePath - Path to check
|
|
56
|
+
* @returns {Promise<boolean>}
|
|
57
|
+
*/
|
|
58
|
+
export async function exists(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(filePath);
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exec as execCallback } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
const exec = promisify(execCallback);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check Node.js version
|
|
12
|
+
* @returns {Promise<{ok: boolean, version: string, message: string}>}
|
|
13
|
+
*/
|
|
14
|
+
export async function checkNode() {
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await exec('node --version');
|
|
17
|
+
const version = stdout.trim();
|
|
18
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
19
|
+
|
|
20
|
+
if (major >= 18) {
|
|
21
|
+
return { ok: true, version, message: `Node.js ${version}` };
|
|
22
|
+
}
|
|
23
|
+
return { ok: false, version, message: `Node.js ${version} (requires >=18)` };
|
|
24
|
+
} catch {
|
|
25
|
+
return { ok: false, version: 'unknown', message: 'Node.js not found' };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check Python version
|
|
31
|
+
* @returns {Promise<{ok: boolean, version: string, message: string}>}
|
|
32
|
+
*/
|
|
33
|
+
export async function checkPython() {
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await exec('python3 --version');
|
|
36
|
+
const version = stdout.trim().replace('Python ', '');
|
|
37
|
+
const [major, minor] = version.split('.').map(Number);
|
|
38
|
+
|
|
39
|
+
if (major >= 3 && minor >= 9) {
|
|
40
|
+
return { ok: true, version, message: `Python ${version}` };
|
|
41
|
+
}
|
|
42
|
+
return { ok: false, version, message: `Python ${version} (requires >=3.9)` };
|
|
43
|
+
} catch {
|
|
44
|
+
return { ok: false, version: 'unknown', message: 'Python 3 not found' };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check Chrome/Chromium
|
|
50
|
+
* @returns {Promise<{ok: boolean, path: string, message: string}>}
|
|
51
|
+
*/
|
|
52
|
+
export async function checkChrome() {
|
|
53
|
+
const paths = {
|
|
54
|
+
darwin: [
|
|
55
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
56
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium'
|
|
57
|
+
],
|
|
58
|
+
linux: [
|
|
59
|
+
'/usr/bin/google-chrome',
|
|
60
|
+
'/usr/bin/chromium-browser',
|
|
61
|
+
'/usr/bin/chromium'
|
|
62
|
+
],
|
|
63
|
+
win32: [
|
|
64
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
65
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const platformPaths = paths[process.platform] || [];
|
|
70
|
+
|
|
71
|
+
for (const chromePath of platformPaths) {
|
|
72
|
+
try {
|
|
73
|
+
const fs = await import('fs/promises');
|
|
74
|
+
await fs.access(chromePath);
|
|
75
|
+
return { ok: true, path: chromePath, message: 'Chrome found' };
|
|
76
|
+
} catch {
|
|
77
|
+
// Continue to next path
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Try which/where command
|
|
82
|
+
try {
|
|
83
|
+
const cmd = process.platform === 'win32' ? 'where chrome' : 'which google-chrome || which chromium';
|
|
84
|
+
const { stdout } = await exec(cmd);
|
|
85
|
+
const found = stdout.trim().split('\n')[0];
|
|
86
|
+
if (found) {
|
|
87
|
+
return { ok: true, path: found, message: 'Chrome found' };
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Not found
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { ok: false, path: '', message: 'Chrome/Chromium not found' };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check Puppeteer
|
|
98
|
+
* @returns {Promise<{ok: boolean, message: string}>}
|
|
99
|
+
*/
|
|
100
|
+
export async function checkPuppeteer() {
|
|
101
|
+
try {
|
|
102
|
+
await import('puppeteer');
|
|
103
|
+
return { ok: true, message: 'Puppeteer installed' };
|
|
104
|
+
} catch {
|
|
105
|
+
return { ok: false, message: 'Puppeteer not installed (optional)' };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Run all checks
|
|
111
|
+
* @returns {Promise<Object>}
|
|
112
|
+
*/
|
|
113
|
+
export async function runAllChecks() {
|
|
114
|
+
const [node, python, chrome, puppeteer] = await Promise.all([
|
|
115
|
+
checkNode(),
|
|
116
|
+
checkPython(),
|
|
117
|
+
checkChrome(),
|
|
118
|
+
checkPuppeteer()
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
return { node, python, chrome, puppeteer };
|
|
122
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Basic Clone Workflow
|
|
2
|
+
|
|
3
|
+
Quick website design capture with screenshots and source extraction.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- Quick design reference/inspiration
|
|
8
|
+
- Simple single-page sites
|
|
9
|
+
- Initial exploration before pixel-perfect clone
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
### 1. Capture Screenshots + Extract Source
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node src/core/screenshot.js \
|
|
17
|
+
--url "https://example.com" \
|
|
18
|
+
--output ./cloned-design \
|
|
19
|
+
--extract-html \
|
|
20
|
+
--extract-css
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Filter Unused CSS (Optional)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node src/core/filter-css.js \
|
|
27
|
+
--html ./cloned-design/source.html \
|
|
28
|
+
--css ./cloned-design/source-raw.css \
|
|
29
|
+
--output ./cloned-design/source.css
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Output Files
|
|
33
|
+
|
|
34
|
+
| File | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| desktop.png | 1920x1080 viewport screenshot |
|
|
37
|
+
| tablet.png | 768x1024 viewport screenshot |
|
|
38
|
+
| mobile.png | 375x812 viewport screenshot |
|
|
39
|
+
| source.html | Cleaned HTML (inline styles removed) |
|
|
40
|
+
| source-raw.css | All extracted CSS (unfiltered) |
|
|
41
|
+
| source.css | Filtered CSS (after filter-css.js) |
|
|
42
|
+
|
|
43
|
+
## Common Options
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Custom viewports
|
|
47
|
+
--viewports '[{"width":1440,"height":900,"name":"laptop"}]'
|
|
48
|
+
|
|
49
|
+
# Full page capture
|
|
50
|
+
--full-page
|
|
51
|
+
|
|
52
|
+
# Wait for animations
|
|
53
|
+
--wait 3000
|
|
54
|
+
|
|
55
|
+
# Skip HTML extraction
|
|
56
|
+
--extract-html false
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Tips
|
|
60
|
+
|
|
61
|
+
- Add `--wait 2000` for sites with loading animations
|
|
62
|
+
- Use `--full-page` for long scrolling pages
|
|
63
|
+
- Check `source.html` for missing sections before pixel-perfect
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
All script options and parameters.
|
|
4
|
+
|
|
5
|
+
## screenshot.js
|
|
6
|
+
|
|
7
|
+
Core screenshot and extraction tool.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node src/core/screenshot.js [options]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Option | Type | Default | Description |
|
|
14
|
+
|--------|------|---------|-------------|
|
|
15
|
+
| --url | string | required | Target URL |
|
|
16
|
+
| --output | string | required | Output directory |
|
|
17
|
+
| --viewports | string | all | Comma-separated: desktop,tablet,mobile |
|
|
18
|
+
| --full-page | bool | true | Capture full page height |
|
|
19
|
+
| --max-size | number | 5 | Max file size in MB before compression |
|
|
20
|
+
| --headless | bool | false | Run in headless mode (desktop always uses headless) |
|
|
21
|
+
| --scroll-delay | number | 1500 | Pause time in ms between scroll steps for lazy content |
|
|
22
|
+
| --close | bool | false | Close browser after capture (false keeps session) |
|
|
23
|
+
| --extract-html | bool | false | Extract cleaned HTML |
|
|
24
|
+
| --extract-css | bool | false | Extract all CSS from page |
|
|
25
|
+
| --filter-unused | bool | true | Filter CSS to remove unused selectors |
|
|
26
|
+
| --verbose | bool | false | Verbose logging |
|
|
27
|
+
|
|
28
|
+
**Output**: JSON with screenshot paths and metadata. Includes `browserRestarts` count tracking for stability monitoring.
|
|
29
|
+
|
|
30
|
+
## filter-css.js
|
|
31
|
+
|
|
32
|
+
Remove unused CSS selectors.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node src/core/filter-css.js --html FILE --css FILE --output FILE [--verbose]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Option | Required | Description |
|
|
39
|
+
|--------|----------|-------------|
|
|
40
|
+
| --html | yes | Source HTML file |
|
|
41
|
+
| --css | yes | Raw CSS file |
|
|
42
|
+
| --output | yes | Filtered CSS output |
|
|
43
|
+
| --verbose | no | Show stats |
|
|
44
|
+
|
|
45
|
+
## analyze-structure.py
|
|
46
|
+
|
|
47
|
+
AI structure analysis with Gemini.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python src/ai/analyze-structure.py -s SCREENSHOT -o OUTPUT [options]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Option | Required | Description |
|
|
54
|
+
|--------|----------|-------------|
|
|
55
|
+
| -s, --screenshot | yes | Desktop screenshot |
|
|
56
|
+
| -o, --output | yes | Output directory |
|
|
57
|
+
| --html | no | Source HTML (improves accuracy) |
|
|
58
|
+
| --css | no | Source CSS (improves accuracy) |
|
|
59
|
+
| --model | no | Gemini model (default: gemini-2.5-flash) |
|
|
60
|
+
| -v, --verbose | no | Verbose output |
|
|
61
|
+
|
|
62
|
+
## extract-design-tokens.py
|
|
63
|
+
|
|
64
|
+
Extract colors, typography, spacing.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python src/ai/extract-design-tokens.py -s SCREENSHOT -o OUTPUT [options]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Same options as analyze-structure.py.
|
|
71
|
+
|
|
72
|
+
## extract-assets.js
|
|
73
|
+
|
|
74
|
+
Download images, fonts, icons.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node src/core/extract-assets.js --url URL --output DIR
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## verify-menu.js
|
|
81
|
+
|
|
82
|
+
Validate navigation structure.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node src/verification/verify-menu.js --html FILE
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## verify-layout.js
|
|
89
|
+
|
|
90
|
+
Verify layout consistency.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
node src/verification/verify-layout.js --html FILE
|
|
94
|
+
```
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Design Clone Skill Architecture
|
|
2
|
+
|
|
3
|
+
Technical architecture of the design-clone skill for Claude Code.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
design-clone/
|
|
9
|
+
├── SKILL.md # Entry point
|
|
10
|
+
├── bin/ # npm CLI tool
|
|
11
|
+
│ ├── cli.js
|
|
12
|
+
│ ├── commands/
|
|
13
|
+
│ └── utils/
|
|
14
|
+
├── src/
|
|
15
|
+
│ ├── core/ # Core scripts
|
|
16
|
+
│ ├── ai/ # AI analysis
|
|
17
|
+
│ ├── verification/ # Verification scripts
|
|
18
|
+
│ ├── post-process/ # Post-processing
|
|
19
|
+
│ └── utils/ # Shared utilities
|
|
20
|
+
├── docs/ # Documentation
|
|
21
|
+
├── templates/ # Output templates
|
|
22
|
+
└── tests/ # Test files
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Architecture Diagram
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ Claude Code │
|
|
30
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
31
|
+
│ │ /design:clone │ │ /design:clone-px │ │
|
|
32
|
+
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
33
|
+
└───────────┼─────────────────────┼───────────────────────────────┘
|
|
34
|
+
│ │
|
|
35
|
+
▼ ▼
|
|
36
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ SKILL.md │
|
|
38
|
+
│ - Activation triggers: clone, copy, replicate website │
|
|
39
|
+
│ - Commands: design:clone, design:clone-px │
|
|
40
|
+
│ - References: progressive disclosure │
|
|
41
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Core Scripts (src/) │
|
|
46
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
47
|
+
│ │ core/ │ │ core/ │ │ ai/ │ │
|
|
48
|
+
│ │ screenshot.js │ │ filter-css.js │ │ analyze-struct │ │
|
|
49
|
+
│ └────────┬────────┘ └────────┬────────┘ │ .py │ │
|
|
50
|
+
│ │ │ └────────┬────────┘ │
|
|
51
|
+
└───────────┼────────────────────┼───────────────────┼───────────┘
|
|
52
|
+
│ │ │
|
|
53
|
+
▼ ▼ ▼
|
|
54
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ src/utils/ (Shared) │
|
|
56
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
57
|
+
│ │ browser.js │ │ env.js │ │ env.py │ │
|
|
58
|
+
│ │ (facade) │ │ (Node.js) │ │ (Python) │ │
|
|
59
|
+
│ └──────┬──────┘ └─────────────┘ └─────────────┘ │
|
|
60
|
+
│ │ │
|
|
61
|
+
│ ▼ │
|
|
62
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
63
|
+
│ │ Browser Provider Selection │ │
|
|
64
|
+
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
|
|
65
|
+
│ │ │ chrome-devtools │ OR │ puppeteer.js (standalone)│ │ │
|
|
66
|
+
│ │ │ (if exists) │ │ (bundled fallback) │ │ │
|
|
67
|
+
│ │ └─────────────────┘ └─────────────────────────┘ │ │
|
|
68
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
69
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
73
|
+
│ Chrome/Chromium │
|
|
74
|
+
│ Auto-detected paths: │
|
|
75
|
+
│ - macOS: /Applications/Google Chrome.app/... │
|
|
76
|
+
│ - Linux: /usr/bin/google-chrome, /usr/bin/chromium │
|
|
77
|
+
│ - Windows: C:\Program Files\Google\Chrome\... │
|
|
78
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Component Details
|
|
82
|
+
|
|
83
|
+
### 1. Browser Abstraction Layer
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
src/utils/
|
|
87
|
+
├── browser.js # Facade - auto-selects provider
|
|
88
|
+
├── puppeteer.js # Standalone Puppeteer wrapper
|
|
89
|
+
├── helpers.js # CLI utilities (parseArgs, outputJSON)
|
|
90
|
+
├── env.js # Node.js env resolution
|
|
91
|
+
└── env.py # Python env resolution
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**browser.js** - Facade pattern for browser automation:
|
|
95
|
+
```javascript
|
|
96
|
+
// Auto-detects chrome-devtools skill or falls back to standalone
|
|
97
|
+
async function initProvider() {
|
|
98
|
+
if (fs.existsSync(CHROME_DEVTOOLS_PATH)) {
|
|
99
|
+
browserModule = await import(CHROME_DEVTOOLS_PATH);
|
|
100
|
+
providerName = 'chrome-devtools';
|
|
101
|
+
} else {
|
|
102
|
+
browserModule = await import('./puppeteer.js');
|
|
103
|
+
providerName = 'standalone';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**puppeteer.js** - Standalone browser wrapper:
|
|
109
|
+
- Cross-platform Chrome detection (macOS, Linux, Windows)
|
|
110
|
+
- Session persistence via WebSocket endpoint caching
|
|
111
|
+
- PID tracking for cleanup
|
|
112
|
+
|
|
113
|
+
### 2. Environment Resolution
|
|
114
|
+
|
|
115
|
+
Both Node.js and Python share same resolution order:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
1. process.env / os.environ (already set)
|
|
119
|
+
2. .env in current working directory
|
|
120
|
+
3. .env in skill directory
|
|
121
|
+
4. .env in ~/.claude/skills/
|
|
122
|
+
5. .env in ~/.claude/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Cross-platform support:**
|
|
126
|
+
- Windows: Uses `USERPROFILE` when `HOME` unavailable
|
|
127
|
+
- Python 3.9+: Uses `List[Path]` from typing module
|
|
128
|
+
|
|
129
|
+
### 3. Core Scripts
|
|
130
|
+
|
|
131
|
+
| Script | Location | Language | Purpose |
|
|
132
|
+
|--------|----------|----------|---------|
|
|
133
|
+
| screenshot.js | src/core/ | Node.js | Screenshot capture, HTML/CSS extraction |
|
|
134
|
+
| filter-css.js | src/core/ | Node.js | Remove unused CSS selectors |
|
|
135
|
+
| extract-assets.js | src/core/ | Node.js | Download images, fonts, icons |
|
|
136
|
+
| analyze-structure.py | src/ai/ | Python | Gemini AI structure analysis |
|
|
137
|
+
| extract-design-tokens.py | src/ai/ | Python | Color, typography, spacing extraction |
|
|
138
|
+
| verify-menu.js | src/verification/ | Node.js | Test responsive navigation |
|
|
139
|
+
| verify-layout.js | src/verification/ | Node.js | Verify layout consistency |
|
|
140
|
+
|
|
141
|
+
### 4. Post-Processing
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
src/post-process/
|
|
145
|
+
├── fetch-images.js # Fetch and optimize images
|
|
146
|
+
├── inject-icons.js # Replace icons with Font Awesome
|
|
147
|
+
└── enhance-assets.js # Enhance extracted assets
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 5. Progressive Disclosure
|
|
151
|
+
|
|
152
|
+
SKILL.md kept concise. Detailed docs in docs/:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
docs/
|
|
156
|
+
├── basic-clone.md # design:clone workflow
|
|
157
|
+
├── pixel-perfect.md # design:clone-px workflow
|
|
158
|
+
├── cli-reference.md # All script options
|
|
159
|
+
├── design-clone-architecture.md # This file
|
|
160
|
+
└── troubleshooting.md # Common issues
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 6. CLI Tool
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
bin/
|
|
167
|
+
├── cli.js # Entry point (bin: design-clone)
|
|
168
|
+
├── commands/
|
|
169
|
+
│ ├── init.js # Install skill to ~/.claude/skills/
|
|
170
|
+
│ ├── verify.js # Check installation status
|
|
171
|
+
│ └── help.js # Usage information
|
|
172
|
+
└── utils/
|
|
173
|
+
├── copy.js # Recursive file copy
|
|
174
|
+
└── validate.js # Environment checks
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Data Flow
|
|
178
|
+
|
|
179
|
+
### design:clone
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
URL → src/core/screenshot.js → Screenshots (3 viewports)
|
|
183
|
+
→ source.html (cleaned)
|
|
184
|
+
→ source-raw.css
|
|
185
|
+
→ src/core/filter-css.js → source.css (filtered)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### design:clone-px
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
URL → src/core/screenshot.js → Screenshots + HTML/CSS
|
|
192
|
+
→ src/core/filter-css.js → Filtered CSS
|
|
193
|
+
→ src/core/extract-assets.js → assets/ (images, fonts, icons)
|
|
194
|
+
→ src/ai/analyze-structure.py → structure.md (AI analysis)
|
|
195
|
+
→ src/ai/extract-design-tokens.py → tokens.json, tokens.css
|
|
196
|
+
→ src/verification/verify-menu.js → Menu validation report
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Output Structure
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
cloned-design/
|
|
203
|
+
├── desktop.png # 1920x1080
|
|
204
|
+
├── tablet.png # 768x1024
|
|
205
|
+
├── mobile.png # 375x812
|
|
206
|
+
├── source.html # Cleaned HTML
|
|
207
|
+
├── source.css # Filtered CSS
|
|
208
|
+
├── source-raw.css # Original CSS
|
|
209
|
+
├── structure.md # AI analysis (optional)
|
|
210
|
+
├── tokens.json # Design tokens
|
|
211
|
+
├── tokens.css # CSS variables
|
|
212
|
+
└── assets/
|
|
213
|
+
├── images/
|
|
214
|
+
├── fonts/
|
|
215
|
+
└── icons/
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Dependencies
|
|
219
|
+
|
|
220
|
+
### Node.js (package.json)
|
|
221
|
+
- `css-tree`: CSS parsing and filtering
|
|
222
|
+
- `puppeteer`: Browser automation (peerDep, optional)
|
|
223
|
+
|
|
224
|
+
### Python (requirements.txt)
|
|
225
|
+
- `google-genai`: Gemini AI for vision analysis
|
|
226
|
+
|
|
227
|
+
## Installation Methods
|
|
228
|
+
|
|
229
|
+
### npm (Recommended)
|
|
230
|
+
```bash
|
|
231
|
+
npm install -g design-clone
|
|
232
|
+
design-clone init
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Manual
|
|
236
|
+
```bash
|
|
237
|
+
cp -r design-clone ~/.claude/skills/design-clone
|
|
238
|
+
cd ~/.claude/skills/design-clone
|
|
239
|
+
npm install && pip install -r requirements.txt
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Security Considerations
|
|
243
|
+
|
|
244
|
+
1. **Path validation**: Prevents directory traversal
|
|
245
|
+
2. **CSS sanitization**: Removes XSS vectors (expression(), javascript:)
|
|
246
|
+
3. **Size limits**: 10MB max CSS input
|
|
247
|
+
4. **No secrets in output**: Scripts don't expose env vars
|