extraktor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/chunk-5IH5TLAQ.js +91 -0
- package/dist/chunk-PHMSK7VD.js +6411 -0
- package/dist/chunk-VLLFGYUN.js +2773 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +3261 -0
- package/dist/design-md-generator-YMQOE2IW.js +502 -0
- package/dist/index.d.ts +2774 -0
- package/dist/index.js +58 -0
- package/dist/server-DR7RCM5S.js +328 -0
- package/dist/style-applier-BMHP6V57.js +1032 -0
- package/dist/theme-package-generator-E55BBBZN.js +412 -0
- package/package.json +99 -0
- package/skills/analyze-design.md +20 -0
- package/skills/apply-style.md +24 -0
- package/skills/clone-site.md +29 -0
- package/skills/design-md.md +29 -0
- package/skills/devtools-extract.md +30 -0
- package/skills/extract-tokens.md +21 -0
- package/skills/genome.md +31 -0
- package/skills/regen.md +32 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLogger
|
|
3
|
+
} from "./chunk-5IH5TLAQ.js";
|
|
4
|
+
|
|
5
|
+
// src/generators/theme-package/theme-package-generator.ts
|
|
6
|
+
import fs from "fs-extra";
|
|
7
|
+
import path from "path";
|
|
8
|
+
var logger = createLogger({ prefix: "theme-generator" });
|
|
9
|
+
var ThemePackageGenerator = class {
|
|
10
|
+
async generate(tokens, options) {
|
|
11
|
+
const { outputDir, packageName } = options;
|
|
12
|
+
logger.info(`Generating theme package: ${packageName}`);
|
|
13
|
+
await fs.ensureDir(outputDir);
|
|
14
|
+
await this.generatePackageJson(outputDir, packageName);
|
|
15
|
+
await this.generateTypes(outputDir, tokens);
|
|
16
|
+
await this.generateThemeConfig(outputDir, tokens, options);
|
|
17
|
+
if (options.includeCSSVariables !== false) {
|
|
18
|
+
await this.generateCSSVariables(outputDir, tokens, options);
|
|
19
|
+
}
|
|
20
|
+
if (options.includeTailwindPreset !== false) {
|
|
21
|
+
await this.generateTailwindPreset(outputDir, tokens, options);
|
|
22
|
+
}
|
|
23
|
+
if (options.includeComponents) {
|
|
24
|
+
await this.generateComponentUtils(outputDir, tokens);
|
|
25
|
+
}
|
|
26
|
+
await this.generateReadme(outputDir, packageName, options);
|
|
27
|
+
await this.generateTsConfig(outputDir);
|
|
28
|
+
logger.info(`\u2705 Theme package generated at: ${outputDir}`);
|
|
29
|
+
}
|
|
30
|
+
async generatePackageJson(outputDir, packageName) {
|
|
31
|
+
const packageJson = {
|
|
32
|
+
name: packageName,
|
|
33
|
+
version: "1.0.0",
|
|
34
|
+
description: "Design system theme package generated by extraktor",
|
|
35
|
+
main: "dist/index.js",
|
|
36
|
+
types: "dist/index.d.ts",
|
|
37
|
+
type: "module",
|
|
38
|
+
exports: {
|
|
39
|
+
".": {
|
|
40
|
+
import: "./dist/index.js",
|
|
41
|
+
types: "./dist/index.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"./theme": {
|
|
44
|
+
import: "./dist/theme.js",
|
|
45
|
+
types: "./dist/theme.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./tailwind": {
|
|
48
|
+
import: "./dist/tailwind.js",
|
|
49
|
+
types: "./dist/tailwind.d.ts"
|
|
50
|
+
},
|
|
51
|
+
"./css": "./dist/theme.css"
|
|
52
|
+
},
|
|
53
|
+
files: ["dist", "README.md"],
|
|
54
|
+
scripts: {
|
|
55
|
+
build: "tsup src/index.ts src/theme.ts src/tailwind.ts --format esm --dts",
|
|
56
|
+
dev: "tsup src/index.ts src/theme.ts src/tailwind.ts --format esm --dts --watch",
|
|
57
|
+
prepublishOnly: "npm run build"
|
|
58
|
+
},
|
|
59
|
+
peerDependencies: {
|
|
60
|
+
tailwindcss: "^4.0.0"
|
|
61
|
+
},
|
|
62
|
+
devDependencies: {
|
|
63
|
+
tsup: "^8.5.1",
|
|
64
|
+
typescript: "^5.9.3",
|
|
65
|
+
tailwindcss: "^4.1.18"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
await fs.writeJson(path.join(outputDir, "package.json"), packageJson, { spaces: 2 });
|
|
69
|
+
}
|
|
70
|
+
async generateTypes(outputDir, tokens) {
|
|
71
|
+
const srcDir = path.join(outputDir, "src");
|
|
72
|
+
await fs.ensureDir(srcDir);
|
|
73
|
+
const colorNames = tokens.colors?.map((c) => `'${c.name}'`).join(" | ") || "string";
|
|
74
|
+
const fontNames = tokens.fonts?.map((f) => `'${f.name}'`).join(" | ") || "string";
|
|
75
|
+
const typesContent = `// Auto-generated theme types
|
|
76
|
+
export interface ThemeColors {
|
|
77
|
+
${tokens.colors?.map((c) => `${c.name}: '${c.value}';`).join("\n ") || ""}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ThemeFonts {
|
|
81
|
+
${tokens.fonts?.map((f) => `${f.name}: string;`).join("\n ") || ""}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ThemeSpacing {
|
|
85
|
+
${tokens.spacing?.map((s) => `${s.name}: '${s.value}';`).join("\n ") || ""}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ColorName = ${colorNames};
|
|
89
|
+
export type FontName = ${fontNames};
|
|
90
|
+
|
|
91
|
+
export interface Theme {
|
|
92
|
+
colors: ThemeColors;
|
|
93
|
+
fonts: ThemeFonts;
|
|
94
|
+
spacing: ThemeSpacing;
|
|
95
|
+
borderRadius: Record<string, string>;
|
|
96
|
+
shadows: Record<string, string>;
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
await fs.writeFile(path.join(srcDir, "types.ts"), typesContent);
|
|
100
|
+
}
|
|
101
|
+
async generateThemeConfig(outputDir, tokens, options) {
|
|
102
|
+
const srcDir = path.join(outputDir, "src");
|
|
103
|
+
const colors = options.customColors ? this.mergeColors(tokens.colors || [], options.customColors) : tokens.colors || [];
|
|
104
|
+
const themeContent = `import type { Theme } from './types.js';
|
|
105
|
+
|
|
106
|
+
export const theme: Theme = {
|
|
107
|
+
colors: {
|
|
108
|
+
${colors.map((c) => `${c.name}: '${c.value}',`).join("\n ")}
|
|
109
|
+
},
|
|
110
|
+
fonts: {
|
|
111
|
+
${tokens.fonts?.map((f) => `${f.name}: '${f.name}',`).join("\n ") || ""}
|
|
112
|
+
},
|
|
113
|
+
spacing: {
|
|
114
|
+
${tokens.spacing?.map((s) => `${s.name}: '${s.value}',`).join("\n ") || ""}
|
|
115
|
+
},
|
|
116
|
+
borderRadius: {
|
|
117
|
+
${tokens.borderRadius?.map((br) => `${br.name}: '${br.value}',`).join("\n ") || ""}
|
|
118
|
+
},
|
|
119
|
+
shadows: {
|
|
120
|
+
${tokens.shadows?.map((sh) => `${sh.name}: '${sh.value}',`).join("\n ") || ""}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
${options.darkMode ? this.generateDarkTheme(colors) : ""}
|
|
125
|
+
|
|
126
|
+
export default theme;
|
|
127
|
+
`;
|
|
128
|
+
await fs.writeFile(path.join(srcDir, "theme.ts"), themeContent);
|
|
129
|
+
const indexContent = `export * from './types.js';
|
|
130
|
+
export * from './theme.js';
|
|
131
|
+
`;
|
|
132
|
+
await fs.writeFile(path.join(srcDir, "index.ts"), indexContent);
|
|
133
|
+
}
|
|
134
|
+
mergeColors(colors, customColors) {
|
|
135
|
+
const colorMap = new Map(colors.map((c) => [c.name, c]));
|
|
136
|
+
for (const [name, value] of Object.entries(customColors)) {
|
|
137
|
+
colorMap.set(name, { name, value, type: "color" });
|
|
138
|
+
}
|
|
139
|
+
return Array.from(colorMap.values());
|
|
140
|
+
}
|
|
141
|
+
generateDarkTheme(colors) {
|
|
142
|
+
return `
|
|
143
|
+
export const darkTheme: Theme = {
|
|
144
|
+
...theme,
|
|
145
|
+
colors: {
|
|
146
|
+
...theme.colors,
|
|
147
|
+
${colors.filter((c) => c.name.includes("background") || c.name.includes("foreground")).map((c) => `${c.name}: '${this.invertColor(c.value)}',`).join("\n ")}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
invertColor(hex) {
|
|
153
|
+
const num = parseInt(hex.replace("#", ""), 16);
|
|
154
|
+
const inverted = (16777215 ^ num).toString(16).padStart(6, "0");
|
|
155
|
+
return `#${inverted}`;
|
|
156
|
+
}
|
|
157
|
+
async generateCSSVariables(outputDir, tokens, options) {
|
|
158
|
+
const distDir = path.join(outputDir, "dist");
|
|
159
|
+
await fs.ensureDir(distDir);
|
|
160
|
+
const colors = options.customColors ? this.mergeColors(tokens.colors || [], options.customColors) : tokens.colors || [];
|
|
161
|
+
let cssContent = `:root {
|
|
162
|
+
/* Colors */
|
|
163
|
+
${colors.map((c) => `--color-${c.name}: ${c.value};`).join("\n ")}
|
|
164
|
+
|
|
165
|
+
/* Fonts */
|
|
166
|
+
${tokens.fonts?.map((f) => `--font-${f.name}: '${f.name}', sans-serif;`).join("\n ") || ""}
|
|
167
|
+
|
|
168
|
+
/* Spacing */
|
|
169
|
+
${tokens.spacing?.map((s) => `--spacing-${s.name}: ${s.value};`).join("\n ") || ""}
|
|
170
|
+
|
|
171
|
+
/* Border Radius */
|
|
172
|
+
${tokens.borderRadius?.map((br) => `--radius-${br.name}: ${br.value};`).join("\n ") || ""}
|
|
173
|
+
|
|
174
|
+
/* Shadows */
|
|
175
|
+
${tokens.shadows?.map((sh) => `--shadow-${sh.name}: ${sh.value};`).join("\n ") || ""}
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
if (options.darkMode) {
|
|
179
|
+
cssContent += `
|
|
180
|
+
[data-theme="dark"] {
|
|
181
|
+
/* Dark mode color overrides */
|
|
182
|
+
${colors.filter((c) => c.name.includes("background") || c.name.includes("foreground")).map((c) => `--color-${c.name}: ${this.invertColor(c.value)};`).join("\n ")}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
await fs.writeFile(path.join(distDir, "theme.css"), cssContent);
|
|
187
|
+
}
|
|
188
|
+
async generateTailwindPreset(outputDir, tokens, options) {
|
|
189
|
+
const srcDir = path.join(outputDir, "src");
|
|
190
|
+
const colors = options.customColors ? this.mergeColors(tokens.colors || [], options.customColors) : tokens.colors || [];
|
|
191
|
+
const tailwindContent = `import type { Config } from 'tailwindcss';
|
|
192
|
+
|
|
193
|
+
const preset: Partial<Config> = {
|
|
194
|
+
theme: {
|
|
195
|
+
extend: {
|
|
196
|
+
colors: {
|
|
197
|
+
${colors.map((c) => `'${c.name}': '${c.value}',`).join("\n ")}
|
|
198
|
+
},
|
|
199
|
+
fontFamily: {
|
|
200
|
+
${tokens.fonts?.map((f) => `'${f.name}': ['${f.name}', 'sans-serif'],`).join("\n ") || ""}
|
|
201
|
+
},
|
|
202
|
+
spacing: {
|
|
203
|
+
${tokens.spacing?.map((s) => `'${s.name}': '${s.value}',`).join("\n ") || ""}
|
|
204
|
+
},
|
|
205
|
+
borderRadius: {
|
|
206
|
+
${tokens.borderRadius?.map((br) => `'${br.name}': '${br.value}',`).join("\n ") || ""}
|
|
207
|
+
},
|
|
208
|
+
boxShadow: {
|
|
209
|
+
${tokens.shadows?.map((sh) => `'${sh.name}': '${sh.value}',`).join("\n ") || ""}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}${options.darkMode ? `,
|
|
213
|
+
darkMode: 'class'` : ""}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default preset;
|
|
217
|
+
`;
|
|
218
|
+
await fs.writeFile(path.join(srcDir, "tailwind.ts"), tailwindContent);
|
|
219
|
+
}
|
|
220
|
+
async generateComponentUtils(outputDir, tokens) {
|
|
221
|
+
const srcDir = path.join(outputDir, "src");
|
|
222
|
+
const utilsContent = `import { theme } from './theme.js';
|
|
223
|
+
import type { ColorName, FontName } from './types.js';
|
|
224
|
+
|
|
225
|
+
export function getColor(name: ColorName): string {
|
|
226
|
+
return theme.colors[name];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getFont(name: FontName): string {
|
|
230
|
+
return theme.fonts[name];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function getSpacing(name: string): string {
|
|
234
|
+
return theme.spacing[name] || '0';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function cn(...classes: (string | undefined | false)[]): string {
|
|
238
|
+
return classes.filter(Boolean).join(' ');
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
await fs.writeFile(path.join(srcDir, "utils.ts"), utilsContent);
|
|
242
|
+
const indexPath = path.join(srcDir, "index.ts");
|
|
243
|
+
const indexContent = await fs.readFile(indexPath, "utf-8");
|
|
244
|
+
await fs.writeFile(indexPath, indexContent + `export * from './utils.js';
|
|
245
|
+
`);
|
|
246
|
+
}
|
|
247
|
+
async generateReadme(outputDir, packageName, options) {
|
|
248
|
+
const readme = `# ${packageName}
|
|
249
|
+
|
|
250
|
+
Design system theme package generated by [extraktor](https://github.com/rainbow-me/extraktor).
|
|
251
|
+
|
|
252
|
+
## Installation
|
|
253
|
+
|
|
254
|
+
\`\`\`bash
|
|
255
|
+
npm install ${packageName}
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
## Usage
|
|
259
|
+
|
|
260
|
+
### Import Theme
|
|
261
|
+
|
|
262
|
+
\`\`\`typescript
|
|
263
|
+
import { theme } from '${packageName}';
|
|
264
|
+
|
|
265
|
+
console.log(theme.colors.primary);
|
|
266
|
+
console.log(theme.fonts.sans);
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
### CSS Variables
|
|
270
|
+
|
|
271
|
+
Import the CSS file in your app:
|
|
272
|
+
|
|
273
|
+
\`\`\`typescript
|
|
274
|
+
import '${packageName}/css';
|
|
275
|
+
\`\`\`
|
|
276
|
+
|
|
277
|
+
Then use variables in your CSS:
|
|
278
|
+
|
|
279
|
+
\`\`\`css
|
|
280
|
+
.button {
|
|
281
|
+
background-color: var(--color-primary);
|
|
282
|
+
font-family: var(--font-sans);
|
|
283
|
+
padding: var(--spacing-4);
|
|
284
|
+
border-radius: var(--radius-md);
|
|
285
|
+
box-shadow: var(--shadow-sm);
|
|
286
|
+
}
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
### Tailwind CSS
|
|
290
|
+
|
|
291
|
+
Add the preset to your \`tailwind.config.ts\`:
|
|
292
|
+
|
|
293
|
+
\`\`\`typescript
|
|
294
|
+
import preset from '${packageName}/tailwind';
|
|
295
|
+
|
|
296
|
+
export default {
|
|
297
|
+
presets: [preset],
|
|
298
|
+
content: ['./src/**/*.{js,ts,jsx,tsx}']
|
|
299
|
+
};
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
Then use the theme in your components:
|
|
303
|
+
|
|
304
|
+
\`\`\`tsx
|
|
305
|
+
export function Button() {
|
|
306
|
+
return (
|
|
307
|
+
<button className="bg-primary text-white font-sans px-4 py-2 rounded-md shadow-sm">
|
|
308
|
+
Click me
|
|
309
|
+
</button>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
\`\`\`
|
|
313
|
+
|
|
314
|
+
${options.darkMode ? `
|
|
315
|
+
### Dark Mode
|
|
316
|
+
|
|
317
|
+
Enable dark mode by adding \`data-theme="dark"\` to your HTML element:
|
|
318
|
+
|
|
319
|
+
\`\`\`typescript
|
|
320
|
+
import { darkTheme } from '${packageName}';
|
|
321
|
+
|
|
322
|
+
// Toggle dark mode
|
|
323
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
324
|
+
|
|
325
|
+
// Or in React
|
|
326
|
+
function App() {
|
|
327
|
+
const [isDark, setIsDark] = useState(false);
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
|
|
331
|
+
}, [isDark]);
|
|
332
|
+
|
|
333
|
+
return <YourApp />;
|
|
334
|
+
}
|
|
335
|
+
\`\`\`
|
|
336
|
+
` : ""}
|
|
337
|
+
|
|
338
|
+
${options.includeComponents ? `
|
|
339
|
+
### Utility Functions
|
|
340
|
+
|
|
341
|
+
\`\`\`typescript
|
|
342
|
+
import { getColor, getFont, getSpacing, cn } from '${packageName}';
|
|
343
|
+
|
|
344
|
+
const primaryColor = getColor('primary');
|
|
345
|
+
const sansFont = getFont('sans');
|
|
346
|
+
const spacing = getSpacing('4');
|
|
347
|
+
|
|
348
|
+
// Combine class names
|
|
349
|
+
const className = cn(
|
|
350
|
+
'button',
|
|
351
|
+
isActive && 'active',
|
|
352
|
+
isDisabled && 'disabled'
|
|
353
|
+
);
|
|
354
|
+
\`\`\`
|
|
355
|
+
` : ""}
|
|
356
|
+
|
|
357
|
+
## TypeScript
|
|
358
|
+
|
|
359
|
+
Full TypeScript support included:
|
|
360
|
+
|
|
361
|
+
\`\`\`typescript
|
|
362
|
+
import type { Theme, ColorName, FontName } from '${packageName}';
|
|
363
|
+
|
|
364
|
+
const color: ColorName = 'primary'; // Type-safe
|
|
365
|
+
const font: FontName = 'sans'; // Type-safe
|
|
366
|
+
\`\`\`
|
|
367
|
+
|
|
368
|
+
## Customization
|
|
369
|
+
|
|
370
|
+
You can extend the theme in your project:
|
|
371
|
+
|
|
372
|
+
\`\`\`typescript
|
|
373
|
+
import { theme } from '${packageName}';
|
|
374
|
+
|
|
375
|
+
export const customTheme = {
|
|
376
|
+
...theme,
|
|
377
|
+
colors: {
|
|
378
|
+
...theme.colors,
|
|
379
|
+
brand: '#FF6B35'
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
\`\`\`
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
MIT
|
|
387
|
+
`;
|
|
388
|
+
await fs.writeFile(path.join(outputDir, "README.md"), readme);
|
|
389
|
+
}
|
|
390
|
+
async generateTsConfig(outputDir) {
|
|
391
|
+
const tsconfig = {
|
|
392
|
+
compilerOptions: {
|
|
393
|
+
target: "ES2022",
|
|
394
|
+
module: "ESNext",
|
|
395
|
+
moduleResolution: "bundler",
|
|
396
|
+
declaration: true,
|
|
397
|
+
declarationMap: true,
|
|
398
|
+
esModuleInterop: true,
|
|
399
|
+
forceConsistentCasingInFileNames: true,
|
|
400
|
+
strict: true,
|
|
401
|
+
skipLibCheck: true,
|
|
402
|
+
resolveJsonModule: true
|
|
403
|
+
},
|
|
404
|
+
include: ["src/**/*"],
|
|
405
|
+
exclude: ["node_modules", "dist"]
|
|
406
|
+
};
|
|
407
|
+
await fs.writeJson(path.join(outputDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
export {
|
|
411
|
+
ThemePackageGenerator
|
|
412
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "extraktor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Extract design systems from any website using Vision AI. Turn any URL into React components, design tokens, and Tailwind themes.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"private": false,
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/aytuncyildizli/extraktor.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/aytuncyildizli/extraktor#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/aytuncyildizli/extraktor/issues"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"extraktor": "./dist/cli/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"skills",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup src/cli/index.ts src/index.ts --format esm --dts --clean",
|
|
32
|
+
"dev": "tsup src/cli/index.ts src/index.ts --format esm --watch",
|
|
33
|
+
"start": "node dist/cli/index.js",
|
|
34
|
+
"test": "vitest",
|
|
35
|
+
"test:run": "vitest run",
|
|
36
|
+
"lint": "eslint src --ext .ts",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"extract": "node dist/cli/index.js extract",
|
|
39
|
+
"clone": "node dist/cli/index.js clone",
|
|
40
|
+
"generate": "node dist/cli/index.js generate",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"release": "npm version patch && npm publish"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"design-system",
|
|
46
|
+
"design-tokens",
|
|
47
|
+
"ui-extraction",
|
|
48
|
+
"react-components",
|
|
49
|
+
"tailwind",
|
|
50
|
+
"nextjs",
|
|
51
|
+
"vision-ai",
|
|
52
|
+
"storybook",
|
|
53
|
+
"playwright",
|
|
54
|
+
"css-to-tailwind",
|
|
55
|
+
"framer-motion",
|
|
56
|
+
"component-extraction",
|
|
57
|
+
"style-guide",
|
|
58
|
+
"cli"
|
|
59
|
+
],
|
|
60
|
+
"author": "Aytunc Yildizli",
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18.0.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
68
|
+
"@svgr/core": "^8.1.0",
|
|
69
|
+
"chalk": "^5.6.2",
|
|
70
|
+
"cheerio": "^1.1.2",
|
|
71
|
+
"chroma-js": "^3.2.0",
|
|
72
|
+
"color": "^5.0.3",
|
|
73
|
+
"commander": "^14.0.2",
|
|
74
|
+
"cosmiconfig": "^9.0.0",
|
|
75
|
+
"css-tree": "^3.1.0",
|
|
76
|
+
"fs-extra": "^11.3.3",
|
|
77
|
+
"glob": "^13.0.0",
|
|
78
|
+
"nanoid": "^5.1.6",
|
|
79
|
+
"ora": "^9.0.0",
|
|
80
|
+
"playwright": "^1.57.0",
|
|
81
|
+
"postcss": "^8.5.6",
|
|
82
|
+
"postcss-selector-parser": "^7.1.1",
|
|
83
|
+
"postcss-value-parser": "^4.2.0",
|
|
84
|
+
"sharp": "^0.34.5",
|
|
85
|
+
"style-dictionary": "^5.1.4",
|
|
86
|
+
"svgo": "^4.0.0",
|
|
87
|
+
"tailwind-merge": "^3.4.0",
|
|
88
|
+
"tailwindcss": "^4.1.18",
|
|
89
|
+
"zod": "^3.25.76"
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@types/chroma-js": "^3.1.2",
|
|
93
|
+
"@types/fs-extra": "^11.0.4",
|
|
94
|
+
"@types/node": "^25.0.9",
|
|
95
|
+
"tsup": "^8.5.1",
|
|
96
|
+
"typescript": "^5.9.3",
|
|
97
|
+
"vitest": "^4.0.17"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analyze-design
|
|
3
|
+
description: Quick analysis of any website's design - colors, fonts, spacing, layout patterns, framework detection. No files generated, no API key needed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Analyze Design
|
|
7
|
+
|
|
8
|
+
Quick design analysis with no output files:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx extraktor analyze <url>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No API key needed. Returns: color palette, font families, font sizes, spacing values, shadow count, animation count, SPA framework detection, element count, CSS variable count.
|
|
15
|
+
|
|
16
|
+
Use this when the user asks things like:
|
|
17
|
+
- "What colors does this site use?"
|
|
18
|
+
- "What font is that?"
|
|
19
|
+
- "How many design tokens does this site have?"
|
|
20
|
+
- "What framework is this built with?"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apply-style
|
|
3
|
+
description: Apply a website's design style to your existing project. Extracts tokens and integrates them into your Tailwind config, CSS, or component files.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Apply Style
|
|
7
|
+
|
|
8
|
+
Extract a site's design and apply it to the user's project:
|
|
9
|
+
|
|
10
|
+
1. Extract tokens:
|
|
11
|
+
```bash
|
|
12
|
+
npx extraktor extract <url> -o /tmp/style-source
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. Read the generated `tailwind.config.ts` and `variables.css`
|
|
16
|
+
|
|
17
|
+
3. Merge the extracted theme into the user's existing Tailwind config:
|
|
18
|
+
- Add colors to `theme.extend.colors`
|
|
19
|
+
- Add fonts to `theme.extend.fontFamily`
|
|
20
|
+
- Add spacing to `theme.extend.spacing`
|
|
21
|
+
|
|
22
|
+
4. If user has a `globals.css`, add the CSS variables from `variables.css`
|
|
23
|
+
|
|
24
|
+
This is the "make my site look like X" workflow. No API key needed.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clone-site
|
|
3
|
+
description: Clone any website into a working Next.js project with images, fonts, videos, and animations. No API key needed for basic clone.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Clone Site
|
|
7
|
+
|
|
8
|
+
Clone a website into a complete Next.js project:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx extraktor clone <url> -o ./cloned --no-ai
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This downloads all assets (images, fonts, videos), preserves animations, and creates a runnable Next.js 15 project. No API key needed.
|
|
15
|
+
|
|
16
|
+
For AI-enhanced clone with clean React components (needs ANTHROPIC_API_KEY):
|
|
17
|
+
```bash
|
|
18
|
+
npx extraktor clone <url> -o ./cloned
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After cloning, tell the user:
|
|
22
|
+
```bash
|
|
23
|
+
cd ./cloned/<project-name>
|
|
24
|
+
npm install
|
|
25
|
+
npm run dev
|
|
26
|
+
# Visit http://localhost:3000
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Options: `--content` (extract content.json), `--crawl` (multi-page), `--handover` (zip package).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-md
|
|
3
|
+
description: Generate a DESIGN.md file from any website. Stitch-compatible 9-section format that AI agents can use to build pixel-perfect UI. No API key needed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generate DESIGN.md
|
|
7
|
+
|
|
8
|
+
Generate a comprehensive DESIGN.md from any URL:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx extraktor design-md <url> -o ./DESIGN.md
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No API key needed. If ANTHROPIC_API_KEY is available, the Visual Theme section gets an AI-enhanced narrative description.
|
|
15
|
+
|
|
16
|
+
Output is a single markdown file with 9 sections:
|
|
17
|
+
1. Visual Theme & Atmosphere
|
|
18
|
+
2. Color Palette & Roles
|
|
19
|
+
3. Typography Rules
|
|
20
|
+
4. Component Stylings
|
|
21
|
+
5. Layout Principles
|
|
22
|
+
6. Depth & Elevation
|
|
23
|
+
7. Do's and Don'ts
|
|
24
|
+
8. Responsive Behavior
|
|
25
|
+
9. Agent Prompt Guide
|
|
26
|
+
|
|
27
|
+
After generating, tell the user to drop DESIGN.md into their project root. Any AI coding agent (Claude Code, Cursor, Copilot) can then read it and build UI that matches the source design.
|
|
28
|
+
|
|
29
|
+
Compatible with the Stitch DESIGN.md format used by awesome-design-md.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devtools-extract
|
|
3
|
+
description: Extract design from authenticated pages (dashboards, admin panels) by connecting to the user's existing Chrome session via DevTools Protocol.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DevTools Extract
|
|
7
|
+
|
|
8
|
+
Extract from pages the user is logged into by connecting to their Chrome.
|
|
9
|
+
|
|
10
|
+
First, tell the user to start Chrome with remote debugging:
|
|
11
|
+
```bash
|
|
12
|
+
# macOS
|
|
13
|
+
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
|
|
14
|
+
|
|
15
|
+
# Linux
|
|
16
|
+
google-chrome --remote-debugging-port=9222
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then have them log into the site they want to extract from.
|
|
20
|
+
|
|
21
|
+
Then run extraktor with `--devtools`:
|
|
22
|
+
```bash
|
|
23
|
+
npx extraktor extract <url> --devtools
|
|
24
|
+
npx extraktor genome <url> --devtools
|
|
25
|
+
npx extraktor clone <url> --devtools --no-ai
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This connects to the running Chrome via CDP. Cookies, sessions, and authentication are preserved. The user's browser stays running after extraction.
|
|
29
|
+
|
|
30
|
+
Custom endpoint: `--devtools http://localhost:9333`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extract-tokens
|
|
3
|
+
description: Extract design tokens (colors, fonts, spacing) from any URL into Tailwind config, CSS variables, and DTCG tokens. No API key needed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extract Design Tokens
|
|
7
|
+
|
|
8
|
+
Run extraktor to extract design tokens from a URL. No API key required.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx extraktor extract <url> -o ./tokens --format tailwind tokens css-vars
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
After extraction, read the output files:
|
|
15
|
+
- `tailwind.config.ts` - drop into any Tailwind project
|
|
16
|
+
- `variables.css` - CSS custom properties
|
|
17
|
+
- `tokens.json` - DTCG standard design tokens
|
|
18
|
+
|
|
19
|
+
Present the extracted colors, fonts, and spacing to the user. If they want to use them, help integrate into their project.
|
|
20
|
+
|
|
21
|
+
Options: `--scroll` (lazy content), `--hover-states`, `--timeout <ms>`, `--no-headless` (visible browser).
|
package/skills/genome.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: genome
|
|
3
|
+
description: Reverse-engineer a website into a complete design system with React components, Storybook, and portable genome.json using Vision AI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Genome - Design System Extraction
|
|
7
|
+
|
|
8
|
+
Extract a complete, reusable design system from any URL using Claude Vision:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx extraktor genome <url> -o ./genome-output
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Uses ANTHROPIC_API_KEY from your environment (already set in Claude Code).
|
|
15
|
+
|
|
16
|
+
This runs a 4-phase pipeline:
|
|
17
|
+
1. Extracts design tokens (colors, fonts, spacing, animations)
|
|
18
|
+
2. Takes screenshots and sends them to Claude Vision for component identification
|
|
19
|
+
3. Generates clean React/Tailwind components by analyzing both screenshot and DOM
|
|
20
|
+
4. Packages everything into a portable genome
|
|
21
|
+
|
|
22
|
+
Output structure:
|
|
23
|
+
- `genome.json` - portable design DNA
|
|
24
|
+
- `components/` - clean React/TSX components with TypeScript props
|
|
25
|
+
- `theme/` - Tailwind config, CSS variables, animation presets, DTCG tokens
|
|
26
|
+
- `stories/` - Storybook for every component
|
|
27
|
+
- `screenshots/` - vision analysis captures
|
|
28
|
+
|
|
29
|
+
After extraction, read `genome.json` and present the components to the user.
|
|
30
|
+
|
|
31
|
+
Options: `--max-components <n>`, `--skip-storybook`, `--skip-screenshots`, `--model <model>`.
|