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.
Files changed (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /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
- * Clone multiple pages from a website with shared CSS and working navigation.
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
- skipConfirm = false,
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/7] Discovering pages...');
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/7] Capturing pages...');
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: Merge CSS files (prefer filtered CSS)
168
- console.error('\n[3/7] Merging CSS...');
169
-
170
- const mergedCssPath = path.join(outputDir, 'styles.css');
171
- let mergeResult = { success: false };
172
-
173
- // Use filtered CSS if available, fallback to raw CSS
174
- const cssToMerge = captureResult.cssFilesFiltered?.length > 0
175
- ? captureResult.cssFilesFiltered
176
- : captureResult.cssFiles;
177
-
178
- const cssType = captureResult.cssFilesFiltered?.length > 0 ? 'filtered' : 'raw';
179
-
180
- if (cssToMerge.length > 0) {
181
- mergeResult = await mergeCssFiles(cssToMerge, mergedCssPath);
182
- if (mergeResult.success) {
183
- console.error(` Merged ${mergeResult.input.fileCount} ${cssType} files`);
184
- console.error(` Reduction: ${mergeResult.stats.reduction}`);
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: pageList.pages.length,
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
- Clone multiple pages from a website with shared CSS and working navigation.
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
 
@@ -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] Install skill to ~/.claude/skills/
11
- design-clone verify Check installation status
12
- design-clone clone-site <url> Clone multiple pages from a website
13
- design-clone help Show this help
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 Extract design tokens using Gemini AI (requires GEMINI_API_KEY)
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. Set GEMINI_API_KEY in ~/.claude/.env (optional, for AI analysis)
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
@@ -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
- // Check if playwright is already installed
136
- let playwrightInstalled = false;
137
- try {
138
- await exec('node -e "require.resolve(\'playwright\')"', { cwd: SKILL_DEST });
139
- playwrightInstalled = true;
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. (Optional) Set GEMINI_API_KEY in ~/.claude/.env for AI analysis');
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
+ }