design-clone 2.1.0 → 2.3.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/README.md +13 -34
- package/SKILL.md +69 -45
- package/bin/cli.js +22 -4
- package/bin/commands/clone-site.js +31 -171
- package/bin/commands/help.js +19 -6
- package/bin/commands/init.js +9 -86
- package/bin/commands/uninstall.js +105 -0
- package/bin/commands/update.js +70 -0
- package/bin/commands/verify.js +7 -14
- package/bin/utils/paths.js +28 -0
- package/bin/utils/validate.js +2 -22
- package/bin/utils/version.js +23 -0
- package/docs/code-standards.md +789 -0
- package/docs/codebase-summary.md +533 -286
- package/docs/index.md +74 -0
- package/docs/project-overview-pdr.md +797 -0
- package/docs/system-architecture.md +718 -0
- package/package.json +14 -17
- package/src/ai/prompts/design-tokens/basic.md +80 -0
- package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
- package/src/ai/prompts/design-tokens/section.md +48 -0
- package/src/ai/prompts/design-tokens/with-css.md +87 -0
- package/src/ai/prompts/structure-analysis/basic.md +55 -0
- package/src/ai/prompts/structure-analysis/with-context.md +59 -0
- package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
- package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
- package/src/ai/prompts/ux-audit/aggregation.md +42 -0
- package/src/ai/prompts/ux-audit/desktop.md +92 -0
- package/src/ai/prompts/ux-audit/mobile.md +93 -0
- package/src/ai/prompts/ux-audit/tablet.md +92 -0
- package/src/core/animation/animation-extractor-ast.js +183 -0
- package/src/core/animation/animation-extractor-output.js +152 -0
- package/src/core/animation/animation-extractor.js +178 -0
- package/src/core/animation/state-capture-detection.js +200 -0
- package/src/core/animation/state-capture.js +193 -0
- package/src/core/capture/browser-context-pool.js +96 -0
- package/src/core/capture/multi-page-screenshot-page.js +110 -0
- package/src/core/capture/multi-page-screenshot.js +208 -0
- package/src/core/capture/screenshot-extraction.js +186 -0
- package/src/core/capture/screenshot-helpers.js +175 -0
- package/src/core/capture/screenshot-orchestrator.js +174 -0
- package/src/core/capture/screenshot-viewport.js +93 -0
- package/src/core/capture/screenshot.js +192 -0
- package/src/core/content/content-counter-dom.js +191 -0
- package/src/core/content/content-counter.js +76 -0
- package/src/core/css/breakpoint-detector.js +66 -0
- package/src/core/css/chromium-defaults.json +23 -0
- package/src/core/css/computed-style-extractor.js +102 -0
- package/src/core/css/css-chunker.js +103 -0
- package/src/core/css/filter-css-dead-code.js +120 -0
- package/src/core/css/filter-css-html-analyzer.js +110 -0
- package/src/core/css/filter-css-selector-matcher.js +172 -0
- package/src/core/css/filter-css.js +206 -0
- package/src/core/css/merge-css-atrule-processor.js +158 -0
- package/src/core/css/merge-css-file-io.js +68 -0
- package/src/core/css/merge-css.js +148 -0
- package/src/core/detection/framework-detector-routing.js +68 -0
- package/src/core/detection/framework-detector-signals.js +65 -0
- package/src/core/detection/framework-detector.js +198 -0
- package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
- package/src/core/dimension/dimension-extractor.js +317 -0
- package/src/core/dimension/dimension-output-ai-summary.js +111 -0
- package/src/core/dimension/dimension-output.js +173 -0
- package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
- package/src/core/dimension/dom-tree-analyzer.js +191 -0
- package/src/core/discovery/app-state-snapshot-capture.js +195 -0
- package/src/core/discovery/app-state-snapshot-utils.js +178 -0
- package/src/core/discovery/app-state-snapshot.js +131 -0
- package/src/core/discovery/discover-pages-routes.js +84 -0
- package/src/core/discovery/discover-pages-utils.js +177 -0
- package/src/core/discovery/discover-pages.js +191 -0
- package/src/core/html/html-extractor-inline-styler.js +70 -0
- package/src/core/html/html-extractor.js +147 -0
- package/src/core/html/semantic-enhancer-mappings.js +200 -0
- package/src/core/html/semantic-enhancer-page.js +148 -0
- package/src/core/html/semantic-enhancer.js +135 -0
- package/src/core/links/rewrite-links-css-rewriter.js +53 -0
- package/src/core/links/rewrite-links.js +173 -0
- package/src/core/media/asset-validator.js +118 -0
- package/src/core/media/extract-assets-downloader.js +187 -0
- package/src/core/media/extract-assets-page-scraper.js +115 -0
- package/src/core/media/extract-assets.js +159 -0
- package/src/core/media/video-capture-convert.js +200 -0
- package/src/core/media/video-capture.js +201 -0
- package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
- package/src/core/section/section-cropper-helpers.js +43 -0
- package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
- package/src/core/section/section-detector-strategies.js +139 -0
- package/src/core/section/section-detector-utils.js +100 -0
- package/src/core/section/section-detector.js +88 -0
- package/src/core/tests/test-section-cropper.js +2 -2
- package/src/core/tests/test-section-detector.js +2 -2
- package/src/post-process/enhance-assets.js +29 -4
- package/src/post-process/fetch-images-unsplash-client.js +123 -0
- package/src/post-process/fetch-images.js +60 -263
- package/src/post-process/inject-gosnap.js +88 -0
- package/src/post-process/inject-icons-svg-replacer.js +76 -0
- package/src/post-process/inject-icons.js +47 -200
- package/src/route-discoverers/base-discoverer-utils.js +137 -0
- package/src/route-discoverers/base-discoverer.js +29 -118
- package/src/route-discoverers/index.js +1 -1
- package/src/shared/config.js +38 -0
- package/src/shared/error-codes.js +31 -0
- package/src/shared/viewports.js +46 -0
- package/src/utils/browser.js +0 -7
- package/src/utils/helpers.js +4 -0
- package/src/utils/log.js +12 -0
- package/src/utils/playwright-loader.js +76 -0
- package/src/utils/playwright.js +3 -69
- package/src/utils/progress.js +32 -0
- package/src/verification/generate-audit-report-css-fixes.js +52 -0
- package/src/verification/generate-audit-report-sections.js +158 -0
- package/src/verification/generate-audit-report.js +5 -281
- package/src/verification/quality-scorer.js +92 -0
- package/src/verification/verify-footer-checks.js +103 -0
- package/src/verification/verify-footer-helpers.js +178 -0
- package/src/verification/verify-footer.js +23 -381
- package/src/verification/verify-header-checks.js +104 -0
- package/src/verification/verify-header-helpers.js +156 -0
- package/src/verification/verify-header.js +23 -365
- package/src/verification/verify-layout-report.js +101 -0
- package/src/verification/verify-layout.js +13 -259
- package/src/verification/verify-menu-checks.js +104 -0
- package/src/verification/verify-menu-helpers.js +112 -0
- package/src/verification/verify-menu.js +17 -285
- package/src/verification/verify-slider-checks.js +115 -0
- package/src/verification/verify-slider-constants.js +65 -0
- package/src/verification/verify-slider-helpers.js +164 -0
- package/src/verification/verify-slider.js +23 -414
- package/.env.example +0 -14
- package/docs/basic-clone.md +0 -63
- package/docs/cli-reference.md +0 -316
- package/docs/design-clone-architecture.md +0 -492
- package/docs/pixel-perfect.md +0 -117
- package/docs/project-roadmap.md +0 -382
- package/docs/troubleshooting.md +0 -170
- package/requirements.txt +0 -5
- package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
- package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
- package/src/ai/analyze-structure.py +0 -375
- package/src/ai/extract-design-tokens.py +0 -782
- package/src/ai/prompts/__init__.py +0 -2
- package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
- package/src/ai/prompts/design_tokens.py +0 -316
- package/src/ai/prompts/structure_analysis.py +0 -592
- package/src/ai/prompts/ux_audit.py +0 -198
- package/src/ai/ux-audit.js +0 -596
- package/src/core/animation-extractor.js +0 -526
- package/src/core/app-state-snapshot.js +0 -511
- package/src/core/content-counter.js +0 -342
- package/src/core/design-tokens.js +0 -103
- package/src/core/dimension-extractor.js +0 -438
- package/src/core/dimension-output.js +0 -305
- package/src/core/discover-pages.js +0 -542
- package/src/core/dom-tree-analyzer.js +0 -298
- package/src/core/extract-assets.js +0 -468
- package/src/core/filter-css.js +0 -499
- package/src/core/framework-detector.js +0 -538
- package/src/core/html-extractor.js +0 -212
- package/src/core/merge-css.js +0 -407
- package/src/core/multi-page-screenshot.js +0 -380
- package/src/core/rewrite-links.js +0 -226
- package/src/core/screenshot.js +0 -701
- package/src/core/section-detector.js +0 -386
- package/src/core/semantic-enhancer.js +0 -492
- package/src/core/state-capture.js +0 -598
- package/src/core/video-capture.js +0 -546
- package/src/utils/__init__.py +0 -16
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/env.py +0 -134
- /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
- /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
- /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Clone Site Command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Capture multi-viewport screenshots of multiple pages from a website.
|
|
5
|
+
* Screenshots are used by Claude Code vision to generate new HTML/CSS.
|
|
5
6
|
*
|
|
6
7
|
* Usage:
|
|
7
8
|
* design-clone clone-site <url> [options]
|
|
@@ -12,19 +13,13 @@
|
|
|
12
13
|
* --viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
13
14
|
* --yes Skip confirmation prompt
|
|
14
15
|
* --output <dir> Custom output directory
|
|
15
|
-
* --ux-audit Run UX audit using Gemini Vision (requires GEMINI_API_KEY)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import fs from 'fs/promises';
|
|
19
19
|
import path from 'path';
|
|
20
|
-
import { fileURLToPath } from 'url';
|
|
21
20
|
|
|
22
|
-
import { discoverPages } from '../../src/core/discover-pages.js';
|
|
23
|
-
import { captureMultiplePages } from '../../src/core/multi-page-screenshot.js';
|
|
24
|
-
import { mergeCssFiles } from '../../src/core/merge-css.js';
|
|
25
|
-
import { rewriteLinks, createPageManifest, rewriteAllLinks } from '../../src/core/rewrite-links.js';
|
|
26
|
-
import { extractDesignTokens } from '../../src/core/design-tokens.js';
|
|
27
|
-
import { runUXAudit } from '../../src/ai/ux-audit.js';
|
|
21
|
+
import { discoverPages } from '../../src/core/discovery/discover-pages.js';
|
|
22
|
+
import { captureMultiplePages } from '../../src/core/capture/multi-page-screenshot.js';
|
|
28
23
|
|
|
29
24
|
/**
|
|
30
25
|
* Generate output directory name
|
|
@@ -54,9 +49,7 @@ export function parseArgs(args) {
|
|
|
54
49
|
maxPages: 10,
|
|
55
50
|
viewports: ['desktop', 'tablet', 'mobile'],
|
|
56
51
|
skipConfirm: false,
|
|
57
|
-
output: null
|
|
58
|
-
ai: false,
|
|
59
|
-
uxAudit: false
|
|
52
|
+
output: null
|
|
60
53
|
};
|
|
61
54
|
|
|
62
55
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -72,10 +65,6 @@ export function parseArgs(args) {
|
|
|
72
65
|
options.skipConfirm = true;
|
|
73
66
|
} else if (arg === '--output' && args[i + 1]) {
|
|
74
67
|
options.output = args[++i];
|
|
75
|
-
} else if (arg === '--ai') {
|
|
76
|
-
options.ai = true;
|
|
77
|
-
} else if (arg === '--ux-audit') {
|
|
78
|
-
options.uxAudit = true;
|
|
79
68
|
} else if (!arg.startsWith('--') && !options.url) {
|
|
80
69
|
options.url = arg;
|
|
81
70
|
}
|
|
@@ -85,7 +74,7 @@ export function parseArgs(args) {
|
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
/**
|
|
88
|
-
* Clone multiple pages from a website
|
|
77
|
+
* Clone multiple pages from a website (screenshot-only capture)
|
|
89
78
|
* @param {string} url - Target URL
|
|
90
79
|
* @param {Object} options - Clone options
|
|
91
80
|
* @returns {Promise<Object>} Clone result
|
|
@@ -96,10 +85,7 @@ export async function cloneSite(url, options = {}) {
|
|
|
96
85
|
pages: manualPages,
|
|
97
86
|
maxPages = 10,
|
|
98
87
|
viewports = ['desktop', 'tablet', 'mobile'],
|
|
99
|
-
|
|
100
|
-
output,
|
|
101
|
-
ai = false,
|
|
102
|
-
uxAudit = false
|
|
88
|
+
output
|
|
103
89
|
} = options;
|
|
104
90
|
|
|
105
91
|
// Validate URL
|
|
@@ -117,11 +103,10 @@ export async function cloneSite(url, options = {}) {
|
|
|
117
103
|
console.error(`[clone-site] Output: ${outputDir}`);
|
|
118
104
|
|
|
119
105
|
// Step 1: Discover or use manual pages
|
|
120
|
-
console.error('\n[1/
|
|
106
|
+
console.error('\n[1/3] Discovering pages...');
|
|
121
107
|
|
|
122
108
|
let pageList;
|
|
123
109
|
if (manualPages && manualPages.length > 0) {
|
|
124
|
-
// Use manual page list
|
|
125
110
|
pageList = {
|
|
126
111
|
success: true,
|
|
127
112
|
pages: manualPages.map(p => ({
|
|
@@ -132,7 +117,6 @@ export async function cloneSite(url, options = {}) {
|
|
|
132
117
|
};
|
|
133
118
|
console.error(` Using ${pageList.pages.length} manual pages`);
|
|
134
119
|
} else {
|
|
135
|
-
// Auto-discover
|
|
136
120
|
pageList = await discoverPages(url, { maxPages });
|
|
137
121
|
if (!pageList.success) {
|
|
138
122
|
console.error(` Warning: Discovery failed - ${pageList.error}`);
|
|
@@ -141,13 +125,12 @@ export async function cloneSite(url, options = {}) {
|
|
|
141
125
|
console.error(` Found ${pageList.pages.length} pages`);
|
|
142
126
|
}
|
|
143
127
|
|
|
144
|
-
// Show discovered pages
|
|
145
128
|
for (const page of pageList.pages) {
|
|
146
129
|
console.error(` - ${page.path} (${page.name})`);
|
|
147
130
|
}
|
|
148
131
|
|
|
149
|
-
// Step 2: Capture all pages
|
|
150
|
-
console.error('\n[2/
|
|
132
|
+
// Step 2: Capture all pages (screenshots only)
|
|
133
|
+
console.error('\n[2/3] Capturing screenshots...');
|
|
151
134
|
|
|
152
135
|
const captureResult = await captureMultiplePages(pageList.pages, {
|
|
153
136
|
outputDir,
|
|
@@ -164,147 +147,30 @@ export async function cloneSite(url, options = {}) {
|
|
|
164
147
|
console.error(` Captured ${captureResult.stats.successfulPages}/${captureResult.stats.totalPages} pages`);
|
|
165
148
|
console.error(` Screenshots: ${captureResult.stats.totalScreenshots}`);
|
|
166
149
|
|
|
167
|
-
// Step 3:
|
|
168
|
-
console.error('\n[3/
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} else {
|
|
186
|
-
console.error(` Warning: Merge failed - ${mergeResult.error}`);
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
console.error(' No CSS files to merge');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Step 4: Extract design tokens (if --ai flag)
|
|
193
|
-
console.error('\n[4/7] Extracting design tokens...');
|
|
194
|
-
|
|
195
|
-
let hasTokens = false;
|
|
196
|
-
if (ai) {
|
|
197
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
198
|
-
const tokenResult = await extractDesignTokens(outputDir, mergedCssPath);
|
|
199
|
-
if (tokenResult.success) {
|
|
200
|
-
hasTokens = true;
|
|
201
|
-
console.error(` Created: tokens.css, design-tokens.json`);
|
|
202
|
-
} else {
|
|
203
|
-
console.error(` Warning: Token extraction failed - ${tokenResult.error}`);
|
|
204
|
-
if (tokenResult.hint) {
|
|
205
|
-
console.error(` Hint: ${tokenResult.hint}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
console.error(' Skipped: GEMINI_API_KEY not set');
|
|
210
|
-
console.error(' Hint: Set GEMINI_API_KEY in ~/.claude/.env for AI token extraction');
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
console.error(' Skipped (use --ai flag to enable)');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Step 5: UX Audit (if --ux-audit flag)
|
|
217
|
-
console.error('\n[5/7] Running UX audit...');
|
|
218
|
-
|
|
219
|
-
let uxAuditResult = null;
|
|
220
|
-
if (uxAudit) {
|
|
221
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
222
|
-
// Find homepage screenshots for audit
|
|
223
|
-
const screenshotDir = path.join(outputDir, 'screenshots');
|
|
224
|
-
const screenshotPaths = {};
|
|
225
|
-
|
|
226
|
-
for (const viewport of viewports) {
|
|
227
|
-
const screenshotPath = path.join(screenshotDir, `index-${viewport}.png`);
|
|
228
|
-
try {
|
|
229
|
-
await fs.access(screenshotPath);
|
|
230
|
-
screenshotPaths[viewport] = screenshotPath;
|
|
231
|
-
} catch {
|
|
232
|
-
// Try alternative naming
|
|
233
|
-
const altPath = path.join(screenshotDir, `${viewport}.png`);
|
|
234
|
-
try {
|
|
235
|
-
await fs.access(altPath);
|
|
236
|
-
screenshotPaths[viewport] = altPath;
|
|
237
|
-
} catch {
|
|
238
|
-
// Skip this viewport
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (Object.keys(screenshotPaths).length > 0) {
|
|
244
|
-
const analysisDir = path.join(outputDir, 'analysis');
|
|
245
|
-
await fs.mkdir(analysisDir, { recursive: true });
|
|
246
|
-
|
|
247
|
-
uxAuditResult = await runUXAudit(screenshotPaths, {
|
|
248
|
-
output: analysisDir,
|
|
249
|
-
verbose: true,
|
|
250
|
-
url
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
if (uxAuditResult.success) {
|
|
254
|
-
console.error(` UX Score: ${uxAuditResult.summary.uxScore}%`);
|
|
255
|
-
console.error(` Accessibility: ${uxAuditResult.summary.accessibilityScore}%`);
|
|
256
|
-
console.error(` Issues: ${uxAuditResult.summary.issueCount} (${uxAuditResult.summary.criticalCount} critical)`);
|
|
257
|
-
console.error(` Report: analysis/ux-audit.md`);
|
|
258
|
-
} else {
|
|
259
|
-
console.error(` Warning: UX audit failed - ${uxAuditResult.error}`);
|
|
260
|
-
}
|
|
261
|
-
} else {
|
|
262
|
-
console.error(' Skipped: No screenshots found for audit');
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
console.error(' Skipped: GEMINI_API_KEY not set');
|
|
266
|
-
console.error(' Hint: Set GEMINI_API_KEY in ~/.claude/.env for UX audit');
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
console.error(' Skipped (use --ux-audit flag to enable)');
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Step 6: Rewrite links
|
|
273
|
-
console.error('\n[6/7] Rewriting links...');
|
|
274
|
-
|
|
275
|
-
const manifest = createPageManifest(pageList.pages, {
|
|
276
|
-
hasTokens,
|
|
150
|
+
// Step 3: Generate manifest
|
|
151
|
+
console.error('\n[3/3] Generating manifest...');
|
|
152
|
+
|
|
153
|
+
const manifest = {
|
|
154
|
+
baseUrl: url,
|
|
155
|
+
capturedAt: new Date().toISOString(),
|
|
156
|
+
pages: captureResult.pages
|
|
157
|
+
.filter(p => p.success)
|
|
158
|
+
.map(p => ({
|
|
159
|
+
path: p.path,
|
|
160
|
+
name: p.name,
|
|
161
|
+
originalUrl: p.url,
|
|
162
|
+
screenshots: Object.fromEntries(
|
|
163
|
+
Object.entries(p.screenshots)
|
|
164
|
+
.filter(([, v]) => !v.failed)
|
|
165
|
+
.map(([vp, v]) => [vp, path.relative(outputDir, v.path)])
|
|
166
|
+
)
|
|
167
|
+
})),
|
|
277
168
|
stats: {
|
|
278
|
-
totalPages:
|
|
169
|
+
totalPages: captureResult.stats.totalPages,
|
|
279
170
|
totalScreenshots: captureResult.stats.totalScreenshots,
|
|
280
|
-
cssReduction: mergeResult.stats?.reduction || '0%',
|
|
281
171
|
captureTimeMs: captureResult.stats.totalTimeMs
|
|
282
172
|
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Copy HTML files to pages/ directory and rewrite links
|
|
286
|
-
const pagesDir = path.join(outputDir, 'pages');
|
|
287
|
-
await fs.mkdir(pagesDir, { recursive: true });
|
|
288
|
-
|
|
289
|
-
for (const page of manifest.pages) {
|
|
290
|
-
const sourceHtml = path.join(outputDir, 'html', page.file);
|
|
291
|
-
const destHtml = path.join(pagesDir, page.file);
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
let html = await fs.readFile(sourceHtml, 'utf-8');
|
|
295
|
-
html = rewriteLinks(html, manifest, {
|
|
296
|
-
baseUrl: url,
|
|
297
|
-
injectTokensCss: hasTokens
|
|
298
|
-
});
|
|
299
|
-
await fs.writeFile(destHtml, html, 'utf-8');
|
|
300
|
-
console.error(` Rewritten: ${page.file}`);
|
|
301
|
-
} catch (err) {
|
|
302
|
-
console.error(` Warning: Failed to rewrite ${page.file}: ${err.message}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Step 7: Generate manifest
|
|
307
|
-
console.error('\n[7/7] Generating manifest...');
|
|
173
|
+
};
|
|
308
174
|
|
|
309
175
|
const manifestPath = path.join(outputDir, 'manifest.json');
|
|
310
176
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
@@ -322,8 +188,6 @@ export async function cloneSite(url, options = {}) {
|
|
|
322
188
|
outputDir: path.resolve(outputDir),
|
|
323
189
|
manifest,
|
|
324
190
|
captureResult,
|
|
325
|
-
mergeResult,
|
|
326
|
-
uxAuditResult,
|
|
327
191
|
totalTimeMs: totalTime
|
|
328
192
|
};
|
|
329
193
|
}
|
|
@@ -335,7 +199,7 @@ export function showHelp() {
|
|
|
335
199
|
console.log(`
|
|
336
200
|
Usage: design-clone clone-site <url> [options]
|
|
337
201
|
|
|
338
|
-
|
|
202
|
+
Capture multi-viewport screenshots of multiple pages from a website.
|
|
339
203
|
|
|
340
204
|
Options:
|
|
341
205
|
--pages <paths> Comma-separated paths (e.g., /,/about,/contact)
|
|
@@ -343,15 +207,11 @@ Options:
|
|
|
343
207
|
--viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
344
208
|
--yes Skip confirmation prompt
|
|
345
209
|
--output <dir> Custom output directory
|
|
346
|
-
--ai Extract design tokens using Gemini AI (requires GEMINI_API_KEY)
|
|
347
|
-
--ux-audit Run UX audit using Gemini Vision (requires GEMINI_API_KEY)
|
|
348
210
|
|
|
349
211
|
Examples:
|
|
350
212
|
design-clone clone-site https://example.com
|
|
351
213
|
design-clone clone-site https://example.com --max-pages 5
|
|
352
214
|
design-clone clone-site https://example.com --pages /,/about,/contact
|
|
353
|
-
design-clone clone-site https://example.com --ai
|
|
354
|
-
design-clone clone-site https://example.com --ux-audit
|
|
355
215
|
`);
|
|
356
216
|
}
|
|
357
217
|
|
package/bin/commands/help.js
CHANGED
|
@@ -7,33 +7,46 @@ export function help() {
|
|
|
7
7
|
design-clone - Claude Code skill for website design cloning
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
|
-
design-clone init [options]
|
|
11
|
-
design-clone verify
|
|
12
|
-
design-clone
|
|
13
|
-
design-clone
|
|
10
|
+
design-clone init [options] Install skill to ~/.claude/skills/
|
|
11
|
+
design-clone verify Check installation status
|
|
12
|
+
design-clone update [options] Update to latest version
|
|
13
|
+
design-clone uninstall [options] Remove skill installation
|
|
14
|
+
design-clone clone-site <url> Clone multiple pages from a website
|
|
15
|
+
design-clone help Show this help
|
|
16
|
+
design-clone --version Show version
|
|
14
17
|
|
|
15
18
|
Init Options:
|
|
16
19
|
--force, -f Overwrite existing installation
|
|
17
20
|
--skip-deps Skip dependency installation
|
|
18
21
|
|
|
22
|
+
Update Options:
|
|
23
|
+
--force, -f Reinstall even if already up to date
|
|
24
|
+
--skip-deps Skip dependency installation
|
|
25
|
+
|
|
26
|
+
Uninstall Options:
|
|
27
|
+
--yes, -y Skip confirmation prompt
|
|
28
|
+
|
|
19
29
|
Clone-site Options:
|
|
20
30
|
--pages <paths> Comma-separated paths (e.g., /,/about,/contact)
|
|
21
31
|
--max-pages <n> Maximum pages to auto-discover (default: 10)
|
|
22
32
|
--viewports <list> Viewport list (default: desktop,tablet,mobile)
|
|
23
33
|
--yes Skip confirmation prompt
|
|
24
34
|
--output <dir> Custom output directory
|
|
25
|
-
--ai
|
|
35
|
+
--ai (Removed: AI analysis is now built-in via Claude Code)
|
|
26
36
|
|
|
27
37
|
Examples:
|
|
28
38
|
design-clone init # Install skill
|
|
29
39
|
design-clone init --force # Reinstall, overwrite existing
|
|
30
40
|
design-clone verify # Check if installed correctly
|
|
41
|
+
design-clone update # Update to latest version
|
|
42
|
+
design-clone uninstall # Remove skill installation
|
|
43
|
+
design-clone --version # Show version
|
|
31
44
|
design-clone clone-site https://example.com
|
|
32
45
|
design-clone clone-site https://example.com --max-pages 5
|
|
33
46
|
design-clone clone-site https://example.com --pages /,/about,/contact
|
|
34
47
|
|
|
35
48
|
After installation:
|
|
36
|
-
1.
|
|
49
|
+
1. AI analysis is built-in via Claude Code vision (no API key needed)
|
|
37
50
|
2. Use /design:clone or /design:clone-px in Claude Code
|
|
38
51
|
|
|
39
52
|
For more info: https://github.com/bienhoang/design-clone
|
package/bin/commands/init.js
CHANGED
|
@@ -9,16 +9,13 @@ import { exec as execCallback } from 'child_process';
|
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { copyRecursive, exists } from '../utils/copy.js';
|
|
11
11
|
import { runAllChecks } from '../utils/validate.js';
|
|
12
|
+
import { getSkillDest, getCommandsDest } from '../utils/paths.js';
|
|
12
13
|
|
|
13
14
|
const exec = promisify(execCallback);
|
|
14
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
|
|
16
17
|
// Source: package root (where SKILL.md is)
|
|
17
18
|
const SKILL_SOURCE = path.resolve(__dirname, '../..');
|
|
18
|
-
// Destination: ~/.claude/skills/design-clone
|
|
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');
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Install skill to Claude Code skills directory
|
|
@@ -36,7 +33,6 @@ export async function init(args) {
|
|
|
36
33
|
const checks = await runAllChecks();
|
|
37
34
|
|
|
38
35
|
console.log(` Node.js: ${checks.node.ok ? '✓' : '✗'} ${checks.node.message}`);
|
|
39
|
-
console.log(` Python: ${checks.python.ok ? '✓' : '✗'} ${checks.python.message}`);
|
|
40
36
|
console.log(` Chrome: ${checks.chrome.ok ? '✓' : '✗'} ${checks.chrome.message}`);
|
|
41
37
|
console.log('');
|
|
42
38
|
|
|
@@ -45,10 +41,6 @@ export async function init(args) {
|
|
|
45
41
|
process.exit(1);
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
if (!checks.python.ok) {
|
|
49
|
-
console.warn('Warning: Python 3.9+ not found. AI analysis features will be unavailable.');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
if (!checks.chrome.ok) {
|
|
53
45
|
console.warn('Warning: Chrome not found. Playwright will download Chromium during installation.');
|
|
54
46
|
}
|
|
@@ -129,90 +121,21 @@ export async function init(args) {
|
|
|
129
121
|
console.warn(` Warning: npm install failed: ${error.message}`);
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
// Install Playwright
|
|
133
|
-
console.log('Installing Playwright...');
|
|
124
|
+
// Install Playwright browsers (chromium only for smaller footprint)
|
|
125
|
+
console.log('Installing Playwright browsers...');
|
|
134
126
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(' Playwright already installed');
|
|
141
|
-
} catch {
|
|
142
|
-
// Need to install
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!playwrightInstalled) {
|
|
146
|
-
await exec('npm install playwright', { cwd: SKILL_DEST });
|
|
147
|
-
console.log(' Playwright installed');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Install Playwright browsers (chromium only for smaller footprint)
|
|
151
|
-
console.log('Installing Playwright browsers...');
|
|
152
|
-
try {
|
|
153
|
-
await exec('npx playwright install chromium', { cwd: SKILL_DEST, timeout: 300000 });
|
|
154
|
-
console.log(' Chromium browser installed');
|
|
155
|
-
} catch (browserError) {
|
|
156
|
-
console.warn(` Warning: Browser install failed: ${browserError.message}`);
|
|
157
|
-
console.warn(' Run manually: npx playwright install chromium');
|
|
158
|
-
}
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.warn(` Warning: Playwright install failed: ${error.message}`);
|
|
161
|
-
console.warn(' Run manually: npm install playwright && npx playwright install chromium');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Python dependencies
|
|
165
|
-
if (checks.python.ok) {
|
|
166
|
-
console.log('Installing Python dependencies...');
|
|
167
|
-
|
|
168
|
-
// Check if google-genai already installed
|
|
169
|
-
let alreadyInstalled = false;
|
|
170
|
-
try {
|
|
171
|
-
await exec('python3 -c "import google.genai"');
|
|
172
|
-
alreadyInstalled = true;
|
|
173
|
-
console.log(' Python packages already available');
|
|
174
|
-
} catch {
|
|
175
|
-
// Need to install
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!alreadyInstalled) {
|
|
179
|
-
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
180
|
-
const sharedVenvPip = path.join(HOME, '.claude/skills/.venv/bin/pip');
|
|
181
|
-
|
|
182
|
-
// Try installation methods in order of preference
|
|
183
|
-
const pipCommands = [
|
|
184
|
-
{ cmd: `"${sharedVenvPip}" install google-genai`, name: 'shared venv' },
|
|
185
|
-
{ cmd: 'pip3 install --user google-genai', name: 'pip3 --user' },
|
|
186
|
-
{ cmd: 'pip install --user google-genai', name: 'pip --user' }
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
let installed = false;
|
|
190
|
-
for (const { cmd, name } of pipCommands) {
|
|
191
|
-
try {
|
|
192
|
-
await exec(cmd);
|
|
193
|
-
console.log(` Python packages installed via ${name}`);
|
|
194
|
-
installed = true;
|
|
195
|
-
break;
|
|
196
|
-
} catch {
|
|
197
|
-
// Try next method
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!installed) {
|
|
202
|
-
console.warn(' Warning: Could not install Python packages automatically');
|
|
203
|
-
console.warn(' Try one of these manually:');
|
|
204
|
-
console.warn(` ${sharedVenvPip} install google-genai`);
|
|
205
|
-
console.warn(' pip3 install --user google-genai');
|
|
206
|
-
console.warn(' pip3 install --break-system-packages google-genai');
|
|
207
|
-
}
|
|
208
|
-
}
|
|
127
|
+
await exec('npx playwright install chromium', { cwd: SKILL_DEST, timeout: 300000 });
|
|
128
|
+
console.log(' Chromium browser installed');
|
|
129
|
+
} catch (browserError) {
|
|
130
|
+
console.warn(` Warning: Browser install failed: ${browserError.message}`);
|
|
131
|
+
console.warn(' Run manually: npx playwright install chromium');
|
|
209
132
|
}
|
|
210
133
|
}
|
|
211
134
|
|
|
212
135
|
// Success
|
|
213
136
|
console.log('\n✓ design-clone skill installed successfully!\n');
|
|
214
137
|
console.log('Next steps:');
|
|
215
|
-
console.log(' 1.
|
|
138
|
+
console.log(' 1. AI analysis is built-in (no API key needed)');
|
|
216
139
|
console.log(' 2. Use slash commands in Claude Code:');
|
|
217
140
|
console.log(' /design:clone - Clone single page');
|
|
218
141
|
console.log(' /design:clone-site - Clone multiple pages');
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall command - remove skill from ~/.claude/skills/
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { exists } from '../utils/copy.js';
|
|
8
|
+
import { getSkillDest, getCommandsDest } from '../utils/paths.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Prompt user for confirmation
|
|
12
|
+
* @param {string} message - Prompt message
|
|
13
|
+
* @returns {Promise<boolean>}
|
|
14
|
+
*/
|
|
15
|
+
function confirm(message) {
|
|
16
|
+
const rl = readline.createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stdout,
|
|
19
|
+
});
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
22
|
+
rl.close();
|
|
23
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Uninstall skill from Claude Code skills directory
|
|
30
|
+
* @param {string[]} args - CLI arguments
|
|
31
|
+
*/
|
|
32
|
+
export async function uninstall(args) {
|
|
33
|
+
const skipConfirm = args.includes('--yes') || args.includes('-y');
|
|
34
|
+
const SKILL_DEST = getSkillDest();
|
|
35
|
+
const COMMANDS_DEST = getCommandsDest();
|
|
36
|
+
|
|
37
|
+
console.log('design-clone uninstaller\n');
|
|
38
|
+
|
|
39
|
+
// Check what exists
|
|
40
|
+
const skillExists = await exists(SKILL_DEST);
|
|
41
|
+
const commandsExist = await exists(COMMANDS_DEST);
|
|
42
|
+
|
|
43
|
+
if (!skillExists && !commandsExist) {
|
|
44
|
+
console.log('Nothing to uninstall. design-clone is not installed.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show what will be removed
|
|
49
|
+
console.log('The following will be removed:');
|
|
50
|
+
if (skillExists) console.log(` - ${SKILL_DEST}`);
|
|
51
|
+
if (commandsExist) console.log(` - ${COMMANDS_DEST}`);
|
|
52
|
+
console.log('');
|
|
53
|
+
|
|
54
|
+
// Confirm
|
|
55
|
+
if (!skipConfirm) {
|
|
56
|
+
const confirmed = await confirm('Are you sure you want to uninstall?');
|
|
57
|
+
if (!confirmed) {
|
|
58
|
+
console.log('Uninstall cancelled.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Remove directories - attempt both before exiting on errors
|
|
65
|
+
const errors = [];
|
|
66
|
+
|
|
67
|
+
if (skillExists) {
|
|
68
|
+
try {
|
|
69
|
+
await fs.rm(SKILL_DEST, { recursive: true, force: true });
|
|
70
|
+
console.log(` Removed: ${SKILL_DEST}`);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
errors.push(`skill directory: ${error.message}`);
|
|
73
|
+
console.error(` Error removing skill directory: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (commandsExist) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.rm(COMMANDS_DEST, { recursive: true, force: true });
|
|
80
|
+
console.log(` Removed: ${COMMANDS_DEST}`);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
errors.push(`commands directory: ${error.message}`);
|
|
83
|
+
console.error(` Error removing commands directory: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (errors.length > 0) {
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Verify cleanup
|
|
92
|
+
const skillStillExists = await exists(SKILL_DEST);
|
|
93
|
+
const commandsStillExist = await exists(COMMANDS_DEST);
|
|
94
|
+
|
|
95
|
+
console.log('');
|
|
96
|
+
if (!skillStillExists && !commandsStillExist) {
|
|
97
|
+
console.log('design-clone uninstalled successfully.');
|
|
98
|
+
console.log('\nTo reinstall: design-clone init');
|
|
99
|
+
} else {
|
|
100
|
+
console.error('Warning: Some files may not have been removed completely.');
|
|
101
|
+
if (skillStillExists) console.error(` Still exists: ${SKILL_DEST}`);
|
|
102
|
+
if (commandsStillExist) console.error(` Still exists: ${COMMANDS_DEST}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update command - update installed skill to latest version
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { exists } from '../utils/copy.js';
|
|
8
|
+
import { getVersion } from '../utils/version.js';
|
|
9
|
+
import { getSkillDest } from '../utils/paths.js';
|
|
10
|
+
import { init } from './init.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read version from installed skill's package.json
|
|
14
|
+
* @returns {string|null} Installed version or null if not found
|
|
15
|
+
*/
|
|
16
|
+
function getInstalledVersion() {
|
|
17
|
+
const pkgPath = path.join(getSkillDest(), 'package.json');
|
|
18
|
+
try {
|
|
19
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
20
|
+
return pkg.version;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Update installed skill to current version
|
|
28
|
+
* @param {string[]} args - CLI arguments
|
|
29
|
+
*/
|
|
30
|
+
export async function update(args) {
|
|
31
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
32
|
+
const skipDeps = args.includes('--skip-deps');
|
|
33
|
+
const SKILL_DEST = getSkillDest();
|
|
34
|
+
|
|
35
|
+
console.log('design-clone updater\n');
|
|
36
|
+
|
|
37
|
+
// Check if installed
|
|
38
|
+
const installed = await exists(SKILL_DEST);
|
|
39
|
+
if (!installed) {
|
|
40
|
+
console.log('design-clone is not installed.');
|
|
41
|
+
console.log('Run "design-clone init" to install.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Compare versions
|
|
46
|
+
const currentVersion = getVersion();
|
|
47
|
+
const installedVersion = getInstalledVersion();
|
|
48
|
+
|
|
49
|
+
console.log(` Installed: v${installedVersion || 'unknown'}`);
|
|
50
|
+
console.log(` Available: v${currentVersion}`);
|
|
51
|
+
console.log('');
|
|
52
|
+
|
|
53
|
+
if (installedVersion === currentVersion && !force) {
|
|
54
|
+
console.log('Already up to date.');
|
|
55
|
+
console.log('Use --force to reinstall anyway.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build args for init
|
|
60
|
+
const initArgs = ['--force'];
|
|
61
|
+
if (skipDeps) initArgs.push('--skip-deps');
|
|
62
|
+
|
|
63
|
+
// Run init with --force
|
|
64
|
+
console.log('Updating...\n');
|
|
65
|
+
await init(initArgs);
|
|
66
|
+
|
|
67
|
+
// Verify new version
|
|
68
|
+
const newVersion = getInstalledVersion();
|
|
69
|
+
console.log(`\nUpdate complete: v${installedVersion || 'unknown'} -> v${newVersion}`);
|
|
70
|
+
}
|