dembrandt 0.28.0 → 0.29.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.
Files changed (72) hide show
  1. package/README.md +59 -404
  2. package/dist/index.js +49 -23
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/colors.d.ts +15 -0
  5. package/dist/lib/colors.js +17 -9
  6. package/dist/lib/colors.js.map +1 -1
  7. package/dist/lib/drift.js +159 -23
  8. package/dist/lib/drift.js.map +1 -1
  9. package/dist/lib/exit-codes.d.ts +3 -1
  10. package/dist/lib/exit-codes.js +3 -2
  11. package/dist/lib/exit-codes.js.map +1 -1
  12. package/dist/lib/extractors/index.js +27 -0
  13. package/dist/lib/extractors/index.js.map +1 -1
  14. package/dist/lib/extractors/voice.d.ts +12 -0
  15. package/dist/lib/extractors/voice.js +247 -0
  16. package/dist/lib/extractors/voice.js.map +1 -0
  17. package/dist/lib/formatters/html.js +9 -2
  18. package/dist/lib/formatters/html.js.map +1 -1
  19. package/dist/lib/formatters/markdown.d.ts +19 -0
  20. package/dist/lib/formatters/markdown.js +19 -0
  21. package/dist/lib/formatters/markdown.js.map +1 -1
  22. package/dist/lib/mcp/jobs.d.ts +66 -0
  23. package/dist/lib/mcp/jobs.js +133 -0
  24. package/dist/lib/mcp/jobs.js.map +1 -0
  25. package/dist/lib/mcp/options.d.ts +58 -0
  26. package/dist/lib/mcp/options.js +76 -0
  27. package/dist/lib/mcp/options.js.map +1 -0
  28. package/dist/lib/merger.js +6 -0
  29. package/dist/lib/merger.js.map +1 -1
  30. package/dist/lib/types.d.ts +79 -1
  31. package/dist/lib/version.d.ts +11 -1
  32. package/dist/lib/version.js +11 -1
  33. package/dist/lib/version.js.map +1 -1
  34. package/dist/lib/voice/index.d.ts +11 -0
  35. package/dist/lib/voice/index.js +46 -0
  36. package/dist/lib/voice/index.js.map +1 -0
  37. package/dist/lib/voice/metrics.d.ts +14 -0
  38. package/dist/lib/voice/metrics.js +153 -0
  39. package/dist/lib/voice/metrics.js.map +1 -0
  40. package/dist/lib/voice/page-type.d.ts +12 -0
  41. package/dist/lib/voice/page-type.js +28 -0
  42. package/dist/lib/voice/page-type.js.map +1 -0
  43. package/dist/mcp-server.js +101 -139
  44. package/dist/mcp-server.js.map +1 -1
  45. package/dist/package.json +4 -2
  46. package/dist/test/color-format-drift.test.js +124 -0
  47. package/dist/test/color-format-drift.test.js.map +1 -1
  48. package/dist/test/design-md-lint.test.d.ts +1 -0
  49. package/dist/test/design-md-lint.test.js +99 -0
  50. package/dist/test/design-md-lint.test.js.map +1 -0
  51. package/dist/test/drift.test.js +25 -0
  52. package/dist/test/drift.test.js.map +1 -1
  53. package/dist/test/exit-codes.test.js +9 -1
  54. package/dist/test/exit-codes.test.js.map +1 -1
  55. package/dist/test/mcp-jobs.test.d.ts +1 -0
  56. package/dist/test/mcp-jobs.test.js +167 -0
  57. package/dist/test/mcp-jobs.test.js.map +1 -0
  58. package/dist/test/mcp-options.test.d.ts +1 -0
  59. package/dist/test/mcp-options.test.js +136 -0
  60. package/dist/test/mcp-options.test.js.map +1 -0
  61. package/dist/test/merger.test.js +30 -0
  62. package/dist/test/merger.test.js.map +1 -1
  63. package/dist/test/voice-collect.test.d.ts +1 -0
  64. package/dist/test/voice-collect.test.js +175 -0
  65. package/dist/test/voice-collect.test.js.map +1 -0
  66. package/dist/test/voice-fixture.test.d.ts +1 -0
  67. package/dist/test/voice-fixture.test.js +141 -0
  68. package/dist/test/voice-fixture.test.js.map +1 -0
  69. package/dist/test/voice-metrics.test.d.ts +1 -0
  70. package/dist/test/voice-metrics.test.js +134 -0
  71. package/dist/test/voice-metrics.test.js.map +1 -0
  72. package/package.json +4 -2
package/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  [![license](https://img.shields.io/npm/l/dembrandt.svg)](https://github.com/dembrandt/dembrandt/blob/main/LICENSE)
6
6
  [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-me-pink?style=flat&logo=github-sponsors)](https://github.com/sponsors/dembrandt)
7
7
 
8
- Extract a website's design system into design tokens in a few seconds: logo, colors, typography, borders, and more. One command.
8
+ Extract any website's design system in one command. Enforce it in CI.
9
+
10
+ Logo, colors, typography, spacing, borders, shadows, motion, components. W3C design tokens in seconds.
9
11
 
10
12
  ![Dembrandt: Any website to design tokens](https://raw.githubusercontent.com/dembrandt/dembrandt/main/docs/images/banner.png)
11
13
 
@@ -22,57 +24,12 @@ which ships no browser binaries, so a fresh install has nothing to launch until
22
24
  run it. Skipping it fails with `browser engine not available`.
23
25
 
24
26
  Or use npx without installing: `npx dembrandt dembrandt.com`. The browser step applies
25
- here too run `npx dembrandt install-browser` first. Browsers land in a shared
27
+ here too: run `npx dembrandt install-browser` first. Browsers land in a shared
26
28
  Playwright cache, so either route only needs it once.
27
29
 
28
30
  Requires Node.js 18+
29
31
 
30
- ## AI Agent Integration (MCP)
31
-
32
- Use Dembrandt as a tool in Claude Code, Cursor, Windsurf, or any MCP-compatible client. Ask your agent to "extract the color palette from dembrandt.com" and it calls Dembrandt automatically.
33
-
34
- ```bash
35
- claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt dembrandt-mcp
36
- ```
37
-
38
- Or add to your project's `.mcp.json`:
39
-
40
- ```json
41
- {
42
- "mcpServers": {
43
- "dembrandt": {
44
- "command": "npx",
45
- "args": ["-y", "--package", "dembrandt", "dembrandt-mcp"]
46
- }
47
- }
48
- }
49
- ```
50
-
51
- Available tools include `get_design_tokens`, `get_color_palette`, `get_typography`, `get_component_styles`, `get_surfaces`, `get_spacing`, and `get_brand_identity`, plus pure analysis tools (`compute_drift`, `get_findings`, `export_dtcg`, `generate_design_md`, `render_report`) and job-control tools. Extraction tools accept `mobile`, `cookie` (for authenticated pages), and `wcag` options.
52
-
53
- Pair with **[dembrandt-skills](https://github.com/dembrandt/dembrandt-skills)** to give your agent UX intelligence on top of extracted tokens — hierarchy, accessibility, interaction states, and a full 6-stage design pipeline orchestrator.
54
-
55
- ```bash
56
- npx skills add dembrandt/dembrandt-skills
57
- ```
58
-
59
- ## Dembrandt App (Beta)
60
-
61
- Load extractions, track token drift, and compare snapshots. **[dembrandt.com/app](https://www.dembrandt.com/app)**
62
-
63
- * **Automatic drift tracking from CI.** Generate an API key at [dembrandt.com/app/api-keys](https://www.dembrandt.com/app/api-keys), then pass `--key` to the CLI. Every run uploads a snapshot to your account and scores it against the previous one for that domain. Wire into GitHub Actions or any CI runner and every deploy records itself.
64
- * **Pin a baseline.** Mark any snapshot as your reference. Every subsequent extraction is automatically scored against it.
65
- * **Visual diff.** Color swatches, before/after values, delta scores per category: colors, typography, spacing, radius, shadows.
66
- * **Snapshot timeline.** Proportional timeline per domain — scrub across any date range from days to years.
67
- * **Compare side by side.** Load multiple extractions into one view: two releases, two sites, or two surfaces.
68
- * **Copy tokens.** Paste values straight into Copilot, Claude, or Cursor.
69
- * **No login required for local use.** Data stays in the browser. Sign in with GitHub to enable cloud sync.
70
-
71
- ## Recipe library
72
-
73
- **[dembrandt.com/recipes](https://www.dembrandt.com/recipes)** — ready-to-run workflows. Copy a command, paste a prompt, get a result. Covers competitor benchmarking, WCAG audits, CI/CD drift detection, Figma token push, and agentic design system builds. Filterable by role.
74
-
75
- ## What to expect from extraction?
32
+ ## What you get
76
33
 
77
34
  - Colors (semantic, palette, CSS variables, gradients)
78
35
  - Typography (fonts, sizes, weights, sources, font file URLs)
@@ -84,399 +41,90 @@ Load extractions, track token drift, and compare snapshots. **[dembrandt.com/app
84
41
  - Breakpoints
85
42
  - Icons & frameworks
86
43
 
87
- ## Usage
88
-
89
- ```bash
90
- dembrandt <url> # Basic extraction (terminal display only)
91
- dembrandt dembrandt.com --json-only # Output raw JSON to terminal (no formatted display, no file save)
92
- dembrandt dembrandt.com --save-output # Save JSON to output/dembrandt.com/YYYY-MM-DDTHH-MM-SS.json
93
- dembrandt dembrandt.com --dtcg # Export in W3C Design Tokens (DTCG) format (auto-saves as .tokens.json)
94
- dembrandt dembrandt.com --color-format=oklch # Notation for displayed colors: hex|rgb|lch|oklch|source (default: hex)
95
- dembrandt dembrandt.com --dark-mode # Extract colors from dark mode variant
96
- dembrandt dembrandt.com --mobile # Use mobile viewport (390x844) for responsive analysis
97
- dembrandt dembrandt.com --slow # 3x longer timeouts (24s hydration) for JavaScript-heavy sites
98
- dembrandt dembrandt.com --brand-guide # Generate a brand guide PDF
99
- dembrandt dembrandt.com --design-md # Generate a DESIGN.md file for AI agents
100
- dembrandt dembrandt.com --tailwind # Write a Tailwind v4 @theme CSS file (observed values only)
101
- dembrandt dembrandt.com /pricing /docs # Extract specific paths and merge results into one output
102
- dembrandt dembrandt.com --crawl 5 # Analyze 5 pages (homepage + 4 discovered pages), merges results
103
- dembrandt dembrandt.com --sitemap # Discover pages from sitemap.xml instead of DOM links
104
- dembrandt dembrandt.com --crawl 10 --sitemap # Combine: up to 10 pages discovered via sitemap
105
- dembrandt dembrandt.com --no-sandbox # Disable Chromium sandbox (required for Docker/CI)
106
- dembrandt dembrandt.com --key dmb_··· # Push snapshot to your Dembrandt account; auto-scored against the previous snapshot for that domain
107
- # DEMBRANDT_API_URL env var overrides the upload endpoint (default: https://www.dembrandt.com)
108
- dembrandt dembrandt.com --browser=firefox # Use Firefox instead of Chromium (better for Cloudflare bypass)
109
- dembrandt dembrandt.com --wcag # WCAG 2.1 contrast analysis — real DOM pairs, AA/AAA grades
110
- dembrandt dembrandt.com --stealth # Opt-in anti-detection: navigator spoofing + human mouse simulation (use only when authorized)
111
- dembrandt dembrandt.com --locale fi-FI --timezone Europe/Helsinki # Browser fingerprint: locale and timezone
112
- dembrandt dembrandt.com --user-agent "Mozilla/5.0 ..." # Custom user agent string
113
- dembrandt dembrandt.com --accept-language "fi,en;q=0.9" # Custom Accept-Language header
114
- dembrandt dembrandt.com --screen-size 2560x1440 # Physical screen resolution to report
115
- ```
116
-
117
- Default: formatted terminal display only. Use `--save-output` to persist results as JSON files. Browser automatically retries in visible mode if headless extraction fails.
118
-
119
- `--color-format` is presentational and covers terminal output only: the palette, borders and every component section print the notation you pick. `source` shows a declared token as it was authored. The JSON payload is unaffected — it carries hex, rgb, lch and oklch for every color regardless — so `--json-only`, `--save-output`, `--dtcg`, `--design-md`, `--html` and `--brand-guide` are untouched, and drift comparisons stay stable across notations. The CLI warns when you combine it with one of those.
120
-
121
- All flags combine unless noted otherwise: see [docs/FLAGS.md](docs/FLAGS.md) for the flag compatibility tables (interactions, ignored combinations, multi-page propagation).
122
-
123
- ### Multi-Page Extraction
124
-
125
- 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.
126
-
127
- ```bash
128
- # Analyze homepage + 4 auto-discovered pages (default: 5 total)
129
- dembrandt dembrandt.com --crawl 5
130
-
131
- # Use sitemap.xml for page discovery instead of DOM link scraping
132
- dembrandt dembrandt.com --sitemap
133
-
134
- # Combine both: up to 10 pages from sitemap
135
- dembrandt dembrandt.com --crawl 10 --sitemap
136
- ```
137
-
138
- **Page discovery** works two ways:
139
- - **DOM links** (default): Reads navigation, header, and footer links from the homepage, prioritizing key pages like /pricing, /about, /features
140
- - **Sitemap** (`--sitemap`): Parses sitemap.xml (checks robots.txt first), follows sitemapindex references, and scores URLs by importance
141
-
142
- Pages are fetched sequentially with polite delays. Failed pages are skipped without aborting the run.
143
-
144
- ### Browser Selection
145
-
146
- By default, dembrandt uses Chromium. If you encounter bot detection or timeouts (especially on sites behind Cloudflare), try Firefox which is often more successful at bypassing these protections:
147
-
148
- ```bash
149
- # One-time: install-browser defaults to chromium, so fetch firefox explicitly
150
- dembrandt install-browser firefox
151
-
152
- # Use Firefox instead of Chromium
153
- dembrandt dembrandt.com --browser=firefox
154
-
155
- # Combine with other flags
156
- dembrandt dembrandt.com --browser=firefox --save-output --dtcg
157
- ```
158
-
159
- **When to use Firefox:**
160
- - Sites behind Cloudflare or other bot detection systems
161
- - Timeout issues on heavily protected sites
162
- - WSL environments where headless Chromium may struggle
163
-
164
- **Installation:**
165
- Browsers are installed on demand, not by `npm install` (dembrandt depends on the lean `playwright-core`, which carries no browser binaries). Fetch the engine you need, matched to the installed `playwright-core`:
166
-
167
- ```bash
168
- dembrandt install-browser # chromium (default)
169
- dembrandt install-browser firefox # a specific engine
170
- ```
171
-
172
- This resolves the browser revision from the `playwright-core` dembrandt actually drives, so the two cannot drift apart. Prefer it over calling Playwright directly: a bare `npx playwright install` fetches whatever version the registry serves, and a mismatch fails with `Executable doesn't exist`.
173
-
174
- On Linux and in CI, system libraries are installed separately:
175
-
176
- ```bash
177
- npx playwright@$(node -p "require('playwright-core/package.json').version") install --with-deps chromium
178
- ```
179
-
180
- ### Connect to an existing browser (CDP)
181
-
182
- Skip the bundled browser entirely and drive an already-running Chromium over the DevTools Protocol. Useful in CI or containers where a browser is already up, and it needs no local browser download at all:
183
-
184
- ```bash
185
- BROWSER_CDP_ENDPOINT=http://localhost:9222 dembrandt dembrandt.com --browser chromium
186
- ```
187
-
188
- CDP is supported only with `--browser chromium`.
189
-
190
- ### W3C Design Tokens (DTCG) Format
191
-
192
- Use `--dtcg` to export in the standardized [W3C Design Tokens Community Group](https://www.designtokens.org/) format:
193
-
194
- ```bash
195
- dembrandt dembrandt.com --dtcg
196
- # Saves to: output/dembrandt.com/TIMESTAMP.tokens.json
197
- ```
198
-
199
- The DTCG format is an industry-standard JSON schema that can be consumed by design tools and token transformation libraries like [Style Dictionary](https://styledictionary.com).
200
-
201
- ### DESIGN.md
202
-
203
- 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. The export follows Google's DESIGN.md draft format: YAML design tokens in front matter plus ordered Markdown guidance sections.
204
-
205
- ```bash
206
- dembrandt dembrandt.com --design-md
207
- # Saves to: output/dembrandt.com/DESIGN.md
208
- ```
209
-
210
- DESIGN.md reports only what Dembrandt observed on the source site. Exact values (colors, typography, spacing, radii, shadows) live in the YAML front matter when available, and the Markdown body adds human-readable context. Sections with no extracted evidence are omitted rather than filled with invented defaults. For example, the elevation section is dropped when the site uses no box-shadow tokens.
211
-
212
- ### Tailwind theme
213
-
214
- Use `--tailwind` to write a Tailwind v4 `@theme` block you can drop into a new project's CSS entry point.
215
-
216
- ```bash
217
- dembrandt dembrandt.com --tailwind
218
- # Saves to: output/dembrandt.com/theme.css
219
-
220
- dembrandt dembrandt.com --tailwind src/app.css # or write it straight into a project
221
- ```
222
-
223
- ```css
224
- @import "tailwindcss";
225
-
226
- @theme {
227
- --color-primary: #ea580c;
228
- --text-display: 96px;
229
- --text-display--line-height: 1;
230
- --spacing: 8px;
231
- --radius-lg: 8px;
232
- --breakpoint-md: 700px;
233
- }
234
- ```
235
-
236
- The file contains **only values observed on the page**. No 50–950 shade ramps, no interpolated scale steps, no derived hover or on-color variants: an invented shade is indistinguishable from a measured one once it is in the file, and this export is meant as the starting point you extend by hand. Colors keep their semantic role name (`--color-primary`), or the author's own custom property name where the page declares one; the rest are numbered `--color-brand-N`. Tailwind's defaults still apply to everything not listed, so the block extends the theme rather than replacing it.
44
+ Playwright renders the page, dembrandt reads computed styles from the DOM, analyzes color usage and confidence, groups similar typography, detects spacing patterns, and returns design tokens.
237
45
 
238
- Spacing is emitted as v4's `--spacing` multiplier when the page has a recognizable base-N rhythm, and as named steps otherwise. Values with a usage count (spacing, radii) are selected by how often they appear, not by size, so sub-pixel one-offs stay out of the scale.
239
-
240
- v4 only. The output is plain CSS custom properties, so nothing you install depends on or pins Tailwind; a v3 `tailwind.config.js` emitter would be a second serialization of the same data and is not written until someone needs it. Tailwind is a devDependency here purely so the test suite can run the real compiler over the emitted theme and assert that every token produces the utility it claims to, which is the only check that catches a namespace spelled plausibly but wrongly.
241
-
242
- A scheduled `Tailwind Watch` workflow runs weekly. It checks for a new major, which is the event that can invalidate the theme namespaces used here, and separately recompiles the emitted theme against the latest published Tailwind so a minor that changes what a namespace means cannot pass unnoticed behind the pinned devDependency. Either signal hands the update to an agent, which verifies each namespace against the current docs and opens a PR; it falls back to opening an issue if no agent credential is configured.
243
-
244
- ### WCAG Contrast Analysis
245
-
246
- Use `--wcag` to check accessibility contrast ratios across the page. Unlike palette-based checkers, dembrandt walks the actual DOM and finds what color is rendered on top of what background — per element.
247
-
248
- ```bash
249
- dembrandt dembrandt.com --wcag
250
- ```
251
-
252
- Returns every text/background pair with contrast ratio and WCAG 2.1 grade (AA, AA-Large, AAA, or fail), sorted by how often each pair appears. Results are shown in terminal and included in JSON output as `wcag`.
253
-
254
- Also captures **interactive state contrast**: dembrandt simulates hover, focus, and disabled states on buttons, links, and inputs and checks contrast on each state. State pairs are tagged `[hover]`, `[focus]`, or `[disabled]` in output so you can catch contrast failures that only appear on interaction.
255
-
256
- ### Motion Tokens
257
-
258
- Motion tokens are extracted automatically on every run — no flag needed. Dembrandt analyzes CSS transitions and animations across the page and returns a structured motion profile.
259
-
260
- ```bash
261
- dembrandt dembrandt.com
262
- ```
263
-
264
- Returns:
265
- - **Duration scale**: all unique animation durations found on the page
266
- - **Easing curves**: named easing types (ease-out, spring, custom cubic-bezier) with usage counts
267
- - **Per-context profiles**: motion behavior by component type (button, nav, card, modal, hero)
268
- - **Hover interaction deltas**: which properties animate on hover (transform, opacity, background, color) and the pattern (scale-up, fade-in, color-shift, slide-y)
269
-
270
- Motion data is included in JSON output as `motion` and printed in terminal under a dedicated Motion section.
271
-
272
- ### ML-powered brand color detection (experimental)
46
+ ## Common flags
273
47
 
274
48
  ```bash
275
- dembrandt dembrandt.com --ai
276
- # ML primary #533afd (score 0.93 · 68% acc)
49
+ dembrandt dembrandt.com --save-output # Save JSON to output/dembrandt.com/TIMESTAMP.json
50
+ dembrandt dembrandt.com --dtcg # W3C Design Tokens (DTCG) export, for Style Dictionary or Tokens Studio
51
+ dembrandt dembrandt.com --design-md # DESIGN.md for AI agents
52
+ dembrandt dembrandt.com --tailwind # Tailwind v4 @theme CSS, observed values only
53
+ dembrandt dembrandt.com --wcag # WCAG 2.1 contrast, real DOM pairs with AA/AAA grades
54
+ dembrandt dembrandt.com --crawl 10 # Merge 10 pages into one output, cross-page confidence boosting
55
+ dembrandt dembrandt.com --slow # 3x timeouts for JavaScript-heavy sites
277
56
  ```
278
57
 
279
- Replaces the heuristic with a trained model more accurate (68% vs 32%). Requires the optional `onnxruntime-node` dep (`npm install onnxruntime-node`). Without the flag nothing changes.
58
+ Default is formatted terminal output only. Full flag reference in **[docs/usage.md](docs/usage.md)**: mobile and dark mode, browser selection and CDP, brand guide PDF, motion tokens, fingerprint options.
280
59
 
281
- ### Brand Guide PDF
60
+ ## Catch design drift in CI
282
61
 
283
- Use `--brand-guide` to generate a printable PDF summarizing the extracted design system: colors, typography, components, and logo on a single document.
284
-
285
- ```bash
286
- dembrandt dembrandt.com --brand-guide
287
- # Saves to: output/dembrandt.com/TIMESTAMP.brand-guide.pdf
288
- ```
289
-
290
- ## Continuous integration
291
-
292
- ### GitHub Action
293
-
294
- The official action wraps extract → compare → gate into one step: it installs a matching Chromium, runs a pinned CLI version, fails the job on drift, and renders the drifted tokens as inline annotations on the PR.
62
+ Extract a preview deployment, compare against a committed baseline, fail the job when tokens moved:
295
63
 
296
64
  ```yaml
297
- - uses: dembrandt/dembrandt@v0.28.0
65
+ - uses: dembrandt/dembrandt@v0.29.0
298
66
  with:
299
67
  url: https://preview.example.com
300
68
  baseline: .dembrandt/baseline.json
301
69
  ```
302
70
 
303
- Gating a Vercel preview needs no extra wiring trigger on the deployment event and pass its URL:
304
-
305
- ```yaml
306
- on:
307
- deployment_status:
308
-
309
- jobs:
310
- drift:
311
- if: github.event.deployment_status.state == 'success'
312
- runs-on: ubuntu-latest
313
- steps:
314
- - uses: actions/checkout@v4
315
- - uses: dembrandt/dembrandt@v0.28.0
316
- with:
317
- url: ${{ github.event.deployment_status.environment_url }}
318
- baseline: .dembrandt/baseline.json
319
- key: ${{ secrets.DEMBRANDT_KEY }}
320
- ```
321
-
322
- | Input | Required | Description |
323
- |---|---|---|
324
- | `url` | yes | URL to extract — typically the PR's preview deployment |
325
- | `baseline` | no | Committed baseline JSON path, or an App baseline id. Omit to extract without gating |
326
- | `key` | no | API key for cloud snapshot sync ([dembrandt.com/app/api-keys](https://www.dembrandt.com/app/api-keys)) |
327
- | `args` | no | Extra CLI flags, e.g. `--wcag` or `--crawl 3` |
328
-
329
- Outputs: `report` (path to the drift/extraction JSON) and `score` (drift score, empty without a baseline). The action pins the CLI version per release, so a version-tagged gate never changes behavior under you. For a fully hand-rolled workflow (per-page PR comment, preview vs production, report artifact), see [`examples/drift-gate.yml`](examples/drift-gate.yml).
330
-
331
- ### Running the CLI directly
332
-
333
- Dembrandt drives a real browser, so the browser revision must match `playwright-core`.
334
-
335
- If you are not using the Playwright container image, install the browser revision that matches `playwright-core`:
336
-
337
- ```bash
338
- # matches the bundled playwright-core automatically
339
- dembrandt install-browser
340
- # on a bare Linux runner, add the system libraries too
341
- npx playwright@$(node -p "require('playwright-core/package.json').version") install --with-deps chromium
342
- ```
343
-
344
- A mismatched version fails with "Executable doesn't exist". The container image avoids this entirely — just match its tag (`v1.60.0`) to the `playwright-core` version.
345
-
346
- ### Drift gate
347
-
348
- Compare an extraction against a committed baseline and fail the job on drift:
349
-
350
- ```bash
351
- # capture a baseline once (same environment you will check against)
352
- dembrandt https://app.example.com --json-only > baseline.json
353
-
354
- # in CI — exits non-zero on drift; writes a report artifact
355
- dembrandt https://app.example.com --compare baseline.json --html report.html
356
- ```
357
-
358
- When the change is intended, accept it as the new baseline — `--approve` overwrites the local baseline file and passes instead of failing:
359
-
360
- ```bash
361
- dembrandt https://app.example.com --compare baseline.json --approve
362
- ```
363
-
364
- Add `--json-only` to a `--compare` run to get the drift report as machine-readable JSON under a `drift` key — `score`, `status`, `summary`, and per-token `changes[]` (each with `category`, `kind`, `before`, `after`, `delta`). A CI gate can render exactly which tokens moved (e.g. in a PR comment) from this instead of parsing the HTML report:
365
-
366
- ```bash
367
- dembrandt https://app.example.com --compare baseline.json --json-only
368
- ```
369
-
370
- **Any CI.** The gate is platform-neutral — it is just the exit code plus the drift JSON, so it drops into any runner:
71
+ The action annotates the PR with the drifted tokens. On any other runner the gate is just an exit code plus JSON: `dembrandt URL --compare baseline.json --json-only` exits 1 on drift and prints per-token `changes[]`. See **[docs/ci.md](docs/ci.md)** for the Action inputs, the platform-neutral gate, and the exit code table.
371
72
 
372
- ```bash
373
- dembrandt "$PREVIEW/checkout" --compare base.json --json-only > drift.json
374
- # exit 1 = drift. Read drift.json (.drift.changes) and surface it however your
375
- # platform does: a GitLab MR note, an Azure DevOps PR thread, a Jenkins status,
376
- # a Slack message, or an auto-filed Jira/Linear ticket.
377
- ```
378
-
379
- A ready-to-use **GitHub Actions** workflow (preview vs production, per-page PR comment with the exact tokens that changed, run summary, report artifact, host-auth bypass) is in [`examples/drift-gate.yml`](examples/drift-gate.yml) as one full reference. The result-surfacing step (annotations, PR comment) is the only platform-specific part; the extract → compare → branch-on-exit-code core is identical on GitLab CI, Jenkins, and Azure DevOps.
380
-
381
- ### Exit codes
382
-
383
- A pipeline can branch on the exit code; "design drifted" and "extraction broke" are distinct:
73
+ ## Recipes
384
74
 
385
- | Code | Meaning |
386
- |---|---|
387
- | `0` | Success, or stable (no drift) under `--compare` |
388
- | `1` | Drift detected (`--compare`) |
389
- | `2` | Extraction failure (`EXTRACTION_FAILED`, `BROWSER_UNAVAILABLE`) |
390
- | `67` | Navigation/connection timeout (`NAVIGATION_TIMEOUT`) — retryable, try `--slow` |
75
+ Copy a command, paste a prompt, get a result. Competitor benchmarking, WCAG audits, Figma token push, agentic design system builds. Filterable by role at **[dembrandt.com/recipes](https://www.dembrandt.com/recipes)**, with the basics in [docs/recipes.md](docs/recipes.md).
391
76
 
392
- With `--json-only`, a failure also prints a machine-readable `{ "error": { "code", "message" } }` to stdout.
77
+ ## AI Agent Integration (MCP)
393
78
 
394
- ## Recipes
79
+ Use Dembrandt as a tool in Claude Code, Cursor, Windsurf, or any MCP-compatible client. Ask your agent to "extract the color palette from dembrandt.com" and it calls Dembrandt automatically.
395
80
 
396
- **Quick brand scan**
397
81
  ```bash
398
- dembrandt dembrandt.com
82
+ claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt dembrandt-mcp
399
83
  ```
400
84
 
401
- **Compare two sites**
402
- ```bash
403
- dembrandt dembrandt.com --save-output
404
- dembrandt braintree.com --save-output
405
- # Compare output/dembrandt.com and output/braintree.com side by side
406
- ```
85
+ Or add to your project's `.mcp.json`:
407
86
 
408
- **Multi-page audit** — get a fuller picture across the whole site
409
- ```bash
410
- dembrandt dembrandt.com --crawl 10 --sitemap --save-output
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "dembrandt": {
91
+ "command": "npx",
92
+ "args": ["-y", "--package", "dembrandt", "dembrandt-mcp"]
93
+ }
94
+ }
95
+ }
411
96
  ```
412
97
 
413
- **Spot-check a value** verify a specific token fast
414
- ```bash
415
- dembrandt dembrandt.com --json-only | grep -i "border-radius"
416
- ```
98
+ Available tools include `get_design_tokens`, `get_color_palette`, `get_typography`, `get_component_styles`, `get_surfaces`, `get_spacing`, and `get_brand_identity`, plus pure analysis tools (`compute_drift`, `get_findings`, `export_dtcg`, `generate_design_md`, `render_report`) and job-control tools.
417
99
 
418
- **Export for Tailwind** get spacing and color values into your config
419
- ```bash
420
- dembrandt dembrandt.com --dtcg --save-output
421
- # Use the .tokens.json with Style Dictionary to generate tailwind.config.js
422
- ```
100
+ Extraction tools accept `slow`, `mobile`, `darkMode`, `wcag`, `cookie` and `header` (for authenticated pages), `userAgent`, and `noSandbox` (Docker and most CI containers). Set `pages` above 1 to crawl and merge several pages, which produces a markedly stronger token set than one page; `paths` names them explicitly and `sitemap` discovers them from sitemap.xml.
423
101
 
424
- **Export for Tokens Studio / Figma**
425
- ```bash
426
- dembrandt dembrandt.com --dtcg --save-output
427
- # Import the .tokens.json directly into Tokens Studio
428
- ```
102
+ Extraction returns a `job_id`. Poll it with `get_job_status`, then hand that same id to the pure tools instead of passing the extraction back as an argument:
429
103
 
430
- **Generate DESIGN.md for your AI agent**
431
- ```bash
432
- dembrandt dembrandt.com --design-md
433
- # Point your agent at the output DESIGN.md
434
104
  ```
435
-
436
- **Accessibility audit** — check contrast on any live URL
437
- ```bash
438
- dembrandt dembrandt.com --wcag
105
+ get_design_tokens(url: "example.com", pages: 5) -> job_id
106
+ get_job_status(job_id) -> tokens
107
+ get_findings(job_id) -> contrast and consistency issues
108
+ export_dtcg(job_id) -> W3C design tokens
439
109
  ```
440
110
 
441
- **Regression baseline** snapshot now, catch drift later
442
- ```bash
443
- dembrandt myapp.com --save-output --dtcg
444
- # Store output as baseline, re-run after deploys and diff
445
- ```
111
+ Pair with **[dembrandt-skills](https://github.com/dembrandt/dembrandt-skills)** to give your agent UX intelligence on top of extracted tokens: hierarchy, accessibility, interaction states, and a full 6-stage design pipeline orchestrator.
446
112
 
447
- **CI / headless environments**
448
113
  ```bash
449
- dembrandt myapp.com --no-sandbox --save-output
114
+ npx skills add dembrandt/dembrandt-skills
450
115
  ```
451
116
 
452
- ## Use Cases
453
-
454
- - Design system documentation
455
- - Multi-site design consolidation
456
- - Internal design audits on your own properties
457
- - Learning how design tokens map to real CSS
458
-
459
- ## How It Works
460
-
461
- Uses Playwright to render the page, reads computed styles from the DOM, analyzes color usage and confidence, groups similar typography, detects spacing patterns, and returns design tokens.
462
-
463
- ### Extraction Process
464
-
465
- 1. Browser Launch - Launches browser (Chromium by default, Firefox optional) with stealth configuration
466
- 2. Anti-Detection - Injects scripts to bypass bot detection
467
- 3. Navigation - Navigates to target URL with retry logic
468
- 4. Hydration - Waits for SPAs to fully load (8s initial + 4s stabilization)
469
- 5. Content Validation - Verifies page content is substantial (>500 chars)
470
- 6. Parallel Extraction - Runs all extractors concurrently for speed
471
- 7. Analysis - Analyzes computed styles, DOM structure, and CSS variables
472
- 8. Scoring - Assigns confidence scores based on context and usage
117
+ ## Dembrandt App (Beta)
473
118
 
474
- ### Color Confidence
119
+ Load extractions, track token drift, and compare snapshots. **[dembrandt.com/app](https://www.dembrandt.com/app)**
475
120
 
476
- - High: Logo, primary interactive elements
477
- - Medium: Secondary interactive elements, icons, navigation
478
- - Low: Generic UI components (filtered from display)
479
- - Only shows high and medium confidence colors in terminal. Full palette in JSON.
121
+ * **Automatic drift tracking from CI.** Generate an API key at [dembrandt.com/app/api-keys](https://www.dembrandt.com/app/api-keys), then pass `--key` to the CLI. Every run uploads a snapshot to your account and scores it against the previous one for that domain. Wire into GitHub Actions or any CI runner and every deploy records itself.
122
+ * **Pin a baseline.** Mark any snapshot as your reference. Every subsequent extraction is automatically scored against it.
123
+ * **Visual diff.** Color swatches, before/after values, delta scores per category: colors, typography, spacing, radius, shadows.
124
+ * **Snapshot timeline.** Proportional timeline per domain, scrub across any date range from days to years.
125
+ * **Compare side by side.** Load multiple extractions into one view: two releases, two sites, or two surfaces.
126
+ * **Copy tokens.** Paste values straight into Copilot, Claude, or Cursor.
127
+ * **No login required for local use.** Data stays in the browser. Sign in with GitHub to enable cloud sync.
480
128
 
481
129
  ## Limitations
482
130
 
@@ -505,6 +153,13 @@ The CLI is MIT-licensed and free. Sponsorship funds the enforcement layer: a com
505
153
  <!-- Backer ($25+) and Lead sponsor ($500+) logos appear here. -->
506
154
  <!-- sponsors -->
507
155
 
156
+ ## Documentation
157
+
158
+ - [docs/usage.md](docs/usage.md): every flag, multi-page extraction, browser selection, CDP, DTCG, DESIGN.md, Tailwind theme, WCAG, motion, brand guide PDF
159
+ - [docs/ci.md](docs/ci.md): GitHub Action, drift gate, exit codes
160
+ - [docs/recipes.md](docs/recipes.md): copy-paste workflows
161
+ - [docs/FLAGS.md](docs/FLAGS.md): flag interactions, ignored combinations, multi-page propagation
162
+
508
163
  ## Contributing
509
164
 
510
165
  Bugs, weird sites, pull requests. All welcome.
package/dist/index.js CHANGED
@@ -74,6 +74,9 @@ program
74
74
  .option("--screenshot <path>", "Save a viewport screenshot of the page (not full-page)")
75
75
  // Internal, undocumented flag. Hidden from --help; not part of the product surface.
76
76
  .addOption(new Option("--teach").hideHelp())
77
+ // Hidden until the role set is validated: an undocumented opt-in flag has no
78
+ // consumers, so roles can still be cut without a breaking contract change.
79
+ .addOption(new Option("--voice").hideHelp())
77
80
  .option("--ai", "Use ML model to predict brand primary color (experimental)")
78
81
  .option("--wcag", "Analyze WCAG contrast ratios between palette colors")
79
82
  .option("--crawl [n]", "Auto-discover and extract up to N pages via DOM links (default: 5); combine with --sitemap to use sitemap discovery instead", (v) => {
@@ -203,6 +206,7 @@ program
203
206
  screenshotPath: opts.screenshot,
204
207
  discoverLinks: isAutoCrawl ? crawlN - 1 : null,
205
208
  wcag: opts.wcag,
209
+ voice: opts.voice,
206
210
  includeRawColors: opts.rawColors,
207
211
  stealth: opts.stealth,
208
212
  cookie: opts.cookie,
@@ -269,6 +273,7 @@ program
269
273
  header: opts.header,
270
274
  screenSize: opts.screenSize,
271
275
  wcag: opts.wcag,
276
+ voice: opts.voice,
272
277
  includeRawColors: opts.rawColors,
273
278
  userAgent: opts.userAgent,
274
279
  locale: opts.locale,
@@ -355,6 +360,7 @@ program
355
360
  const outputData = opts.dtcg ? toDtcgTokens(result) : result;
356
361
  // Collect "saved to" notices and print them after the results below
357
362
  const savedNotices = [];
363
+ let syncFailed = false;
358
364
  // Save JSON output if --save-output or --dtcg is specified
359
365
  if (opts.saveOutput || opts.dtcg) {
360
366
  try {
@@ -530,36 +536,53 @@ program
530
536
  console.log(color.warning(`! Could not write HTML report: ${err.message}`));
531
537
  }
532
538
  }
533
- // Sync to cloud if --key / DEMBRANDT_KEY is set
539
+ // Sync to cloud if --key / DEMBRANDT_KEY is set. A failed sync exits
540
+ // SYNC_FAILED; a rate limit does not, and only warns.
534
541
  if (apiKey) {
542
+ const syncFailure = (reason, remedy) => {
543
+ console.error(color.error(`✖ Cloud sync failed: ${reason}`));
544
+ console.error(chalk.dim(` ${remedy}`));
545
+ console.error(chalk.dim(` The extraction itself succeeded. Drift tracking did NOT record this run.`));
546
+ syncFailed = true;
547
+ };
535
548
  try {
536
- const payload = JSON.stringify(result);
537
- const byteSize = Buffer.byteLength(payload, "utf8");
538
- const MAX_BYTES = 150_000;
539
- if (byteSize > MAX_BYTES) {
540
- console.error(color.warning(`! Extraction too large to sync (${byteSize} bytes > ${MAX_BYTES}). Skipping cloud upload.`));
549
+ // No size check here. The cap belongs to the API, which already
550
+ // enforces it and reports the exact limit; duplicating the number in
551
+ // the CLI meant two sources of truth in two repos that could drift
552
+ // apart silently, and raising the cap would have needed a CLI release.
553
+ const apiBase = process.env.DEMBRANDT_API_URL ?? "https://www.dembrandt.com";
554
+ const syncRes = await fetch(`${apiBase}/api/extractions`, {
555
+ method: "POST",
556
+ headers: {
557
+ "Authorization": `Bearer ${apiKey}`,
558
+ "Content-Type": "application/json",
559
+ },
560
+ body: JSON.stringify(result),
561
+ });
562
+ if (syncRes.ok) {
563
+ savedNotices.push(chalk.dim(`☁ Synced to your account (--key)`));
564
+ }
565
+ else if (syncRes.status === 429) {
566
+ // Hitting the rate limit is the system working, not a fault, and
567
+ // the recipes have always promised it does not fail a pipeline.
568
+ const err = await syncRes.json().catch(() => ({ error: syncRes.statusText }));
569
+ console.error(color.warning(`! Cloud sync skipped: ${err.error ?? "rate limit reached"}`));
570
+ console.error(chalk.dim(` This run was not recorded. Limits reset hourly.`));
541
571
  }
542
572
  else {
543
- const apiBase = process.env.DEMBRANDT_API_URL ?? "https://www.dembrandt.com";
544
- const syncRes = await fetch(`${apiBase}/api/extractions`, {
545
- method: "POST",
546
- headers: {
547
- "Authorization": `Bearer ${apiKey}`,
548
- "Content-Type": "application/json",
549
- },
550
- body: payload,
551
- });
552
- if (syncRes.ok) {
553
- savedNotices.push(chalk.dim(`☁ Synced to your account (--key)`));
554
- }
555
- else {
556
- const err = await syncRes.json().catch(() => ({ error: syncRes.statusText }));
557
- console.error(color.warning(`! Cloud sync failed: ${err.error ?? syncRes.statusText}`));
558
- }
573
+ const err = await syncRes.json().catch(() => ({ error: syncRes.statusText }));
574
+ const pageCount = result.pages?.length ?? 0;
575
+ syncFailure(`${err.error ?? syncRes.statusText} (HTTP ${syncRes.status})`, syncRes.status === 413
576
+ ? pageCount > 1
577
+ ? `Extract fewer pages (--crawl ${Math.max(1, Math.floor(pageCount / 2))} or lower), or drop --raw-colors.`
578
+ : `Drop --raw-colors, or open an issue if a single page really is this large.`
579
+ : syncRes.status === 401
580
+ ? `Check the API key at dembrandt.com/app/api-keys.`
581
+ : `Retry, or check dembrandt.com/app for service status.`);
559
582
  }
560
583
  }
561
584
  catch (syncErr) {
562
- console.error(color.warning(`! Cloud sync error: ${syncErr.message}`));
585
+ syncFailure(syncErr.message, `Check network access to the API host and retry.`);
563
586
  }
564
587
  }
565
588
  // Output to terminal
@@ -610,6 +633,9 @@ program
610
633
  console.log(chalk.dim("💡 --key <token> snapshots each run to your account and catches design drift over time: ") + chalk.dim(terminalLink("https://www.dembrandt.com/recipes/cloud-drift-ci", "dembrandt.com/recipes/cloud-drift-ci")));
611
634
  }
612
635
  }
636
+ // Drift (1) outranks this.
637
+ if (syncFailed && process.exitCode === undefined)
638
+ process.exitCode = EXIT.SYNC_FAILED;
613
639
  }
614
640
  catch (err) {
615
641
  const { code, exit } = classifyError(err);