dembrandt 0.11.0 → 0.12.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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Extract a website's design system into design tokens in a few seconds: logo, colors, typography, borders, and more. One command.
8
8
 
9
- ![Dembrandt Any website to design tokens](https://raw.githubusercontent.com/dembrandt/dembrandt/main/docs/images/banner.png)
9
+ ![Dembrandt: Any website to design tokens](https://raw.githubusercontent.com/dembrandt/dembrandt/main/docs/images/banner.png)
10
10
 
11
11
  ## Install
12
12
 
@@ -77,7 +77,7 @@ Default: formatted terminal display only. Use `--save-output` to persist results
77
77
 
78
78
  ### Multi-Page Extraction
79
79
 
80
- Analyze multiple pages to get a more complete picture of a site's design system. Results are merged into a single unified output with cross-page confidence boosting tokens appearing on multiple pages get higher confidence scores.
80
+ Analyze multiple pages to get a more complete picture of a site's design system. Results are merged into a single unified output with cross-page confidence boosting: tokens appearing on multiple pages get higher confidence scores.
81
81
 
82
82
  ```bash
83
83
  # Analyze homepage + 4 auto-discovered pages (default: 5 total)
@@ -133,7 +133,7 @@ The DTCG format is an industry-standard JSON schema that can be consumed by desi
133
133
 
134
134
  ### DESIGN.md
135
135
 
136
- Use `--design-md` to generate a [DESIGN.md](https://stitch.withgoogle.com/docs/design-md) file a plain-text design system document readable by AI agents.
136
+ Use `--design-md` to generate a [DESIGN.md](https://stitch.withgoogle.com/docs/design-md) file, a plain-text design system document readable by AI agents.
137
137
 
138
138
  ```bash
139
139
  dembrandt example.com --design-md
@@ -142,7 +142,7 @@ dembrandt example.com --design-md
142
142
 
143
143
  ### Brand Guide PDF
144
144
 
145
- Use `--brand-guide` to generate a printable PDF summarizing the extracted design system colors, typography, components, and logo on a single document.
145
+ Use `--brand-guide` to generate a printable PDF summarizing the extracted design system: colors, typography, components, and logo on a single document.
146
146
 
147
147
  ```bash
148
148
  dembrandt example.com --brand-guide
@@ -176,7 +176,7 @@ Opens http://localhost:5173 with API on port 3002.
176
176
  - Spacing, shadows, border radius visualization
177
177
  - Button and link component previews
178
178
  - Dark/light theme toggle
179
- - Section nav links on extraction pages jump directly to Colors, Typography, Shadows, etc. via a sticky sidebar
179
+ - Section nav links on extraction pages, jump directly to Colors, Typography, Shadows, etc. via a sticky sidebar
180
180
 
181
181
  Extractions are performed via CLI (`dembrandt <url> --save-output`) and automatically appear in the UI.
182
182
 
@@ -204,9 +204,9 @@ Uses Playwright to render the page, reads computed styles from the DOM, analyzes
204
204
 
205
205
  ### Color Confidence
206
206
 
207
- - High Logo, primary interactive elements
208
- - Medium Secondary interactive elements, icons, navigation
209
- - Low Generic UI components (filtered from display)
207
+ - High: Logo, primary interactive elements
208
+ - Medium: Secondary interactive elements, icons, navigation
209
+ - Low: Generic UI components (filtered from display)
210
210
  - Only shows high and medium confidence colors in terminal. Full palette in JSON.
211
211
 
212
212
  ## Limitations
@@ -228,12 +228,10 @@ Dembrandt does not host, redistribute, or claim rights to any third-party brand
228
228
 
229
229
  ## Contributing
230
230
 
231
- Bugs, weird sites, pull requests all welcome.
231
+ Bugs, weird sites, pull requests. All welcome.
232
232
 
233
233
  Open an [Issue](https://github.com/dembrandt/dembrandt/issues) or PR.
234
234
 
235
235
  @thevangelist
236
236
 
237
- ---
238
-
239
- MIT — do whatever you want with it.
237
+ MIT. Do whatever you want with it.
package/index.js CHANGED
@@ -27,7 +27,7 @@ program
27
27
  .description("Extract design tokens from any website")
28
28
  .version("0.11.0")
29
29
  .argument("<url>")
30
- .option("--browser <type>", "Browser to use (chromium|firefox)", "chromium")
30
+ .option("--browser <type>", "Browser to use (chromium|firefox); set BROWSER_CDP_ENDPOINT env var to connect to an existing Chromium instance via CDP", "chromium")
31
31
  .option("--json-only", "Output raw JSON")
32
32
  .option("--save-output", "Save JSON file to output folder")
33
33
  .option("--dtcg", "Export in W3C Design Tokens (DTCG) format")
@@ -93,10 +93,18 @@ program
93
93
  if (opts.noSandbox && opts.browser === 'chromium') {
94
94
  launchArgs.push("--no-sandbox", "--disable-setuid-sandbox");
95
95
  }
96
- browser = await browserType.launch({
97
- headless: !useHeaded,
98
- args: launchArgs,
99
- });
96
+ if (process.env.BROWSER_CDP_ENDPOINT) {
97
+ if (opts.browser !== 'chromium') {
98
+ throw new Error("BROWSER_CDP_ENDPOINT is only supported with --browser chromium.");
99
+ }
100
+ spinner.text = "Connecting over CDP...";
101
+ browser = await browserType.connectOverCDP(process.env.BROWSER_CDP_ENDPOINT);
102
+ } else {
103
+ browser = await browserType.launch({
104
+ headless: !useHeaded,
105
+ args: launchArgs,
106
+ });
107
+ }
100
108
 
101
109
  try {
102
110
  const isMultiPage = opts.pages || opts.sitemap;
@@ -170,7 +178,7 @@ program
170
178
  await browser.close();
171
179
  browser = null;
172
180
 
173
- if (useHeaded) throw err;
181
+ if (useHeaded || process.env.BROWSER_CDP_ENDPOINT) throw err;
174
182
 
175
183
  if (
176
184
  err.message.includes("Timeout") ||
package/lib/extractors.js CHANGED
@@ -464,7 +464,7 @@ export async function extractBranding(
464
464
  // Merge hover/focus colors into palette
465
465
  hoverFocusColors.forEach(({ color, property }) => {
466
466
  const isDuplicate = colors.palette.some((c) => c.color === color);
467
- if (!isDuplicate && color) {
467
+ if (!isDuplicate && color && /^(rgba?\(|hsla?\(|#)/i.test(color.trim())) {
468
468
  // Normalize and add to palette
469
469
  const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
470
470
  let normalized = color.toLowerCase();
@@ -1000,6 +1000,8 @@ async function extractColors(page) {
1000
1000
  // Only accept if it contains rgb/hsl/# inside
1001
1001
  return /#[0-9a-f]{3,6}|rgba?\(|hsla?\(/i.test(value);
1002
1002
  }
1003
+ // Reject modern color functions not parseable to hex (oklab, oklch, lch, lab, color(), display-p3)
1004
+ if (/^(oklab|oklch|lch|lab|color)\s*\(/i.test(value)) return false;
1003
1005
  // Accept hex, rgb, hsl, named colors
1004
1006
  return /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\(|[a-z]+)/i.test(value);
1005
1007
  }
@@ -1171,12 +1173,14 @@ async function extractColors(page) {
1171
1173
  const colorRegex = /(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\)|[a-z]+)/gi;
1172
1174
  const matches = colorValue.match(colorRegex) || [];
1173
1175
 
1174
- // Filter out invalid matches
1176
+ // Filter out invalid matches and modern CSS function names that aren't parseable
1177
+ const cssColorFunctions = new Set(['oklab','oklch','lch','lab','color','display','hsl','rgb','rgba','hsla','inherit','initial','unset','none','auto','normal']);
1175
1178
  return matches.filter(c =>
1176
1179
  c !== 'transparent' &&
1177
1180
  c !== 'rgba(0, 0, 0, 0)' &&
1178
1181
  c !== 'rgba(0,0,0,0)' &&
1179
- c.length > 2
1182
+ c.length > 2 &&
1183
+ !cssColorFunctions.has(c.toLowerCase())
1180
1184
  );
1181
1185
  }
1182
1186
 
@@ -1244,9 +1248,10 @@ async function extractColors(page) {
1244
1248
  return true;
1245
1249
  }
1246
1250
 
1247
- // Chromatic colors (saturated) that never appear as backgrounds are likely browser defaults
1248
- // or framework internals, not brand colors. Real brand colors appear on backgrounds somewhere.
1249
- if (data.bgCount === 0) {
1251
+ // Chromatic colors with no background usage and no semantic context are likely browser
1252
+ // defaults or framework internals. But link/text brand colors have semantic score > count
1253
+ // (they appear on semantically-labeled elements), so exempt those.
1254
+ if (data.bgCount === 0 && data.score < data.count * 1.5) {
1250
1255
  const hex = normalized.replace('#', '');
1251
1256
  const r = parseInt(hex.substring(0, 2), 16);
1252
1257
  const g = parseInt(hex.substring(2, 4), 16);
@@ -1254,7 +1259,6 @@ async function extractColors(page) {
1254
1259
  const max = Math.max(r, g, b);
1255
1260
  const min = Math.min(r, g, b);
1256
1261
  const saturation = max === 0 ? 0 : (max - min) / max;
1257
- // If saturation > 0.3, this is a chromatic color with no background usage
1258
1262
  if (saturation > 0.3) {
1259
1263
  return true;
1260
1264
  }
@@ -1347,8 +1351,10 @@ async function extractColors(page) {
1347
1351
 
1348
1352
  const palette = Array.from(colorMap.entries())
1349
1353
  .filter(([normalizedColor, data]) => {
1350
- // Filter out colors below threshold
1351
- if (data.count < threshold) return false;
1354
+ // High-score colors (semantically significant: buttons, headers with brand colors) bypass
1355
+ // the count threshold a header that appears once can still be a primary brand color.
1356
+ const highScore = data.score >= 10 || (data.count > 0 && data.score / data.count >= 3);
1357
+ if (!highScore && data.count < threshold) return false;
1352
1358
 
1353
1359
  // Filter out structural colors (very high usage without semantic context)
1354
1360
  if (isStructuralColor(data, totalElements)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dembrandt",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Extract design tokens and publicly visible CSS information from any website",
5
5
  "mcpName": "io.github.dembrandt/dembrandt",
6
6
  "main": "index.js",