create-design-system

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Create Design System from Website

从网站创建设计系统

Extract design tokens and brand assets from any website and save them as structured files for use by agents building UIs consistent with that brand.

从任意网站提取设计令牌和品牌资产,保存为结构化文件,供构建符合该品牌 UI 的 Agent 使用。

Prerequisites

前置条件

This skill needs Node.js, Playwright CLI, and a configured browser. Check that they are available. If any are missing, briefly inform the user, then follow the one-time setup guide and install them through the permitted package manager. Ask for approval only if the environment requires it.
The examples use
playwright-cli
with one named session. If the project uses a local CLI, use its runner in each command. Do not rely on a shell alias persisting between calls. Close the session with
playwright-cli -s=design close
when finished.

本 Skill 需要 Node.js、Playwright CLI 和已配置的浏览器。请检查它们是否可用。如有缺失,请简要告知用户,然后按照一次性设置指南通过允许的包管理器安装。仅在环境要求时才请求批准。
示例使用带有一个命名会话的
playwright-cli
。如果项目使用本地 CLI,请在每条命令中使用其运行器。不要依赖 shell 别名在调用之间持久存在。完成后使用
playwright-cli -s=design close
关闭会话。

Process

流程

Step 1 — Prepare output directories

步骤 1 — 准备输出目录

Create the required directories before starting:
On Bash, use
mkdir -p assets docs
. On Windows PowerShell, use
New-Item -ItemType Directory -Force assets, docs | Out-Null
.
开始前创建所需目录:
在 Bash 中,使用
mkdir -p assets docs
。在 Windows PowerShell 中,使用
New-Item -ItemType Directory -Force assets, docs | Out-Null
。

Step 2 — Navigate and dismiss overlays

步骤 2 — 导航并关闭覆盖层

⚠️ MUST DO — dismiss every cookie banner / modal / overlay covering the page BEFORE you screenshot or extract styles. This is the single most common failure of this skill. An overlay both skews computed styles and ruins the screenshot.
Open the URL with the browser configured during the one-time install:
text
playwright-cli -s=design open <URL>
If the page returns HTTP 403 or displays a blocked/challenge page, do not extract styles or screenshot that page. Wait briefly and retry navigation once or twice in the same session; a temporary block can clear. A configured alternative browser may be tried, but do not assume Chrome is installed or bypass a site's access controls. If the real page remains unavailable, stop and report the block instead of inventing a design system. This is a site response, not necessarily a Linux or Playwright setup problem.
Cookie/consent banners frequently load on a DELAY and are NOT present in the first snapshot. (Real example: a banner appeared only several seconds after load and silently ended up in the screenshot.) So:
  1. Wait for a delayed banner to mount. There is no
    wait
    command; use
    run-code
    :
    bash
    playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"
  2. Search for the banner instead of reading a whole snapshot.
    find
    returns only matching nodes with context, which costs a fraction of the tokens:
    bash
    playwright-cli -s=design find --regex "/cookie|consent|accept|akceptuj|zgadzam/i"
  3. Snapshot just the banner region by its ref to see the exact buttons:
    bash
    playwright-cli -s=design snapshot e945
  4. Click the accept button by ref. Common labels: "Accept", "Accept all", "Accept All Cookies", "OK", "I agree", "Allow all", and Polish: "Akceptuj", "Akceptuj wszystkie", "Zgadzam się", "Rozumiem".
    bash
    playwright-cli -s=design click e955
  5. Confirm it is gone before continuing — re-run
    find
    for the button label and expect no matches:
    bash
    playwright-cli -s=design find --regex "/Accept All Cookies/i"
  6. Also dismiss any tooltip / onboarding / newsletter popups that appear after consent.
Every command prints a snapshot link (
.playwright-cli/page-<timestamp>.yml
) rather than the snapshot itself. Read or grep that file only if
find
was not enough.
⚠️ 必须执行 — 在截图或提取样式之前,关闭所有覆盖页面的 cookie 横幅 / 模态框 / 覆盖层。 这是本 Skill 最常见的失败原因。覆盖层既会扭曲计算出的样式,也会破坏截图效果。
使用一次性安装时配置的浏览器打开 URL:
text
playwright-cli -s=design open <URL>
如果页面返回 HTTP 403 或显示被拦截/验证页面,不要提取该页面的样式或截图。稍等片刻,在同一会话中重试导航一到两次;临时拦截可能会解除。可以尝试已配置的备用浏览器,但不要假设 Chrome 已安装,也不要绕过网站的访问控制。如果真实页面仍然无法访问,请停止并报告拦截情况,而不是编造一个设计系统。这是网站的响应,不一定是 Linux 或 Playwright 的设置问题。
Cookie/同意横幅通常会延迟加载,在第一次快照中并不存在。(真实案例:一个横幅在加载数秒后才出现,悄无声息地出现在截图中。)因此:
  1. 等待延迟加载的横幅挂载。没有
    wait
    命令;请使用
    run-code
    :
    bash
    playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"
  2. 搜索横幅而不是读取整个快照。
    find
    仅返回带上下文的匹配节点,仅消耗极少的 token:
    bash
    playwright-cli -s=design find --regex "/cookie|consent|accept|akceptuj|zgadzam/i"
  3. 通过引用仅对横幅区域拍快照,查看确切的按钮:
    bash
    playwright-cli -s=design snapshot e945
  4. 通过引用点击接受按钮。常见标签:"Accept"、"Accept all"、"Accept All Cookies"、"OK"、"I agree"、"Allow all",以及波兰语:"Akceptuj"、"Akceptuj wszystkie"、"Zgadzam się"、"Rozumiem"。
    bash
    playwright-cli -s=design click e955
  5. 确认横幅已消失 后再继续 — 重新运行
    find
    搜索按钮标签,预期无匹配项:
    bash
    playwright-cli -s=design find --regex "/Accept All Cookies/i"
  6. 同时关闭同意后出现的任何工具提示 / 新手引导 / 邮件订阅弹窗。
每条命令都会打印一个快照链接(
.playwright-cli/page-<timestamp>.yml
)而不是快照本身。仅当
find
不够用时才读取或 grep 该文件。

Step 3 — Take the homepage screenshot

步骤 3 — 截取首页截图

🛑 STOP — re-check for overlays first (yes, again). Before this screenshot, take one more snapshot and confirm NO cookie banner, modal, or popup is covering content. Banners load on a delay, so even if the page looked clean in Step 2 one may have appeared since. If anything is covering the page, dismiss it (Step 2) and only then screenshot.
bash
playwright-cli -s=design screenshot --filename=assets/homepage.png --full-page
After saving, open
assets/homepage.png
with an available image viewer and visually verify
there is no banner/overlay strip across it. If there is, dismiss it and retake — do not move on with a polluted screenshot.
🛑 停 — 首先再次检查覆盖层(是的,再来一次)。 在截图前,再拍一次快照,确认没有 cookie 横幅、模态框或弹窗覆盖内容。横幅是延迟加载的,所以即使步骤 2 中页面看起来很干净,之后也可能出现。如果有任何东西覆盖页面,请先关闭它(步骤 2),然后再截图。
bash
playwright-cli -s=design screenshot --filename=assets/homepage.png --full-page
保存后,用可用的图像查看器打开
assets/homepage.png
并目视验证
图片上没有横幅/覆盖层条带。如果有,关闭后重新截图 — 不要带着有污染的截图继续。

Step 4 — Extract design tokens via eval (run in multiple small calls)

步骤 4 — 通过 eval 提取设计令牌(分多次小调用运行)

Each call below is a JavaScript arrow function passed to
playwright-cli -s=design eval
. Keep the three calls separate: a single mega-call is hard to debug and its output is hard to read.
Small results can print inline; anything sizeable should go to a file with
--filename
so it never enters the context window:
bash
playwright-cli -s=design eval "<the arrow function below>" --filename=tokens-a.json
--filename
writes an object as JSON; a returned string may be JSON-quoted. Inspect the file before parsing and parse a string only once.
下面的每个调用都是一个 JavaScript 箭头函数,传递给
playwright-cli -s=design eval
。请将三个调用分开:单个巨型调用难以调试,输出也难以阅读。
小结果可以内联打印;任何较大的结果都应该通过
--filename
写入文件,这样就不会进入上下文窗口:
bash
playwright-cli -s=design eval "<the arrow function below>" --filename=tokens-a.json
--filename
将对象写入为 JSON;返回的字符串可能会被 JSON 转义。解析前请检查文件,且字符串仅解析一次。

Call A — Core element styles

调用 A — 核心元素样式

js
() => {
  const getStyles = (el) => {
    if (!el) return null;
    const s = getComputedStyle(el);
    return {
      color: s.color,
      backgroundColor: s.backgroundColor,
      fontFamily: s.fontFamily,
      fontSize: s.fontSize,
      fontWeight: s.fontWeight,
      padding: s.padding,
      margin: s.margin,
      borderRadius: s.borderRadius,
      lineHeight: s.lineHeight,
    };
  };
  return {
    body: getStyles(document.body),
    header: getStyles(document.querySelector('header') || document.querySelector('[class*="header"]')),
    nav: getStyles(document.querySelector('nav')),
    h1: getStyles(document.querySelector('h1')),
    h2: getStyles(document.querySelector('h2')),
    link: getStyles(document.querySelector('a')),
  };
}
js
() => {
  const getStyles = (el) => {
    if (!el) return null;
    const s = getComputedStyle(el);
    return {
      color: s.color,
      backgroundColor: s.backgroundColor,
      fontFamily: s.fontFamily,
      fontSize: s.fontSize,
      fontWeight: s.fontWeight,
      padding: s.padding,
      margin: s.margin,
      borderRadius: s.borderRadius,
      lineHeight: s.lineHeight,
    };
  };
  return {
    body: getStyles(document.body),
    header: getStyles(document.querySelector('header') || document.querySelector('[class*="header"]')),
    nav: getStyles(document.querySelector('nav')),
    h1: getStyles(document.querySelector('h1')),
    h2: getStyles(document.querySelector('h2')),
    link: getStyles(document.querySelector('a')),
  };
}

Call B — Colors, radius, buttons

调用 B — 颜色、圆角、按钮

js
() => {
  const allEls = document.querySelectorAll('*');
  const bgColors = new Set(), textColors = new Set(), borderRadii = new Set();
  for (let i = 0; i < Math.min(allEls.length, 500); i++) {
    const s = getComputedStyle(allEls[i]);
    if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') bgColors.add(s.backgroundColor);
    textColors.add(s.color);
    if (s.borderRadius !== '0px') borderRadii.add(s.borderRadius);
  }

  // Buttons with non-transparent backgrounds
  const btnStyles = [];
  for (const btn of document.querySelectorAll('button, [class*="btn"], a[class*="button"]')) {
    const s = getComputedStyle(btn);
    if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') {
      btnStyles.push({
        text: btn.innerText?.trim().substring(0, 30),
        bg: s.backgroundColor, color: s.color,
        borderRadius: s.borderRadius, padding: s.padding,
        fontSize: s.fontSize, fontWeight: s.fontWeight,
        border: s.border
      });
      if (btnStyles.length >= 5) break;
    }
  }
  return {
    uniqueBackgrounds: [...bgColors],
    uniqueTextColors: [...textColors],
    uniqueBorderRadii: [...borderRadii],
    btnStyles
  };
}
js
() => {
  const allEls = document.querySelectorAll('*');
  const bgColors = new Set(), textColors = new Set(), borderRadii = new Set();
  for (let i = 0; i < Math.min(allEls.length, 500); i++) {
    const s = getComputedStyle(allEls[i]);
    if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') bgColors.add(s.backgroundColor);
    textColors.add(s.color);
    if (s.borderRadius !== '0px') borderRadii.add(s.borderRadius);
  }

  // Buttons with non-transparent backgrounds
  const btnStyles = [];
  for (const btn of document.querySelectorAll('button, [class*="btn"], a[class*="button"]')) {
    const s = getComputedStyle(btn);
    if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') {
      btnStyles.push({
        text: btn.innerText?.trim().substring(0, 30),
        bg: s.backgroundColor, color: s.color,
        borderRadius: s.borderRadius, padding: s.padding,
        fontSize: s.fontSize, fontWeight: s.fontWeight,
        border: s.border
      });
      if (btnStyles.length >= 5) break;
    }
  }
  return {
    uniqueBackgrounds: [...bgColors],
    uniqueTextColors: [...textColors],
    uniqueBorderRadii: [...borderRadii],
    btnStyles
  };
}

Call C — Logo, favicon, fonts

调用 C — Logo、favicon、字体

js
() => {
  // Logo: look in header .logo or [class*="logo"] — NOT country flag selectors
  const logoEl = document.querySelector('header .logo') ||
                 document.querySelector('[class*="header"] [class*="logo"]');
  const logoSVGEl = logoEl?.matches('svg') ? logoEl : logoEl?.querySelector('svg');
  const logoSVG = logoSVGEl?.outerHTML?.trim() ?? null;
  const logoImageCandidates = [...document.querySelectorAll('header img, [class*="header"] img')]
    .filter(img => img.getBoundingClientRect().width > 50)
    .slice(0, 8)
    .map(img => ({ alt: img.alt, src: img.currentSrc || img.src }));

  // Favicon href
  const favicon = document.querySelector('link[rel*="icon"]');

  // Font faces from CSS rules
  const fontFaces = [];
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule.type === CSSRule.FONT_FACE_RULE) {
          fontFaces.push(rule.cssText.substring(0, 300));
        }
      }
    } catch(e) {}
  }

  // Nav links
  const navLinks = [...document.querySelectorAll('nav a')].slice(0, 5).map(a => ({
    text: a.innerText?.trim().substring(0, 30),
    color: getComputedStyle(a).color,
    fontWeight: getComputedStyle(a).fontWeight,
    textTransform: getComputedStyle(a).textTransform,
  }));

  return {
    logoSVG,
    logoImageCandidates,
    faviconHref: favicon?.href ?? null,
    fontFaces: fontFaces.slice(0, 8),
    navLinks
  };
}
js
() => {
  // Logo: look in header .logo or [class*="logo"] — NOT country flag selectors
  const logoEl = document.querySelector('header .logo') ||
                 document.querySelector('[class*="header"] [class*="logo"]');
  const logoSVGEl = logoEl?.matches('svg') ? logoEl : logoEl?.querySelector('svg');
  const logoSVG = logoSVGEl?.outerHTML?.trim() ?? null;
  const logoImageCandidates = [...document.querySelectorAll('header img, [class*="header"] img')]
    .filter(img => img.getBoundingClientRect().width > 50)
    .slice(0, 8)
    .map(img => ({ alt: img.alt, src: img.currentSrc || img.src }));

  // Favicon href
  const favicon = document.querySelector('link[rel*="icon"]');

  // Font faces from CSS rules
  const fontFaces = [];
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule.type === CSSRule.FONT_FACE_RULE) {
          fontFaces.push(rule.cssText.substring(0, 300));
        }
      }
    } catch(e) {}
  }

  // Nav links
  const navLinks = [...document.querySelectorAll('nav a')].slice(0, 5).map(a => ({
    text: a.innerText?.trim().substring(0, 30),
    color: getComputedStyle(a).color,
    fontWeight: getComputedStyle(a).fontWeight,
    textTransform: getComputedStyle(a).textTransform,
  }));

  return {
    logoSVG,
    logoImageCandidates,
    faviconHref: favicon?.href ?? null,
    fontFaces: fontFaces.slice(0, 8),
    navLinks
  };
}

Step 5 — Save brand assets

步骤 5 — 保存品牌资产

Logo: Save
logoSVG
to
assets/logo.svg
only when it contains SVG markup. If it is absent, inspect
logoImageCandidates
and the visible header to identify the actual wordmark, not a flag or product image. When its image URL serves SVG and the page permits fetching it, fetch the SVG in-page, validate the returned text, then save it as
assets/logo.svg
. A
--filename
result containing SVG text may be a JSON-encoded string: inspect it and parse once, not twice.
If the selected wordmark is a PNG, JPEG, or WebP image, fetch its actual bytes in the page context. Return a single-entry object mapping
logo.png
,
logo.jpg
, or
logo.webp
to base64 (choose the extension from the response type or file signature), save that object with
--filename=logo-binary.json
, and decode it with the existing Node helper in Step 5b, using
logo-binary.json
as the input. Open the saved image to verify it. If the fetch is blocked, report that the logo could not be saved. Never put raster bytes or HTML in
logo.svg
.
Favicon + custom fonts (binary assets): Use the in-page fetch helper in Step 5b on every platform. Do not use
curl
,
wget
, or PowerShell
Invoke-WebRequest
to download these assets; shell downloaders can bypass the site's same-origin context and may be denied by the agent environment. Respect the site's asset rights.
To grab just the favicon you can run the helper (it also handles fonts — see Step 5b) and keep
favicon.*
from its output.
Logo: 仅当
logoSVG
包含 SVG 标记时,才将其保存到
assets/logo.svg
。如果不存在,请检查
logoImageCandidates
和可见的页头,识别出实际的字标,而不是国旗或产品图片。当图片 URL 提供 SVG 且页面允许获取时,在页面内获取 SVG,验证返回的文本,然后保存为
assets/logo.svg
。包含 SVG 文本的
--filename
结果可能是 JSON 编码的字符串:请检查它并仅解析一次,不要解析两次。
如果选中的字标是 PNG、JPEG 或 WebP 图片,请在页面上下文中获取其实际字节。返回一个单条目对象,将
logo.png
、
logo.jpg
或
logo.webp
映射到 base64(根据响应类型或文件签名选择扩展名),使用
--filename=logo-binary.json
保存该对象,然后在步骤 5b 中使用现有的 Node 辅助工具解码,以
logo-binary.json
作为输入。打开保存的图片进行验证。如果获取被阻止,请报告无法保存 logo。切勿将栅格字节或 HTML 放入
logo.svg
中。
Favicon + 自定义字体(二进制资产): 所有平台都使用步骤 5b中的页面内获取辅助工具。不要使用
curl
、
wget
或 PowerShell 的
Invoke-WebRequest
下载这些资产;shell 下载工具可能会绕过网站的同源策略,并且可能被 Agent 环境拒绝。请尊重网站的资产权利。
如果只想获取 favicon,可以运行辅助工具(它也处理字体 — 见步骤 5b),并从输出中保留
favicon.*
。

Step 5b — Download custom/self-hosted fonts (only if the site provides them)

步骤 5b — 下载自定义/自托管字体(仅当网站提供时)

When to do this: ONLY when the site serves its own custom fonts via self-hosted
@font-face
rules (font files on the site's origin, e.g.
…/themes/site/fonts/Brand-Regular.woff2
). Skip it when the site only uses:
  • generic system fonts (
    Arial
    ,
    -apple-system
    ,
    Segoe UI
    , …), or
  • a third-party CDN you can't fetch same-origin (e.g.
    fonts.gstatic.com
    , Adobe Typekit) — these are CORS-blocked and not "fonts provided by the website" to redistribute.
If the
fontFaces
you collected in Step 4 / Call C point at real font files on the site's domain, download them:
  1. Fetch all binary assets in-page (fonts + favicon), writing base64 to a file so it never floods context. Use the command for the folder containing this skill. Run project-folder commands from the project root. If the skill lives elsewhere (for example
    .claude/skills/create-design-system
    in the project or
    $HOME/.claude/skills/create-design-system
    in the user folder), locate this
    SKILL.md
    and use its adjacent
    scripts/
    files.
    Bash, script in the project skills folder:
    bash
    playwright-cli -s=design eval "$(cat '.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js')" --filename=binary-assets.json
    Bash, script in the user skills folder:
    bash
    playwright-cli -s=design eval "$(cat "$HOME/.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js")" --filename=binary-assets.json
    Windows PowerShell, script in the project skills folder:
    powershell
    playwright-cli -s=design eval (Get-Content -Raw '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js') --filename=binary-assets.json
    Windows PowerShell, script in the user skills folder:
    powershell
    playwright-cli -s=design eval (Get-Content -Raw (Join-Path $HOME '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js')) --filename=binary-assets.json
    The script returns an object mapping asset paths to base64 data.
  2. Decode to real files with the Node helper in the foreground. Use the matching script location:
    Bash, script in the project skills folder:
    bash
    node '.agents/skills/create-design-system/scripts/decode-binary-assets.cjs' binary-assets.json assets
    Bash, script in the user skills folder:
    bash
    node "$HOME/.agents/skills/create-design-system/scripts/decode-binary-assets.cjs" binary-assets.json assets
    Windows PowerShell, script in the project skills folder:
    powershell
    node '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs' binary-assets.json assets
    Windows PowerShell, script in the user skills folder:
    powershell
    node (Join-Path $HOME '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs') binary-assets.json assets
    It writes
    assets/fonts/<family>/*
    and
    assets/favicon.*
    in the current project, regardless of where the helper script is stored, then prints how many files it wrote.
  3. Verify and clean up: Inspect
    assets/fonts/
    and its subdirectories. Font files should have nonzero, plausible sizes, and no unexpected files should appear at the top level. Delete the temporary
    binary-assets.json
    when done.
🧨 Learn from this incident — read before writing your own decode loop. If the in-page code returns a string (e.g.
JSON.stringify(obj)
), the saved file can end up double-encoded (a JSON string of JSON). A naive
JSON.parse(file)
then yields a string, and
Object.entries(string)
iterates one entry per character — a previous run wrote ~600,000 zero-byte files named
0
,
1
,
2
,… into
assets/fonts/
. The provided helpers prevent this two ways: (a)
fetch-binary-assets.browser.js
returns an object, not a stringified string; (b)
decode-binary-assets.cjs
re-parses if it still sees a string AND refuses to write if there are >200 entries, numeric keys, or values too short to be a font. Always prefer the helpers. If you must hand-roll, replicate those guards, and never run the decoder in the background.
📜 Tell the user about asset rights (REQUIRED). Custom fonts and logos may be licensed. In your final response to the user, identify the downloaded fonts and logos and ask them to verify usage rights before using or redistributing those assets. Do not assume fonts, logos, or other brand assets are free to reuse or redistribute.
何时执行: 仅当网站通过自托管的
@font-face
规则提供其自有自定义字体时(字体文件位于网站的源站,例如
…/themes/site/fonts/Brand-Regular.woff2
)。如果网站仅使用以下内容则跳过:
  • 通用系统字体(
    Arial
    、
    -apple-system
    、
    Segoe UI
    等),或
  • 无法同源获取的第三方 CDN(例如
    fonts.gstatic.com
    、Adobe Typekit)— 这些会被 CORS 阻止,不属于“网站提供的可再分发字体”。
如果你在步骤 4 / 调用 C 中收集的
fontFaces
指向网站域名上的真实字体文件,请下载它们:
  1. 在页面内获取所有二进制资产(字体 + favicon),将 base64 写入文件,避免充斥上下文。使用对应于包含本 Skill 的文件夹的命令。从项目根目录运行项目文件夹命令。如果 Skill 位于其他位置(例如项目中的
    .claude/skills/create-design-system
    或用户文件夹中的
    $HOME/.claude/skills/create-design-system
    ),请找到该
    SKILL.md
    并使用其相邻的
    scripts/
    文件。
    Bash,项目技能文件夹中的脚本:
    bash
    playwright-cli -s=design eval "$(cat '.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js')" --filename=binary-assets.json
    Bash,用户技能文件夹中的脚本:
    bash
    playwright-cli -s=design eval "$(cat "$HOME/.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js")" --filename=binary-assets.json
    Windows PowerShell,项目技能文件夹中的脚本:
    powershell
    playwright-cli -s=design eval (Get-Content -Raw '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js') --filename=binary-assets.json
    Windows PowerShell,用户技能文件夹中的脚本:
    powershell
    playwright-cli -s=design eval (Get-Content -Raw (Join-Path $HOME '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js')) --filename=binary-assets.json
    该脚本返回一个将资产路径映射到 base64 数据的对象。
  2. 使用 Node 辅助工具在前台解码为真实文件。使用匹配的脚本位置:
    Bash,项目技能文件夹中的脚本:
    bash
    node '.agents/skills/create-design-system/scripts/decode-binary-assets.cjs' binary-assets.json assets
    Bash,用户技能文件夹中的脚本:
    bash
    node "$HOME/.agents/skills/create-design-system/scripts/decode-binary-assets.cjs" binary-assets.json assets
    Windows PowerShell,项目技能文件夹中的脚本:
    powershell
    node '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs' binary-assets.json assets
    Windows PowerShell,用户技能文件夹中的脚本:
    powershell
    node (Join-Path $HOME '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs') binary-assets.json assets
    无论辅助脚本存储在哪里,它都会在当前项目中写入
    assets/fonts/<family>/*
    和
    assets/favicon.*
    ,然后打印写入的文件数量。
  3. 验证并清理: 检查
    assets/fonts/
    及其子目录。字体文件应具有非零的合理大小,顶层不应出现意外文件。完成后删除临时的
    binary-assets.json
    。
🧨 从该事件中吸取教训 — 在编写自己的解码循环前请阅读。 如果页面内代码返回一个字符串(例如
JSON.stringify(obj)
),保存的文件最终可能会双重编码(JSON 的 JSON 字符串)。简单的
JSON.parse(file)
会返回一个字符串,而
Object.entries(string)
会每个字符迭代一个条目 — 之前的一次运行向
assets/fonts/
中写入了约 60 万个零字节文件,文件名分别为
0
、
1
、
2
……。提供的辅助工具通过两种方式防止这种情况:(a)
fetch-binary-assets.browser.js
返回一个对象,而不是字符串化的字符串;(b)
decode-binary-assets.cjs
如果仍然看到字符串会重新解析,并且如果条目数 >200、存在数字键或值太短而不是字体,则拒绝写入。始终优先使用辅助工具。如果必须手动编写,请复制这些防护措施,并且永远不要在后台运行解码器。
📜 告知用户资产权利(必需)。 自定义字体和 logo 可能受许可保护。在你给用户的最终回复中,说明下载的字体和 logo,并要求他们在使用或再分发这些资产之前验证使用权利。不要假设字体、logo 或其他品牌资产可以自由重用或再分发。

Step 6 — Build design-tokens.json

步骤 6 — 构建 design-tokens.json

Map the extracted values into a structured JSON. Follow this schema:
After dismissing consent, its elements may remain hidden in the DOM. Ignore hidden or zero-size elements when choosing colors and components; verify candidate buttons, links, and headings against the visible page instead of trusting the first selector match.
json
{
  "colors": {
    "brand": { "primary": "#...", "accent": "#...", "error": "#...", "success": "#..." },
    "background": { "default": "#fff", "light": "#...", "overlay": "rgba(...)" },
    "text": { "primary": "#...", "secondary": "#...", "muted": "#...", "onDark": "#fff" }
  },
  "typography": {
    "fontFamily": { "primary": "\"Font Name\", Arial, sans-serif" },
    "fontWeights": { "regular": 400, "medium": 500, "semibold": 600, "bold": 700 },
    "fontSize": { "sm": "12px", "base": "14px", "md": "16px", "lg": "20px", "xl": "24px" },
    "lineHeight": { "tight": "1.2", "base": "1.4", "relaxed": "1.6" }
  },
  "spacing": {
    "1": "4px", "2": "8px", "3": "12px", "4": "16px",
    "5": "20px", "6": "24px", "7": "28px", "8": "32px"
  },
  "borderRadius": {
    "none": "0px", "sm": "4px", "md": "8px", "lg": "16px",
    "full": "999px", "circle": "50%"
  },
  "components": {
    "button": { "primary": { "backgroundColor": "#...", "color": "#...", "padding": "...", "borderRadius": "..." } },
    "header": { "backgroundColor": "#...", "padding": "...", "fontSize": "..." },
    "nav": { "color": "#...", "fontSize": "...", "fontWeight": "..." }
  }
}
Save as
assets/design-tokens.json
.
将提取的值映射为结构化的 JSON。遵循此模式:
关闭同意横幅后,其元素可能仍隐藏在 DOM 中。选择颜色和组件时请忽略隐藏或零尺寸的元素;请根据可见页面验证候选按钮、链接和标题,而不是信任第一个选择器匹配项。
json
{
  "colors": {
    "brand": { "primary": "#...", "accent": "#...", "error": "#...", "success": "#..." },
    "background": { "default": "#fff", "light": "#...", "overlay": "rgba(...)" },
    "text": { "primary": "#...", "secondary": "#...", "muted": "#...", "onDark": "#fff" }
  },
  "typography": {
    "fontFamily": { "primary": "\"Font Name\", Arial, sans-serif" },
    "fontWeights": { "regular": 400, "medium": 500, "semibold": 600, "bold": 700 },
    "fontSize": { "sm": "12px", "base": "14px", "md": "16px", "lg": "20px", "xl": "24px" },
    "lineHeight": { "tight": "1.2", "base": "1.4", "relaxed": "1.6" }
  },
  "spacing": {
    "1": "4px", "2": "8px", "3": "12px", "4": "16px",
    "5": "20px", "6": "24px", "7": "28px", "8": "32px"
  },
  "borderRadius": {
    "none": "0px", "sm": "4px", "md": "8px", "lg": "16px",
    "full": "999px", "circle": "50%"
  },
  "components": {
    "button": { "primary": { "backgroundColor": "#...", "color": "#...", "padding": "...", "borderRadius": "..." } },
    "header": { "backgroundColor": "#...", "padding": "...", "fontSize": "..." },
    "nav": { "color": "#...", "fontSize": "...", "fontWeight": "..." }
  }
}
保存为
assets/design-tokens.json
。

Step 7 — Write docs/design-guidelines.md

步骤 7 — 编写 docs/design-guidelines.md

The guidelines document must include:
  1. Assets table — links to all saved files relative to
    docs/
  2. Colors section — token name, hex, usage
  3. Typography section — font family (with @font-face details), weight scale, size scale. If custom fonts were downloaded (Step 5b), list each downloaded file and its repo path under
    assets/fonts/…
    so agents know the fonts exist locally and can be used in the app — and add a one-line note that their license must be verified before use.
  4. Spacing section — base unit and common values
  5. Border radius section — all radii with usage context
  6. Components section — header, nav, button, inputs, promo bar etc.
  7. Logo usage — how/where to use the saved logo, with an inverted variant on dark backgrounds if one exists
  8. Visual style summary — 3–5 sentences describing the brand personality
Save as
docs/design-guidelines.md
.
指南文档必须包含:
  1. 资产表格 — 所有保存文件相对于
    docs/
    的链接
  2. 颜色部分 — 令牌名称、十六进制值、用途
  3. 排版部分 — 字体系列(含 @font-face 详情)、字重刻度、字号刻度。如果下载了自定义字体(步骤 5b),请列出每个下载的文件及其在
    assets/fonts/…
    下的仓库路径
    ,以便 Agent 知道字体存在于本地并可在应用中使用 — 并添加一行说明,指出使用前必须验证其许可证。
  4. 间距部分 — 基本单位和常用值
  5. 圆角部分 — 所有圆角及使用场景
  6. 组件部分 — 页头、导航、按钮、输入框、推广栏等
  7. Logo 使用规范 — 如何/在何处使用保存的 logo,如果存在深色背景下的反色变体也应说明
  8. 视觉风格总结 — 3-5 句话描述品牌个性
保存为
docs/design-guidelines.md
。

Step 8 — Verify and hand off

步骤 8 — 验证并交付

Delete temporary files (e.g.
binary-assets.json
, snapshot dumps), confirm no junk slipped into
assets/
, and review the generated files with the user. In a Git repository, check
git status --short
so unrelated changes are not included. Commit only when the user requested a commit and the repository's contribution process permits it.

删除临时文件(例如
binary-assets.json
、快照转储),确认
assets/
中没有混入垃圾文件,并与用户一起审查生成的文件。在 Git 仓库中,请运行
git status --short
检查,确保不包含无关更改。仅当用户要求提交且仓库的贡献流程允许时才提交。

Common Pitfalls and Fixes

常见陷阱与修复方法

IssueFix
eval
result floods the context
Add
--filename=<file>.json
and read the file back, rather than printing it
Logo selector misses the wordmarkInspect visible header images; the wordmark may be an
<img>
pointing to an SVG or raster file rather than inline SVG
Site opens to a 403 or blocked pageRetry briefly in the same browser session; if access remains blocked, report it and do not extract the block page
No
wait
command in the CLI
Use
playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"
eval
fails with
SyntaxError: Unexpected token ';'
The argument must be an expression. A script file ending in
};
breaks it — the helper script deliberately ends in
}
with no semicolon
Decoder dies with
require is not defined in ES module scope
In projects using ES modules, run the provided
.cjs
helper rather than changing its extension
Snapshot too large to readUse
playwright-cli -s=design find "<text>"
or
playwright-cli -s=design find --regex "/pattern/i"
— it returns only matching nodes with context
Direct asset downloads are deniedFetch same-origin assets in-page with
scripts/fetch-binary-assets.browser.js
, then decode with
scripts/decode-binary-assets.cjs
Cookie banner missed — ends up in the screenshotIt loads on a delay and is absent from the first snapshot. Wait 3s →
find
the banner → dismiss →
find
again and expect no matches, THEN screenshot. Also visually inspect the saved PNG to confirm it is clean.
Cookie consent dialog never dismissedAlways check for and click "Accept all" / "OK" / "Akceptuj wszystkie" before extracting styles — overlays produce incorrect computed styles
Decode created hundreds of thousands of 0-byte filesThe
filename
sink double-encodes a string return. Return an object from the in-page script; the decoder must re-parse a string AND refuse >200 entries / numeric keys. Use the provided helpers; never
run_in_background
the decoder.
Verifying only the subfolders you expectedAfter downloading fonts, inspect the parent
assets/fonts/
directory too. Junk files can land at the top level and a subfolder-only check hides them.
Computed style returns
rgba(0, 0, 0, 0)
for bg
This means transparent — skip these when building color palettes
Fonts show as
Arial
instead of brand font
Check
@font-face
rules in stylesheets — the brand font may load as a custom alias (e.g.,
Euclid
= Euclid Circular B)

问题修复方法
eval
结果充斥上下文
添加
--filename=<file>.json
并回读文件,而不是打印它
Logo 选择器未命中字标检查可见的页头图片;字标可能是指向 SVG 或栅格文件的
<img>
,而不是内联 SVG
网站打开显示 403 或被拦截页面在同一浏览器会话中稍等重试;如果访问仍然被阻止,请报告,不要提取拦截页面
CLI 中没有
wait
命令
使用
playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"
eval
失败并报错
SyntaxError: Unexpected token ';'
参数必须是一个表达式。以
};
结尾的脚本文件会导致失败 — 辅助脚本特意以
}
结尾,不带分号
解码器报错
require is not defined in ES module scope
而失败
在使用 ES 模块的项目中,请运行提供的
.cjs
辅助工具,而不是更改其扩展名
快照太大无法读取使用
playwright-cli -s=design find "<text>"
或
playwright-cli -s=design find --regex "/pattern/i"
— 它仅返回带上下文的匹配节点
直接下载资产被拒绝使用
scripts/fetch-binary-assets.browser.js
在页面内获取同源资产,然后用
scripts/decode-binary-assets.cjs
解码
遗漏 cookie 横幅 — 出现在截图中它是延迟加载的,在第一次快照中不存在。等待 3 秒 →
find
横幅 → 关闭 → 再次
find
预期无匹配项,然后再截图。同时目视检查保存的 PNG 确认其干净。
Cookie 同意对话框从未关闭提取样式前始终检查并点击 "Accept all" / "OK" / "Akceptuj wszystkie" — 覆盖层会导致计算出的样式不正确
解码生成了数十万个零字节文件
filename
输出端会对字符串返回值进行双重编码。请从页面内脚本返回一个对象;解码器必须对字符串重新解析,并且拒绝 >200 个条目 / 数字键。使用提供的辅助工具;永远不要在后台运行解码器。
仅验证你预期的子文件夹下载字体后,还要检查父级
assets/fonts/
目录。垃圾文件可能出现在顶层,仅检查子文件夹会隐藏它们。
计算出的背景样式返回
rgba(0, 0, 0, 0)
这表示透明 — 构建调色板时请跳过这些
字体显示为
Arial
而不是品牌字体
检查样式表中的
@font-face
规则 — 品牌字体可能以自定义别名加载(例如
Euclid
= Euclid Circular B)

Output Files

输出文件

All files are saved to the project root:
assets/
  homepage.png           # Full-page screenshot (no overlays!)
  logo.<source-format>   # Brand wordmark, e.g. logo.svg or logo.png (only when obtainable)
  favicon.ico            # Favicon binary
  design-tokens.json     # Structured design tokens
  fonts/                 # Self-hosted custom fonts (only if the site provides them)
    <family>/            #   e.g. brygada-1918/, libre-franklin/
      *.woff2 / *.ttf    #   downloaded font files
docs/
  design-guidelines.md   # Human-readable design system reference
所有文件都保存到项目根目录:
assets/
  homepage.png           # Full-page screenshot (no overlays!)
  logo.<source-format>   # Brand wordmark, e.g. logo.svg or logo.png (only when obtainable)
  favicon.ico            # Favicon binary
  design-tokens.json     # Structured design tokens
  fonts/                 # Self-hosted custom fonts (only if the site provides them)
    <family>/            #   e.g. brygada-1918/, libre-franklin/
      *.woff2 / *.ttf    #   downloaded font files
docs/
  design-guidelines.md   # Human-readable design system reference