designlang 12.3.0 → 12.7.1
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/.claude-plugin/marketplace.json +15 -7
- package/.claude-plugin/plugin.json +19 -8
- package/CHANGELOG.md +230 -0
- package/README.md +38 -8
- package/SUPPORT.md +22 -0
- package/bin/design-extract.js +234 -0
- package/commands/battle.md +27 -0
- package/commands/brand.md +59 -0
- package/commands/extract.md +41 -0
- package/commands/grade.md +29 -0
- package/commands/pack.md +37 -0
- package/commands/remix.md +29 -0
- package/commands/theme-swap.md +42 -0
- package/package.json +3 -3
- package/src/ci.js +36 -2
- package/src/formatters/brand-book.js +1052 -0
- package/src/formatters/theme-swap.js +272 -0
- package/src/pack.js +376 -0
- package/src/recolor.js +199 -0
- package/src/utils/color-gamut.js +64 -0
package/bin/design-extract.js
CHANGED
|
@@ -49,6 +49,10 @@ import { formatBattle, formatBattleMarkdown } from '../src/formatters/battle.js'
|
|
|
49
49
|
import { formatScoreBadge } from '../src/formatters/badge.js';
|
|
50
50
|
import { formatRemix } from '../src/formatters/remix.js';
|
|
51
51
|
import { VOCABULARIES, getVocabulary, listVocabularies } from '../src/vocabularies/index.js';
|
|
52
|
+
import { buildPack } from '../src/pack.js';
|
|
53
|
+
import { recolorDesign } from '../src/recolor.js';
|
|
54
|
+
import { formatThemeSwap, formatThemeSwapMarkdown } from '../src/formatters/theme-swap.js';
|
|
55
|
+
import { formatBrandBook, formatBrandBookMarkdown } from '../src/formatters/brand-book.js';
|
|
52
56
|
import { nameFromUrl } from '../src/utils.js';
|
|
53
57
|
|
|
54
58
|
function validateUrl(url) {
|
|
@@ -1172,6 +1176,236 @@ program
|
|
|
1172
1176
|
}
|
|
1173
1177
|
});
|
|
1174
1178
|
|
|
1179
|
+
// ── Pack command — bundle every emitter into one design-system directory
|
|
1180
|
+
program
|
|
1181
|
+
.command('pack <url>')
|
|
1182
|
+
.description('Bundle every output (tokens, components, storybook, prompts, starter) into a single design-system directory')
|
|
1183
|
+
.option('-o, --out <dir>', 'output directory (default: ./<host>-design-system)')
|
|
1184
|
+
.option('--with-clone', 'include the full Next.js clone as the starter (slower; otherwise emits a minimal HTML starter)')
|
|
1185
|
+
.option('--open', 'open the starter index.html in the default browser')
|
|
1186
|
+
.action(async (url, opts) => {
|
|
1187
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
1188
|
+
validateUrl(url);
|
|
1189
|
+
|
|
1190
|
+
const spinner = ora(`Extracting ${url}...`).start();
|
|
1191
|
+
try {
|
|
1192
|
+
const design = await extractDesignLanguage(url);
|
|
1193
|
+
|
|
1194
|
+
const defaultDirName = `${nameFromUrl(url)}-design-system`;
|
|
1195
|
+
const outDir = resolve(opts.out || defaultDirName);
|
|
1196
|
+
|
|
1197
|
+
spinner.text = 'Packing artifacts...';
|
|
1198
|
+
const { files } = buildPack(design, {
|
|
1199
|
+
outDir,
|
|
1200
|
+
version: PKG_VERSION,
|
|
1201
|
+
withClone: !!opts.withClone,
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
spinner.stop();
|
|
1205
|
+
console.log('');
|
|
1206
|
+
console.log(` ${chalk.bold('Packed')} ${chalk.gray('·')} ${chalk.cyan(files.length)} files ${chalk.gray('·')} ${chalk.gray(url)}`);
|
|
1207
|
+
console.log('');
|
|
1208
|
+
console.log(` ${chalk.green('✓')} ${chalk.bold(outDir)}`);
|
|
1209
|
+
console.log('');
|
|
1210
|
+
console.log(chalk.gray(' Top-level layout:'));
|
|
1211
|
+
const top = ['README.md', 'LICENSE.txt', 'tokens/', 'components/', 'storybook/', 'starter/', 'prompts/', 'extras/'];
|
|
1212
|
+
for (const t of top) console.log(` ${chalk.gray('·')} ${t}`);
|
|
1213
|
+
console.log('');
|
|
1214
|
+
console.log(chalk.gray(` Zip it: cd ${outDir} && zip -r ../${defaultDirName}.zip .`));
|
|
1215
|
+
console.log(chalk.gray(` Storybook: cd ${outDir}/storybook && npm install && npm run storybook`));
|
|
1216
|
+
console.log('');
|
|
1217
|
+
|
|
1218
|
+
if (opts.open) {
|
|
1219
|
+
const starter = join(outDir, 'starter', 'index.html');
|
|
1220
|
+
const { spawn } = await import('child_process');
|
|
1221
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1222
|
+
spawn(cmd, [starter], { detached: true, stdio: 'ignore' }).unref();
|
|
1223
|
+
}
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
spinner.fail('Pack failed');
|
|
1226
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
1227
|
+
process.exit(1);
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
// ── Theme-swap command — recolour an extracted design around a new primary
|
|
1232
|
+
program
|
|
1233
|
+
.command('theme-swap <url>')
|
|
1234
|
+
.description('Recolour the extracted design around a new brand primary (preserves type, spacing, neutrals)')
|
|
1235
|
+
.requiredOption('--primary <hex>', 'target primary colour as hex (e.g. "#ff4800")')
|
|
1236
|
+
.option('--from <hex>', 'override the auto-detected source primary (e.g. when the extractor misclassifies)')
|
|
1237
|
+
.option('-o, --out <dir>', 'output directory', './design-extract-output')
|
|
1238
|
+
.option('-n, --name <name>', 'output file prefix (default: derived from URL + target hex)')
|
|
1239
|
+
.option('--format <fmt>', 'output format: html, md, json, tokens, all', 'all')
|
|
1240
|
+
.option('--open', 'open the HTML preview in the default browser')
|
|
1241
|
+
.action(async (url, opts) => {
|
|
1242
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
1243
|
+
validateUrl(url);
|
|
1244
|
+
|
|
1245
|
+
const spinner = ora(`Extracting ${url}...`).start();
|
|
1246
|
+
try {
|
|
1247
|
+
const original = await extractDesignLanguage(url);
|
|
1248
|
+
spinner.text = `Recolouring around ${opts.primary}...`;
|
|
1249
|
+
const { design: recoloured, summary } = recolorDesign(original, {
|
|
1250
|
+
primary: opts.primary,
|
|
1251
|
+
fromPrimary: opts.from,
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
const outDir = resolve(opts.out);
|
|
1255
|
+
mkdirSync(outDir, { recursive: true });
|
|
1256
|
+
const targetSlug = String(opts.primary).replace(/^#/, '').toLowerCase();
|
|
1257
|
+
const prefix = opts.name || `${nameFromUrl(url)}-themeswap-${targetSlug}`;
|
|
1258
|
+
const written = [];
|
|
1259
|
+
|
|
1260
|
+
if (opts.format === 'all' || opts.format === 'html') {
|
|
1261
|
+
const html = formatThemeSwap(original, recoloured, { version: PKG_VERSION });
|
|
1262
|
+
const p = join(outDir, `${prefix}.themeswap.html`);
|
|
1263
|
+
writeFileSync(p, html);
|
|
1264
|
+
written.push(p);
|
|
1265
|
+
}
|
|
1266
|
+
if (opts.format === 'all' || opts.format === 'md') {
|
|
1267
|
+
const md = formatThemeSwapMarkdown(original, recoloured);
|
|
1268
|
+
const p = join(outDir, `${prefix}.themeswap.md`);
|
|
1269
|
+
writeFileSync(p, md);
|
|
1270
|
+
written.push(p);
|
|
1271
|
+
}
|
|
1272
|
+
if (opts.format === 'all' || opts.format === 'json') {
|
|
1273
|
+
const p = join(outDir, `${prefix}.themeswap.json`);
|
|
1274
|
+
writeFileSync(p, JSON.stringify({
|
|
1275
|
+
url: original.meta?.url,
|
|
1276
|
+
from: summary.from,
|
|
1277
|
+
to: summary.to,
|
|
1278
|
+
hueShift: summary.hueShift,
|
|
1279
|
+
changedColors: summary.changes.length,
|
|
1280
|
+
changes: summary.changes,
|
|
1281
|
+
timestamp: new Date().toISOString(),
|
|
1282
|
+
}, null, 2));
|
|
1283
|
+
written.push(p);
|
|
1284
|
+
}
|
|
1285
|
+
if (opts.format === 'all' || opts.format === 'tokens') {
|
|
1286
|
+
const tokens = formatDtcgTokens(recoloured);
|
|
1287
|
+
const p = join(outDir, `${prefix}.themeswap.tokens.json`);
|
|
1288
|
+
writeFileSync(p, typeof tokens === 'string' ? tokens : JSON.stringify(tokens, null, 2));
|
|
1289
|
+
written.push(p);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
spinner.stop();
|
|
1293
|
+
console.log('');
|
|
1294
|
+
console.log(` ${chalk.bold(`${summary.from} → ${summary.to}`)} ${chalk.gray('·')} ${chalk.cyan(summary.changes.length)} colours ${chalk.gray('·')} ${chalk.gray(url)}`);
|
|
1295
|
+
console.log(` ${chalk.gray(`Hue shift: ${(summary.hueShift).toFixed(1)}° · neutrals preserved · type/spacing/motion untouched`)}`);
|
|
1296
|
+
console.log('');
|
|
1297
|
+
for (const f of written) console.log(` ${chalk.green('✓')} ${chalk.gray(f)}`);
|
|
1298
|
+
console.log('');
|
|
1299
|
+
|
|
1300
|
+
if (opts.open) {
|
|
1301
|
+
const htmlPath = written.find(p => p.endsWith('.html'));
|
|
1302
|
+
if (htmlPath) {
|
|
1303
|
+
const { spawn } = await import('child_process');
|
|
1304
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1305
|
+
spawn(cmd, [htmlPath], { detached: true, stdio: 'ignore' }).unref();
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
spinner.fail('Theme-swap failed');
|
|
1310
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// ── Brand command — full editorial brand-guidelines book ────
|
|
1316
|
+
program
|
|
1317
|
+
.command('brand <url>')
|
|
1318
|
+
.description('Generate a full brand-guidelines book — colour, type, spacing, motion, voice, components, accessibility, tokens, and how-to-use guidance')
|
|
1319
|
+
.option('-o, --out <dir>', 'output directory', './design-extract-output')
|
|
1320
|
+
.option('-n, --name <name>', 'output file prefix (default: derived from URL)')
|
|
1321
|
+
.option('--format <fmt>', 'output format: html, md, json, all', 'all')
|
|
1322
|
+
.option('--open', 'open the HTML book in the default browser')
|
|
1323
|
+
.action(async (url, opts) => {
|
|
1324
|
+
if (!url.startsWith('http')) url = `https://${url}`;
|
|
1325
|
+
validateUrl(url);
|
|
1326
|
+
|
|
1327
|
+
const spinner = ora(`Building brand guidelines for ${url}...`).start();
|
|
1328
|
+
try {
|
|
1329
|
+
// The brand book leans on the full extraction (logo, motion, voice,
|
|
1330
|
+
// anatomy, accessibility), so default to --full unless the caller has
|
|
1331
|
+
// explicitly opted out via env.
|
|
1332
|
+
const design = await extractDesignLanguage(url, {
|
|
1333
|
+
screenshots: true,
|
|
1334
|
+
responsive: false,
|
|
1335
|
+
interactions: false,
|
|
1336
|
+
deepInteract: true,
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
const outDir = resolve(opts.out);
|
|
1340
|
+
mkdirSync(outDir, { recursive: true });
|
|
1341
|
+
const prefix = opts.name || `${nameFromUrl(url)}.brand`;
|
|
1342
|
+
const written = [];
|
|
1343
|
+
|
|
1344
|
+
if (opts.format === 'all' || opts.format === 'html') {
|
|
1345
|
+
const html = formatBrandBook(design, { version: PKG_VERSION });
|
|
1346
|
+
const p = join(outDir, `${prefix}.html`);
|
|
1347
|
+
writeFileSync(p, html);
|
|
1348
|
+
written.push(p);
|
|
1349
|
+
}
|
|
1350
|
+
if (opts.format === 'all' || opts.format === 'md') {
|
|
1351
|
+
const md = formatBrandBookMarkdown(design);
|
|
1352
|
+
const p = join(outDir, `${prefix}.md`);
|
|
1353
|
+
writeFileSync(p, md);
|
|
1354
|
+
written.push(p);
|
|
1355
|
+
}
|
|
1356
|
+
if (opts.format === 'all' || opts.format === 'json') {
|
|
1357
|
+
// A trimmed JSON of the most-used surfaces in the book — useful for
|
|
1358
|
+
// programmatic consumption without re-running extraction.
|
|
1359
|
+
const p = join(outDir, `${prefix}.json`);
|
|
1360
|
+
writeFileSync(p, JSON.stringify({
|
|
1361
|
+
url: design.meta?.url,
|
|
1362
|
+
title: design.meta?.title,
|
|
1363
|
+
timestamp: design.meta?.timestamp,
|
|
1364
|
+
intent: design.pageIntent,
|
|
1365
|
+
material: design.materialLanguage,
|
|
1366
|
+
imagery: design.imageryStyle,
|
|
1367
|
+
library: design.componentLibrary,
|
|
1368
|
+
stack: design.stack,
|
|
1369
|
+
voice: design.voice,
|
|
1370
|
+
colors: design.colors,
|
|
1371
|
+
typography: design.typography,
|
|
1372
|
+
spacing: design.spacing,
|
|
1373
|
+
shadows: design.shadows,
|
|
1374
|
+
borders: design.borders,
|
|
1375
|
+
motion: design.motion,
|
|
1376
|
+
accessibility: design.accessibility,
|
|
1377
|
+
score: design.score,
|
|
1378
|
+
}, null, 2));
|
|
1379
|
+
written.push(p);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
spinner.stop();
|
|
1383
|
+
const colorCount = (design.colors?.all || []).length;
|
|
1384
|
+
const fontCount = (design.typography?.families || []).length;
|
|
1385
|
+
const grade = design.score?.grade || '—';
|
|
1386
|
+
console.log('');
|
|
1387
|
+
console.log(` ${chalk.bold('Brand book')} ${chalk.gray('·')} ${chalk.cyan(colorCount + ' tokens')} ${chalk.gray('·')} ${chalk.cyan(fontCount + ' fonts')} ${chalk.gray('·')} ${chalk.cyan('grade ' + grade)} ${chalk.gray('·')} ${chalk.gray(url)}`);
|
|
1388
|
+
console.log('');
|
|
1389
|
+
for (const f of written) console.log(` ${chalk.green('✓')} ${chalk.gray(f)}`);
|
|
1390
|
+
console.log('');
|
|
1391
|
+
console.log(chalk.gray(` Open the .html — it's a self-contained, print-ready guidelines book.`));
|
|
1392
|
+
console.log('');
|
|
1393
|
+
|
|
1394
|
+
if (opts.open) {
|
|
1395
|
+
const htmlPath = written.find(p => p.endsWith('.html'));
|
|
1396
|
+
if (htmlPath) {
|
|
1397
|
+
const { spawn } = await import('child_process');
|
|
1398
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1399
|
+
spawn(cmd, [htmlPath], { detached: true, stdio: 'ignore' }).unref();
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
spinner.fail('Brand book failed');
|
|
1404
|
+
console.error(chalk.red(`\n ${err.message}\n`));
|
|
1405
|
+
process.exit(1);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1175
1409
|
// ── Apply command ──────────────────────────────────────────
|
|
1176
1410
|
program
|
|
1177
1411
|
.command('apply <url>')
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Head-to-head graded battle card between two sites — eight dimensions, bar-by-bar, verdict line.
|
|
3
|
+
argument-hint: <urlA> <urlB>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Pit two sites against each other and emit a single shareable HTML battle card.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang battle $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If `$ARGUMENTS` is empty or contains fewer than two URLs, ask the user for both sites.
|
|
13
|
+
|
|
14
|
+
Both sites are extracted in parallel (~30s total). Outputs land in `./design-extract-output/`:
|
|
15
|
+
|
|
16
|
+
- `<a>-vs-<b>.battle.html` — the shareable card
|
|
17
|
+
- `<a>-vs-<b>.battle.md` — markdown summary
|
|
18
|
+
- `<a>-vs-<b>.battle.json` — structured scores
|
|
19
|
+
|
|
20
|
+
After the run:
|
|
21
|
+
|
|
22
|
+
1. Read the `*.battle.md` file
|
|
23
|
+
2. Show the user the verdict line ("X wins" / "tie") and the per-dimension table
|
|
24
|
+
3. Highlight the dimensions where the gap is widest
|
|
25
|
+
4. Offer to open the HTML card
|
|
26
|
+
|
|
27
|
+
This is pure viral content — battles are designed to be tweeted. Pair with `/grade <url> --badge` so each side has a permanent badge to link back to.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate a full editorial brand-guidelines book for any URL. 13 chapters covering colour, typography, spacing, shape, iconography, motion, components, voice, accessibility, tokens, and how-to-use guidance. Print-ready, dark-mode toggle, hand-off-ready single HTML.
|
|
3
|
+
argument-hint: <url> [--open]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Build a self-contained, hand-off-ready brand-guidelines book from any live URL.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang brand $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If no URL is provided, ask the user which site to document.
|
|
13
|
+
|
|
14
|
+
Output goes to `./design-extract-output/`:
|
|
15
|
+
|
|
16
|
+
- `*.brand.html` — the editorial book (open this — it's a self-contained, print-ready document with TOC, smooth-scroll, dark-mode toggle)
|
|
17
|
+
- `*.brand.md` — terse markdown summary (good for diffing snapshots)
|
|
18
|
+
- `*.brand.json` — structured slice of the design with the surfaces the book renders
|
|
19
|
+
|
|
20
|
+
After the run completes:
|
|
21
|
+
|
|
22
|
+
1. Read `*.brand.md` to summarise what was captured (host, page intent, material language, tone, palette size, type families, WCAG score)
|
|
23
|
+
2. Tell the user the headline numbers (`X tokens · Y fonts · grade Z`)
|
|
24
|
+
3. Offer to open the HTML book (`--open` does this automatically)
|
|
25
|
+
4. Suggest pairing: `/pack <url>` if they want a developer-facing bundle to drop into a project, `/grade <url>` for the audit, `/theme-swap <url>` to derive a recoloured variant
|
|
26
|
+
|
|
27
|
+
## What's in the book
|
|
28
|
+
|
|
29
|
+
| § | Chapter | What it documents |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| 01 | About | Page intent, material language, imagery style, component library, stack, voice tone |
|
|
32
|
+
| 02 | Logo | Extracted SVG logo + clearspace + dimensions |
|
|
33
|
+
| 03 | Colour | Brand colours with HEX/RGB/HSL/usage, neutrals grid, full palette, A11y callout |
|
|
34
|
+
| 04 | Typography | Display + body families, weights, scale table, large specimen |
|
|
35
|
+
| 05 | Spacing | Base unit, scale length, visual rhythm bars |
|
|
36
|
+
| 06 | Shape | Border radii visualised, shadow elevation system |
|
|
37
|
+
| 07 | Iconography | Icon library detection + captured icons grid |
|
|
38
|
+
| 08 | Motion | Feel, durations (animated dots), easings, spring presence, scroll-linked flag |
|
|
39
|
+
| 09 | Components | Detected components with slots/variants/sizes |
|
|
40
|
+
| 10 | Voice & tone | Tone, pronoun posture, heading case, top CTA verbs, sample headings |
|
|
41
|
+
| 11 | Accessibility | WCAG score, failing pairs table, suggested replacements |
|
|
42
|
+
| 12 | Tokens | Drop-in code blocks (CSS vars, Tailwind config) + cross-ref to `pack` |
|
|
43
|
+
| 13 | How to use | Six rules of thumb for derivative work |
|
|
44
|
+
|
|
45
|
+
## Useful flags
|
|
46
|
+
|
|
47
|
+
| Flag | Effect |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `--format html\|md\|json\|all` | Pick the output(s). Default `all`. |
|
|
50
|
+
| `--out <dir>` | Output directory. Default `./design-extract-output`. |
|
|
51
|
+
| `-n, --name <name>` | Output file prefix. Default derived from URL. |
|
|
52
|
+
| `--open` | Open the HTML book in your default browser. |
|
|
53
|
+
|
|
54
|
+
## Pairs nicely with
|
|
55
|
+
|
|
56
|
+
- `/pack <url>` — when the recipient wants files to drop into a project, not a guidelines doc
|
|
57
|
+
- `/grade <url>` — when the recipient wants the audit/score, not the full book
|
|
58
|
+
- `/theme-swap <url> --primary <hex>` — when the recipient wants the same system in their brand colour
|
|
59
|
+
- `/remix <url> --as <vocab>` — when the recipient wants a vocabulary swap (brutalist, swiss, etc.)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Extract the complete design language from a URL — DTCG tokens, Tailwind, Figma vars, motion, voice, components.
|
|
3
|
+
argument-hint: <url> [extra flags…]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run **designlang** against the user-provided URL and surface the result.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If no `$ARGUMENTS` were supplied, ask the user which URL to extract.
|
|
13
|
+
|
|
14
|
+
Default output goes to `./design-extract-output/`. Once it finishes, read the generated `*-design-language.md` (the AI-optimized markdown) and present a tight summary to the user:
|
|
15
|
+
|
|
16
|
+
- Primary palette (hex codes)
|
|
17
|
+
- Type families + scale
|
|
18
|
+
- Spacing base + scale
|
|
19
|
+
- WCAG accessibility score
|
|
20
|
+
- Component patterns detected
|
|
21
|
+
- Notable signals (motion feel, material language, brand voice tone)
|
|
22
|
+
|
|
23
|
+
Then offer follow-ups:
|
|
24
|
+
|
|
25
|
+
- `/grade <url>` — shareable HTML Design Report Card + SVG badge
|
|
26
|
+
- `/battle <urlA> <urlB>` — head-to-head graded comparison
|
|
27
|
+
- `/remix <url> --as <vocab>` — restyle in another vocabulary
|
|
28
|
+
- `/pack <url>` — bundle every output into one design-system directory
|
|
29
|
+
|
|
30
|
+
Useful flags the user may pass via `$ARGUMENTS`:
|
|
31
|
+
|
|
32
|
+
| Flag | Effect |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `--full` | screenshots + responsive + interactions + deep-interact |
|
|
35
|
+
| `--depth <n>` | crawl N additional canonical pages |
|
|
36
|
+
| `--dark` | also extract dark mode |
|
|
37
|
+
| `--platforms ios,android,flutter,wordpress` | multi-platform emitters |
|
|
38
|
+
| `--smart` | LLM fallback for low-confidence classifiers (needs `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) |
|
|
39
|
+
| `--cookie-file ./session.json` | authenticated extraction |
|
|
40
|
+
|
|
41
|
+
Full reference: https://github.com/Manavarya09/design-extract#full-cli-reference
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate a shareable HTML "Design Report Card" — letter grade, 8 dimensions, evidence (palette, type, rhythm), strengths + fixes, plus an SVG badge.
|
|
3
|
+
argument-hint: <url> [--badge] [--open]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Audit the design system at the user-provided URL. Emits a self-contained HTML report card plus JSON + Markdown variants. Pass `--badge` to also emit a shields.io-style SVG you can drop into any README.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang grade $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If no `$ARGUMENTS` were supplied, ask the user which URL to grade.
|
|
13
|
+
|
|
14
|
+
After the run completes:
|
|
15
|
+
|
|
16
|
+
1. Read the generated `*.grade.md` file from `./design-extract-output/`
|
|
17
|
+
2. Surface the headline grade (A–F · score · 8 dimensions)
|
|
18
|
+
3. Highlight the top 3 strengths and top 3 issues from the report
|
|
19
|
+
4. Offer to open the HTML in the browser (the `--open` flag does this automatically)
|
|
20
|
+
|
|
21
|
+
If the user wants the live shareable badge URL instead of generating files locally, they can use:
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+

|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This endpoint is blob-cached 24h with edge caching for ~50ms repeat hits.
|
|
28
|
+
|
|
29
|
+
Compare two sites with `/battle <A> <B>`, restyle with `/remix`, bundle everything with `/pack`.
|
package/commands/pack.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Bundle every designlang output (DTCG tokens, Tailwind, shadcn, Figma vars, motion, anatomy, Storybook, prompts) into one polished design-system directory ready to zip and ship.
|
|
3
|
+
argument-hint: <url> [--with-clone] [--open]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the full extraction once and write every emitter output into a single, signed, layered directory.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang pack $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If no URL is provided, ask the user. Default output is `./<host>-design-system/`.
|
|
13
|
+
|
|
14
|
+
Layout produced:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
<host>-design-system/
|
|
18
|
+
├── README.md bespoke "Built from <host>" + grade + at-a-glance
|
|
19
|
+
├── LICENSE.txt provenance + usage guidance
|
|
20
|
+
├── tokens/ DTCG + Tailwind + CSS vars + Figma vars + motion + theme.js
|
|
21
|
+
├── components/ typed React stubs (anatomy.tsx)
|
|
22
|
+
├── storybook/ runnable Storybook project
|
|
23
|
+
├── starter/ minimal HTML starter wired to tokens/variables.css
|
|
24
|
+
├── prompts/ v0 / Lovable / Cursor / Claude Artifacts + named recipes/*.md
|
|
25
|
+
└── extras/ voice.json + prompt-pack.md rollup
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
After the run:
|
|
29
|
+
|
|
30
|
+
1. Surface the headline (`X files, Y KB · ./<dir>`)
|
|
31
|
+
2. Read the auto-generated `README.md` and tell the user what's inside
|
|
32
|
+
3. Suggest next steps:
|
|
33
|
+
- `cd <dir> && zip -r ../<dir>.zip .` to package for sharing
|
|
34
|
+
- `cd <dir>/storybook && npm install && npm run storybook` to run the design system locally
|
|
35
|
+
- Copy `tokens/tailwind.config.js` straight into a project
|
|
36
|
+
|
|
37
|
+
Use `--with-clone` to swap the minimal HTML starter for the full Next.js clone (slower; only when the user wants a runnable app).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Restyle a site in another design vocabulary — brutalist, swiss, art-deco, cyberpunk, soft-ui, or editorial. Preserves page shape, swaps the visual vocabulary.
|
|
3
|
+
argument-hint: <url> --as <brutalist|swiss|art-deco|cyberpunk|soft-ui|editorial> | --all
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Restyle the page shape of an extracted site under a different visual vocabulary.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang remix $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If `$ARGUMENTS` is empty, ask the user for a URL and offer the six vocabularies. If only a URL is given, default to `--all` so they see every variant side by side.
|
|
13
|
+
|
|
14
|
+
The six vocabularies:
|
|
15
|
+
|
|
16
|
+
| `--as <id>` | Vibe |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `brutalist` | Raw, blocky, monospace, no shadows |
|
|
19
|
+
| `swiss` | Clean grid, sans, minimal palette |
|
|
20
|
+
| `art-deco` | Geometric, gold accents, serif display |
|
|
21
|
+
| `cyberpunk` | Neon, dark, glowing CTAs |
|
|
22
|
+
| `soft-ui` | Pastel, soft shadows, generous radii |
|
|
23
|
+
| `editorial` | Newspaper feel, serif, narrow columns |
|
|
24
|
+
|
|
25
|
+
Use `--all` to emit all six in one pass — six standalone HTML files plus a comparison index.
|
|
26
|
+
|
|
27
|
+
After the run, read the comparison index (`*-remix-index.html` or `*-remix.<vocab>.html`) and tell the user which file to open. Offer to launch `--open` automatically.
|
|
28
|
+
|
|
29
|
+
Pair with `/battle` for cross-vocab fights ("Stripe-as-cyberpunk vs Vercel-as-art-deco"), or `/pack` to bundle a remixed system as a downloadable design directory.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Recolour an extracted site's design around a new brand primary. OKLCH hue rotation preserves perceptual lightness — neutrals, type, spacing, and motion stay untouched. Side-by-side HTML preview + recoloured tokens (DTCG, Tailwind, shadcn, Figma).
|
|
3
|
+
argument-hint: <url> --primary <hex> [--from <hex>] [--open]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Take any extracted site and swap its brand primary to the user-provided hex. The whole palette rotates around that hue while neutrals and structural tokens stay put.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx designlang theme-swap $ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If `$ARGUMENTS` doesn't contain both a URL and `--primary <hex>`, ask the user for the missing piece.
|
|
13
|
+
|
|
14
|
+
After the run completes, all output goes to `./design-extract-output/`:
|
|
15
|
+
|
|
16
|
+
- `*.themeswap.html` — editorial side-by-side preview (open this)
|
|
17
|
+
- `*.themeswap.md` — markdown diff table
|
|
18
|
+
- `*.themeswap.json` — structured before→after deltas
|
|
19
|
+
- `*.themeswap.tokens.json` — recoloured DTCG token set you can drop into a project
|
|
20
|
+
|
|
21
|
+
After the command finishes:
|
|
22
|
+
|
|
23
|
+
1. Read `*.themeswap.md` to summarise the swap (`from → to`, hue shift, count of changed colours).
|
|
24
|
+
2. Show the user the verdict line ("X colours changed, neutrals preserved, type/spacing/motion untouched").
|
|
25
|
+
3. Offer to open the HTML preview (`--open` does this automatically).
|
|
26
|
+
4. Suggest the recoloured token files as drop-ins for an existing Tailwind / shadcn project.
|
|
27
|
+
|
|
28
|
+
## Useful flags
|
|
29
|
+
|
|
30
|
+
| Flag | Effect |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `--primary <hex>` | **required.** Target brand colour (e.g. `"#ff4800"`). |
|
|
33
|
+
| `--from <hex>` | Override the auto-detected source primary when the extractor misclassifies (e.g. a neutral got promoted by usage count). |
|
|
34
|
+
| `--format html\|md\|json\|tokens\|all` | Pick the output(s). Default `all`. |
|
|
35
|
+
| `--out <dir>` | Output directory. Default `./design-extract-output`. |
|
|
36
|
+
| `--open` | Open the HTML preview in your default browser. |
|
|
37
|
+
|
|
38
|
+
## Pairs nicely with
|
|
39
|
+
|
|
40
|
+
- `/grade <url>` — grade the recoloured site by feeding the same target through `/extract` then `/grade`.
|
|
41
|
+
- `/pack <url>` — bundle the recoloured tokens as a downloadable design-system folder.
|
|
42
|
+
- `/remix <url> --as <vocab>` — full vocabulary swap (brutalist, art-deco, cyberpunk…) instead of just brand colour.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "designlang",
|
|
3
|
-
"version": "12.
|
|
4
|
-
"description": "Extract the complete design language from any website and ship it
|
|
3
|
+
"version": "12.7.1",
|
|
4
|
+
"description": "Extract the complete design language from any website and ship it \u2014 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": {
|
|
7
7
|
"designlang": "./bin/design-extract.js"
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
],
|
|
49
49
|
"author": "masyv",
|
|
50
50
|
"license": "MIT"
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/ci.js
CHANGED
|
@@ -32,9 +32,43 @@ export async function runCi(url, opts) {
|
|
|
32
32
|
out.push(`## designlang · design regression guard`);
|
|
33
33
|
out.push(`\n**URL:** \`${url}\` \n**Run:** ${new Date().toISOString()}\n`);
|
|
34
34
|
|
|
35
|
-
// 1. Extract
|
|
35
|
+
// 1. Extract — guard the throw site so CI artifacts never come back empty.
|
|
36
|
+
// Playwright can crash on flaky networks, ad-walled URLs, or self-signed
|
|
37
|
+
// proxies; when that happens we still want a report file + a summary.json
|
|
38
|
+
// so downstream jobs (artifact uploads, status comments, dashboards) have
|
|
39
|
+
// something to point at. We preserve the original error via `cause` so the
|
|
40
|
+
// stack survives — losing the trace was a real problem with #76's draft.
|
|
36
41
|
const { extractDesignLanguage } = await import('./index.js');
|
|
37
|
-
|
|
42
|
+
let design;
|
|
43
|
+
try {
|
|
44
|
+
design = await extractDesignLanguage(url);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const stack = err?.stack || String(err);
|
|
47
|
+
out.push(section('Extraction', [
|
|
48
|
+
`_failed — ${err.message}_`,
|
|
49
|
+
'',
|
|
50
|
+
'```',
|
|
51
|
+
stack,
|
|
52
|
+
'```',
|
|
53
|
+
].join('\n')));
|
|
54
|
+
const md = out.join('\n');
|
|
55
|
+
const mdPath = join(outDir, 'ci-report.md');
|
|
56
|
+
writeFileSync(mdPath, md, 'utf-8');
|
|
57
|
+
const summary = {
|
|
58
|
+
url,
|
|
59
|
+
score: null,
|
|
60
|
+
grade: null,
|
|
61
|
+
driftVerdict: 'unknown',
|
|
62
|
+
extractionFailed: true,
|
|
63
|
+
error: err.message,
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
writeFileSync(join(outDir, 'ci-summary.json'), JSON.stringify(summary, null, 2), 'utf-8');
|
|
67
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
68
|
+
try { writeFileSync(process.env.GITHUB_STEP_SUMMARY, md, { flag: 'a' }); } catch {}
|
|
69
|
+
}
|
|
70
|
+
return { mdPath, md, summary, shouldFail: true, cause: err };
|
|
71
|
+
}
|
|
38
72
|
|
|
39
73
|
// 2. Score block
|
|
40
74
|
if (design.score) {
|