mcpField
C
create-design-system

create-design-system

github.com/edukeyteam设计与 UI185 次安装2 天前更新
代码仓库

关于 create-design-system

此技能可从网站提取设计令牌与品牌素材,并整理成结构化文件,供构建品牌一致界面的 Agent 使用。它涵盖浏览器检查、截图和素材处理流程。

主要功能

  1. 01提取样式与设计令牌
  2. 02截取首页
  3. 03保存 Logo 与网站图标
  4. 04收集符合条件的自托管字体
  5. 05生成结构化设计文件

适用场景

  1. 01记录网站的视觉体系
  2. 02为 UI Agent 准备品牌参考
  3. 03保存网站 Logo 和图标
  4. 04收集网站提供的字体

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/edukeyteam/agent-toolbox --skill create-design-system

安装后在 Claude Code 中输入/create-design-system确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
setup.md
scripts/
decode-binary-assets.cjs
fetch-binary-assets.browser.js

详细介绍

从网站创建设计系统

从任意网站提取设计令牌和品牌素材,并将其保存为结构化文件,供构建符合该品牌风格的 UI 的 Agent 使用。


前提条件

此技能需要 Node.js、Playwright CLI 和已配置的浏览器。请检查这些组件是否可用。如果有缺失,请简要告知用户,然后按照一次性设置指南通过获准的包管理器安装。仅当环境要求时才请求批准。

示例使用 playwright-cli 和一个命名会话。如果项目使用本地 CLI,请在每条命令中使用其运行器。不要依赖 Shell 别名在多次调用之间持续有效。完成后,使用 playwright-cli -s=design close 关闭会话。


流程

步骤 1 — 准备输出目录

开始前创建所需目录:

在 Bash 中,使用 mkdir -p assets docs。在 Windows PowerShell 中,使用 New-Item -ItemType Directory -Force assets, docs | Out-Null。

步骤 2 — 导航并关闭浮层

⚠️ 必须执行 — 截图或提取样式前,先关闭遮挡页面的所有 Cookie 横幅、模态框和浮层。 这是此技能最常见的失败原因。浮层既会影响计算样式,也会破坏截图。

使用一次性安装期间配置的浏览器打开 URL:

playwright-cli -s=design open <URL>

如果页面返回 HTTP 403,或显示被拦截/验证页面,请勿从该页面提取样式或截图。稍等片刻后,在同一会话中重试导航一到两次;临时拦截可能会解除。可以尝试已配置的其他浏览器,但不要假设已安装 Chrome,也不要绕过网站的访问控制。如果真实页面仍无法访问,请停止并报告拦截情况,而不是臆造设计系统。这是网站的响应,不一定是 Linux 或 Playwright 的设置问题。

Cookie/同意横幅通常会延迟加载,首次快照中可能没有。(真实案例:横幅在页面加载几秒后才出现,结果悄然进入了截图。)因此:

  1. 等待延迟横幅加载。没有 wait 命令;请使用 run-code:
    playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"
    
  2. 搜索横幅,而不是读取整份快照。find 只返回含上下文的匹配节点,所需令牌数仅为完整快照的一小部分:
    playwright-cli -s=design find --regex "/cookie|consent|accept|akceptuj|zgadzam/i"
    
  3. 按引用定位横幅区域的快照,查看准确的按钮:
    playwright-cli -s=design snapshot e945
    
  4. 按引用点击接受按钮。常见标签包括:“Accept”、“Accept all”、“Accept All Cookies”、“OK”、“I agree”、“Allow all”,以及波兰语:“Akceptuj”、“Akceptuj wszystkie”、“Zgadzam się”、“Rozumiem”。
    playwright-cli -s=design click e955
    
  5. 继续前确认横幅已消失——重新按按钮标签运行 find,确认没有匹配项:
    playwright-cli -s=design find --regex "/Accept All Cookies/i"
    
  6. 还要关闭同意操作后出现的任何工具提示、入门引导或新闻简报弹窗。

每条命令都会打印快照链接(.playwright-cli/page-<timestamp>.yml),而不是快照本身。只有在 find 不够用时,才读取或 grep 该文件。

步骤 3 — 截取主页

🛑 暂停——再次检查浮层。 截图前,再截取一次快照并确认没有 Cookie 横幅、模态框或弹窗遮挡内容。横幅会延迟加载,因此即使步骤 2 中页面看起来正常,也可能在此后出现。如果有任何内容遮挡页面,请关闭它(步骤 2),然后再截图。

playwright-cli -s=design screenshot --filename=assets/homepage.png --full-page

保存后,使用可用的图像查看器打开 assets/homepage.png 并进行目视检查,确认图像上没有横幅/浮层条带。如果有,请关闭浮层并重新截图——不要带着被污染的截图继续操作。

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

下面的每次调用都是传递给 playwright-cli -s=design eval 的 JavaScript 箭头函数。请将三次调用分开:合并成一次超大调用难以调试,输出也难以阅读。

较小的结果可以直接打印;较大的结果应使用 --filename 保存到文件,避免进入上下文窗口:

playwright-cli -s=design eval "<the arrow function below>" --filename=tokens-a.json

--filename 会将对象写为 JSON;返回的字符串可能会以 JSON 字符串形式编码。解析前请先检查文件,字符串只解析一次。

调用 A — 核心元素样式
() => {
  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')),
  };
}
调用 B — 颜色、圆角、按钮
() => {
  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
  };
}
调用 C — Logo、favicon、字体
() => {
  // 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
  };
}
步骤 5 — 保存品牌素材

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.*。

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

何时执行:仅当网站通过自托管的 @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 写入文件,避免大量内容涌入上下文。使用与此技能所在文件夹对应的命令。从项目根目录运行项目文件夹中的命令。如果技能位于其他位置(例如项目中的 .claude/skills/create-design-system,或用户文件夹中的 $HOME/.claude/skills/create-design-system),请找到此 SKILL.md,并使用其同级的 scripts/ 文件。

    Bash,脚本位于项目技能文件夹中:

    playwright-cli -s=design eval "$(cat '.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js')" --filename=binary-assets.json
    

    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,脚本位于项目技能文件夹中:

    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,脚本位于用户技能文件夹中:

    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,脚本位于项目技能文件夹中:

    node '.agents/skills/create-design-system/scripts/decode-binary-assets.cjs' binary-assets.json assets
    

    Bash,脚本位于用户技能文件夹中:

    node "$HOME/.agents/skills/create-design-system/scripts/decode-binary-assets.cjs" binary-assets.json assets
    

    Windows PowerShell,脚本位于项目技能文件夹中:

    node '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs' binary-assets.json assets
    

    Windows 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/ 中写入了约 600,000 个大小为 0 字节的文件,文件名为 0、1、2……提供的辅助程序通过两种方式避免了这一问题:(a) fetch-binary-assets.browser.js 返回的是对象,而不是序列化后的字符串;(b) decode-binary-assets.cjs 如果发现结果仍是字符串,就会重新解析,且在条目数超过 200、键为数字或值短到不可能是字体时,拒绝写入。请始终优先使用这些辅助程序。如果必须自行编写,请复现这些防护措施,且绝不要在后台运行解码器。

📜 告知用户资产权利问题(必须执行)。 自定义字体和徽标可能受许可证约束。在给用户的最终回复中,指出下载的字体和徽标,并请用户在使用或再分发这些资产前核实其使用权。不要假设字体、徽标或其他品牌资产可以免费使用或再分发。

步骤 6 — 构建 design-tokens.json

将提取的值映射到结构化 JSON 中。遵循以下架构:

关闭同意提示后,其元素可能仍隐藏在 DOM 中。选择颜色和组件时,请忽略隐藏或尺寸为零的元素;应对照可见页面验证候选按钮、链接和标题,而不要轻信第一个选择器匹配结果。

{
  "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。

步骤 7 — 编写 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。

步骤 8 — 验证并交接

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


常见问题和解决方法

问题解决方法
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 同意对话框始终未关闭提取样式前,务必检查并点击“全部接受”/“确定”/“Akceptuj wszystkie”——遮罩层会导致计算后的样式不正确
解码后生成数十万个 0 字节文件filename 接收器会对字符串返回值进行二次编码。在页面内脚本中返回一个对象;解码器必须重新解析字符串,并拒绝超过 200 个条目或使用数字键的对象。请使用提供的辅助脚本;切勿对解码器使用 run_in_background。
只检查了预期中的子文件夹下载字体后,也要检查上级 assets/fonts/ 目录。无关文件可能出现在顶层,只检查子文件夹会漏掉这些文件。
计算样式返回 rgba(0, 0, 0, 0) 作为背景色这表示透明——构建颜色调色板时跳过这些值
字体显示为 Arial,而不是品牌字体检查样式表中的 @font-face 规则——品牌字体可能以自定义别名加载(例如,Euclid = Euclid Circular B)

输出文件

所有文件都保存到项目根目录:

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

评论

支持 Markdown