@codify-ai/mcp-client 1.0.30 → 1.0.31

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/dist/index.js CHANGED
@@ -1,947 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { readFileSync, existsSync, mkdirSync } from "fs";
5
- import path, { dirname, resolve } from "path";
6
- import { fileURLToPath } from "url";
7
- import { z } from "zod";
8
- import axios from "axios";
9
- import fs from "fs/promises";
10
- import { parse } from "node-html-parser";
11
- const __dirname$1 = dirname(fileURLToPath(import.meta.url));
12
- const generationRules = readFileSync(
13
- resolve(__dirname$1, "reversal-protocol.md"),
14
- "utf-8"
15
- );
16
- const designPhilosophy = readFileSync(
17
- resolve(__dirname$1, "design-philosophy.md"),
18
- "utf-8"
19
- );
20
- const zh = {
21
- getCodifyGuidelines: {
22
- description: "【会话入口】当用户表示要使用 Codify / MasterGo 相关能力时(例如「使用 codify mcp」「用 Codify 做设计」),应首先调用本工具。返回 reversal-protocol.md 与 design-philosophy.md 全文,将基础规范载入上下文。内容与 MCP 资源 codify://generation-rules、codify://design-philosophy 一致;若宿主已自动读取资源可跳过。",
23
- inputSchema: "无需参数"
24
- },
25
- createPage: {
26
- description: `将代码发送到 Codify 插件转换为设计稿。
27
- 【极致性能要求】若纯 HTML 已保存为本地文件,强烈建议通过 filePath 传入该文件的绝对路径以节省 Token。如果是直接生成的临时代码,可以通过 code 参数直接传入。`,
28
- code: "【可选】要发送的 HTML 代码内容。仅当代码是临时生成且未保存为文件时使用。",
29
- filePath: "【可选】本地 HTML 文件的绝对路径。若文件已存在本地,必须传此参数,工具会自动读取并执行落盘。",
30
- projectDir: "【必填】用户当前工作区的根目录绝对路径",
31
- saveCodeToLocal: "是否将插件返回的渲染结果保存到本地 .codify 目录(落盘机制)"
32
- },
33
- updateNode: {
34
- description: "发回局部修改的代码。⚠️【工具选择规则】:所有不涉及布局和排版的修改(如修改文字内容、尺寸、颜色、边框、阴影、特效等),必须使用 agent_update_node。注意:如果是修改父容器的样式,传递的 HTML 代码中必须包含其原本所有的子元素结构以防丢失数据。",
35
- documentId: "当前 MasterGo 文档 ID。",
36
- documentPageId: "当前 MasterGo 页面 ID。",
37
- targetNodeId: "【选填】目标图层 ID (例如 123:456)。如果不传,则默认更新 MasterGo 中当前选中的图层。",
38
- code: "【必填】修改后的 HTML 代码片段。必须包含 data-node-id。"
39
- },
40
- replaceNode: {
41
- description: "对指定节点进行【结构替换】。当你需要大规模改变一个组件的内部 HTML 结构(如增加、删除或重新排列子元素)时调用。即便结构变化,该工具也会自动尝试“借尸还魂”逻辑以保留根节点 ID,确保 Agent 上下文不丢失。",
42
- documentId: "当前 MasterGo 文档 ID。",
43
- documentPageId: "当前 MasterGo 页面 ID。",
44
- targetNodeId: "【选填】目标图层 ID (例如 123:456)。如果不传,则默认替换 MasterGo 中当前选中的图层。",
45
- code: "【必填】新的 HTML 代码内容。将完整替换目标节点内部。"
46
- },
47
- syncToDesign: {
48
- description: "将本地完整的静态 HTML 文件内容同步覆盖到 MasterGo 画布进行【全量同步】(如保存整个页面、复杂的模块同步)。必须传入根节点 ID (rootId) 以确保层级正确。",
49
- documentId: "当前 MasterGo 文档 ID。",
50
- documentPageId: "当前 MasterGo 页面 ID。",
51
- targetNodeId: "【必填】页面的根节点 ID (rootId)。",
52
- filePath: "【必填】本地静态 HTML 文件的绝对路径(通常位于 .codify/... 目录下)。工具会自动读取内容。严禁传入 .vue/.tsx 业务代码路径!"
53
- },
54
- getSelectionCode: {
55
- description: "获取 MasterGo 中当前选中图层(或指定图层)的代码。⚠️ 【严禁盲改】:用户的每一次修改操作(如改色、改文字等),你都必须首先调用本工具拉取最新的节点代码作为上下文!如果你获取的是子节点,工具只会将代码作为纯文本返回给你进行局部修改上下文,绝对不会在本地生成烦人的 HTML 碎片文件!若是根节点则会自动将完整页面代码同步保存到 .codify 目录。",
56
- projectDir: "【必填】用户当前工作区的根目录绝对路径",
57
- targetNodeId: "【选填】MasterGo图层ID (例如 123:456)。如果提供,将直接拉取该ID的代码;如果不提供,将拉取当前选中图层的代码。",
58
- syncToBase: "【选填】是否将获取到的子图层代码同步回本地 .codify 目录下的基准 HTML 文件(合并更新)。默认为 true。"
59
- },
60
- createComponent: {
61
- description: '创建一个 MasterGo 母版组件或组件集(变体)。应当使用 HTML 格式并包含 data-type="component" 属性。',
62
- code: '组件的 HTML 结构。必须包含 data-type="component" 或 "component-set"。'
63
- },
64
- getDesignDiff: {
65
- description: "获取本地基准文件与 MasterGo 画布设计现状的差异。调用后返回 JSON 形式的 Diff 结果,用于协助判断有哪些图层或样式发生了改变。",
66
- projectDir: "【必填】用户当前工作区的根目录绝对路径",
67
- targetNodeId: "【选填】MasterGo图层ID (例如 123:456)。如果不传,则默认获取当前选中图层的代码进行对比。",
68
- filePath: "【选填】本地基准 HTML 文件的绝对路径。如果已通过 write_to_file 更新了基准文件,请直接传此路径。"
69
- },
70
- getCodeList: {
71
- description: "获取所有可用的代码列表",
72
- inputSchema: "无需参数获取代码列表"
73
- },
74
- design: {
75
- description: "根据需求生成符合 Codify 规范的 HTML+CSS 代码。生成完成后,应调用 agent_create_page 将代码发送到画布。",
76
- requirement: '界面需求描述,例如:"一个美观的登录页面"、"现代化的仪表盘界面"等。'
77
- },
78
- getUserInfo: {
79
- description: "获取当前登录用户的信息,包括配额、团队等",
80
- inputSchema: "获取当前用户信息"
81
- },
82
- getCode: {
83
- description: '【特定场景】通过 codify://getCode/{contentId} 从 Codify 插件获取代码。常规的"获取选中代码"请优先使用 get_selection_code 工具。',
84
- contentId: "从Codify插件复制图层的指令 (contentId)",
85
- documentId: "当前 MasterGo 文档 ID。",
86
- documentPageId: "当前 MasterGo 页面 ID。",
87
- projectDir: "【必填】用户当前工作区的根目录绝对路径",
88
- outDir: "【必填】保存代码和资源的绝对路径"
89
- },
90
- removeNode: {
91
- description: "在 MasterGo 画布中执行删除节点操作。支持通过 targetNodeId 指定 ID,或在不传 ID 时默认删除当前选中图层。",
92
- documentId: "当前 MasterGo 文档 ID。",
93
- documentPageId: "当前 MasterGo 页面 ID。",
94
- targetNodeId: "【选填】要删除的目标图层 ID (例如 123:456)。如果不传,则默认删除 MasterGo 中当前选中的图层。"
95
- }
96
- };
97
- const i18n = zh;
98
- const getCodifyGuidelinesTool = {
99
- name: "get_codify_guidelines",
100
- description: i18n.getCodifyGuidelines.description,
101
- inputSchema: z.object({}).describe(i18n.getCodifyGuidelines.inputSchema),
102
- handler: async () => {
103
- const text = `# Codify 规范(reversal-protocol.md)
104
-
105
- ${generationRules}
106
-
107
- ---
108
-
109
- # Codify 设计哲学(design-philosophy.md)
110
-
111
- ${designPhilosophy}`;
112
- return {
113
- content: [{ type: "text", text }]
114
- };
115
- }
116
- };
117
- const urlArg = process.argv.find((arg) => arg.startsWith("--url="));
118
- const SERVER_URL = urlArg ? urlArg.slice("--url=".length) : process.env.CODIFY_SERVER_URL || "https://mcp.codify-api.com";
119
- const ACCESS_KEY = process.env.CODIFY_ACCESS_KEY;
120
- const CODIFY_DOC_DIR = ".codify";
121
- const CODIFY_OUTPUT_DIR = ".codify-output";
122
- const isEmpty = (value) => value === null || value === void 0 || value === "" || typeof value === "string" && value.trim() === "";
123
- function buildSuccessText(result, actionName = "操作") {
124
- let responseText = `✅ ${actionName}已成功完成`;
125
- if (result.targetNodeId) responseText += `
126
- - 节点 ID: ${result.targetNodeId}`;
127
- if (result.documentId) responseText += `
128
- - 文档: ${result.documentName || ""} (${result.documentId})`;
129
- if (result.documentPageId) responseText += `
130
- - 页面: ${result.documentPageName || ""} (${result.documentPageId})`;
131
- return responseText;
132
- }
133
- async function callApi(method, endpoint, data = null) {
134
- const headers = {};
135
- if (ACCESS_KEY) headers["Authorization"] = `Bearer ${ACCESS_KEY}`;
136
- const config = {
137
- method,
138
- url: `${SERVER_URL}${endpoint}`,
139
- headers,
140
- ...data && { data }
141
- };
142
- try {
143
- const response = await axios(config);
144
- return { data: response.data, error: null };
145
- } catch (error) {
146
- const status = error.response?.status;
147
- const errorData = error.response?.data || {};
148
- let message = errorData.message || error.message;
149
- if (status === 401) message = "认证失败,请检查 CODIFY_ACCESS_KEY";
150
- if (status === 403) {
151
- if (errorData.mcp_get_limit !== void 0 || errorData.mcp_generate_limit !== void 0 || message.includes("limit")) {
152
- message = `配额不足: ${message}`;
153
- } else {
154
- message = `权限不足: ${message}`;
155
- }
156
- }
157
- if (status === 404) message = "未找到 API 终结点或活跃连接,请确保插件已打开";
158
- return { data: null, error: { status, message, data: errorData } };
159
- }
160
- }
161
- function parseResourcePath(resourcePath) {
162
- const map = { image: "asset/images", svg: "asset/icons", shape: "asset/shapes" };
163
- if (!isEmpty(resourcePath)) {
164
- try {
165
- const parsed = typeof resourcePath === "string" ? JSON.parse(resourcePath) : resourcePath;
166
- if (parsed.image) map.image = parsed.image.replace(/^\.\//, "");
167
- if (parsed.svg) map.svg = parsed.svg.replace(/^\.\//, "");
168
- if (parsed.shape) map.shape = parsed.shape.replace(/^\.\//, "");
169
- } catch (e) {
170
- }
171
- }
172
- return map;
173
- }
174
- async function writeResource(resData, targetDir, folderName, ext) {
175
- if (isEmpty(resData)) return 0;
176
- const parsed = typeof resData === "string" ? JSON.parse(resData) : resData;
177
- const keys = Object.keys(parsed);
178
- if (keys.length === 0) return 0;
179
- const resDir = path.join(targetDir, folderName);
180
- if (!existsSync(resDir)) mkdirSync(resDir, { recursive: true });
181
- const writePromises = Object.entries(parsed).map(async ([key, value]) => {
182
- const match = key.match(/(.+)\.([a-zA-Z0-9]+)$/);
183
- const safeKey = (match ? match[1] : key).replace(/[^a-zA-Z0-9_-]/g, "_");
184
- const finalExt = match ? match[2] : ext;
185
- const filePath = path.join(resDir, `${safeKey}.${finalExt}`);
186
- let content = value;
187
- if (typeof content === "string" && content.startsWith("http")) {
188
- try {
189
- const response = await axios.get(content, { responseType: "arraybuffer" });
190
- await fs.writeFile(filePath, response.data);
191
- } catch (err) {
192
- }
193
- } else if (typeof content === "string" && content.startsWith("data:image/")) {
194
- const parts = content.split(";base64,");
195
- if (parts.length === 2) await fs.writeFile(filePath, parts[1], "base64");
196
- } else {
197
- const dataToWrite = typeof content === "object" ? JSON.stringify(content, null, 2) : content;
198
- const encoding = finalExt === "png" || finalExt === "jpg" || finalExt === "jpeg" ? "base64" : "utf8";
199
- await fs.writeFile(filePath, dataToWrite, encoding);
200
- }
201
- });
202
- await Promise.all(writePromises);
203
- return keys.length;
204
- }
205
- async function saveCodeAndResources({
206
- baseDir,
207
- outDir,
208
- documentId,
209
- documentPageId,
210
- contentId,
211
- targetNodeId,
212
- nodeName,
213
- code,
214
- resourcePath,
215
- shape,
216
- svg,
217
- image,
218
- isSelection = false
219
- }) {
220
- 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 : ""}`);
221
- if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
222
- const safeId = String(targetNodeId || Date.now()).replace(/:/g, "-");
223
- const safeName = String(nodeName || (isSelection ? "selection" : "node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, "-");
224
- const htmlFileName = isSelection || targetNodeId || nodeName ? `${safeName}-${safeId}.html` : `${contentId || "index"}.html`;
225
- const htmlPath = path.join(targetDir, htmlFileName);
226
- if (code) {
227
- await fs.writeFile(htmlPath, code, "utf8");
228
- }
229
- const resPathMap = parseResourcePath(resourcePath);
230
- const stats = await Promise.all([
231
- writeResource(shape, targetDir, resPathMap.shape, "json"),
232
- writeResource(svg, targetDir, resPathMap.svg, "svg"),
233
- writeResource(image, targetDir, resPathMap.image, "png")
234
- ]);
235
- return { targetDir, htmlFileName, htmlPath, shapeCount: stats[0], svgCount: stats[1], imageCount: stats[2], resourcePathMap: resPathMap };
236
- }
237
- const createComponentTool = {
238
- name: "agent_create_component",
239
- description: i18n.createComponent.description,
240
- inputSchema: {
241
- code: z.string().describe(i18n.createComponent.code)
242
- },
243
- handler: async (args) => {
244
- const { code } = args;
245
- const { error } = await callApi("POST", "/api/createComponent", { code });
246
- if (error) return { content: [{ type: "text", text: `❌ 创建失败: ${error.message}` }], isError: true };
247
- return { content: [{ type: "text", text: "✅ 已成功向插件发送组件创建指令" }] };
248
- }
249
- };
250
- const createPageTool = {
251
- name: "agent_create_page",
252
- description: i18n.createPage.description,
253
- inputSchema: {
254
- code: z.string().optional().describe(i18n.createPage.code),
255
- filePath: z.string().optional().describe(i18n.createPage.filePath),
256
- projectDir: z.string().describe(i18n.createPage.projectDir),
257
- saveCodeToLocal: z.boolean().default(true).describe(i18n.createPage.saveCodeToLocal)
258
- },
259
- handler: async (args) => {
260
- const { projectDir, saveCodeToLocal = true } = args;
261
- let code = args.code || "";
262
- if (args.filePath) {
263
- try {
264
- const absolutePath = path.resolve(args.filePath);
265
- code = await fs.readFile(absolutePath, "utf8");
266
- } catch (e) {
267
- return { content: [{ type: "text", text: `❌ 读取文件失败: ${e.message}` }], isError: true };
268
- }
269
- }
270
- if (!code) return { content: [{ type: "text", text: "参数错误: 请提供 code 或 filePath" }], isError: true };
271
- const cleanCode = code.replace(/<design_plan>[\s\S]*?<\/design_plan>/gi, "").trim();
272
- const { data, error } = await callApi("POST", "/api/createPage", { code: cleanCode });
273
- if (error) return { content: [{ type: "text", text: `❌ 发送失败: ${error.message}` }], isError: true };
274
- let responseText = buildSuccessText(data, "设计稿生成");
275
- if (saveCodeToLocal) {
276
- try {
277
- const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
278
- const saveResult = await saveCodeAndResources({
279
- baseDir,
280
- code: data.htmlCode || code,
281
- documentId: data.documentId,
282
- documentPageId: data.documentPageId,
283
- targetNodeId: data.targetNodeId,
284
- nodeName: data.nodeName,
285
- resourcePath: data.resourcePath,
286
- shape: data.shape,
287
- svg: data.svg,
288
- image: data.image
289
- });
290
- responseText += `
291
- - 代码已本地持久化至: ${saveResult.htmlPath}`;
292
- } catch (e) {
293
- responseText += `
294
- - ⚠️ 本地保存失败: ${e.message}`;
295
- }
296
- }
297
- return { content: [{ type: "text", text: responseText }] };
298
- }
299
- };
300
- const designTool = {
301
- name: "design",
302
- description: i18n.design.description,
303
- inputSchema: {
304
- requirement: z.string().describe(i18n.design.requirement)
305
- },
306
- handler: async (args) => {
307
- const { requirement } = args;
308
- if (!requirement) {
309
- return { content: [{ type: "text", text: "参数错误: 未提供需求描述" }], isError: true };
310
- }
311
- return {
312
- content: [
313
- {
314
- type: "text",
315
- text: `📋 收到需求:${requirement}
316
-
317
- ⚠️ 请立即基于你的设计哲学构思 UI,并严格遵循 reversal-protocol.md 规范(如禁用 margin,强制 flex,所有间距使用 gap/padding 等)。
318
-
319
- 代码生成并审计通过后,请直接调用 agent_create_page 工具使用 code 参数发送完整代码至画布:
320
- - agent_create_page({ code: "生成的完整 HTML 代码", projectDir: "当前工作目录" })`
321
- }
322
- ]
323
- };
324
- }
325
- };
326
- const getCodeTool = {
327
- name: "get_code",
328
- description: i18n.getCode.description,
329
- inputSchema: {
330
- contentId: z.string().describe(i18n.getCode.contentId),
331
- documentId: z.string().optional().describe(i18n.getCode.documentId),
332
- documentPageId: z.string().optional().describe(i18n.getCode.documentPageId),
333
- projectDir: z.string().describe(i18n.getCode.projectDir),
334
- outDir: z.string().describe(i18n.getCode.outDir)
335
- },
336
- handler: async (args) => {
337
- const { contentId, outDir, projectDir, documentId, documentPageId } = args;
338
- if (!contentId) return { content: [{ type: "text", text: "参数错误: 未提供 contentId" }], isError: true };
339
- const { data, error } = await callApi("GET", `/api/getCode/${contentId}`);
340
- if (error) return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
341
- if (!data.code) return { content: [{ type: "text", text: "⚠️ 未找到代码内容" }] };
342
- const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
343
- const saveResult = await saveCodeAndResources({
344
- baseDir,
345
- outDir,
346
- documentId,
347
- documentPageId,
348
- contentId,
349
- code: data.code,
350
- resourcePath: data.resourcePath,
351
- shape: data.shape,
352
- svg: data.svg,
353
- image: data.image
354
- });
355
- let resultText = `✅ 代码拉取完成
356
- - 目录: ${saveResult.targetDir}
357
- - 文件: ${saveResult.htmlFileName}
358
- `;
359
- if (saveResult.shapeCount > 0) resultText += `- Shape: ${saveResult.shapeCount} 个
360
- `;
361
- if (saveResult.svgCount > 0) resultText += `- SVG: ${saveResult.svgCount} 个
362
- `;
363
- if (saveResult.imageCount > 0) resultText += `- Image: ${saveResult.imageCount} 个
364
- `;
365
- return { content: [{ type: "text", text: resultText }] };
366
- }
367
- };
368
- const getCodeListTool = {
369
- name: "get_code_list",
370
- description: i18n.getCodeList.description,
371
- inputSchema: z.object({}).describe(i18n.getCodeList.inputSchema),
372
- handler: async () => {
373
- const { data, error } = await callApi("GET", "/api/getCodeList");
374
- if (error) return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
375
- if (!Array.isArray(data) || data.length === 0) {
376
- return { content: [{ type: "text", text: "📋 代码列表为空" }] };
377
- }
378
- const totalCount = data.length;
379
- let resultText = `✅ 成功获取代码列表 (共 ${totalCount} 项)
380
- `;
381
- if (totalCount >= 10) {
382
- resultText += `⚠️ 仅展示最近更新的前 10 条记录
383
- `;
384
- }
385
- resultText += `
386
- ${JSON.stringify(data, null, 2)}`;
387
- return { content: [{ type: "text", text: resultText }] };
388
- }
389
- };
390
- const getSelectionCodeTool = {
391
- name: "get_selection_code",
392
- description: i18n.getSelectionCode.description,
393
- inputSchema: {
394
- projectDir: z.string().describe(i18n.getSelectionCode.projectDir),
395
- targetNodeId: z.string().optional().describe(i18n.getSelectionCode.targetNodeId),
396
- syncToBase: z.boolean().default(true).describe(i18n.getSelectionCode.syncToBase)
397
- },
398
- /**
399
- * 工具处理器
400
- * @param args.syncToBase 是否尝试将子节点代码合并到基准 HTML 文件
401
- * @param args._depth 内部参数,用于防止递归获取根节点时出现无限循环
402
- */
403
- handler: async (args) => {
404
- const {
405
- projectDir,
406
- targetNodeId: id,
407
- syncToBase = true,
408
- _depth = 0
409
- } = args;
410
- if (_depth > 2) {
411
- return {
412
- content: [
413
- { type: "text", text: "❌ 递归获取根节点深度过深,已停止。" }
414
- ],
415
- isError: true
416
- };
417
- }
418
- const endpoint = id ? `/api/getSelectionCode?id=${encodeURIComponent(id)}` : "/api/getSelectionCode";
419
- const { data, error } = await callApi("GET", endpoint);
420
- if (error) {
421
- if (error.status === 400 && error.data?.error === "NoSelection") {
422
- return {
423
- content: [
424
- {
425
- type: "text",
426
- text: "❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"
427
- }
428
- ],
429
- isError: true
430
- };
431
- }
432
- return {
433
- content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }],
434
- isError: true
435
- };
436
- }
437
- const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
438
- const nodeInfo = data.nodeInfo || {};
439
- const {
440
- rootId,
441
- documentId,
442
- documentPageId,
443
- targetNodeId,
444
- nodeName,
445
- parentId
446
- } = nodeInfo;
447
- if (targetNodeId === rootId) {
448
- const saveResult = await saveCodeAndResources({
449
- baseDir,
450
- documentId,
451
- documentPageId,
452
- targetNodeId: rootId,
453
- nodeName,
454
- code: data.code,
455
- resourcePath: data.resourcePath,
456
- shape: data.shape,
457
- svg: data.svg,
458
- image: data.image
459
- });
460
- return {
461
- content: [
462
- {
463
- type: "text",
464
- text: `✅ 成功获取并保存根节点代码
465
- - 节点: ${nodeName} (${targetNodeId})
466
- - 根节点: ${rootId}
467
- - 文件: ${saveResult.htmlPath}。`
468
- }
469
- ]
470
- };
471
- } else {
472
- let mergedToFilePath = "";
473
- if (syncToBase && documentId && documentPageId) {
474
- const targetPageDir = path.join(
475
- baseDir,
476
- CODIFY_DOC_DIR,
477
- String(documentId).replace(/:/g, "-"),
478
- String(documentPageId).replace(/:/g, "-")
479
- );
480
- const safeRootId = String(rootId).replace(/:/g, "-");
481
- let rootFile = "";
482
- const findRootFile = async () => {
483
- if (existsSync(targetPageDir)) {
484
- const files = await fs.readdir(targetPageDir);
485
- return files.find((f) => f.endsWith(`-${safeRootId}.html`)) || "";
486
- }
487
- return "";
488
- };
489
- rootFile = await findRootFile();
490
- if (!rootFile && _depth === 0) {
491
- await getSelectionCodeTool.handler({
492
- projectDir,
493
- targetNodeId: rootId,
494
- syncToBase: true,
495
- _depth: _depth + 1
496
- });
497
- rootFile = await findRootFile();
498
- }
499
- if (rootFile) {
500
- const rootFilePath = path.join(targetPageDir, rootFile);
501
- try {
502
- const htmlContent = await fs.readFile(rootFilePath, "utf8");
503
- const rootNode = parse(htmlContent);
504
- const targetElement = rootNode.querySelector(
505
- `[data-node-id="${targetNodeId}"]`
506
- );
507
- if (targetElement) {
508
- targetElement.replaceWith(data.code);
509
- } else if (parentId) {
510
- const parentElement = rootNode.querySelector(
511
- `[data-node-id="${parentId}"]`
512
- );
513
- if (parentElement) {
514
- parentElement.insertAdjacentHTML("beforeend", data.code);
515
- }
516
- }
517
- if (targetElement || parentId && rootNode.querySelector(`[data-node-id="${parentId}"]`)) {
518
- await fs.writeFile(rootFilePath, rootNode.toString(), "utf8");
519
- mergedToFilePath = rootFilePath;
520
- }
521
- } catch (err) {
522
- }
523
- }
524
- }
525
- const saveResult = await saveCodeAndResources({
526
- baseDir,
527
- documentId,
528
- documentPageId,
529
- targetNodeId,
530
- nodeName,
531
- code: "",
532
- // 核心改动:强制将此参数留空,彻底阻断 .html 碎片文件的生成
533
- resourcePath: data.resourcePath,
534
- shape: data.shape,
535
- svg: data.svg,
536
- image: data.image
537
- });
538
- let successText = `✅ 成功获取子节点代码
539
- - 节点: ${nodeName} (${targetNodeId})
540
- - 根节点: ${rootId}`;
541
- if (mergedToFilePath) {
542
- successText += `
543
- - 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${mergedToFilePath}`;
544
- } else {
545
- successText += `
546
- - 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。可以直接针对返回代码进行修改。`;
547
- }
548
- if (targetNodeId !== rootId) {
549
- successText += `
550
- - 资源同步: 图片(${saveResult.imageCount}), 图标(${saveResult.svgCount}), 图形(${saveResult.shapeCount})`;
551
- }
552
- return {
553
- content: [
554
- {
555
- type: "text",
556
- text: `${successText}
557
-
558
- 代码内容:
559
-
560
- ${data.code}`
561
- }
562
- ]
563
- };
564
- }
565
- }
566
- };
567
- const getUserInfoTool = {
568
- name: "get_user_info",
569
- description: i18n.getUserInfo.description,
570
- inputSchema: z.object({}).describe(i18n.getUserInfo.inputSchema),
571
- handler: async () => {
572
- const { data, error } = await callApi("GET", "/api/getUserInfo");
573
- if (error) return { content: [{ type: "text", text: `❌ 获取失败: ${error.message}` }], isError: true };
574
- let resultText = `✅ 用户信息
575
-
576
- 👤 用户: ${data.realname || data.userName || data.userId}
577
- `;
578
- if (data.quota) {
579
- resultText += `
580
- 📊 配额:
581
- `;
582
- resultText += ` - 生成设计: ${data.quota.mcp_generate_count || 0} / ${data.quota.mcp_generate_limit || "无"}
583
- `;
584
- resultText += ` - 获取代码: ${data.quota.mcp_get_count || 0} / ${data.quota.mcp_get_limit || "无"}
585
- `;
586
- }
587
- if (data.teams?.length) {
588
- resultText += `
589
- 👥 团队:
590
- `;
591
- data.teams.forEach((t, i) => resultText += ` ${i + 1}. ${t.name} (ID: ${t.teamId})
592
- `);
593
- }
594
- return { content: [{ type: "text", text: resultText }] };
595
- }
596
- };
597
- const updateNodeTool = {
598
- name: "agent_update_node",
599
- description: i18n.updateNode.description,
600
- inputSchema: {
601
- documentId: z.string().optional().describe(i18n.updateNode.documentId),
602
- documentPageId: z.string().optional().describe(i18n.updateNode.documentPageId),
603
- targetNodeId: z.string().optional().describe(i18n.updateNode.targetNodeId),
604
- code: z.string().describe(i18n.updateNode.code)
605
- },
606
- handler: async (args) => {
607
- const { documentId, documentPageId, code } = args;
608
- let finalTargetNodeId = args.targetNodeId;
609
- if (!code) {
610
- return { content: [{ type: "text", text: `❌ 必须提供 code` }], isError: true };
611
- }
612
- if (!finalTargetNodeId) {
613
- try {
614
- const rootNode = parse(code);
615
- const firstElement = rootNode.querySelector("[data-node-id]");
616
- if (firstElement) {
617
- const id = firstElement.getAttribute("data-node-id");
618
- if (id) {
619
- finalTargetNodeId = id;
620
- }
621
- }
622
- } catch (e) {
623
- }
624
- }
625
- if (!finalTargetNodeId) {
626
- return {
627
- content: [{
628
- type: "text",
629
- text: `❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止灾难性地覆盖当前选中的未知图层,本次操作已被拦截。请明确指定 targetNodeId 或在 HTML 包含 data-node-id。`
630
- }],
631
- isError: true
632
- };
633
- }
634
- const { data, error } = await callApi("POST", "/api/updateNode", { code, targetNodeId: finalTargetNodeId, documentId, documentPageId });
635
- if (error) return { content: [{ type: "text", text: `❌ 局部更新失败: ${error.message}` }], isError: true };
636
- return {
637
- content: [{ type: "text", text: `✅ [局部修改] 指令已发送${data.targetNodeId ? `
638
- - 节点 ID: ${data.targetNodeId}` : ""}
639
- 💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。` }]
640
- };
641
- }
642
- };
643
- const replaceNodeTool = {
644
- name: "agent_replace_node",
645
- description: i18n.replaceNode.description,
646
- inputSchema: {
647
- documentId: z.string().optional().describe(i18n.replaceNode.documentId),
648
- documentPageId: z.string().optional().describe(i18n.replaceNode.documentPageId),
649
- targetNodeId: z.string().optional().describe(i18n.replaceNode.targetNodeId),
650
- code: z.string().describe(i18n.replaceNode.code)
651
- },
652
- handler: async (args) => {
653
- const { documentId, documentPageId, code } = args;
654
- let finalTargetNodeId = args.targetNodeId;
655
- if (!code) {
656
- return { content: [{ type: "text", text: `❌ 必须提供 code` }], isError: true };
657
- }
658
- if (!finalTargetNodeId) {
659
- try {
660
- const rootNode = parse(code);
661
- const firstElement = rootNode.querySelector("[data-node-id]");
662
- if (firstElement) {
663
- const id = firstElement.getAttribute("data-node-id");
664
- if (id) {
665
- finalTargetNodeId = id;
666
- }
667
- }
668
- } catch (e) {
669
- }
670
- }
671
- if (!finalTargetNodeId) {
672
- return {
673
- content: [{
674
- type: "text",
675
- text: `❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止把您当前极可能选中的大容器直接覆盖损毁,系统已拦截。请务必明确目标节点的 ID。`
676
- }],
677
- isError: true
678
- };
679
- }
680
- const { data, error } = await callApi("POST", "/api/replaceNode", { code, targetNodeId: finalTargetNodeId, documentId, documentPageId });
681
- if (error) return { content: [{ type: "text", text: `❌ 结构替换失败: ${error.message}` }], isError: true };
682
- return {
683
- content: [{ type: "text", text: `✅ [结构替换] 指令已发送${data.targetNodeId ? `
684
- - 节点 ID: ${data.targetNodeId}` : ""}
685
- 💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。` }]
686
- };
687
- }
688
- };
689
- const getDesignDiffTool = {
690
- name: "get_design_diff",
691
- description: i18n.getDesignDiff.description,
692
- inputSchema: {
693
- projectDir: z.string().describe(i18n.getDesignDiff.projectDir),
694
- targetNodeId: z.string().optional().describe(i18n.getDesignDiff.targetNodeId),
695
- filePath: z.string().optional().describe(i18n.getDesignDiff.filePath)
696
- },
697
- handler: async (args) => {
698
- const { projectDir, targetNodeId, filePath } = args;
699
- const baseDir = projectDir ? path.resolve(projectDir) : process.cwd();
700
- let currentTargetNodeId = targetNodeId;
701
- let oldHtml = "";
702
- let localFilePath = filePath || "";
703
- if (localFilePath) {
704
- try {
705
- const absolutePath = path.isAbsolute(localFilePath) ? localFilePath : path.resolve(baseDir, localFilePath);
706
- oldHtml = await fs.readFile(absolutePath, "utf8");
707
- } catch (e) {
708
- return {
709
- content: [
710
- { type: "text", text: `❌ 无法读取指定的 filePath: ${e.message}` }
711
- ],
712
- isError: true
713
- };
714
- }
715
- }
716
- let finalRootId = "";
717
- if (!oldHtml || !currentTargetNodeId) {
718
- const selectionEndpoint = currentTargetNodeId ? `/api/getSelectionCode?id=${encodeURIComponent(currentTargetNodeId)}` : "/api/getSelectionCode";
719
- const { data: selectionData, error: selectionError } = await callApi(
720
- "GET",
721
- selectionEndpoint
722
- );
723
- if (selectionError || !selectionData?.nodeInfo?.targetNodeId) {
724
- return {
725
- content: [
726
- {
727
- type: "text",
728
- text: `❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。`
729
- }
730
- ],
731
- isError: true
732
- };
733
- }
734
- const {
735
- rootId,
736
- targetNodeId: finalTargetId,
737
- nodeName: finalNodeName
738
- } = selectionData.nodeInfo;
739
- finalRootId = rootId;
740
- if (!currentTargetNodeId) currentTargetNodeId = finalTargetId;
741
- if (!oldHtml && rootId) {
742
- const safeRootId = String(rootId).replace(/:/g, "-");
743
- const findFile = async (dir) => {
744
- if (!existsSync(dir)) return null;
745
- const entries = await fs.readdir(dir, { withFileTypes: true });
746
- for (const entry of entries) {
747
- const fullPath = path.join(dir, entry.name);
748
- if (entry.isDirectory()) {
749
- const found = await findFile(fullPath);
750
- if (found) return found;
751
- } else if (entry.name.endsWith(`-${safeRootId}.html`)) {
752
- return fullPath;
753
- }
754
- }
755
- return null;
756
- };
757
- const codifyDir = path.join(baseDir, CODIFY_DOC_DIR);
758
- localFilePath = await findFile(codifyDir) || "";
759
- if (localFilePath) {
760
- oldHtml = await fs.readFile(localFilePath, "utf8");
761
- }
762
- }
763
- }
764
- if (!oldHtml) {
765
- return {
766
- content: [
767
- {
768
- type: "text",
769
- text: `❌ 在本地 .codify 目录中找不到 ID 为 ${finalRootId || currentTargetNodeId} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`
770
- }
771
- ],
772
- isError: true
773
- };
774
- }
775
- const { data, error } = await callApi("POST", "/api/getDesignDiff", {
776
- code: oldHtml,
777
- targetNodeId: currentTargetNodeId
778
- });
779
- if (error) {
780
- return {
781
- content: [
782
- { type: "text", text: `❌ 获取设计稿差异失败: ${error.message}` }
783
- ],
784
- isError: true
785
- };
786
- }
787
- let diffs = data.diffs || [];
788
- if (diffs && !Array.isArray(diffs) && Array.isArray(diffs.diffs)) {
789
- diffs.nodeInfo;
790
- diffs = diffs.diffs;
791
- }
792
- if (diffs.length === 0) {
793
- return {
794
- content: [
795
- {
796
- type: "text",
797
- text: `✅ 设计稿与本地代码完全一致,没有发现任何变更。`
798
- }
799
- ]
800
- };
801
- }
802
- return {
803
- content: [
804
- {
805
- type: "text",
806
- text: `✅ 成功获取设计稿差异 (共 ${diffs.length} 处变更):
807
-
808
- \`\`\`json
809
- ${JSON.stringify(diffs, null, 2)}
810
- \`\`\`
811
-
812
- 请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 .codify 的基准文件。`
813
- }
814
- ]
815
- };
816
- }
817
- };
818
- const removeNodeTool = {
819
- name: "agent_remove_node",
820
- description: i18n.removeNode.description,
821
- inputSchema: {
822
- documentId: z.string().optional().describe(i18n.removeNode.documentId),
823
- documentPageId: z.string().optional().describe(i18n.removeNode.documentPageId),
824
- targetNodeId: z.string().optional().describe(i18n.removeNode.targetNodeId)
825
- },
826
- handler: async (args) => {
827
- const { targetNodeId, documentId, documentPageId } = args;
828
- const { data, error } = await callApi("POST", "/api/removeNode", { targetNodeId, documentId, documentPageId });
829
- if (error) {
830
- return {
831
- content: [{ type: "text", text: `❌ 删除失败: ${error.message}` }],
832
- isError: true
833
- };
834
- }
835
- return {
836
- content: [{
837
- type: "text",
838
- text: `✅ 已成功发送删除指令
839
- - 目标节点 ID: ${data.targetNodeId || targetNodeId || "当前选中图层"}`
840
- }]
841
- };
842
- }
843
- };
844
- const syncToDesignTool = {
845
- name: "agent_sync_design",
846
- description: i18n.syncToDesign.description,
847
- inputSchema: {
848
- documentId: z.string().optional().describe(i18n.syncToDesign.documentId),
849
- documentPageId: z.string().optional().describe(i18n.syncToDesign.documentPageId),
850
- targetNodeId: z.string().optional().describe(i18n.syncToDesign.targetNodeId),
851
- filePath: z.string().describe(i18n.syncToDesign.filePath)
852
- },
853
- handler: async (args) => {
854
- const { targetNodeId, documentId, documentPageId, filePath } = args;
855
- let finalCode = "";
856
- if (filePath) {
857
- try {
858
- const absolutePath = path.resolve(filePath);
859
- finalCode = await fs.readFile(absolutePath, "utf8");
860
- } catch (e) {
861
- return { content: [{ type: "text", text: `❌ 读取本地文件失败: ${e.message}` }], isError: true };
862
- }
863
- }
864
- if (!finalCode) {
865
- return { content: [{ type: "text", text: `❌ 同步失败: 未能从指定路径读取到有效代码` }], isError: true };
866
- }
867
- const { data, error } = await callApi("POST", "/api/syncToDesign", { code: finalCode, targetNodeId, documentId, documentPageId });
868
- if (error) return { content: [{ type: "text", text: `❌ [全量同步] 失败: ${error.message}` }], isError: true };
869
- return {
870
- content: [{ type: "text", text: `✅ [全量同步] 已成功推送至画布${data.targetNodeId ? `
871
- - 根节点 ID: ${data.targetNodeId}` : ""}
872
-
873
- 💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 .codify 基准。` }]
874
- };
875
- }
876
- };
877
- const allTools = [
878
- getCodifyGuidelinesTool,
879
- designTool,
880
- getCodeTool,
881
- getCodeListTool,
882
- getUserInfoTool,
883
- createPageTool,
884
- createComponentTool,
885
- getSelectionCodeTool,
886
- updateNodeTool,
887
- replaceNodeTool,
888
- syncToDesignTool,
889
- getDesignDiffTool,
890
- removeNodeTool
891
- ];
892
- function registerAllTools(mcpServer2) {
893
- allTools.forEach((tool) => {
894
- mcpServer2.registerTool(tool.name, {
895
- description: tool.description,
896
- inputSchema: tool.inputSchema
897
- }, tool.handler);
898
- });
899
- }
900
- const mcpServer = new McpServer(
901
- {
902
- name: "Codify-MCP-Client",
903
- version: "1.0.22"
904
- },
905
- {
906
- capabilities: {
907
- resources: {},
908
- tools: {},
909
- prompts: {}
910
- }
911
- }
912
- );
913
- mcpServer.registerResource(
914
- "generation-rules",
915
- "codify://generation-rules",
916
- { description: "Codify 逆向转译协议(必须严格遵守的 HTML/Tailwind 语法规范)" },
917
- async (uri) => ({
918
- contents: [
919
- {
920
- uri: uri.toString(),
921
- text: generationRules,
922
- mimeType: "text/markdown"
923
- }
924
- ]
925
- })
926
- );
927
- mcpServer.registerResource(
928
- "design-philosophy",
929
- "codify://design-philosophy",
930
- { description: "Codify 核心设计哲学(仅在从零设计新页面或重构 UI 风格时加载,用于指导商业级审美)" },
931
- async (uri) => ({
932
- contents: [
933
- {
934
- uri: uri.toString(),
935
- text: designPhilosophy,
936
- mimeType: "text/markdown"
937
- }
938
- ]
939
- })
940
- );
941
- registerAllTools(mcpServer);
942
- try {
943
- const transport = new StdioServerTransport();
944
- await mcpServer.connect(transport);
945
- } catch (error) {
946
- process.exit(1);
947
- }
2
+ import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as n}from"zod";import{readFileSync as r,existsSync as o,readdirSync as i,statSync as a,mkdirSync as s}from"fs";import c,{dirname as d,resolve as u}from"path";import{fileURLToPath as m}from"url";import l from"axios";import p from"fs/promises";import{parse as g}from"node-html-parser";const f=d(m(import.meta.url)),y=r(u(f,"page-generate.md"),"utf-8"),h=r(u(f,"component-import.md"),"utf-8"),b=r(u(f,"component-generate.md"),"utf-8"),x=d(m(import.meta.url));const w=function(e){const t=u(x,`${e}.json`),n=r(t,"utf8");return JSON.parse(n)}("en"===process.env.CODIFY_LANG?"en":"zh");let $=!1,I=!1,S="",j="",v="",N=!1,_=!1,C=!1;function P(){return N}function L(){return _}function D(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function A(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function E(e){return A(e).replace(/[^a-z0-9\u4e00-\u9fa5]+/g,"")}function T(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return D(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?D(n[1]):""}(e),n=I&&S.trim()&&t.trim()&&(r=t,A(S)===A(r));var r;$=!0,n||(I=!1,j="",v="",S=t)}function k(e){I=!0,e.teamLibraryName?.trim()&&(S=e.teamLibraryName.trim()),e.filePath?.trim()&&(j=e.filePath.trim()),e.baseDir?.trim()&&(v=e.baseDir.trim())}function M(e){const t=c.join(e,".codify","library");if(!o(t))return"";const n=S.trim(),s=A(n),d=(u=n,u.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)).toLowerCase();var u;const m=E(n),l=function(e,t,n,i){const a=c.join(e,"catalog.json");if(!o(a))return"";try{const s=r(a,"utf8"),d=JSON.parse(s),u=Array.isArray(d?.libraries)?d.libraries:[];if(0===u.length)return"";const m=t=>{if(!t.indexPath)return"";const n=c.join(e,t.indexPath);return o(n)?n:""};if(!t)return 1!==u.length?"":m(u[0]);const l=u.find(e=>{const n=A(String(e.name||"")),r=A(String(e.slug||"")),o=A(String(e.id||""));return n===t||r===t||o===t});if(l)return m(l);const p=u.find(e=>{const r=String(e.name||""),o=String(e.slug||""),a=String(e.id||""),s=A(r),c=A(o),d=A(a),u=E(r),m=E(o);return s.includes(t)||t.includes(s)||c.includes(t)||t.includes(c)||d.includes(t)||r.toLowerCase().includes(n)||o.toLowerCase().includes(n)||u.includes(i)||i.includes(u)||m.includes(i)||i.includes(m)});if(p)return m(p)}catch{return""}return""}(t,s,d,m);if(l)return l;const p=i(t).flatMap(e=>{const n=c.join(t,e);try{const t=a(n);if(t.isFile()&&"index.json"===e.toLowerCase())return[n];if(!t.isDirectory())return[];const s=[],d=c.join(n,"meta.json");if(o(d))try{const e=r(d,"utf8"),t=JSON.parse(e),i=t?.paths?.index;if("string"==typeof i&&i.trim()){const e=c.join(n,i);o(e)&&s.push(e)}}catch{}for(const e of i(n)){const t=c.join(n,e);try{if(!a(t).isDirectory())continue;const e=c.join(t,"index.json");o(e)&&s.push(e)}catch{}}return s}catch{return[]}}).filter(e=>o(e));if(0===p.length)return"";const g=[...p.map(e=>({filePath:e,stem:c.basename(e,".json"),display:c.basename(c.dirname(e))}))].sort((e,t)=>{const n="index"===e.stem.toLowerCase();return n===("index"===t.stem.toLowerCase())?0:n?-1:1});if(s){const e=g.find(e=>{const t=A(e.stem),n=A(e.display);return e.stem.toLowerCase()===d||t===s||n===s});if(e)return e.filePath;const t=g.find(e=>{const t=A(e.stem),n=A(e.display),r=E(e.stem),o=E(e.display);return t.includes(s)||s.includes(t)||n.includes(s)||s.includes(n)||e.stem.toLowerCase().includes(d)||r.includes(m)||m.includes(r)||o.includes(m)||m.includes(o)});if(t)return t.filePath}return 1===p.length?p[0]:""}function O(e,t){if(!$||I)return null;if(function(e){if(!$||I)return!0;const t=M(e);return!!t&&(k({teamLibraryName:S,filePath:t}),!0)}(t||v||process.cwd()))return null;return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但在 .codify/library 未找到可用落盘数据。\n\n请先按顺序执行:\n1) 先检查 .codify/library 是否已有对应团队库 index.json\n2) 若没有,再调用 get_library_list(先询问用户并确认团队库)\n3) 调用 get_component_info(拉取并落盘 index + components/icons 分片)\n4) 然后再继续「${e}」当前流程\n${S?`\n- 目标团队库: ${S}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,否则高概率转译失败。`}],isError:!0}}function F(){if(!$||!I)return"";return`\n\n🧩 组件库模式已就绪,生成前必须先读取 index.json(name/description),再按 components/\${key}.json 读取组件详情:${S?`\n- 团队库: ${S}`:""}${j?`\n- 组件索引文件: ${j}`:""}\n- 先确认构建策略:full-components / hybrid\n- full-components 模式:可组件化区块必须使用 <ui-component>,仅在库中确实不存在时才可局部回退 HTML,并需说明缺失原因\n- hybrid 模式:视觉优先,关键功能区使用组件;并且必须使用组件库样式/变量与图标系统\n- 样式变量规则:优先使用 variable.json 中变量,关键样式属性使用 var(...)(如 bg/text/gap/padding/radius/border/shadow);var(...) 内必须是变量名(name/ukey),禁止直接写 value\n- <ui-component>.name/props 只能来自 components[]\n- 子实例替换规则:若组件使用 instance_swap(如“图标/前缀/尾部操作/状态子组件”),必须写在 <ui-component instance_swap='...'> 上;禁止用子节点伪造同一替换位\n- 若该替换位存在对应 props 开关(如“显示图标/显示右边图标/启用前缀”),使用替换时必须显式设为 true\n- <ui-component> 的布局样式按排版需要声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- <ui-icon>.name 只能来自 icons[]\n- 禁止臆造组件名、图标名或 props\n- 禁止在未读取 library 数据前直接生成组件引用`}const J=["# Final Hard Gate","","在调用提交类工具前,至少满足:","1) 仅输出根节点片段;禁止 html/head/body/script/style/link/meta/title","2) 所有节点包含 data-name;禁用 margin/grid/原生表单标签","3) 禁止相对单位(%/vw/vh/rem/em/calc),尺寸使用绝对像素","4) Flex 容器写全:flex + flex-row|flex-col + justify-* + items-*","5) 组件模式下,<ui-component>/<ui-icon> 仅可引用落盘 JSON 中存在的 name/props/slots/icon","6) 组件模式下,<ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]",'7) 若用户诉求包含“图标/icon/替换图标”,非组件库场景必须使用 FontAwesome <i class="fas/fa* fa-...">,禁止 div/svg/path 手绘图标(除非用户明确要求自定义 SVG)',"8) 组件库模式下,样式优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 中必须使用变量名(name/ukey),禁止直接抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true"].join("\n");function G(e){return"page-generate"===e?y:"component-generate"===e?["# Component Generate Rules (Master Component / Component Set)",b,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):"component-import"===e?["# Component Import Rules",h,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):["# Page Generate Rules (Base, Must Follow)",y,"","# Component Import Rules (Apply Only In Component Mode)",h,"",J].join("\n")}function H(e){return"all"===e?P()&&L():"page-generate"===e?P():"component-import"===e?L():C}function q(e){const t=H(e);return t||function(e){switch(e){case"all":N=!0,_=!0;break;case"page-generate":N=!0;break;case"component-import":_=!0;break;case"component-generate":C=!0}}(e),{loadedNow:!t,text:G(e)}}const R={name:"get_codify_guidelines",description:w.getCodifyGuidelines.description,inputSchema:n.object({scope:n.enum(["all","page-generate","component-import","component-generate"]).optional()}).describe(w.getCodifyGuidelines.inputSchema),handler:async e=>{const t=e?.scope||"all",{text:n}=q(t);return{content:[{type:"text",text:n}]}}},z=process.argv.find(e=>e.startsWith("--url=")),U=z?z.slice(6):process.env.CODIFY_SERVER_URL||"https://mcp.codify-api.com",W=process.env.CODIFY_ACCESS_KEY,B=".codify",V="design",Y=".codify-output",Z="请确认已启动 MasterGo 并打开 Codify 插件(保持插件面板在线后重试)。",K=["/api/getTeamLibraryList","/api/getComponentInfo","/api/createPage","/api/getSelectionCode","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const Q=e=>null==e||""===e||"string"==typeof e&&""===e.trim();async function X(e,t,n=null){const r={};W&&(r.Authorization=`Bearer ${W}`);const o={method:e,url:`${U}${t}`,headers:r,...n&&{data:n}};try{return{data:(await l(o)).data,error:null}}catch(e){const n=e.response?.status,r=e.response?.data||{};let o=r.message||e.message;return 401===n&&(o="认证失败,请检查 CODIFY_ACCESS_KEY"),403===n&&(o=void 0!==r.mcp_get_limit||void 0!==r.mcp_generate_limit||o.includes("limit")?`配额不足: ${o}`:`权限不足: ${o}`),404===n&&(o="未找到 API 终结点或活跃连接"),function(e,t,n){const r=String(n||"").toLowerCase(),o=K.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes("Codify 插件")||e.includes("MasterGo")?e:`${e}\n\n${Z}`:Z}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function ee(e,t,n,r){if(Q(e))return 0;const i="string"==typeof e?JSON.parse(e):e,a=Object.keys(i);if(0===a.length)return 0;const d=c.join(t,n);o(d)||s(d,{recursive:!0});const u=Object.entries(i).map(async([e,t])=>{const n=e.match(/(.+)\.([a-zA-Z0-9]+)$/),o=(n?n[1]:e).replace(/[^a-zA-Z0-9_-]/g,"_"),i=n?n[2]:r,a=c.join(d,`${o}.${i}`);let s=t;if("string"==typeof s&&s.startsWith("http"))try{const e=await l.get(s,{responseType:"arraybuffer"});await p.writeFile(a,e.data)}catch(e){}else if("string"==typeof s&&s.startsWith("data:image/")){const e=s.split(";base64,");2===e.length&&await p.writeFile(a,e[1],"base64")}else{const e="object"==typeof s?JSON.stringify(s,null,2):s,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await p.writeFile(a,e,t)}});return await Promise.all(u),a.length}async function te({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:i,targetNodeId:a,nodeName:d,code:u,resourcePath:m,shape:l,svg:g,image:f,isSelection:y=!1}){let h=t?c.resolve(e,t):n&&r?c.join(e,B,V,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!i||y||a?"":"code"):y?c.join(e,Y,"selection"):c.join(e,`${Y}${i?"/"+i:""}`);o(h)||s(h,{recursive:!0});const b=String(a||Date.now()).replace(/:/g,"-"),x=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),w=y||a||d?`${x}-${b}.html`:`${i||"index"}.html`,$=c.join(h,w);u&&await p.writeFile($,u,"utf8");const I=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!Q(e))try{const n="string"==typeof e?JSON.parse(e):e;n.image&&(t.image=n.image.replace(/^\.\//,"")),n.svg&&(t.svg=n.svg.replace(/^\.\//,"")),n.shape&&(t.shape=n.shape.replace(/^\.\//,""))}catch(e){}return t}(m),S=await Promise.all([ee(l,h,I.shape,"json"),ee(g,h,I.svg,"svg"),ee(f,h,I.image,"png")]);return{targetDir:h,htmlFileName:w,htmlPath:$,shapeCount:S[0],svgCount:S[1],imageCount:S[2],resourcePathMap:I}}const ne={name:"agent_create_component",description:w.createComponent.description,inputSchema:{code:n.string().describe(w.createComponent.code)},handler:async e=>{const{code:t}=e,n=q("component-generate");if(n.loadedNow)return{content:[{type:"text",text:`🧭 已自动加载规则(component-generate):\n${n.text}\n\n请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。`}],isError:!0};const{error:r}=await X("POST","/api/createComponent",{code:t});return r?{content:[{type:"text",text:`❌ 创建失败: ${r.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向插件发送组件创建指令"}]}}};function re(e){const t=function(e){const t=String(e||"").trim(),n=t.match(/^```[a-zA-Z0-9_-]*\n([\s\S]*?)\n```$/);return n?.[1]?.trim()||t}(e);return function(e){const t=String(e||"");return/&(lt|gt);/i.test(t)||/&#x0*3c;|&#x0*3e;/i.test(t)||/&#0*60;|&#0*62;/i.test(t)}(t)?function(e){const t={"&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&apos;":"'","&amp;":"&","&nbsp;":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|&#39;/gi,e=>t[e.toLowerCase()]||e);return n=n.replace(/&#(\d+);/g,(e,t)=>{const n=Number.parseInt(t,10);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n=n.replace(/&#x([0-9a-fA-F]+);/g,(e,t)=>{const n=Number.parseInt(t,16);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n}(t).trim():t}function oe(e){const t=String(e||"").trim();if(!t)return t;const n=function(e){const t=String(e||"").match(/<main\b[\s\S]*?<\/main>/i);return(t?.[0]||"").trim()}(t);if(n)return n;const r=function(e){const t=[...String(e||"").matchAll(/```(?:html)?\s*([\s\S]*?)```/gi)];for(const e of t){const t=String(e[1]||"").trim();if(/<(main|div|section|article|aside|header|footer)\b/i.test(t))return t}return""}(t);if(r)return r;const o=t.search(/<(main|div|section|article|aside|header|footer)\b/i);return o>0?t.slice(o).trim():t}const ie={name:"agent_create_page",description:w.createPage.description,inputSchema:{code:n.string().optional().describe(w.createPage.code),filePath:n.string().optional().describe(w.createPage.filePath),projectDir:n.string().describe(w.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe(w.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=c.resolve(e.filePath);r=await p.readFile(t,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取文件失败: ${e.message}`}],isError:!0}}if(!r)return{content:[{type:"text",text:"参数错误: 请提供 code 或 filePath"}],isError:!0};const o=oe(re(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:i,error:a}=await X("POST","/api/createPage",{code:o});if(a)return{content:[{type:"text",text:`❌ 发送失败: ${a.message}`}],isError:!0};let s=function(e,t="操作"){let n=`✅ ${t}已成功完成`;return e.targetNodeId&&(n+=`\n- 节点 ID: ${e.targetNodeId}`),e.documentId&&(n+=`\n- 文档: ${e.documentName||""} (${e.documentId})`),e.documentPageId&&(n+=`\n- 页面: ${e.documentPageName||""} (${e.documentPageId})`),n}(i,"设计稿生成");if(n)try{const e=t?c.resolve(t):process.cwd();s+=`\n- 代码已本地持久化至: ${(await te({baseDir:e,code:i.htmlCode||o,documentId:i.documentId,documentPageId:i.documentPageId,targetNodeId:i.targetNodeId,nodeName:i.nodeName,resourcePath:i.resourcePath,shape:i.shape,svg:i.svg,image:i.image})).htmlPath}`}catch(e){s+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:s}]}}};let ae=null;function se(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const ce="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 agent_create_page(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function de(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}const ue={name:"design",description:w.design.description,inputSchema:{requirement:n.string().describe(w.design.requirement),code:n.string().optional().describe(w.design.code),projectDir:n.string().optional().describe(w.design.projectDir),useComponentLibrary:n.boolean().optional().describe(w.design.useComponentLibrary),userConfirmedUseComponentLibrary:n.boolean().optional().default(!1).describe(w.design.userConfirmedUseComponentLibrary),teamLibraryName:n.string().optional().describe(w.design.teamLibraryName),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe(w.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,useComponentLibrary:o,userConfirmedUseComponentLibrary:i=!1,teamLibraryName:a,buildStrategy:s}=e;if("string"==typeof n&&n.trim()){const e=r?c.resolve(r):process.cwd();return ie.handler({code:se(n),projectDir:e})}if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};if(!P()||!L())return{content:[{type:"text",text:'⏸️ 执行 design 前,请先显式调用规则加载工具。\n\n请先调用:get_codify_guidelines({ scope: "all" })\n加载完成后,再次调用 design。'}],isError:!0};const d=t.trim();if(ae&&ae.requirement===d||(ae={requirement:d,confirmed:!1}),!(ae.confirmed||"boolean"==typeof o&&i))return{content:[{type:"text",text:"⏸️ 继续设计前,请先让用户确认:是否使用组件库来生成页面?\n\n请二选一后再次调用 design:\n1) 使用组件库:useComponentLibrary: true\n2) 不使用组件库:useComponentLibrary: false\n\n并且必须同时传:userConfirmedUseComponentLibrary: true"}],isError:!0};if(ae.confirmed=!0,!o){$=!1,I=!1,S="",j="",v="";const{loadedNow:e,text:n}=q("page-generate"),r=e?`🧭 自动加载规则(page-generate):\n${n}\n\n`:"🧭 规则状态:page-generate 已加载,本次不重复展开。\n\n";return ae=null,{content:[{type:"text",text:`${r}📋 收到需求:${t}\n\n请直接按 page-generate 规则生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}`}],isError:!1}}if(!a?.trim())return{content:[{type:"text",text:'⏸️ 你已选择“使用组件库生成页面”,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 先检查本地 .codify/library 是否已有可用团队库快照\n2) 若本地没有,再调用 get_library_list 获取团队库列表\n3) 询问用户选择具体团队库(禁止助手自动选择)\n\n用户确认后再次调用 design,并传入:\n- useComponentLibrary: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!0};T(`使用 ${a} 团队库`);const u=O("design",r?c.resolve(r):void 0);if(u)return u;if(!s)return{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design:\n1) full-components(全组件设计模式:包括组件、样式变量、图标系统;样式优先使用 var(...))\n2) hybrid(混合设计模式:仅必要组件 + 全量使用组件库样式变量与图标系统;样式优先使用 var(...))`}],isError:!0};const{loadedNow:m,text:l}=q("all"),p=m?`🧭 自动加载规则(page-generate + component-import):\n${l}\n\n`:"🧭 规则状态:page-generate + component-import 已加载,本次不重复展开。\n\n",g="full-components"===s?"\n【full-components 执行约束】\n- 页面功能区必须优先使用 <ui-component>,禁止把可组件化区块改为普通 HTML 自绘\n- <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 样式必须优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因\n- 若某区块未组件化,必须是“本地组件快照中不存在可替代组件”,并在输出前自检逐项说明缺失原因":"\n【hybrid 执行约束】\n- 视觉优先,关键功能区使用组件\n- 已使用的 <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 非关键区可自由绘制,但必须全量使用组件库样式变量与图标系统\n- 样式必须优先使用 variable.json 的 var(...) token;var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因";return{content:[{type:"text",text:`${p}📋 收到需求:${t}\n- 团队库:${a}\n- 构建策略:${de(s)}\n${g}\n\n请严格基于落盘组件库 JSON(index/components/icons/variable)生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}\n\n${F()}\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}],isError:!1}}},me={name:"get_code",description:w.getCode.description,inputSchema:{contentId:n.string().describe(w.getCode.contentId),documentId:n.string().optional().describe(w.getCode.documentId),documentPageId:n.string().optional().describe(w.getCode.documentPageId),projectDir:n.string().describe(w.getCode.projectDir),outDir:n.string().describe(w.getCode.outDir)},handler:async e=>{const{contentId:t,outDir:n,projectDir:r,documentId:o,documentPageId:i}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 contentId"}],isError:!0};const{data:a,error:s}=await X("GET",`/api/getCode/${t}`);if(s)return{content:[{type:"text",text:`❌ 获取失败: ${s.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?c.resolve(r):process.cwd(),u=await te({baseDir:d,outDir:n,documentId:o,documentPageId:i,contentId:t,code:a.code,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image});let m=`✅ 代码拉取完成\n- 目录: ${u.targetDir}\n- 文件: ${u.htmlFileName}\n`;return u.shapeCount>0&&(m+=`- Shape: ${u.shapeCount} 个\n`),u.svgCount>0&&(m+=`- SVG: ${u.svgCount} 个\n`),u.imageCount>0&&(m+=`- Image: ${u.imageCount} 个\n`),{content:[{type:"text",text:m}]}}},le={name:"get_code_list",description:w.getCodeList.description,inputSchema:n.object({}).describe(w.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getCodeList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};if(!Array.isArray(e)||0===e.length)return{content:[{type:"text",text:"📋 代码列表为空"}]};const n=e.length,r=n>=10?e.slice(0,10):e;let o=`✅ 成功获取代码列表 (共 ${n} 项)\n`;return n>=10&&(o+="⚠️ 仅展示最近更新的前 10 条记录\n"),o+=`\n${JSON.stringify(r,null,2)}`,{content:[{type:"text",text:o}]}}},pe={name:"get_selection_code",description:w.getSelectionCode.description,inputSchema:{projectDir:n.string().describe(w.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe(w.getSelectionCode.targetNodeId),syncToBase:n.boolean().default(!0).describe(w.getSelectionCode.syncToBase)},handler:async e=>{const{projectDir:t,targetNodeId:n,syncToBase:r=!0,_depth:i=0}=e;if(i>2)return{content:[{type:"text",text:"❌ 递归获取根节点深度过深,已停止。"}],isError:!0};const a=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}`:"/api/getSelectionCode",{data:s,error:d}=await X("GET",a);if(d)return 400===d.status&&"NoSelection"===d.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${d.message}`}],isError:!0};const u=t?c.resolve(t):process.cwd(),m=s.nodeInfo||{},{rootId:l,documentId:f,documentPageId:y,targetNodeId:h,nodeName:b,parentId:x}=m;if(h===l){return{content:[{type:"text",text:`✅ 成功获取并保存根节点代码\n- 节点: ${b} (${h})\n- 根节点: ${l}\n- 文件: ${(await te({baseDir:u,documentId:f,documentPageId:y,targetNodeId:l,nodeName:b,code:s.code,resourcePath:s.resourcePath,shape:s.shape,svg:s.svg,image:s.image})).htmlPath}。`}]}}{let e="",n="";if(r&&f&&y){const r=c.join(u,B,V,String(f).replace(/:/g,"-"),String(y).replace(/:/g,"-")),a=c.join(u,B,String(f).replace(/:/g,"-"),String(y).replace(/:/g,"-")),d=String(l).replace(/:/g,"-");let m="",b=r;const w=async()=>{const e=async e=>{if(!o(e))return"";const t=(await p.readdir(e)).filter(e=>e.endsWith(`-${d}.html`));if(0===t.length)return"";if(1===t.length)return t[0];const n=await Promise.all(t.map(async t=>({fileName:t,mtimeMs:(await p.stat(c.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return b=r,t;const n=await e(a);return n?(b=a,n):""};if(m=await w(),m||0!==i||(await pe.handler({projectDir:t,targetNodeId:l,syncToBase:!0,_depth:i+1}),m=await w()),m){const t=c.join(b,m);try{const n=await p.readFile(t,"utf8"),r=g(n),o=r.querySelector(`[data-node-id="${h}"]`);if(o)o.replaceWith(s.code);else if(x){const e=r.querySelector(`[data-node-id="${x}"]`);e&&e.insertAdjacentHTML("beforeend",s.code)}(o||x&&r.querySelector(`[data-node-id="${x}"]`))&&(await p.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const a=await te({baseDir:u,documentId:f,documentPageId:y,targetNodeId:h,nodeName:b,code:"",resourcePath:s.resourcePath,shape:s.shape,svg:s.svg,image:s.image});let d=`✅ 成功获取子节点代码\n- 节点: ${b} (${h})\n- 根节点: ${l}`;return d+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。可以直接针对返回代码进行修改。",n&&(d+=`\n- ${n}`),h!==l&&(d+=`\n- 资源同步: 图片(${a.imageCount}), 图标(${a.svgCount}), 图形(${a.shapeCount})`),{content:[{type:"text",text:`${d}\n\n代码内容:\n\n${s.code}`}]}}}},ge={name:"get_user_info",description:w.getUserInfo.description,inputSchema:n.object({}).describe(w.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getUserInfo");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};let n=`✅ 用户信息\n\n👤 用户: ${e.realname||e.userName||e.userId}\n`;if(e.quota){const t=Number(e?.quota?.plan??e?.plan??0),r=t>0?"不限量":String(e.quota.mcp_generate_limit||"无"),o=t>0?"不限量":String(e.quota.mcp_get_limit||"无");n+="\n📊 配额:\n",n+=` - 生成设计: ${e.quota.mcp_generate_count||0} / ${r}\n`,n+=` - 获取代码: ${e.quota.mcp_get_count||0} / ${o}\n`}return e.teams?.length&&(n+="\n👥 团队:\n",e.teams.forEach((e,t)=>n+=` ${t+1}. ${e.name} (ID: ${e.teamId})\n`)),{content:[{type:"text",text:n}]}}},fe={name:"agent_update_node",description:w.updateNode.description,inputSchema:{documentId:n.string().optional().describe(w.updateNode.documentId),documentPageId:n.string().optional().describe(w.updateNode.documentPageId),targetNodeId:n.string().optional().describe(w.updateNode.targetNodeId),code:n.string().describe(w.updateNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止灾难性地覆盖当前选中的未知图层,本次操作已被拦截。请明确指定 targetNodeId 或在 HTML 包含 data-node-id。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});return a?{content:[{type:"text",text:`❌ 局部更新失败: ${a.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [局部修改] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}},ye={name:"agent_replace_node",description:w.replaceNode.description,inputSchema:{documentId:n.string().optional().describe(w.replaceNode.documentId),documentPageId:n.string().optional().describe(w.replaceNode.documentPageId),targetNodeId:n.string().optional().describe(w.replaceNode.targetNodeId),code:n.string().describe(w.replaceNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止把您当前极可能选中的大容器直接覆盖损毁,系统已拦截。请务必明确目标节点的 ID。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});return a?{content:[{type:"text",text:`❌ 结构替换失败: ${a.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [结构替换] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}};function he(e){return String(e).replace(/:/g,"-")}async function be(e,t){if(!o(e))return"";const n=(await p.readdir(e,{withFileTypes:!0})).filter(e=>e.isFile()&&e.name.endsWith(`-${t}.html`));if(0===n.length)return"";const r=await Promise.all(n.map(async t=>{const n=c.join(e,t.name);return{fullPath:n,mtimeMs:(await p.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function xe(e,t){if(!o(e))return"";const n=await p.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=c.join(e,o.name);if(o.isDirectory()){const e=await xe(n,t);if(e){const t=await p.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await p.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const we={name:"get_design_diff",description:w.getDesignDiff.description,inputSchema:{projectDir:n.string().describe(w.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe(w.getDesignDiff.targetNodeId),filePath:n.string().optional().describe(w.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?c.resolve(t):process.cwd();let i=n,a="",s=r||"";if(s)try{const e=c.isAbsolute(s)?s:c.resolve(o,s);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 无法读取指定的 filePath: ${e.message}`}],isError:!0}}let d="";if(!a||!i){const e=i?`/api/getSelectionCode?id=${encodeURIComponent(i)}`:"/api/getSelectionCode",{data:t,error:n}=await X("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:u,documentId:m,documentPageId:l}=t.nodeInfo;if(d=r,i||(i=u),!a&&r){const e=String(r).replace(/:/g,"-"),t=m&&l?c.join(o,B,V,he(m),he(l)):"",n=m&&l?c.join(o,B,he(m),he(l)):"";if(t&&(s=await be(t,e)),!s&&n&&(s=await be(n,e)),!s){const t=c.join(o,B,V);s=await xe(t,e)}if(!s){const t=c.join(o,B);s=await xe(t,e)}s&&(a=await p.readFile(s,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 .codify 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:u,error:m}=await X("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(m)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${m.message}`}],isError:!0};let l=u.diffs||[];return l&&!Array.isArray(l)&&Array.isArray(l.diffs)&&(l=l.diffs),0===l.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${l.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(l,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 .codify 的基准文件。`}]}}},$e={name:"agent_remove_node",description:w.removeNode.description,inputSchema:{documentId:n.string().optional().describe(w.removeNode.documentId),documentPageId:n.string().optional().describe(w.removeNode.documentPageId),targetNodeId:n.string().optional().describe(w.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await X("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});return i?{content:[{type:"text",text:`❌ 删除失败: ${i.message}`}],isError:!0}:{content:[{type:"text",text:`✅ 已成功发送删除指令\n- 目标节点 ID: ${o.targetNodeId||t||"当前选中图层"}`}]}}},Ie={name:"agent_sync_design",description:w.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe(w.syncToDesign.documentId),documentPageId:n.string().optional().describe(w.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe(w.syncToDesign.targetNodeId),filePath:n.string().describe(w.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe(w.syncToDesign.userConfirmed)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r,filePath:o,userConfirmed:i=!1}=e;if(!i)return{content:[{type:"text",text:"⏸️ 已阻止自动同步到画布。\n\n仅当用户明确提出“请同步到画布”时,才允许调用 agent_sync_design。\n请先向用户确认,再以 userConfirmed=true 重新调用。"}],isError:!0};let a="";if(o)try{const e=c.resolve(o);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取本地文件失败: ${e.message}`}],isError:!0}}if(!a)return{content:[{type:"text",text:"❌ 同步失败: 未能从指定路径读取到有效代码"}],isError:!0};const{data:s,error:d}=await X("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});return d?{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${s.targetNodeId?`\n- 根节点 ID: ${s.targetNodeId}`:""}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 .codify 基准。`}]}}};function Se(e){return e.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)}function je(e){return String(e??"").trim().toLowerCase()}function ve(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ne(e){if(!ve(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.ukey&&e.ukey.trim()?e.ukey:void 0,r="string"==typeof e.description?e.description:"",o=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||ve(e)?e:String(e)}(e.value);return void 0===o?{ukey:n,name:t,description:r}:{ukey:n,name:t,description:r,value:o}}function _e(e){return function(e){return Array.isArray(e)?e:[]}(e).map(Ne).filter(e=>!!e)}function Ce(e){const t=_e(e.paints);return{boxShadow:_e(e.effects),color:t,fill:t,borderRadius:_e(e.cornerRadiuses),padding:_e(e.paddings),gap:_e(e.spacings),borderWidth:_e(e.strokeWidths),typography:_e(e.texts)}}function Pe(e){return null===e||["string","number","boolean"].includes(typeof e)}function Le(e){const t=Number(e);return Number.isFinite(t)?t:0}function De(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[Le(t[0]),Le(t[1])];const n=ve(e.style)?e.style:{};return[Le(e.width??n.width),Le(e.height??n.height)]}function Ae(e){if(Array.isArray(e)){const t=e.filter(Pe);return t.length>0?t:void 0}if(ve(e)){const t=Array.isArray(e.enum)?e.enum.filter(Pe):void 0,n=Pe(e.default)?e.default:void 0;if(t&&t.length>0){return 2===t.length&&t.includes(!1)&&t.includes(!0)&&"boolean"==typeof n?n:t}return void 0!==n?n:void 0}if(Pe(e))return e}function Ee(e){if(!ve(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=Ae(r);void 0!==e&&(t[n]=e)}return t}function Te(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function ke(e){if(!ve(e))return{};const t={},n=e=>{const t=String(e||"").trim();return!!t&&(t.includes("+")||/^[a-zA-Z0-9_-]+:\d+$/.test(t))};for(const[r,o]of Object.entries(e)){if("string"!=typeof o)continue;const e=r.trim(),i=o.trim();e&&i&&(n(i)||(t[e]=i))}return t}function Me(e){return"string"==typeof e?e:""}function Oe(e){return"string"==typeof e&&e.trim()?e:void 0}function Fe(e){const t=ve(e)?e:{},n=Array.isArray(t.components)?t.components:Array.isArray(t["component-set"])?t["component-set"]:[],r=[],o=function(e){if(!Array.isArray(e))return[];const t=[],n=new Set;for(const r of e){if("string"==typeof r&&r.trim()){const e=r.trim(),o=`::${e}`;n.has(o)||(n.add(o),t.push({name:e}));continue}if(ve(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o="string"==typeof r.id&&r.id.trim()?r.id.trim():void 0,i=`${o||""}::${e}`;n.has(i)||(n.add(i),t.push({id:o,name:e}))}}return t}(t.icons);for(const e of n){if(!ve(e))continue;const t="string"==typeof e.name?e.name:"",n=Me(e.description),o=Oe(e.id),i=Oe(e.ukey),a=ve(e.props)?e.props:ve(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:Oe(e.cover),size:De(e),props:Ee(a),slots:Te(s),instance_swap:ke(c)})}return{variables:ve(t.variables)?t.variables:ve(t.style)?Ce(t.style):ve(t.styles)?Ce(t.styles):ve(t.tokens)?t.tokens:{},components:r,icons:o}}function Je(e,t){const n=Se(e||"component").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function Ge(e){try{const t=await p.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function He(e,t){await p.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(Pe(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(Pe)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(ve(e)){const o=Object.entries(e).filter(([,e])=>void 0!==e);return 0===o.length?"{}":`{\n${o.map(([e,o])=>`${n(t+1)}${JSON.stringify(e)}: ${r(o,t+1)}`).join(",\n")}\n${n(t)}}`}return JSON.stringify(String(e))};return r(e,0)}(t)}\n`,"utf8")}async function qe(e){if(!o(e))return[];const t=[],n=c.join(e,"catalog.json"),r=await Ge(n);if(Array.isArray(r?.libraries))for(const n of r.libraries){const r=String(n?.id||"").trim(),i=String(n?.name||"").trim(),a=String(n?.slug||"").trim(),s=String(n?.indexPath||"").trim();if(!r||!s)continue;const d=c.join(e,s);o(d)&&t.push({id:r,name:i||a||r,slug:a||Se(i||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await p.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=c.join(e,r),a=await Ge(c.join(i,"meta.json"));if(a?.paths?.index){const e=c.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.name||"").trim(),o=String(a?.teamLibrary?.slug||"").trim();t.push({id:r,name:n||o||r,slug:o||Se(n||r),indexAbsPath:e});continue}}const s=await p.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of s){if(!e.isDirectory())continue;const n=c.join(i,e.name,"index.json");if(!o(n))continue;const a=e.name;t.push({id:r,name:a,slug:a,indexAbsPath:n})}}const a=function(e){const t=new Map;for(const n of e)n.indexAbsPath&&t.set(n.indexAbsPath,n);return Array.from(t.values())}(t);for(const e of a){const t=await Ge(e.indexAbsPath);if(!t)continue;const n=String(t?.teamLibrary?.id||"").trim(),r=String(t?.teamLibrary?.name||"").trim();n&&(e.id=n),r&&(e.name=r,e.slug=Se(r)),"number"==typeof t?.summary?.componentCount&&(e.componentCount=t.summary.componentCount),"number"==typeof t?.summary?.iconCount&&(e.iconCount=t.summary.iconCount)}return a}async function Re(){const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function ze(e){return null==e||"string"==typeof e&&""===e.trim()}const Ue=[R,ue,me,le,ge,{name:"get_library_list",description:w.getLibraryList.description,inputSchema:n.object({}).describe(w.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};const n=e?.data??e;if(!Array.isArray(n)||0===n.length)return{content:[{type:"text",text:"📚 团队库列表为空(请确认插件已连接且当前文件已订阅团队库)"}]};const r=n.map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`✅ 当前文件团队库列表(共 ${r.length} 个)\n\n${JSON.stringify(r,null,2)}\n\n若用户要求“使用某组件库生成页面”,请按最短流程:\n1) 先检查 .codify/library 是否已有该团队库 JSON(优先本地)\n2) 若本地没有,再确定团队库(可回复:使用 <团队库名称> 团队库)\n3) 调用 get_component_info 拉取并落盘 index + 组件详情 JSON\n`}]}}},{name:"get_component_info",description:w.getComponentInfo.description,inputSchema:{projectDir:n.string().describe(w.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe(w.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe(w.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe(w.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:i=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const a=c.resolve(t),d=c.join(a,B,"library"),u=c.join(d,"catalog.json"),m=await qe(d);let l=n,p=r;if(!l&&!p){if(m.length>0){const e=m.map(e=>({id:e.id,name:e.name,componentCount:e.componentCount??void 0,iconCount:e.iconCount??void 0,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`✅ 检测到本地 .codify/library 已有落盘团队库(共 ${e.length} 个),优先复用本地快照。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await Re()).map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`⚠️ 你还没有指定团队库。\n\n当前文件团队库列表(共 ${e.length} 个):\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info 并传入 teamLibraryName。\n\n组件库生成页面的标准流程:先本地复用,未命中才远端拉取并落盘。`}]}}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}}const g=function(e,t,n){if(!e.length)return{status:"none"};let r=[...e];if(!ze(t)){const e=je(String(t)),n=je(Se(String(t))),o=r.filter(t=>{const r=je(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!ze(n)){const e=je(String(n)),t=je(Se(String(n))),o=r.filter(n=>{const r=je(n.name),o=je(n.slug);return r===e||o===e||r===t||o===t});if(o.length>0)r=o;else{const t=r.filter(t=>{const n=je(t.name),r=je(t.slug);return n.includes(e)||e.includes(n)||r.includes(e)||e.includes(r)});if(!t.length)return{status:"none"};r=t}}return 1===r.length?{status:"matched",entry:r[0]}:{status:"ambiguous",candidates:r}}(m,l,p);if("matched"===g.status){const e=g.entry,t=await Ge(e.indexAbsPath),n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||l||"",i=r||e.id||l||"",s=c.dirname(e.indexAbsPath),d=c.join(s,"icons.json"),u=c.join(s,"variable.json");return k({teamLibraryName:o||i,filePath:e.indexAbsPath,baseDir:a}),{content:[{type:"text",text:`♻️ 已命中本地团队库快照,跳过远端拉取。\n- 团队库: ${o} (${i})\n- 索引文件: ${e.indexAbsPath}\n- 图标文件: ${d}\n- 变量文件: ${u}\n- 组件目录: ${c.join(s,"components")}\n\n⚠️ 下一步生成页面前,必须先读取 index.json,再按 components/\${key}.json 读取组件详情,并读取 icons.json + variable.json。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}]}}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 .codify/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!l&&p)try{const e=await Re(),t=je(p),n=e.filter(e=>je(e?.name)===t),r=e.filter(e=>!ze(e?.name)&&je(e?.name).includes(t)),o=n.length>0?n:r;if(0===o.length)return{content:[{type:"text",text:`❌ 未找到匹配的团队库:${p}\n\n请先调用 get_library_list 查看可用团队库,或直接传 teamLibraryId。`}],isError:!0};if(o.length>1){const e=o.map(e=>({id:e?.id,name:e?.name}));return{content:[{type:"text",text:`⚠️ 团队库名称匹配到多个候选,请更精确地指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n你可以改用 teamLibraryId 直接调用 get_component_info。`}]}}l=o[0]?.id,p=o[0]?.name}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}if(!l)return{content:[{type:"text",text:"参数错误: 未能确定 teamLibraryId"}],isError:!0};if(!p)try{const e=(await Re()).find(e=>String(e?.id||"")===String(l));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(l)}&includePropertyDetails=${i?"true":"false"}`,{data:y,error:h}=await X("GET",f);if(h)return{content:[{type:"text",text:`❌ 获取失败: ${h.message}`}],isError:!0};const b=Fe(y?.data??y),x=Se(l),w=Se(p||l),$=(new Date).toISOString(),I=c.join(d,x),S=c.join(I,w),j=c.join(S,"components");o(I)||s(I,{recursive:!0}),o(S)||s(S,{recursive:!0}),o(j)||s(j,{recursive:!0});const v=[],N=[],_=new Set;b.components.forEach((e,t)=>{let n=Je(e.name,t),r=1;for(;_.has(n);)r+=1,n=`${Je(e.name,t)}-${r}`;_.add(n),v.push({key:n,id:e.id,ukey:e.ukey,name:e.name,description:e.description||"",cover:e.cover}),N.push(He(c.join(S,`components/${n}.json`),{id:e.id,ukey:e.ukey,name:e.name,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const C="icons.json",P="variable.json";await Promise.all([...N,He(c.join(S,C),{icons:b.icons}),He(c.join(S,P),{variables:b.variables})]);const L={schemaVersion:2,generatedAt:$,teamLibrary:{id:String(l),name:String(p||l)},summary:{componentCount:b.components.length,iconCount:b.icons.length,componentFileCount:v.length},components:v,files:{componentsDir:"components",pathTemplate:"components/{key}.json",icons:C,variables:P}},D=c.join(S,"index.json");await He(D,L);const A={schemaVersion:1,updatedAt:$,teamLibrary:{id:String(l),name:String(p||l),slug:w},paths:{index:c.relative(I,D),icons:c.relative(I,c.join(S,C)),variables:c.relative(I,c.join(S,P)),componentsDir:c.relative(I,j)}};return await He(c.join(I,"meta.json"),A),await async function(e,t){const n=await Ge(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id);r.push(t),r.sort((e,t)=>e.name.localeCompare(t.name,"zh-Hans-CN"));const o={schemaVersion:1,updatedAt:t.updatedAt,libraries:r};await He(e,o)}(u,{id:x,name:String(p||l),slug:w,updatedAt:$,componentCount:b.components.length,iconCount:b.icons.length,indexPath:c.relative(d,D)}),k({teamLibraryName:p||l,filePath:D,baseDir:a}),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${l})\n- 目录: ${S}\n- 索引文件: ${D}\n- 组件详情文件: ${v.length} 个\n- 图标文件: ${c.join(S,C)}\n- 变量文件: ${c.join(S,P)}\n- 全局目录索引: ${u}\n\n⚠️ 下一步生成页面前,必须先读取索引文件,再按固定路径 components/\${key}.json 读取所需组件详情,并读取 icons.json + variable.json:\n结构速记:components[].{id,name,description,size,props,slots,slot_layout,instance_swap} + icons[].{id,name} + variables{}。\n先确认构建策略:full-components 或 hybrid(推荐)。\n变量引用统一使用 var(...),例如 gap-[var(间距_index_10/sm)],变量名称应来自 variable.json 的 variables。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}]}}},ie,ne,pe,fe,ye,Ie,we,$e];const We=new e({name:"Codify-MCP-Client",version:"1.0.23"},{capabilities:{resources:{},tools:{},prompts:{}}});var Be;We.registerResource("rule","codify://rule",{description:"Codify 聚合规范(page-generate + component-import + 最终硬闸门)"},async e=>({contents:[{uri:e.toString(),text:G("all"),mimeType:"text/markdown"}]})),Be=We,Ue.forEach(e=>{Be.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await We.connect(e)}catch(e){process.exit(1)}