@codify-ai/mcp-client 1.0.17 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,8 +21,8 @@
21
21
  - 🔌 **一键安装**:使用 `npx` 无需手动下载
22
22
  - 📡 **MCP 协议**:完美集成 Cursor 等支持 MCP 的 IDE
23
23
  - 🔐 **安全认证**:支持 access_key 认证
24
- - 🛠️ **工具集成**:提供 `getCode` 和 `sendToCanvas` 工具
25
- - 📦 **资源访问**:通过 URI `codify://getCode/{content_id}` 访问代码
24
+ - 🛠️ **工具集成**:提供 `getCode` 和 `agent_create_page` 工具
25
+ - 📦 **资源访问**:通过 URI `Codify://getCode/{content_id}` 访问代码
26
26
 
27
27
  ## 架构说明
28
28
 
@@ -84,11 +84,11 @@ Figma Plugin (WebSocket)
84
84
 
85
85
  ### 命令行参数
86
86
 
87
- | 参数 | 说明 | 默认值 |
88
- | ----------------- | ----------------- | --------------------------- |
87
+ | 参数 | 说明 | 默认值 |
88
+ | ----------------- | ----------------- | ---------------------------- |
89
89
  | `--url=<URL>` | Codify 服务器地址 | `https://mcp.codify-api.com` |
90
- | `--help`, `-h` | 显示帮助信息 | - |
91
- | `--version`, `-v` | 显示版本号 | - |
90
+ | `--help`, `-h` | 显示帮助信息 | - |
91
+ | `--version`, `-v` | 显示版本号 | - |
92
92
 
93
93
  ### 环境变量
94
94
 
package/mcp-client.js CHANGED
@@ -8,84 +8,45 @@ import { z } from 'zod'
8
8
  import axios from 'axios'
9
9
  import fs from 'fs'
10
10
  import path from 'path'
11
- import os from 'os'
12
- import { fileURLToPath } from 'url'
13
11
 
14
- const __filename = fileURLToPath(import.meta.url)
15
- const __dirname = path.dirname(__filename)
16
-
17
- function installSkills() {
18
- const skillName = 'codify-gen'
19
- const sourceDir = path.join(__dirname, 'skills', skillName)
20
- if (!fs.existsSync(sourceDir)) return
12
+ function parseArgs() {
13
+ const args = process.argv.slice(2)
14
+ let serverUrl = process.env.CODIFY_SERVER_URL || 'http://localhost:8080'
15
+ for (let i = 0; i < args.length; i++) {
16
+ const arg = args[i]
17
+ if (arg === '--help' || arg === '-h') {
18
+ console.log(`
19
+ Codify MCP Client - 连接到远程 Codify MCP 服务器
21
20
 
22
- const home = os.homedir()
23
- const cwd = process.cwd()
21
+ 用法:
22
+ npx -y @codify-ai/mcp-client [选项]
24
23
 
25
- // 优先使用环境变量指定的路径
26
- const envDir = process.env.CODIFY_SKILLS_DIR
27
- ? path.join(process.env.CODIFY_SKILLS_DIR, skillName)
28
- : null
24
+ 选项:
25
+ --url=<URL> 服务器 URL (默认: https://mcp.codify-api.com)
26
+ --help, -h 显示帮助信息
27
+ --version, -v 显示版本号
29
28
 
30
- if (envDir) {
31
- const targets = [envDir]
32
- for (const targetDir of targets) {
33
- try {
34
- fs.mkdirSync(targetDir, { recursive: true })
35
- for (const file of fs.readdirSync(sourceDir)) {
36
- fs.copyFileSync(path.join(sourceDir, file), path.join(targetDir, file))
37
- }
38
- } catch { /* 静默失败 */ }
39
- }
40
- return
41
- }
29
+ 环境变量:
30
+ CODIFY_SERVER_URL 服务器 URL
31
+ CODIFY_ACCESS_KEY 访问密钥
42
32
 
43
- // 优先安装到当前项目(Cursor 启动 MCP 时 cwd 为项目根目录)
44
- const projectSkillsDir = path.join(cwd, '.cursor', 'skills')
45
- const projectTarget = path.join(projectSkillsDir, skillName)
46
- const hasProjectCursor = fs.existsSync(path.join(cwd, '.cursor'))
47
-
48
- // 自动探测用户级 IDE skills 目录
49
- const candidates = [
50
- path.join(home, '.cursor', 'skills', skillName), // Cursor
51
- path.join(home, '.windsurf', 'skills', skillName), // Windsurf
52
- path.join(home, '.codeium', 'windsurf', 'skills', skillName), // Codeium
53
- path.join(home, '.trae', 'skills', skillName), // Trae
54
- ]
55
- const userTargets = candidates.filter(p => fs.existsSync(path.dirname(path.dirname(p))))
56
-
57
- // 构建最终安装列表:项目级优先,用户级兜底
58
- const targets = []
59
- if (hasProjectCursor) targets.push(projectTarget)
60
- targets.push(...userTargets)
61
-
62
- // 若都没探测到,回退到 Cursor 用户级
63
- if (targets.length === 0) {
64
- targets.push(path.join(home, '.cursor', 'skills', skillName))
65
- }
33
+ 示例:
34
+ npx -y @codify-ai/mcp-client --url=https://mcp.codify-api.com
35
+ CODIFY_ACCESS_KEY=sk-xxx npx -y @codify-ai/mcp-client
66
36
 
67
- for (const targetDir of targets) {
68
- try {
69
- fs.mkdirSync(targetDir, { recursive: true })
70
- for (const file of fs.readdirSync(sourceDir)) {
71
- const src = path.join(sourceDir, file)
72
- const dest = path.join(targetDir, file)
73
- fs.copyFileSync(src, dest)
74
- }
75
- } catch {
76
- // 静默失败,不阻断 MCP 启动
37
+ Cursor 配置示例 (~/.cursor/mcp.json):
38
+ {
39
+ "mcpServers": {
40
+ "codify": {
41
+ "command": "npx",
42
+ "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],
43
+ "env": {
44
+ "CODIFY_ACCESS_KEY": "sk-your-access-key"
45
+ }
77
46
  }
78
47
  }
79
48
  }
80
-
81
- installSkills()
82
-
83
- function parseArgs() {
84
- const args = process.argv.slice(2)
85
- let serverUrl = process.env.CODIFY_SERVER_URL || 'http://localhost:8080'
86
- for (let i = 0; i < args.length; i++) {
87
- const arg = args[i]
88
- if (arg === '--help' || arg === '-h') {
49
+ `)
89
50
  process.exit(0)
90
51
  }
91
52
  if (arg === '--version' || arg === '-v') {
@@ -105,6 +66,220 @@ function parseArgs() {
105
66
  const { serverUrl: SERVER_URL } = parseArgs()
106
67
  const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY
107
68
 
69
+ // 系统提示词,用于生成Codify能够解析的代码结构。
70
+ const generationRules = `
71
+ # 📐 HTML + CSS 视觉创构与逆向转译协议 (Visual Generation & Reversal Protocol)
72
+
73
+ ## 🎯 角色与任务目标 (Dual-Core Persona)
74
+
75
+ 你是一个拥有双重核心的 **商业级 UI 创构与 Figma 逆向编译引擎**。你的大脑分为两个绝对隔离的模块,必须严格按顺序执行:
76
+
77
+ **核心一:顶级 UI/UX 视觉架构师(负责“创造美”)**
78
+ 你需要根据用户的需求,动用 Dribbble/Behance 级别的顶尖商业审美。运用本提示词中的《核心设计哲学》,推导最匹配的视觉维度、极其优雅的排版、舒适的留白以及极具转化率的色彩系统。你的目标是:**设计出让人一眼惊艳的界面方案。**
79
+
80
+ **核心二:严苛的 Figma 协议编译器(负责“格式化”)**
81
+ 一旦视觉方案定型,你必须将这个绝美的界面,**100% 严格地“降维、压缩、翻译”**成符合底层规范的代码。你不再是设计师,而是一个没有感情的机器,确保每一行代码都能被程序完美逆向解析为 Figma 图层。任何非标代码都会导致转换失败系统崩溃。
82
+
83
+ ## 📜 最终产出总纲 (Final Output Standards)
84
+
85
+ 你所有的工作产出,必须无条件满足以下 7 大黄金标准:
86
+
87
+ 1. **视觉标准**:Dribbble/Behance 级别的顶级商业 UI 审美。
88
+ 2. **代码标准**:仅返回包含在 \`<main>\` 根容器内的代码,全部使用 **Tailwind CSS Utility Classes**。
89
+ 3. **语法标准**:为了保证 1:1 还原 Figma 数值,**必须**使用 Tailwind 的 **Arbitrary Values (任意值)** 语法 (e.g., \`w-[320px]\`, \`bg-[#F5F5F5]\`),严禁使用依赖 Theme 的默认类名 (如 \`w-1/2\`, \`bg-red-500\`)。
90
+ 4. **结构标准**:完全符合下述的“图层原子化协议”。
91
+ 5. **命名规范**:每个标签必须包含 \`data-name="..."\`,使用语义化英文 (e.g., \`card-container\`, \`user-avatar\`)。
92
+ 6. **图片处理**:只需要返回 \`<img src="{{keyword}}" />\` 语义化内容即可,系统会根据\`{{keyword}}\`来搜索相关图片,如: \`<img src="{{Cyberpunk City}}" />\`。
93
+ 7. **图标系统**:使用 FontAwesome \`fas\`, \`far\` 系列图标 (\`<i class="fas fa-...">\`),必须在 class 中使用 \`text-[size]\` \`text-[#color]\` 定义。
94
+
95
+ ## 🔄 强制的执行生命周期 (Execution Lifecycle)
96
+
97
+ 为了保证美感与代码规范互不干扰,你必须严格按照以下两步输出:
98
+
99
+ **第一步:输出视觉构思与自检 (Design Planning)**
100
+ 在写任何代码之前,必须先使用 \`<design_plan>\` 标签输出你的设计策略,打好高质量草稿:
101
+
102
+ <design_plan>
103
+
104
+ 1. **风格定位**:选择的主辅视觉维度是什么?为什么?
105
+ 2. **色彩推导**:主色 (Hex)、强引导色、背景色的具体取值及语义理由。
106
+ 3. **结构拆解**:界面分为哪几个核心区块?主次容器的定宽与自适应比例是如何分配的?
107
+ 4. **编译红线自检**:弃用所有原生 input/button 标签,并绝对禁止使用 m- (Margin),所有间距将严格使用 Flex gap 或 p- 替代。
108
+
109
+ </design_plan>
110
+
111
+ **第二步:输出协议代码 (Protocol Output)**
112
+ 构思完成后,严格遵照红线标准输出代码,且**仅输出**包含在 \`<main>\` 根容器内的 HTML,包裹在 \`\`\`html 代码块中。
113
+
114
+ ## 🛑 红色警戒区 (Critical Constraints)
115
+
116
+ **以下规则享有最高优先级,违反任何一条均视为 SYSTEM FAILURE:**
117
+
118
+ ### 1. 🚫 绝对禁用的属性 (Blocklist)
119
+
120
+ - ❌ **严禁使用 Margin (Zero Tolerance)**:
121
+ - 无论任何情况,**绝对禁止**出现 \`m-[...]\`, \`my-[...]\`, \`mt-[...]\` 等类名。
122
+ - **替代方案 A** (均匀间距):在父容器 Flex 中使用 \`gap-[数值]px\`。
123
+ - **替代方案 B** (内边距):如果是元素离边框的距离,使用父容器的 \`p-[数值]px\`。
124
+ - **替代方案 C** (推挤布局):使用 \`justify-between\` 将首尾元素撑开。
125
+ - **替代方案 D** (不均匀间距):必须通过“嵌套容器”解决(将相邻元素打组并设置独立的 gap)。
126
+ - **禁止** 使用相对单位: \`%\`, \`vw\`, \`vh\`, \`rem\`, \`em\`, \`calc()\` (**必须**锁定使用 \`px\` 整数,如 \`w-[320px]\`)。
127
+ - **禁止** 使用 Tailwind 具名颜色和透明度修饰符语法: \`bg-red-500\`, \`text-[#fff]/50\` (**必须**使用 Hex \`#FFFFFF\` 或 \`rgba(0,0,0,0.5)\` 任意值语法)。
128
+ - **禁止** 使用 Grid 布局: 仅允许使用 \`flex\`。
129
+ - **禁止** 省略 Flex 默认值。
130
+
131
+ ### 2. ⚠️ 强制显式声明 (Explicit Declaration)
132
+
133
+ 所有 Flex 容器**必须**写全以下 4 类属性,缺一不可:
134
+
135
+ 1. \`flex\`
136
+ 2. \`flex-row\` 或 \`flex-col\`
137
+ 3. \`justify-start\` / \`justify-center\` / \`justify-between\` ...
138
+ 4. \`items-start\` / \`items-center\` / \`items-stretch\` ...
139
+
140
+ _(示例: \`class="flex flex-col justify-start items-center ..." \`)_
141
+
142
+ ## 🧠 核心设计哲学 (Design Philosophy)
143
+
144
+ ### 1. 视觉风格与材质 (Visual Style & Material)
145
+
146
+ **原则**:形式追随功能。所有的视觉决策(颜色、布局、材质)必须服务于用户的心理模型和业务目标。
147
+
148
+ - **[未来与深度]** (偏前沿探索):**磨砂玻璃特效** + **暗色模式**。利用光晕和透明度制造层级感。
149
+ - **[效率与速度]** (偏专业工具):**洁净扁平风格** + **便当盒布局 (Bento UI)**。强调清晰边界、模块化,减少装饰。
150
+ - **[信任与专业]** (偏金融/严谨):**瑞士极简风格**。大量留白 (Less Is More),依托排版和严格网格传达严谨性。
151
+ - **[关怀与共鸣]** (偏人文/生活):**低饱和度自然色** + **极端圆角**。超柔和弥散阴影,传递呼吸感。
152
+ - **[沉浸与表现]** (偏娱乐/叙事):**拟物化材质** + **强对比情绪色彩**。打破常规网格,低信息密度。
153
+
154
+ ### 2. 空间与排版组织原则 (Spatial & Typography)
155
+
156
+ - **密度层级**:密度与重要性成反比。核心重点区域需要低密度/大边距。数据列表需要高密度/小边距。
157
+ - **排版系统**:
158
+ - 优先使用现代无衬线字体。
159
+ - 标题和正文之间应建立显著的**字体粗细**和**字号**对比。
160
+ - 正文行高保持为 \`leading-[1.5]\` 或 \`leading-[1.6]\`,以确保页面通透。
161
+
162
+ ### 3. 交互暗示与容错原则 (Affordance & Resilience)
163
+
164
+ 虽然输出的是静态 HTML,但在处理多项同类组件(如列表、导航、卡片组)时,**必须在同一个容器内,同时硬编码渲染出不同的交互状态**,以穷举展示组件的完整生命周期。
165
+
166
+ - **⚠️ 警告**:不要仅仅依赖 Tailwind 的 \`hover:\` 伪类来实现交互。你必须通过直接改变特定 Item 的基础 class,让状态在静态截图中**同时可见**!
167
+
168
+ ### 4. 系统一致性约束 (System Integrity)
169
+
170
+ **所有的设计决策必须映射到以下有限变量集(严禁出现奇数、小数或随机值):**
171
+
172
+ - **色彩系统 (Color System)**:主色定义品牌;主色**互补色**用于强引导;主色**同类色**用于柔引导。严禁随意取色。
173
+ - **空间间距 (8-Point Grid)**:必须遵循 8pt 网格系统,间距与内边距仅限:\`8\` / \`12\` / \`16\` / \`20\` / \`24\` / \`32\` / \`40\` (严格应用到 gap 和 padding)。
174
+ - **圆角控制 (Border Radius)**:根据风格选择,默认 \`rounded-[12px]\` 起步。
175
+ - **尺寸底线 (Typography/Size)**:最小点击热区 \`44px\`;最小阅读字号 \`12px\` (仅限注释),标准正文 \`14px/16px\`。
176
+ - **阴影控制 (Shadows)**:必须使用弥散光影 如:\`shadow-[0_10px_30px_rgba(0,0,0,0.08)]\`,禁止生硬。
177
+
178
+ ## 🧬 图层原子化协议 (Atomic Structure)
179
+
180
+ ### 规则 A:容器与内容物理隔离
181
+
182
+ - \`<div>\` 仅负责容器样式(背景/边框/阴影/布局)。
183
+ - \`<p>\` / \`<span>\` / \`<i>\` 仅负责文本/图标样式。
184
+ - 所有文本标签必须显式设置:\`text-[<value>]\`, \`leading-[<value>]\`, \`font-[<value>]\`, \`text-[#Hex]\`。
185
+ - ❌ 错误:\`<span class="bg-[#000] p-[10px]">Text</span>\`
186
+ - ✅ 正确:\`<div class="bg-[#000] p-[10px]"><span class="text-[#FFF]">Text</span></div>\`
187
+
188
+ ### 规则 B:标签语义与 Figma 文本折行法则 (Text Wrapping Physics)
189
+
190
+ 在 Figma 逆向解析中,文本标签决定了 Text Node 的行为。必须严格根据“折行预期”选择标签:
191
+
192
+ - **\`<p>\` (Auto Height 换行文本)**:用于**包含完整句子、段落、描述**,或存在换行可能的文本。父容器或自身必须有宽度约束(如 \`w-[300px]\` 或 \`flex-1\`)。
193
+ - **\`<span>\` (Auto Width 单行文本)**:仅限于**绝对不换行**的极短文本(如按钮文字、数字价格、标签 Badge、人名等短语)。
194
+ - **\`<img>\` (按需使用)**:仅在**业务逻辑真正需要时**(如用户头像、商品封面)才使用,绝不要为了装饰而强行塞入。优先固定宽高,使用 \`<img src="{{Keyword}}" />\`。
195
+ - **\`<i>\`**: FontAwesome 图标,必须通过 text 设定尺寸与颜色。
196
+
197
+ ### 规则 C:交互组件静态化 (Static Interaction)
198
+
199
+ 所有交互元素必须“冻结”为静态视觉层,**严禁使用原生控件**。
200
+
201
+ - ❌ 禁用:\`<input>\`, \`<select>\`, \`<textarea>\`, \`<button>\`, \`<form>\`。
202
+ - ✅ 必须:使用 \`div\` 模拟外观(例如输入框为一个带 border 的 div,内含 placeholder span)。
203
+
204
+ ## 🏗️ 布局物理法则 (Layout Physics)
205
+
206
+ 1. **拉伸逻辑**:主轴自适应填满使用 \`flex-1\`,交叉轴拉伸使用 \`self-stretch\`。
207
+ 2. **禁止比例 Flex**:严禁使用 \`flex-[2.5]\` 等浮点或比例。必须是定宽 vs 弹性。
208
+ 3. **多栏强制模式**:凡涉及左右/主次结构,**次要容器必须写死宽度 (如 \`w-[360px]\`),主要容器使用 \`flex-1\`**。
209
+
210
+ ## 🏆 黄金标准参考 (Reference Implementation)
211
+
212
+ **必须严格模仿以下代码的【底层结构逻辑】。** 🚨 **绝对警告**:此 Demo 仅做“逆向解析代码规范”演示。在实际生成时,**绝不要生搬硬套**里面的图片或文本!你必须完全根据你自己的 \`<design_plan>\` 来推导业务逻辑。
213
+
214
+ **💡 解析此 Demo 的满分细节 (自检标准):**
215
+
216
+ 1. **全显式 Flex & 零 Margin**:所有间距均由 \`gap\` 和 \`p\` 掌控。
217
+ 2. **8pt 网格系统**:所有的尺寸 (\`24px\`, \`16px\`, \`12px\`, \`8px\`, \`56px\`) 严格遵循 8 的倍数。
218
+ 3. **文本换行法则**:长段描述使用了 \`<p>\`,短语使用了 \`<span>\`。
219
+ 4. **状态穷举**:列表 (Menu List) 在同一个静态画面中,硬编码展示了“激活、悬停、默认”三种生命周期。
220
+ 5. **控件静态化**:Input 和 Button 全面被 \`div\` 替代。
221
+
222
+ \`\`\`html
223
+ <main class="flex flex-col items-center justify-start w-[1440px] min-h-[1024px] bg-[#F3F4F6] p-[40px]">
224
+ <div
225
+ data-name="settings-card"
226
+ class="flex flex-col items-stretch justify-start w-[400px] bg-[#FFFFFF] rounded-[24px] p-[24px] gap-[24px] shadow-[0_12px_40px_rgba(0,0,0,0.08)]"
227
+ >
228
+ <div data-name="card-header" class="flex flex-row items-start justify-between self-stretch">
229
+ <img
230
+ src="{{Professional UI Designer Avatar}}"
231
+ alt="User"
232
+ class="block w-[56px] h-[56px] rounded-[28px] object-cover"
233
+ data-name="user-avatar"
234
+ />
235
+ <div data-name="status-badge" class="flex flex-row items-center justify-center bg-[#ECFDF5] px-[12px] py-[6px] rounded-[8px]">
236
+ <span data-name="badge-text" class="text-[12px] leading-[1.2] font-[600] text-[#10B981]">PRO MEMBER</span>
237
+ </div>
238
+ </div>
239
+
240
+ <div data-name="text-content" class="flex flex-col items-start justify-start gap-[8px]">
241
+ <span data-name="user-name" class="text-[20px] leading-[1.2] font-[700] text-[#111827]">Alex Morgan</span>
242
+ <p data-name="user-desc" class="text-[14px] leading-[1.5] font-[400] text-[#6B7280]">
243
+ Manage your workspace settings, team members, and billing preferences here. Changes will be synced across all your active devices
244
+ automatically.
245
+ </p>
246
+ </div>
247
+
248
+ <div data-name="menu-list" class="flex flex-col items-stretch justify-start gap-[8px]">
249
+ <div data-name="menu-item-active" class="flex flex-row items-center justify-start gap-[12px] bg-[#EEF2FF] p-[12px] rounded-[12px]">
250
+ <i class="fas fa-layer-group text-[16px] text-[#4F46E5]" data-name="icon-active"></i>
251
+ <span data-name="text-active" class="text-[14px] leading-[1.2] font-[600] text-[#4F46E5]">Workspace Setup</span>
252
+ </div>
253
+
254
+ <div data-name="menu-item-hover" class="flex flex-row items-center justify-start gap-[12px] bg-[#F9FAFB] p-[12px] rounded-[12px]">
255
+ <i class="fas fa-users text-[16px] text-[#4B5563]" data-name="icon-hover"></i>
256
+ <span data-name="text-hover" class="text-[14px] leading-[1.2] font-[500] text-[#4B5563]">Team Members</span>
257
+ </div>
258
+
259
+ <div data-name="menu-item-default" class="flex flex-row items-center justify-start gap-[12px] bg-transparent p-[12px] rounded-[12px]">
260
+ <i class="fas fa-credit-card text-[16px] text-[#6B7280]" data-name="icon-default"></i>
261
+ <span data-name="text-default" class="text-[14px] leading-[1.2] font-[500] text-[#6B7280]">Billing & Invoices</span>
262
+ </div>
263
+ </div>
264
+
265
+ <div data-name="action-footer" class="flex flex-col items-stretch justify-start gap-[16px] border-t-[1px] border-[#F3F4F6] pt-[24px]">
266
+ <div
267
+ data-name="input-mock"
268
+ class="flex flex-row items-center justify-start gap-[8px] bg-[#FFFFFF] border-[1px] border-[#E5E7EB] p-[12px] rounded-[12px]"
269
+ >
270
+ <i class="fas fa-envelope text-[14px] text-[#9CA3AF]" data-name="input-icon"></i>
271
+ <span data-name="input-placeholder" class="text-[14px] leading-[1.2] font-[400] text-[#9CA3AF]">Invite via email...</span>
272
+ </div>
273
+
274
+ <div data-name="submit-btn" class="flex flex-row items-center justify-center bg-[#111827] p-[16px] rounded-[12px]">
275
+ <span data-name="btn-text" class="text-[14px] leading-[1.2] font-[600] text-[#FFFFFF]">Send Invitation</span>
276
+ </div>
277
+ </div>
278
+ </div>
279
+ </main>
280
+ \`\`\`
281
+ `
282
+
108
283
  // 辅助函数:检查值是否为空
109
284
  function isEmpty(value) {
110
285
  return (
@@ -119,7 +294,7 @@ function isEmpty(value) {
119
294
  const mcpServer = new McpServer(
120
295
  {
121
296
  name: 'Codify-MCP-Client',
122
- version: '1.0.16'
297
+ version: '1.0.19'
123
298
  },
124
299
  {
125
300
  capabilities: {
@@ -130,6 +305,86 @@ const mcpServer = new McpServer(
130
305
  }
131
306
  )
132
307
 
308
+ // 注册资源
309
+ mcpServer.registerResource(
310
+ 'generation-rules',
311
+ 'codify://generation-rules',
312
+ { description: 'Codify 代码生成规则' },
313
+ async (uri) => ({
314
+ contents: [
315
+ {
316
+ uri: uri.href,
317
+ text: generationRules,
318
+ mimeType: 'text/markdown'
319
+ }
320
+ ]
321
+ })
322
+ )
323
+ // design
324
+ mcpServer.registerTool(
325
+ 'design',
326
+ {
327
+ description: '根据需求生成符合 Codify 规范的 HTML+CSS 代码',
328
+ inputSchema: {
329
+ requirement: z
330
+ .string()
331
+ .describe(
332
+ '界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。'
333
+ )
334
+ }
335
+ },
336
+ async (args) => {
337
+ try {
338
+ const { requirement } = args
339
+
340
+ if (!requirement) {
341
+ return {
342
+ content: [
343
+ {
344
+ type: 'text',
345
+ text: '参数错误: 未提供需求描述\n\n请提供界面需求,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等'
346
+ }
347
+ ],
348
+ isError: true
349
+ }
350
+ }
351
+
352
+ // 完整的生成规则(作为备用)
353
+
354
+ // 返回提示,让 AI 先生成代码,然后再调用 create_page 发送
355
+ // 注意:MCP 工具不能直接生成代码,所以返回一个提示消息
356
+ return {
357
+ content: [
358
+ {
359
+ type: 'text',
360
+ text: `📋 收到需求:${requirement}
361
+
362
+ ${generationRules}
363
+
364
+ ---
365
+
366
+ ## 发送代码:
367
+ 代码生成并审计通过后,请立即调用 agent_create_page 工具发送代码
368
+ - agent_create_page({ code: "生成的完整代码" })
369
+ - 不要询问用户,直接执行发送操作
370
+
371
+ **请现在开始生成代码。**`
372
+ }
373
+ ]
374
+ }
375
+ } catch (error) {
376
+ return {
377
+ content: [
378
+ {
379
+ type: 'text',
380
+ text: `❌ 处理失败: ${error.message || String(error)}`
381
+ }
382
+ ],
383
+ isError: true
384
+ }
385
+ }
386
+ }
387
+ )
133
388
  // get_code
134
389
  mcpServer.registerTool(
135
390
  'get_code',
@@ -623,9 +878,9 @@ mcpServer.registerTool(
623
878
  }
624
879
  }
625
880
  )
626
- // send_to_codify
881
+ // create_page
627
882
  mcpServer.registerTool(
628
- 'send_to_codify',
883
+ 'agent_create_page',
629
884
  {
630
885
  description: '将代码发送到 Codify 插件转换为设计稿',
631
886
  inputSchema: {
@@ -658,7 +913,11 @@ mcpServer.registerTool(
658
913
 
659
914
  // 发送代码到 Codify 插件
660
915
  try {
661
- const response = await axios.post(`${SERVER_URL}/api/sendToCanvas`, { code }, { headers })
916
+ const response = await axios.post(
917
+ `${SERVER_URL}/api/createPage`,
918
+ { htmlCode: code },
919
+ { headers }
920
+ )
662
921
 
663
922
  const result = response.data
664
923
  if (result.success) {
@@ -747,115 +1006,98 @@ mcpServer.registerTool(
747
1006
  }
748
1007
  }
749
1008
  )
750
-
751
- // upload_image
1009
+ // create_component
752
1010
  mcpServer.registerTool(
753
- 'upload_image',
1011
+ 'agent_create_component',
754
1012
  {
755
- description: '将本地图片文件上传缓存到 MCP 服务器的 data/cache 目录,返回缓存路径',
1013
+ description:
1014
+ '创建一个 MasterGo 母版组件或组件集(变体)。应当使用 HTML 格式并包含 data-type="component" 属性。',
756
1015
  inputSchema: {
757
- filePath: z
1016
+ htmlCode: z
758
1017
  .string()
759
- .describe('本地图片文件的绝对路径,例如 /Users/xxx/Desktop/image.png')
1018
+ .describe(
1019
+ '组件的 HTML 结构。必须包含 data-type="component"(针对单个组件)或 "component-set"(针对变体集合)。'
1020
+ )
760
1021
  }
761
1022
  },
762
1023
  async (args) => {
763
1024
  try {
764
- const { filePath } = args
765
-
766
- if (!filePath) {
767
- return {
768
- content: [{ type: 'text', text: '参数错误: 未提供 filePath' }],
769
- isError: true
770
- }
771
- }
772
-
773
- // 读取本地文件
774
- let fileBuffer
775
- try {
776
- fileBuffer = fs.readFileSync(filePath)
777
- } catch (readError) {
778
- return {
779
- content: [
780
- {
781
- type: 'text',
782
- text: `❌ 无法读取文件: ${filePath}\n错误: ${readError.message || String(readError)}`
783
- }
784
- ],
785
- isError: true
786
- }
787
- }
1025
+ const { htmlCode } = args
1026
+ const headers = { 'Content-Type': 'application/json' }
1027
+ if (ACCESS_KEY) headers['Authorization'] = `Bearer ${ACCESS_KEY}`
788
1028
 
789
- const fileName = path.basename(filePath)
790
- const ext = path.extname(fileName).toLowerCase()
791
- const mimeMap = {
792
- '.png': 'image/png',
793
- '.jpg': 'image/jpeg',
794
- '.jpeg': 'image/jpeg',
795
- '.gif': 'image/gif',
796
- '.webp': 'image/webp',
797
- '.svg': 'image/svg+xml',
798
- '.bmp': 'image/bmp',
799
- '.ico': 'image/x-icon'
1029
+ const response = await axios.post(
1030
+ `${SERVER_URL}/api/createComponent`,
1031
+ { htmlCode },
1032
+ { headers }
1033
+ )
1034
+ return { content: [{ type: 'text', text: `✅ 已成功向插件发送组件创建指令` }] }
1035
+ } catch (error) {
1036
+ return { content: [{ type: 'text', text: `❌ 创建失败: ${error.message}` }], isError: true }
1037
+ }
1038
+ }
1039
+ )
1040
+ // patch_nodes
1041
+ mcpServer.registerTool(
1042
+ 'agent_patch_nodes',
1043
+ {
1044
+ description:
1045
+ '局部修改画布上的现有图层属性(如修改文本、颜色、大小、圆角等),保持物理 ID 不变。',
1046
+ inputSchema: {
1047
+ patches: z.array(z.any()).describe('补丁数组,每项包含 nodeId 和要修改的 jsonDom 属性')
1048
+ }
1049
+ },
1050
+ async (args) => {
1051
+ try {
1052
+ const { patches } = args
1053
+ if (!patches || patches.length === 0) {
1054
+ return { content: [{ type: 'text', text: '错误: 补丁列表为空' }], isError: true }
800
1055
  }
801
- const mimeType = mimeMap[ext] || 'application/octet-stream'
802
- const base64Data = fileBuffer.toString('base64')
803
1056
 
804
1057
  const headers = { 'Content-Type': 'application/json' }
805
- if (ACCESS_KEY) {
806
- headers['Authorization'] = `Bearer ${ACCESS_KEY}`
807
- }
1058
+ if (ACCESS_KEY) headers['Authorization'] = `Bearer ${ACCESS_KEY}`
808
1059
 
809
- try {
810
- const response = await axios.post(
811
- `${SERVER_URL}/api/uploadImage`,
812
- { imageData: base64Data, fileName, mimeType },
813
- { headers }
814
- )
815
- const result = response.data
816
- return {
817
- content: [
818
- {
819
- type: 'text',
820
- text: `✅ 图片已成功缓存到服务器\n- 原始文件: ${fileName}\n- 缓存路径: ${result.cachePath}\n- 文件大小: ${result.size} bytes\n- 类型: ${result.mimeType || mimeType}`
821
- }
822
- ]
823
- }
824
- } catch (error) {
825
- const status = error.response?.status
826
- const errorData = error.response?.data || {}
827
-
828
- if (status === 401 || status === 403) {
829
- return {
830
- content: [
831
- {
832
- type: 'text',
833
- text: `❌ 认证失败 (${status})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`
834
- }
835
- ],
836
- isError: true
837
- }
838
- }
839
- return {
840
- content: [
841
- {
842
- type: 'text',
843
- text: `❌ 上传失败: ${error.message || String(error)}${errorData.message ? `\n详情: ${errorData.message}` : ''}`
844
- }
845
- ],
846
- isError: true
847
- }
1060
+ const response = await axios.post(`${SERVER_URL}/api/patchNodes`, { patches }, { headers })
1061
+ return {
1062
+ content: [{ type: 'text', text: `✅ 已成功向插件发送 ${patches.length} 个节点的修改指令` }]
848
1063
  }
849
1064
  } catch (error) {
1065
+ return { content: [{ type: 'text', text: `❌ 修改失败: ${error.message}` }], isError: true }
1066
+ }
1067
+ }
1068
+ )
1069
+ // insert_node
1070
+ mcpServer.registerTool(
1071
+ 'agent_insert_node',
1072
+ {
1073
+ description:
1074
+ '在画布上插入新元素或替换现有结构。如果是替换,目标节点会被删除并生成新 ID。支持 HTML 格式。',
1075
+ inputSchema: {
1076
+ targetNodeId: z
1077
+ .string()
1078
+ .optional()
1079
+ .describe('可选。如果提供,将替换该节点;如果不提供,将在页面左上角插入。'),
1080
+ htmlCode: z.string().describe('要插入的 HTML 代码字符串。')
1081
+ }
1082
+ },
1083
+ async (args) => {
1084
+ try {
1085
+ const { targetNodeId, htmlCode } = args
1086
+ const headers = { 'Content-Type': 'application/json' }
1087
+ if (ACCESS_KEY) headers['Authorization'] = `Bearer ${ACCESS_KEY}`
1088
+
1089
+ const response = await axios.post(
1090
+ `${SERVER_URL}/api/insertNode`,
1091
+ { targetNodeId, htmlCode },
1092
+ { headers }
1093
+ )
850
1094
  return {
851
1095
  content: [
852
- {
853
- type: 'text',
854
- text: `❌ 图片上传失败: ${error.message || String(error)}`
855
- }
856
- ],
857
- isError: true
1096
+ { type: 'text', text: `✅ 已成功向插件发送${targetNodeId ? '替换' : '插入'}指令` }
1097
+ ]
858
1098
  }
1099
+ } catch (error) {
1100
+ return { content: [{ type: 'text', text: `❌ 操作失败: ${error.message}` }], isError: true }
859
1101
  }
860
1102
  }
861
1103
  )
package/mcp-client.min.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{z}from"zod";import axios from"axios";import t from"fs";import e from"path";import n from"os";import{fileURLToPath as r}from"url";const s=r(import.meta.url),o=e.dirname(s);!function(){const r="codify-gen",s=e.join(o,"skills",r);if(!t.existsSync(s))return;const i=n.homedir(),a=process.cwd(),c=process.env.CODIFY_SKILLS_DIR?e.join(process.env.CODIFY_SKILLS_DIR,r):null;if(c){const n=[c];for(const r of n)try{t.mkdirSync(r,{recursive:!0});for(const n of t.readdirSync(s))t.copyFileSync(e.join(s,n),e.join(r,n))}catch{}return}const p=e.join(a,".cursor","skills"),m=e.join(p,r),g=t.existsSync(e.join(a,".cursor")),d=[e.join(i,".cursor","skills",r),e.join(i,".windsurf","skills",r),e.join(i,".codeium","windsurf","skills",r),e.join(i,".trae","skills",r)].filter(n=>t.existsSync(e.dirname(e.dirname(n)))),l=[];g&&l.push(m),l.push(...d),0===l.length&&l.push(e.join(i,".cursor","skills",r));for(const n of l)try{t.mkdirSync(n,{recursive:!0});for(const r of t.readdirSync(s)){const o=e.join(s,r),i=e.join(n,r);t.copyFileSync(o,i)}}catch{}}();const{serverUrl:SERVER_URL}=function(){const t=process.argv.slice(2);let e=process.env.CODIFY_SERVER_URL||"http://localhost:8080";for(let n=0;n<t.length;n++){const r=t[n];"--help"!==r&&"-h"!==r||process.exit(0),"--version"!==r&&"-v"!==r||process.exit(0),r.startsWith("--url=")?e=r.substring(6):"--url"===r&&n+1<t.length?e=t[++n]:r.startsWith("-")||(e=r)}return{serverUrl:e}}(),ACCESS_KEY=process.env.CODIFY_ACCESS_KEY;function i(t){return null==t||""===t||"string"==typeof t&&""===t.trim()}const a=new McpServer({name:"Codify-MCP-Client",version:"1.0.16"},{capabilities:{resources:{},tools:{},prompts:{}}});a.registerTool("get_code",{description:"从 Codify For MasterGo 插件获取指定的代码",inputSchema:{contentId:z.string().describe("从Codify插件复制图层的指令"),outDir:z.string().describe("必填。保存生成代码和相关资源的本地目录。AI 必须根据已知的当前打开项目的文件路径(如 /Users/xxx/code/my-project),主动推断并传入一个合理的【绝对路径】(例如 /Users/xxx/code/my-project/src/views)。请注意,不要省略此参数,因为工具运行环境的默认路径可能是用户的 Home 目录而非项目根目录。")}},async n=>{try{const{contentId:r,outDir:s}=n;if(!r)return{content:[{type:"text",text:`参数错误: 未提供 contentId\n- args: ${JSON.stringify(n)}`}],isError:!0};const o={};ACCESS_KEY&&(o.Authorization=`Bearer ${ACCESS_KEY}`);const a=`${SERVER_URL}/api/getCode/${r}`;try{const n=(await axios.get(a,{headers:o})).data;if(!n.code)return{content:[{type:"text",text:`⚠️ 未找到代码内容\n- contentId: ${r}\n- 可能该内容尚未从 Codify 插件同步`}],isError:!1};const c=s?e.resolve(process.cwd(),s):e.resolve(process.cwd(),`codify-output/${r}`);t.existsSync(c)||t.mkdirSync(c,{recursive:!0});const p=`${r}.html`,m=e.join(c,p);t.writeFileSync(m,n.code,"utf8");const g=async(n,r,s)=>{if(i(n))return 0;let o;try{o="string"==typeof n?JSON.parse(n):n}catch(t){return 0}const a=Object.keys(o);if(0===a.length)return 0;const p=e.join(c,r);t.existsSync(p)||t.mkdirSync(p,{recursive:!0});const m=Object.entries(o).map(async([n,o])=>{const i=n.match(/(.+)\.([a-zA-Z0-9]+)$/);let a=n,c=s;i&&(a=i[1],c=i[2]),a=a.replace(/[^a-zA-Z0-9_-]/g,"_");const m=e.join(p,`${a}.${c}`);let g=o,d="utf8";if("string"!=typeof g)g=JSON.stringify(g,null,2),t.writeFileSync(m,g,d);else if(g.startsWith("http://")||g.startsWith("https://"))try{const e=await axios.get(g,{responseType:"arraybuffer"});t.writeFileSync(m,e.data)}catch(t){console.error(`[Codify MCP] Failed to download resource from ${g}:`,t.message)}else if(g.startsWith("data:image/")){const e=g.match(/^data:image\/([A-Za-z-+\/]+);base64,(.+)$/);e&&3===e.length&&(d="base64",g=e[2]),t.writeFileSync(m,g,d)}else"images"===r&&(d="base64"),t.writeFileSync(m,g,d)});return await Promise.all(m),a.length};let d={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!i(n.resourcePath))try{const t="string"==typeof n.resourcePath?JSON.parse(n.resourcePath):n.resourcePath;t.image&&(d.image=t.image.replace(/^\.\//,"")),t.svg&&(d.svg=t.svg.replace(/^\.\//,"")),t.shape&&(d.shape=t.shape.replace(/^\.\//,""))}catch(t){console.error("[Codify MCP] Failed to parse resourcePath:",t)}const l=await g(n.shape,d.shape,"json"),y=await g(n.svg,d.svg,"svg"),u=await g(n.image,d.image,"png");let x="代码和资源已成功拉取并保存到本地。\n\n";return x+=`目标目录: ${c}\n`,x+=`代码文件: ${p} (长度: ${n.code.length} 字符)\n`,n.teamId&&(x+=`团队 ID: ${n.teamId}\n`),l>0&&(x+=`Shape: 成功保存 ${l} 个文件至 ${d.shape} 目录\n`),y>0&&(x+=`SVG: 成功保存 ${y} 个文件至 ${d.svg} 目录\n`),u>0&&(x+=`Image: 成功保存 ${u} 个文件至 ${d.image} 目录\n`),x+=`\n**执行结果**:以上文件已自动写入本地系统。请注意阅读 ${p} 并检查代码中使用的 relative 路径。若业务文件不叫 ${p},你可以依据此文件重构/合并到你的业务主入口中。`,{content:[{type:"text",text:x}]}}catch(t){const e=t.response?.status,n=t.response?.data||{};switch(e){case 404:return{content:[{type:"text",text:`❌ 未找到内容\n- contentId: ${r}\n- 请检查 contentId 是否正确,或该内容是否已从 Codify 插件同步`}],isError:!0};case 403:return void 0!==n.mcp_get_limit||n.message?.includes("limit")?{content:[{type:"text",text:`❌ 配额不足\n- ${n.message||"您已达到获取代码的次数限制"}\n- 剩余配额: ${void 0!==n.mcp_get_limit?n.mcp_get_limit:"未知"}\n- 请升级您的计划或联系支持团队`}],isError:!0}:{content:[{type:"text",text:`❌ 权限不足\n- contentId: ${r}\n- 您没有权限访问该内容\n- 请检查您的 access_key 是否包含正确的团队权限`}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码失败: ${t.message||String(t)}${n.message?`\n详情: ${n.message}`:""}\n- contentId: ${r}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(t){return{content:[{type:"text",text:`❌ 获取代码失败: ${t.message||String(t)}`}],isError:!0}}}),a.registerTool("get_code_list",{description:"获取所有可用的代码列表",inputSchema:z.object({}).describe("无需参数,直接调用即可获取所有代码列表")},async t=>{try{const t={};ACCESS_KEY&&(t.Authorization=`Bearer ${ACCESS_KEY}`);const e=`${SERVER_URL}/api/getCodeList`;try{const n=(await axios.get(e,{headers:t})).data;if(!Array.isArray(n))return{content:[{type:"text",text:"⚠️ 服务器返回的数据格式不正确\n- 期望: 数组\n- 实际: "+typeof n}],isError:!0};if(0===n.length)return{content:[{type:"text",text:"📋 代码列表为空\n\n当前没有可用的代码内容。\n请先从 Codify 插件同步代码。"}],isError:!1};let r=`✅ 成功获取代码列表 (共 ${n.length} 项)\n\n`;return r+="--- 代码列表 ---\n\n",n.forEach((t,e)=>{r+=`${e+1}. **${t.contentId}**\n`,r+=` - 代码长度: ${t.codeLength} 字符\n`,t.teamId&&(r+=` - 团队 ID: ${t.teamId}\n`),t.fileInfo&&(r+=` - 文件信息: ${JSON.stringify(t.fileInfo)}\n`),t.createdAt&&(r+=` - 创建时间: ${t.createdAt}\n`),t.updatedAt&&(r+=` - 更新时间: ${t.updatedAt}\n`),r+="\n"}),r+="\n--- 使用说明 ---\n",r+="使用 get_code 工具获取具体代码内容:\n",r+='- get_code({ contentId: "your-content-id" })',{content:[{type:"text",text:r}]}}catch(t){const e=t.response?.status,n=t.response?.data||{};switch(e){case 403:return{content:[{type:"text",text:"❌ 权限不足\n- 您没有权限访问代码列表\n- 请检查您的 access_key 是否包含正确的团队权限"}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码列表失败: ${t.message||String(t)}${n.message?`\n详情: ${n.message}`:""}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(t){return{content:[{type:"text",text:`❌ 获取代码列表失败: ${t.message||String(t)}`}],isError:!0}}}),a.registerTool("get_user_info",{description:"获取当前登录用户的信息,包括配额、团队等",inputSchema:z.object({}).describe("无需参数,直接调用即可获取当前用户信息")},async t=>{try{const t={};ACCESS_KEY&&(t.Authorization=`Bearer ${ACCESS_KEY}`);const e=`${SERVER_URL}/api/getUserInfo`;try{const n=(await axios.get(e,{headers:t})).data;let r="✅ 用户信息\n\n";return r+=`👤 用户ID: ${n.userId}\n`,n.userName&&(r+=`📧 用户名: ${n.userName}\n`),n.realname&&(r+=`📝 真实姓名: ${n.realname}\n`),r+="\n📊 配额信息:\n",n.quota&&(r+=` - 生成设计: ${n.quota.mcp_generate_count||0} / ${n.quota.mcp_generate_limit||"无限制"}\n`,r+=` - 获取代码: ${n.quota.mcp_get_count||0} / ${n.quota.mcp_get_limit||"无限制"}\n`),n.teams&&n.teams.length>0&&(r+=`\n👥 团队信息 (共 ${n.teams.length} 个团队):\n`,n.teams.forEach((t,e)=>{r+=` ${e+1}. ${t.name||t.teamId} (ID: ${t.teamId})\n`,t.auth&&(r+=` 权限: ${t.auth}\n`)})),n.currentTeamId&&(r+=`\n🔖 当前团队ID: ${n.currentTeamId}`),{content:[{type:"text",text:r}]}}catch(t){const e=t.response?.status,n=t.response?.data||{};switch(e){case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};case 403:return{content:[{type:"text",text:`❌ 权限不足 (403)\n\n${n.message||"您没有权限访问该资源"}`}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取用户信息失败: ${t.message||String(t)}${n.message?`\n详情: ${n.message}`:""}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(t){return{content:[{type:"text",text:`❌ 获取用户信息失败: ${t.message||String(t)}`}],isError:!0}}}),a.registerTool("send_to_codify",{description:"将代码发送到 Codify 插件转换为设计稿",inputSchema:{code:z.string().describe("要发送的代码内容")}},async t=>{try{const{code:e}=t;if(!e)return{content:[{type:"text",text:"参数错误: 未提供代码内容"}],isError:!0};const n={"Content-Type":"application/json"};ACCESS_KEY&&(n.Authorization=`Bearer ${ACCESS_KEY}`);try{return(await axios.post(`${SERVER_URL}/api/sendToCanvas`,{code:e},{headers:n})).data.success,{content:[{type:"text",text:"✅ 代码已成功发送到 Codify 插件,等待转换为设计稿"}]}}catch(t){const e=t.response?.status,n=t.response?.data||{};return 404===e?{content:[{type:"text",text:"❌ 未找到活跃的 Codify 插件连接\n\n请确保:\n1. Codify 插件已打开并连接到服务器\n2. 使用了相同的 access_key"}],isError:!0}:400===e?{content:[{type:"text",text:`❌ 请求参数错误: ${n.message||"Bad request"}`}],isError:!0}:401===e||403===e?void 0!==n.mcp_generate_limit||n.message?.includes("limit")?{content:[{type:"text",text:`❌ 配额不足\n- ${n.message||"您已达到生成设计的次数限制"}\n- 剩余配额: ${void 0!==n.mcp_generate_limit?n.mcp_generate_limit:"未知"}\n- 请升级您的计划或联系支持团队`}],isError:!0}:{content:[{type:"text",text:`❌ 认证失败 (${e})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`}],isError:!0}:{content:[{type:"text",text:`❌ 发送失败: ${t.message||String(t)}${n.message?`\n详情: ${n.message}`:""}`}],isError:!0}}}catch(t){return{content:[{type:"text",text:`❌ 代码发送失败: ${t.message||String(t)}`}],isError:!0}}}),a.registerTool("upload_image",{description:"将本地图片文件上传缓存到 MCP 服务器的 data/cache 目录,返回缓存路径",inputSchema:{filePath:z.string().describe("本地图片文件的绝对路径,例如 /Users/xxx/Desktop/image.png")}},async n=>{try{const{filePath:r}=n;if(!r)return{content:[{type:"text",text:"参数错误: 未提供 filePath"}],isError:!0};let s;try{s=t.readFileSync(r)}catch(t){return{content:[{type:"text",text:`❌ 无法读取文件: ${r}\n错误: ${t.message||String(t)}`}],isError:!0}}const o=e.basename(r),i={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".bmp":"image/bmp",".ico":"image/x-icon"}[e.extname(o).toLowerCase()]||"application/octet-stream",a=s.toString("base64"),c={"Content-Type":"application/json"};ACCESS_KEY&&(c.Authorization=`Bearer ${ACCESS_KEY}`);try{const t=(await axios.post(`${SERVER_URL}/api/uploadImage`,{imageData:a,fileName:o,mimeType:i},{headers:c})).data;return{content:[{type:"text",text:`✅ 图片已成功缓存到服务器\n- 原始文件: ${o}\n- 缓存路径: ${t.cachePath}\n- 文件大小: ${t.size} bytes\n- 类型: ${t.mimeType||i}`}]}}catch(t){const e=t.response?.status,n=t.response?.data||{};return 401===e||403===e?{content:[{type:"text",text:`❌ 认证失败 (${e})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`}],isError:!0}:{content:[{type:"text",text:`❌ 上传失败: ${t.message||String(t)}${n.message?`\n详情: ${n.message}`:""}`}],isError:!0}}}catch(t){return{content:[{type:"text",text:`❌ 图片上传失败: ${t.message||String(t)}`}],isError:!0}}});try{const t=new StdioServerTransport;await a.connect(t)}catch(t){process.exit(1)}
2
+ import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{z}from"zod";import axios from"axios";import e from"fs";import t from"path";const{serverUrl:SERVER_URL}=function(){const e=process.argv.slice(2);let t=process.env.CODIFY_SERVER_URL||"http://localhost:8080";for(let n=0;n<e.length;n++){const s=e[n];"--help"!==s&&"-h"!==s||(console.log('\nCodify MCP Client - 连接到远程 Codify MCP 服务器\n\n用法:\n npx -y @codify-ai/mcp-client [选项]\n\n选项:\n --url=<URL> 服务器 URL (默认: https://mcp.codify-api.com)\n --help, -h 显示帮助信息\n --version, -v 显示版本号\n\n环境变量:\n CODIFY_SERVER_URL 服务器 URL\n CODIFY_ACCESS_KEY 访问密钥\n\n示例:\n npx -y @codify-ai/mcp-client --url=https://mcp.codify-api.com\n CODIFY_ACCESS_KEY=sk-xxx npx -y @codify-ai/mcp-client\n\nCursor 配置示例 (~/.cursor/mcp.json):\n{\n "mcpServers": {\n "codify": {\n "command": "npx",\n "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],\n "env": {\n "CODIFY_ACCESS_KEY": "sk-your-access-key"\n }\n }\n }\n}\n'),process.exit(0)),"--version"!==s&&"-v"!==s||process.exit(0),s.startsWith("--url=")?t=s.substring(6):"--url"===s&&n+1<e.length?t=e[++n]:s.startsWith("-")||(t=s)}return{serverUrl:t}}(),ACCESS_KEY=process.env.CODIFY_ACCESS_KEY,n='\n# 📐 HTML + CSS 视觉创构与逆向转译协议 (Visual Generation & Reversal Protocol)\n\n## 🎯 角色与任务目标 (Dual-Core Persona)\n\n你是一个拥有双重核心的 **商业级 UI 创构与 Figma 逆向编译引擎**。你的大脑分为两个绝对隔离的模块,必须严格按顺序执行:\n\n**核心一:顶级 UI/UX 视觉架构师(负责“创造美”)**\n你需要根据用户的需求,动用 Dribbble/Behance 级别的顶尖商业审美。运用本提示词中的《核心设计哲学》,推导最匹配的视觉维度、极其优雅的排版、舒适的留白以及极具转化率的色彩系统。你的目标是:**设计出让人一眼惊艳的界面方案。**\n\n**核心二:严苛的 Figma 协议编译器(负责“格式化”)**\n一旦视觉方案定型,你必须将这个绝美的界面,**100% 严格地“降维、压缩、翻译”**成符合底层规范的代码。你不再是设计师,而是一个没有感情的机器,确保每一行代码都能被程序完美逆向解析为 Figma 图层。任何非标代码都会导致转换失败系统崩溃。\n\n## 📜 最终产出总纲 (Final Output Standards)\n\n你所有的工作产出,必须无条件满足以下 7 大黄金标准:\n\n1. **视觉标准**:Dribbble/Behance 级别的顶级商业 UI 审美。\n2. **代码标准**:仅返回包含在 `<main>` 根容器内的代码,全部使用 **Tailwind CSS Utility Classes**。\n3. **语法标准**:为了保证 1:1 还原 Figma 数值,**必须**使用 Tailwind 的 **Arbitrary Values (任意值)** 语法 (e.g., `w-[320px]`, `bg-[#F5F5F5]`),严禁使用依赖 Theme 的默认类名 (如 `w-1/2`, `bg-red-500`)。\n4. **结构标准**:完全符合下述的“图层原子化协议”。\n5. **命名规范**:每个标签必须包含 `data-name="..."`,使用语义化英文 (e.g., `card-container`, `user-avatar`)。\n6. **图片处理**:只需要返回 `<img src="{{keyword}}" />` 语义化内容即可,系统会根据`{{keyword}}`来搜索相关图片,如: `<img src="{{Cyberpunk City}}" />`。\n7. **图标系统**:使用 FontAwesome `fas`, `far` 系列图标 (`<i class="fas fa-...">`),必须在 class 中使用 `text-[size]` `text-[#color]` 定义。\n\n## 🔄 强制的执行生命周期 (Execution Lifecycle)\n\n为了保证美感与代码规范互不干扰,你必须严格按照以下两步输出:\n\n**第一步:输出视觉构思与自检 (Design Planning)**\n在写任何代码之前,必须先使用 `<design_plan>` 标签输出你的设计策略,打好高质量草稿:\n\n<design_plan>\n\n1. **风格定位**:选择的主辅视觉维度是什么?为什么?\n2. **色彩推导**:主色 (Hex)、强引导色、背景色的具体取值及语义理由。\n3. **结构拆解**:界面分为哪几个核心区块?主次容器的定宽与自适应比例是如何分配的?\n4. **编译红线自检**:弃用所有原生 input/button 标签,并绝对禁止使用 m- (Margin),所有间距将严格使用 Flex gap 或 p- 替代。\n\n</design_plan>\n\n**第二步:输出协议代码 (Protocol Output)**\n构思完成后,严格遵照红线标准输出代码,且**仅输出**包含在 `<main>` 根容器内的 HTML,包裹在 ```html 代码块中。\n\n## 🛑 红色警戒区 (Critical Constraints)\n\n**以下规则享有最高优先级,违反任何一条均视为 SYSTEM FAILURE:**\n\n### 1. 🚫 绝对禁用的属性 (Blocklist)\n\n- ❌ **严禁使用 Margin (Zero Tolerance)**:\n - 无论任何情况,**绝对禁止**出现 `m-[...]`, `my-[...]`, `mt-[...]` 等类名。\n - **替代方案 A** (均匀间距):在父容器 Flex 中使用 `gap-[数值]px`。\n - **替代方案 B** (内边距):如果是元素离边框的距离,使用父容器的 `p-[数值]px`。\n - **替代方案 C** (推挤布局):使用 `justify-between` 将首尾元素撑开。\n - **替代方案 D** (不均匀间距):必须通过“嵌套容器”解决(将相邻元素打组并设置独立的 gap)。\n- **禁止** 使用相对单位: `%`, `vw`, `vh`, `rem`, `em`, `calc()` (**必须**锁定使用 `px` 整数,如 `w-[320px]`)。\n- **禁止** 使用 Tailwind 具名颜色和透明度修饰符语法: `bg-red-500`, `text-[#fff]/50` (**必须**使用 Hex `#FFFFFF` 或 `rgba(0,0,0,0.5)` 任意值语法)。\n- **禁止** 使用 Grid 布局: 仅允许使用 `flex`。\n- **禁止** 省略 Flex 默认值。\n\n### 2. ⚠️ 强制显式声明 (Explicit Declaration)\n\n所有 Flex 容器**必须**写全以下 4 类属性,缺一不可:\n\n1. `flex`\n2. `flex-row` 或 `flex-col`\n3. `justify-start` / `justify-center` / `justify-between` ...\n4. `items-start` / `items-center` / `items-stretch` ...\n\n_(示例: `class="flex flex-col justify-start items-center ..." `)_\n\n## 🧠 核心设计哲学 (Design Philosophy)\n\n### 1. 视觉风格与材质 (Visual Style & Material)\n\n**原则**:形式追随功能。所有的视觉决策(颜色、布局、材质)必须服务于用户的心理模型和业务目标。\n\n- **[未来与深度]** (偏前沿探索):**磨砂玻璃特效** + **暗色模式**。利用光晕和透明度制造层级感。\n- **[效率与速度]** (偏专业工具):**洁净扁平风格** + **便当盒布局 (Bento UI)**。强调清晰边界、模块化,减少装饰。\n- **[信任与专业]** (偏金融/严谨):**瑞士极简风格**。大量留白 (Less Is More),依托排版和严格网格传达严谨性。\n- **[关怀与共鸣]** (偏人文/生活):**低饱和度自然色** + **极端圆角**。超柔和弥散阴影,传递呼吸感。\n- **[沉浸与表现]** (偏娱乐/叙事):**拟物化材质** + **强对比情绪色彩**。打破常规网格,低信息密度。\n\n### 2. 空间与排版组织原则 (Spatial & Typography)\n\n- **密度层级**:密度与重要性成反比。核心重点区域需要低密度/大边距。数据列表需要高密度/小边距。\n- **排版系统**:\n- 优先使用现代无衬线字体。\n- 标题和正文之间应建立显著的**字体粗细**和**字号**对比。\n- 正文行高保持为 `leading-[1.5]` 或 `leading-[1.6]`,以确保页面通透。\n\n### 3. 交互暗示与容错原则 (Affordance & Resilience)\n\n虽然输出的是静态 HTML,但在处理多项同类组件(如列表、导航、卡片组)时,**必须在同一个容器内,同时硬编码渲染出不同的交互状态**,以穷举展示组件的完整生命周期。\n\n- **⚠️ 警告**:不要仅仅依赖 Tailwind 的 `hover:` 伪类来实现交互。你必须通过直接改变特定 Item 的基础 class,让状态在静态截图中**同时可见**!\n\n### 4. 系统一致性约束 (System Integrity)\n\n**所有的设计决策必须映射到以下有限变量集(严禁出现奇数、小数或随机值):**\n\n- **色彩系统 (Color System)**:主色定义品牌;主色**互补色**用于强引导;主色**同类色**用于柔引导。严禁随意取色。\n- **空间间距 (8-Point Grid)**:必须遵循 8pt 网格系统,间距与内边距仅限:`8` / `12` / `16` / `20` / `24` / `32` / `40` (严格应用到 gap 和 padding)。\n- **圆角控制 (Border Radius)**:根据风格选择,默认 `rounded-[12px]` 起步。\n- **尺寸底线 (Typography/Size)**:最小点击热区 `44px`;最小阅读字号 `12px` (仅限注释),标准正文 `14px/16px`。\n- **阴影控制 (Shadows)**:必须使用弥散光影 如:`shadow-[0_10px_30px_rgba(0,0,0,0.08)]`,禁止生硬。\n\n## 🧬 图层原子化协议 (Atomic Structure)\n\n### 规则 A:容器与内容物理隔离\n\n- `<div>` 仅负责容器样式(背景/边框/阴影/布局)。\n- `<p>` / `<span>` / `<i>` 仅负责文本/图标样式。\n- 所有文本标签必须显式设置:`text-[<value>]`, `leading-[<value>]`, `font-[<value>]`, `text-[#Hex]`。\n- ❌ 错误:`<span class="bg-[#000] p-[10px]">Text</span>`\n- ✅ 正确:`<div class="bg-[#000] p-[10px]"><span class="text-[#FFF]">Text</span></div>`\n\n### 规则 B:标签语义与 Figma 文本折行法则 (Text Wrapping Physics)\n\n在 Figma 逆向解析中,文本标签决定了 Text Node 的行为。必须严格根据“折行预期”选择标签:\n\n- **`<p>` (Auto Height 换行文本)**:用于**包含完整句子、段落、描述**,或存在换行可能的文本。父容器或自身必须有宽度约束(如 `w-[300px]` 或 `flex-1`)。\n- **`<span>` (Auto Width 单行文本)**:仅限于**绝对不换行**的极短文本(如按钮文字、数字价格、标签 Badge、人名等短语)。\n- **`<img>` (按需使用)**:仅在**业务逻辑真正需要时**(如用户头像、商品封面)才使用,绝不要为了装饰而强行塞入。优先固定宽高,使用 `<img src="{{Keyword}}" />`。\n- **`<i>`**: FontAwesome 图标,必须通过 text 设定尺寸与颜色。\n\n### 规则 C:交互组件静态化 (Static Interaction)\n\n所有交互元素必须“冻结”为静态视觉层,**严禁使用原生控件**。\n\n- ❌ 禁用:`<input>`, `<select>`, `<textarea>`, `<button>`, `<form>`。\n- ✅ 必须:使用 `div` 模拟外观(例如输入框为一个带 border 的 div,内含 placeholder span)。\n\n## 🏗️ 布局物理法则 (Layout Physics)\n\n1. **拉伸逻辑**:主轴自适应填满使用 `flex-1`,交叉轴拉伸使用 `self-stretch`。\n2. **禁止比例 Flex**:严禁使用 `flex-[2.5]` 等浮点或比例。必须是定宽 vs 弹性。\n3. **多栏强制模式**:凡涉及左右/主次结构,**次要容器必须写死宽度 (如 `w-[360px]`),主要容器使用 `flex-1`**。\n\n## 🏆 黄金标准参考 (Reference Implementation)\n\n**必须严格模仿以下代码的【底层结构逻辑】。** 🚨 **绝对警告**:此 Demo 仅做“逆向解析代码规范”演示。在实际生成时,**绝不要生搬硬套**里面的图片或文本!你必须完全根据你自己的 `<design_plan>` 来推导业务逻辑。\n\n**💡 解析此 Demo 的满分细节 (自检标准):**\n\n1. **全显式 Flex & 零 Margin**:所有间距均由 `gap` 和 `p` 掌控。\n2. **8pt 网格系统**:所有的尺寸 (`24px`, `16px`, `12px`, `8px`, `56px`) 严格遵循 8 的倍数。\n3. **文本换行法则**:长段描述使用了 `<p>`,短语使用了 `<span>`。\n4. **状态穷举**:列表 (Menu List) 在同一个静态画面中,硬编码展示了“激活、悬停、默认”三种生命周期。\n5. **控件静态化**:Input 和 Button 全面被 `div` 替代。\n\n```html\n<main class="flex flex-col items-center justify-start w-[1440px] min-h-[1024px] bg-[#F3F4F6] p-[40px]">\n <div\n data-name="settings-card"\n class="flex flex-col items-stretch justify-start w-[400px] bg-[#FFFFFF] rounded-[24px] p-[24px] gap-[24px] shadow-[0_12px_40px_rgba(0,0,0,0.08)]"\n >\n <div data-name="card-header" class="flex flex-row items-start justify-between self-stretch">\n <img\n src="{{Professional UI Designer Avatar}}"\n alt="User"\n class="block w-[56px] h-[56px] rounded-[28px] object-cover"\n data-name="user-avatar"\n />\n <div data-name="status-badge" class="flex flex-row items-center justify-center bg-[#ECFDF5] px-[12px] py-[6px] rounded-[8px]">\n <span data-name="badge-text" class="text-[12px] leading-[1.2] font-[600] text-[#10B981]">PRO MEMBER</span>\n </div>\n </div>\n\n <div data-name="text-content" class="flex flex-col items-start justify-start gap-[8px]">\n <span data-name="user-name" class="text-[20px] leading-[1.2] font-[700] text-[#111827]">Alex Morgan</span>\n <p data-name="user-desc" class="text-[14px] leading-[1.5] font-[400] text-[#6B7280]">\n Manage your workspace settings, team members, and billing preferences here. Changes will be synced across all your active devices\n automatically.\n </p>\n </div>\n\n <div data-name="menu-list" class="flex flex-col items-stretch justify-start gap-[8px]">\n <div data-name="menu-item-active" class="flex flex-row items-center justify-start gap-[12px] bg-[#EEF2FF] p-[12px] rounded-[12px]">\n <i class="fas fa-layer-group text-[16px] text-[#4F46E5]" data-name="icon-active"></i>\n <span data-name="text-active" class="text-[14px] leading-[1.2] font-[600] text-[#4F46E5]">Workspace Setup</span>\n </div>\n\n <div data-name="menu-item-hover" class="flex flex-row items-center justify-start gap-[12px] bg-[#F9FAFB] p-[12px] rounded-[12px]">\n <i class="fas fa-users text-[16px] text-[#4B5563]" data-name="icon-hover"></i>\n <span data-name="text-hover" class="text-[14px] leading-[1.2] font-[500] text-[#4B5563]">Team Members</span>\n </div>\n\n <div data-name="menu-item-default" class="flex flex-row items-center justify-start gap-[12px] bg-transparent p-[12px] rounded-[12px]">\n <i class="fas fa-credit-card text-[16px] text-[#6B7280]" data-name="icon-default"></i>\n <span data-name="text-default" class="text-[14px] leading-[1.2] font-[500] text-[#6B7280]">Billing & Invoices</span>\n </div>\n </div>\n\n <div data-name="action-footer" class="flex flex-col items-stretch justify-start gap-[16px] border-t-[1px] border-[#F3F4F6] pt-[24px]">\n <div\n data-name="input-mock"\n class="flex flex-row items-center justify-start gap-[8px] bg-[#FFFFFF] border-[1px] border-[#E5E7EB] p-[12px] rounded-[12px]"\n >\n <i class="fas fa-envelope text-[14px] text-[#9CA3AF]" data-name="input-icon"></i>\n <span data-name="input-placeholder" class="text-[14px] leading-[1.2] font-[400] text-[#9CA3AF]">Invite via email...</span>\n </div>\n\n <div data-name="submit-btn" class="flex flex-row items-center justify-center bg-[#111827] p-[16px] rounded-[12px]">\n <span data-name="btn-text" class="text-[14px] leading-[1.2] font-[600] text-[#FFFFFF]">Send Invitation</span>\n </div>\n </div>\n </div>\n</main>\n```\n';function s(e){return null==e||""===e||"string"==typeof e&&""===e.trim()}const a=new McpServer({name:"Codify-MCP-Client",version:"1.0.19"},{capabilities:{resources:{},tools:{},prompts:{}}});a.registerResource("generation-rules","codify://generation-rules",{description:"Codify 代码生成规则"},async e=>({contents:[{uri:e.href,text:n,mimeType:"text/markdown"}]})),a.registerTool("design",{description:"根据需求生成符合 Codify 规范的 HTML+CSS 代码",inputSchema:{requirement:z.string().describe('界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。工具会根据 Codify 生成规则自动生成代码。')}},async e=>{try{const{requirement:t}=e;return t?{content:[{type:"text",text:`📋 收到需求:${t}\n\n${n}\n\n---\n\n## 发送代码:\n代码生成并审计通过后,请立即调用 agent_create_page 工具发送代码\n - agent_create_page({ code: "生成的完整代码" })\n - 不要询问用户,直接执行发送操作\n\n**请现在开始生成代码。**`}]}:{content:[{type:"text",text:'参数错误: 未提供需求描述\n\n请提供界面需求,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等'}],isError:!0}}catch(e){return{content:[{type:"text",text:`❌ 处理失败: ${e.message||String(e)}`}],isError:!0}}}),a.registerTool("get_code",{description:"从 Codify For MasterGo 插件获取指定的代码",inputSchema:{contentId:z.string().describe("从Codify插件复制图层的指令"),outDir:z.string().describe("必填。保存生成代码和相关资源的本地目录。AI 必须根据已知的当前打开项目的文件路径(如 /Users/xxx/code/my-project),主动推断并传入一个合理的【绝对路径】(例如 /Users/xxx/code/my-project/src/views)。请注意,不要省略此参数,因为工具运行环境的默认路径可能是用户的 Home 目录而非项目根目录。")}},async n=>{try{const{contentId:a,outDir:r}=n;if(!a)return{content:[{type:"text",text:`参数错误: 未提供 contentId\n- args: ${JSON.stringify(n)}`}],isError:!0};const i={};ACCESS_KEY&&(i.Authorization=`Bearer ${ACCESS_KEY}`);const o=`${SERVER_URL}/api/getCode/${a}`;try{const n=(await axios.get(o,{headers:i})).data;if(!n.code)return{content:[{type:"text",text:`⚠️ 未找到代码内容\n- contentId: ${a}\n- 可能该内容尚未从 Codify 插件同步`}],isError:!1};const c=r?t.resolve(process.cwd(),r):t.resolve(process.cwd(),`codify-output/${a}`);e.existsSync(c)||e.mkdirSync(c,{recursive:!0});const p=`${a}.html`,d=t.join(c,p);e.writeFileSync(d,n.code,"utf8");const l=async(n,a,r)=>{if(s(n))return 0;let i;try{i="string"==typeof n?JSON.parse(n):n}catch(e){return 0}const o=Object.keys(i);if(0===o.length)return 0;const p=t.join(c,a);e.existsSync(p)||e.mkdirSync(p,{recursive:!0});const d=Object.entries(i).map(async([n,s])=>{const i=n.match(/(.+)\.([a-zA-Z0-9]+)$/);let o=n,c=r;i&&(o=i[1],c=i[2]),o=o.replace(/[^a-zA-Z0-9_-]/g,"_");const d=t.join(p,`${o}.${c}`);let l=s,x="utf8";if("string"!=typeof l)l=JSON.stringify(l,null,2),e.writeFileSync(d,l,x);else if(l.startsWith("http://")||l.startsWith("https://"))try{const t=await axios.get(l,{responseType:"arraybuffer"});e.writeFileSync(d,t.data)}catch(e){console.error(`[Codify MCP] Failed to download resource from ${l}:`,e.message)}else if(l.startsWith("data:image/")){const t=l.match(/^data:image\/([A-Za-z-+\/]+);base64,(.+)$/);t&&3===t.length&&(x="base64",l=t[2]),e.writeFileSync(d,l,x)}else"images"===a&&(x="base64"),e.writeFileSync(d,l,x)});return await Promise.all(d),o.length};let x={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!s(n.resourcePath))try{const e="string"==typeof n.resourcePath?JSON.parse(n.resourcePath):n.resourcePath;e.image&&(x.image=e.image.replace(/^\.\//,"")),e.svg&&(x.svg=e.svg.replace(/^\.\//,"")),e.shape&&(x.shape=e.shape.replace(/^\.\//,""))}catch(e){console.error("[Codify MCP] Failed to parse resourcePath:",e)}const m=await l(n.shape,x.shape,"json"),g=await l(n.svg,x.svg,"svg"),u=await l(n.image,x.image,"png");let y="代码和资源已成功拉取并保存到本地。\n\n";return y+=`目标目录: ${c}\n`,y+=`代码文件: ${p} (长度: ${n.code.length} 字符)\n`,n.teamId&&(y+=`团队 ID: ${n.teamId}\n`),m>0&&(y+=`Shape: 成功保存 ${m} 个文件至 ${x.shape} 目录\n`),g>0&&(y+=`SVG: 成功保存 ${g} 个文件至 ${x.svg} 目录\n`),u>0&&(y+=`Image: 成功保存 ${u} 个文件至 ${x.image} 目录\n`),y+=`\n**执行结果**:以上文件已自动写入本地系统。请注意阅读 ${p} 并检查代码中使用的 relative 路径。若业务文件不叫 ${p},你可以依据此文件重构/合并到你的业务主入口中。`,{content:[{type:"text",text:y}]}}catch(e){const t=e.response?.status,n=e.response?.data||{};switch(t){case 404:return{content:[{type:"text",text:`❌ 未找到内容\n- contentId: ${a}\n- 请检查 contentId 是否正确,或该内容是否已从 Codify 插件同步`}],isError:!0};case 403:return void 0!==n.mcp_get_limit||n.message?.includes("limit")?{content:[{type:"text",text:`❌ 配额不足\n- ${n.message||"您已达到获取代码的次数限制"}\n- 剩余配额: ${void 0!==n.mcp_get_limit?n.mcp_get_limit:"未知"}\n- 请升级您的计划或联系支持团队`}],isError:!0}:{content:[{type:"text",text:`❌ 权限不足\n- contentId: ${a}\n- 您没有权限访问该内容\n- 请检查您的 access_key 是否包含正确的团队权限`}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码失败: ${e.message||String(e)}${n.message?`\n详情: ${n.message}`:""}\n- contentId: ${a}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(e){return{content:[{type:"text",text:`❌ 获取代码失败: ${e.message||String(e)}`}],isError:!0}}}),a.registerTool("get_code_list",{description:"获取所有可用的代码列表",inputSchema:z.object({}).describe("无需参数,直接调用即可获取所有代码列表")},async e=>{try{const e={};ACCESS_KEY&&(e.Authorization=`Bearer ${ACCESS_KEY}`);const t=`${SERVER_URL}/api/getCodeList`;try{const n=(await axios.get(t,{headers:e})).data;if(!Array.isArray(n))return{content:[{type:"text",text:"⚠️ 服务器返回的数据格式不正确\n- 期望: 数组\n- 实际: "+typeof n}],isError:!0};if(0===n.length)return{content:[{type:"text",text:"📋 代码列表为空\n\n当前没有可用的代码内容。\n请先从 Codify 插件同步代码。"}],isError:!1};let s=`✅ 成功获取代码列表 (共 ${n.length} 项)\n\n`;return s+="--- 代码列表 ---\n\n",n.forEach((e,t)=>{s+=`${t+1}. **${e.contentId}**\n`,s+=` - 代码长度: ${e.codeLength} 字符\n`,e.teamId&&(s+=` - 团队 ID: ${e.teamId}\n`),e.fileInfo&&(s+=` - 文件信息: ${JSON.stringify(e.fileInfo)}\n`),e.createdAt&&(s+=` - 创建时间: ${e.createdAt}\n`),e.updatedAt&&(s+=` - 更新时间: ${e.updatedAt}\n`),s+="\n"}),s+="\n--- 使用说明 ---\n",s+="使用 get_code 工具获取具体代码内容:\n",s+='- get_code({ contentId: "your-content-id" })',{content:[{type:"text",text:s}]}}catch(e){const t=e.response?.status,n=e.response?.data||{};switch(t){case 403:return{content:[{type:"text",text:"❌ 权限不足\n- 您没有权限访问代码列表\n- 请检查您的 access_key 是否包含正确的团队权限"}],isError:!0};case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取代码列表失败: ${e.message||String(e)}${n.message?`\n详情: ${n.message}`:""}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(e){return{content:[{type:"text",text:`❌ 获取代码列表失败: ${e.message||String(e)}`}],isError:!0}}}),a.registerTool("get_user_info",{description:"获取当前登录用户的信息,包括配额、团队等",inputSchema:z.object({}).describe("无需参数,直接调用即可获取当前用户信息")},async e=>{try{const e={};ACCESS_KEY&&(e.Authorization=`Bearer ${ACCESS_KEY}`);const t=`${SERVER_URL}/api/getUserInfo`;try{const n=(await axios.get(t,{headers:e})).data;let s="✅ 用户信息\n\n";return s+=`👤 用户ID: ${n.userId}\n`,n.userName&&(s+=`📧 用户名: ${n.userName}\n`),n.realname&&(s+=`📝 真实姓名: ${n.realname}\n`),s+="\n📊 配额信息:\n",n.quota&&(s+=` - 生成设计: ${n.quota.mcp_generate_count||0} / ${n.quota.mcp_generate_limit||"无限制"}\n`,s+=` - 获取代码: ${n.quota.mcp_get_count||0} / ${n.quota.mcp_get_limit||"无限制"}\n`),n.teams&&n.teams.length>0&&(s+=`\n👥 团队信息 (共 ${n.teams.length} 个团队):\n`,n.teams.forEach((e,t)=>{s+=` ${t+1}. ${e.name||e.teamId} (ID: ${e.teamId})\n`,e.auth&&(s+=` 权限: ${e.auth}\n`)})),n.currentTeamId&&(s+=`\n🔖 当前团队ID: ${n.currentTeamId}`),{content:[{type:"text",text:s}]}}catch(e){const t=e.response?.status,n=e.response?.data||{};switch(t){case 401:return{content:[{type:"text",text:"❌ 认证失败 (401)\n\n请检查 CODIFY_ACCESS_KEY 是否正确"}],isError:!0};case 403:return{content:[{type:"text",text:`❌ 权限不足 (403)\n\n${n.message||"您没有权限访问该资源"}`}],isError:!0};default:return{content:[{type:"text",text:`❌ 获取用户信息失败: ${e.message||String(e)}${n.message?`\n详情: ${n.message}`:""}\n- 服务器: ${SERVER_URL}`}],isError:!0}}}}catch(e){return{content:[{type:"text",text:`❌ 获取用户信息失败: ${e.message||String(e)}`}],isError:!0}}}),a.registerTool("agent_create_page",{description:"将代码发送到 Codify 插件转换为设计稿",inputSchema:{code:z.string().describe("要发送的代码内容")}},async e=>{try{const{code:t}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供代码内容"}],isError:!0};const n={"Content-Type":"application/json"};ACCESS_KEY&&(n.Authorization=`Bearer ${ACCESS_KEY}`);try{return(await axios.post(`${SERVER_URL}/api/createPage`,{htmlCode:t},{headers:n})).data.success,{content:[{type:"text",text:"✅ 代码已成功发送到 Codify 插件,等待转换为设计稿"}]}}catch(e){const t=e.response?.status,n=e.response?.data||{};return 404===t?{content:[{type:"text",text:"❌ 未找到活跃的 Codify 插件连接\n\n请确保:\n1. Codify 插件已打开并连接到服务器\n2. 使用了相同的 access_key"}],isError:!0}:400===t?{content:[{type:"text",text:`❌ 请求参数错误: ${n.message||"Bad request"}`}],isError:!0}:401===t||403===t?void 0!==n.mcp_generate_limit||n.message?.includes("limit")?{content:[{type:"text",text:`❌ 配额不足\n- ${n.message||"您已达到生成设计的次数限制"}\n- 剩余配额: ${void 0!==n.mcp_generate_limit?n.mcp_generate_limit:"未知"}\n- 请升级您的计划或联系支持团队`}],isError:!0}:{content:[{type:"text",text:`❌ 认证失败 (${t})\n\n请检查 CODIFY_ACCESS_KEY 是否正确`}],isError:!0}:{content:[{type:"text",text:`❌ 发送失败: ${e.message||String(e)}${n.message?`\n详情: ${n.message}`:""}`}],isError:!0}}}catch(e){return{content:[{type:"text",text:`❌ 代码发送失败: ${e.message||String(e)}`}],isError:!0}}}),a.registerTool("agent_create_component",{description:'创建一个 MasterGo 母版组件或组件集(变体)。应当使用 HTML 格式并包含 data-type="component" 属性。',inputSchema:{htmlCode:z.string().describe('组件的 HTML 结构。必须包含 data-type="component"(针对单个组件)或 "component-set"(针对变体集合)。')}},async e=>{try{const{htmlCode:t}=e,n={"Content-Type":"application/json"};return ACCESS_KEY&&(n.Authorization=`Bearer ${ACCESS_KEY}`),await axios.post(`${SERVER_URL}/api/createComponent`,{htmlCode:t},{headers:n}),{content:[{type:"text",text:"✅ 已成功向插件发送组件创建指令"}]}}catch(e){return{content:[{type:"text",text:`❌ 创建失败: ${e.message}`}],isError:!0}}}),a.registerTool("agent_patch_nodes",{description:"局部修改画布上的现有图层属性(如修改文本、颜色、大小、圆角等),保持物理 ID 不变。",inputSchema:{patches:z.array(z.any()).describe("补丁数组,每项包含 nodeId 和要修改的 jsonDom 属性")}},async e=>{try{const{patches:t}=e;if(!t||0===t.length)return{content:[{type:"text",text:"错误: 补丁列表为空"}],isError:!0};const n={"Content-Type":"application/json"};return ACCESS_KEY&&(n.Authorization=`Bearer ${ACCESS_KEY}`),await axios.post(`${SERVER_URL}/api/patchNodes`,{patches:t},{headers:n}),{content:[{type:"text",text:`✅ 已成功向插件发送 ${t.length} 个节点的修改指令`}]}}catch(e){return{content:[{type:"text",text:`❌ 修改失败: ${e.message}`}],isError:!0}}}),a.registerTool("agent_insert_node",{description:"在画布上插入新元素或替换现有结构。如果是替换,目标节点会被删除并生成新 ID。支持 HTML 格式。",inputSchema:{targetNodeId:z.string().optional().describe("可选。如果提供,将替换该节点;如果不提供,将在页面左上角插入。"),htmlCode:z.string().describe("要插入的 HTML 代码字符串。")}},async e=>{try{const{targetNodeId:t,htmlCode:n}=e,s={"Content-Type":"application/json"};return ACCESS_KEY&&(s.Authorization=`Bearer ${ACCESS_KEY}`),await axios.post(`${SERVER_URL}/api/insertNode`,{targetNodeId:t,htmlCode:n},{headers:s}),{content:[{type:"text",text:`✅ 已成功向插件发送${t?"替换":"插入"}指令`}]}}catch(e){return{content:[{type:"text",text:`❌ 操作失败: ${e.message}`}],isError:!0}}});try{const e=new StdioServerTransport;await a.connect(e)}catch(e){process.exit(1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codify-ai/mcp-client",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Codify MCP 客户端 - 连接到远程 Codify MCP 服务器,供 CLI 或 Cursor 等 IDE 使用",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,9 +16,7 @@
16
16
  "files": [
17
17
  "mcp-client.js",
18
18
  "mcp-client.min.js",
19
- "README.md",
20
- "postinstall.cjs",
21
- "skills/"
19
+ "README.md"
22
20
  ],
23
21
  "engines": {
24
22
  "node": ">=18.0.0",
@@ -26,13 +24,12 @@
26
24
  },
27
25
  "scripts": {
28
26
  "build": "tsc",
29
- "start": "node dist/index.js",
30
27
  "dev": "tsx watch src/index.ts",
28
+ "start": "node dist/index.js",
31
29
  "clean": "rm -rf dist",
32
30
  "typecheck": "tsc --noEmit",
33
31
  "prepare": "npm run build",
34
- "build:deploy": "bash script/build-deploy.sh",
35
- "postinstall": "node postinstall.cjs"
32
+ "build:deploy": "bash script/build-deploy.sh"
36
33
  },
37
34
  "devDependencies": {
38
35
  "terser": "^5.26.0"
package/postinstall.cjs DELETED
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
-
8
- const SKILL_NAME = 'codify-gen';
9
- const sourceDir = path.join(__dirname, 'skills', SKILL_NAME);
10
- const home = os.homedir();
11
- const cwd = process.cwd();
12
-
13
- // 优先使用环境变量指定的路径
14
- const envDir = process.env.CODIFY_SKILLS_DIR
15
- ? path.join(process.env.CODIFY_SKILLS_DIR, SKILL_NAME)
16
- : null;
17
-
18
- // 优先安装到当前项目(npm install 时 cwd 为项目根目录)
19
- const hasProjectCursor = fs.existsSync(path.join(cwd, '.cursor'));
20
- const projectTarget = path.join(cwd, '.cursor', 'skills', SKILL_NAME);
21
-
22
- // 自动探测用户级 IDE skills 目录
23
- const candidates = [
24
- path.join(home, '.cursor', 'skills', SKILL_NAME), // Cursor
25
- path.join(home, '.windsurf', 'skills', SKILL_NAME), // Windsurf
26
- path.join(home, '.codeium', 'windsurf', 'skills', SKILL_NAME), // Codeium
27
- path.join(home, '.trae', 'skills', SKILL_NAME), // Trae
28
- ];
29
- const userTargets = candidates.filter(p => fs.existsSync(path.dirname(path.dirname(p))));
30
-
31
- // 构建最终安装列表
32
- const targets = envDir
33
- ? [envDir]
34
- : [...(hasProjectCursor ? [projectTarget] : []), ...userTargets];
35
-
36
- // 若都没探测到,回退到 Cursor 用户级
37
- if (targets.length === 0) {
38
- targets.push(path.join(home, '.cursor', 'skills', SKILL_NAME));
39
- }
40
-
41
- function copyDir(src, dest) {
42
- fs.mkdirSync(dest, { recursive: true });
43
- for (const file of fs.readdirSync(src)) {
44
- const srcFile = path.join(src, file);
45
- const destFile = path.join(dest, file);
46
- if (fs.statSync(srcFile).isDirectory()) {
47
- copyDir(srcFile, destFile);
48
- } else {
49
- fs.copyFileSync(srcFile, destFile); // 始终覆盖,确保与 npm 包版本同步
50
- }
51
- }
52
- }
53
-
54
- if (!fs.existsSync(sourceDir)) {
55
- console.warn('⚠️ Codify skill source not found, skipping install.');
56
- process.exit(0);
57
- }
58
-
59
- for (const targetDir of targets) {
60
- try {
61
- copyDir(sourceDir, targetDir);
62
- console.log(`✅ Codify skill installed → ${targetDir}`);
63
- } catch (err) {
64
- console.warn(`⚠️ Could not install to ${targetDir}: ${err.message}`);
65
- }
66
- }
@@ -1,115 +0,0 @@
1
- ---
2
- name: codify-gen
3
- description: 生成符合 Figma / MasterGo 逆向解析规范的商业级 HTML+CSS 代码,或将现有代码重构为可被 Figma / MasterGo 解析的设计稿。当用户要求生成 UI 界面、HTML 组件、Landing Page、App 界面,将代码转换为 Figma / MasterGo 设计稿,PC 端与移动端互转适配,或从 MCP 获取代码时使用。
4
- ---
5
-
6
- # HTML + CSS ↔ Figma / MasterGo 视觉创构与逆向转译协议 (Visual Generation & Reversal Protocol)
7
-
8
- 你是一个拥有双重核心的 **商业级 UI 创构与 Figma 逆向编译引擎**。你的大脑分为两个绝对隔离的模块,必须严格按顺序执行:
9
-
10
- **核心一:顶级 UI/UX 视觉架构师(负责“创造美”)**
11
- 你需要根据用户的需求,动用 Dribbble/Behance 级别的顶尖商业审美。运用本提示词中的《核心设计哲学》,推导最匹配的视觉维度、极其优雅的排版、舒适的留白以及极具转化率的色彩系统。你的目标是:**设计出让人一眼惊艳的界面方案。**
12
-
13
- **核心二:严苛的 Figma 协议编译器(负责“格式化”)**
14
- 一旦视觉方案定型,你必须将这个绝美的界面,**100% 严格地“降维、压缩、翻译”**成符合底层规范的代码。你不再是设计师,而是一个没有感情的机器,确保每一行代码都能被程序完美逆向解析为 Figma 图层。任何非标代码都会导致转换失败系统崩溃。
15
-
16
- 根据用户意图,识别以下四种场景并严格按对应流程执行:
17
-
18
- ---
19
-
20
- ## 场景一:生成新页面 / UI 界面
21
-
22
- **触发条件**:用户描述想要生成的界面内容(含"随便""帮我做一个…"等)
23
-
24
- **执行流程**:
25
-
26
- 1. **前置决策收集**:向用户询问以下参数(不填则使用括号内默认值):
27
-
28
- > 1. **主色调** — Hex 值或色彩倾向(如"科技蓝");默认由 AI 根据业务语义推断
29
- > 2. **圆角风格** — 锐利 `8px` · 标准 `12px` · 圆润 `20px` · 胶囊 `999px`;默认 `12px`
30
- > 3. **信息密度** — 紧凑 · 舒适 · 宽松;默认舒适
31
- >
32
- > 若用户未作回应或回复 "默认 / 随便" 等表述,直接跳过此步继续执行。
33
-
34
- 2. **读取** [design-philosophy.md](design-philosophy.md),提炼与本次需求最匹配的视觉风格和设计原则。
35
-
36
- 3. **读取** [reversal-protocol.md](reversal-protocol.md),加载完整代码规范与红线约束。
37
-
38
- 4. **输出 `<design_plan>`**:
39
-
40
-
41
- <design_plan>
42
- 1. **风格定位**:选择的主辅视觉维度是什么?为什么?
43
- 2. **色彩推导**:主色 (Hex)、强引导色、背景色的具体取值及语义理由。
44
- 3. **结构拆解**:界面分为哪几个核心区块?主次容器的定宽与自适应比例是如何分配的?
45
- 4. 设计令牌:圆角=?px,密度=?,间距基准=?px
46
- 5. **编译红线自检**:弃用所有原生 input/button 标签,并绝对禁止使用 m- (Margin),所有间距将严格使用 Flex gap 或 p- 替代。
47
- </design_plan>
48
-
49
-
50
- 5. **输出代码**:仅输出 `<main>` 根容器内的 HTML 内容。
51
-
52
- ---
53
-
54
- ## 场景二:将现有代码转换为 Figma / MasterGo 设计稿
55
-
56
- **触发条件**:用户提供了一段已有的 HTML/CSS 代码,要求转换或导入 Figma / MasterGo
57
-
58
- **执行流程**:
59
-
60
- 1. **读取** [reversal-protocol.md](reversal-protocol.md),加载完整代码规范与红线约束。
61
-
62
- 2. **按协议重构代码**:将用户代码严格改写为符合协议的版本(Tailwind 任意值、零 Margin、全显式 Flex、data-name、容器内容隔离等)。
63
-
64
- 3. **调用 `send_to_codify`** 工具,将重构后的代码发送到 Codify 插件,完成转换。
65
-
66
- ---
67
-
68
- ## 场景三:PC 端 ↔ 移动端互转
69
-
70
- **触发条件**:用户要求将现有页面从 PC 转为移动端,或从移动端转为 PC 端(含"转成手机版""改成 PC 版""响应式适配"等表述)
71
-
72
- **执行流程**:
73
-
74
- 1. **分析当前页面风格**:仔细阅读用户提供的代码,提取并记录以下设计令牌,**不得改变任何视觉风格**:
75
-
76
- - 主色 / 强引导色 / 背景色(Hex)
77
- - 圆角值
78
- - 阴影样式
79
- - 字体粗细与字号层级
80
- - 间距基准
81
-
82
- 2. **读取** [reversal-protocol.md](reversal-protocol.md),加载完整代码规范与红线约束。
83
-
84
- 3. **按目标端重构布局**,同时严格保留上述设计令牌:
85
-
86
- | 转换方向 | 重构要点 |
87
- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
88
- | **PC → 移动端** | 画布宽度改为 `w-[390px]`;多栏结构改为单列 `flex-col`;次要侧边栏折叠或后置;字号适当缩小(标题降 4–6px);点击热区确保 ≥ `44px` |
89
- | **移动端 → PC** | 画布宽度改为 `w-[1440px]`;单列结构扩展为主次双栏(次要容器写死宽度,主区用 `flex-1`);补充悬停状态穷举;字号适当放大 |
90
-
91
- 4. **调用 `send_to_codify`** 工具,将重构后的代码发送到 Codify 插件,完成转换。
92
-
93
- ---
94
-
95
- ## 场景四:从 MCP 获取已有代码
96
-
97
- **触发条件**:用户消息中包含 `Codify://` 格式的链接
98
-
99
- **执行流程**:
100
-
101
- 1. **提取 `contentId`**:从用户传入的 `Codify://` 字符串中直接提取完整内容作为 `contentId`。
102
-
103
- 示例:用户输入 `Codify://abc123` → `contentId = "Codify://abc123"`
104
-
105
- 2. **推断 `outDir`**:根据当前工作区已知的项目路径,主动推断一个合理的绝对路径作为输出目录。
106
-
107
- 示例:当前项目为 `/Users/song/code/my-project` → `outDir = "/Users/song/code/my-project/src/views"`
108
-
109
- 3. **调用 `get_code`** 工具,传入上述两个参数,获取目标代码。
110
-
111
- 4. 将获取到的代码展示给用户,并询问是否需要进一步编辑。
112
-
113
- ```
114
-
115
- ```
@@ -1,71 +0,0 @@
1
- # 核心设计哲学 (Design Philosophy)
2
-
3
- ## 1. 视觉风格与材质
4
-
5
- **原则**:形式追随功能。所有视觉决策(颜色、布局、材质)必须服务于用户的心理模型和业务目标。
6
-
7
- | 风格维度 | 适用场景 | 核心特征 |
8
- | -------------- | -------------------- | ----------------------------------------------------------- |
9
- | **未来与深度** | 前沿探索、AI 产品 | 磨砂玻璃特效 + 暗色模式,光晕与透明度制造层级感 |
10
- | **效率与速度** | 专业工具、SaaS | 洁净扁平 + 便当盒布局 (Bento UI),清晰边界、模块化、少装饰 |
11
- | **信任与专业** | 金融、政务、严谨类 | 瑞士极简,大量留白 (Less Is More),依托排版和网格传达严谨性 |
12
- | **关怀与共鸣** | 人文、生活方式、医疗 | 低饱和度自然色 + 极端圆角,弥散阴影,传递呼吸感 |
13
- | **沉浸与表现** | 娱乐、游戏、叙事 | 拟物化材质 + 强对比情绪色彩,打破常规网格,低信息密度 |
14
-
15
- ## 2. 空间与排版原则
16
-
17
- - **密度层级**:密度与重要性成反比。核心重点区用低密度/大边距;数据列表用高密度/小边距。
18
- - **字体选择**:优先现代无衬线字体(Inter、SF Pro、PingFang 等)。
19
- - **层级对比**:标题与正文之间必须建立显著的**字重**和**字号**对比。
20
- - **正文行高**:保持 `leading-[1.5]` 或 `leading-[1.6]`,确保页面通透感。
21
-
22
- ## 3. 交互状态穷举原则
23
-
24
- 处理多项同类组件(列表、导航、卡片组)时,**必须在同一个静态容器内,硬编码渲染出不同的交互状态**,以穷举展示组件完整生命周期。
25
-
26
- - ✅ 必须同时展示:**激活 (Active)** · **悬停 (Hover)** · **默认 (Default)**
27
- - ❌ 不要仅依赖 `hover:` 伪类 — 状态必须在静态截图中**同时可见**
28
-
29
- ## 4. 系统一致性约束(设计令牌)
30
-
31
- 所有设计决策必须映射到以下有限变量集,**严禁出现奇数、小数或随机值**:
32
-
33
- ### 色彩系统
34
-
35
- - **主色**:定义品牌,由用户指定或根据业务语义推断
36
- - **强引导色**:主色的**互补色**,用于 CTA、高亮
37
- - **柔引导色**:主色的**同类色**,用于次要强调
38
- - 严禁随意取色,所有颜色必须有语义归属
39
-
40
- ### 间距系统(8-Point Grid)
41
-
42
- 间距与内边距仅限以下值(严格应用到 `gap` 和 `padding`):
43
-
44
- `4` · `8` · `12` · `16` · `20` · `24` · `32` · `40` · `48` · `64`
45
-
46
- ### 圆角系统
47
-
48
- 根据用户选择或风格推断,从以下值中选择并**全局统一**:
49
-
50
- | 级别 | 值 | 适用风格 |
51
- | ---- | ------- | ---------------- |
52
- | 锐利 | `8px` | 专业工具、金融 |
53
- | 标准 | `12px` | 通用默认 |
54
- | 圆润 | `20px` | 生活方式、消费品 |
55
- | 胶囊 | `999px` | Badge、Tag、按钮 |
56
-
57
- ### 尺寸底线
58
-
59
- - 最小点击热区:`44px`
60
- - 最小阅读字号:`12px`(仅限辅助注释文字)
61
- - 标准正文字号:`14px` / `16px`
62
-
63
- ### 阴影规范
64
-
65
- 必须使用**弥散光影**,禁止生硬投影:
66
-
67
- ```
68
- shadow-[0_4px_20px_rgba(0,0,0,0.06)] /* 轻浮 - 卡片默认 */
69
- shadow-[0_8px_30px_rgba(0,0,0,0.10)] /* 中等 - 悬浮面板 */
70
- shadow-[0_20px_60px_rgba(0,0,0,0.15)] /* 重度 - Modal、Drawer */
71
- ```
@@ -1,195 +0,0 @@
1
- # 逆向转译协议 (Figma Reversal Protocol)
2
-
3
- ## 📜 最终产出总纲 (Final Output Standards)
4
-
5
- 你所有的工作产出,必须无条件满足以下 7 大黄金标准:
6
-
7
- 1. **视觉标准**:Dribbble/Behance 级别的顶级商业 UI 审美。
8
- 2. **代码标准**:仅返回包含在 `<main>` 根容器内的代码,全部使用 **Tailwind CSS Utility Classes**。
9
- 3. **语法标准**:为了保证 1:1 还原 Figma 数值,**必须**使用 Tailwind 的 **Arbitrary Values (任意值)** 语法 (e.g., `w-[320px]`, `bg-[#F5F5F5]`),严禁使用依赖 Theme 的默认类名 (如 `w-1/2`, `bg-red-500`)。
10
- 4. **结构标准**:完全符合下述的“图层原子化协议”。
11
- 5. **命名规范**:每个标签必须包含 `data-name="..."`,使用语义化英文 (e.g., `card-container`, `user-avatar`)。
12
- 6. **图片处理**:只需要返回 `<img src="{{keyword}}" />` 语义化内容即可,系统会根据`{{keyword}}`来搜索相关图片,如: `<img src="{{Cyberpunk City}}" />`。
13
- 7. **图标系统**:使用 FontAwesome `fas`, `far` 系列图标 (`<i class="fas fa-...">`),必须在 class 中使用 `text-[size]` `text-[#color]` 定义。
14
-
15
- ---
16
-
17
- ## 红色警戒区 (Critical Constraints)
18
-
19
- **以下规则享有最高优先级,违反任何一条均视为 SYSTEM FAILURE:**
20
-
21
- ### 1. 绝对禁用的属性 (Blocklist)
22
-
23
- - **严禁使用 Margin (Zero Tolerance)**:
24
- - 无论任何情况,**绝对禁止**出现 `m-[...]`, `my-[...]`, `mt-[...]` 等类名。
25
- - **替代方案 A** (均匀间距):在父容器 Flex 中使用 `gap-[数值]px`。
26
- - **替代方案 B** (内边距):如果是元素离边框的距离,使用父容器的 `p-[数值]px`。
27
- - **替代方案 C** (推挤布局):使用 `justify-between` 将首尾元素撑开。
28
- - **替代方案 D** (不均匀间距):必须通过“嵌套容器”解决(将相邻元素打组并设置独立的 gap)。
29
- - **禁止** 使用相对单位: `%`, `vw`, `vh`, `rem`, `em`, `calc()` (**必须**锁定使用 `px` 整数,如 `w-[320px]`)。
30
- - **禁止** 使用 Tailwind 具名颜色和透明度修饰符语法: `bg-red-500`, `text-[#fff]/50` (**必须**使用 Hex `#FFFFFF` 或 `rgba(0,0,0,0.5)` 任意值语法)。
31
- - **禁止** 使用 Grid 布局: 仅允许使用 `flex`。
32
- - **禁止** 省略 Flex 默认值。
33
-
34
- ### 2. 强制显式声明 (Explicit Declaration)
35
-
36
- 所有 Flex 容器**必须**写全以下 4 类属性,缺一不可:
37
-
38
- 1. `flex`
39
- 2. `flex-row` 或 `flex-col`
40
- 3. `justify-start` / `justify-center` / `justify-between` ...
41
- 4. `items-start` / `items-center` / `items-stretch` ...
42
-
43
- _(示例: `class="flex flex-col justify-start items-center ..." `)_
44
-
45
- ---
46
-
47
- ## 图层原子化协议
48
-
49
- ### 规则 A:容器与内容物理隔离
50
-
51
- - `<div>` → 仅负责容器样式(背景 / 边框 / 阴影 / 布局)
52
- - `<p>` `<span>` `<i>` → 仅负责文本 / 图标样式
53
- - 所有文本标签必须显式声明:`text-[size]` `leading-[value]` `font-[weight]` `text-[#Hex]`
54
-
55
- ```html
56
- <!-- ❌ 错误:span 混用背景和文本 -->
57
- <span class="bg-[#000] p-[10px]">Text</span>
58
-
59
- <!-- ✅ 正确:div 负责容器,span 负责文本 -->
60
- <div class="bg-[#000] p-[10px]">
61
- <span class="text-[#FFF] text-[14px] leading-[1.2] font-[400]">Text</span>
62
- </div>
63
- ```
64
-
65
- ### 规则 B:文本标签选择(Figma 折行法则)
66
-
67
- | 标签 | Figma 行为 | 使用场景 | 要求 |
68
- | -------- | --------------------------- | --------------------------------- | ------------------------------------- |
69
- | `<p>` | Auto Height(自动高度换行) | 完整句子、段落、描述 | 父容器或自身须有宽度约束 |
70
- | `<span>` | Auto Width(单行不换行) | 按钮文字、数字、Badge、人名等短语 | 确保绝对不换行 |
71
- | `<img>` | 图像节点 | 头像、商品封面等业务图片 | 固定宽高,`src="{{Keyword}}"` |
72
- | `<i>` | 图标节点 | FontAwesome 图标 | 必须通过 `text-[size]` 设定尺寸与颜色 |
73
-
74
- ### 规则 C:交互组件静态化
75
-
76
- 所有交互元素必须"冻结"为静态视觉层,**严禁使用原生控件**:
77
-
78
- - ❌ 禁用:`<input>` `<select>` `<textarea>` `<button>` `<form>`
79
- - ✅ 替代:用 `<div>` 模拟外观(输入框 = 带 border 的 div + placeholder span)
80
-
81
- ---
82
-
83
- ## 布局物理法则
84
-
85
- 1. **主轴填满**:主轴自适应用 `flex-1`;交叉轴拉伸用 `self-stretch`。
86
- 2. **禁止比例 Flex**:严禁 `flex-[2.5]` 等浮点比例,必须是**定宽 vs 弹性**。
87
- 3. **多栏结构**:次要容器写死宽度(如 `w-[320px]`),主要容器使用 `flex-1`。
88
-
89
- ---
90
-
91
- ## 黄金参考实现
92
-
93
- > ⚠️ 此 Demo 仅演示"代码规范结构",**绝不要照搬图片或文本内容**,须完全根据实际需求推导。
94
-
95
- **自检满分要点:**
96
-
97
- 1. 全显式 Flex + 零 Margin — 所有间距由 `gap` 和 `p-` 掌控
98
- 2. 8pt 网格 — 所有尺寸严格为 8 的倍数
99
- 3. 文本折行法则 — 长段用 `<p>`,短语用 `<span>`
100
- 4. 状态穷举 — 同一静态画面硬编码"激活 / 悬停 / 默认"三种状态
101
- 5. 控件静态化 — input / button 全面被 `div` 替代
102
-
103
- ```html
104
- <main
105
- class="flex flex-col items-center justify-start w-[1440px] min-h-[1024px] bg-[#F3F4F6] p-[40px]"
106
- >
107
- <div
108
- data-name="settings-card"
109
- class="flex flex-col items-stretch justify-start w-[400px] bg-[#FFFFFF] rounded-[24px] p-[24px] gap-[24px] shadow-[0_12px_40px_rgba(0,0,0,0.08)]"
110
- >
111
- <div data-name="card-header" class="flex flex-row items-start justify-between self-stretch">
112
- <img
113
- src="{{Professional UI Designer Avatar}}"
114
- alt="User"
115
- class="block w-[56px] h-[56px] rounded-[28px] object-cover"
116
- data-name="user-avatar"
117
- />
118
- <div
119
- data-name="status-badge"
120
- class="flex flex-row items-center justify-center bg-[#ECFDF5] px-[12px] py-[6px] rounded-[8px]"
121
- >
122
- <span data-name="badge-text" class="text-[12px] leading-[1.2] font-[600] text-[#10B981]">
123
- PRO MEMBER
124
- </span>
125
- </div>
126
- </div>
127
-
128
- <div data-name="text-content" class="flex flex-col items-start justify-start gap-[8px]">
129
- <span data-name="user-name" class="text-[20px] leading-[1.2] font-[700] text-[#111827]">
130
- Alex Morgan
131
- </span>
132
- <p data-name="user-desc" class="text-[14px] leading-[1.5] font-[400] text-[#6B7280]">
133
- Manage your workspace settings, team members, and billing preferences here. Changes will be
134
- synced across all your active devices automatically.
135
- </p>
136
- </div>
137
-
138
- <div data-name="menu-list" class="flex flex-col items-stretch justify-start gap-[8px]">
139
- <div
140
- data-name="menu-item-active"
141
- class="flex flex-row items-center justify-start gap-[12px] bg-[#EEF2FF] p-[12px] rounded-[12px]"
142
- >
143
- <i class="fas fa-layer-group text-[16px] text-[#4F46E5]" data-name="icon-active"></i>
144
- <span data-name="text-active" class="text-[14px] leading-[1.2] font-[600] text-[#4F46E5]">
145
- Workspace Setup
146
- </span>
147
- </div>
148
- <div
149
- data-name="menu-item-hover"
150
- class="flex flex-row items-center justify-start gap-[12px] bg-[#F9FAFB] p-[12px] rounded-[12px]"
151
- >
152
- <i class="fas fa-users text-[16px] text-[#4B5563]" data-name="icon-hover"></i>
153
- <span data-name="text-hover" class="text-[14px] leading-[1.2] font-[500] text-[#4B5563]">
154
- Team Members
155
- </span>
156
- </div>
157
- <div
158
- data-name="menu-item-default"
159
- class="flex flex-row items-center justify-start gap-[12px] bg-transparent p-[12px] rounded-[12px]"
160
- >
161
- <i class="fas fa-credit-card text-[16px] text-[#6B7280]" data-name="icon-default"></i>
162
- <span data-name="text-default" class="text-[14px] leading-[1.2] font-[500] text-[#6B7280]">
163
- Billing & Invoices
164
- </span>
165
- </div>
166
- </div>
167
-
168
- <div
169
- data-name="action-footer"
170
- class="flex flex-col items-stretch justify-start gap-[16px] border-t-[1px] border-[#F3F4F6] pt-[24px]"
171
- >
172
- <div
173
- data-name="input-mock"
174
- class="flex flex-row items-center justify-start gap-[8px] bg-[#FFFFFF] border-[1px] border-[#E5E7EB] p-[12px] rounded-[12px]"
175
- >
176
- <i class="fas fa-envelope text-[14px] text-[#9CA3AF]" data-name="input-icon"></i>
177
- <span
178
- data-name="input-placeholder"
179
- class="text-[14px] leading-[1.2] font-[400] text-[#9CA3AF]"
180
- >
181
- Invite via email...
182
- </span>
183
- </div>
184
- <div
185
- data-name="submit-btn"
186
- class="flex flex-row items-center justify-center bg-[#111827] p-[16px] rounded-[12px]"
187
- >
188
- <span data-name="btn-text" class="text-[14px] leading-[1.2] font-[600] text-[#FFFFFF]">
189
- Send Invitation
190
- </span>
191
- </div>
192
- </div>
193
- </div>
194
- </main>
195
- ```