关于 penpot-build-from-code
penpot-build-from-code 是将 JSX、HTML、Vue/Svelte 模板及样式代码转为可编辑 Penpot 画面的设计技能,面向需要让界面代码遵循现有设计系统的设计与开发人员。它先盘点令牌和组件,再逐段构建并验证画面。
主要功能
- 01盘点设计令牌与组件
- 02按区块逐步构建画面
- 03将样式绑定到语义令牌
- 04优先复用组件实例
- 05导出画面并检查结构
- 06验证令牌覆盖与遗留值
适用场景
- 01把 React 页面代码重建为 Penpot 画面
- 02将 HTML 或 Vue 模板映射到现有设计系统
- 03检查新画面中的未绑定样式值
MCP 相关
该技能通过 Penpot MCP 工具读取令牌、组件和画面结构,并使用工具创建及修改画面。它还通过导出画面进行视觉检查。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ penpot/ penpot-ai-kit --skill penpot-build-from-code安装后在 Claude Code 中输入/penpot-build-from-code确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/01-discovery.md02-component-assembly.md03-token-binding.md04-incremental-transform.md05-anti-rationalization.md06-error-recovery.mdscripts/buildSection.jscreateScreenWrapper.jsinspectDesignSystem.jsvalidateScreen.js详细介绍
从代码构建 — 将应用代码转换为系统内 Penpot 屏幕
1. 标题 + 工作原理
此技能可将现有应用程序产物(JSX/TSX、HTML、Vue/Svelte 模板、CSS/Tailwind 或 styled-components 树)重建为可编辑、绑定到系统的 Penpot 屏幕。所有修改都通过 execute_code 完成;使用 export_shape 进行视觉验证;使用 penpotUtils.shapeStructure 读取结构(完整工具接口:shared/penpot-mcp-tool-reference.md)。核心做法是先探索(阶段 0 会枚举必须映射到的现有令牌和组件),然后逐步、逐节转换代码,将每个 CSS/样式值映射到语义令牌,将每个重复的 UI 部分映射到现有库组件——绝不硬编码十六进制颜色或网格外间距,也绝不把组件重新实现为原始方框。
2. 最重要的一条规则
绝不一次性生成整个屏幕,也绝不硬编码令牌已能表达的值。 每次调用只构建屏幕中的一个部分(每次调用进行一个逻辑上的 execute_code 步骤),并且在编写任何填充、间距、圆角或字体之前,先根据已发现的令牌集进行匹配。从代码得出的 #0066FF 不是填充色,而是设计系统中的一次查找。如果没有匹配的令牌,你就提出一个并停止;不能自行臆造。一次调用中转换整个页面,或将原始计算 CSS 粘贴到图形上,正是此技能要避免的失败模式。
3. Penpot MCP 工具参考
完整接口:shared/penpot-mcp-tool-reference.md。此技能主要使用以下调用:
- 探索(只读):
penpotUtils.tokenOverview()、penpot.library.local(组件、令牌)、penpotUtils.shapeStructure(shape, depth)、penpotUtils.findShape/findShapes。 - 屏幕框架:
penpot.createBoard()、board.addFlexLayout()、penpotUtils.addFlexLayout(container, dir)。 - 构建部分:
penpot.createRectangle/createText/createEllipse/createPath、component.instance()、container.appendChild(child)、child.layoutChild。 - 令牌绑定:
shape.applyToken(token, properties[])、token.resolvedValue、penpotUtils.findTokenByName。 - 验证:
export_shape(单独的工具调用)和一次shapeStructure读取。
4. 插件 API 要点
这些容易踩坑的地方,用一句话概括——完整说明见 shared/plugin-api-gotchas.md:
- #1 样式数组不可变 — 赋值一个全新的
fills/strokes数组;绝不要修改fills[0]。 - #2 应用令牌是异步的(约 100 ms) — 在一次
execute_code调用中应用,并在后续调用中验证resolvedValue。 - #3
resize()会强制将文本的growType设为'fixed'— 调整需要流式排布的文本大小后,将growType设为'auto-width'/'auto-height'。 - #4 Flex/grid 会覆盖子元素的 x/y — 通过添加顺序和间距/内边距/对齐/分布来定位子元素;仅在需要退出布局时设置
child.layoutChild.absolute = true。 - #5
width/height和parentX/parentY为只读 — 使用shape.resize(w, h)和penpotUtils.setParentXY(shape, x, y)。 - #6 修改实例内部结构前先分离实例 — 先调用
detach(),并说明这一操作。 - #7 必须添加,否则不会显示在画布上 — 使用
container.appendChild(child)(或penpot.currentPage.root)。 - #8 内边距令牌 — 内边距令牌在 2.17 及更高版本中生效;旧版实例中,
createScreenWrapper.js会回退为镜像数字,并记录账本例外。 - #9
switchVariant(pos, value)—pos是variants.properties中的索引(参见storage.run.ds.componentsByRole[role].axes),绝不是轴名称。 - #13b 精确匹配字体 —
penpot.fonts.all.find(f => f.name === "…");findByName会进行子字符串搜索。 - #15 在添加后设置
layoutChild— 仅当子元素已位于布局画板中时,才设置layoutChild.*;openPage是异步的。 - #16 在填充尺寸调整后处理
flipX— 每个使用填充尺寸的部分完成后都运行clearFlip扫描(shared/visual-effects.md)。 - #17 布局几何信息 — 读取几何信息前先执行
await board.waitForLayoutUpdate()(替代sleep-100ms的惯用写法)。 - 使用前,通过
penpot_api_info(type, member)核实任何不熟悉的签名。
5. 令牌感知型需求简报约定
在进行任何修改前,先按此约定重述请求。以资深设计工程师的身份行事,将设计系统视为唯一事实来源;只要存在令牌,就绝不硬编码值。
- 背景 — 这段代码来自哪个产品/界面,由谁使用,对应哪个页面/视图(例如“B2B 管理控制台的桌面端设置页面”)。
- 目标 — 单一且具体:“将
<SettingsPage>组件重建为可编辑的 Penpot 画板,并绑定现有的semantic令牌集,复用Button、InputField和Card组件。” - 输入 — 实际代码(组件文件、CSS/Tailwind 配置、设计令牌导出文件,如有)、目标 Penpot 页面、已发现的令牌集、已发现的组件库。
- 约束 — 禁止操作(不使用原始十六进制值、不使用偏离 4px 网格的间距、不重新发明组件、未经批准不得调用
detach());不可违反的规则(使用语义令牌而非原始令牌;优先使用现有组件而非新建图形);视口宽度。 - 验收标准 — 量化指标:≥95% 的颜色/间距/圆角/字体值绑定到令牌(由
validateScreen.js验证);标记出的孤立原始值为零;所有具有匹配库组件的重复 UI 部件都必须是实例,而非图形;所有间距均符合 4px 网格;图层名称具有语义(不能使用Rectangle 12)。
6. 强制工作流
视觉自查(强制): 在每个展示视觉成果的 ✋ 检查点之前, 运行
shared/visual-self-review.md中的导出 → 检查 → 修复循环——导出刚完成的单元, 根据检查清单自行检查图像,修复可见缺陷(最多迭代 2 次),并展示同一份导出结果,同时指出任何仍然存在的缺陷。
阶段 0 — 发现(只读)
**目标:**梳理现有 Penpot 设计系统,为代码中的值找到对应位置。不做任何修改。
- 调用一次
high_level_overview。 execute_code:运行scripts/inspectDesignSystem.js→ 列出令牌集、每个令牌(name、type、resolvedValue)以及每个库组件(name、id、变体轴)。缓存到storage.run.ds。execute_code:运行penpotUtils.tokenOverview(),确认当前启用的主题/令牌集。- 将输入代码解析为区段大纲(页眉、导航、主视觉、表单、卡片网格、页脚……)和样式清单(代码使用的每个不同颜色/间距/圆角/字体值)。
**退出标准:**你已准备好:(a) 按解析值索引的令牌映射;(b) 按用途索引的组件目录;(c) 区段大纲;(d) 样式清单,其中每个值都已匹配令牌,或标记为
UNMAPPED。 ✋ 检查点:展示令牌/组件覆盖情况和UNMAPPED列表。询问哪些UNMAPPED值应作为拟议的新令牌,哪些应作为已记录的例外接受。参见references/01-discovery.md。
阶段 1 — 屏幕容器
**目标:**创建带有正确弹性容器和 RUN_ID 台账条目的屏幕 Board。
execute_code:运行scripts/createScreenWrapper.js(按 Board 名称实现幂等)→ 创建一个尺寸与视口一致的 Board,调用addFlexLayout('column'),并将间距和内边距绑定到间距令牌(2.16.x 中,内边距需镜像并添加padding-mirrors-token例外)。 **退出标准:**存在一个空的、尺寸正确的弹性列布局 Board;其 id 已记入台账。 **✋ 检查点:**对空 Board 调用export_shape;在填充内容前确认尺寸/方向。
阶段 2 — 逐步构建区段(每个区段重复)
目标:根据代码片段,在系统中准确构建一个区段。参见 references/02-component-assembly.md、references/03-token-binding.md、references/04-incremental-transform.md。
execute_code:为该区段运行scripts/buildSection.js——对于有匹配目录组件的部分,优先使用component.instance();仅为真正定制的部分创建原始形状;通过applyToken将每个填充/间距/圆角/字体绑定到令牌。execute_code(单独调用,与 Agent 的回合间隔约 100 ms):读取token.resolvedValue/shapeStructure,确认绑定已应用。- 对该区段调用
export_shape。 **退出标准:**区段能够渲染,每个值都已绑定令牌或被批准为例外,组件均为实例。 ✋ 检查点:展示该区段;“看起来不错”仅表示批准当前区段。继续前先说出下一个区段的名称。
阶段 3 — 验证与报告
**目标:**证明屏幕符合系统规范。
execute_code:运行scripts/validateScreen.js→ 令牌覆盖率、孤立原始值列表、结构合理性检查(已设置弹性布局、没有自动命名图层、实例与形状的对比)。- 生成结构化报告(
AGENTS.md的第 12 节)。 **退出标准:**达到验收标准,或明确列出所有待审查的差距。 **✋ 检查点:**展示覆盖情况和剩余差距;请求签字确认。
7. 关键规则
- 始终先执行
high_level_overview;任何写入操作前都必须先完成 Phase 0 探索。 - 每次
execute_code修改调用只能处理一个区段。绝不在一次调用中构建多个区段。 - 每个颜色、间距、圆角、边框和字体值在应用前都必须解析为 token。如果存在匹配的 token,形状上就不能使用原始 hex/rgb/px 值。
- 所有间距、内边距和间隔都必须是 4px 的倍数(将代码值四舍五入到最近的网格 token;如有例外,须记录)。
- 优先使用语义层级的 token,而非基础 token,以确保主题切换正常(
tokens-schema.json)。 - 任何存在匹配库组件的 UI 部分都必须使用实例,不能手工构建形状组。
- 绝不自行发明 token。如果某个代码值没有匹配项,请提出建议(名称/值/层级),然后暂停并等待批准。
- Token 应用是异步的——必须在后续的
execute_code调用中验证resolvedValue,不能在同一次调用中验证。 - 未经明确批准,绝不对实例调用
detach();如有调用,须报告。 - 根据代码中的用途使用语义化 kebab-case 图层名称(
nav、header、card-container)——绝不能使用Rectangle 12。 - 每个阶段结束后都要写入
RUN_ID台账;确保每次创建操作均可幂等执行(按名称检查是否已存在)。
8. 领域架构
此技能生成屏幕树,并使用设计系统映射。
- 设计系统映射(Phase 0 输出,缓存在
storage.run.ds中):tokensByValue: { "#0066FF": "color.action.primary.bg", "16": "spacing.4", ... }— 反向索引,可快速将代码值查找为 token。tokensByName: { "color.action.primary.bg": {type, resolvedValue} }。componentsByRole: { button: {id, variants}, input: {...}, card: {...} }。
- 区段大纲(来自代码): 有序列表
[{ role, codeFragment, childrenRoles }]。 - 样式清单:
[{ codeValue, cssProp, matchedToken | "UNMAPPED" }]。 - 屏幕树(Penpot 输出):
Board(flex column) → section Boards(flex) → 组件实例 + 绑定形状。 - 台账(
RUN_ID):{ runId, phase, created:[{kind,name,id}], proposedTokens:[...], exceptions:[...], pendingReview:[...] }。
9. 模式与策略
默认模式为审查(Apply-with-review):构建每个区段,然后在检查点暂停,在继续下一个区段前提供 export_shape 和摘要。链接:shared/modes-and-policies.md。
- 安全集(可自动应用): 语义化图层重命名;将原始值替换为与某个 token 的解析值完全相等的 token(无损替换);添加插件数据/元数据。
- 始终需要审查: 创建屏幕 Board 和任何几何形状;实例化/重构组件;任何新 token 提案;
detach();任何非精确相等、需要判断的 token 匹配。
10. 状态管理
链接:shared/state-management.md。台账命名空间为 penpot-ai,以 RUN_ID 为键(例如 bfc-2026-06-05-a)。
storage.run.ds— Phase 0 中缓存的设计系统映射(token、组件)。setSharedPluginData('penpot-ai', '<RUN_ID>.phase', n)— 当前阶段。setSharedPluginData('penpot-ai', '<RUN_ID>.ledger', JSON)—{boardId, sectionsBuilt:[role], proposedTokens, exceptions}。- 恢复: 重新读取台账,重新运行
inspectDesignSystem.js以重新推导 DS 映射,再运行shapeStructure(board)查看哪些区段已存在,并从ledger.phase继续。幂等步骤可确保重新运行安全。
11. 用户检查点
| 阶段之后 | 展示的产物 | 要询问的问题 |
|---|---|---|
| 0 探索 | Token/组件覆盖率表;未映射的样式值 | "哪些未映射值应成为 token 提案,哪些应记录为例外?" |
| 1 外层框架 | 空 Board 的 export_shape + 尺寸 | "视口尺寸和列方向是否正确?" |
| 2 区段(逐个) | 区段的 export_shape + token 绑定摘要 | "批准此区段吗?(会明确指出下一个区段的名称)" |
| 3 验证 | 覆盖率、孤立值列表、结构合理性检查 | "批准,还是先处理列出的问题?" |
12. 命名约定
链接:shared/naming-conventions.md。领域细则:
- Screen Board:
screen-<view-name>(kebab 格式),例如screen-settings。 - Section Board:使用语义化 HTML 角色——
header、nav、main、section、footer,或 kebab 格式的角色名称,如card-grid、field-row。 - 组件实例保留目录中的组件名称。
- 提议的令牌遵循
tokens-schema.json中的点分层级;优先采用根据代码用法提炼、描述意图的语义名称(color.action.primary.bg,而不是color.blue.500)。
13. 反合理化对照表
| LLM 的借口 | 错误原因 | 可确定性执行并中止流程的对策 |
|---|---|---|
“CSS 写的是 #0066FF,我就直接把它设为填充色。” | 硬编码的十六进制值不属于设计系统,无法适配主题,也不符合治理要求。 | 在 storage.run.ds.tokensByValue 中查找。若匹配 → applyToken。若不匹配 → 提议一个令牌并停止,等待审批。绝不设置原始十六进制值。 |
“Tailwind 的 p-5 是 20px,设成 20 就差不多了。” | 直接使用字面值会绕过令牌比例尺,也可能不符合网格。 | 四舍五入到最接近的 4px 网格间距令牌,然后执行 applyToken。只有获得批准后,才能记录例外情况。 |
“有 Button 组件,不过用圆角矩形加文字画一个更快。” | 重新实现系统组件会破坏更新、变体和交接的一致性。 | 若存在 storage.run.ds.componentsByRole.button,则调用 component.instance() 并设置属性/变体。只有真正定制的部分才使用原始形状。 |
“我会用一次 execute_code 构建整页,省得多次调用。” | 一次性构建会隐藏失败,并产出未绑定、未经验证的结果。 | 每次调用构建一个区块;在之后的调用中验证令牌绑定;在继续下一区块前执行 export_shape 并创建检查点。 |
“这个代码值没有对应令牌;我自己添加 color.misc.x,这样就能继续了。” | 默默臆造令牌会污染系统,并绕过人工治理。 | 将其连同名称/值/层级添加到 ledger.proposedTokens,并在发现检查点停止。不得擅自创建。 |
| “令牌读取失败;我还是把值硬编码进去作为备用方案吧。” | 硬编码备用值正是此技能禁止的孤立值;回读是异步的。 | 在之后的 execute_code 调用中重新读取 resolvedValue。如果确实失败,应修复绑定——绝不能添加原始备用值。 |
14. 辅助代码片段
// Reverse-index lookup: code value -> token (Phase 2, inside buildSection)
const ds = storage.run.ds;
const tokenName = ds.tokensByValue[String(codeValue).toLowerCase()]; // e.g. "#0066ff" -> "color.action.primary.bg"
if (tokenName) {
const token = penpotUtils.findTokenByName(tokenName);
shape.applyToken(token, ["fill"]); // verify resolvedValue in a LATER call
} else {
ledger.proposedTokens.push({ from: codeValue, suggest: "propose a semantic token", tier: "semantic" });
}
return { applied: !!tokenName, codeValue };
// Prefer an existing component over a raw shape
const role = "button";
const cat = storage.run.ds.componentsByRole[role];
let node;
if (cat) {
const comp = penpot.library.local.components.find(c => c.id === cat.id);
node = comp.instance(); // instance, not a redrawn box
} else {
node = penpot.createRectangle(); // bespoke only
}
container.appendChild(node);
return { role, usedComponent: !!cat, id: node.id };
15. 参考资源
penpot_api_info("LibraryComponent")、penpot_api_info("Shape", "applyToken")、penpot_api_info("Board", "addFlexLayout")、penpot_api_info("Token", "resolvedValue")— 使用前先验证。shared/penpot-mcp-tool-reference.md(工具接口)、shared/plugin-api-gotchas.md(易踩陷阱)、shared/tokens-schema.json(令牌类型/层级)、shared/naming-conventions.md、shared/state-management.md。- 相关技能:
penpot-build-screen(基于需求简述的生成流程)、penpot-foundations(系统缺少可映射令牌时使用——其推断模式)、penpot-audit-accessibility(构建后的对比度检查)。
16. 支持文件
references/
| 文件 | 加载阶段 | 用途 |
|---|---|---|
references/01-discovery.md | 阶段 0 | 枚举令牌/组件;构建反向值→令牌索引和按角色分类的组件目录;将代码解析为章节大纲和样式清单。 |
references/02-component-assembly.md | 阶段 2 | 决定使用实例还是形状;实例化库组件、设置变体/属性;使用 flex 组装定制部分。 |
references/03-token-binding.md | 阶段 2 | 将代码样式映射到语义令牌;applyToken 属性名称;异步验证;提出新令牌。 |
references/04-incremental-transform.md | 阶段 2 | 逐节循环、排序、更新记录、恢复执行。 |
references/05-anti-rationalization.md | 所有阶段 | 扩展的应对措施,包含具体的代码转换陷阱。 |
references/06-error-recovery.md | 失败时 | 诊断绑定失败、孤立值、flex/x-y 冲突、分离失误。 |
scripts/
| 文件 | 阶段 | 用途 |
|---|---|---|
scripts/inspectDesignSystem.js | 0 | 枚举令牌集/令牌和库组件;构建值→令牌反向索引及按角色分类的组件目录;在 storage 中缓存。 |
scripts/createScreenWrapper.js | 1 | 幂等地创建屏幕 Board,设置 flex 列布局以及绑定令牌的内边距/间距。 |
scripts/buildSection.js | 2 | 根据代码片段构建一个章节:实例化组件、创建定制形状、绑定令牌。 |
scripts/validateScreen.js | 3 | 计算令牌覆盖率、列出孤立原始值、检查结构是否合理(flex、名称、实例)。 |
规范路径。 在原生安装中,shared/… 和 policies/… 在此软件包内解析(由安装程序随附);在 Claude Code 插件安装中,它们位于插件根目录 — ${CLAUDE_PLUGIN_ROOT}/shared/…,从本文件起向上两级目录。