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/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ import {
2
+ AnimationExtractor,
3
+ BaseExtractor,
4
+ CSSVarsTransformer,
5
+ ColorExtractor,
6
+ EffectsExtractor,
7
+ ExtractorRegistry,
8
+ Orchestrator,
9
+ PageAnalyzer,
10
+ PlaywrightDriver,
11
+ SpacingExtractor,
12
+ TailwindTransformer,
13
+ TokenTransformer,
14
+ TypographyExtractor,
15
+ areColorsSimilar,
16
+ createDefaultConfig,
17
+ extractGradientColors,
18
+ generateColorName,
19
+ generateColorScale,
20
+ getContrastRatio,
21
+ getDefaultConfig,
22
+ groupSimilarColors,
23
+ loadConfig,
24
+ mergeConfig,
25
+ parseColor,
26
+ toColorFormat
27
+ } from "./chunk-PHMSK7VD.js";
28
+ import {
29
+ createLogger
30
+ } from "./chunk-5IH5TLAQ.js";
31
+ export {
32
+ AnimationExtractor,
33
+ BaseExtractor,
34
+ CSSVarsTransformer,
35
+ ColorExtractor,
36
+ EffectsExtractor,
37
+ ExtractorRegistry,
38
+ Orchestrator,
39
+ PageAnalyzer,
40
+ PlaywrightDriver,
41
+ SpacingExtractor,
42
+ TailwindTransformer,
43
+ TokenTransformer,
44
+ TypographyExtractor,
45
+ areColorsSimilar,
46
+ createDefaultConfig,
47
+ createLogger,
48
+ extractGradientColors,
49
+ generateColorName,
50
+ generateColorScale,
51
+ getContrastRatio,
52
+ getDefaultConfig,
53
+ groupSimilarColors,
54
+ loadConfig,
55
+ mergeConfig,
56
+ parseColor,
57
+ toColorFormat
58
+ };
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GenomeEngine,
4
+ SiteCloner
5
+ } from "./chunk-VLLFGYUN.js";
6
+ import {
7
+ Orchestrator,
8
+ PlaywrightDriver,
9
+ getDefaultConfig
10
+ } from "./chunk-PHMSK7VD.js";
11
+ import {
12
+ createLogger
13
+ } from "./chunk-5IH5TLAQ.js";
14
+
15
+ // src/mcp/server.ts
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { z } from "zod";
19
+ import path from "path";
20
+ import fs from "fs-extra";
21
+ var logger = createLogger({ level: "error" });
22
+ async function startMcpServer() {
23
+ const server = new McpServer({
24
+ name: "extraktor",
25
+ version: "1.0.0"
26
+ });
27
+ server.tool(
28
+ "extract_design_tokens",
29
+ "Extract design tokens (colors, typography, spacing, animations, effects) from any URL. Returns Tailwind config, CSS variables, and DTCG tokens. No API key required.",
30
+ {
31
+ url: z.string().describe("URL to extract from"),
32
+ format: z.enum(["tailwind", "css-variables", "design-tokens", "all"]).default("all").describe("Output format")
33
+ },
34
+ async ({ url, format }) => {
35
+ const outputDir = path.join("/tmp", `extraktor-mcp-${Date.now()}`);
36
+ await fs.ensureDir(outputDir);
37
+ try {
38
+ const config = getDefaultConfig();
39
+ config.browser.headless = true;
40
+ config.browser.timeout = 3e4;
41
+ config.output.formats = format === "all" ? ["tailwind", "css-variables", "design-tokens"] : [format === "css-variables" ? "css-variables" : format === "design-tokens" ? "design-tokens" : "tailwind"];
42
+ const orchestrator = new Orchestrator({ config, logger });
43
+ const result = await orchestrator.extract(url, outputDir);
44
+ const output = {};
45
+ const tailwindPath = path.join(outputDir, "tailwind.config.ts");
46
+ if (await fs.pathExists(tailwindPath)) {
47
+ output.tailwindConfig = await fs.readFile(tailwindPath, "utf-8");
48
+ }
49
+ const cssPath = path.join(outputDir, "variables.css");
50
+ if (await fs.pathExists(cssPath)) {
51
+ output.cssVariables = await fs.readFile(cssPath, "utf-8");
52
+ }
53
+ const tokensPath = path.join(outputDir, "tokens.json");
54
+ if (await fs.pathExists(tokensPath)) {
55
+ output.designTokens = await fs.readFile(tokensPath, "utf-8");
56
+ }
57
+ const summary = {
58
+ url: result.metadata.url,
59
+ title: result.metadata.title,
60
+ colors: result.colors.palette.length,
61
+ fonts: result.typography.fontFamilies.length,
62
+ fontSizes: result.typography.fontSizes.length,
63
+ spacingValues: result.spacing.scale.length,
64
+ shadows: result.effects.shadows.length,
65
+ animations: result.animations.keyframes.length,
66
+ ...output
67
+ };
68
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
69
+ } finally {
70
+ await fs.remove(outputDir);
71
+ }
72
+ }
73
+ );
74
+ server.tool(
75
+ "analyze_site",
76
+ "Quick analysis of a website - colors, fonts, spacing, layout patterns, SPA framework detection. No files generated, no API key needed.",
77
+ {
78
+ url: z.string().describe("URL to analyze")
79
+ },
80
+ async ({ url }) => {
81
+ const config = getDefaultConfig();
82
+ config.browser.headless = true;
83
+ config.browser.timeout = 3e4;
84
+ const orchestrator = new Orchestrator({ config, logger });
85
+ const result = await orchestrator.analyze(url);
86
+ const analysis = {
87
+ url: result.metadata.url,
88
+ title: result.metadata.title,
89
+ spaFramework: result.metadata.spaFramework,
90
+ elementsAnalyzed: result.metadata.elementsAnalyzed,
91
+ colors: {
92
+ count: result.colors.palette.length,
93
+ top5: result.colors.palette.slice(0, 5).map((c) => ({ name: c.name, value: c.value })),
94
+ gradients: result.colors.gradients.length
95
+ },
96
+ typography: {
97
+ fonts: result.typography.fontFamilies.map((f) => f.name),
98
+ sizeCount: result.typography.fontSizes.length,
99
+ sizes: result.typography.fontSizes.slice(0, 8).map((s) => s.value)
100
+ },
101
+ spacing: {
102
+ scaleCount: result.spacing.scale.length,
103
+ values: result.spacing.scale.slice(0, 10).map((s) => s.value)
104
+ },
105
+ effects: {
106
+ shadows: result.effects.shadows.length,
107
+ borderRadii: result.effects.borderRadii.length
108
+ },
109
+ animations: {
110
+ keyframes: result.animations.keyframes.length,
111
+ transitions: result.animations.transitions.length
112
+ },
113
+ layout: {
114
+ containers: result.layout.containers.length,
115
+ gridPatterns: result.layout.gridPatterns.length,
116
+ flexPatterns: result.layout.flexPatterns.length,
117
+ breakpoints: result.layout.breakpoints.length
118
+ },
119
+ cssVariables: Object.keys(result.cssVariables).length
120
+ };
121
+ return { content: [{ type: "text", text: JSON.stringify(analysis, null, 2) }] };
122
+ }
123
+ );
124
+ server.tool(
125
+ "clone_site",
126
+ "Clone a website into a working Next.js project with all assets (images, fonts, videos). Downloads and localizes everything. No API key needed.",
127
+ {
128
+ url: z.string().describe("URL to clone"),
129
+ outputDir: z.string().describe("Directory to save the project"),
130
+ projectName: z.string().optional().describe("Project name (defaults to domain)")
131
+ },
132
+ async ({ url, outputDir, projectName }) => {
133
+ const resolvedOutput = path.resolve(outputDir);
134
+ await fs.ensureDir(resolvedOutput);
135
+ const config = getDefaultConfig();
136
+ config.browser.timeout = 6e4;
137
+ const driver = new PlaywrightDriver({
138
+ browserConfig: config.browser,
139
+ spaConfig: config.spa,
140
+ logger
141
+ });
142
+ await driver.initialize();
143
+ const navResult = await driver.navigate(url);
144
+ if (!navResult.success) {
145
+ await driver.close();
146
+ return { content: [{ type: "text", text: `Navigation failed: ${navResult.error}` }] };
147
+ }
148
+ const page = driver.getPage();
149
+ if (!page) {
150
+ await driver.close();
151
+ return { content: [{ type: "text", text: "Failed to get page" }] };
152
+ }
153
+ const name = projectName || new URL(url).hostname.replace(/\./g, "-");
154
+ const cloner = new SiteCloner();
155
+ const result = await cloner.clone(page, {
156
+ url,
157
+ outputDir: resolvedOutput,
158
+ projectName: name,
159
+ downloadAssets: true,
160
+ inlineStyles: true
161
+ });
162
+ await driver.close();
163
+ return {
164
+ content: [{
165
+ type: "text",
166
+ text: JSON.stringify({
167
+ projectDir: result.projectDir,
168
+ platform: result.platform,
169
+ assets: result.assets,
170
+ pagesCloned: result.pagesCloned,
171
+ nextSteps: `cd ${result.projectDir} && npm install && npm run dev`
172
+ }, null, 2)
173
+ }]
174
+ };
175
+ }
176
+ );
177
+ server.tool(
178
+ "extract_genome",
179
+ "Vision-AI design system extraction. Identifies components visually using Claude Vision, generates clean React/Tailwind components, Storybook, and a portable genome.json. Uses your ANTHROPIC_API_KEY.",
180
+ {
181
+ url: z.string().describe("URL to extract genome from"),
182
+ outputDir: z.string().describe("Directory to save the genome"),
183
+ maxComponents: z.number().default(20).describe("Max components to synthesize"),
184
+ skipStorybook: z.boolean().default(false).describe("Skip Storybook generation")
185
+ },
186
+ async ({ url, outputDir, maxComponents, skipStorybook }) => {
187
+ const apiKey = process.env.ANTHROPIC_API_KEY;
188
+ if (!apiKey) {
189
+ return {
190
+ content: [{
191
+ type: "text",
192
+ text: "ANTHROPIC_API_KEY not found in environment. genome requires Claude Vision API access. Set the environment variable and restart."
193
+ }]
194
+ };
195
+ }
196
+ const resolvedOutput = path.resolve(outputDir);
197
+ const engine = new GenomeEngine({ logger });
198
+ const genome = await engine.genome({
199
+ url,
200
+ outputDir: resolvedOutput,
201
+ apiKey,
202
+ maxComponents,
203
+ skipStorybook,
204
+ headless: true
205
+ });
206
+ return {
207
+ content: [{
208
+ type: "text",
209
+ text: JSON.stringify({
210
+ sourceUrl: genome.meta.sourceUrl,
211
+ componentCount: genome.meta.componentCount,
212
+ components: genome.components.map((c) => ({ name: c.name, type: c.type, description: c.description })),
213
+ sectionOrder: genome.layout.sectionOrder,
214
+ outputDir: resolvedOutput,
215
+ colorCount: Object.keys(genome.theme.colors).length,
216
+ styleNarrative: genome.styleNarrative.slice(0, 300) + "..."
217
+ }, null, 2)
218
+ }]
219
+ };
220
+ }
221
+ );
222
+ server.tool(
223
+ "regen_page",
224
+ "Generate a new page from a previously extracted genome. Creates a Next.js page using the genome's components and design language with original content. Uses your ANTHROPIC_API_KEY.",
225
+ {
226
+ genomeDir: z.string().describe("Path to genome directory (containing genome.json)"),
227
+ prompt: z.string().describe("Description of the page to generate"),
228
+ outputDir: z.string().describe("Directory to save the generated page")
229
+ },
230
+ async ({ genomeDir, prompt, outputDir }) => {
231
+ const apiKey = process.env.ANTHROPIC_API_KEY;
232
+ if (!apiKey) {
233
+ return {
234
+ content: [{
235
+ type: "text",
236
+ text: "ANTHROPIC_API_KEY not found in environment. regen requires Claude API access."
237
+ }]
238
+ };
239
+ }
240
+ const engine = new GenomeEngine({ logger });
241
+ const result = await engine.regen({
242
+ genomeDir: path.resolve(genomeDir),
243
+ prompt,
244
+ outputDir: path.resolve(outputDir),
245
+ apiKey
246
+ });
247
+ return {
248
+ content: [{
249
+ type: "text",
250
+ text: JSON.stringify({
251
+ outputDir: result,
252
+ prompt,
253
+ nextSteps: `cd ${result} && npm install && npm run dev`
254
+ }, null, 2)
255
+ }]
256
+ };
257
+ }
258
+ );
259
+ server.tool(
260
+ "extract_colors",
261
+ "Extract just the color palette from a URL. Returns colors with names, hex values, and usage categories. No API key needed.",
262
+ {
263
+ url: z.string().describe("URL to extract colors from")
264
+ },
265
+ async ({ url }) => {
266
+ const config = getDefaultConfig();
267
+ config.browser.headless = true;
268
+ config.browser.timeout = 2e4;
269
+ config.output.formats = [];
270
+ const orchestrator = new Orchestrator({ config, logger });
271
+ const result = await orchestrator.analyze(url);
272
+ const colors = {
273
+ palette: result.colors.palette.map((c) => ({
274
+ name: c.name,
275
+ value: c.value,
276
+ usage: c.usage,
277
+ frequency: c.frequency
278
+ })),
279
+ gradients: result.colors.gradients.map((g) => ({
280
+ value: g.value,
281
+ type: g.type
282
+ }))
283
+ };
284
+ return { content: [{ type: "text", text: JSON.stringify(colors, null, 2) }] };
285
+ }
286
+ );
287
+ server.tool(
288
+ "extract_typography",
289
+ "Extract typography system from a URL. Returns font families, sizes, weights, line heights. No API key needed.",
290
+ {
291
+ url: z.string().describe("URL to extract typography from")
292
+ },
293
+ async ({ url }) => {
294
+ const config = getDefaultConfig();
295
+ config.browser.headless = true;
296
+ config.browser.timeout = 2e4;
297
+ config.output.formats = [];
298
+ const orchestrator = new Orchestrator({ config, logger });
299
+ const result = await orchestrator.analyze(url);
300
+ const typography = {
301
+ families: result.typography.fontFamilies.map((f) => ({
302
+ name: f.name,
303
+ category: f.category,
304
+ source: f.source
305
+ })),
306
+ sizes: result.typography.fontSizes.map((s) => ({
307
+ name: s.name,
308
+ value: s.value,
309
+ pxValue: s.pxValue
310
+ })),
311
+ weights: result.typography.fontWeights.map((w) => ({
312
+ name: w.name,
313
+ value: w.value
314
+ })),
315
+ lineHeights: result.typography.lineHeights.map((l) => ({
316
+ name: l.name,
317
+ value: l.value
318
+ }))
319
+ };
320
+ return { content: [{ type: "text", text: JSON.stringify(typography, null, 2) }] };
321
+ }
322
+ );
323
+ const transport = new StdioServerTransport();
324
+ await server.connect(transport);
325
+ }
326
+ export {
327
+ startMcpServer
328
+ };