design-clone 1.2.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 (174) hide show
  1. package/README.md +32 -39
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -106
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +11 -56
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +11 -16
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +24 -28
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +556 -0
  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 +20 -21
  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-extractor.js → css/css-extractor.js} +4 -4
  51. package/src/core/css/filter-css-dead-code.js +120 -0
  52. package/src/core/css/filter-css-html-analyzer.js +110 -0
  53. package/src/core/css/filter-css-selector-matcher.js +172 -0
  54. package/src/core/css/filter-css.js +206 -0
  55. package/src/core/css/merge-css-atrule-processor.js +158 -0
  56. package/src/core/css/merge-css-file-io.js +68 -0
  57. package/src/core/css/merge-css.js +148 -0
  58. package/src/core/detection/framework-detector-routing.js +68 -0
  59. package/src/core/detection/framework-detector-signals.js +65 -0
  60. package/src/core/detection/framework-detector.js +198 -0
  61. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  62. package/src/core/dimension/dimension-extractor.js +317 -0
  63. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  64. package/src/core/dimension/dimension-output.js +173 -0
  65. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  66. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  67. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  68. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  69. package/src/core/discovery/app-state-snapshot.js +131 -0
  70. package/src/core/discovery/discover-pages-routes.js +84 -0
  71. package/src/core/discovery/discover-pages-utils.js +177 -0
  72. package/src/core/discovery/discover-pages.js +191 -0
  73. package/src/core/html/html-extractor-inline-styler.js +70 -0
  74. package/src/core/html/html-extractor.js +147 -0
  75. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  76. package/src/core/html/semantic-enhancer-page.js +148 -0
  77. package/src/core/html/semantic-enhancer.js +135 -0
  78. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  79. package/src/core/links/rewrite-links.js +173 -0
  80. package/src/core/media/asset-validator.js +118 -0
  81. package/src/core/media/extract-assets-downloader.js +187 -0
  82. package/src/core/media/extract-assets-page-scraper.js +115 -0
  83. package/src/core/media/extract-assets.js +159 -0
  84. package/src/core/media/video-capture-convert.js +200 -0
  85. package/src/core/media/video-capture.js +201 -0
  86. package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +1 -1
  87. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +44 -46
  88. package/src/core/{page-readiness.js → page-prep/page-readiness.js} +8 -8
  89. package/src/core/section/section-cropper-helpers.js +43 -0
  90. package/src/core/section/section-cropper.js +132 -0
  91. package/src/core/section/section-detector-strategies.js +139 -0
  92. package/src/core/section/section-detector-utils.js +100 -0
  93. package/src/core/section/section-detector.js +88 -0
  94. package/src/core/tests/test-section-cropper.js +177 -0
  95. package/src/core/tests/test-section-detector.js +55 -0
  96. package/src/post-process/enhance-assets.js +29 -4
  97. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  98. package/src/post-process/fetch-images.js +60 -263
  99. package/src/post-process/inject-gosnap.js +88 -0
  100. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  101. package/src/post-process/inject-icons.js +47 -200
  102. package/src/route-discoverers/angular-discoverer.js +157 -0
  103. package/src/route-discoverers/astro-discoverer.js +123 -0
  104. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  105. package/src/route-discoverers/base-discoverer.js +153 -0
  106. package/src/route-discoverers/index.js +106 -0
  107. package/src/route-discoverers/next-discoverer.js +130 -0
  108. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  109. package/src/route-discoverers/react-discoverer.js +139 -0
  110. package/src/route-discoverers/svelte-discoverer.js +109 -0
  111. package/src/route-discoverers/universal-discoverer.js +227 -0
  112. package/src/route-discoverers/vue-discoverer.js +118 -0
  113. package/src/shared/config.js +38 -0
  114. package/src/shared/error-codes.js +31 -0
  115. package/src/shared/viewports.js +46 -0
  116. package/src/utils/browser.js +11 -44
  117. package/src/utils/helpers.js +4 -0
  118. package/src/utils/log.js +12 -0
  119. package/src/utils/playwright-loader.js +76 -0
  120. package/src/utils/playwright.js +147 -0
  121. package/src/utils/progress.js +32 -0
  122. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  123. package/src/verification/generate-audit-report-sections.js +158 -0
  124. package/src/verification/generate-audit-report.js +122 -0
  125. package/src/verification/quality-scorer.js +92 -0
  126. package/src/verification/verify-footer-checks.js +103 -0
  127. package/src/verification/verify-footer-helpers.js +178 -0
  128. package/src/verification/verify-footer.js +135 -0
  129. package/src/verification/verify-header-checks.js +104 -0
  130. package/src/verification/verify-header-helpers.js +156 -0
  131. package/src/verification/verify-header.js +144 -0
  132. package/src/verification/verify-layout-report.js +101 -0
  133. package/src/verification/verify-layout.js +14 -260
  134. package/src/verification/verify-menu-checks.js +104 -0
  135. package/src/verification/verify-menu-helpers.js +112 -0
  136. package/src/verification/verify-menu.js +18 -302
  137. package/src/verification/verify-slider-checks.js +115 -0
  138. package/src/verification/verify-slider-constants.js +65 -0
  139. package/src/verification/verify-slider-helpers.js +164 -0
  140. package/src/verification/verify-slider.js +142 -0
  141. package/.env.example +0 -14
  142. package/docs/basic-clone.md +0 -63
  143. package/docs/cli-reference.md +0 -118
  144. package/docs/design-clone-architecture.md +0 -275
  145. package/docs/pixel-perfect.md +0 -86
  146. package/docs/troubleshooting.md +0 -169
  147. package/requirements.txt +0 -5
  148. package/src/ai/analyze-structure.py +0 -305
  149. package/src/ai/extract-design-tokens.py +0 -439
  150. package/src/ai/prompts/__init__.py +0 -2
  151. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  152. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  153. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  154. package/src/ai/prompts/design_tokens.py +0 -183
  155. package/src/ai/prompts/structure_analysis.py +0 -273
  156. package/src/core/animation-extractor.js +0 -526
  157. package/src/core/design-tokens.js +0 -103
  158. package/src/core/dimension-extractor.js +0 -366
  159. package/src/core/dimension-output.js +0 -208
  160. package/src/core/discover-pages.js +0 -314
  161. package/src/core/extract-assets.js +0 -468
  162. package/src/core/filter-css.js +0 -499
  163. package/src/core/html-extractor.js +0 -171
  164. package/src/core/merge-css.js +0 -407
  165. package/src/core/multi-page-screenshot.js +0 -377
  166. package/src/core/rewrite-links.js +0 -226
  167. package/src/core/screenshot.js +0 -572
  168. package/src/core/state-capture.js +0 -602
  169. package/src/core/video-capture.js +0 -540
  170. package/src/utils/__init__.py +0 -16
  171. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  172. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  173. package/src/utils/env.py +0 -134
  174. package/src/utils/puppeteer.js +0 -281
@@ -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,12 +41,8 @@ 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
- console.warn('Warning: Chrome not found. Screenshots may not work without Puppeteer\'s bundled Chromium.');
45
+ console.warn('Warning: Chrome not found. Playwright will download Chromium during installation.');
54
46
  }
55
47
 
56
48
  // Check existing installation
@@ -129,58 +121,21 @@ export async function init(args) {
129
121
  console.warn(` Warning: npm install failed: ${error.message}`);
130
122
  }
131
123
 
132
- // Python dependencies
133
- if (checks.python.ok) {
134
- console.log('Installing Python dependencies...');
135
-
136
- // Check if google-genai already installed
137
- let alreadyInstalled = false;
138
- try {
139
- await exec('python3 -c "import google.genai"');
140
- alreadyInstalled = true;
141
- console.log(' Python packages already available');
142
- } catch {
143
- // Need to install
144
- }
145
-
146
- if (!alreadyInstalled) {
147
- const HOME = process.env.HOME || process.env.USERPROFILE || '';
148
- const sharedVenvPip = path.join(HOME, '.claude/skills/.venv/bin/pip');
149
-
150
- // Try installation methods in order of preference
151
- const pipCommands = [
152
- { cmd: `"${sharedVenvPip}" install google-genai`, name: 'shared venv' },
153
- { cmd: 'pip3 install --user google-genai', name: 'pip3 --user' },
154
- { cmd: 'pip install --user google-genai', name: 'pip --user' }
155
- ];
156
-
157
- let installed = false;
158
- for (const { cmd, name } of pipCommands) {
159
- try {
160
- await exec(cmd);
161
- console.log(` Python packages installed via ${name}`);
162
- installed = true;
163
- break;
164
- } catch {
165
- // Try next method
166
- }
167
- }
168
-
169
- if (!installed) {
170
- console.warn(' Warning: Could not install Python packages automatically');
171
- console.warn(' Try one of these manually:');
172
- console.warn(` ${sharedVenvPip} install google-genai`);
173
- console.warn(' pip3 install --user google-genai');
174
- console.warn(' pip3 install --break-system-packages google-genai');
175
- }
176
- }
124
+ // Install Playwright browsers (chromium only for smaller footprint)
125
+ console.log('Installing Playwright browsers...');
126
+ try {
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');
177
132
  }
178
133
  }
179
134
 
180
135
  // Success
181
136
  console.log('\n✓ design-clone skill installed successfully!\n');
182
137
  console.log('Next steps:');
183
- 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)');
184
139
  console.log(' 2. Use slash commands in Claude Code:');
185
140
  console.log(' /design:clone - Clone single page');
186
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
+ }
@@ -2,18 +2,16 @@
2
2
  * Verify command - check installation status
3
3
  */
4
4
 
5
- import fs from 'fs/promises';
6
5
  import path from 'path';
7
6
  import { exists } from '../utils/copy.js';
8
7
  import { runAllChecks } from '../utils/validate.js';
9
-
10
- const getSkillDir = () => path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude/skills/design-clone');
8
+ import { getSkillDest } from '../utils/paths.js';
11
9
 
12
10
  /**
13
11
  * Verify skill installation
14
12
  */
15
13
  export async function verify() {
16
- const SKILL_DIR = getSkillDir();
14
+ const SKILL_DIR = getSkillDest();
17
15
  let allOk = true;
18
16
 
19
17
  console.log('design-clone skill verification\n');
@@ -30,13 +28,10 @@ export async function verify() {
30
28
  // Check required files
31
29
  const requiredFiles = [
32
30
  'SKILL.md',
33
- 'src/core/screenshot.js',
34
- 'src/core/filter-css.js',
35
- 'src/ai/analyze-structure.py',
31
+ 'src/core/capture/screenshot.js',
32
+ 'src/core/css/filter-css.js',
36
33
  'src/utils/browser.js',
37
- 'src/utils/env.js',
38
- 'src/utils/env.py',
39
- 'requirements.txt'
34
+ 'src/utils/env.js'
40
35
  ];
41
36
 
42
37
  let filesOk = true;
@@ -63,16 +58,16 @@ export async function verify() {
63
58
  console.log('\nEnvironment:');
64
59
  const checks = await runAllChecks();
65
60
 
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}`);
61
+ console.log(` Node.js: ${checks.node.ok ? '✓' : '✗'} ${checks.node.message}`);
62
+ console.log(` Playwright: ${checks.playwright.ok ? '✓' : '✗'} ${checks.playwright.message}`);
63
+ console.log(` Chrome: ${checks.chrome.ok ? '✓' : ''} ${checks.chrome.message}${checks.playwright.ok ? ' (optional with Playwright)' : ''}`);
69
64
 
70
65
  if (!checks.node.ok) allOk = false;
66
+ if (!checks.playwright.ok && !checks.chrome.ok) allOk = false;
71
67
 
72
- // Check Gemini API key
68
+ // Check optional features
73
69
  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)'}`);
70
+ console.log(` AI analysis: built-in (Claude Code vision)`);
76
71
 
77
72
  // Check .env files
78
73
  const envLocations = [
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared path helpers for CLI commands
3
+ */
4
+
5
+ import path from 'path';
6
+
7
+ /**
8
+ * Get user home directory or exit if not available
9
+ * @returns {string} Home directory path
10
+ */
11
+ export function getHomeDir() {
12
+ const home = process.env.HOME || process.env.USERPROFILE;
13
+ if (!home) {
14
+ console.error('Error: HOME environment variable is not set.');
15
+ process.exit(1);
16
+ }
17
+ return home;
18
+ }
19
+
20
+ /** Skill installation directory: ~/.claude/skills/design-clone */
21
+ export function getSkillDest() {
22
+ return path.join(getHomeDir(), '.claude/skills/design-clone');
23
+ }
24
+
25
+ /** Slash commands directory: ~/.claude/commands/design */
26
+ export function getCommandsDest() {
27
+ return path.join(getHomeDir(), '.claude/commands/design');
28
+ }
@@ -26,25 +26,6 @@ export async function checkNode() {
26
26
  }
27
27
  }
28
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
29
  /**
49
30
  * Check Chrome/Chromium
50
31
  * @returns {Promise<{ok: boolean, path: string, message: string}>}
@@ -94,15 +75,31 @@ export async function checkChrome() {
94
75
  }
95
76
 
96
77
  /**
97
- * Check Puppeteer
78
+ * Check Playwright
98
79
  * @returns {Promise<{ok: boolean, message: string}>}
99
80
  */
100
- export async function checkPuppeteer() {
81
+ export async function checkPlaywright() {
101
82
  try {
102
- await import('puppeteer');
103
- return { ok: true, message: 'Puppeteer installed' };
83
+ const playwright = await import('playwright');
84
+ // Check if browsers are installed by checking chromium executable
85
+ if (playwright.chromium?.executablePath) {
86
+ try {
87
+ const fs = await import('fs/promises');
88
+ await fs.access(playwright.chromium.executablePath());
89
+ return { ok: true, message: 'Playwright installed with browsers' };
90
+ } catch {
91
+ return { ok: true, message: 'Playwright installed (browsers may need install)' };
92
+ }
93
+ }
94
+ return { ok: true, message: 'Playwright installed' };
104
95
  } catch {
105
- return { ok: false, message: 'Puppeteer not installed (optional)' };
96
+ // Try playwright-core
97
+ try {
98
+ await import('playwright-core');
99
+ return { ok: true, message: 'playwright-core installed (needs Chrome)' };
100
+ } catch {
101
+ return { ok: false, message: 'Playwright not installed' };
102
+ }
106
103
  }
107
104
  }
108
105
 
@@ -111,12 +108,11 @@ export async function checkPuppeteer() {
111
108
  * @returns {Promise<Object>}
112
109
  */
113
110
  export async function runAllChecks() {
114
- const [node, python, chrome, puppeteer] = await Promise.all([
111
+ const [node, chrome, playwright] = await Promise.all([
115
112
  checkNode(),
116
- checkPython(),
117
113
  checkChrome(),
118
- checkPuppeteer()
114
+ checkPlaywright()
119
115
  ]);
120
116
 
121
- return { node, python, chrome, puppeteer };
117
+ return { node, chrome, playwright };
122
118
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Version utility - reads version from package.json
3
+ */
4
+
5
+ import { readFileSync } from 'fs';
6
+ import { resolve, dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+
11
+ /**
12
+ * Get package version from package.json
13
+ * @returns {string} Version string (e.g., "2.1.0")
14
+ */
15
+ export function getVersion() {
16
+ try {
17
+ const pkgPath = resolve(__dirname, '../../package.json');
18
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
19
+ return pkg.version;
20
+ } catch {
21
+ return 'unknown';
22
+ }
23
+ }