designlang 2.0.0 → 4.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/README.md +138 -77
- package/bin/design-extract.js +272 -2
- package/designlang.png +0 -0
- package/package.json +1 -1
- package/skills/extract-design/SKILL.md +52 -25
- package/src/clone.js +218 -0
- package/src/crawler.js +7 -0
- package/src/extractors/components.js +86 -0
- package/src/extractors/interactions.js +128 -0
- package/src/extractors/layout.js +114 -0
- package/src/extractors/responsive.js +132 -0
- package/src/extractors/scoring.js +132 -0
- package/src/formatters/markdown.js +178 -0
- package/src/index.js +13 -0
- package/src/multibrand.js +151 -0
- package/src/sync.js +69 -0
- package/src/watch.js +47 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">designlang</h1>
|
|
3
|
-
<p align="center">
|
|
3
|
+
<p align="center">Reverse-engineer any website's complete design system in one command.</p>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="designlang.png" alt="designlang in action" width="100%">
|
|
16
|
+
</p>
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
**designlang** crawls any website with a headless browser, extracts every computed style from the live DOM, and generates **8 output files** — including an AI-optimized markdown file, visual HTML preview, Tailwind config, React theme, shadcn/ui theme, Figma variables, W3C design tokens, and CSS custom properties.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
But unlike every other tool out there, it also extracts **layout patterns** (grids, flexbox, containers), captures **responsive behavior** across 4 breakpoints, records **interaction states** (hover, focus, active), scores **WCAG accessibility**, and lets you **compare multiple brands** or **sync live sites to local tokens**.
|
|
19
21
|
|
|
20
22
|
## Quick Start
|
|
21
23
|
|
|
@@ -23,18 +25,26 @@ It also does **WCAG accessibility scoring**, **component screenshot capture**, *
|
|
|
23
25
|
npx designlang https://stripe.com
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
Get everything at once:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx designlang https://stripe.com --full
|
|
32
|
+
```
|
|
33
|
+
|
|
26
34
|
## What You Get (8 Files)
|
|
27
35
|
|
|
28
36
|
| File | What it is |
|
|
29
37
|
|------|------------|
|
|
30
|
-
| `*-design-language.md` | AI-optimized markdown —
|
|
31
|
-
| `*-preview.html` | Visual
|
|
32
|
-
| `*-design-tokens.json` | [W3C Design Tokens](https://design-tokens.github.io/community-group/format/)
|
|
33
|
-
| `*-tailwind.config.js` | Drop-in Tailwind CSS theme
|
|
34
|
-
| `*-variables.css` | CSS custom properties
|
|
38
|
+
| `*-design-language.md` | AI-optimized markdown — feed it to any LLM to recreate the design |
|
|
39
|
+
| `*-preview.html` | Visual report with swatches, type scale, shadows, a11y score |
|
|
40
|
+
| `*-design-tokens.json` | [W3C Design Tokens](https://design-tokens.github.io/community-group/format/) format |
|
|
41
|
+
| `*-tailwind.config.js` | Drop-in Tailwind CSS theme |
|
|
42
|
+
| `*-variables.css` | CSS custom properties |
|
|
35
43
|
| `*-figma-variables.json` | Figma Variables import (with dark mode support) |
|
|
36
|
-
| `*-theme.js` | React/CSS-in-JS theme
|
|
37
|
-
| `*-shadcn-theme.css` | shadcn/ui globals.css
|
|
44
|
+
| `*-theme.js` | React/CSS-in-JS theme (Chakra, Stitches, Vanilla Extract) |
|
|
45
|
+
| `*-shadcn-theme.css` | shadcn/ui globals.css variables |
|
|
46
|
+
|
|
47
|
+
The markdown output has **14 sections**: Color Palette, Typography, Spacing, Border Radii, Box Shadows, CSS Custom Properties, Breakpoints, Transitions & Animations, Component Patterns, Layout System, Responsive Design, Interaction States, Accessibility (WCAG 2.1), and Quick Start.
|
|
38
48
|
|
|
39
49
|
## Install
|
|
40
50
|
|
|
@@ -44,94 +54,96 @@ npx designlang https://example.com
|
|
|
44
54
|
|
|
45
55
|
# Or install globally
|
|
46
56
|
npm install -g designlang
|
|
47
|
-
```
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
# As an agent skill (Claude Code, Cursor, Codex, 40+ agents)
|
|
59
|
+
npx skills add Manavarya09/design-extract
|
|
60
|
+
```
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
## What Makes This Different
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
designlang https://stripe.com --depth 5
|
|
57
|
-
```
|
|
64
|
+
Most design extraction tools give you colors and fonts. That's it. designlang fills 5 market gaps that no other tool addresses:
|
|
58
65
|
|
|
59
|
-
###
|
|
66
|
+
### 1. Layout System Extraction
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
Extracts the structural skeleton — grid column patterns, flex direction usage, container widths, gap values, and justify/align patterns.
|
|
62
69
|
|
|
63
70
|
```
|
|
64
|
-
|
|
71
|
+
Layout: 55 grids, 492 flex containers
|
|
65
72
|
```
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
Every other tool gives you the paint. designlang gives you the architecture.
|
|
68
75
|
|
|
69
|
-
###
|
|
76
|
+
### 2. Responsive Multi-Breakpoint Capture
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
Crawls the site at 4 viewports (mobile, tablet, desktop, wide) and maps exactly what changes:
|
|
72
79
|
|
|
73
80
|
```bash
|
|
74
|
-
designlang https://vercel.com --
|
|
81
|
+
designlang https://vercel.com --responsive
|
|
75
82
|
```
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
```
|
|
85
|
+
Responsive: 4 viewports, 3 breakpoint changes
|
|
86
|
+
375px → 768px: Nav visibility hidden → visible, Hamburger shown → hidden
|
|
87
|
+
768px → 1280px: Max grid columns 1 → 3, H1 size 32px → 48px
|
|
88
|
+
```
|
|
80
89
|
|
|
81
|
-
|
|
82
|
-
- Color swatches for the full palette
|
|
83
|
-
- Live type scale rendering
|
|
84
|
-
- Spacing scale visualization
|
|
85
|
-
- Shadow cards with actual CSS shadows
|
|
86
|
-
- Accessibility score and failing pair analysis
|
|
87
|
-
- Component screenshots (when `--screenshots` is used)
|
|
90
|
+
No other tool captures how the design *adapts*, just how it looks at one size.
|
|
88
91
|
|
|
89
|
-
###
|
|
92
|
+
### 3. Interaction State Capture
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
Programmatically hovers and focuses interactive elements, capturing the actual style transitions:
|
|
92
95
|
|
|
93
96
|
```bash
|
|
94
|
-
designlang
|
|
97
|
+
designlang https://stripe.com --interactions
|
|
95
98
|
```
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
```css
|
|
101
|
+
/* Button Hover */
|
|
102
|
+
background-color: rgb(83, 58, 253) → rgb(67, 47, 202);
|
|
103
|
+
box-shadow: none → 0 4px 12px rgba(83, 58, 253, 0.4);
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
/* Input Focus */
|
|
106
|
+
border-color: rgb(200, 200, 200) → rgb(83, 58, 253);
|
|
107
|
+
outline: none → 2px solid rgb(83, 58, 253);
|
|
108
|
+
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
### 4. Live Site Sync
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
# Each extraction auto-saves a snapshot
|
|
105
|
-
designlang https://stripe.com
|
|
112
|
+
Treat the deployed site as your source of truth, not Figma:
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
designlang
|
|
114
|
+
```bash
|
|
115
|
+
designlang sync https://stripe.com --out ./src/tokens
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
### Framework Themes
|
|
118
|
+
Detects design changes and auto-updates your local `design-tokens.json`, `tailwind.config.js`, and `variables.css`.
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
### 5. Multi-Brand Comparison
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
- **shadcn/ui** — CSS variables in the exact format shadcn expects (paste into globals.css)
|
|
119
|
-
- **Tailwind** — full theme extension with colors, fonts, spacing, radii, shadows, screens
|
|
122
|
+
Compare N brands side-by-side:
|
|
120
123
|
|
|
121
|
-
|
|
124
|
+
```bash
|
|
125
|
+
designlang brands stripe.com vercel.com github.com linear.app
|
|
126
|
+
```
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
128
|
+
Generates a matrix with color overlap analysis, typography comparison, spacing systems, and accessibility scores. Outputs both `brands.md` and `brands.html`.
|
|
129
|
+
|
|
130
|
+
## All Features
|
|
131
|
+
|
|
132
|
+
| Feature | Flag / Command | Description |
|
|
133
|
+
|---------|---------------|-------------|
|
|
134
|
+
| Base extraction | `designlang <url>` | Colors, typography, spacing, shadows, radii, CSS vars, breakpoints, animations, components |
|
|
135
|
+
| Layout system | automatic | Grid patterns, flex usage, container widths, gap values |
|
|
136
|
+
| Accessibility | automatic | WCAG 2.1 contrast ratios for all fg/bg pairs |
|
|
137
|
+
| Dark mode | `--dark` | Extracts dark color scheme |
|
|
138
|
+
| Multi-page | `--depth <n>` | Crawl N internal pages for site-wide tokens |
|
|
139
|
+
| Screenshots | `--screenshots` | Capture buttons, cards, inputs, nav, hero, full page |
|
|
140
|
+
| Responsive | `--responsive` | Crawl at 4 viewports, map breakpoint changes |
|
|
141
|
+
| Interactions | `--interactions` | Capture hover/focus/active state transitions |
|
|
142
|
+
| Everything | `--full` | Enable screenshots + responsive + interactions |
|
|
143
|
+
| Diff | `designlang diff <A> <B>` | Compare two sites (MD + HTML) |
|
|
144
|
+
| Multi-brand | `designlang brands <urls...>` | N-site comparison matrix |
|
|
145
|
+
| Sync | `designlang sync <url>` | Update local tokens from live site |
|
|
146
|
+
| History | `designlang history <url>` | Track design changes over time |
|
|
135
147
|
|
|
136
148
|
## Full CLI Reference
|
|
137
149
|
|
|
@@ -145,29 +157,78 @@ Options:
|
|
|
145
157
|
--height <px> Viewport height (default: 800)
|
|
146
158
|
--wait <ms> Wait after page load for SPAs (default: 0)
|
|
147
159
|
--dark Also extract dark mode styles
|
|
148
|
-
--depth <n>
|
|
160
|
+
--depth <n> Internal pages to crawl (default: 0)
|
|
149
161
|
--screenshots Capture component screenshots
|
|
162
|
+
--responsive Capture at multiple breakpoints
|
|
163
|
+
--interactions Capture hover/focus/active states
|
|
164
|
+
--full Enable all captures
|
|
150
165
|
--framework <type> Only generate specific theme (react, shadcn)
|
|
151
166
|
--no-history Skip saving to history
|
|
152
167
|
--verbose Detailed progress output
|
|
153
168
|
|
|
154
169
|
Commands:
|
|
155
170
|
diff <urlA> <urlB> Compare two sites' design languages
|
|
156
|
-
|
|
171
|
+
brands <urls...> Multi-brand comparison matrix
|
|
172
|
+
sync <url> Sync local tokens with live site
|
|
173
|
+
history <url> View design change history
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Example Output
|
|
177
|
+
|
|
178
|
+
Running `designlang https://vercel.com --full`:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
designlang
|
|
182
|
+
https://vercel.com
|
|
183
|
+
|
|
184
|
+
Output files:
|
|
185
|
+
✓ vercel-com-design-language.md (32.6KB)
|
|
186
|
+
✓ vercel-com-design-tokens.json (5.6KB)
|
|
187
|
+
✓ vercel-com-tailwind.config.js (3.4KB)
|
|
188
|
+
✓ vercel-com-variables.css (18.6KB)
|
|
189
|
+
✓ vercel-com-preview.html (31.8KB)
|
|
190
|
+
✓ vercel-com-figma-variables.json (12.4KB)
|
|
191
|
+
✓ vercel-com-theme.js (1.4KB)
|
|
192
|
+
✓ vercel-com-shadcn-theme.css (477B)
|
|
193
|
+
✓ screenshots/button.png
|
|
194
|
+
✓ screenshots/card.png
|
|
195
|
+
✓ screenshots/nav.png
|
|
196
|
+
✓ screenshots/hero.png
|
|
197
|
+
✓ screenshots/full-page.png
|
|
198
|
+
|
|
199
|
+
Summary:
|
|
200
|
+
Colors: 27 unique colors
|
|
201
|
+
Fonts: Geist, Geist Mono
|
|
202
|
+
Spacing: 18 values (base: 2px)
|
|
203
|
+
Shadows: 11 unique shadows
|
|
204
|
+
Radii: 10 unique values
|
|
205
|
+
Breakpoints: 45 breakpoints
|
|
206
|
+
Components: 4 patterns detected
|
|
207
|
+
CSS Vars: 407 custom properties
|
|
208
|
+
Layout: 55 grids, 492 flex containers
|
|
209
|
+
Responsive: 4 viewports, 3 breakpoint changes
|
|
210
|
+
Interactions: 8 state changes captured
|
|
211
|
+
A11y: 94% WCAG score (7 failing pairs)
|
|
157
212
|
```
|
|
158
213
|
|
|
159
214
|
## How It Works
|
|
160
215
|
|
|
161
|
-
1. **Crawl** — Launches headless Chromium via Playwright
|
|
162
|
-
2. **Extract** — `page.evaluate()` walks up to 5,000 DOM elements collecting computed
|
|
163
|
-
3. **Process** —
|
|
164
|
-
4. **Format** — 8 formatter modules generate
|
|
165
|
-
5. **Score** — Accessibility extractor calculates WCAG contrast ratios
|
|
166
|
-
6. **Capture** — Optional
|
|
216
|
+
1. **Crawl** — Launches headless Chromium via Playwright, waits for network idle and fonts
|
|
217
|
+
2. **Extract** — Single `page.evaluate()` walks up to 5,000 DOM elements collecting 25+ computed style properties including layout (grid, flex, container) data
|
|
218
|
+
3. **Process** — 12 extractor modules parse, deduplicate, cluster, and classify the raw data
|
|
219
|
+
4. **Format** — 8 formatter modules generate output files
|
|
220
|
+
5. **Score** — Accessibility extractor calculates WCAG contrast ratios for all color pairs
|
|
221
|
+
6. **Capture** — Optional: screenshots, responsive viewport crawling, interaction state recording
|
|
222
|
+
|
|
223
|
+
## Agent Skill
|
|
167
224
|
|
|
168
|
-
|
|
225
|
+
Works with **Claude Code, Cursor, Codex, and 40+ AI coding agents** via the skills ecosystem:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
npx skills add Manavarya09/design-extract
|
|
229
|
+
```
|
|
169
230
|
|
|
170
|
-
|
|
231
|
+
In Claude Code, use `/extract-design <url>`.
|
|
171
232
|
|
|
172
233
|
## Contributing
|
|
173
234
|
|
package/bin/design-extract.js
CHANGED
|
@@ -15,6 +15,12 @@ import { formatFigma } from '../src/formatters/figma.js';
|
|
|
15
15
|
import { formatReactTheme, formatShadcnTheme } from '../src/formatters/theme.js';
|
|
16
16
|
import { diffDesigns, formatDiffMarkdown, formatDiffHtml } from '../src/diff.js';
|
|
17
17
|
import { saveSnapshot, getHistory, formatHistoryMarkdown } from '../src/history.js';
|
|
18
|
+
import { captureResponsive } from '../src/extractors/responsive.js';
|
|
19
|
+
import { captureInteractions } from '../src/extractors/interactions.js';
|
|
20
|
+
import { syncDesign } from '../src/sync.js';
|
|
21
|
+
import { compareBrands, formatBrandMatrix, formatBrandMatrixHtml } from '../src/multibrand.js';
|
|
22
|
+
import { generateClone } from '../src/clone.js';
|
|
23
|
+
import { watchSite } from '../src/watch.js';
|
|
18
24
|
import { nameFromUrl } from '../src/utils.js';
|
|
19
25
|
|
|
20
26
|
const program = new Command();
|
|
@@ -22,7 +28,7 @@ const program = new Command();
|
|
|
22
28
|
program
|
|
23
29
|
.name('designlang')
|
|
24
30
|
.description('Extract the complete design language from any website')
|
|
25
|
-
.version('
|
|
31
|
+
.version('4.0.0');
|
|
26
32
|
|
|
27
33
|
// ── Main command: extract ──────────────────────────────────────
|
|
28
34
|
program
|
|
@@ -36,6 +42,9 @@ program
|
|
|
36
42
|
.option('--depth <n>', 'number of internal pages to also crawl', parseInt, 0)
|
|
37
43
|
.option('--screenshots', 'capture component screenshots')
|
|
38
44
|
.option('--framework <type>', 'generate framework theme (react, shadcn)')
|
|
45
|
+
.option('--responsive', 'capture design at multiple breakpoints')
|
|
46
|
+
.option('--interactions', 'capture hover/focus/active states')
|
|
47
|
+
.option('--full', 'enable all extra captures (screenshots, responsive, interactions)')
|
|
39
48
|
.option('--no-history', 'skip saving to history')
|
|
40
49
|
.option('--verbose', 'show detailed progress')
|
|
41
50
|
.action(async (url, opts) => {
|
|
@@ -58,10 +67,22 @@ program
|
|
|
58
67
|
wait: opts.wait,
|
|
59
68
|
dark: opts.dark,
|
|
60
69
|
depth: opts.depth,
|
|
61
|
-
screenshots: opts.screenshots,
|
|
70
|
+
screenshots: opts.screenshots || opts.full,
|
|
62
71
|
outDir,
|
|
63
72
|
});
|
|
64
73
|
|
|
74
|
+
// Responsive capture
|
|
75
|
+
if (opts.responsive || opts.full) {
|
|
76
|
+
spinner.text = 'Capturing responsive breakpoints...';
|
|
77
|
+
design.responsive = await captureResponsive(url, { wait: opts.wait });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Interaction state capture
|
|
81
|
+
if (opts.interactions || opts.full) {
|
|
82
|
+
spinner.text = 'Capturing interaction states...';
|
|
83
|
+
design.interactions = await captureInteractions(url, { width: opts.width, height: parseInt(opts.height) || 800, wait: opts.wait });
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
spinner.text = 'Generating outputs...';
|
|
66
87
|
mkdirSync(outDir, { recursive: true });
|
|
67
88
|
|
|
@@ -125,6 +146,22 @@ program
|
|
|
125
146
|
console.log(` ${chalk.gray('Breakpoints:')} ${design.breakpoints.length} breakpoints`);
|
|
126
147
|
console.log(` ${chalk.gray('Components:')} ${Object.keys(design.components).length} patterns detected`);
|
|
127
148
|
console.log(` ${chalk.gray('CSS Vars:')} ${Object.values(design.variables).reduce((s, v) => s + Object.keys(v).length, 0)} custom properties`);
|
|
149
|
+
if (design.layout) {
|
|
150
|
+
console.log(` ${chalk.gray('Layout:')} ${design.layout.gridCount} grids, ${design.layout.flexCount} flex containers`);
|
|
151
|
+
}
|
|
152
|
+
if (design.responsive) {
|
|
153
|
+
console.log(` ${chalk.gray('Responsive:')} ${design.responsive.viewports.length} viewports, ${design.responsive.changes.length} breakpoint changes`);
|
|
154
|
+
}
|
|
155
|
+
if (design.interactions) {
|
|
156
|
+
const ic = design.interactions;
|
|
157
|
+
const total = ic.buttons.length + ic.links.length + ic.inputs.length;
|
|
158
|
+
console.log(` ${chalk.gray('Interactions:')} ${total} state changes captured`);
|
|
159
|
+
}
|
|
160
|
+
if (design.score) {
|
|
161
|
+
const s = design.score;
|
|
162
|
+
const gradeColor = s.grade === 'A' ? chalk.green : s.grade === 'B' ? chalk.cyan : s.grade === 'C' ? chalk.yellow : chalk.red;
|
|
163
|
+
console.log(` ${chalk.gray('Design Score:')} ${gradeColor(`${s.overall}/100 (${s.grade})`)}${s.issues.length > 0 ? ` — ${s.issues.length} issues` : ''}`);
|
|
164
|
+
}
|
|
128
165
|
|
|
129
166
|
// Accessibility summary
|
|
130
167
|
if (design.accessibility) {
|
|
@@ -216,4 +253,237 @@ program
|
|
|
216
253
|
console.log(formatHistoryMarkdown(url, history));
|
|
217
254
|
});
|
|
218
255
|
|
|
256
|
+
// ── Brands command (multi-site comparison) ─────────────────
|
|
257
|
+
program
|
|
258
|
+
.command('brands <urls...>')
|
|
259
|
+
.description('Compare design languages across multiple brands')
|
|
260
|
+
.option('-o, --out <dir>', 'output directory', './design-brands-output')
|
|
261
|
+
.action(async (urls, opts) => {
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(chalk.bold(' designlang brands'));
|
|
264
|
+
console.log(chalk.gray(` Comparing ${urls.length} sites`));
|
|
265
|
+
console.log('');
|
|
266
|
+
|
|
267
|
+
const spinner = ora(`Extracting ${urls.length} sites...`).start();
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const brands = await compareBrands(urls);
|
|
271
|
+
|
|
272
|
+
const outDir = resolve(opts.out);
|
|
273
|
+
mkdirSync(outDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
const md = formatBrandMatrix(brands);
|
|
276
|
+
const html = formatBrandMatrixHtml(brands);
|
|
277
|
+
|
|
278
|
+
writeFileSync(join(outDir, 'brands.md'), md, 'utf-8');
|
|
279
|
+
writeFileSync(join(outDir, 'brands.html'), html, 'utf-8');
|
|
280
|
+
|
|
281
|
+
spinner.succeed('Brand comparison complete!');
|
|
282
|
+
console.log('');
|
|
283
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan('brands.md')} — Markdown matrix`);
|
|
284
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan('brands.html')} — Visual matrix`);
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log(chalk.gray(` Saved to ${outDir}`));
|
|
287
|
+
|
|
288
|
+
// Quick summary
|
|
289
|
+
const valid = brands.filter(b => !b.error);
|
|
290
|
+
for (const b of valid) {
|
|
291
|
+
console.log(` ${chalk.cyan(b.hostname)}: ${b.design.colors.all.length} colors, ${b.design.typography.families.map(f => f.name).join(', ')}, ${b.design.accessibility?.score ?? '?'}% a11y`);
|
|
292
|
+
}
|
|
293
|
+
console.log('');
|
|
294
|
+
|
|
295
|
+
} catch (err) {
|
|
296
|
+
spinner.fail('Brand comparison failed');
|
|
297
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ── Sync command ────────────────────────────────────────────
|
|
303
|
+
program
|
|
304
|
+
.command('sync <url>')
|
|
305
|
+
.description('Sync local design tokens with a live website')
|
|
306
|
+
.option('-o, --out <dir>', 'directory with token files to update', '.')
|
|
307
|
+
.action(async (url, opts) => {
|
|
308
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
309
|
+
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(chalk.bold(' designlang sync'));
|
|
312
|
+
console.log(chalk.gray(` ${url}`));
|
|
313
|
+
console.log('');
|
|
314
|
+
|
|
315
|
+
const spinner = ora('Extracting current design...').start();
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const result = await syncDesign(url, { out: resolve(opts.out) });
|
|
319
|
+
|
|
320
|
+
if (result.isFirstRun) {
|
|
321
|
+
spinner.succeed('First sync — baseline saved.');
|
|
322
|
+
} else if (result.changes.length === 0) {
|
|
323
|
+
spinner.succeed('No design changes detected.');
|
|
324
|
+
} else {
|
|
325
|
+
spinner.succeed(`${result.changes.length} design changes detected!`);
|
|
326
|
+
console.log('');
|
|
327
|
+
for (const c of result.changes) {
|
|
328
|
+
console.log(` ${chalk.yellow('≠')} ${c.property}: ${c.from} → ${c.to}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (result.updatedFiles.length > 0) {
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(chalk.bold(' Updated files:'));
|
|
335
|
+
for (const f of result.updatedFiles) {
|
|
336
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(f)}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
console.log('');
|
|
340
|
+
|
|
341
|
+
} catch (err) {
|
|
342
|
+
spinner.fail('Sync failed');
|
|
343
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ── Clone command ───────────────────────────────────────────
|
|
349
|
+
program
|
|
350
|
+
.command('clone <url>')
|
|
351
|
+
.description('Generate a working Next.js starter from a site\'s design')
|
|
352
|
+
.option('-o, --out <dir>', 'output directory', './cloned-design')
|
|
353
|
+
.action(async (url, opts) => {
|
|
354
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
355
|
+
|
|
356
|
+
console.log('');
|
|
357
|
+
console.log(chalk.bold(' designlang clone'));
|
|
358
|
+
console.log(chalk.gray(` ${url}`));
|
|
359
|
+
console.log('');
|
|
360
|
+
|
|
361
|
+
const spinner = ora('Extracting design...').start();
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const design = await extractDesignLanguage(url);
|
|
365
|
+
spinner.text = 'Generating Next.js project...';
|
|
366
|
+
|
|
367
|
+
const result = generateClone(design, resolve(opts.out));
|
|
368
|
+
|
|
369
|
+
spinner.succeed('Clone generated!');
|
|
370
|
+
console.log('');
|
|
371
|
+
for (const f of result.files) {
|
|
372
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(f)}`);
|
|
373
|
+
}
|
|
374
|
+
console.log('');
|
|
375
|
+
console.log(chalk.bold(' To run:'));
|
|
376
|
+
console.log(chalk.gray(` cd ${opts.out} && npm install && npm run dev`));
|
|
377
|
+
console.log('');
|
|
378
|
+
|
|
379
|
+
} catch (err) {
|
|
380
|
+
spinner.fail('Clone failed');
|
|
381
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ── Watch command ───────────────────────────────────────────
|
|
387
|
+
program
|
|
388
|
+
.command('watch <url>')
|
|
389
|
+
.description('Monitor a site for design changes')
|
|
390
|
+
.option('--interval <minutes>', 'check interval in minutes', parseInt, 60)
|
|
391
|
+
.action(async (url, opts) => {
|
|
392
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
393
|
+
const intervalMs = (opts.interval || 60) * 60 * 1000;
|
|
394
|
+
|
|
395
|
+
console.log('');
|
|
396
|
+
console.log(chalk.bold(' designlang watch'));
|
|
397
|
+
console.log(chalk.gray(` ${url} (every ${opts.interval || 60}min)`));
|
|
398
|
+
console.log('');
|
|
399
|
+
|
|
400
|
+
const check = async () => {
|
|
401
|
+
const spinner = ora('Checking for design changes...').start();
|
|
402
|
+
try {
|
|
403
|
+
const result = await watchSite(url);
|
|
404
|
+
|
|
405
|
+
if (result.isFirstRun) {
|
|
406
|
+
spinner.succeed('Baseline captured. Watching for changes...');
|
|
407
|
+
} else if (result.changes.length === 0) {
|
|
408
|
+
spinner.succeed(`No changes — ${new Date().toLocaleTimeString()}`);
|
|
409
|
+
} else {
|
|
410
|
+
spinner.warn(`${result.changes.length} changes detected!`);
|
|
411
|
+
for (const c of result.changes) {
|
|
412
|
+
console.log(` ${chalk.yellow('≠')} ${c.what}: ${c.from} → ${c.to}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
spinner.fail(`Check failed: ${err.message}`);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
await check();
|
|
421
|
+
console.log(chalk.gray(`\n Next check in ${opts.interval || 60} minutes. Press Ctrl+C to stop.\n`));
|
|
422
|
+
setInterval(check, intervalMs);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// ── Score command ───────────────────────────────────────────
|
|
426
|
+
program
|
|
427
|
+
.command('score <url>')
|
|
428
|
+
.description('Score a website\'s design system quality')
|
|
429
|
+
.action(async (url) => {
|
|
430
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
431
|
+
|
|
432
|
+
const spinner = ora('Analyzing design...').start();
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const design = await extractDesignLanguage(url);
|
|
436
|
+
const s = design.score;
|
|
437
|
+
|
|
438
|
+
spinner.stop();
|
|
439
|
+
console.log('');
|
|
440
|
+
console.log(chalk.bold(' Design System Score'));
|
|
441
|
+
console.log(chalk.gray(` ${url}`));
|
|
442
|
+
console.log('');
|
|
443
|
+
|
|
444
|
+
const gradeColor = s.grade === 'A' ? chalk.green : s.grade === 'B' ? chalk.cyan : s.grade === 'C' ? chalk.yellow : chalk.red;
|
|
445
|
+
console.log(` ${gradeColor.bold(` ${s.overall}/100 Grade: ${s.grade}`)}`);
|
|
446
|
+
console.log('');
|
|
447
|
+
|
|
448
|
+
// Category breakdown
|
|
449
|
+
const cats = [
|
|
450
|
+
['Color Discipline', s.scores.colorDiscipline],
|
|
451
|
+
['Typography', s.scores.typographyConsistency],
|
|
452
|
+
['Spacing System', s.scores.spacingSystem],
|
|
453
|
+
['Shadows', s.scores.shadowConsistency],
|
|
454
|
+
['Border Radii', s.scores.radiusConsistency],
|
|
455
|
+
['Accessibility', s.scores.accessibility],
|
|
456
|
+
['Tokenization', s.scores.tokenization],
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
for (const [name, score] of cats) {
|
|
460
|
+
const bar = '█'.repeat(Math.round(score / 5)) + '░'.repeat(20 - Math.round(score / 5));
|
|
461
|
+
const color = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;
|
|
462
|
+
console.log(` ${chalk.gray(name.padEnd(20))} ${color(bar)} ${score}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (s.strengths.length > 0) {
|
|
466
|
+
console.log('');
|
|
467
|
+
console.log(chalk.bold(' Strengths:'));
|
|
468
|
+
for (const str of s.strengths) {
|
|
469
|
+
console.log(` ${chalk.green('✓')} ${str}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (s.issues.length > 0) {
|
|
474
|
+
console.log('');
|
|
475
|
+
console.log(chalk.bold(' Issues:'));
|
|
476
|
+
for (const issue of s.issues) {
|
|
477
|
+
console.log(` ${chalk.yellow('!')} ${issue}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
console.log('');
|
|
481
|
+
|
|
482
|
+
} catch (err) {
|
|
483
|
+
spinner.fail('Scoring failed');
|
|
484
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
219
489
|
program.parse();
|
package/designlang.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "designlang",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Extract the complete design language from any website — colors, typography, spacing, shadows, and more. Outputs AI-optimized markdown, W3C design tokens, Tailwind config, and CSS variables.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|