@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 +87 -98
- package/dist/index.js +392 -141
- package/dist/rules.md +24 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**Codify MCP 客户端 - 连接
|
|
9
|
+
**Codify MCP 客户端 - 连接 MasterGo 和 IDE / 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
|
-
- 🛠️
|
|
25
|
-
- 📦 **资源访问**:通过 URI `Codify://
|
|
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. 配置
|
|
31
|
+
### 1. 配置 MCP
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
例如:编辑 `~/.cursor/mcp.json`(如果文件不存在则创建):
|
|
44
34
|
|
|
45
35
|
```json
|
|
46
36
|
{
|
|
47
37
|
"mcpServers": {
|
|
48
38
|
"codify": {
|
|
49
39
|
"command": "npx",
|
|
50
|
-
"args": [
|
|
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. 重启
|
|
53
|
+
### 2. 重启IDE / CLI
|
|
60
54
|
|
|
61
|
-
重启
|
|
55
|
+
重启Cursor、VSCode、Trae 等 IDE 或者 CLI 工具,或重新加载 MCP 服务器配置。
|
|
62
56
|
|
|
63
57
|
### 3. 开始使用
|
|
64
58
|
|
|
65
|
-
在
|
|
66
|
-
|
|
67
|
-
#### 方式 1:使用 AI 工具调用
|
|
59
|
+
在IDE / CLI 中,您可以:
|
|
68
60
|
|
|
69
|
-
对 AI 说:
|
|
70
61
|
|
|
71
|
-
|
|
72
|
-
请使用 getCode 工具获取 code_id 的代码
|
|
73
|
-
```
|
|
62
|
+
### 方式 1:将设计稿给其他人(研发)拉取代码到本地
|
|
74
63
|
|
|
75
|
-
|
|
64
|
+
在 Codify 插件中选中一个元素,复制的指令然后给到其他人来获取代码。
|
|
76
65
|
|
|
77
|
-
|
|
66
|
+
无需启动 MasterGo,直接在 IDE 中输入该指令即可获取代码。
|
|
78
67
|
|
|
79
68
|
```
|
|
80
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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.
|
|
29
|
-
-
|
|
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
|
-
|
|
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 && !
|
|
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(
|
|
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 ||
|
|
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:
|
|
215
|
+
description: i18n.createComponent.description,
|
|
144
216
|
inputSchema: {
|
|
145
|
-
code: z.string().describe(
|
|
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:
|
|
228
|
+
description: i18n.createPage.description,
|
|
157
229
|
inputSchema: {
|
|
158
|
-
code: z.string().optional().describe(
|
|
159
|
-
filePath: z.string().optional().describe(
|
|
160
|
-
projectDir: z.string().describe(
|
|
161
|
-
saveCodeToLocal: z.boolean().default(true).describe(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
307
|
+
description: i18n.getCode.description,
|
|
239
308
|
inputSchema: {
|
|
240
|
-
contentId: z.string().describe(
|
|
241
|
-
documentId: z.string().optional().describe(
|
|
242
|
-
documentPageId: z.string().optional().describe(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
371
|
+
description: i18n.getSelectionCode.description,
|
|
304
372
|
inputSchema: {
|
|
305
|
-
projectDir: z.string().describe(
|
|
306
|
-
|
|
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 {
|
|
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 {
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: "text",
|
|
405
|
+
text: "❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"
|
|
406
|
+
}
|
|
407
|
+
],
|
|
408
|
+
isError: true
|
|
409
|
+
};
|
|
315
410
|
}
|
|
316
|
-
return {
|
|
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 {
|
|
321
|
-
|
|
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
|
-
|
|
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} (${
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
请查看该文件了解当前结构。`
|
|
447
|
+
- 节点: ${nodeName} (${targetNodeId})
|
|
448
|
+
- 根节点: ${rootId}
|
|
449
|
+
- 文件: ${saveResult.htmlPath}。`
|
|
345
450
|
}
|
|
346
451
|
]
|
|
347
452
|
};
|
|
348
453
|
} else {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
|
379
|
-
await saveCodeAndResources({
|
|
514
|
+
const saveResult = await saveCodeAndResources({
|
|
380
515
|
baseDir,
|
|
381
516
|
documentId,
|
|
382
517
|
documentPageId,
|
|
383
|
-
|
|
518
|
+
targetNodeId,
|
|
384
519
|
nodeName,
|
|
385
520
|
code: "",
|
|
386
|
-
//
|
|
521
|
+
// 核心改动:强制将此参数留空,彻底阻断 .html 碎片文件的生成
|
|
387
522
|
resourcePath: data.resourcePath,
|
|
388
523
|
shape: data.shape,
|
|
389
524
|
svg: data.svg,
|
|
390
525
|
image: data.image
|
|
391
|
-
})
|
|
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
|
-
|
|
398
|
-
|
|
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:
|
|
588
|
+
description: i18n.updateNode.description,
|
|
438
589
|
inputSchema: {
|
|
439
|
-
documentId: z.string().optional().describe(
|
|
440
|
-
documentPageId: z.string().optional().describe(
|
|
441
|
-
targetNodeId: z.string().optional().describe(
|
|
442
|
-
code: z.string().
|
|
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,
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
460
|
-
if (error) return { content: [{ type: "text", text: `❌
|
|
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: `✅
|
|
463
|
-
-
|
|
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:
|
|
610
|
+
description: i18n.getDesignDiff.description,
|
|
472
611
|
inputSchema: {
|
|
473
|
-
projectDir: z.string().describe(
|
|
474
|
-
|
|
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,
|
|
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 (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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
|
-
|
|
508
|
-
|
|
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
|
-
|
|
696
|
+
targetNodeId: currentTargetNodeId
|
|
516
697
|
});
|
|
517
698
|
if (error) {
|
|
518
|
-
return {
|
|
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
|
-
|
|
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
|
|
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 大黄金标准:
|