designlang 11.1.0 โ†’ 11.2.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/SECURITY.md ADDED
@@ -0,0 +1,46 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 11.x | :white_check_mark: |
8
+ | 10.x | :white_check_mark: (critical only) |
9
+ | < 10 | :x: |
10
+
11
+ Only the latest minor of each supported major receives fixes.
12
+
13
+ ## Reporting a vulnerability
14
+
15
+ **Please don't open a public issue.** Use GitHub's private vulnerability reporting instead:
16
+
17
+ ๐Ÿ‘‰ <https://github.com/Manavarya09/design-extract/security/advisories/new>
18
+
19
+ What to include:
20
+
21
+ - A description of the issue and its impact.
22
+ - A minimal repro (URL being extracted, CLI flags, OS + Node version).
23
+ - Suggested severity and, if you have one, a proposed fix.
24
+
25
+ ### What to expect
26
+
27
+ - Acknowledgement within **72 hours**.
28
+ - A triage verdict (accepted / needs info / out of scope) within **7 days**.
29
+ - Public disclosure coordinated with you after a fix ships โ€” usually as part of a patch release.
30
+
31
+ ## Scope
32
+
33
+ In scope:
34
+
35
+ - The `designlang` CLI and all subcommands (`extract`, `clone`, `ci`, `studio`, `replay`, `mcp`, etc.).
36
+ - The MCP server (`src/mcp/server.js`).
37
+ - The hosted extractor website (`website/`) when used at its canonical URL.
38
+ - The Figma, Chrome, Raycast, and VS Code extensions shipped in this repo.
39
+
40
+ Out of scope:
41
+
42
+ - Vulnerabilities that require the user to run `designlang` against a page **they themselves control** (i.e. self-owned XSS in content they feed us).
43
+ - Denial of service via crafted sites that simply take a long time to extract โ€” we already time-bound Playwright operations.
44
+ - Browser-level shadow-DOM opacity (closed shadow roots are unreachable by web platform design, not a designlang bug).
45
+
46
+ Thanks for taking the time to report responsibly.
@@ -94,6 +94,7 @@ program
94
94
  .option('--smart', 'use optional LLM fallback when heuristic classifiers have low confidence (needs OPENAI_API_KEY or ANTHROPIC_API_KEY)')
95
95
  .option('--pages <n>', 'crawl N canonical pages (pricing/docs/blog/about/product) in addition to the homepage', parseInt)
96
96
  .option('--no-prompts', 'skip writing the prompt-pack directory')
97
+ .option('--no-design-md', 'skip writing the agent-native DESIGN.md (single-file, 8-section, YAML front matter)')
97
98
  .option('--responsive-shots', 'capture full-page PNGs at 4 breakpoints ร— (light,dark)')
98
99
  .option('--perf', 'measure Core Web Vitals + bundle profile (LCP/CLS/INP, JS/CSS/font/img bytes, third-party count)')
99
100
  .option('--json', 'output raw JSON to stdout (for CI/CD)')
@@ -352,6 +353,13 @@ program
352
353
  }
353
354
  files.push({ name: `${prefix}-voice.json`, content: JSON.stringify(design.voice || {}, null, 2), label: 'Brand Voice' });
354
355
 
356
+ // v11.2: agent-native single-file DESIGN.md (compatible with the
357
+ // 8-canonical-section convention; default-on, opt-out via --no-design-md).
358
+ if (merged.designMd !== false) {
359
+ const { formatDesignMd } = await import('../src/formatters/design-md.js');
360
+ files.push({ name: `${prefix}-DESIGN.md`, content: formatDesignMd(design), label: 'DESIGN.md (agent-native)' });
361
+ }
362
+
355
363
  // v10: page intent + section roles + visual DNA + component library + multi-page + prompt pack.
356
364
  files.push({ name: `${prefix}-intent.json`, content: JSON.stringify({ pageIntent: design.pageIntent, sectionRoles: design.sectionRoles }, null, 2), label: 'Page Intent + Section Roles' });
357
365
  files.push({ name: `${prefix}-visual-dna.json`, content: JSON.stringify({ materialLanguage: design.materialLanguage, imageryStyle: design.imageryStyle, backgroundPatterns: design.backgroundPatterns }, null, 2), label: 'Visual DNA' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "designlang",
3
- "version": "11.1.0",
3
+ "version": "11.2.0",
4
4
  "description": "Extract the complete design language from any website and ship it โ€” clone to a working Next.js starter, guard tokens with a CI drift bot, or browse everything in a local studio. Outputs W3C DTCG tokens, motion tokens, typed anatomy stubs, Tailwind config, and ready-to-paste v0 / Lovable / Cursor / Claude-Artifacts prompts.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,319 @@
1
+ // DESIGN.md โ€” agent-native single-file output. Compatible with the
2
+ // 8-canonical-section convention (Overview ยท Colors ยท Typography ยท Layout
3
+ // ยท Elevation and Depth ยท Shapes ยท Components ยท Do's and Don'ts), plus
4
+ // YAML front matter holding the machine-readable token snapshot.
5
+ //
6
+ // Designed to be a drop-in replacement for design-extractor.com's
7
+ // DESIGN.md output, but driven by the v10/v11 semantic layer (intent,
8
+ // material, voice, anatomy, library detection) rather than just colors.
9
+
10
+ import { readFileSync } from 'fs';
11
+
12
+ function yamlString(v) {
13
+ if (v == null) return '~';
14
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
15
+ const s = String(v);
16
+ if (s === '' || /[:#&*!?[\]{},|>%@`'"\n]/.test(s) || /^\s|\s$/.test(s)) {
17
+ return JSON.stringify(s);
18
+ }
19
+ return s;
20
+ }
21
+
22
+ function yamlList(arr, indent = ' ') {
23
+ if (!arr || arr.length === 0) return '[]';
24
+ return '\n' + arr.map(v => `${indent}- ${yamlString(v)}`).join('\n');
25
+ }
26
+
27
+ function yamlMap(obj, indent = ' ') {
28
+ const entries = Object.entries(obj || {}).filter(([, v]) => v != null);
29
+ if (entries.length === 0) return '{}';
30
+ return '\n' + entries.map(([k, v]) => `${indent}${k}: ${yamlString(v)}`).join('\n');
31
+ }
32
+
33
+ function topColor(colors, role) {
34
+ return colors?.[role]?.hex || null;
35
+ }
36
+
37
+ function fmtPx(v) {
38
+ if (v == null) return null;
39
+ const n = typeof v === 'number' ? v : parseFloat(String(v));
40
+ return Number.isFinite(n) ? `${Math.round(n)}px` : String(v);
41
+ }
42
+
43
+ function uniq(arr) { return [...new Set(arr.filter(Boolean))]; }
44
+
45
+ function pickHeading(voice, fallback) {
46
+ const s = (voice?.sampleHeadings || []).find(h => h && h.length > 4 && h.length < 80);
47
+ return s || fallback;
48
+ }
49
+
50
+ function ratioLine(v, n) { return `${v} (${n})`; }
51
+
52
+ // โ”€โ”€โ”€ Section renderers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
53
+
54
+ function sectionOverview(design) {
55
+ const intent = design.pageIntent?.type || 'landing';
56
+ const intentConf = design.pageIntent?.confidence;
57
+ const material = design.materialLanguage?.label || 'flat';
58
+ const matConf = design.materialLanguage?.confidence;
59
+ const library = design.componentLibrary?.library;
60
+ const libConf = design.componentLibrary?.confidence;
61
+ const order = (design.sectionRoles?.readingOrder || []).join(' โ†’ ') || 'โ€”';
62
+ const voice = design.voice || {};
63
+ const tone = voice.tone || 'neutral';
64
+ const lede = pickHeading(voice, design.meta?.title || '');
65
+ const url = design.meta?.url || '';
66
+
67
+ const lines = [];
68
+ lines.push(`A **${intent}** page${intentConf ? ` (heuristic confidence ${intentConf})` : ''}, dressed in **${material}** material${matConf ? ` (${matConf})` : ''}.`);
69
+ if (library && library !== 'unknown') {
70
+ lines.push('');
71
+ lines.push(`Component library appears to be **${library}**${libConf ? ` (${libConf})` : ''}.`);
72
+ }
73
+ if (lede) {
74
+ lines.push('');
75
+ lines.push(`> "${lede}"`);
76
+ }
77
+ lines.push('');
78
+ lines.push(`The author writes in a **${tone}** voice; headings tend to be **${voice.headingStyle || 'sentence'}** case and **${voice.headingLengthClass || 'balanced'}**.`);
79
+ lines.push('');
80
+ lines.push(`Reading order detected on the source: \`${order}\`.`);
81
+ if (url) {
82
+ lines.push('');
83
+ lines.push(`Source: <${url}>.`);
84
+ }
85
+ return lines.join('\n');
86
+ }
87
+
88
+ function sectionColors(design) {
89
+ const c = design.colors || {};
90
+ const lines = ['| role | hex | usage |', '|---|---|---|'];
91
+ const rows = [
92
+ ['primary', c.primary?.hex, c.primary?.count],
93
+ ['secondary', c.secondary?.hex, c.secondary?.count],
94
+ ['accent', c.accent?.hex, c.accent?.count],
95
+ ['background', c.backgrounds?.[0], 'โ€”'],
96
+ ['foreground', c.text?.[0], 'โ€”'],
97
+ ];
98
+ for (const [role, hex, count] of rows) {
99
+ if (!hex) continue;
100
+ lines.push(`| ${role} | \`${hex}\` | ${count ?? 'โ€”'} |`);
101
+ }
102
+ const neutrals = (c.neutrals || []).slice(0, 5);
103
+ if (neutrals.length) {
104
+ lines.push('');
105
+ lines.push('**Neutrals:** ' + neutrals.map(n => `\`${n.hex}\``).join(' ยท '));
106
+ }
107
+ if (c.all?.length) {
108
+ lines.push('');
109
+ lines.push(`**Total unique colors detected:** ${c.all.length}.`);
110
+ }
111
+ return lines.join('\n');
112
+ }
113
+
114
+ function sectionTypography(design) {
115
+ const t = design.typography || {};
116
+ const lines = [];
117
+ if (t.families?.length) {
118
+ lines.push('**Families**');
119
+ for (const f of t.families.slice(0, 4)) {
120
+ lines.push(`- \`${f.name}\`${f.weights ? ` โ€” weights ${[...new Set(f.weights)].join(', ')}` : ''}${f.count ? ` ยท ${f.count} uses` : ''}`);
121
+ }
122
+ }
123
+ if (t.body?.size) {
124
+ lines.push('');
125
+ lines.push(`**Body size:** \`${t.body.size}px\` / line-height \`${t.body.lineHeight ?? '1.5'}\`.`);
126
+ }
127
+ if (t.headings?.length) {
128
+ lines.push('');
129
+ lines.push('**Heading scale**');
130
+ lines.push('| level | size | weight | line-height |');
131
+ lines.push('|---|---|---|---|');
132
+ for (const [i, h] of t.headings.slice(0, 4).entries()) {
133
+ lines.push(`| h${i + 1} | \`${h.size}px\` | \`${h.weight}\` | \`${h.lineHeight}\` |`);
134
+ }
135
+ }
136
+ return lines.join('\n');
137
+ }
138
+
139
+ function sectionLayout(design) {
140
+ const sp = design.spacing || {};
141
+ const bp = design.breakpoints || [];
142
+ const layout = design.layout || {};
143
+ const lines = [];
144
+ if (sp.base) lines.push(`**Spacing base:** \`${sp.base}px\` increments.`);
145
+ if (sp.scale?.length) lines.push(`**Scale:** ${sp.scale.slice(0, 10).map(s => `\`${(s.value ?? s)}px\``).join(' ยท ')}`);
146
+ if (layout.gridCount != null || layout.flexCount != null) {
147
+ lines.push('');
148
+ lines.push(`**Layout primitives:** ${layout.gridCount ?? 0} grid containers ยท ${layout.flexCount ?? 0} flex containers.`);
149
+ }
150
+ if (bp.length) {
151
+ lines.push('');
152
+ lines.push(`**Breakpoints:** ${bp.map(b => `\`${b}px\``).join(' ยท ')}`);
153
+ }
154
+ return lines.join('\n') || '_No layout signals captured._';
155
+ }
156
+
157
+ function sectionElevation(design) {
158
+ const sh = design.shadows?.values || [];
159
+ const z = design.zIndex || {};
160
+ const lines = [];
161
+ if (sh.length) {
162
+ lines.push('**Shadow scale**');
163
+ for (const s of sh.slice(0, 6)) {
164
+ lines.push(`- \`${s.label || '?'}\` โ€” \`${s.raw || s.value}\``);
165
+ }
166
+ } else {
167
+ lines.push('_No discrete shadow tokens detected โ€” flat material._');
168
+ }
169
+ if (z.allValues?.length) {
170
+ lines.push('');
171
+ lines.push(`**Z-index layers:** ${z.allValues.length}${z.issues?.length ? ` ยท โš  ${z.issues.length} issue(s)` : ''}`);
172
+ }
173
+ return lines.join('\n');
174
+ }
175
+
176
+ function sectionShapes(design) {
177
+ const r = design.borders?.radii || [];
178
+ const lines = [];
179
+ if (r.length) {
180
+ lines.push('**Radius scale**');
181
+ for (const x of r.slice(0, 6)) lines.push(`- \`${x.label || '?'}\` โ€” \`${x.value}px\``);
182
+ } else {
183
+ lines.push('_No discrete radius tokens detected โ€” sharp/brutalist shapes._');
184
+ }
185
+ return lines.join('\n');
186
+ }
187
+
188
+ function sectionComponents(design) {
189
+ const lines = [];
190
+ const detected = Object.keys(design.components || {});
191
+ if (detected.length) {
192
+ lines.push(`**Detected patterns:** ${detected.map(c => `\`${c}\``).join(' ยท ')}`);
193
+ lines.push('');
194
+ }
195
+ const anatomies = design.componentAnatomy || [];
196
+ if (anatomies.length) {
197
+ lines.push('**Anatomy**');
198
+ lines.push('| kind | variants | sizes | instances |');
199
+ lines.push('|---|---|---|---|');
200
+ for (const a of anatomies.slice(0, 8)) {
201
+ const variants = (a.props?.variant || []).join(', ') || 'โ€”';
202
+ const sizes = (a.props?.size || []).join(', ') || 'โ€”';
203
+ lines.push(`| ${a.kind} | ${variants} | ${sizes} | ${a.totalInstances ?? 'โ€”'} |`);
204
+ }
205
+ }
206
+ if (!lines.length) lines.push('_No component anatomy extracted._');
207
+ return lines.join('\n');
208
+ }
209
+
210
+ function sectionDosDonts(design) {
211
+ const voice = design.voice || {};
212
+ const score = design.score || {};
213
+ const a11y = design.accessibility || {};
214
+ const lines = [];
215
+
216
+ lines.push("**Do's**");
217
+ const dos = [];
218
+ const ctas = (voice.ctaVerbs || []).slice(0, 3).map(v => v.value).filter(Boolean);
219
+ if (ctas.length) dos.push(`Use \`${ctas.join('\`, \`')}\` as the primary verbs in CTAs โ€” these dominate the source.`);
220
+ if (voice.headingStyle) dos.push(`Write headings in **${voice.headingStyle}** case, **${voice.headingLengthClass || 'balanced'}** length.`);
221
+ if (voice.pronoun && voice.pronoun !== 'neutral') dos.push(`Address the reader with the pronoun posture **${voice.pronoun}**.`);
222
+ if (design.materialLanguage?.label) dos.push(`Stay inside the **${design.materialLanguage.label}** material โ€” match shadow and radius habits.`);
223
+ if (!dos.length) dos.push('_No strong directional signals captured._');
224
+ for (const d of dos) lines.push(`- ${d}`);
225
+
226
+ lines.push('');
227
+ lines.push("**Don'ts**");
228
+ const donts = [];
229
+ if (a11y.failCount > 0) donts.push(`Don't ship copy on the colors flagged in accessibility โ€” ${a11y.failCount} contrast pair(s) fail WCAG AA on the source itself.`);
230
+ if (score.issues?.length) for (const i of score.issues.slice(0, 4)) donts.push(`Don't ${i.toLowerCase().replace(/^./, c => c.toLowerCase())}.`);
231
+ if (!donts.length) donts.push("_No anti-patterns surfaced. Don't invent new tokens โ€” reuse the scale above._");
232
+ for (const d of donts) lines.push(`- ${d}`);
233
+
234
+ return lines.join('\n');
235
+ }
236
+
237
+ // โ”€โ”€โ”€ YAML front matter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
238
+
239
+ function frontMatter(design, version) {
240
+ const url = design.meta?.url || '';
241
+ const title = design.meta?.title || '';
242
+ const c = design.colors || {};
243
+ const t = design.typography || {};
244
+ const sp = design.spacing || {};
245
+ const r = design.borders?.radii || [];
246
+ const sh = design.shadows?.values || [];
247
+
248
+ const lines = ['---'];
249
+ lines.push(`site: ${yamlString(title || url)}`);
250
+ if (url) lines.push(`url: ${yamlString(url)}`);
251
+ lines.push(`generated_at: ${yamlString(new Date().toISOString())}`);
252
+ lines.push(`generator: ${yamlString(`designlang@${version}`)}`);
253
+ if (design.pageIntent?.type) lines.push(`intent: ${yamlString(design.pageIntent.type)}`);
254
+ if (design.materialLanguage?.label) lines.push(`material: ${yamlString(design.materialLanguage.label)}`);
255
+ if (design.componentLibrary?.library && design.componentLibrary.library !== 'unknown') {
256
+ lines.push(`library: ${yamlString(design.componentLibrary.library)}`);
257
+ }
258
+
259
+ lines.push('tokens:');
260
+ lines.push(' colors:' + yamlMap({
261
+ primary: topColor(c, 'primary'),
262
+ secondary: topColor(c, 'secondary'),
263
+ accent: topColor(c, 'accent'),
264
+ background: c.backgrounds?.[0],
265
+ foreground: c.text?.[0],
266
+ }, ' '));
267
+ lines.push(' typography:' + yamlMap({
268
+ sans: t.families?.[0]?.name,
269
+ mono: t.families?.find(f => /mono/i.test(f.name))?.name,
270
+ base: t.body?.size,
271
+ }, ' '));
272
+ lines.push(' spacing:' + yamlMap({
273
+ base: sp.base,
274
+ scale: sp.scale?.length ? '[' + sp.scale.slice(0, 10).map(s => (s.value ?? s)).join(', ') + ']' : null,
275
+ }, ' '));
276
+ if (r.length) {
277
+ lines.push(' radii:' + yamlMap(Object.fromEntries(r.slice(0, 6).map(x => [x.label || `r${x.value}`, x.value])), ' '));
278
+ }
279
+ if (sh.length) {
280
+ lines.push(' shadows:' + yamlMap(Object.fromEntries(sh.slice(0, 4).map(x => [x.label || 'shadow', x.raw || x.value])), ' '));
281
+ }
282
+ lines.push('---');
283
+ return lines.join('\n');
284
+ }
285
+
286
+ // โ”€โ”€โ”€ Main โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
287
+
288
+ let CACHED_VERSION = null;
289
+ function pkgVersion() {
290
+ if (CACHED_VERSION) return CACHED_VERSION;
291
+ try {
292
+ const url = new URL('../../package.json', import.meta.url);
293
+ CACHED_VERSION = JSON.parse(readFileSync(url, 'utf-8')).version;
294
+ } catch { CACHED_VERSION = '0.0.0'; }
295
+ return CACHED_VERSION;
296
+ }
297
+
298
+ export function formatDesignMd(design) {
299
+ const version = pkgVersion();
300
+ const fm = frontMatter(design, version);
301
+
302
+ const sections = [
303
+ ['Overview', sectionOverview(design)],
304
+ ['Colors', sectionColors(design)],
305
+ ['Typography', sectionTypography(design)],
306
+ ['Layout', sectionLayout(design)],
307
+ ['Elevation and Depth', sectionElevation(design)],
308
+ ['Shapes', sectionShapes(design)],
309
+ ['Components', sectionComponents(design)],
310
+ ["Do's and Don'ts", sectionDosDonts(design)],
311
+ ];
312
+
313
+ const body = sections.map(([h, b]) => `# ${h}\n\n${b || '_โ€”_'}\n`).join('\n');
314
+
315
+ const sourceUrl = design.meta?.url || '';
316
+ const footer = `\n---\n_Generated by [designlang](https://github.com/Manavarya09/design-extract) v${version} from <${sourceUrl}>._\n_Compatible with the DESIGN.md convention pioneered by [design-extractor.com](https://www.design-extractor.com) โ€” extended with intent, material, voice, anatomy, and library detection._\n`;
317
+
318
+ return `${fm}\n\n${body}${footer}`;
319
+ }