@codify-ai/mcp-client 1.0.25 → 1.0.28

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
@@ -2,15 +2,15 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-1.0.28-blue.svg)
6
6
  ![License](https://img.shields.io/badge/license-MIT-green.svg)
7
7
  ![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)
8
8
 
9
- **Codify MCP 客户端 - 连接 FigmaCursor 的桥梁**
9
+ **Codify MCP 客户端 - 连接 MasterGoIDE / CLI 的桥梁**
10
10
 
11
11
  通过 MCP 协议从远程 Codify 服务器获取代码
12
12
 
13
- [快速开始](#快速开始) · [配置说明](#配置说明) · [功能特性](#功能特性)
13
+ [快速开始](#快速开始) · [功能特性](#功能特性) · [可用工具](#可用工具) · [Codify 仪表盘](https://codify.fun)
14
14
 
15
15
  </div>
16
16
 
@@ -21,33 +21,27 @@
21
21
  - 🔌 **一键安装**:使用 `npx` 无需手动下载
22
22
  - 📡 **MCP 协议**:完美集成 Cursor 等支持 MCP 的 IDE
23
23
  - 🔐 **安全认证**:支持 access_key 认证
24
- - 🛠️ **工具集成**:提供 `getCode` 和 `agent_create_page` 工具
25
- - 📦 **资源访问**:通过 URI `Codify://getCode/{content_id}` 访问代码
24
+ - 🛠️ **全能工具箱**:包含代码获取、设计稿生成、双向同步等 10+ 个专业工具
25
+ - 📦 **资源访问**:通过 URI `Codify://get_code/{content_id}` 访问代码
26
26
 
27
- ## 架构说明
28
27
 
29
- ```
30
- Figma Plugin (WebSocket)
31
-
32
- Codify MCP Server (远程)
33
-
34
- @codify-ai/mcp-client (本包)
35
-
36
- Cursor IDE (本地)
37
- ```
38
28
 
39
29
  ## 快速开始
40
30
 
41
- ### 1. 配置 Cursor
31
+ ### 1. 配置 MCP
42
32
 
43
- 编辑 `~/.cursor/mcp.json`(如果文件不存在则创建):
33
+ 例如:编辑 `~/.cursor/mcp.json`(如果文件不存在则创建):
44
34
 
45
35
  ```json
46
36
  {
47
37
  "mcpServers": {
48
38
  "codify": {
49
39
  "command": "npx",
50
- "args": ["-y", "@codify-ai/mcp-client", "--url=https://mcp.codify-api.com"],
40
+ "args": [
41
+ "-y",
42
+ "@codify-ai/mcp-client",
43
+ "--url=https://mcp.codify-api.com"
44
+ ],
51
45
  "env": {
52
46
  "CODIFY_ACCESS_KEY": "sk-your-access-key"
53
47
  }
@@ -56,126 +50,121 @@ Figma Plugin (WebSocket)
56
50
  }
57
51
  ```
58
52
 
59
- ### 2. 重启 Cursor
53
+ ### 2. 重启IDE / CLI
60
54
 
61
- 重启 Cursor 或重新加载 MCP 服务器配置。
55
+ 重启Cursor、VSCode、Trae IDE 或者 CLI 工具,或重新加载 MCP 服务器配置。
62
56
 
63
57
  ### 3. 开始使用
64
58
 
65
- Cursor 中,您可以:
66
-
67
- #### 方式 1:使用 AI 工具调用
59
+ IDE / CLI 中,您可以:
68
60
 
69
- 对 AI 说:
70
61
 
71
- ```
72
- 请使用 getCode 工具获取 code_id 的代码
73
- ```
62
+ ### 方式 1:将设计稿给其他人(研发)拉取代码到本地
74
63
 
75
- #### 方式 2:通过资源 URI 访问
64
+ Codify 插件中选中一个元素,复制的指令然后给到其他人来获取代码。
76
65
 
77
- 在代码或对话中引用:
66
+ 无需启动 MasterGo,直接在 IDE 中输入该指令即可获取代码。
78
67
 
79
68
  ```
80
- @codify://getCode/code_id
69
+ codify://getCode/{contentId}
81
70
  ```
82
71
 
83
- ## 配置说明
84
72
 
85
- ### 命令行参数
86
73
 
87
- | 参数 | 说明 | 默认值 |
88
- | ----------------- | ----------------- | ---------------------------- |
89
- | `--url=<URL>` | Codify 服务器地址 | `https://mcp.codify-api.com` |
90
- | `--help`, `-h` | 显示帮助信息 | - |
91
- | `--version`, `-v` | 显示版本号 | - |
74
+ #### 方式 2:使用 AI 工具进行设计稿与代码的双向交互
92
75
 
93
- ### 环境变量
76
+ 直接在对话框输入指令,AI 会自动识别并调用工具:
94
77
 
95
- | 变量名 | 说明 | 必需 |
96
- | ------------------- | ----------------------------- | ---- |
97
- | `CODIFY_SERVER_URL` | 服务器地址(可被 --url 覆盖) | 否 |
98
- | `CODIFY_ACCESS_KEY` | 访问密钥 | 是\* |
78
+ - "获取 MasterGo 中选中的图层代码" (**get_selection_code**)
79
+ - "使用Codify **design** 工具设计一个深色模式的统计仪表盘" (**design**)
80
+ - "修改设计稿选中的图层,改为红色背景" (**agent_update_node**)
81
+ - "将我选中的图层删除" (**agent_remove_node**)
82
+ - "将我选中的图层,同步到本地代码" (**agent_sync_design**)
83
+ - "对比本地代码与设计稿的差异" (**get_design_diff**)
99
84
 
100
- \*如果服务器启用了认证则必需
85
+ #### 方式 3:引用核心规范资源
101
86
 
102
- ## 使用示例
87
+ 在对话中通过或直接输入 URI 引用:
103
88
 
104
- ### 示例 1:连接远程服务器(带认证)
89
+ - `codify://generation-rules`
90
+ - 逆向转译与代码规范:
91
+ 你需要让 LLM 严格遵守转译代码规范才能完美的将它转译为 MasterGo 设计稿。当你需要将你已有的前端项目转换为设计稿的时候,必须让 LLM 读取此文档。
92
+
93
+ - `codify://design-philosophy`
94
+ - 核心设计哲学:
95
+ Codify为你总结了一套非常实用的UI设计哲学,它能够让LLM在生成页面的时候,保持优秀的设计审美。
96
+
105
97
 
106
- ```json
107
- {
108
- "mcpServers": {
109
- "codify": {
110
- "command": "npx",
111
- "args": ["-y", "@codify/mcp-client", "--url=https://codify.example.com"],
112
- "env": {
113
- "CODIFY_ACCESS_KEY": "sk-abc123xyz789"
114
- }
115
- }
116
- }
117
- }
118
- ```
119
98
 
120
- ### 示例 2:使用环境变量
121
99
 
122
- ```json
123
- {
124
- "mcpServers": {
125
- "codify": {
126
- "command": "npx",
127
- "args": ["-y", "@codify-api/mcp-client"],
128
- "env": {
129
- "CODIFY_SERVER_URL": "https://codify.example.com",
130
- "CODIFY_ACCESS_KEY": "sk-abc123xyz789"
131
- }
132
- }
133
- }
134
- }
135
- ```
136
100
 
137
101
  ## 可用工具
138
102
 
139
- ### getCode
103
+ ### 🔍 获取与比对
140
104
 
141
- 获取指定频道的代码。
105
+ #### **get_selection_code**
106
+ 获取 MasterGo 中当前选中图层(或指定图层)的代码。支持根节点完整保存,以及子节点自动合并到本地基准 HTML 文件。
107
+ - **参数**: `projectDir` (必填), `targetNodeId` (可选), `syncToBase` (可选, 默认 true)
108
+ - **示例**: "获取当前选中图层的代码"
142
109
 
143
- **参数:**
110
+ #### **get_design_diff**
111
+ 获取最新设计稿代码与本地 `.codify` 目录中旧代码的差异 (Diff)。用于全量或局部同步。
112
+ - **参数**: `projectDir` (必填), `targetNodeId` (可选), `filePath` (可选)
113
+ - **示例**: "对比最新设计稿,看看有哪些改动"
144
114
 
145
- - `content_id` (string, 必需) - 频道 ID
115
+ #### **get_code_list**
116
+ 列出所有可用的代码列表。
117
+ - **参数**: 无
118
+ - **示例**: "查看我可以拉取的代码列表"
146
119
 
147
- **示例:**
120
+ #### **get_code**
121
+ 通过 `contentId` 获取特定代码并保存到本地。
122
+ - **参数**: `contentId` (必填), `projectDir` (必填), `outDir` (必填), `documentId` (可选)
123
+ - **示例**: "从频道 demo-123 获取代码"
148
124
 
149
- ```
150
- AI,请使用 getCode 工具获取 demo-channel 的代码
151
- ```
125
+ ### 🎨 生成与设计
152
126
 
153
- ### getCodeList
127
+ #### **design**
128
+ 根据自然语言需求生成符合 Codify 规范的 HTML+CSS 代码。
129
+ - **参数**: `requirement` (必填)
130
+ - **示例**: "帮我设计一个深色模式的登录页面"
154
131
 
155
- 列出所有可用频道。
132
+ #### **agent_create_page**
133
+ 将生成的 HTML 代码发送到 MasterGo 插件并创建新页面。
134
+ - **参数**: `code` (可选), `filePath` (可选), `projectDir` (必填), `saveCodeToLocal` (可选)
135
+ - **示例**: "在 MasterGo 中创建一个新页面,代码在 ./temp.html"
156
136
 
157
- **参数:**
137
+ #### **agent_create_component**
138
+ 创建一个 MasterGo 母版组件或组件集(变体)。
139
+ - **参数**: `code` (必填)
140
+ - **示例**: "将这段 HTML 创建为 MasterGo 组件"
158
141
 
159
- **示例:**
142
+ ### 🛠️ 操作与更新
160
143
 
161
- ```
162
- AI,请使用 getCodeList 工具查看所有频道
163
- ```
144
+ #### **agent_update_node**
145
+ 将修改后的 HTML 代码发回 MasterGo 进行局部更新。**注意:必须包含 `data-node-id`。**
146
+ - **参数**: `code` (必填), `targetNodeId` (可选), `documentId` (可选)
147
+ - **示例**: "更新选中的按钮颜色为蓝色"
164
148
 
165
- ## 可用资源
149
+ #### **agent_sync_design**
150
+ 将本地完整的静态 HTML 文件内容全量同步覆盖到 MasterGo 画布。
151
+ - **参数**: `filePath` (必填), `targetNodeId` (必填, 根节点 ID), `documentId` (可选)
152
+ - **示例**: "同步本地 index.html 到设计稿根节点"
166
153
 
167
- 通过资源 URI 直接访问代码:
154
+ #### **agent_remove_node**
155
+ 在 MasterGo 中删除指定的或当前选中的图层。
156
+ - **参数**: `targetNodeId` (可选), `documentId` (可选)
157
+ - **示例**: "删除选中的图层"
168
158
 
169
- ```
170
- codify://{content_id}/current
171
- ```
172
159
 
173
- **示例:**
174
160
 
175
- ```
176
- codify://getCode/code_123
177
- codify://getCode/code_456
178
- ```
161
+ ### 👤 系统信息
162
+
163
+ #### **get_user_info**
164
+ 查看当前登录用户信息、团队信息以及配额使用情况。
165
+ - **参数**: 无
166
+ - **示例**: "查看我的配额还剩多少"
167
+
179
168
 
180
169
  ## 许可证
181
170
 
package/dist/index.js CHANGED
@@ -25,8 +25,8 @@ const CODIFY_OUTPUT_DIR = ".codify-output";
25
25
  const isEmpty = (value) => value === null || value === void 0 || value === "" || typeof value === "string" && value.trim() === "";
26
26
  function buildSuccessText(result, actionName = "操作") {
27
27
  let responseText = `✅ ${actionName}已成功完成`;
28
- if (result.rootNodeId) responseText += `
29
- - 根节点 ID: ${result.rootNodeId}`;
28
+ if (result.targetNodeId) responseText += `
29
+ - 节点 ID: ${result.targetNodeId}`;
30
30
  if (result.documentId) responseText += `
31
31
  - 文档: ${result.documentName || ""} (${result.documentId})`;
32
32
  if (result.documentPageId) responseText += `
@@ -112,7 +112,7 @@ async function saveCodeAndResources({
112
112
  documentId,
113
113
  documentPageId,
114
114
  contentId,
115
- nodeId,
115
+ targetNodeId,
116
116
  nodeName,
117
117
  code,
118
118
  resourcePath,
@@ -121,11 +121,11 @@ async function saveCodeAndResources({
121
121
  image,
122
122
  isSelection = false
123
123
  }) {
124
- let targetDir = outDir ? path.resolve(baseDir, outDir) : documentId && documentPageId ? path.join(baseDir, CODIFY_DOC_DIR, String(documentId).replace(/:/g, "-"), String(documentPageId).replace(/:/g, "-"), contentId && !isSelection && !nodeId ? "code" : "") : isSelection ? path.join(baseDir, CODIFY_OUTPUT_DIR, "selection") : path.join(baseDir, `${CODIFY_OUTPUT_DIR}${contentId ? "/" + contentId : ""}`);
124
+ let targetDir = outDir ? path.resolve(baseDir, outDir) : documentId && documentPageId ? path.join(baseDir, CODIFY_DOC_DIR, String(documentId).replace(/:/g, "-"), String(documentPageId).replace(/:/g, "-"), contentId && !isSelection && !targetNodeId ? "code" : "") : isSelection ? path.join(baseDir, CODIFY_OUTPUT_DIR, "selection") : path.join(baseDir, `${CODIFY_OUTPUT_DIR}${contentId ? "/" + contentId : ""}`);
125
125
  if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
126
- const safeId = String(nodeId || Date.now()).replace(/:/g, "-");
126
+ const safeId = String(targetNodeId || Date.now()).replace(/:/g, "-");
127
127
  const safeName = String(nodeName || (isSelection ? "selection" : "node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, "-");
128
- const htmlFileName = isSelection || nodeId || nodeName ? `${safeName}-${safeId}.html` : `${contentId || "index"}.html`;
128
+ const htmlFileName = isSelection || targetNodeId || nodeName ? `${safeName}-${safeId}.html` : `${contentId || "index"}.html`;
129
129
  const htmlPath = path.join(targetDir, htmlFileName);
130
130
  if (code) {
131
131
  await fs.writeFile(htmlPath, code, "utf8");
@@ -138,11 +138,83 @@ async function saveCodeAndResources({
138
138
  ]);
139
139
  return { targetDir, htmlFileName, htmlPath, shapeCount: stats[0], svgCount: stats[1], imageCount: stats[2], resourcePathMap: resPathMap };
140
140
  }
141
+ const zh = {
142
+ createPage: {
143
+ description: `将代码发送到 Codify 插件转换为设计稿。
144
+ 【极致性能要求】若纯 HTML 已保存为本地文件,你必须且只能通过 filePath 传入该文件的绝对路径。严禁使用 Read 工具读取文件内容,严禁将大段代码写入 code 参数。工具底层会自动读取并传输,以节省 Token。`,
145
+ code: "【可选】要发送的 HTML 代码内容。仅当代码是临时生成且未保存为文件时使用。大段代码严禁使用此参数。",
146
+ filePath: "【可选】本地 HTML 文件的绝对路径。若文件已存在本地,必须传此参数,工具会自动读取并执行落盘。",
147
+ projectDir: "【必填】用户当前工作区的根目录绝对路径",
148
+ saveCodeToLocal: "是否将插件返回的渲染结果保存到本地 .codify 目录(落盘机制)"
149
+ },
150
+ updateNode: {
151
+ description: "将修改后的 HTML 代码发回 MasterGo 画布进行【局部修改】(如改颜色、加文字、改间距等)。⚠️ 【调用前置条件】:你必须且只能在通过 getSelectionCode 拿到该节点的最新代码后进行调用修改!完成代码修改后直接调用此类发送给插件。绝对不要去同步或修改本地基准文件!注意:仅传包含 data-node-id 的那一段 HTML 片段。",
152
+ documentId: "当前 MasterGo 文档 ID。",
153
+ documentPageId: "当前 MasterGo 页面 ID。",
154
+ targetNodeId: "【选填】目标图层 ID (例如 123:456)。如果不传,则默认更新 MasterGo 中当前选中的图层。",
155
+ code: "【必填】修改后的 HTML 代码片段。必须包含 data-node-id。"
156
+ },
157
+ syncToDesign: {
158
+ description: "将本地完整的静态 HTML 文件内容同步覆盖到 MasterGo 画布进行【全量同步】(如保存整个页面、复杂的模块同步)。必须传入根节点 ID (rootId) 以确保层级正确。",
159
+ documentId: "当前 MasterGo 文档 ID。",
160
+ documentPageId: "当前 MasterGo 页面 ID。",
161
+ targetNodeId: "【必填】页面的根节点 ID (rootId)。",
162
+ filePath: "【必填】本地静态 HTML 文件的绝对路径(通常位于 .codify/... 目录下)。工具会自动读取内容。严禁传入 .vue/.tsx 业务代码路径!"
163
+ },
164
+ getSelectionCode: {
165
+ description: "获取 MasterGo 中当前选中图层(或指定图层)的代码。⚠️ 【严禁盲改】:用户的每一次修改操作(如改色、改文字等),你都必须首先调用本工具拉取最新的节点代码作为上下文!如果你获取的是子节点,工具只会将代码作为纯文本返回给你进行局部修改上下文,绝对不会在本地生成烦人的 HTML 碎片文件!若是根节点则会自动将完整页面代码同步保存到 .codify 目录。",
166
+ projectDir: "【必填】用户当前工作区的根目录绝对路径",
167
+ targetNodeId: "【选填】MasterGo图层ID (例如 123:456)。如果提供,将直接拉取该ID的代码;如果不提供,将拉取当前选中图层的代码。",
168
+ syncToBase: "【选填】是否将获取到的子图层代码同步回本地 .codify 目录下的基准 HTML 文件(合并更新)。默认为 true。"
169
+ },
170
+ createComponent: {
171
+ description: '创建一个 MasterGo 母版组件或组件集(变体)。应当使用 HTML 格式并包含 data-type="component" 属性。',
172
+ code: '组件的 HTML 结构。必须包含 data-type="component" 或 "component-set"。'
173
+ },
174
+ getDesignDiff: {
175
+ description: `获取设计稿与本地代码的差异。
176
+ 【标准双向同步规程】:
177
+ 1. 准备:将当前项目业务代码(Vue/React)按 codify://generation-rules 规范物理逆推为纯静态 HTML。
178
+ 2. 刷新:将转译产物覆盖写入 .codify 目录对应的基准 HTML 文件。
179
+ 3. 比对:调用此工具比对“基准文件”与“画布现状”获得差异列表。
180
+ 4. 决策:展示差异,由用户决定执行“全量同步到设计稿”或“按差异局部同步到项目”。`,
181
+ projectDir: "【必填】用户当前工作区的根目录绝对路径",
182
+ targetNodeId: "【选填】MasterGo图层ID (例如 123:456)。如果不传,则默认获取当前选中图层的代码进行对比。",
183
+ filePath: "【选填】本地基准 HTML 文件的绝对路径。如果已通过 write_to_file 更新了基准文件,请直接传此路径。"
184
+ },
185
+ getCodeList: {
186
+ description: "获取所有可用的代码列表",
187
+ inputSchema: "无需参数获取代码列表"
188
+ },
189
+ design: {
190
+ description: "根据需求生成符合 Codify 规范的 HTML+CSS 代码。生成完成后,应调用 agent_create_page 将代码发送到画布。",
191
+ requirement: '界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。'
192
+ },
193
+ getUserInfo: {
194
+ description: "获取当前登录用户的信息,包括配额、团队等",
195
+ inputSchema: "获取当前用户信息"
196
+ },
197
+ getCode: {
198
+ description: '【特定场景】通过 codify://getCode/{contentId} 从 Codify 插件获取代码。常规的"获取选中代码"请优先使用 get_selection_code 工具。',
199
+ contentId: "从Codify插件复制图层的指令 (contentId)",
200
+ documentId: "当前 MasterGo 文档 ID。",
201
+ documentPageId: "当前 MasterGo 页面 ID。",
202
+ projectDir: "【必填】用户当前工作区的根目录绝对路径",
203
+ outDir: "【必填】保存代码和资源的绝对路径"
204
+ },
205
+ removeNode: {
206
+ description: "在 MasterGo 画布中执行删除节点操作。支持通过 targetNodeId 指定 ID,或在不传 ID 时默认删除当前选中图层。",
207
+ documentId: "当前 MasterGo 文档 ID。",
208
+ documentPageId: "当前 MasterGo 页面 ID。",
209
+ targetNodeId: "【选填】要删除的目标图层 ID (例如 123:456)。如果不传,则默认删除 MasterGo 中当前选中的图层。"
210
+ }
211
+ };
212
+ const i18n = zh;
141
213
  const createComponentTool = {
142
214
  name: "agent_create_component",
143
- description: '创建一个 MasterGo 母版组件或组件集(变体)。应当使用 HTML 格式并包含 data-type="component" 属性。',
215
+ description: i18n.createComponent.description,
144
216
  inputSchema: {
145
- code: z.string().describe('组件的 HTML 结构。必须包含 data-type="component" 或 "component-set"。')
217
+ code: z.string().describe(i18n.createComponent.code)
146
218
  },
147
219
  handler: async (args) => {
148
220
  const { code } = args;
@@ -153,12 +225,12 @@ const createComponentTool = {
153
225
  };
154
226
  const createPageTool = {
155
227
  name: "agent_create_page",
156
- description: "将代码发送到 Codify 插件转换为设计稿。若 HTML 已保存为本地文件,请通过 filePath 传入,避免大段 code 占用 Token。",
228
+ description: i18n.createPage.description,
157
229
  inputSchema: {
158
- code: z.string().optional().describe("【可选】要发送的 HTML 代码内容。小片段可直接传此参数。"),
159
- filePath: z.string().optional().describe("【可选】本地 HTML 文件的绝对路径。创建设计稿且文件已存在时,优先传此参数,工具会读取后发送并执行落盘。"),
160
- projectDir: z.string().describe("【必填】用户当前工作区的根目录绝对路径"),
161
- saveCodeToLocal: z.boolean().default(true).describe("是否将插件返回的渲染结果保存到本地 .codify 目录(落盘机制)")
230
+ code: z.string().optional().describe(i18n.createPage.code),
231
+ filePath: z.string().optional().describe(i18n.createPage.filePath),
232
+ projectDir: z.string().describe(i18n.createPage.projectDir),
233
+ saveCodeToLocal: z.boolean().default(true).describe(i18n.createPage.saveCodeToLocal)
162
234
  },
163
235
  handler: async (args) => {
164
236
  const { projectDir, saveCodeToLocal = true } = args;
@@ -184,7 +256,7 @@ const createPageTool = {
184
256
  code: data.htmlCode || code,
185
257
  documentId: data.documentId,
186
258
  documentPageId: data.documentPageId,
187
- nodeId: data.rootNodeId,
259
+ targetNodeId: data.targetNodeId,
188
260
  nodeName: data.nodeName,
189
261
  resourcePath: data.resourcePath,
190
262
  shape: data.shape,
@@ -203,9 +275,9 @@ const createPageTool = {
203
275
  };
204
276
  const designTool = {
205
277
  name: "design",
206
- description: "根据需求生成符合 Codify 规范的 HTML+CSS 代码",
278
+ description: i18n.design.description,
207
279
  inputSchema: {
208
- requirement: z.string().describe('界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。')
280
+ requirement: z.string().describe(i18n.design.requirement)
209
281
  },
210
282
  handler: async (args) => {
211
283
  const { requirement } = args;
@@ -224,10 +296,7 @@ ${generationRules}
224
296
 
225
297
  ## 发送代码:
226
298
  代码生成并审计通过后,请立即调用 agent_create_page 工具发送代码
227
- - agent_create_page({ code: "生成的完整代码", projectDir: "当前工作目录" })
228
- - 不要询问用户,直接执行发送操作
229
-
230
- **请现在开始生成代码。**`
299
+ - agent_create_page({ code: "生成的完整代码", projectDir: "当前工作目录" })`
231
300
  }
232
301
  ]
233
302
  };
@@ -235,13 +304,13 @@ ${generationRules}
235
304
  };
236
305
  const getCodeTool = {
237
306
  name: "get_code",
238
- description: "从 Codify For MasterGo 插件获取指定的代码",
307
+ description: i18n.getCode.description,
239
308
  inputSchema: {
240
- contentId: z.string().describe("从Codify插件复制图层的指令"),
241
- documentId: z.string().optional().describe("当前 MasterGo 文档 ID。"),
242
- documentPageId: z.string().optional().describe("当前 MasterGo 页面 ID。"),
243
- projectDir: z.string().describe("【必填】用户当前工作区的根目录绝对路径"),
244
- outDir: z.string().describe("【必填】保存代码和资源的绝对路径")
309
+ contentId: z.string().describe(i18n.getCode.contentId),
310
+ documentId: z.string().optional().describe(i18n.getCode.documentId),
311
+ documentPageId: z.string().optional().describe(i18n.getCode.documentPageId),
312
+ projectDir: z.string().describe(i18n.getCode.projectDir),
313
+ outDir: z.string().describe(i18n.getCode.outDir)
245
314
  },
246
315
  handler: async (args) => {
247
316
  const { contentId, outDir, projectDir, documentId, documentPageId } = args;
@@ -277,54 +346,92 @@ const getCodeTool = {
277
346
  };
278
347
  const getCodeListTool = {
279
348
  name: "get_code_list",
280
- description: "获取所有可用的代码列表",
281
- inputSchema: z.object({}).describe("无需参数获取代码列表"),
349
+ description: i18n.getCodeList.description,
350
+ inputSchema: z.object({}).describe(i18n.getCodeList.inputSchema),
282
351
  handler: async () => {
283
352
  const { data, error } = await callApi("GET", "/api/getCodeList");
284
353
  if (error) return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
285
354
  if (!Array.isArray(data) || data.length === 0) {
286
355
  return { content: [{ type: "text", text: "📋 代码列表为空" }] };
287
356
  }
288
- let resultText = `✅ 成功获取代码列表 (共 ${data.length} 项)
289
-
290
- `;
291
- data.forEach((item, index) => {
292
- resultText += `${index + 1}. **${item.contentId}**
357
+ const totalCount = data.length;
358
+ let resultText = `✅ 成功获取代码列表 (共 ${totalCount} 项)
293
359
  `;
294
- resultText += ` - 长度: ${item.codeLength} 字符 | 创建于: ${item.createdAt || "未知"}
295
-
360
+ if (totalCount >= 10) {
361
+ resultText += `⚠️ 仅展示最近更新的前 10 条记录
296
362
  `;
297
- });
363
+ }
364
+ resultText += `
365
+ ${JSON.stringify(data, null, 2)}`;
298
366
  return { content: [{ type: "text", text: resultText }] };
299
367
  }
300
368
  };
301
369
  const getSelectionCodeTool = {
302
370
  name: "get_selection_code",
303
- description: "获取 MasterGo 中当前选中图层(或指定图层)的代码,并智能落盘到本地 .codify 目录。\n\n【强制触发场景】凡用户说「将选中的 xxx 改成 yyy」「将选中的按钮改成红色」「修改当前选中的图层」等含「选中」的修改指令,你必须首先调用此工具拉取当前画布选中节点的最新代码,再根据返回的 HTML 进行修改,禁止凭空生成或直接修改本地文件。",
371
+ description: i18n.getSelectionCode.description,
304
372
  inputSchema: {
305
- projectDir: z.string().describe("【必填】用户当前工作区的根目录绝对路径"),
306
- id: z.string().optional().describe("【选填】MasterGo图层ID (例如 123:456)。如果提供,将直接拉取该ID的代码;如果不提供,将拉取当前选中图层的代码。")
373
+ projectDir: z.string().describe(i18n.getSelectionCode.projectDir),
374
+ targetNodeId: z.string().optional().describe(i18n.getSelectionCode.targetNodeId),
375
+ syncToBase: z.boolean().default(true).describe(i18n.getSelectionCode.syncToBase)
307
376
  },
377
+ /**
378
+ * 工具处理器
379
+ * @param args.syncToBase 是否尝试将子节点代码合并到基准 HTML 文件
380
+ * @param args._depth 内部参数,用于防止递归获取根节点时出现无限循环
381
+ */
308
382
  handler: async (args) => {
309
- const { projectDir, id } = args;
383
+ const {
384
+ projectDir,
385
+ targetNodeId: id,
386
+ syncToBase = true,
387
+ _depth = 0
388
+ } = args;
389
+ if (_depth > 2) {
390
+ return {
391
+ content: [
392
+ { type: "text", text: "❌ 递归获取根节点深度过深,已停止。" }
393
+ ],
394
+ isError: true
395
+ };
396
+ }
310
397
  const endpoint = id ? `/api/getSelectionCode?id=${encodeURIComponent(id)}` : "/api/getSelectionCode";
311
398
  const { data, error } = await callApi("GET", endpoint);
312
399
  if (error) {
313
400
  if (error.status === 400 && error.data?.error === "NoSelection") {
314
- return { content: [{ type: "text", text: "❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。" }], isError: true };
401
+ return {
402
+ content: [
403
+ {
404
+ type: "text",
405
+ text: "❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"
406
+ }
407
+ ],
408
+ isError: true
409
+ };
315
410
  }
316
- return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
411
+ return {
412
+ content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }],
413
+ isError: true
414
+ };
317
415
  }
318
416
  const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
319
417
  const nodeInfo = data.nodeInfo || {};
320
- const { rootId, documentId, documentPageId, nodeId, nodeName, parentId } = nodeInfo;
321
- if (!parentId || parentId === "null") {
418
+ const {
419
+ rootId,
420
+ documentId,
421
+ documentPageId,
422
+ targetNodeId,
423
+ nodeName,
424
+ parentId
425
+ } = nodeInfo;
426
+ if (targetNodeId === rootId) {
427
+ console.log(
428
+ `[getSelectionCode] 当前节点ID: ${targetNodeId}, 根节点ID: ${rootId}`
429
+ );
322
430
  const saveResult = await saveCodeAndResources({
323
431
  baseDir,
324
432
  documentId,
325
433
  documentPageId,
326
- nodeId: rootId || nodeId,
327
- // 使用 rootId 作为文件名后缀
434
+ targetNodeId: rootId,
328
435
  nodeName,
329
436
  code: data.code,
330
437
  resourcePath: data.resourcePath,
@@ -332,70 +439,114 @@ const getSelectionCodeTool = {
332
439
  svg: data.svg,
333
440
  image: data.image
334
441
  });
335
- console.log("saveResult", saveResult);
336
442
  return {
337
443
  content: [
338
444
  {
339
445
  type: "text",
340
446
  text: `✅ 成功获取并保存根节点代码
341
- - 节点: ${nodeName} (${nodeId})
342
- - 文件: ${saveResult.htmlPath}
343
-
344
- 请查看该文件了解当前结构。`
447
+ - 节点: ${nodeName} (${targetNodeId})
448
+ - 根节点: ${rootId}
449
+ - 文件: ${saveResult.htmlPath}。`
345
450
  }
346
451
  ]
347
452
  };
348
453
  } else {
349
- if (!documentId || !documentPageId) {
350
- return { content: [{ type: "text", text: `❌ 更新失败: 缺少 documentId 或 documentPageId 信息。` }], isError: true };
351
- }
352
- const targetDir = path.join(baseDir, CODIFY_DOC_DIR, String(documentId).replace(/:/g, "-"), String(documentPageId).replace(/:/g, "-"));
353
- if (!existsSync(targetDir)) {
354
- return { content: [{ type: "text", text: `❌ 更新失败: 找不到根节点所在目录 ${targetDir}。请先选中并获取根节点的代码。` }], isError: true };
355
- }
356
- const files = await fs.readdir(targetDir);
357
- const safeRootId = String(rootId).replace(/:/g, "-");
358
- const rootFile = files.find((f) => f.endsWith(`-${safeRootId}.html`));
359
- if (!rootFile) {
360
- return { content: [{ type: "text", text: `❌ 更新失败: 在目录中找不到包含根节点 ID (${rootId}) 的文件。请先选中并获取根节点的代码。` }], isError: true };
361
- }
362
- const rootFilePath = path.join(targetDir, rootFile);
363
- const htmlContent = await fs.readFile(rootFilePath, "utf8");
364
- const rootNode = parse(htmlContent);
365
- const targetElement = rootNode.querySelector(`[data-node-id="${nodeId}"]`);
366
- if (targetElement) {
367
- targetElement.replaceWith(data.code);
368
- } else if (nodeInfo.parentId) {
369
- const parentElement = rootNode.querySelector(`[data-node-id="${nodeInfo.parentId}"]`);
370
- if (parentElement) {
371
- parentElement.insertAdjacentHTML("beforeend", data.code);
372
- } else {
373
- return { content: [{ type: "text", text: `❌ 更新失败: 在文件 ${rootFile} 中找不到当前节点(${nodeId}),也找不到父节点(${nodeInfo.parentId})。` }], isError: true };
454
+ console.log(
455
+ `[getSelectionCode] 当前节点ID: ${targetNodeId}, 根节点ID: ${rootId}`
456
+ );
457
+ let mergedToFilePath = "";
458
+ if (syncToBase && documentId && documentPageId) {
459
+ const targetPageDir = path.join(
460
+ baseDir,
461
+ CODIFY_DOC_DIR,
462
+ String(documentId).replace(/:/g, "-"),
463
+ String(documentPageId).replace(/:/g, "-")
464
+ );
465
+ const safeRootId = String(rootId).replace(/:/g, "-");
466
+ let rootFile = "";
467
+ const findRootFile = async () => {
468
+ if (existsSync(targetPageDir)) {
469
+ const files = await fs.readdir(targetPageDir);
470
+ return files.find((f) => f.endsWith(`-${safeRootId}.html`)) || "";
471
+ }
472
+ return "";
473
+ };
474
+ rootFile = await findRootFile();
475
+ if (!rootFile && _depth === 0) {
476
+ console.log(
477
+ `[getSelectionCode] 基准文件不存在,自动拉取根节点: ${rootId}`
478
+ );
479
+ await getSelectionCodeTool.handler({
480
+ projectDir,
481
+ targetNodeId: rootId,
482
+ syncToBase: true,
483
+ _depth: _depth + 1
484
+ });
485
+ rootFile = await findRootFile();
486
+ }
487
+ if (rootFile) {
488
+ const rootFilePath = path.join(targetPageDir, rootFile);
489
+ try {
490
+ const htmlContent = await fs.readFile(rootFilePath, "utf8");
491
+ const rootNode = parse(htmlContent);
492
+ const targetElement = rootNode.querySelector(
493
+ `[data-node-id="${targetNodeId}"]`
494
+ );
495
+ if (targetElement) {
496
+ targetElement.replaceWith(data.code);
497
+ } else if (parentId) {
498
+ const parentElement = rootNode.querySelector(
499
+ `[data-node-id="${parentId}"]`
500
+ );
501
+ if (parentElement) {
502
+ parentElement.insertAdjacentHTML("beforeend", data.code);
503
+ }
504
+ }
505
+ if (targetElement || parentId && rootNode.querySelector(`[data-node-id="${parentId}"]`)) {
506
+ await fs.writeFile(rootFilePath, rootNode.toString(), "utf8");
507
+ mergedToFilePath = rootFilePath;
508
+ }
509
+ } catch (err) {
510
+ console.error("合并到基准文件操作出错:", err);
511
+ }
374
512
  }
375
- } else {
376
- return { content: [{ type: "text", text: `❌ 更新失败: 在文件 ${rootFile} 中找不到 data-node-id="${nodeId}" 的元素,且未提供 parentId 作为后备。` }], isError: true };
377
513
  }
378
- await fs.writeFile(rootFilePath, rootNode.toString(), "utf8");
379
- await saveCodeAndResources({
514
+ const saveResult = await saveCodeAndResources({
380
515
  baseDir,
381
516
  documentId,
382
517
  documentPageId,
383
- nodeId,
518
+ targetNodeId,
384
519
  nodeName,
385
520
  code: "",
386
- // 我们已经手动更新了 HTML,这里传空字符串,主要是为了利用它去保存资源
521
+ // 核心改动:强制将此参数留空,彻底阻断 .html 碎片文件的生成
387
522
  resourcePath: data.resourcePath,
388
523
  shape: data.shape,
389
524
  svg: data.svg,
390
525
  image: data.image
391
- }).catch((e) => console.error("保存子节点资源失败:", e));
526
+ });
527
+ let successText = `✅ 成功获取子节点代码
528
+ - 节点: ${nodeName} (${targetNodeId})
529
+ - 根节点: ${rootId}`;
530
+ if (mergedToFilePath) {
531
+ successText += `
532
+ - 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${mergedToFilePath}`;
533
+ } else {
534
+ successText += `
535
+ - 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。可以直接针对返回代码进行修改。`;
536
+ }
537
+ if (targetNodeId !== rootId) {
538
+ successText += `
539
+ - 资源同步: 图片(${saveResult.imageCount}), 图标(${saveResult.svgCount}), 图形(${saveResult.shapeCount})`;
540
+ }
392
541
  return {
393
542
  content: [
394
543
  {
395
544
  type: "text",
396
- text: `✅ 成功更新子节点代码
397
- - 节点: ${nodeName} (${nodeId})
398
- - 已更新文件: ${rootFilePath}`
545
+ text: `${successText}
546
+
547
+ 代码内容:
548
+
549
+ ${data.code}`
399
550
  }
400
551
  ]
401
552
  };
@@ -404,8 +555,8 @@ const getSelectionCodeTool = {
404
555
  };
405
556
  const getUserInfoTool = {
406
557
  name: "get_user_info",
407
- description: "获取当前登录用户的信息,包括配额、团队等",
408
- inputSchema: z.object({}).describe("获取当前用户信息"),
558
+ description: i18n.getUserInfo.description,
559
+ inputSchema: z.object({}).describe(i18n.getUserInfo.inputSchema),
409
560
  handler: async () => {
410
561
  const { data, error } = await callApi("GET", "/api/getUserInfo");
411
562
  if (error) return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
@@ -434,67 +585,95 @@ const getUserInfoTool = {
434
585
  };
435
586
  const updateNodeTool = {
436
587
  name: "agent_update_node",
437
- description: '将修改后的 HTML 代码发回 MasterGo 画布进行更新。\n\n【调用前置条件】若用户说的是「将选中的 xxx 改成 yyy」等含「选中」的修改指令,你必须先调用 get_selection_code 拉取当前选中节点代码,修改完成后再调用本工具,不得跳过前置步骤。\n\n【局部修改(改文字/颜色/某处)】只传你改动的那一个节点(含 data-node-id 及子节点)的 HTML 片段到 code 参数,严禁传整个文件。\n\n【全量同步(用户明确说"同步到设计稿")】才用 filePath 传本地文件绝对路径,禁止用 Read 工具读大文件再塞进 code。\n\n若是从 Vue/React 业务代码同步,请剔除框架指令(v-for、@click 等),只传纯 HTML。',
588
+ description: i18n.updateNode.description,
438
589
  inputSchema: {
439
- documentId: z.string().optional().describe("当前 MasterGo 文档 ID。"),
440
- documentPageId: z.string().optional().describe("当前 MasterGo 页面 ID。"),
441
- targetNodeId: z.string().optional().describe("【选填】目标图层 ID (例如 123:456)。如果不传,则默认更新 MasterGo 中当前选中的图层。"),
442
- code: z.string().optional().describe("【可选】修改后的 HTML 代码片段。如果只是局部小修改,可以直接传代码。"),
443
- filePath: z.string().optional().describe("【可选】如果需要发送整个文件或大量代码,请提供该文件的绝对路径,工具会自动读取并发送,绝对不要把整个文件的代码塞进 code 参数里!")
590
+ documentId: z.string().optional().describe(i18n.updateNode.documentId),
591
+ documentPageId: z.string().optional().describe(i18n.updateNode.documentPageId),
592
+ targetNodeId: z.string().optional().describe(i18n.updateNode.targetNodeId),
593
+ code: z.string().describe(i18n.updateNode.code)
444
594
  },
445
595
  handler: async (args) => {
446
- const { targetNodeId, documentId, documentPageId, filePath } = args;
447
- let finalCode = args.code || "";
448
- if (filePath) {
449
- try {
450
- const absolutePath = path.resolve(filePath);
451
- finalCode = await fs.readFile(absolutePath, "utf8");
452
- } catch (e) {
453
- return { content: [{ type: "text", text: `❌ 读取文件失败: ${e.message}` }], isError: true };
454
- }
455
- }
456
- if (!finalCode) {
457
- return { content: [{ type: "text", text: `❌ 必须提供 code 或 filePath` }], isError: true };
596
+ const { targetNodeId, documentId, documentPageId, code } = args;
597
+ if (!code) {
598
+ return { content: [{ type: "text", text: `❌ 必须提供 code` }], isError: true };
458
599
  }
459
- const { data, error } = await callApi("POST", "/api/updateNode", { code: finalCode, targetNodeId, documentId, documentPageId });
460
- if (error) return { content: [{ type: "text", text: `❌ 更新失败: ${error.message}` }], isError: true };
600
+ const { data, error } = await callApi("POST", "/api/updateNode", { code, targetNodeId, documentId, documentPageId });
601
+ if (error) return { content: [{ type: "text", text: `❌ 局部更新失败: ${error.message}` }], isError: true };
461
602
  return {
462
- content: [{ type: "text", text: `✅ 已成功发送更新指令${data.nodeId ? `
463
- - 结果节点 ID: ${data.nodeId}` : ""}
464
-
465
- ⚠️ 强烈建议:为了保证本地 .codify 目录中的基准 HTML 与设计稿保持一致,请立即调用 get_selection_code 工具(传入对应的根节点 ID),拉取最新的纯净 HTML 并覆盖本地基准文件。` }]
603
+ content: [{ type: "text", text: `✅ [局部修改] 指令已发送${data.targetNodeId ? `
604
+ - 节点 ID: ${data.targetNodeId}` : ""}` }]
466
605
  };
467
606
  }
468
607
  };
469
608
  const getDesignDiffTool = {
470
609
  name: "get_design_diff",
471
- description: "获取最新设计稿代码与本地 .codify 目录中旧代码的差异 (Diff)。当用户要求“同步设计稿到业务代码”或“对比最新设计稿”时使用。注意:拿到差异后,请根据 data-node-id 精准修改用户的业务代码,绝对不要破坏原有的框架逻辑(如 Vue/React 指令)。",
610
+ description: i18n.getDesignDiff.description,
472
611
  inputSchema: {
473
- projectDir: z.string().describe("【必填】用户当前工作区的根目录绝对路径"),
474
- id: z.string().optional().describe("【选填】MasterGo图层ID (例如 123:456)。如果不传,则默认获取当前选中图层的代码进行对比。")
612
+ projectDir: z.string().describe(i18n.getDesignDiff.projectDir),
613
+ targetNodeId: z.string().optional().describe(i18n.getDesignDiff.targetNodeId),
614
+ filePath: z.string().optional().describe(i18n.getDesignDiff.filePath)
475
615
  },
476
616
  handler: async (args) => {
477
- const { projectDir, id } = args;
617
+ const { projectDir, targetNodeId, filePath } = args;
478
618
  const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
619
+ let currentTargetNodeId = targetNodeId;
479
620
  let oldHtml = "";
480
- let localFilePath = "";
481
- if (id) {
482
- const safeId = String(id).replace(/:/g, "-");
483
- const findFile = async (dir) => {
484
- const entries = await fs.readdir(dir, { withFileTypes: true });
485
- for (const entry of entries) {
486
- const fullPath = path.join(dir, entry.name);
487
- if (entry.isDirectory()) {
488
- const found = await findFile(fullPath);
489
- if (found) return found;
490
- } else if (entry.name.endsWith(`-${safeId}.html`)) {
491
- return fullPath;
621
+ let localFilePath = filePath || "";
622
+ if (localFilePath) {
623
+ try {
624
+ const absolutePath = path.isAbsolute(localFilePath) ? localFilePath : path.resolve(baseDir, localFilePath);
625
+ oldHtml = await fs.readFile(absolutePath, "utf8");
626
+ } catch (e) {
627
+ return {
628
+ content: [
629
+ { type: "text", text: `❌ 无法读取指定的 filePath: ${e.message}` }
630
+ ],
631
+ isError: true
632
+ };
633
+ }
634
+ }
635
+ let finalRootId = "";
636
+ if (!oldHtml || !currentTargetNodeId) {
637
+ const selectionEndpoint = currentTargetNodeId ? `/api/getSelectionCode?id=${encodeURIComponent(currentTargetNodeId)}` : "/api/getSelectionCode";
638
+ const { data: selectionData, error: selectionError } = await callApi(
639
+ "GET",
640
+ selectionEndpoint
641
+ );
642
+ if (selectionError || !selectionData?.nodeInfo?.targetNodeId) {
643
+ return {
644
+ content: [
645
+ {
646
+ type: "text",
647
+ text: `❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。`
648
+ }
649
+ ],
650
+ isError: true
651
+ };
652
+ }
653
+ const {
654
+ rootId,
655
+ targetNodeId: finalTargetId,
656
+ nodeName: finalNodeName
657
+ } = selectionData.nodeInfo;
658
+ finalRootId = rootId;
659
+ if (!currentTargetNodeId) currentTargetNodeId = finalTargetId;
660
+ if (!oldHtml && rootId) {
661
+ const safeRootId = String(rootId).replace(/:/g, "-");
662
+ const findFile = async (dir) => {
663
+ if (!existsSync(dir)) return null;
664
+ const entries = await fs.readdir(dir, { withFileTypes: true });
665
+ for (const entry of entries) {
666
+ const fullPath = path.join(dir, entry.name);
667
+ if (entry.isDirectory()) {
668
+ const found = await findFile(fullPath);
669
+ if (found) return found;
670
+ } else if (entry.name.endsWith(`-${safeRootId}.html`)) {
671
+ return fullPath;
672
+ }
492
673
  }
493
- }
494
- return null;
495
- };
496
- const codifyDir = path.join(baseDir, CODIFY_DOC_DIR);
497
- if (existsSync(codifyDir)) {
674
+ return null;
675
+ };
676
+ const codifyDir = path.join(baseDir, CODIFY_DOC_DIR);
498
677
  localFilePath = await findFile(codifyDir) || "";
499
678
  if (localFilePath) {
500
679
  oldHtml = await fs.readFile(localFilePath, "utf8");
@@ -503,21 +682,32 @@ const getDesignDiffTool = {
503
682
  }
504
683
  if (!oldHtml) {
505
684
  return {
506
- content: [{
507
- type: "text",
508
- text: id ? `❌ 在本地 .codify 目录中找不到 ID 为 ${id} 的代码文件。请确保已执行过拉取操作。` : `❌ 请提供图层 ID 以便从本地 .codify 目录加载基准代码进行 Diff。`
509
- }],
685
+ content: [
686
+ {
687
+ type: "text",
688
+ text: `❌ 在本地 .codify 目录中找不到 ID 为 ${finalRootId || currentTargetNodeId} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`
689
+ }
690
+ ],
510
691
  isError: true
511
692
  };
512
693
  }
513
694
  const { data, error } = await callApi("POST", "/api/getDesignDiff", {
514
695
  code: oldHtml,
515
- id
696
+ targetNodeId: currentTargetNodeId
516
697
  });
517
698
  if (error) {
518
- return { content: [{ type: "text", text: `❌ 获取设计稿差异失败: ${error.message}` }], isError: true };
699
+ return {
700
+ content: [
701
+ { type: "text", text: `❌ 获取设计稿差异失败: ${error.message}` }
702
+ ],
703
+ isError: true
704
+ };
705
+ }
706
+ let diffs = data.diffs || [];
707
+ if (diffs && !Array.isArray(diffs) && Array.isArray(diffs.diffs)) {
708
+ diffs.nodeInfo;
709
+ diffs = diffs.diffs;
519
710
  }
520
- const diffs = data.diffs || [];
521
711
  if (diffs.length === 0) {
522
712
  return {
523
713
  content: [
@@ -544,6 +734,65 @@ ${JSON.stringify(diffs, null, 2)}
544
734
  };
545
735
  }
546
736
  };
737
+ const removeNodeTool = {
738
+ name: "agent_remove_node",
739
+ description: i18n.removeNode.description,
740
+ inputSchema: {
741
+ documentId: z.string().optional().describe(i18n.removeNode.documentId),
742
+ documentPageId: z.string().optional().describe(i18n.removeNode.documentPageId),
743
+ targetNodeId: z.string().optional().describe(i18n.removeNode.targetNodeId)
744
+ },
745
+ handler: async (args) => {
746
+ const { targetNodeId, documentId, documentPageId } = args;
747
+ const { data, error } = await callApi("POST", "/api/removeNode", { targetNodeId, documentId, documentPageId });
748
+ if (error) {
749
+ return {
750
+ content: [{ type: "text", text: `❌ 删除失败: ${error.message}` }],
751
+ isError: true
752
+ };
753
+ }
754
+ return {
755
+ content: [{
756
+ type: "text",
757
+ text: `✅ 已成功发送删除指令
758
+ - 目标节点 ID: ${data.targetNodeId || targetNodeId || "当前选中图层"}`
759
+ }]
760
+ };
761
+ }
762
+ };
763
+ const syncToDesignTool = {
764
+ name: "agent_sync_design",
765
+ description: i18n.syncToDesign.description,
766
+ inputSchema: {
767
+ documentId: z.string().optional().describe(i18n.syncToDesign.documentId),
768
+ documentPageId: z.string().optional().describe(i18n.syncToDesign.documentPageId),
769
+ targetNodeId: z.string().optional().describe(i18n.syncToDesign.targetNodeId),
770
+ filePath: z.string().describe(i18n.syncToDesign.filePath)
771
+ },
772
+ handler: async (args) => {
773
+ const { targetNodeId, documentId, documentPageId, filePath } = args;
774
+ let finalCode = "";
775
+ if (filePath) {
776
+ try {
777
+ const absolutePath = path.resolve(filePath);
778
+ finalCode = await fs.readFile(absolutePath, "utf8");
779
+ } catch (e) {
780
+ return { content: [{ type: "text", text: `❌ 读取本地文件失败: ${e.message}` }], isError: true };
781
+ }
782
+ }
783
+ if (!finalCode) {
784
+ return { content: [{ type: "text", text: `❌ 同步失败: 未能从指定路径读取到有效代码` }], isError: true };
785
+ }
786
+ const { data, error } = await callApi("POST", "/api/syncToDesign", { code: finalCode, targetNodeId, documentId, documentPageId });
787
+ if (error) return { content: [{ type: "text", text: `❌ [全量同步] 失败: ${error.message}` }], isError: true };
788
+ return {
789
+ content: [{ type: "text", text: `✅ [全量同步] 已成功推送至画布${data.targetNodeId ? `
790
+ - 根节点 ID: ${data.targetNodeId}` : ""}
791
+
792
+ 💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 .codify 基准。` }]
793
+ };
794
+ }
795
+ };
547
796
  const allTools = [
548
797
  designTool,
549
798
  getCodeTool,
@@ -553,7 +802,9 @@ const allTools = [
553
802
  createComponentTool,
554
803
  getSelectionCodeTool,
555
804
  updateNodeTool,
556
- getDesignDiffTool
805
+ syncToDesignTool,
806
+ getDesignDiffTool,
807
+ removeNodeTool
557
808
  ];
558
809
  function registerAllTools(mcpServer2) {
559
810
  allTools.forEach((tool) => {
package/dist/rules.md CHANGED
@@ -1,4 +1,4 @@
1
- # 📐 HTML + CSS 视觉创构与逆向转译协议 (Visual Generation & Reversal Protocol)
1
+ # 📐 HTML + CSS 逆向转译协议 (Reversal Protocol)
2
2
 
3
3
  ## 🎯 角色与任务目标 (Dual-Core Persona)
4
4
 
@@ -10,6 +10,29 @@
10
10
  **核心二:严苛的 Figma 协议编译器(负责“格式化”)**
11
11
  一旦视觉方案定型,你必须将这个绝美的界面,**100% 严格地“降维、压缩、翻译”**成符合底层规范的代码。你不再是设计师,而是一个没有感情的机器,确保每一行代码都能被程序完美逆向解析为 Figma 图层。任何非标代码都会导致转换失败系统崩溃。
12
12
 
13
+ ## 🔄 标准双向同步协议 (Standard Bi-directional Sync Protocol)
14
+
15
+ 当需要将本地工程与 MasterGo 设计稿进行比对或同步时,必须严格遵守以下“三步走”规程,严禁跳步:
16
+
17
+ ### Step 1: 准备与逆向转译 (Prepare & Reverse Transpile)
18
+ 你必须读取当前项目对应的业务代码(如 `.vue`, `.tsx`),并将其物理“解构”为符合本协议规范的纯静态 HTML。
19
+ - **清除所有框架指令**:删除 `v-for`, `@click`, `v-if`, `props` 等所有动态逻辑。
20
+ - **展开静态数据**:将变量替换为高逼真的静态假数据,将循环展开为重复的 HTML 结构。
21
+ - **验证样式**:确保所有类名已转换为本协议要求的 Tailwind 任意值语法。
22
+
23
+ ### Step 2: 刷新本地基准 (Refresh Local Base)
24
+ 将 Step 1 产出的纯静态 HTML 代码,通过 `write_to_file` 覆盖写入到 `.codify` 目录下对应的基准 `.html` 文件中。
25
+ - **理由**:通过此步骤,我们确保 `.codify` 目录实时映射了当前代码的真实视觉状态,使得后续的 Diff 能够准确反映“代码”与“设计”的差异,而非过期的快照。
26
+
27
+ ### Step 3: 比对与决策 (Diff & Decision)
28
+ 调用 `get_design_diff` 工具,获取基准文件与画布现状的差异 JSON。
29
+ - **展示差异**:向用户展示变更列表(修改、插入、删除)。
30
+ - **用户决策**:
31
+ - **A. 同步到设计稿**:用户确认代码是正确的,调用 `agent_update_node` (带 `filePath`) 将文件强行覆盖到画布根节点。
32
+ - **B. 同步到代码**:用户确认设计是正确的,你根据 Diff JSON 精准修补业务代码中的样式和结构。
33
+
34
+ ---
35
+
13
36
  ## 📜 最终产出总纲 (Final Output Standards)
14
37
 
15
38
  你所有的工作产出,必须无条件满足以下 7 大黄金标准:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codify-ai/mcp-client",
3
- "version": "1.0.25",
3
+ "version": "1.0.28",
4
4
  "description": "Codify MCP 客户端 - 连接到远程 Codify MCP 服务器,供 CLI 或 Cursor 等 IDE 使用",
5
5
  "type": "module",
6
6
  "bin": {