@codify-ai/mcp-client 1.0.33 → 1.0.34
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 +2 -2
- package/dist/component-import.md +1 -1
- package/dist/en.template.json +11 -6
- package/dist/index.js +1 -1
- package/dist/page-generate.md +5 -3
- package/dist/variable-generate.md +19 -8
- package/dist/variable-import.md +2 -2
- package/dist/zh.template.json +13 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -156,14 +156,14 @@ codify://getCode/{contentId}
|
|
|
156
156
|
|
|
157
157
|
### 🎨 生成与设计
|
|
158
158
|
|
|
159
|
-
#### **
|
|
159
|
+
#### **design_page**
|
|
160
160
|
|
|
161
161
|
根据自然语言需求生成符合 Codify 规范的 HTML+CSS 代码。
|
|
162
162
|
|
|
163
163
|
- **参数**: `requirement` (必填)
|
|
164
164
|
- **示例**: "帮我设计一个深色模式的登录页面"
|
|
165
165
|
|
|
166
|
-
#### **
|
|
166
|
+
#### **submit_page_to_canvas**
|
|
167
167
|
|
|
168
168
|
将生成的 HTML 代码发送到 MasterGo 并创建新页面。
|
|
169
169
|
|
package/dist/component-import.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
示例:
|
|
29
29
|
```text
|
|
30
|
-
get_library_list -> get_component_info -> 读取 index.md/components/icons/variable -> 生成页面 ->
|
|
30
|
+
get_library_list -> get_component_info -> 读取 index.md/components/icons/variable -> 生成页面 -> submit_page_to_canvas
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## 数据来源(唯一事实)
|
package/dist/en.template.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"inputSchema": "Optional: scope (string array, items in page-generate / component-import / component-generate / variable-import / variable-generate). Default [\"page-generate\"]. Multiple scopes are concatenated and a Final Hard Gate section is appended."
|
|
5
5
|
},
|
|
6
6
|
"createPage": {
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "[INTERNAL submission tool · Only call after design_page has produced final HTML] Submits a fully-generated page HTML to the {{pluginName}} canvas. ⚠️ When the user says \"design / create / make / build / draw / generate a page\" (or the Chinese equivalents 设计 / 创建 / 做一个 / 帮我画 / 生成 一个页面), DO NOT call this tool directly — you MUST first call design_page to go through requirement decomposition and design-source selection. This tool only ships the final HTML produced by the design_page flow.\n\n[Performance Tip] If pure HTML is already saved as a local file, prefer passing the absolute path via filePath to save tokens. If the code is temporarily generated, use the code parameter directly.\n\nReverse Transpilation Warning: Strictly forbidden to directly send Vue/React business code containing dynamic bindings ({{}}) or framework directives! If you want to convert business code to a design, you MUST first perform \"Reverse Transpilation\" and, when needed, load the official rules through get_guidelines before sending pure HTML with static mock data.",
|
|
8
8
|
"code": "[Optional] The HTML code content to send. Use ONLY if the code is temporarily generated and not saved as a file.",
|
|
9
9
|
"filePath": "[Optional] The absolute path of the local HTML file. If the file exists locally, you MUST pass this parameter; the tool will automatically read and persist it.",
|
|
10
10
|
"projectDir": "[Required] The absolute path of the user's current workspace root directory.",
|
|
@@ -44,11 +44,16 @@
|
|
|
44
44
|
"userConfirmed": "[Required] Whether the user explicitly confirmed syncing to canvas. Only pass true after explicit user request."
|
|
45
45
|
},
|
|
46
46
|
"getSelectionCode": {
|
|
47
|
-
"description": "Get HTML/CSS code for the currently selected layer (or a specified layer) in MasterGo and intelligently persist it to the local project directory. This tool ONLY reads/syncs context — do NOT chain agent_update_node / agent_replace_node / sync_to_design after this call unless the user has issued an explicit modification request. The generated code is typically used for partial modifications or syncing local base files.
|
|
47
|
+
"description": "Get HTML/CSS code for the currently selected layer (or a specified layer) in MasterGo and intelligently persist it to the local project directory. This tool ONLY reads/syncs context — do NOT chain agent_update_node / agent_replace_node / sync_to_design after this call unless the user has issued an explicit modification request. The generated code is typically used for partial modifications or syncing local base files. For a rendered preview image (visual reference), call get_selection_image separately.",
|
|
48
48
|
"projectDir": "[Required] The absolute path of the user's current workspace root directory.",
|
|
49
49
|
"targetNodeId": "[Optional] MasterGo layer ID (e.g., 123:456). If provided, the code for that ID will be pulled directly; if not, the code for the currently selected layer will be pulled.",
|
|
50
|
-
"syncToBase": "[Optional] Whether to sync the obtained child layer code back to the base HTML file in the local {{docDir}} directory (merge update). Defaults to true."
|
|
51
|
-
|
|
50
|
+
"syncToBase": "[Optional] Whether to sync the obtained child layer code back to the base HTML file in the local {{docDir}} directory (merge update). Defaults to true."
|
|
51
|
+
},
|
|
52
|
+
"getSelectionImage": {
|
|
53
|
+
"description": "Get a rendered preview (PNG by default) of the currently selected layer (or a specified layer) in MasterGo. Call this ONLY when you need a visual reference — e.g. \"match the design\", \"reproduce this design\", \"follow the visual\", \"reference the mockup\", \"还原设计稿\", \"对照视觉\". The response includes an image content block for multimodal grounding and persists a copy to {{docDir}}/.preview/ so the user can verify what the model saw. For routine code sync / small edits use get_selection_code, not this tool.",
|
|
54
|
+
"projectDir": "[Required] Absolute path of the workspace root. Previews are persisted to {{docDir}}/.preview/{documentId}/{pageId}/{nodeId}.png under this root.",
|
|
55
|
+
"targetNodeId": "[Optional] MasterGo layer ID (e.g., 123:456). If provided, that layer is exported; otherwise the currently selected layer is exported.",
|
|
56
|
+
"targetNodeIds": "[Optional] Batch list of target layer IDs. When provided, previews are exported in list order."
|
|
52
57
|
},
|
|
53
58
|
"createComponent": {
|
|
54
59
|
"description": "Create a MasterGo master component or component set (variant). Only when creating master components/component sets, load get_guidelines(scope=[\"component-generate\"]) first, then call this tool. Do not use component-generate for team/component-library page generation. This tool only sends code to plugin and does not perform fallback rule validation.",
|
|
@@ -67,11 +72,11 @@
|
|
|
67
72
|
"inputSchema": "Get code list without parameters"
|
|
68
73
|
},
|
|
69
74
|
"design": {
|
|
70
|
-
"description": "
|
|
75
|
+
"description": "[PAGE DESIGN MAIN ENTRY · MUST be the first call] When the user expresses any page-level generation intent — \"design / create / make / build / draw / generate a page\" or the Chinese equivalents 设计 / 创建 / 做一个 / 帮我画 / 生成 一个页面 — you MUST call this tool first. NEVER call submit_page_to_canvas directly to skip this flow. This tool owns requirement decomposition, design-source selection, and HTML+CSS generation; the final HTML is then handed off to submit_page_to_canvas. Fixed flow: first let the user choose one of three design sources — free-draw / current-file-variables / component-library. Choosing current-file-variables enforces get_variables to persist the current file variables and then references them via variable-import.md. Choosing component-library asks for a team library and a build strategy. Only after these confirmations will the page be generated.",
|
|
71
76
|
"requirement": "Interface requirement description, e.g., \"a beautiful login page\", \"a modern dashboard interface\", etc.",
|
|
72
77
|
"code": "[Optional] If full code is already generated (can include Markdown + <main>), it will auto-extract <main> and submit directly to canvas.",
|
|
73
78
|
"projectDir": "[Optional] Absolute path of the user workspace root. Required when reading or writing {{docDir}} snapshots (variables / component library).",
|
|
74
|
-
"designSource": "[Required by workflow] Design source, one of: free-draw / current-file-variables / component-library. The user must explicitly choose each
|
|
79
|
+
"designSource": "[Required by workflow] Design source, one of: free-draw / current-file-variables / component-library. The user must explicitly choose each design_page call.",
|
|
75
80
|
"userConfirmedDesignSource": "[Required by workflow] Whether the user confirmation for the three-way design-source choice has been completed in this page-generation flow.",
|
|
76
81
|
"teamLibraryName": "[Required in component mode] User-confirmed team library name. Must be reconfirmed each time; assistant must not auto-pick.",
|
|
77
82
|
"teamLibraryId": "[Recommended in component mode] User-confirmed team library ID. When the ID is already known (e.g. from get_library_list), pass it alongside teamLibraryName; used to dedupe same-library checks accurately and avoid false reuse when two libraries normalize to the same name.",
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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,mkdirSync as i,statSync as a}from"fs";import s,{dirname as c,resolve as d}from"path";import{fileURLToPath as l}from"url";import u from"axios";import m,{mkdir as p,writeFile as g,readdir as f,stat as y,readFile as b}from"fs/promises";import{parse as h}from"node-html-parser";const v={serverName:"Codify-MCP-Client",displayName:"Codify",pluginName:"Codify 插件",defaultServerUrl:"https://mcp.codify-api.com",serverUrlEnv:"CODIFY_SERVER_URL",accessKeyEnv:"CODIFY_ACCESS_KEY",docDir:".codify",uriScheme:"codify"};function $(e){return e.replace(/\{\{displayName\}\}/g,v.displayName).replace(/\{\{pluginName\}\}/g,v.pluginName).replace(/\{\{docDir\}\}/g,v.docDir).replace(/\{\{uriScheme\}\}/g,v.uriScheme)}const I=c(l(import.meta.url));function x(e){return $(r(d(I,e),"utf-8"))}const w=x("page-generate.md"),S=x("component-import.md"),j=x("component-generate.md"),_=x("variable-import.md"),N=x("variable-generate.md"),P=c(l(import.meta.url));function D(e){return"string"==typeof e?$(e):Array.isArray(e)?e.map(D):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,t])=>[e,D(t)])):e}const C=function(e){const t=d(P,`${e}.template.json`),n=r(t,"utf8");return D(JSON.parse(n))}("en"===process.env.LANG?"en":"zh"),A=process.argv.find(e=>e.startsWith("--url=")),L=A?A.slice(6):process.env[v.serverUrlEnv]||v.defaultServerUrl,E=process.env.ACCESS_KEY||process.env[v.accessKeyEnv],T=v.docDir,M="design",O=s.join(T,"created"),V=`请确认已启动 MasterGo,并且 ${v.pluginName} 处于连接状态。`,F=["/api/getTeamLibraryList","/api/getComponentInfo","/api/getVariables","/api/updateVariables","/api/removeVariable","/api/createPage","/api/getSelectionCode","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const k=e=>null==e||""===e||"string"==typeof e&&""===e.trim();function J(e){return String(e??"unknown-document").trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)||"unknown-document"}function G(e){return e?.trim()?s.resolve(e.trim()):""}async function q(e,t,n=null){const r={};E&&(r.Authorization=`Bearer ${E}`);const o={method:e,url:`${L}${t}`,headers:r,...n&&{data:n}};try{return{data:(await u(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="认证失败,请检查 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=F.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mg")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes(v.pluginName)||e.includes("mg")||e.includes("MasterGo")?e:`${e}\n\n${V}`:V}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function z(e,t,n,r){if(k(e))return 0;const a="string"==typeof e?JSON.parse(e):e,c=Object.keys(a);if(0===c.length)return 0;const d=s.join(t,n);o(d)||i(d,{recursive:!0});const l=Object.entries(a).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=s.join(d,`${o}.${i}`);let c=t;if("string"==typeof c&&c.startsWith("http"))try{const e=await u.get(c,{responseType:"arraybuffer"});await m.writeFile(a,e.data)}catch(e){}else if("string"==typeof c&&c.startsWith("data:image/")){const e=c.split(";base64,");2===e.length&&await m.writeFile(a,e[1],"base64")}else{const e="object"==typeof c?JSON.stringify(c,null,2):c,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await m.writeFile(a,e,t)}});return await Promise.all(l),c.length}async function R({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:a,targetNodeId:c,nodeName:d,code:l,resourcePath:u,shape:p,svg:g,image:f,isSelection:y=!1}){let b=t?s.resolve(e,t):n&&r?s.join(e,T,M,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!a||y||c?"":"code"):y?s.join(e,O,"selection"):a?s.join(e,O,a):s.join(e,O);o(b)||i(b,{recursive:!0});const h=String(c||Date.now()).replace(/:/g,"-"),v=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),$=y||c||d?`${v}-${h}.html`:`${a||"index"}.html`,I=s.join(b,$);l&&await m.writeFile(I,l,"utf8");const x=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!k(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}(u),w=await Promise.all([z(p,b,x.shape,"json"),z(g,b,x.svg,"svg"),z(f,b,x.image,"png")]);return{targetDir:b,htmlFileName:$,htmlPath:I,shapeCount:w[0],svgCount:w[1],imageCount:w[2],resourcePathMap:x}}let U=!1,B=!1,W="",H="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",se=0,ce="";function de(e){const t=Date.now(),n=G(e);let r=!1;return(se>0&&t-se>18e5||!!n&&!!ce&&n!==ce)&&(U=!1,B=!1,W="",H="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",Ie.clear(),r=!0),se=t,n&&(ce=n),{reset:r}}function le(e){switch(e){case"page-generate":Z=!0;break;case"component-import":Q=!0;break;case"component-generate":X=!0;break;case"variable-import":ee=!0;break;case"variable-generate":te=!0}}function ue(e){if(Array.isArray(e))return e.some(e=>ue(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.reference||Object.values(t).some(e=>ue(e))}function me(e){if(Array.isArray(e))return e.some(e=>me(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.value||Object.values(t).some(e=>me(e))}function pe(e){const t=e.filter(e=>function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"deleteVariable"!==String(t.action||"")&&void 0===t.id}(e));if(0===t.length)return null;const n=t.some(e=>ue(e)),r=t.some(e=>me(e));return n&&r?{content:[{type:"text",text:"❌ 本次变量批次同时包含 value 和 reference。\n\n请拆成两批:\n1) 先提交基础变量(只包含 value),调用 update_variables。\n2) 立即调用 get_variables,落盘最新变量和 ID。\n3) 再提交语义变量(通过 reference 引用基础变量)。\n4) 最后再次调用 get_variables 刷新落盘数据。"}],isError:!0}:null}function ge(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function fe(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function ye(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return ge(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?ge(n[1]):""}(e.requirement),n=e.teamLibraryId?.trim()||"",r=n&&H&&n===H,o=!n&&!H&&B&&W.trim()&&t.trim()&&(i=t,fe(W)===fe(i));var i;U=!0,B&&(r||o)||(B=!1,H="",K="",Y="",W=t)}function be(){U=!1,B=!1,W="",H="",K="",Y=""}function he(){if(!U||!B)return"";return`\n\n🧩 组件库模式已就绪。请按 index.md 导航读取快照,再生成页面:${W?`\n- 团队库: ${W}`:""}${K?`\n- 组件库索引说明: ${K}`:""}\n- 读取顺序:index.md -> components/*.json -> icons.json -> variable.json\n- 所有 name 必须原样来自落盘文件:components、icons、instance_swap、variable\n- 变量写 var(name),例如 {"name":"填充色/常规"} -> var(填充色/常规)\n- instance_swap 写在 <ui-component instance_swap='...'> 上;需要开关类 props 时同步设为 true\n- 不臆造组件名、图标名、props;完整细则以 component-import.md 为准`}function ve(e){const t=G(e);if(!t)return!1;const n=s.join(t,T,"variable");try{if(!o(n))return!1;const e=require("fs").readdirSync(n).filter(e=>e.toLowerCase().endsWith(".json"));for(const t of e){const{names:e}=xe(s.join(n,t));if(e.length>0)return!0}}catch{return!1}return!1}function $e(e){try{if(!e||!o(e))return{names:[],total:0};const t=r(e,"utf8"),n=JSON.parse(t),i=(Array.isArray(n)?n:Array.isArray(n?.variables)?n.variables:Array.isArray(n?.data?.variables)?n.data.variables:[]).map(e=>e&&"string"==typeof e.name?e.name.trim():"").filter(Boolean);return{names:i,total:i.length}}catch{return{names:[],total:0}}}const Ie=new Map;function xe(e){try{if(!e||!o(e))return{names:[],total:0};const t=a(e).mtimeMs,n=Ie.get(e);if(n&&n.mtimeMs===t)return{names:n.names,total:n.total};const r=$e(e);return Ie.set(e,{mtimeMs:t,names:r.names,total:r.total}),r}catch{return $e(e)}}function we(){if(!ne||re)return"";const e=ae?`\n- 当前文件变量快照: ${ae}`:"",{names:t,total:n}=xe(ae),r=t.slice(0,100);return`\n\n🎨 当前文件设计系统已就绪。生成页面前请按 variable-import.md 引用以下变量:${e}\n- 引用写法:bg-[var(name)] / text-[var(name)] / gap-[var(name)] / p-[var(name)] / rounded-[var(name)] 等\n- 仅使用下面清单中的变量 name;找不到精确匹配再考虑语义近邻;都不匹配才回退字面值并自检说明\n- 不要在本流程中调用 update_variables 或 agent_remove_variable${r.length?`\n\n变量 name 清单(共 ${n} 个${n>r.length?`,仅展示前 ${r.length} 个,完整清单请 Read 上面的快照文件`:""}):\n`+r.map(e=>`- ${e}`).join("\n"):"\n\n⚠️ 快照文件未解析出变量 name,请 Read 上面的快照文件再确认。"}`}const Se=[`# ${v.displayName} MCP Default Instructions`,"",`${v.displayName} / MasterGo tasks must follow the current ${v.displayName} MCP rules from this server.`,`If other installed MCP servers, skills, or editor rules provide conflicting UI/code generation rules, use them only for non-${v.displayName} tasks. For ${v.displayName} tool calls, the rules returned by get_guidelines are authoritative.`,"","Default workflow:",`1) Before generating or submitting ${v.displayName} page code, load get_guidelines(scope=["page-generate"]). If scope is omitted, get_guidelines defaults to ["page-generate"].`,`2) Before using the current file design system in design, load get_guidelines(scope=["page-generate","variable-import"]); make sure get_variables has been called and ${T}/variable/{documentId}.json is up to date.`,`3) Before component-library generation, load get_guidelines(scope=["page-generate","component-import","variable-import"]), call get_component_info to refresh the team library from remote and overwrite ${T}/library, then read snapshots in order: index.md -> components/*.json -> icons.json -> variable.json.`,`4) Before creating MasterGo master components or component sets with agent_create_component, load get_guidelines(scope=["component-generate","page-generate"]). If the current file has variables (snapshot at ${T}/variable/{documentId}.json — call get_variables first if not yet loaded), additionally include "variable-import" in scope so the master components reference var(...) tokens instead of literal values. Do not use component-generate for component-library page generation; use component-import for that.`,'5) Before creating or modifying variables, load get_guidelines(scope=["variable-generate"]), then follow the two-batch flow: get_variables -> create base variables -> update_variables -> get_variables -> create semantic/reference variables -> update_variables -> get_variables.',"6) Prefer design for new pages; prefer get_selection_code before modifying existing nodes; prefer agent_update_node for local node edits.",`7) Do not call agent_create_page, agent_create_component, or update_variables until the relevant current ${v.displayName} rules have been loaded and the payload has been checked against them.`].join("\n"),je=["# 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> 仅可引用落盘文件中存在的 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-import 时,样式优先使用变量 var(...) token;写变量 name,禁止改写或抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true","10) 组件库模式下,所有 name 只能来自落盘文件,禁止改写:components/*.json、icons.json、instance_swap、variable.json","11) 变量创建必须分批:基础变量 update_variables 后先 get_variables 落盘,再创建引用基础变量的语义变量,最后再 get_variables"].join("\n"),_e={"page-generate":"# Page Generate Rules (Base, Must Follow)","component-import":"# Component Import Rules","component-generate":"# Component Generate Rules (Master Component / Component Set)","variable-import":"# Variable Import Rules (Read Variables)","variable-generate":"# Variable Generate Rules (Write Variables)"},Ne={"page-generate":w,"component-import":S,"component-generate":j,"variable-import":_,"variable-generate":N};function Pe(e){const t=new Set,n=[];e.includes("page-generate")&&(t.add("page-generate"),n.push("page-generate"));for(const r of e)t.has(r)||(t.add(r),n.push(r));return n}function De(e){switch(e){case"page-generate":return Z;case"component-import":return Q;case"component-generate":return X;case"variable-import":return ee;case"variable-generate":return te}}function Ce(e){const t=Pe(e);if(0===t.length)return w;if(1===t.length&&"component-generate"===t[0])return["# Scope Warning","component-generate is ONLY for agent_create_component: creating MasterGo master components or component sets. It is not for generating pages with an existing team/component library; use component-import for that.","",_e["component-generate"],j,"",_e["page-generate"],w].join("\n");const n=[];for(const e of t)n.push(_e[e]),n.push(Ne[e]),n.push("");return t.length>1&&n.push(je),n.join("\n").trim()}function Ae(e){const t=Pe(Array.isArray(e)?e:[e]);let n=!1;for(const e of t)De(e)||(le(e),n=!0);return{loadedNow:n,text:Ce(t),scopes:t}}function Le(e,t,n={}){de();const{loadedNow:r,text:o,scopes:i}=Ae(e);if(!r)return null;const a=i.join(" + "),s=n.followUp??`请基于以上规则自检并修正后,再次调用 ${t} 提交。`;return{content:[{type:"text",text:`🧭 已自动加载当前 ${v.displayName} MCP 规则(${a}):\n${o}\n\n${s}`}],isError:n.isError??!1}}const Ee={name:"get_guidelines",description:C.getGuidelines.description,inputSchema:n.object({scope:n.array(n.enum(["page-generate","component-import","component-generate","variable-import","variable-generate"])).optional()}).describe(C.getGuidelines.inputSchema),handler:async e=>{const t=e?.scope?.length?e.scope:["page-generate"],{text:n}=Ae(t);return{content:[{type:"text",text:n}]}}},Te={name:"agent_create_component",description:C.createComponent.description,inputSchema:{code:n.string().describe(C.createComponent.code),projectDir:n.string().optional().describe(C.createComponent.projectDir)},handler:async e=>{const{code:t,projectDir:n}=e,r=["component-generate"];n&&ve(s.resolve(n))&&r.push("variable-import");const o=Le(r,"agent_create_component",{followUp:"请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。"});if(o)return o;const{error:i}=await q("POST","/api/createComponent",{code:t});return i?{content:[{type:"text",text:`❌ 创建失败: ${i.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向 MasterGo 发送组件创建指令"}]}}};function Me(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)||/�*3c;|�*3e;/i.test(t)||/�*60;|�*62;/i.test(t)}(t)?function(e){const t={"<":"<",">":">",""":'"',"'":"'","'":"'","&":"&"," ":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|'/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 Ve={name:"agent_create_page",description:C.createPage.description,inputSchema:{code:n.string().optional().describe(C.createPage.code),filePath:n.string().optional().describe(C.createPage.filePath),projectDir:n.string().describe(C.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe(C.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=s.resolve(e.filePath);r=await m.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=Le("page-generate","agent_create_page",{followUp:"请基于以上规则重新自检并修正页面 HTML 后,再次调用 agent_create_page 提交。"});if(o)return o;const i=Oe(Me(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:a,error:c}=await q("POST","/api/createPage",{code:i});if(c)return{content:[{type:"text",text:`❌ 发送失败: ${c.message}`}],isError:!0};let d=function(e,t="操作"){const n="accepted"===e?.status;let r=n?`✅ ${t}请求已发送到 ${v.pluginName},画布仍在后台处理中`:`✅ ${t}已成功完成`;return e.requestId&&(r+=`\n- 请求 ID: ${e.requestId}`),e.targetNodeId&&(r+=`\n- 节点 ID: ${e.targetNodeId}`),e.documentId&&(r+=`\n- 文档: ${e.documentName||""} (${e.documentId})`),e.documentPageId&&(r+=`\n- 页面: ${e.documentPageName||""} (${e.documentPageId})`),n&&(r+="\n- 状态: accepted(已受理,非最终渲染完成回执)"),r}(a,"设计稿生成");if(n)try{const e=t?s.resolve(t):process.cwd();d+=`\n- 代码已本地持久化至: ${(await R({baseDir:e,code:a.htmlCode||i,documentId:a.documentId,documentPageId:a.documentPageId,targetNodeId:a.targetNodeId,nodeName:a.nodeName,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image})).htmlPath}`}catch(e){d+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:d}]}}};let Fe=null;function ke(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const Je="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 agent_create_page(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function Ge(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}function qe(e){const t=ve(e)?"current-file-variables":"free-draw",n=e=>e===t?"(推荐)":"";return`请让用户在以下三项中选择一个:\n1) free-draw${n("free-draw")} - 自由绘制:仅使用 page-generate 基础规则\n2) current-file-variables${n("current-file-variables")} - 使用当前文件的设计系统:会自动落盘当前文件变量到 ${T}/variable/{documentId}.json,并按 variable-import.md 在样式上引用变量\n3) component-library${n("component-library")} - 使用组件库:会要求选择具体团队库,按 component-import.md + variable-import.md 生成`}function ze(e){const{loadedNow:t,text:n}=Ae(e),r=e.join(" + ");return t?`🧭 自动加载规则(${r}):\n${n}\n\n`:`🧭 规则状态:${r} 已加载,本次不重复展开。\n\n`}const Re={name:"design",description:C.design.description,inputSchema:{requirement:n.string().describe(C.design.requirement),code:n.string().optional().describe(C.design.code),projectDir:n.string().optional().describe(C.design.projectDir),designSource:n.enum(["free-draw","current-file-variables","component-library"]).optional().describe(C.design.designSource),userConfirmedDesignSource:n.boolean().optional().default(!1).describe(C.design.userConfirmedDesignSource),teamLibraryName:n.string().optional().describe(C.design.teamLibraryName),teamLibraryId:n.string().optional().describe(C.design.teamLibraryId),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe(C.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,designSource:o,userConfirmedDesignSource:i=!1,teamLibraryName:a,teamLibraryId:c,buildStrategy:d}=e,l=r?s.resolve(r):process.cwd();if(de(l).reset&&(Fe=null),"string"==typeof n&&n.trim())return Ve.handler({code:ke(n),projectDir:l});if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};const u=`${t.trim()}|${o??""}`;if(Fe&&Fe.requirement===u||(Fe={requirement:u,sourceConfirmed:!1}),!(Fe.sourceConfirmed||o&&i))return{content:[{type:"text",text:`⏸️ 继续设计前,请先让用户确认设计来源(三选一)。\n\n${qe(l)}\n\n用户确认后再次调用 design,并同时传:\n- designSource: "free-draw" | "current-file-variables" | "component-library"\n- userConfirmedDesignSource: true`}],isError:!1};if(Fe.sourceConfirmed=!0,"free-draw"===o){be();const e=ze(["page-generate"]);return Fe=null,{content:[{type:"text",text:`${e}📋 收到需求:${t}\n- 设计来源:自由绘制(free-draw)\n\n请直接按 page-generate 规则生成页面代码,并继续调用 agent_create_page 发送到画布。${Je}`}],isError:!1}}if("current-file-variables"===o){be();const e=function(e){const t=G(e.projectDir);if(!ne||oe&&t&&oe!==t)return{content:[{type:"text",text:`❌ 你已选择「使用当前文件的设计系统」,但当前文件变量尚未获取并落盘。\n\n请按顺序执行:\n1) get_variables({ projectDir: "${t||"<你的项目根目录>"}" })\n2) 工具会写入 ${T}/variable/{documentId}.json\n3) 完成后再次调用 design,传 designSource: "current-file-variables"${t?`\n- 当前 projectDir: ${t}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 当前文件变量刚被 update_variables 修改过,使用设计系统生成页面前必须重新调用 get_variables 刷新快照。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:l});if(e)return e;const n=ze(["page-generate","variable-import"]);return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用当前文件的设计系统(current-file-variables)\n\n请按 page-generate + variable-import 规则生成页面代码:所有可匹配的样式属性必须以 var(...) 引用下面清单中的变量;找不到匹配再回退字面值并自检说明原因。${Je}${we()}`}],isError:!1}}if("component-library"===o){if(!a?.trim())return Fe=null,{content:[{type:"text",text:'⏸️ 你已选择「使用组件库生成页面」,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 调用 get_library_list 获取团队库列表\n2) 询问用户选择具体团队库(禁止助手自动选择)\n3) 用户确认后调用 get_component_info,重新远端拉取并覆盖落盘\n\n用户确认后再次调用 design,并传入:\n- designSource: "component-library"\n- userConfirmedDesignSource: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!1};ye({requirement:`使用 ${a} 团队库`,teamLibraryId:c});const e=function(e,t){if(!U||B)return null;const n=t||Y||process.cwd();return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但本轮尚未刷新团队库快照。\n\n请先按顺序执行:\n1) 若未确认团队库,先调用 get_library_list 并让用户选择\n2) 调用 get_component_info(每次远端拉取并覆盖落盘 index.md + components/icons/variable)\n3) 然后再继续「${e}」当前流程\n${W?`\n- 目标团队库: ${W}`:""}${n?`\n- projectDir: ${n}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,避免使用过期组件库数据。`}],isError:!0}}("design",l);if(e)return e;if(!d)return Fe=null,{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design:\n1) full-components(全组件设计模式:组件、变量、图标全量使用;样式优先 var(...))\n2) hybrid(混合设计模式:关键区块使用组件,全量使用组件库变量与图标系统)`}],isError:!1};const n=ze(["page-generate","component-import","variable-import"]),r="full-components"===d?"\n【full-components 执行提示】\n- 可组件化区块优先使用 <ui-component>;缺组件才回退 HTML,并说明原因\n- 组件、图标、instance_swap 写法见 component-import.md;变量写法见 variable-import.md":"\n【hybrid 执行提示】\n- 关键功能区使用组件,非关键区可自由绘制\n- 非组件区块仍必须遵循 variable-import.md 的变量优先约束";return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用组件库(component-library)\n- 团队库:${a}\n- 构建策略:${Ge(d)}\n${r}\n\n请严格基于落盘组件库文件(index.md/components/*.json/icons.json/variable.json)生成页面代码,并继续调用 agent_create_page 发送到画布。${Je}\n\n${he()}`}],isError:!1}}return{content:[{type:"text",text:`❌ 未识别的 designSource:${String(o)}。合法取值:free-draw / current-file-variables / component-library。`}],isError:!0}}},Ue={name:"get_code",description:C.getCode.description,inputSchema:{contentId:n.string().describe(C.getCode.contentId),documentId:n.string().optional().describe(C.getCode.documentId),documentPageId:n.string().optional().describe(C.getCode.documentPageId),projectDir:n.string().describe(C.getCode.projectDir),outDir:n.string().describe(C.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:c}=await q("GET",`/api/getCode/${t}`);if(c)return{content:[{type:"text",text:`❌ 获取失败: ${c.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?s.resolve(r):process.cwd(),l=await R({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 u=`✅ 代码拉取完成\n- 目录: ${l.targetDir}\n- 文件: ${l.htmlFileName}\n`;return l.shapeCount>0&&(u+=`- Shape: ${l.shapeCount} 个\n`),l.svgCount>0&&(u+=`- SVG: ${l.svgCount} 个\n`),l.imageCount>0&&(u+=`- Image: ${l.imageCount} 个\n`),{content:[{type:"text",text:u}]}}},Be={name:"get_code_list",description:C.getCodeList.description,inputSchema:n.object({}).describe(C.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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}]}}},We=e=>e&&"string"==typeof e.base64&&0!==e.base64.length?{type:"image",data:e.base64,mimeType:e.mimeType||"image/webp"}:null,He=e=>String(e||"unknown").replace(/[:/\\?*"<>|]/g,"-"),Ke=async e=>{const{baseDir:t,documentId:n,documentPageId:r,nodeId:o,preview:i}=e;if(!i||"string"!=typeof i.base64||0===i.base64.length)return null;try{const e=((e,t)=>{const n=(e||"").toLowerCase();if(n.includes("webp"))return"webp";if(n.includes("jpeg")||n.includes("jpg"))return"jpg";if(n.includes("png"))return"png";const r=(t||"").toLowerCase();return"webp"===r?"webp":"jpg"===r||"jpeg"===r?"jpg":"png"})(i.mimeType,i.format),a=s.join(t,T,".preview",He(n),He(r));await m.mkdir(a,{recursive:!0});const c=`${He(o)}.${e}`,d=s.join(a,c),l=Buffer.from(i.base64,"base64");return await m.writeFile(d,l),{filePath:d,byteLength:l.byteLength}}catch(e){return console.error("[getSelectionCode] save preview to disk failed:",e),null}},Ye=e=>{if(!e)return"";const t=(e.byteLength/1024).toFixed(1);return`- 预览图(给 LLM 看的): ${e.filePath} (${t} KB)`},Ze=e=>{const t=String(e?.parentDirection||e?.parentDirection||"").trim().toLowerCase();return"row"===t||"column"===t?t:""},Qe=e=>{const t=Number(e?.nodeSize?.w??e?.width),n=Number(e?.nodeSize?.h??e?.height);return Number.isFinite(t)&&Number.isFinite(n)?`- 当前节点尺寸: ${t} x ${n}px`:""},Xe={name:"get_selection_code",description:C.getSelectionCode.description,inputSchema:{projectDir:n.string().describe(C.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe(C.getSelectionCode.targetNodeId),targetNodeIds:n.array(n.string()).optional().describe("[可选] 批量目标图层 ID 列表,传入后按列表顺序拉取代码。"),syncToBase:n.boolean().default(!0).describe(C.getSelectionCode.syncToBase),referenceDesign:n.boolean().optional().describe(C.getSelectionCode.referenceDesign)},handler:async e=>{const{projectDir:t,targetNodeId:n,targetNodeIds:r=[],syncToBase:i=!0,referenceDesign:a=!1,_depth:c=0}=e;if(c>2)return{content:[{type:"text",text:"❌ 递归获取根节点深度过深,已停止。"}],isError:!0};const d=r.filter(e=>"string"==typeof e&&e.trim().length>0),l=a?"&referenceDesign=1":"",u=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}${l}`:d.length>0?`/api/getSelectionCode?ids=${encodeURIComponent(d.join(","))}${l}`:"/api/getSelectionCode"+(l?`?${l.slice(1)}`:""),{data:p,error:g}=await q("GET",u);if(g)return 400===g.status&&"NoSelection"===g.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${g.message}`}],isError:!0};const f=Array.isArray(p.selections)?p.selections:[],y=f.length>1?"all":"primary";if(f.length>1){const e=[];e.push(`✅ 成功获取多选节点代码(共 ${f.length} 个)`),e.push(`- 模式: ${y}`);const n=[],r=[];for(const[o,i]of f.entries()){const a=i?.nodeInfo||{},c=a?.targetNodeId||a?.nodeId||`unknown-${o+1}`,d=a?.nodeName||`node-${o+1}`,l=a?.rootId,u=a?.documentId,m=a?.documentPageId,p=Ze(a);e.push(`- [${o+1}] ${d} (${c})`);const g=Qe(a);g&&e.push(` ${g.replace(/^- /,"")}`),p&&e.push(` parentDirection: ${p}`);const f=We(i?.preview);f&&r.push(f);const y=await Ke({baseDir:t?s.resolve(t):process.cwd(),documentId:u,documentPageId:m,nodeId:c,preview:i?.preview}),b=Ye(y);if(b&&e.push(` ${b.replace(/^- /,"")}`),c===l){let n="";if(u&&m){n=(await R({baseDir:t?s.resolve(t):process.cwd(),documentId:u,documentPageId:m,targetNodeId:l,nodeName:d,code:i?.code||"",resourcePath:i?.resourcePath,shape:i?.shape,svg:i?.svg,image:i?.image})).htmlPath}e.push(" 根节点策略: 未回显代码正文"+(n?`,已落盘到 ${n}`:""));continue}n.push(`### [${o+1}] ${d} (${c})\n\n\`\`\`html\n${i?.code||""}\n\`\`\``)}return{content:[{type:"text",text:`${e.join("\n")}\n\n${n.join("\n\n")}`},...r]}}const b=1===f.length?f[0]:null,v=p.nodeInfo||b?.nodeInfo||{},$=p.code??b?.code??"",I=p.resourcePath??b?.resourcePath,x=p.shape??b?.shape,w=p.svg??b?.svg,S=p.image??b?.image,j=p.preview??b?.preview??null,_=We(j),N=t?s.resolve(t):process.cwd(),P=v||{},D=Ze(P),{rootId:C,documentId:A,documentPageId:L,targetNodeId:E,nodeName:O,parentId:V}=P;if(E===C){const e=await R({baseDir:N,documentId:A,documentPageId:L,targetNodeId:C,nodeName:O,code:$,resourcePath:I,shape:x,svg:w,image:S}),t=await Ke({baseDir:N,documentId:A,documentPageId:L,nodeId:E,preview:j}),n=(e=>{const{nodeName:t,targetNodeId:n,rootId:r,nodeInfo:o,htmlPath:i}=e;return`✅ 成功获取并保存根节点代码\n- 节点: ${t} (${n})\n- 根节点: ${r}\n${Qe(o)?`${Qe(o)}\n`:""}- 文件: ${i}\n- 上下文策略: 根节点代码未回显到对话中,本次仅完成基准文件落盘。\n- 后续动作: 当前仅做"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。`})({nodeName:O,targetNodeId:E,rootId:C,nodeInfo:P,htmlPath:e.htmlPath}),r=Ye(t);return{content:[{type:"text",text:r?`${n}\n${r}`:n},..._?[_]:[]]}}{let e="",n="";if(i&&A&&L){const r=s.join(N,T,M,String(A).replace(/:/g,"-"),String(L).replace(/:/g,"-")),i=s.join(N,T,String(A).replace(/:/g,"-"),String(L).replace(/:/g,"-")),d=String(C).replace(/:/g,"-");let l="",u=r;const p=async()=>{const e=async e=>{if(!o(e))return"";const t=(await m.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 m.stat(s.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return u=r,t;const n=await e(i);return n?(u=i,n):""};if(l=await p(),l||0!==c||(await Xe.handler({projectDir:t,targetNodeId:C,syncToBase:!0,referenceDesign:a,_depth:c+1}),l=await p()),l){const t=s.join(u,l);try{const n=await m.readFile(t,"utf8"),r=h(n),o=r.querySelector(`[data-node-id="${E}"]`);if(o)o.replaceWith($);else if(V){const e=r.querySelector(`[data-node-id="${V}"]`);e&&e.insertAdjacentHTML("beforeend",$)}(o||V&&r.querySelector(`[data-node-id="${V}"]`))&&(await m.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const r=await R({baseDir:N,documentId:A,documentPageId:L,targetNodeId:E,nodeName:O,code:"",resourcePath:I,shape:x,svg:w,image:S});let d=`✅ 成功获取子节点代码\n- 节点: ${O} (${E})\n- 根节点: ${C}`;const l=Qe(P);l&&(d+=`\n${l}`),D&&(d+=`\n- 父容器方向: ${D}`),d+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。",d+='\n- 后续动作: 本次仅完成"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。',n&&(d+=`\n- ${n}`),E!==C&&(d+=`\n- 资源同步: 图片(${r.imageCount}), 图标(${r.svgCount}), 图形(${r.shapeCount})`);const u=(e=>e?"row"===e?"- 充满语义提示(parentDirection=row): 宽度充满=主轴充满(优先 flex-1);高度充满=交叉轴充满(优先 self-stretch)。":"- 充满语义提示(parentDirection=column): 宽度充满=交叉轴充满(优先 self-stretch);高度充满=主轴充满(优先 flex-1)。":"")(D);u&&(d+=`\n${u}`);const p=await Ke({baseDir:N,documentId:A,documentPageId:L,nodeId:E,preview:j}),g=Ye(p);return g&&(d+=`\n${g}`),{content:[{type:"text",text:`${d}\n\n代码内容:\n\n${$}`},..._?[_]:[]]}}}},et={name:"get_user_info",description:C.getUserInfo.description,inputSchema:n.object({}).describe(C.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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}]}}},tt={name:"agent_update_node",description:C.updateNode.description,inputSchema:{documentId:n.string().optional().describe(C.updateNode.documentId),documentPageId:n.string().optional().describe(C.updateNode.documentPageId),targetNodeId:n.string().optional().describe(C.updateNode.targetNodeId),code:n.string().describe(C.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=h(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 q("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 局部更新失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [局部修改] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}},nt={name:"agent_replace_node",description:C.replaceNode.description,inputSchema:{documentId:n.string().optional().describe(C.replaceNode.documentId),documentPageId:n.string().optional().describe(C.replaceNode.documentPageId),targetNodeId:n.string().optional().describe(C.replaceNode.targetNodeId),code:n.string().describe(C.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=h(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 q("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 结构替换失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [结构替换] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}};function rt(e){return String(e).replace(/:/g,"-")}async function ot(e,t){if(!o(e))return"";const n=(await m.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=s.join(e,t.name);return{fullPath:n,mtimeMs:(await m.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function it(e,t){if(!o(e))return"";const n=await m.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=s.join(e,o.name);if(o.isDirectory()){const e=await it(n,t);if(e){const t=await m.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await m.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const at={name:"get_design_diff",description:C.getDesignDiff.description,inputSchema:{projectDir:n.string().describe(C.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe(C.getDesignDiff.targetNodeId),filePath:n.string().optional().describe(C.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?s.resolve(t):process.cwd();let i=n,a="",c=r||"";if(c)try{const e=s.isAbsolute(c)?c:s.resolve(o,c);a=await m.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 q("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:l,documentId:u,documentPageId:p}=t.nodeInfo;if(d=r,i||(i=l),!a&&r){const e=String(r).replace(/:/g,"-"),t=u&&p?s.join(o,T,M,rt(u),rt(p)):"",n=u&&p?s.join(o,T,rt(u),rt(p)):"";if(t&&(c=await ot(t,e)),!c&&n&&(c=await ot(n,e)),!c){const t=s.join(o,T,M);c=await it(t,e)}if(!c){const t=s.join(o,T);c=await it(t,e)}c&&(a=await m.readFile(c,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 ${T} 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:l,error:u}=await q("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(u)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${u.message}`}],isError:!0};let p=l.diffs||[];return p&&!Array.isArray(p)&&Array.isArray(p.diffs)&&(p=p.diffs),0===p.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${p.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(p,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 ${T} 的基准文件。`}]}}},st={name:"agent_remove_node",description:C.removeNode.description,inputSchema:{documentId:n.string().optional().describe(C.removeNode.documentId),documentPageId:n.string().optional().describe(C.removeNode.documentPageId),targetNodeId:n.string().optional().describe(C.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await q("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});if(i)return{content:[{type:"text",text:`❌ 删除失败: ${i.message}`}],isError:!0};const a="accepted"===o.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ 已成功发送删除指令\n- 目标节点 ID: ${o.targetNodeId||t||"当前选中图层"}${a}`}]}}},ct={name:"agent_remove_variable",description:C.removeVariable.description,inputSchema:{documentId:n.string().optional().describe(C.removeVariable.documentId),documentPageId:n.string().optional().describe(C.removeVariable.documentPageId),id:n.string().optional().describe(C.removeVariable.id),collection:n.string().optional().describe(C.removeVariable.collection),collectionId:n.string().optional().describe(C.removeVariable.collectionId),name:n.string().optional().describe(C.removeVariable.name),type:n.string().optional().describe(C.removeVariable.type),variables:n.any().optional().describe(C.removeVariable.variables),operations:n.any().optional().describe(C.removeVariable.variables)},handler:async e=>{const t=Le(["variable-generate"],"agent_remove_variable",{followUp:"⚠️ 删除变量为高危操作。请先按上述规则确认目标变量(优先使用 id)后再次调用 agent_remove_variable 提交。"});if(t)return t;const{data:n,error:r}=await q("POST","/api/removeVariable",e);if(r)return{content:[{type:"text",text:`❌ 变量删除失败: ${r.message}`}],isError:!0};const o=Array.isArray(n?.results)?n.results:[],i=o.filter(e=>!1===e?.success),a=!1!==n?.success&&0===i.length;return{content:[{type:"text",text:`${a?"✅ 变量删除指令已发送":"❌ 变量删除部分或全部失败"}\n\n结果:\n${JSON.stringify(o,null,2)}`+(i.length?`\n\n失败明细:\n${JSON.stringify(i,null,2)}`:"")}],isError:!a}}},dt={name:"agent_sync_design",description:C.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe(C.syncToDesign.documentId),documentPageId:n.string().optional().describe(C.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe(C.syncToDesign.targetNodeId),filePath:n.string().describe(C.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe(C.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=s.resolve(o);a=await m.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:c,error:d}=await q("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});if(d)return{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0};const l="accepted"===c.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${c.targetNodeId?`\n- 根节点 ID: ${c.targetNodeId}`:""}${l}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 ${T} 基准。`}]}}};function lt(e){return String(e??"").trim().toLowerCase()}function ut(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function mt(e){if(!ut(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.description?e.description:"",r=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||ut(e)?e:String(e)}(e.value);return void 0===r?{name:t,description:n}:{name:t,description:n,value:r}}function pt(e){return function(e){return Array.isArray(e)?e:[]}(e).map(mt).filter(e=>!!e)}function gt(e){const t=pt(e.paints),n=pt(e.colors),r=function(e){const t=new Set,n=[];for(const r of e){const e=r.name;t.has(e)||(t.add(e),n.push(r))}return n}([...n,...t]),o=pt(e.texts);return{boxShadow:pt(e.effects),color:r,colors:n,fill:t,paints:t,borderRadius:pt(e.cornerRadiuses),padding:pt(e.paddings),gap:pt(e.spacings),borderWidth:pt(e.strokeWidths),typography:o,texts:o,bools:pt(e.bools),strings:pt(e.strings),grids:pt(e.grids)}}function ft(e){return null===e||["string","number","boolean"].includes(typeof e)}function yt(e){const t=Number(e);return Number.isFinite(t)?t:0}function bt(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[yt(t[0]),yt(t[1])];const n=ut(e.style)?e.style:{};return[yt(e.width??n.width),yt(e.height??n.height)]}function ht(e){if(Array.isArray(e)){const t=e.filter(ft);return t.length>0?t:void 0}if(ut(e)){const t=Array.isArray(e.enum)?e.enum.filter(ft):void 0,n=ft(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(ft(e))return e}function vt(e){if(!ut(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=ht(r);void 0!==e&&(t[n]=e)}return t}function $t(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function It(e){if(!ut(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 xt(e){return"string"==typeof e?e:""}function wt(e){return"string"==typeof e&&e.trim()?e:void 0}function St(e){const t=ut(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(ut(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o=e;n.has(o)||(n.add(o),t.push({name:e}))}}return t}(t.icons);for(const e of n){if(!ut(e))continue;const t="string"==typeof e.name?e.name:"",n=xt(e.description),o=wt(e.id),i=wt(e.ukey),a=ut(e.props)?e.props:ut(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:wt(e.cover),size:bt(e),props:vt(a),slots:$t(s),instance_swap:It(c)})}return{variables:ut(t.variables)?t.variables:ut(t.style)?gt(t.style):ut(t.styles)?gt(t.styles):ut(t.tokens)?t.tokens:{},components:r,icons:o}}function jt(e){if(Array.isArray(e))return e.map(jt);if(ut(e)){const t={};for(const[n,r]of Object.entries(e))"ukey"!==n&&(t[n]=jt(r));return t}return e}function _t(e,t){const n=J(e||"component").replace(/^-+|-+$/g,"").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function Nt(e){try{const t=await m.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function Pt(e,t){await m.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(ft(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(ft)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(ut(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 Dt(e,t){await m.writeFile(e,t.endsWith("\n")?t:`${t}\n`,"utf8")}async function Ct(e){try{await m.unlink(e)}catch(e){if("ENOENT"!==e?.code)throw e}}function At(e){return"index.json"===s.basename(e).toLowerCase()}function Lt(e){const t=s.join(e,"index.md");if(o(t))return t;const n=s.join(e,"index.json");return o(n)?n:""}async function Et(e){if(!o(e))return[];const t=[],n=s.join(e,"catalog.json"),r=await Nt(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(),c=String(n?.indexPath||"").trim();if(!r||!c)continue;const d=s.join(e,c);if(!o(d))continue;const l=c.split(/[\\/]/)[0]||r,u=await Nt(s.join(e,l,"meta.json")),m=String(u?.teamLibrary?.id||"").trim(),p=String(u?.teamLibrary?.name||"").trim(),g=String(u?.teamLibrary?.slug||"").trim();t.push({id:m||r,name:p||i||a||m||r,slug:g||a||J(p||i||m||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await m.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=s.join(e,r),a=await Nt(s.join(i,"meta.json"));if(a?.paths?.index){const e=s.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.id||"").trim(),o=String(a?.teamLibrary?.name||"").trim(),i=String(a?.teamLibrary?.slug||"").trim();t.push({id:n||r,name:o||i||n||r,slug:i||J(o||n||r),indexAbsPath:e});continue}}const c=await m.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of c){if(!e.isDirectory())continue;const n=Lt(s.join(i,e.name));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){if(!At(e.indexAbsPath))continue;const t=await Nt(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=J(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 Tt(){const{data:e,error:t}=await q("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function Mt(e){return null==e||"string"==typeof e&&""===e.trim()}function Ot(e){if(Array.isArray(e))return{documentName:"",documentId:"",variables:e};if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Ot(t.data);const n=Array.isArray(t.variables)?t.variables:[];return{documentName:String(t.documentName??""),documentId:String(t.documentId??""),variables:n}}return{documentName:"",documentId:"",variables:[]}}function Vt(e){if(Array.isArray(e))return e;if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Vt(t.data);if(Array.isArray(t.operations))return t.operations;if(Array.isArray(t.variables))return t.variables;if(t.name||t.id||t.action||t.collection||t.collectionId)return[t]}return[]}function Ft(e){if(Array.isArray(e))return e.some(e=>Ft(e));if(!e||"object"!=typeof e)return!1;const t=e;return"deleteVariable"===String(t.action||"")||(Array.isArray(t.operations)?Ft(t.operations):!!Array.isArray(t.variables)&&Ft(t.variables))}async function kt(e){if(void 0!==e.operations)return{operations:Vt(e.operations),source:"operations 参数",documentId:e.documentId};if(void 0!==e.variables)return{operations:Vt(e.variables),source:"variables 参数",documentId:e.documentId};const t=s.resolve(e.projectDir),n=e.filePath?s.resolve(e.filePath):e.documentId?s.join(t,T,"variable",`${J(e.documentId)}.json`):await async function(e){const t=s.join(e,T,"variable");let n=[];try{n=await f(t)}catch{return null}const r=await Promise.all(n.filter(e=>e.toLowerCase().endsWith(".json")).map(async e=>{const n=s.join(t,e);return{filePath:n,mtimeMs:(await y(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0]?.filePath||null}(t);if(!n)throw new Error("未找到变量文件。请先调用 get_variables,或显式传入 filePath / variables / operations。");const r=await async function(e){const t=await b(e,"utf8");return JSON.parse(t)}(n),o=String(r?.documentId||e.documentId||"").trim()||void 0;return{operations:Vt(r),source:n,documentId:o}}const Jt=[Ee,Re,Ue,Be,et,{name:"get_library_list",description:C.getLibraryList.description,inputSchema:n.object({}).describe(C.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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:"📚 团队库列表为空(请确认 MCP 服务已连接且当前文件已订阅团队库)"}]};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) 让用户确定团队库(可回复:使用 <团队库名称> 团队库)\n2) 调用 get_component_info,每次从远端拉取并覆盖落盘 index.md + components/icons/variable\n`}]}}},{name:"get_component_info",description:C.getComponentInfo.description,inputSchema:{projectDir:n.string().describe(C.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe(C.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe(C.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe(C.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:a=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const c=s.resolve(t);de(c);const d=s.join(c,T,"library"),l=s.join(d,"catalog.json"),u=await Et(d);let m=n,p=r;if(!m&&!p){if(u.length>0){const e=u.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:`✅ 检测到本地 ${T}/library 已有落盘团队库(共 ${e.length} 个)。本地列表仅用于选择团队库;选定后仍会重新远端拉取并覆盖落盘。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await Tt()).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组件库生成页面的标准流程:选定团队库后调用 get_component_info,每次都远端拉取并覆盖落盘。`}]}}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(!Mt(t)){const e=lt(String(t)),n=lt(J(String(t))),o=r.filter(t=>{const r=lt(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!Mt(n)){const e=lt(String(n)),t=lt(J(String(n))),o=r.filter(n=>{const r=lt(n.name),o=lt(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=lt(t.name),r=lt(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}}(u,m,p);if("matched"===g.status){const e=g.entry,t=At(e.indexAbsPath)?await Nt(e.indexAbsPath):null,n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||m||"",i=r||e.id||m||"";m=i||m||e.id,p=o||p||e.name}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 ${T}/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!m&&p)try{const e=await Tt(),t=lt(p),n=e.filter(e=>lt(e?.name)===t),r=e.filter(e=>!Mt(e?.name)&<(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。`}]}}m=o[0]?.id,p=o[0]?.name}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}if(!m)return{content:[{type:"text",text:"参数错误: 未能确定 teamLibraryId"}],isError:!0};if(!p)try{const e=(await Tt()).find(e=>String(e?.id||"")===String(m));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(m)}&includePropertyDetails=${a?"true":"false"}`,{data:y,error:b}=await q("GET",f);if(b)return{content:[{type:"text",text:`❌ 获取失败: ${b.message}`}],isError:!0};const h=St(y?.data??y),v=J(m),$=J(p||m),I=(new Date).toISOString(),x=s.join(d,v),w=s.join(x,$),S=s.join(w,"components");o(x)||i(x,{recursive:!0}),o(w)||i(w,{recursive:!0}),o(S)||i(S,{recursive:!0});const j=[],_=[],N=new Set;h.components.forEach((e,t)=>{let n=_t(e.name,t),r=1;for(;N.has(n);)r+=1,n=`${_t(e.name,t)}-${r}`;N.add(n),j.push(n),_.push(Pt(s.join(w,`components/${n}.json`),{id:e.id,name:e.name,description:e.description||"",cover:e.cover,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const P="icons.json",D="variable.json";await Promise.all([..._,Pt(s.join(w,P),{icons:h.icons}),Pt(s.join(w,D),{variables:jt(h.variables)})]);const C=s.join(w,"index.md"),A=function(e){const{teamLibraryName:t,componentKeys:n,iconsFileName:r,variablesFileName:o}=e,i=n.length?n.map(e=>`- \`${e}.json\``).join("\n"):"- 暂无组件文件";return[`# ${a=t,String(a??"").replace(/\s+/g," ").trim()} 组件库索引`,"","## 快速定位","- 组件数据在 `components/*.json`。每个组件文件都自带 `name`、`description`、`cover`、`size`、`props`、`slots`、`instance_swap`。",`- 图标数据在 \`${r}\`,只能使用 \`icons[].name\` 中存在的图标名。`,`- 样式变量在 \`${o}\`,使用 \`var(...)\` 时写变量 \`name\`,例如 \`{ "name": "填充色/常规" }\` 写 \`var(填充色/常规)\`,禁止改写或直接写 \`value\`。`,"- 本文件只负责导航。生成页面时,以具体组件 JSON、图标 JSON、变量 JSON 为准。","","## 文件结构","- `components/` - 组件详情目录","- `components/{key}.json` - 单个组件详情",`- \`${r}\` - 图标列表`,`- \`${o}\` - 设计变量`,"","## 组件文件",i].join("\n");var a}({teamLibraryName:String(p||m),componentKeys:j,iconsFileName:P,variablesFileName:D});await Promise.all([Dt(C,A),Ct(s.join(w,"index.json"))]);const L={schemaVersion:1,updatedAt:I,teamLibrary:{id:String(m),name:String(p||m),slug:$},paths:{index:s.relative(x,C),icons:s.relative(x,s.join(w,P)),variables:s.relative(x,s.join(w,D)),componentsDir:s.relative(x,S)}};var E;return await Pt(s.join(x,"meta.json"),L),await async function(e,t){const n=await Nt(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id&&e.indexPath!==t.indexPath);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 Pt(e,o)}(l,{id:String(m),name:String(p||m),slug:$,updatedAt:I,componentCount:h.components.length,iconCount:h.icons.length,indexPath:s.relative(d,C)}),E={teamLibraryId:String(m),teamLibraryName:p||String(m),filePath:C,baseDir:c},B=!0,E.teamLibraryId?.trim()&&(H=E.teamLibraryId.trim()),E.teamLibraryName?.trim()&&(W=E.teamLibraryName.trim()),E.filePath?.trim()&&(K=E.filePath.trim()),E.baseDir?.trim()&&(Y=E.baseDir.trim()),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${m})\n- 目录: ${w}\n- 索引说明文件: ${C}\n- 组件详情文件: ${j.length} 个\n- 图标文件: ${s.join(w,P)}\n- 变量文件: ${s.join(w,D)}\n- 全局目录索引: ${l}\n\n下一步生成页面前:读取 index.md 导航,再读取 components/*.json、icons.json、variable.json;具体生成规则以 component-import.md 为准。`}]}}},{name:"get_variables",description:C.getVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.getVariables.projectDir)}).describe(C.getVariables.inputSchema),handler:async e=>{const{projectDir:t}=e,n=s.resolve(t);de(n);const{data:r,error:o}=await q("GET","/api/getVariables");if(o)return{content:[{type:"text",text:`❌ 获取失败: ${o.message}`}],isError:!0};const{documentName:i,documentId:a,variables:c}=Ot(r),d=s.join(n,T,"variable"),l=s.join(d,`${J(a)}.json`),u=new Set(c.map(e=>e?.collection).filter(Boolean)),m=new Set(c.flatMap(e=>Array.isArray(e?.mode)?e.mode:[]).map(e=>e?.name).filter(Boolean)),f={documentName:i,documentId:a,collections:u.size,modes:m.size,variables:c.length};let y="";try{await p(d,{recursive:!0}),await g(l,`${JSON.stringify(c,null,2)}\n`,"utf8"),ne=!0,re=!1,oe=G((b={projectDir:n,documentId:a,filePath:l}).projectDir),ie=String(b.documentId||"").trim(),ae=G(b.filePath),y=`\n\n已落盘: ${l}`}catch(e){y=`\n\n⚠️ 落盘失败: ${e?.message||String(e)}(路径: ${l})`}var b;return{content:[{type:"text",text:`✅ 当前文件变量已获取\n\n摘要:\n${JSON.stringify(f,null,2)}\n\n完整数据:\n${JSON.stringify({documentName:i,documentId:a,variables:c},null,2)}`+y}]}}},{name:"update_variables",description:C.updateVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.updateVariables.projectDir),documentId:n.string().optional().describe(C.updateVariables.documentId),documentPageId:n.string().optional().describe(C.updateVariables.documentPageId),filePath:n.string().optional().describe(C.updateVariables.filePath),variables:n.any().optional().describe(C.updateVariables.variables),operations:n.any().optional().describe(C.updateVariables.operations)}).describe(C.updateVariables.inputSchema),handler:async e=>{de(e.projectDir);const t=Le(["variable-generate"],"update_variables",{followUp:`请先按以上规则读取/修改 ${T}/variable/{documentId}.json,再次调用 update_variables 提交。`});if(t)return t;let n;try{n=await kt(e)}catch(e){return{content:[{type:"text",text:`❌ 读取变量数据失败: ${e?.message||String(e)}`}],isError:!0}}const r=n.operations;if(Ft(r))return{content:[{type:"text",text:"❌ update_variables 不执行 deleteVariable。删除变量是危险操作,请改用 agent_remove_variable。"}],isError:!0};if(!r.length)return{content:[{type:"text",text:"❌ 没有可提交的变量操作。请传入数组、{ variables: [...] }、{ operations: [...] },或使用 get_variables 落盘后的变量 JSON。"}],isError:!0};const o=e.documentId||n.documentId,i=function(e){const t=G(e.projectDir),n=String(e.documentId||"").trim();if(!ne||oe&&t&&oe!==t||ie&&n&&ie!==n)return{content:[{type:"text",text:`❌ 变量更新前必须先调用 get_variables 获取并落盘当前文件变量。\n\n请先执行:\n1) get_variables({ projectDir })\n2) 读取 ${T}/variable/{documentId}.json\n3) 基于落盘数据生成本次变量变更\n4) 再调用 update_variables${t?`\n- 当前 projectDir: ${t}`:""}${n?`\n- 当前 documentId: ${n}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 上一次 update_variables 已提交,继续提交前必须重新调用 get_variables 刷新变量快照。\n\n推荐顺序:基础变量 update_variables -> get_variables 落盘 -> 语义变量 update_variables -> get_variables 落盘。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:e.projectDir,documentId:o});if(i)return i;const a=pe(r);if(a)return a;const{data:s,error:c}=await q("POST","/api/updateVariables",{documentId:o,documentPageId:e.documentPageId,operations:r});if(c)return{content:[{type:"text",text:`❌ 变量更新失败: ${c.message}`}],isError:!0};const d=Array.isArray(s?.results)?s.results:[],l=d.filter(e=>!1===e?.success),u={source:n.source,documentId:o||"",status:s?.status||"completed",operations:r.length,succeeded:d.length?d.length-l.length:void 0,failed:l.length||void 0},m=!1===s?.success||l.length>0?`\n\n失败明细:\n${JSON.stringify(l.length?l:s?.error,null,2)}`:"",p=!1!==s?.success&&0===l.length;var g;return p&&(g={projectDir:e.projectDir,documentId:o},ne=!0,re=!0,oe=G(g.projectDir)||oe,ie=String(g.documentId||"").trim()||ie),{content:[{type:"text",text:`✅ 变量更新指令已发送\n\n摘要:\n${JSON.stringify(u,null,2)}\n\n结果:\n${JSON.stringify(d,null,2)}`+m+`\n\n建议:更新完成后再次调用 get_variables,刷新 ${T}/variable/{documentId}.json。`}],isError:!p}}},Ve,Te,Xe,tt,nt,dt,at,st,ct],Gt=v.enabledTools?Jt.filter(e=>v.enabledTools.includes(e.name)):Jt;const qt=new e({name:v.serverName,version:"1.0.23"},{instructions:Se,capabilities:{resources:{},tools:{},prompts:{}}});var zt;zt=qt,Gt.forEach(e=>{zt.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await qt.connect(e)}catch(e){process.exit(1)}
|
|
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,mkdirSync as i,statSync as a}from"fs";import s,{dirname as c,resolve as d}from"path";import{fileURLToPath as u}from"url";import m from"axios";import l,{mkdir as p,writeFile as g,readdir as f,stat as y,readFile as b}from"fs/promises";import{parse as h}from"node-html-parser";const v={serverName:"Codify-MCP-Client",displayName:"Codify",pluginName:"Codify 插件",defaultServerUrl:"https://mcp.codify-api.com",serverUrlEnv:"CODIFY_SERVER_URL",accessKeyEnv:"CODIFY_ACCESS_KEY",docDir:".codify",uriScheme:"codify"};function $(e){return e.replace(/\{\{displayName\}\}/g,v.displayName).replace(/\{\{pluginName\}\}/g,v.pluginName).replace(/\{\{docDir\}\}/g,v.docDir).replace(/\{\{uriScheme\}\}/g,v.uriScheme)}const I=c(u(import.meta.url));function _(e){return $(r(d(I,e),"utf-8"))}const x=_("page-generate.md"),S=_("component-import.md"),j=_("component-generate.md"),w=_("variable-import.md"),N=_("variable-generate.md"),P=c(u(import.meta.url));function D(e){return"string"==typeof e?$(e):Array.isArray(e)?e.map(D):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,t])=>[e,D(t)])):e}const C=function(e){const t=d(P,`${e}.template.json`),n=r(t,"utf8");return D(JSON.parse(n))}("en"===process.env.LANG?"en":"zh"),A=process.argv.find(e=>e.startsWith("--url=")),E=A?A.slice(6):process.env[v.serverUrlEnv]||v.defaultServerUrl,L=process.env.ACCESS_KEY||process.env[v.accessKeyEnv],T=v.docDir,M="design",O=s.join(T,"created"),V=`请确认已启动 MasterGo,并且 ${v.pluginName} 处于连接状态。`,F=["/api/getTeamLibraryList","/api/getComponentInfo","/api/getVariables","/api/updateVariables","/api/removeVariable","/api/createPage","/api/getSelectionCode","/api/getSelectionImage","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const J=e=>null==e||""===e||"string"==typeof e&&""===e.trim();function k(e){return String(e??"unknown-document").trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)||"unknown-document"}function G(e){return e?.trim()?s.resolve(e.trim()):""}async function q(e,t,n=null){const r={};L&&(r.Authorization=`Bearer ${L}`);const o={method:e,url:`${E}${t}`,headers:r,...n&&{data:n}};try{return{data:(await m(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="认证失败,请检查 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=F.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mg")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes(v.pluginName)||e.includes("mg")||e.includes("MasterGo")?e:`${e}\n\n${V}`:V}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function U(e,t,n,r){if(J(e))return 0;const a="string"==typeof e?JSON.parse(e):e,c=Object.keys(a);if(0===c.length)return 0;const d=s.join(t,n);o(d)||i(d,{recursive:!0});const u=Object.entries(a).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=s.join(d,`${o}.${i}`);let c=t;if("string"==typeof c&&c.startsWith("http"))try{const e=await m.get(c,{responseType:"arraybuffer"});await l.writeFile(a,e.data)}catch(e){}else if("string"==typeof c&&c.startsWith("data:image/")){const e=c.split(";base64,");2===e.length&&await l.writeFile(a,e[1],"base64")}else{const e="object"==typeof c?JSON.stringify(c,null,2):c,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await l.writeFile(a,e,t)}});return await Promise.all(u),c.length}async function z({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:a,targetNodeId:c,nodeName:d,code:u,resourcePath:m,shape:p,svg:g,image:f,isSelection:y=!1}){let b=t?s.resolve(e,t):n&&r?s.join(e,T,M,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!a||y||c?"":"code"):y?s.join(e,O,"selection"):a?s.join(e,O,a):s.join(e,O);o(b)||i(b,{recursive:!0});const h=String(c||Date.now()).replace(/:/g,"-"),v=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),$=y||c||d?`${v}-${h}.html`:`${a||"index"}.html`,I=s.join(b,$);u&&await l.writeFile(I,u,"utf8");const _=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!J(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),x=await Promise.all([U(p,b,_.shape,"json"),U(g,b,_.svg,"svg"),U(f,b,_.image,"png")]);return{targetDir:b,htmlFileName:$,htmlPath:I,shapeCount:x[0],svgCount:x[1],imageCount:x[2],resourcePathMap:_}}let H=!1,R=!1,W="",B="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",se=0,ce="";function de(e){const t=Date.now(),n=G(e);let r=!1;return(se>0&&t-se>18e5||!!n&&!!ce&&n!==ce)&&(H=!1,R=!1,W="",B="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",Ie.clear(),r=!0),se=t,n&&(ce=n),{reset:r}}function ue(e){switch(e){case"page-generate":Z=!0;break;case"component-import":Q=!0;break;case"component-generate":X=!0;break;case"variable-import":ee=!0;break;case"variable-generate":te=!0}}function me(e){if(Array.isArray(e))return e.some(e=>me(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.reference||Object.values(t).some(e=>me(e))}function le(e){if(Array.isArray(e))return e.some(e=>le(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.value||Object.values(t).some(e=>le(e))}function pe(e){const t=e.filter(e=>function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"deleteVariable"!==String(t.action||"")&&void 0===t.id}(e));if(0===t.length)return null;const n=t.some(e=>me(e)),r=t.some(e=>le(e));return n&&r?{content:[{type:"text",text:"❌ 本次变量批次同时包含 value 和 reference。\n\n请拆成两批:\n1) 先提交基础变量(只包含 value),调用 update_variables。\n2) 立即调用 get_variables,落盘最新变量和 ID。\n3) 再提交语义变量(通过 reference 引用基础变量)。\n4) 最后再次调用 get_variables 刷新落盘数据。"}],isError:!0}:null}function ge(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function fe(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function ye(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return ge(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?ge(n[1]):""}(e.requirement),n=e.teamLibraryId?.trim()||"",r=n&&B&&n===B,o=!n&&!B&&R&&W.trim()&&t.trim()&&(i=t,fe(W)===fe(i));var i;H=!0,R&&(r||o)||(R=!1,B="",K="",Y="",W=t)}function be(){H=!1,R=!1,W="",B="",K="",Y=""}function he(){if(!H||!R)return"";return`\n\n🧩 组件库模式已就绪。请按 index.md 导航读取快照,再生成页面:${W?`\n- 团队库: ${W}`:""}${K?`\n- 组件库索引说明: ${K}`:""}\n- 读取顺序:index.md -> components/*.json -> icons.json -> variable.json\n- 所有 name 必须原样来自落盘文件:components、icons、instance_swap、variable\n- 变量写 var(name),例如 {"name":"填充色/常规"} -> var(填充色/常规)\n- instance_swap 写在 <ui-component instance_swap='...'> 上;需要开关类 props 时同步设为 true\n- 不臆造组件名、图标名、props;完整细则以 component-import.md 为准`}function ve(e){const t=G(e);if(!t)return!1;const n=s.join(t,T,"variable");try{if(!o(n))return!1;const e=require("fs").readdirSync(n).filter(e=>e.toLowerCase().endsWith(".json"));for(const t of e){const{names:e}=_e(s.join(n,t));if(e.length>0)return!0}}catch{return!1}return!1}function $e(e){try{if(!e||!o(e))return{names:[],total:0};const t=r(e,"utf8"),n=JSON.parse(t),i=(Array.isArray(n)?n:Array.isArray(n?.variables)?n.variables:Array.isArray(n?.data?.variables)?n.data.variables:[]).map(e=>e&&"string"==typeof e.name?e.name.trim():"").filter(Boolean);return{names:i,total:i.length}}catch{return{names:[],total:0}}}const Ie=new Map;function _e(e){try{if(!e||!o(e))return{names:[],total:0};const t=a(e).mtimeMs,n=Ie.get(e);if(n&&n.mtimeMs===t)return{names:n.names,total:n.total};const r=$e(e);return Ie.set(e,{mtimeMs:t,names:r.names,total:r.total}),r}catch{return $e(e)}}function xe(){if(!ne||re)return"";const e=ae?`\n- 当前文件变量快照: ${ae}`:"",{names:t,total:n}=_e(ae),r=t.slice(0,100);return`\n\n🎨 当前文件设计系统已就绪。生成页面前请按 variable-import.md 引用以下变量:${e}\n- 引用写法:bg-[var(name)] / text-[var(name)] / gap-[var(name)] / p-[var(name)] / rounded-[var(name)] 等\n- 仅使用下面清单中的变量 name;找不到精确匹配再考虑语义近邻;都不匹配才回退字面值并自检说明\n- 不要在本流程中调用 update_variables 或 agent_remove_variable${r.length?`\n\n变量 name 清单(共 ${n} 个${n>r.length?`,仅展示前 ${r.length} 个,完整清单请 Read 上面的快照文件`:""}):\n`+r.map(e=>`- ${e}`).join("\n"):"\n\n⚠️ 快照文件未解析出变量 name,请 Read 上面的快照文件再确认。"}`}const Se=[`# ${v.displayName} MCP 默认指令`,"",`${v.displayName} / MasterGo 任务必须遵循当前 ${v.displayName} MCP server 返回的规则。`,`如果其它已安装的 MCP server、技能(skill)或编辑器规则与本服务器的 UI / 代码生成规则冲突,那些规则只用于非 ${v.displayName} 任务;当前 ${v.displayName} 工具调用的规则以 get_guidelines 返回内容为准。`,"","## [硬规则 · 工具路由] 页面设计请求必须先经过 design_page","","当用户表达任意页面级生成意图时——包括但不限于:",' - 英文:"design / create / make / build / draw / generate a page / a screen / a UI"',' - 中文:"设计 / 创建 / 做一个 / 帮我画 / 生成 / 弄一个 / 一个页面 / 一个界面 / 一个屏"',"必须先调用 `design_page`,禁止为满足此类请求直接调用 `submit_page_to_canvas`。","","`design_page` 是页面设计的主入口:负责需求拆解、设计源选择(自由发挥 / 当前文件变量 / 组件库),并产出最终的 HTML。",'`submit_page_to_canvas` 是内部提交工具:仅负责把 `design_page` 产出的最终 HTML 推到画布上。它绝不能成为模型对"设计页面"请求的首选工具。',"","默认工作流:",`1) 在生成或提交 ${v.displayName} 页面代码之前,先 get_guidelines(scope=["page-generate"])。如果省略 scope,get_guidelines 默认按 ["page-generate"] 处理。`,`2) 在 design_page 中要使用当前文件设计系统时,先 get_guidelines(scope=["page-generate","variable-import"]);并确保 get_variables 已被调用、${T}/variable/{documentId}.json 已是最新。`,`3) 在基于组件库生成之前,先 get_guidelines(scope=["page-generate","component-import","variable-import"]),调用 get_component_info 从远端刷新团队库并覆盖 ${T}/library,再按顺序读取快照:index.md → components/*.json → icons.json → variable.json。`,`4) 通过 agent_create_component 创建 MasterGo 主组件 / 组件集之前,先 get_guidelines(scope=["component-generate","page-generate"])。如果当前文件存在变量(快照在 ${T}/variable/{documentId}.json——若尚未加载,先调 get_variables),还需要在 scope 中加上 "variable-import",使主组件使用 var(...) token 而不是字面值。component-generate 不用于"基于组件库的页面生成",那种场景请用 component-import。`,'5) 在创建或修改变量之前,先 get_guidelines(scope=["variable-generate"]),并按两批次流程执行:get_variables → 创建基础变量 → update_variables → get_variables → 创建语义/引用变量 → update_variables → get_variables。',"6) 新页面始终优先使用 design_page;修改已有节点时优先使用 get_selection_code;局部节点编辑优先使用 agent_update_node。submit_page_to_canvas 仅保留给 design_page 最终 HTML 的交付——绝不是页面设计请求的首选工具。",`7) 在调用 submit_page_to_canvas、agent_create_component、update_variables 之前,必须先加载相关的当前 ${v.displayName} 规则,并对照规则核查 payload。`].join("\n"),je=["# 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> 仅可引用落盘文件中存在的 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-import 时,样式优先使用变量 var(...) token;写变量 name,禁止改写或抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true","10) 组件库模式下,所有 name 只能来自落盘文件,禁止改写:components/*.json、icons.json、instance_swap、variable.json","11) 变量创建必须分批:基础变量 update_variables 后先 get_variables 落盘,再创建引用基础变量的语义变量,最后再 get_variables"].join("\n"),we={"page-generate":"# 页面生成规则(基础规则,必须遵守)","component-import":"# 组件库引用规则(基于团队库 / 组件库生成)","component-generate":"# 主组件生成规则(创建主组件 / 组件集)","variable-import":"# 变量引用规则(读取并使用变量)","variable-generate":"# 变量生成规则(创建 / 修改变量)"},Ne={"page-generate":x,"component-import":S,"component-generate":j,"variable-import":w,"variable-generate":N};function Pe(e){const t=new Set,n=[];e.includes("page-generate")&&(t.add("page-generate"),n.push("page-generate"));for(const r of e)t.has(r)||(t.add(r),n.push(r));return n}function De(e){switch(e){case"page-generate":return Z;case"component-import":return Q;case"component-generate":return X;case"variable-import":return ee;case"variable-generate":return te}}function Ce(e){const t=Pe(e);if(0===t.length)return x;if(1===t.length&&"component-generate"===t[0])return["# Scope 警告",'component-generate 仅用于 agent_create_component:创建 MasterGo 主组件或组件集。它不用于"基于已有团队库 / 组件库生成页面"——那种场景请使用 component-import。',"",we["component-generate"],j,"",we["page-generate"],x].join("\n");const n=[];for(const e of t)n.push(we[e]),n.push(Ne[e]),n.push("");return t.length>1&&n.push(je),n.join("\n").trim()}function Ae(e){const t=Pe(Array.isArray(e)?e:[e]);let n=!1;for(const e of t)De(e)||(ue(e),n=!0);return{loadedNow:n,text:Ce(t),scopes:t}}function Ee(e,t,n={}){de();const{loadedNow:r,text:o,scopes:i}=Ae(e);if(!r)return null;const a=i.join(" + "),s=n.followUp??`请基于以上规则自检并修正后,再次调用 ${t} 提交。`;return{content:[{type:"text",text:`🧭 已自动加载当前 ${v.displayName} MCP 规则(${a}):\n${o}\n\n${s}`}],isError:n.isError??!1}}const Le={name:"get_guidelines",description:C.getGuidelines.description,inputSchema:n.object({scope:n.array(n.enum(["page-generate","component-import","component-generate","variable-import","variable-generate"])).optional()}).describe(C.getGuidelines.inputSchema),handler:async e=>{const t=e?.scope?.length?e.scope:["page-generate"],{text:n}=Ae(t);return{content:[{type:"text",text:n}]}}},Te={name:"agent_create_component",description:C.createComponent.description,inputSchema:{code:n.string().describe(C.createComponent.code),projectDir:n.string().optional().describe(C.createComponent.projectDir)},handler:async e=>{const{code:t,projectDir:n}=e,r=["component-generate"];n&&ve(s.resolve(n))&&r.push("variable-import");const o=Ee(r,"agent_create_component",{followUp:"请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。"});if(o)return o;const{error:i}=await q("POST","/api/createComponent",{code:t});return i?{content:[{type:"text",text:`❌ 创建失败: ${i.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向 MasterGo 发送组件创建指令"}]}}};function Me(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)||/�*3c;|�*3e;/i.test(t)||/�*60;|�*62;/i.test(t)}(t)?function(e){const t={"<":"<",">":">",""":'"',"'":"'","'":"'","&":"&"," ":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|'/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 Ve={name:"submit_page_to_canvas",description:C.createPage.description,inputSchema:{code:n.string().optional().describe(C.createPage.code),filePath:n.string().optional().describe(C.createPage.filePath),projectDir:n.string().describe(C.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe(C.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=s.resolve(e.filePath);r=await l.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=Ee("page-generate","submit_page_to_canvas",{followUp:"请基于以上规则重新自检并修正页面 HTML 后,再次调用 submit_page_to_canvas 提交。"});if(o)return o;const i=Oe(Me(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:a,error:c}=await q("POST","/api/createPage",{code:i});if(c)return{content:[{type:"text",text:`❌ 发送失败: ${c.message}`}],isError:!0};let d=function(e,t="操作"){const n="accepted"===e?.status;let r=n?`✅ ${t}请求已发送到 ${v.pluginName},画布仍在后台处理中`:`✅ ${t}已成功完成`;return e.requestId&&(r+=`\n- 请求 ID: ${e.requestId}`),e.targetNodeId&&(r+=`\n- 节点 ID: ${e.targetNodeId}`),e.documentId&&(r+=`\n- 文档: ${e.documentName||""} (${e.documentId})`),e.documentPageId&&(r+=`\n- 页面: ${e.documentPageName||""} (${e.documentPageId})`),n&&(r+="\n- 状态: accepted(已受理,非最终渲染完成回执)"),r}(a,"设计稿生成");if(n)try{const e=t?s.resolve(t):process.cwd();d+=`\n- 代码已本地持久化至: ${(await z({baseDir:e,code:a.htmlCode||i,documentId:a.documentId,documentPageId:a.documentPageId,targetNodeId:a.targetNodeId,nodeName:a.nodeName,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image})).htmlPath}`}catch(e){d+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:d}]}}};let Fe=null;function Je(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const ke="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 submit_page_to_canvas(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function Ge(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}function qe(e){const t=ve(e)?"current-file-variables":"free-draw",n=e=>e===t?"(推荐)":"";return`请让用户在以下三项中选择一个:\n1) free-draw${n("free-draw")} - 自由绘制:仅使用 page-generate 基础规则\n2) current-file-variables${n("current-file-variables")} - 使用当前文件的设计系统:会自动落盘当前文件变量到 ${T}/variable/{documentId}.json,并按 variable-import.md 在样式上引用变量\n3) component-library${n("component-library")} - 使用组件库:会要求选择具体团队库,按 component-import.md + variable-import.md 生成`}function Ue(e){const{loadedNow:t,text:n}=Ae(e),r=e.join(" + ");return t?`🧭 自动加载规则(${r}):\n${n}\n\n`:`🧭 规则状态:${r} 已加载,本次不重复展开。\n\n`}const ze={name:"design_page",description:C.design.description,inputSchema:{requirement:n.string().describe(C.design.requirement),code:n.string().optional().describe(C.design.code),projectDir:n.string().optional().describe(C.design.projectDir),designSource:n.enum(["free-draw","current-file-variables","component-library"]).optional().describe(C.design.designSource),userConfirmedDesignSource:n.boolean().optional().default(!1).describe(C.design.userConfirmedDesignSource),teamLibraryName:n.string().optional().describe(C.design.teamLibraryName),teamLibraryId:n.string().optional().describe(C.design.teamLibraryId),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe(C.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,designSource:o,userConfirmedDesignSource:i=!1,teamLibraryName:a,teamLibraryId:c,buildStrategy:d}=e,u=r?s.resolve(r):process.cwd();if(de(u).reset&&(Fe=null),"string"==typeof n&&n.trim())return Ve.handler({code:Je(n),projectDir:u});if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};const m=`${t.trim()}|${o??""}`;if(Fe&&Fe.requirement===m||(Fe={requirement:m,sourceConfirmed:!1}),!(Fe.sourceConfirmed||o&&i))return{content:[{type:"text",text:`⏸️ 继续设计前,请先让用户确认设计来源(三选一)。\n\n${qe(u)}\n\n用户确认后再次调用 design_page,并同时传:\n- designSource: "free-draw" | "current-file-variables" | "component-library"\n- userConfirmedDesignSource: true`}],isError:!1};if(Fe.sourceConfirmed=!0,"free-draw"===o){be();const e=Ue(["page-generate"]);return Fe=null,{content:[{type:"text",text:`${e}📋 收到需求:${t}\n- 设计来源:自由绘制(free-draw)\n\n请直接按 page-generate 规则生成页面代码,并继续调用 submit_page_to_canvas 发送到画布。${ke}`}],isError:!1}}if("current-file-variables"===o){be();const e=function(e){const t=G(e.projectDir);if(!ne||oe&&t&&oe!==t)return{content:[{type:"text",text:`❌ 你已选择「使用当前文件的设计系统」,但当前文件变量尚未获取并落盘。\n\n请按顺序执行:\n1) get_variables({ projectDir: "${t||"<你的项目根目录>"}" })\n2) 工具会写入 ${T}/variable/{documentId}.json\n3) 完成后再次调用 design_page,传 designSource: "current-file-variables"${t?`\n- 当前 projectDir: ${t}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 当前文件变量刚被 update_variables 修改过,使用设计系统生成页面前必须重新调用 get_variables 刷新快照。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:u});if(e)return e;const n=Ue(["page-generate","variable-import"]);return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用当前文件的设计系统(current-file-variables)\n\n请按 page-generate + variable-import 规则生成页面代码:所有可匹配的样式属性必须以 var(...) 引用下面清单中的变量;找不到匹配再回退字面值并自检说明原因。${ke}${xe()}`}],isError:!1}}if("component-library"===o){if(!a?.trim())return Fe=null,{content:[{type:"text",text:'⏸️ 你已选择「使用组件库生成页面」,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 调用 get_library_list 获取团队库列表\n2) 询问用户选择具体团队库(禁止助手自动选择)\n3) 用户确认后调用 get_component_info,重新远端拉取并覆盖落盘\n\n用户确认后再次调用 design_page,并传入:\n- designSource: "component-library"\n- userConfirmedDesignSource: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!1};ye({requirement:`使用 ${a} 团队库`,teamLibraryId:c});const e=function(e,t){if(!H||R)return null;const n=t||Y||process.cwd();return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但本轮尚未刷新团队库快照。\n\n请先按顺序执行:\n1) 若未确认团队库,先调用 get_library_list 并让用户选择\n2) 调用 get_component_info(每次远端拉取并覆盖落盘 index.md + components/icons/variable)\n3) 然后再继续「${e}」当前流程\n${W?`\n- 目标团队库: ${W}`:""}${n?`\n- projectDir: ${n}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,避免使用过期组件库数据。`}],isError:!0}}("design_page",u);if(e)return e;if(!d)return Fe=null,{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design_page:\n1) full-components(全组件设计模式:组件、变量、图标全量使用;样式优先 var(...))\n2) hybrid(混合设计模式:关键区块使用组件,全量使用组件库变量与图标系统)`}],isError:!1};const n=Ue(["page-generate","component-import","variable-import"]),r="full-components"===d?"\n【full-components 执行提示】\n- 可组件化区块优先使用 <ui-component>;缺组件才回退 HTML,并说明原因\n- 组件、图标、instance_swap 写法见 component-import.md;变量写法见 variable-import.md":"\n【hybrid 执行提示】\n- 关键功能区使用组件,非关键区可自由绘制\n- 非组件区块仍必须遵循 variable-import.md 的变量优先约束";return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用组件库(component-library)\n- 团队库:${a}\n- 构建策略:${Ge(d)}\n${r}\n\n请严格基于落盘组件库文件(index.md/components/*.json/icons.json/variable.json)生成页面代码,并继续调用 submit_page_to_canvas 发送到画布。${ke}\n\n${he()}`}],isError:!1}}return{content:[{type:"text",text:`❌ 未识别的 designSource:${String(o)}。合法取值:free-draw / current-file-variables / component-library。`}],isError:!0}}},He={name:"get_code",description:C.getCode.description,inputSchema:{contentId:n.string().describe(C.getCode.contentId),documentId:n.string().optional().describe(C.getCode.documentId),documentPageId:n.string().optional().describe(C.getCode.documentPageId),projectDir:n.string().describe(C.getCode.projectDir),outDir:n.string().describe(C.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:c}=await q("GET",`/api/getCode/${t}`);if(c)return{content:[{type:"text",text:`❌ 获取失败: ${c.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?s.resolve(r):process.cwd(),u=await z({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}]}}},Re={name:"get_code_list",description:C.getCodeList.description,inputSchema:n.object({}).describe(C.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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}]}}},We=e=>{const t=String(e?.parentDirection||e?.parentDirection||"").trim().toLowerCase();return"row"===t||"column"===t?t:""},Be=e=>{const t=Number(e?.nodeSize?.w??e?.width),n=Number(e?.nodeSize?.h??e?.height);return Number.isFinite(t)&&Number.isFinite(n)?`- 当前节点尺寸: ${t} x ${n}px`:""},Ke={name:"get_selection_code",description:C.getSelectionCode.description,inputSchema:{projectDir:n.string().describe(C.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe(C.getSelectionCode.targetNodeId),targetNodeIds:n.array(n.string()).optional().describe("[可选] 批量目标图层 ID 列表,传入后按列表顺序拉取代码。"),syncToBase:n.boolean().default(!0).describe(C.getSelectionCode.syncToBase)},handler:async e=>{const{projectDir:t,targetNodeId:n,targetNodeIds:r=[],syncToBase:i=!0,_depth:a=0}=e;if(a>2)return{content:[{type:"text",text:"❌ 递归获取根节点深度过深,已停止。"}],isError:!0};const c=r.filter(e=>"string"==typeof e&&e.trim().length>0),d=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}`:c.length>0?`/api/getSelectionCode?ids=${encodeURIComponent(c.join(","))}`:"/api/getSelectionCode",{data:u,error:m}=await q("GET",d);if(m)return 400===m.status&&"NoSelection"===m.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${m.message}`}],isError:!0};const p=Array.isArray(u.selections)?u.selections:[],g=p.length>1?"all":"primary";if(p.length>1){const e=[];e.push(`✅ 成功获取多选节点代码(共 ${p.length} 个)`),e.push(`- 模式: ${g}`);const n=[];for(const[r,o]of p.entries()){const i=o?.nodeInfo||{},a=i?.targetNodeId||i?.nodeId||`unknown-${r+1}`,c=i?.nodeName||`node-${r+1}`,d=i?.rootId,u=i?.documentId,m=i?.documentPageId,l=We(i);e.push(`- [${r+1}] ${c} (${a})`);const p=Be(i);if(p&&e.push(` ${p.replace(/^- /,"")}`),l&&e.push(` parentDirection: ${l}`),a===d){let n="";if(u&&m){n=(await z({baseDir:t?s.resolve(t):process.cwd(),documentId:u,documentPageId:m,targetNodeId:d,nodeName:c,code:o?.code||"",resourcePath:o?.resourcePath,shape:o?.shape,svg:o?.svg,image:o?.image})).htmlPath}e.push(" 根节点策略: 未回显代码正文"+(n?`,已落盘到 ${n}`:""));continue}n.push(`### [${r+1}] ${c} (${a})\n\n\`\`\`html\n${o?.code||""}\n\`\`\``)}return{content:[{type:"text",text:`${e.join("\n")}\n\n${n.join("\n\n")}`}]}}const f=1===p.length?p[0]:null,y=u.nodeInfo||f?.nodeInfo||{},b=u.code??f?.code??"",v=u.resourcePath??f?.resourcePath,$=u.shape??f?.shape,I=u.svg??f?.svg,_=u.image??f?.image,x=t?s.resolve(t):process.cwd(),S=y||{},j=We(S),{rootId:w,documentId:N,documentPageId:P,targetNodeId:D,nodeName:C,parentId:A}=S;if(D===w){const e=(e=>{const{nodeName:t,targetNodeId:n,rootId:r,nodeInfo:o,htmlPath:i}=e;return`✅ 成功获取并保存根节点代码\n- 节点: ${t} (${n})\n- 根节点: ${r}\n${Be(o)?`${Be(o)}\n`:""}- 文件: ${i}\n- 上下文策略: 根节点代码未回显到对话中,本次仅完成基准文件落盘。\n- 后续动作: 当前仅做"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。`})({nodeName:C,targetNodeId:D,rootId:w,nodeInfo:S,htmlPath:(await z({baseDir:x,documentId:N,documentPageId:P,targetNodeId:w,nodeName:C,code:b,resourcePath:v,shape:$,svg:I,image:_})).htmlPath});return{content:[{type:"text",text:e}]}}{let e="",n="";if(i&&N&&P){const r=s.join(x,T,M,String(N).replace(/:/g,"-"),String(P).replace(/:/g,"-")),i=s.join(x,T,String(N).replace(/:/g,"-"),String(P).replace(/:/g,"-")),c=String(w).replace(/:/g,"-");let d="",u=r;const m=async()=>{const e=async e=>{if(!o(e))return"";const t=(await l.readdir(e)).filter(e=>e.endsWith(`-${c}.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 l.stat(s.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return u=r,t;const n=await e(i);return n?(u=i,n):""};if(d=await m(),d||0!==a||(await Ke.handler({projectDir:t,targetNodeId:w,syncToBase:!0,_depth:a+1}),d=await m()),d){const t=s.join(u,d);try{const n=await l.readFile(t,"utf8"),r=h(n),o=r.querySelector(`[data-node-id="${D}"]`);if(o)o.replaceWith(b);else if(A){const e=r.querySelector(`[data-node-id="${A}"]`);e&&e.insertAdjacentHTML("beforeend",b)}(o||A&&r.querySelector(`[data-node-id="${A}"]`))&&(await l.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const r=await z({baseDir:x,documentId:N,documentPageId:P,targetNodeId:D,nodeName:C,code:"",resourcePath:v,shape:$,svg:I,image:_});let c=`✅ 成功获取子节点代码\n- 节点: ${C} (${D})\n- 根节点: ${w}`;const d=Be(S);d&&(c+=`\n${d}`),j&&(c+=`\n- 父容器方向: ${j}`),c+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。",c+='\n- 后续动作: 本次仅完成"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。',n&&(c+=`\n- ${n}`),D!==w&&(c+=`\n- 资源同步: 图片(${r.imageCount}), 图标(${r.svgCount}), 图形(${r.shapeCount})`);const u=(e=>e?"row"===e?"- 充满语义提示(parentDirection=row): 宽度充满=主轴充满(优先 flex-1);高度充满=交叉轴充满(优先 self-stretch)。":"- 充满语义提示(parentDirection=column): 宽度充满=交叉轴充满(优先 self-stretch);高度充满=主轴充满(优先 flex-1)。":"")(j);return u&&(c+=`\n${u}`),{content:[{type:"text",text:`${c}\n\n代码内容:\n\n${b}`}]}}}},Ye=e=>String(e||"unknown").replace(/[:/\\?*"<>|]/g,"-"),Ze=async e=>{const{baseDir:t,documentId:n,documentPageId:r,nodeId:o,image:i}=e;if(!i.success||!i.base64)return null;try{const e=((e,t)=>{const n=(e||"").toLowerCase();if(n.includes("webp"))return"webp";if(n.includes("jpeg")||n.includes("jpg"))return"jpg";if(n.includes("png"))return"png";const r=(t||"").toLowerCase();return"webp"===r?"webp":"jpg"===r||"jpeg"===r?"jpg":"png"})(i.mimeType,i.format),a=s.join(t,T,".preview",Ye(n),Ye(r));await l.mkdir(a,{recursive:!0});const c=`${Ye(o)}.${e}`,d=s.join(a,c),u=Buffer.from(i.base64,"base64");return await l.writeFile(d,u),{filePath:d,byteLength:u.byteLength}}catch(e){return console.error("[getSelectionImage] save preview to disk failed:",e),null}},Qe=e=>e.success&&e.base64?{type:"image",data:e.base64,mimeType:e.mimeType||"image/webp"}:null,Xe=(e,t,n)=>{if(t){const n=(t.byteLength/1024).toFixed(1);return`- [${e}] ${t.filePath} (${n} KB)`}return n?`- [${e}] 导出失败: ${n}`:`- [${e}] 导出失败或为空(未落盘)`},et={name:"get_selection_image",description:C.getSelectionImage.description,inputSchema:{projectDir:n.string().describe(C.getSelectionImage.projectDir),targetNodeId:n.string().optional().describe(C.getSelectionImage.targetNodeId),targetNodeIds:n.array(n.string()).optional().describe(C.getSelectionImage.targetNodeIds)},handler:async e=>{const{projectDir:t,targetNodeId:n,targetNodeIds:r=[]}=e,o=r.filter(e=>"string"==typeof e&&e.trim().length>0),i=n?`/api/getSelectionImage?id=${encodeURIComponent(n)}`:o.length>0?`/api/getSelectionImage?ids=${encodeURIComponent(o.join(","))}`:"/api/getSelectionImage",{data:a,error:c}=await q("GET",i);if(c)return 400===c.status&&"NoSelection"===c.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${c.message}`}],isError:!0};const d=Array.isArray(a.images)?a.images:[];if(0===d.length)return{content:[{type:"text",text:"❌ 预览图返回为空。"}],isError:!0};const u=t?s.resolve(t):process.cwd(),m="string"==typeof a.documentId?a.documentId:"",l="string"==typeof a.documentPageId?a.documentPageId:"",p=[];p.push(`✅ 成功获取预览图(共 ${d.length} 张)`);const g=[];for(const e of d){const t=Qe(e);t&&g.push(t);const n=await Ze({baseDir:u,documentId:e.documentId||m,documentPageId:e.documentPageId||l,nodeId:e.nodeId,image:e});p.push(Xe(e.nodeId,n,e.error))}return p.push("- 用途: 上方已附带 image content 用于多模态视觉参考。请勿在没有用户明确修改诉求的情况下,紧接着调用 agent_update_node / agent_replace_node / sync_to_design。"),{content:[{type:"text",text:p.join("\n")},...g]}}},tt={name:"get_user_info",description:C.getUserInfo.description,inputSchema:n.object({}).describe(C.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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}]}}},nt={name:"agent_update_node",description:C.updateNode.description,inputSchema:{documentId:n.string().optional().describe(C.updateNode.documentId),documentPageId:n.string().optional().describe(C.updateNode.documentPageId),targetNodeId:n.string().optional().describe(C.updateNode.targetNodeId),code:n.string().describe(C.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=h(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 q("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 局部更新失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [局部修改] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}},rt={name:"agent_replace_node",description:C.replaceNode.description,inputSchema:{documentId:n.string().optional().describe(C.replaceNode.documentId),documentPageId:n.string().optional().describe(C.replaceNode.documentPageId),targetNodeId:n.string().optional().describe(C.replaceNode.targetNodeId),code:n.string().describe(C.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=h(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 q("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 结构替换失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [结构替换] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}};function ot(e){return String(e).replace(/:/g,"-")}async function it(e,t){if(!o(e))return"";const n=(await l.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=s.join(e,t.name);return{fullPath:n,mtimeMs:(await l.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function at(e,t){if(!o(e))return"";const n=await l.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=s.join(e,o.name);if(o.isDirectory()){const e=await at(n,t);if(e){const t=await l.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await l.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const st={name:"get_design_diff",description:C.getDesignDiff.description,inputSchema:{projectDir:n.string().describe(C.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe(C.getDesignDiff.targetNodeId),filePath:n.string().optional().describe(C.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?s.resolve(t):process.cwd();let i=n,a="",c=r||"";if(c)try{const e=s.isAbsolute(c)?c:s.resolve(o,c);a=await l.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 q("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:u,documentId:m,documentPageId:p}=t.nodeInfo;if(d=r,i||(i=u),!a&&r){const e=String(r).replace(/:/g,"-"),t=m&&p?s.join(o,T,M,ot(m),ot(p)):"",n=m&&p?s.join(o,T,ot(m),ot(p)):"";if(t&&(c=await it(t,e)),!c&&n&&(c=await it(n,e)),!c){const t=s.join(o,T,M);c=await at(t,e)}if(!c){const t=s.join(o,T);c=await at(t,e)}c&&(a=await l.readFile(c,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 ${T} 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:u,error:m}=await q("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(m)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${m.message}`}],isError:!0};let p=u.diffs||[];return p&&!Array.isArray(p)&&Array.isArray(p.diffs)&&(p=p.diffs),0===p.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${p.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(p,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 ${T} 的基准文件。`}]}}},ct={name:"agent_remove_node",description:C.removeNode.description,inputSchema:{documentId:n.string().optional().describe(C.removeNode.documentId),documentPageId:n.string().optional().describe(C.removeNode.documentPageId),targetNodeId:n.string().optional().describe(C.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await q("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});if(i)return{content:[{type:"text",text:`❌ 删除失败: ${i.message}`}],isError:!0};const a="accepted"===o.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ 已成功发送删除指令\n- 目标节点 ID: ${o.targetNodeId||t||"当前选中图层"}${a}`}]}}},dt={name:"agent_remove_variable",description:C.removeVariable.description,inputSchema:{documentId:n.string().optional().describe(C.removeVariable.documentId),documentPageId:n.string().optional().describe(C.removeVariable.documentPageId),id:n.string().optional().describe(C.removeVariable.id),collection:n.string().optional().describe(C.removeVariable.collection),collectionId:n.string().optional().describe(C.removeVariable.collectionId),name:n.string().optional().describe(C.removeVariable.name),type:n.string().optional().describe(C.removeVariable.type),variables:n.any().optional().describe(C.removeVariable.variables),operations:n.any().optional().describe(C.removeVariable.variables)},handler:async e=>{const t=Ee(["variable-generate"],"agent_remove_variable",{followUp:"⚠️ 删除变量为高危操作。请先按上述规则确认目标变量(优先使用 id)后再次调用 agent_remove_variable 提交。"});if(t)return t;const{data:n,error:r}=await q("POST","/api/removeVariable",e);if(r)return{content:[{type:"text",text:`❌ 变量删除失败: ${r.message}`}],isError:!0};const o=Array.isArray(n?.results)?n.results:[],i=o.filter(e=>!1===e?.success),a=!1!==n?.success&&0===i.length;return{content:[{type:"text",text:`${a?"✅ 变量删除指令已发送":"❌ 变量删除部分或全部失败"}\n\n结果:\n${JSON.stringify(o,null,2)}`+(i.length?`\n\n失败明细:\n${JSON.stringify(i,null,2)}`:"")}],isError:!a}}},ut={name:"agent_sync_design",description:C.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe(C.syncToDesign.documentId),documentPageId:n.string().optional().describe(C.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe(C.syncToDesign.targetNodeId),filePath:n.string().describe(C.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe(C.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=s.resolve(o);a=await l.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:c,error:d}=await q("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});if(d)return{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0};const u="accepted"===c.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${c.targetNodeId?`\n- 根节点 ID: ${c.targetNodeId}`:""}${u}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 ${T} 基准。`}]}}};function mt(e){return String(e??"").trim().toLowerCase()}function lt(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function pt(e){if(!lt(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.description?e.description:"",r=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||lt(e)?e:String(e)}(e.value);return void 0===r?{name:t,description:n}:{name:t,description:n,value:r}}function gt(e){return function(e){return Array.isArray(e)?e:[]}(e).map(pt).filter(e=>!!e)}function ft(e){const t=gt(e.paints),n=gt(e.colors),r=function(e){const t=new Set,n=[];for(const r of e){const e=r.name;t.has(e)||(t.add(e),n.push(r))}return n}([...n,...t]),o=gt(e.texts);return{boxShadow:gt(e.effects),color:r,colors:n,fill:t,paints:t,borderRadius:gt(e.cornerRadiuses),padding:gt(e.paddings),gap:gt(e.spacings),borderWidth:gt(e.strokeWidths),typography:o,texts:o,bools:gt(e.bools),strings:gt(e.strings),grids:gt(e.grids)}}function yt(e){return null===e||["string","number","boolean"].includes(typeof e)}function bt(e){const t=Number(e);return Number.isFinite(t)?t:0}function ht(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[bt(t[0]),bt(t[1])];const n=lt(e.style)?e.style:{};return[bt(e.width??n.width),bt(e.height??n.height)]}function vt(e){if(Array.isArray(e)){const t=e.filter(yt);return t.length>0?t:void 0}if(lt(e)){const t=Array.isArray(e.enum)?e.enum.filter(yt):void 0,n=yt(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(yt(e))return e}function $t(e){if(!lt(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=vt(r);void 0!==e&&(t[n]=e)}return t}function It(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function _t(e){if(!lt(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 xt(e){return"string"==typeof e?e:""}function St(e){return"string"==typeof e&&e.trim()?e:void 0}function jt(e){const t=lt(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(lt(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o=e;n.has(o)||(n.add(o),t.push({name:e}))}}return t}(t.icons);for(const e of n){if(!lt(e))continue;const t="string"==typeof e.name?e.name:"",n=xt(e.description),o=St(e.id),i=St(e.ukey),a=lt(e.props)?e.props:lt(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:St(e.cover),size:ht(e),props:$t(a),slots:It(s),instance_swap:_t(c)})}return{variables:lt(t.variables)?t.variables:lt(t.style)?ft(t.style):lt(t.styles)?ft(t.styles):lt(t.tokens)?t.tokens:{},components:r,icons:o}}function wt(e){if(Array.isArray(e))return e.map(wt);if(lt(e)){const t={};for(const[n,r]of Object.entries(e))"ukey"!==n&&(t[n]=wt(r));return t}return e}function Nt(e,t){const n=k(e||"component").replace(/^-+|-+$/g,"").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function Pt(e){try{const t=await l.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function Dt(e,t){await l.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(yt(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(yt)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(lt(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 Ct(e,t){await l.writeFile(e,t.endsWith("\n")?t:`${t}\n`,"utf8")}async function At(e){try{await l.unlink(e)}catch(e){if("ENOENT"!==e?.code)throw e}}function Et(e){return"index.json"===s.basename(e).toLowerCase()}function Lt(e){const t=s.join(e,"index.md");if(o(t))return t;const n=s.join(e,"index.json");return o(n)?n:""}async function Tt(e){if(!o(e))return[];const t=[],n=s.join(e,"catalog.json"),r=await Pt(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(),c=String(n?.indexPath||"").trim();if(!r||!c)continue;const d=s.join(e,c);if(!o(d))continue;const u=c.split(/[\\/]/)[0]||r,m=await Pt(s.join(e,u,"meta.json")),l=String(m?.teamLibrary?.id||"").trim(),p=String(m?.teamLibrary?.name||"").trim(),g=String(m?.teamLibrary?.slug||"").trim();t.push({id:l||r,name:p||i||a||l||r,slug:g||a||k(p||i||l||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await l.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=s.join(e,r),a=await Pt(s.join(i,"meta.json"));if(a?.paths?.index){const e=s.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.id||"").trim(),o=String(a?.teamLibrary?.name||"").trim(),i=String(a?.teamLibrary?.slug||"").trim();t.push({id:n||r,name:o||i||n||r,slug:i||k(o||n||r),indexAbsPath:e});continue}}const c=await l.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of c){if(!e.isDirectory())continue;const n=Lt(s.join(i,e.name));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){if(!Et(e.indexAbsPath))continue;const t=await Pt(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=k(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 Mt(){const{data:e,error:t}=await q("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function Ot(e){return null==e||"string"==typeof e&&""===e.trim()}function Vt(e){if(Array.isArray(e))return{documentName:"",documentId:"",variables:e};if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Vt(t.data);const n=Array.isArray(t.variables)?t.variables:[];return{documentName:String(t.documentName??""),documentId:String(t.documentId??""),variables:n}}return{documentName:"",documentId:"",variables:[]}}function Ft(e){if(Array.isArray(e))return e;if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Ft(t.data);if(Array.isArray(t.operations))return t.operations;if(Array.isArray(t.variables))return t.variables;if(t.name||t.id||t.action||t.collection||t.collectionId)return[t]}return[]}function Jt(e){if(Array.isArray(e))return e.some(e=>Jt(e));if(!e||"object"!=typeof e)return!1;const t=e;return"deleteVariable"===String(t.action||"")||(Array.isArray(t.operations)?Jt(t.operations):!!Array.isArray(t.variables)&&Jt(t.variables))}async function kt(e){if(void 0!==e.operations)return{operations:Ft(e.operations),source:"operations 参数",documentId:e.documentId};if(void 0!==e.variables)return{operations:Ft(e.variables),source:"variables 参数",documentId:e.documentId};const t=s.resolve(e.projectDir),n=e.filePath?s.resolve(e.filePath):e.documentId?s.join(t,T,"variable",`${k(e.documentId)}.json`):await async function(e){const t=s.join(e,T,"variable");let n=[];try{n=await f(t)}catch{return null}const r=await Promise.all(n.filter(e=>e.toLowerCase().endsWith(".json")).map(async e=>{const n=s.join(t,e);return{filePath:n,mtimeMs:(await y(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0]?.filePath||null}(t);if(!n)throw new Error("未找到变量文件。请先调用 get_variables,或显式传入 filePath / variables / operations。");const r=await async function(e){const t=await b(e,"utf8");return JSON.parse(t)}(n),o=String(r?.documentId||e.documentId||"").trim()||void 0;return{operations:Ft(r),source:n,documentId:o}}const Gt=[Le,ze,He,Re,tt,{name:"get_library_list",description:C.getLibraryList.description,inputSchema:n.object({}).describe(C.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("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:"📚 团队库列表为空(请确认 MCP 服务已连接且当前文件已订阅团队库)"}]};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) 让用户确定团队库(可回复:使用 <团队库名称> 团队库)\n2) 调用 get_component_info,每次从远端拉取并覆盖落盘 index.md + components/icons/variable\n`}]}}},{name:"get_component_info",description:C.getComponentInfo.description,inputSchema:{projectDir:n.string().describe(C.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe(C.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe(C.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe(C.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:a=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const c=s.resolve(t);de(c);const d=s.join(c,T,"library"),u=s.join(d,"catalog.json"),m=await Tt(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:`✅ 检测到本地 ${T}/library 已有落盘团队库(共 ${e.length} 个)。本地列表仅用于选择团队库;选定后仍会重新远端拉取并覆盖落盘。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await Mt()).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组件库生成页面的标准流程:选定团队库后调用 get_component_info,每次都远端拉取并覆盖落盘。`}]}}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(!Ot(t)){const e=mt(String(t)),n=mt(k(String(t))),o=r.filter(t=>{const r=mt(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!Ot(n)){const e=mt(String(n)),t=mt(k(String(n))),o=r.filter(n=>{const r=mt(n.name),o=mt(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=mt(t.name),r=mt(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=Et(e.indexAbsPath)?await Pt(e.indexAbsPath):null,n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||l||"",i=r||e.id||l||"";l=i||l||e.id,p=o||p||e.name}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 ${T}/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!l&&p)try{const e=await Mt(),t=mt(p),n=e.filter(e=>mt(e?.name)===t),r=e.filter(e=>!Ot(e?.name)&&mt(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 Mt()).find(e=>String(e?.id||"")===String(l));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(l)}&includePropertyDetails=${a?"true":"false"}`,{data:y,error:b}=await q("GET",f);if(b)return{content:[{type:"text",text:`❌ 获取失败: ${b.message}`}],isError:!0};const h=jt(y?.data??y),v=k(l),$=k(p||l),I=(new Date).toISOString(),_=s.join(d,v),x=s.join(_,$),S=s.join(x,"components");o(_)||i(_,{recursive:!0}),o(x)||i(x,{recursive:!0}),o(S)||i(S,{recursive:!0});const j=[],w=[],N=new Set;h.components.forEach((e,t)=>{let n=Nt(e.name,t),r=1;for(;N.has(n);)r+=1,n=`${Nt(e.name,t)}-${r}`;N.add(n),j.push(n),w.push(Dt(s.join(x,`components/${n}.json`),{id:e.id,name:e.name,description:e.description||"",cover:e.cover,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const P="icons.json",D="variable.json";await Promise.all([...w,Dt(s.join(x,P),{icons:h.icons}),Dt(s.join(x,D),{variables:wt(h.variables)})]);const C=s.join(x,"index.md"),A=function(e){const{teamLibraryName:t,componentKeys:n,iconsFileName:r,variablesFileName:o}=e,i=n.length?n.map(e=>`- \`${e}.json\``).join("\n"):"- 暂无组件文件";return[`# ${a=t,String(a??"").replace(/\s+/g," ").trim()} 组件库索引`,"","## 快速定位","- 组件数据在 `components/*.json`。每个组件文件都自带 `name`、`description`、`cover`、`size`、`props`、`slots`、`instance_swap`。",`- 图标数据在 \`${r}\`,只能使用 \`icons[].name\` 中存在的图标名。`,`- 样式变量在 \`${o}\`,使用 \`var(...)\` 时写变量 \`name\`,例如 \`{ "name": "填充色/常规" }\` 写 \`var(填充色/常规)\`,禁止改写或直接写 \`value\`。`,"- 本文件只负责导航。生成页面时,以具体组件 JSON、图标 JSON、变量 JSON 为准。","","## 文件结构","- `components/` - 组件详情目录","- `components/{key}.json` - 单个组件详情",`- \`${r}\` - 图标列表`,`- \`${o}\` - 设计变量`,"","## 组件文件",i].join("\n");var a}({teamLibraryName:String(p||l),componentKeys:j,iconsFileName:P,variablesFileName:D});await Promise.all([Ct(C,A),At(s.join(x,"index.json"))]);const E={schemaVersion:1,updatedAt:I,teamLibrary:{id:String(l),name:String(p||l),slug:$},paths:{index:s.relative(_,C),icons:s.relative(_,s.join(x,P)),variables:s.relative(_,s.join(x,D)),componentsDir:s.relative(_,S)}};var L;return await Dt(s.join(_,"meta.json"),E),await async function(e,t){const n=await Pt(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id&&e.indexPath!==t.indexPath);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 Dt(e,o)}(u,{id:String(l),name:String(p||l),slug:$,updatedAt:I,componentCount:h.components.length,iconCount:h.icons.length,indexPath:s.relative(d,C)}),L={teamLibraryId:String(l),teamLibraryName:p||String(l),filePath:C,baseDir:c},R=!0,L.teamLibraryId?.trim()&&(B=L.teamLibraryId.trim()),L.teamLibraryName?.trim()&&(W=L.teamLibraryName.trim()),L.filePath?.trim()&&(K=L.filePath.trim()),L.baseDir?.trim()&&(Y=L.baseDir.trim()),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${l})\n- 目录: ${x}\n- 索引说明文件: ${C}\n- 组件详情文件: ${j.length} 个\n- 图标文件: ${s.join(x,P)}\n- 变量文件: ${s.join(x,D)}\n- 全局目录索引: ${u}\n\n下一步生成页面前:读取 index.md 导航,再读取 components/*.json、icons.json、variable.json;具体生成规则以 component-import.md 为准。`}]}}},{name:"get_variables",description:C.getVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.getVariables.projectDir)}).describe(C.getVariables.inputSchema),handler:async e=>{const{projectDir:t}=e,n=s.resolve(t);de(n);const{data:r,error:o}=await q("GET","/api/getVariables");if(o)return{content:[{type:"text",text:`❌ 获取失败: ${o.message}`}],isError:!0};const{documentName:i,documentId:a,variables:c}=Vt(r),d=s.join(n,T,"variable"),u=s.join(d,`${k(a)}.json`),m=new Set(c.map(e=>e?.collection).filter(Boolean)),l=new Set(c.flatMap(e=>Array.isArray(e?.mode)?e.mode:[]).map(e=>e?.name).filter(Boolean)),f={documentName:i,documentId:a,collections:m.size,modes:l.size,variables:c.length};let y="";try{await p(d,{recursive:!0}),await g(u,`${JSON.stringify(c,null,2)}\n`,"utf8"),ne=!0,re=!1,oe=G((b={projectDir:n,documentId:a,filePath:u}).projectDir),ie=String(b.documentId||"").trim(),ae=G(b.filePath),y=`\n\n已落盘: ${u}`}catch(e){y=`\n\n⚠️ 落盘失败: ${e?.message||String(e)}(路径: ${u})`}var b;return{content:[{type:"text",text:`✅ 当前文件变量已获取\n\n摘要:\n${JSON.stringify(f,null,2)}\n\n完整数据:\n${JSON.stringify({documentName:i,documentId:a,variables:c},null,2)}`+y}]}}},{name:"update_variables",description:C.updateVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.updateVariables.projectDir),documentId:n.string().optional().describe(C.updateVariables.documentId),documentPageId:n.string().optional().describe(C.updateVariables.documentPageId),filePath:n.string().optional().describe(C.updateVariables.filePath),variables:n.any().optional().describe(C.updateVariables.variables),operations:n.any().optional().describe(C.updateVariables.operations)}).describe(C.updateVariables.inputSchema),handler:async e=>{de(e.projectDir);const t=Ee(["variable-generate"],"update_variables",{followUp:`请先按以上规则读取/修改 ${T}/variable/{documentId}.json,再次调用 update_variables 提交。`});if(t)return t;let n;try{n=await kt(e)}catch(e){return{content:[{type:"text",text:`❌ 读取变量数据失败: ${e?.message||String(e)}`}],isError:!0}}const r=n.operations;if(Jt(r))return{content:[{type:"text",text:"❌ update_variables 不执行 deleteVariable。删除变量是危险操作,请改用 agent_remove_variable。"}],isError:!0};if(!r.length)return{content:[{type:"text",text:"❌ 没有可提交的变量操作。请传入数组、{ variables: [...] }、{ operations: [...] },或使用 get_variables 落盘后的变量 JSON。"}],isError:!0};const o=e.documentId||n.documentId,i=function(e){const t=G(e.projectDir),n=String(e.documentId||"").trim();if(!ne||oe&&t&&oe!==t||ie&&n&&ie!==n)return{content:[{type:"text",text:`❌ 变量更新前必须先调用 get_variables 获取并落盘当前文件变量。\n\n请先执行:\n1) get_variables({ projectDir })\n2) 读取 ${T}/variable/{documentId}.json\n3) 基于落盘数据生成本次变量变更\n4) 再调用 update_variables${t?`\n- 当前 projectDir: ${t}`:""}${n?`\n- 当前 documentId: ${n}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 上一次 update_variables 已提交,继续提交前必须重新调用 get_variables 刷新变量快照。\n\n推荐顺序:基础变量 update_variables -> get_variables 落盘 -> 语义变量 update_variables -> get_variables 落盘。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:e.projectDir,documentId:o});if(i)return i;const a=pe(r);if(a)return a;const{data:s,error:c}=await q("POST","/api/updateVariables",{documentId:o,documentPageId:e.documentPageId,operations:r});if(c)return{content:[{type:"text",text:`❌ 变量更新失败: ${c.message}`}],isError:!0};const d=Array.isArray(s?.results)?s.results:[],u=d.filter(e=>!1===e?.success),m={source:n.source,documentId:o||"",status:s?.status||"completed",operations:r.length,succeeded:d.length?d.length-u.length:void 0,failed:u.length||void 0},l=!1===s?.success||u.length>0?`\n\n失败明细:\n${JSON.stringify(u.length?u:s?.error,null,2)}`:"",p=!1!==s?.success&&0===u.length;var g;return p&&(g={projectDir:e.projectDir,documentId:o},ne=!0,re=!0,oe=G(g.projectDir)||oe,ie=String(g.documentId||"").trim()||ie),{content:[{type:"text",text:`✅ 变量更新指令已发送\n\n摘要:\n${JSON.stringify(m,null,2)}\n\n结果:\n${JSON.stringify(d,null,2)}`+l+`\n\n建议:更新完成后再次调用 get_variables,刷新 ${T}/variable/{documentId}.json。`}],isError:!p}}},Ve,Te,Ke,et,nt,rt,ut,st,ct,dt],qt=v.enabledTools?Gt.filter(e=>v.enabledTools.includes(e.name)):Gt;const Ut=new e({name:v.serverName,version:"1.0.34"},{instructions:Se,capabilities:{resources:{},tools:{},prompts:{}}});var zt;zt=Ut,qt.forEach(e=>{zt.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await Ut.connect(e)}catch(e){process.exit(1)}
|
package/dist/page-generate.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
## 工作流与输出格式(强制)
|
|
18
18
|
1. 先完成「构思与决策」:需求重构分析 + 视觉与架构策略。
|
|
19
19
|
2. 若是普通文本回复场景:可先输出 Markdown 的「构思与决策」,再输出 HTML 代码块。
|
|
20
|
-
3. 若是工具调用场景(调用 `
|
|
20
|
+
3. 若是工具调用场景(调用 `submit_page_to_canvas`):`code` 参数只能包含纯 HTML 根节点片段,禁止混入 Markdown、清单、解释文本、```代码围栏```。
|
|
21
21
|
4. 不允许跳过步骤直接出代码,也不允许把“构思与决策”内容塞进 `code` 字段。
|
|
22
22
|
|
|
23
23
|
### 核心设计哲学(强制映射)
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
2. 禁止 Grid;布局只允许 Flex。
|
|
39
39
|
3. 禁止相对单位:`%`、`vw`、`vh`、`rem`、`em`、`calc()`。
|
|
40
40
|
4. Flex 容器必须写全:`flex` + `flex-row/col` + `justify-*` + `items-*`。
|
|
41
|
-
5. 禁止比例 flex:`flex-[2.5]`
|
|
41
|
+
5. 禁止比例 flex:`flex-[2.5]` 等;采用"主区 `flex-1` + 次区定宽(如 `w-[360px]`)"。
|
|
42
|
+
6. 禁止 Tailwind 渐变工具类:`bg-gradient-to-*` / `bg-linear-to-*` / `bg-radial-*` / `bg-conic-*` 以及配套的 `from-*` / `via-*` / `to-*`。需要渐变时**只能**用 Tailwind 任意值写完整 CSS,例如:
|
|
43
|
+
`bg-[linear-gradient(135deg,#3B82F6_0%,#8B5CF6_100%)]`,颜色与位置都明确写出。
|
|
42
44
|
|
|
43
45
|
## 图层原子化协议
|
|
44
46
|
1. `<div>` 只做容器(背景/边框/阴影/布局)。
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
- [x] 已选择并解释核心设计哲学(A-E)映射。
|
|
54
56
|
- [x] 已确认无原生表单标签。
|
|
55
57
|
- [x] 已确认无 margin、无 grid、无相对单位。
|
|
56
|
-
- [x]
|
|
58
|
+
- [x] 已确认多栏布局采用"次区定宽 + 主区 flex-1"。
|
|
57
59
|
- [x] 已确认图片仅用 `<img src="{{keyword}}" />`。
|
|
58
60
|
- [x] 已确认图标使用 FontAwesome 且非手绘。
|
|
59
61
|
- [x] 已确认所有节点都有 `data-name` 且文案非占位词。
|
|
@@ -6,16 +6,29 @@
|
|
|
6
6
|
- 本文档只定义「如何创建 / 修改 / 删除变量」。
|
|
7
7
|
- 不涉及「如何在页面 HTML 中引用变量」—— 那部分见 `variable-import.md`。
|
|
8
8
|
|
|
9
|
+
## 零容忍硬规则(先看,再读其余)
|
|
10
|
+
|
|
11
|
+
1. **禁止创建任何语义尺寸变量**。包括但不限于:
|
|
12
|
+
- 间距:`间距/...`、`spacing/...`、`gap/...`
|
|
13
|
+
- 圆角:`圆角/...`、`radius/...`、`corner/...`
|
|
14
|
+
- 描边:`描边/...`、`border/...`、`stroke/...`
|
|
15
|
+
- 字号 / 行高 / 字距 等任何"语义命名 + 数值"的尺寸引用
|
|
16
|
+
|
|
17
|
+
尺寸语义在页面里直接使用基础变量即可(`规范尺寸/8`),不需要再套一层语义命名。
|
|
18
|
+
多套一层语义尺寸会让 `variable-import` 解析时找不到引用路径,并污染设计系统索引。
|
|
19
|
+
|
|
20
|
+
2. **语义层只允许颜色变量**(`type: "PAINT"`,`name` 以 `颜色/...` 开头)。其它类型一律放 `基础` collection。
|
|
21
|
+
|
|
9
22
|
## 流程
|
|
10
23
|
|
|
11
24
|
1. `get_variables` 落盘 `{{docDir}}/variable/{documentId}.json`
|
|
12
25
|
2. 读取并修改落盘数组
|
|
13
26
|
3. 创建整套变量时分两批:
|
|
14
27
|
- 第一批:基础变量
|
|
15
|
-
-
|
|
28
|
+
- 第二批:语义变量(**仅颜色**)
|
|
16
29
|
4. 每次 `update_variables` 后都重新 `get_variables`
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
禁止把"基础变量"和"引用基础变量的语义变量"放在同一批提交。
|
|
19
32
|
|
|
20
33
|
## 数据格式
|
|
21
34
|
|
|
@@ -43,13 +56,14 @@
|
|
|
43
56
|
语义
|
|
44
57
|
```
|
|
45
58
|
|
|
46
|
-
类型和用途写进 `name
|
|
59
|
+
类型和用途写进 `name`。**`语义` collection 只放颜色**:
|
|
47
60
|
|
|
48
61
|
```json
|
|
49
62
|
{ "collection": "基础", "name": "基础色板/蓝色/600" }
|
|
50
63
|
{ "collection": "基础", "name": "规范尺寸/8" }
|
|
64
|
+
{ "collection": "基础", "name": "规范圆角/12" }
|
|
65
|
+
{ "collection": "基础", "name": "规范字号/14" }
|
|
51
66
|
{ "collection": "语义", "name": "颜色/填充/操作/主要" }
|
|
52
|
-
{ "collection": "语义", "name": "间距/组件/中" }
|
|
53
67
|
```
|
|
54
68
|
|
|
55
69
|
## 类型 type
|
|
@@ -146,10 +160,6 @@
|
|
|
146
160
|
]
|
|
147
161
|
```
|
|
148
162
|
|
|
149
|
-
语义尺寸:
|
|
150
|
-
|
|
151
|
-
- 不要创建任何语义尺寸
|
|
152
|
-
|
|
153
163
|
## 引用
|
|
154
164
|
|
|
155
165
|
- `reference` 写变量名字符串,不写 `var(...)`
|
|
@@ -171,6 +181,7 @@
|
|
|
171
181
|
- 已读取最新 `{{docDir}}/variable/{documentId}.json`
|
|
172
182
|
- 基础和语义已分批
|
|
173
183
|
- 默认只用 `基础` / `语义` 两个 collection
|
|
184
|
+
- **`语义` collection 内只含颜色变量(`颜色/...` + `type: "PAINT"`),无任何 `间距/`、`圆角/`、`描边/`、`字号/` 路径**
|
|
174
185
|
- 修改已有变量保留 `id`
|
|
175
186
|
- 删除变量只调用 `agent_remove_variable`,不要在 `update_variables` 中写 `deleteVariable`
|
|
176
187
|
- 每个 `mode` 都有 `name`
|
package/dist/variable-import.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- 本文档必须与 `page-generate.md` 的全部硬规则同时满足;若冲突以 `page-generate.md` 为最高优先级。
|
|
7
7
|
|
|
8
8
|
## 模式入口
|
|
9
|
-
变量来源有且只有两种,由
|
|
9
|
+
变量来源有且只有两种,由 design_page 工具的上下文明确告知:
|
|
10
10
|
|
|
11
11
|
1. 「使用当前文件设计系统」:变量来源为 `{{docDir}}/variable/{documentId}.json`
|
|
12
12
|
2. 「使用组件库」:变量来源为 `{{docDir}}/library/<team>/variable.json`
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
两种来源 JSON 结构一致,引用写法一致;下面统称「变量清单 JSON」。
|
|
15
15
|
|
|
16
16
|
## 数据来源(唯一事实)
|
|
17
|
-
- 变量清单 JSON 路径由
|
|
17
|
+
- 变量清单 JSON 路径由 design_page 工具上下文给出,禁止臆造路径。
|
|
18
18
|
- 每个变量项的关键字段:
|
|
19
19
|
- `name`:变量名,**唯一用于 `var(...)` 引用的字段**
|
|
20
20
|
- `value` / `mode[].value`:仅供你理解语义,**禁止直接抄到 class 上**
|
package/dist/zh.template.json
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
"inputSchema": "可选参数:scope,字符串数组,元素取值 page-generate / component-import / component-generate / variable-import / variable-generate。不传默认 [\"page-generate\"]。多个 scope 同传会附加 Final Hard Gate。"
|
|
5
5
|
},
|
|
6
6
|
"design": {
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "【页面设计主入口 · 必须首选】当用户表达「设计 / 创建 / 做一个 / 帮我画 / 生成 一个页面」等任意页面级生成需求时,必须首先调用本工具,禁止直接调用 submit_page_to_canvas 跳过本流程。本工具负责需求拆解、设计来源选择、HTML+CSS 代码生成,并最终在产出完整页面 HTML 后调用 submit_page_to_canvas 推送到画布。流程:先让用户在「自由绘制 / 使用当前文件设计系统 / 使用组件库」三者中选一;选『当前文件设计系统』会强制 get_variables 落盘并按 variable-import.md 引用变量;选『组件库』会要求选择团队库 + 构建策略;最后才生成页面。",
|
|
8
8
|
"requirement": "界面需求描述,例如:\"一个美观的登录页面\"、\"现代化的仪表盘界面\"等。",
|
|
9
9
|
"code": "【可选】若已生成完整代码(可含 Markdown + <main>),传入后将自动提取 <main> 并直接提交到画布。",
|
|
10
10
|
"projectDir": "【选填】用户当前工作区的根目录绝对路径。变量场景与组件库场景下用于读取/写入 {{docDir}} 落盘数据。",
|
|
11
|
-
"designSource": "【必填(流程)】设计来源三选一:free-draw(自由绘制)/ current-file-variables(使用当前文件设计系统)/ component-library(使用组件库)。每次调用
|
|
11
|
+
"designSource": "【必填(流程)】设计来源三选一:free-draw(自由绘制)/ current-file-variables(使用当前文件设计系统)/ component-library(使用组件库)。每次调用 design_page 都应先让用户明确选择。",
|
|
12
12
|
"userConfirmedDesignSource": "【必填(流程)】是否已完成本次「设计来源三选一」的用户确认。每次新页面流程都应传 true 才可继续。",
|
|
13
13
|
"teamLibraryName": "【组件库模式必填】用户确认选择的团队库名称。每次都需要重新确认,不允许助手自动选库。",
|
|
14
14
|
"teamLibraryId": "【组件库模式推荐】用户确认选择的团队库 ID。若已通过 get_library_list 拿到 ID,建议同时传入;用于精确判定是否仍是同一个团队库,避免名字归一化撞车的不同库被误复用。",
|
|
15
15
|
"buildStrategy": "【组件库模式必填】构建策略:full-components / hybrid。full-components 优先使用组件库组件;hybrid 仅关键功能区使用组件。两种策略都必须遵守 component-import.md + variable-import.md 中的组件、变量、图标规则。未指定时必须先询问用户二选一。"
|
|
16
16
|
},
|
|
17
17
|
"createPage": {
|
|
18
|
-
"description": "
|
|
18
|
+
"description": "【内部提交工具 · 仅在 design_page 流程结束后调用】将已生成完整 HTML 推送到 {{pluginName}} 画布。⚠️ 当用户说『设计 / 创建 / 做一个 / 帮我画 / 生成 一个页面』时,禁止直接调用本工具,必须先调用 design_page 走完需求拆解与设计来源选择流程;本工具只负责把 design_page 产出的最终 HTML 提交到画布。【极致性能要求】若纯 HTML 已保存为本地文件,强烈建议通过 filePath 传入该文件的绝对路径以节省 Token;若是直接生成的临时代码,可通过 code 参数直接传入。",
|
|
19
19
|
"code": "【可选】要发送的 HTML 代码内容。仅当代码是临时生成且未保存为文件时使用。",
|
|
20
20
|
"filePath": "【可选】本地 HTML 文件的绝对路径。若文件已存在本地,必须传此参数,工具会自动读取并执行落盘。",
|
|
21
21
|
"projectDir": "【必填】用户当前工作区的根目录绝对路径",
|
|
@@ -50,11 +50,16 @@
|
|
|
50
50
|
"userConfirmed": "【必填】用户是否已明确确认“同步到画布”。仅在用户主动要求同步时传 true。"
|
|
51
51
|
},
|
|
52
52
|
"getSelectionCode": {
|
|
53
|
-
"description": "获取节点代码:拉取 MasterGo 当前选中图层(或指定图层)的最新代码。本工具仅做\"读取/同步上下文\",不要在没有用户具体修改诉求的情况下,紧接着自动调用 agent_update_node / agent_replace_node / sync_to_design。执行任何修改前应先调用本工具,避免基于旧代码操作。拉取子节点时仅返回代码文本;拉取根节点时会同步完整页面到 {{docDir}}
|
|
53
|
+
"description": "获取节点代码:拉取 MasterGo 当前选中图层(或指定图层)的最新代码。本工具仅做\"读取/同步上下文\",不要在没有用户具体修改诉求的情况下,紧接着自动调用 agent_update_node / agent_replace_node / sync_to_design。执行任何修改前应先调用本工具,避免基于旧代码操作。拉取子节点时仅返回代码文本;拉取根节点时会同步完整页面到 {{docDir}} 基准文件。如需视觉参考(图片/预览图),请单独调用 get_selection_image。",
|
|
54
54
|
"projectDir": "【必填】用户当前工作区的根目录绝对路径",
|
|
55
55
|
"targetNodeId": "【选填】MasterGo图层ID (例如 123:456)。如果提供,将直接拉取该ID的代码;如果不提供,将拉取当前选中图层的代码。",
|
|
56
|
-
"syncToBase": "【选填】是否将获取到的子图层代码同步回本地 {{docDir}} 目录下的基准 HTML 文件(合并更新)。默认为 true。"
|
|
57
|
-
|
|
56
|
+
"syncToBase": "【选填】是否将获取到的子图层代码同步回本地 {{docDir}} 目录下的基准 HTML 文件(合并更新)。默认为 true。"
|
|
57
|
+
},
|
|
58
|
+
"getSelectionImage": {
|
|
59
|
+
"description": "获取节点预览图:拉取 MasterGo 当前选中图层(或指定图层)的渲染预览图(默认 PNG)。仅在你需要看一眼设计稿做视觉参考时调用,例如:\"参考设计稿还原\"、\"按图重新写一下\"、\"对照视觉调整\"、\"复刻这个设计\"、\"match the design\" 等。本工具会同时返回 image content 用于多模态判断,并把图片落盘到 {{docDir}}/.preview/ 便于用户核对。日常纯代码同步/小修请使用 get_selection_code,不要调用本工具。",
|
|
60
|
+
"projectDir": "【必填】用户当前工作区的根目录绝对路径;预览图会落盘到该目录下的 {{docDir}}/.preview/{documentId}/{pageId}/{nodeId}.png。",
|
|
61
|
+
"targetNodeId": "【选填】MasterGo图层ID (例如 123:456)。如果提供,将直接导出该ID的预览图;如果不提供,将导出当前选中图层。",
|
|
62
|
+
"targetNodeIds": "【选填】批量目标图层 ID 列表,传入后按列表顺序逐个导出预览图。"
|
|
58
63
|
},
|
|
59
64
|
"createComponent": {
|
|
60
65
|
"description": "创建组件:创建一个 MasterGo 母版组件或组件集(变体)。仅在创建母版组件/组件集时,先调用 get_guidelines(scope=[\"component-generate\"]) 加载组件生成规则;团队库页面生成不要使用 component-generate。本工具仅负责发送到 MasterGo,不做规范兜底校验。",
|
|
@@ -84,11 +89,11 @@
|
|
|
84
89
|
"outDir": "【必填】保存代码和资源的绝对路径"
|
|
85
90
|
},
|
|
86
91
|
"getLibraryList": {
|
|
87
|
-
"description": "团队库列表:获取当前文件已订阅/已加载的团队库列表(名称、ID、组件数、样式数)。仅在用户明确说“使用 xxx 团队库/组件库的组件生成页面”时才允许调用;普通
|
|
92
|
+
"description": "团队库列表:获取当前文件已订阅/已加载的团队库列表(名称、ID、组件数、样式数)。仅在用户明确说“使用 xxx 团队库/组件库的组件生成页面”时才允许调用;普通 design_page 场景禁止调用。",
|
|
88
93
|
"inputSchema": "无需参数"
|
|
89
94
|
},
|
|
90
95
|
"getComponentInfo": {
|
|
91
|
-
"description": "组件信息:获取指定团队库组件数据并落盘为本地快照,用于组件库模式生成页面。每次指定团队库后都从远端重新拉取并覆盖落盘。仅当用户明确要求“使用某团队库/组件库生成页面”时调用;普通
|
|
96
|
+
"description": "组件信息:获取指定团队库组件数据并落盘为本地快照,用于组件库模式生成页面。每次指定团队库后都从远端重新拉取并覆盖落盘。仅当用户明确要求“使用某团队库/组件库生成页面”时调用;普通 design_page 场景禁止调用。未传 teamLibraryId 时会先返回团队库列表供选择,用户确认后可用 teamLibraryName 自动匹配 ID。生成页面规则见 component-import.md。",
|
|
92
97
|
"projectDir": "【必填】用户当前工作区的根目录绝对路径(用于落盘)",
|
|
93
98
|
"teamLibraryId": "【选填】团队库 ID。若提供则直接获取该库组件信息。",
|
|
94
99
|
"teamLibraryName": "【选填】团队库名称。若未提供 teamLibraryId,可用名称自动匹配团队库 ID。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codify-ai/mcp-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "Codify MCP 客户端 - 连接到远程 Codify MCP 服务器,供 CLI 或 Cursor 等 IDE 使用",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
36
36
|
"axios": "^1.6.0",
|
|
37
37
|
"node-html-parser": "^7.1.0",
|
|
38
|
-
"zod": "^3.
|
|
38
|
+
"zod": "^3.25.0"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|