@codify-ai/mcp-client 1.0.32 → 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/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,readdirSync as i,statSync as a,mkdirSync as s}from"fs";import c,{dirname as d,resolve as u}from"path";import{fileURLToPath as m}from"url";import l from"axios";import p from"fs/promises";import{parse as g}from"node-html-parser";const f=d(m(import.meta.url)),y=r(u(f,"page-generate.md"),"utf-8"),h=r(u(f,"component-import.md"),"utf-8"),b=r(u(f,"component-generate.md"),"utf-8"),x=d(m(import.meta.url));const $=function(e){const t=u(x,`${e}.json`),n=r(t,"utf8");return JSON.parse(n)}("en"===process.env.CODIFY_LANG?"en":"zh");let I=!1,w=!1,S="",N="",j="",v=!1,C=!1,_=!1;function P(){return v}function D(){return C}function L(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function A(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function E(e){return A(e).replace(/[^a-z0-9\u4e00-\u9fa5]+/g,"")}function T(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return L(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?L(n[1]):""}(e),n=w&&S.trim()&&t.trim()&&(r=t,A(S)===A(r));var r;I=!0,n||(w=!1,N="",j="",S=t)}function k(e){w=!0,e.teamLibraryName?.trim()&&(S=e.teamLibraryName.trim()),e.filePath?.trim()&&(N=e.filePath.trim()),e.baseDir?.trim()&&(j=e.baseDir.trim())}function M(e){const t=c.join(e,".codify","library");if(!o(t))return"";const n=S.trim(),s=A(n),d=(u=n,u.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)).toLowerCase();var u;const m=E(n),l=function(e,t,n,i){const a=c.join(e,"catalog.json");if(!o(a))return"";try{const s=r(a,"utf8"),d=JSON.parse(s),u=Array.isArray(d?.libraries)?d.libraries:[];if(0===u.length)return"";const m=t=>{if(!t.indexPath)return"";const n=c.join(e,t.indexPath);return o(n)?n:""};if(!t)return 1!==u.length?"":m(u[0]);const l=u.find(e=>{const n=A(String(e.name||"")),r=A(String(e.slug||"")),o=A(String(e.id||""));return n===t||r===t||o===t});if(l)return m(l);const p=u.find(e=>{const r=String(e.name||""),o=String(e.slug||""),a=String(e.id||""),s=A(r),c=A(o),d=A(a),u=E(r),m=E(o);return s.includes(t)||t.includes(s)||c.includes(t)||t.includes(c)||d.includes(t)||r.toLowerCase().includes(n)||o.toLowerCase().includes(n)||u.includes(i)||i.includes(u)||m.includes(i)||i.includes(m)});if(p)return m(p)}catch{return""}return""}(t,s,d,m);if(l)return l;const p=i(t).flatMap(e=>{const n=c.join(t,e);try{const t=a(n);if(t.isFile()&&"index.json"===e.toLowerCase())return[n];if(!t.isDirectory())return[];const s=[],d=c.join(n,"meta.json");if(o(d))try{const e=r(d,"utf8"),t=JSON.parse(e),i=t?.paths?.index;if("string"==typeof i&&i.trim()){const e=c.join(n,i);o(e)&&s.push(e)}}catch{}for(const e of i(n)){const t=c.join(n,e);try{if(!a(t).isDirectory())continue;const e=c.join(t,"index.json");o(e)&&s.push(e)}catch{}}return s}catch{return[]}}).filter(e=>o(e));if(0===p.length)return"";const g=[...p.map(e=>({filePath:e,stem:c.basename(e,".json"),display:c.basename(c.dirname(e))}))].sort((e,t)=>{const n="index"===e.stem.toLowerCase();return n===("index"===t.stem.toLowerCase())?0:n?-1:1});if(s){const e=g.find(e=>{const t=A(e.stem),n=A(e.display);return e.stem.toLowerCase()===d||t===s||n===s});if(e)return e.filePath;const t=g.find(e=>{const t=A(e.stem),n=A(e.display),r=E(e.stem),o=E(e.display);return t.includes(s)||s.includes(t)||n.includes(s)||s.includes(n)||e.stem.toLowerCase().includes(d)||r.includes(m)||m.includes(r)||o.includes(m)||m.includes(o)});if(t)return t.filePath}return 1===p.length?p[0]:""}function O(e,t){if(!I||w)return null;if(function(e){if(!I||w)return!0;const t=M(e);return!!t&&(k({teamLibraryName:S,filePath:t}),!0)}(t||j||process.cwd()))return null;return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但在 .codify/library 未找到可用落盘数据。\n\n请先按顺序执行:\n1) 先检查 .codify/library 是否已有对应团队库 index.json\n2) 若没有,再调用 get_library_list(先询问用户并确认团队库)\n3) 调用 get_component_info(拉取并落盘 index + components/icons 分片)\n4) 然后再继续「${e}」当前流程\n${S?`\n- 目标团队库: ${S}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,否则高概率转译失败。`}],isError:!0}}function F(){if(!I||!w)return"";return`\n\n🧩 组件库模式已就绪,生成前必须先读取 index.json(name/description),再按 components/\${key}.json 读取组件详情:${S?`\n- 团队库: ${S}`:""}${N?`\n- 组件索引文件: ${N}`:""}\n- 先确认构建策略:full-components / hybrid\n- full-components 模式:可组件化区块必须使用 <ui-component>,仅在库中确实不存在时才可局部回退 HTML,并需说明缺失原因\n- hybrid 模式:视觉优先,关键功能区使用组件;并且必须使用组件库样式/变量与图标系统\n- 样式变量规则:优先使用 variable.json 中变量,关键样式属性使用 var(...)(如 bg/text/gap/padding/radius/border/shadow);var(...) 内必须是变量名(name/ukey),禁止直接写 value\n- <ui-component>.name/props 只能来自 components[]\n- 子实例替换规则:若组件使用 instance_swap(如“图标/前缀/尾部操作/状态子组件”),必须写在 <ui-component instance_swap='...'> 上;禁止用子节点伪造同一替换位\n- 若该替换位存在对应 props 开关(如“显示图标/显示右边图标/启用前缀”),使用替换时必须显式设为 true\n- <ui-component> 的布局样式按排版需要声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- <ui-icon>.name 只能来自 icons[]\n- 禁止臆造组件名、图标名或 props\n- 禁止在未读取 library 数据前直接生成组件引用`}const J=["# Final Hard Gate","","在调用提交类工具前,至少满足:","1) 仅输出根节点片段;禁止 html/head/body/script/style/link/meta/title","2) 所有节点包含 data-name;禁用 margin/grid/原生表单标签","3) 禁止相对单位(%/vw/vh/rem/em/calc),尺寸使用绝对像素","4) Flex 容器写全:flex + flex-row|flex-col + justify-* + items-*","5) 组件模式下,<ui-component>/<ui-icon> 仅可引用落盘 JSON 中存在的 name/props/slots/icon","6) 组件模式下,<ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]",'7) 若用户诉求包含“图标/icon/替换图标”,非组件库场景必须使用 FontAwesome <i class="fas/fa* fa-...">,禁止 div/svg/path 手绘图标(除非用户明确要求自定义 SVG)',"8) 组件库模式下,样式优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 中必须使用变量名(name/ukey),禁止直接抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true"].join("\n");function G(e){return"page-generate"===e?y:"component-generate"===e?["# Component Generate Rules (Master Component / Component Set)",b,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):"component-import"===e?["# Component Import Rules",h,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):["# Page Generate Rules (Base, Must Follow)",y,"","# Component Import Rules (Apply Only In Component Mode)",h,"",J].join("\n")}function q(e){return"all"===e?P()&&D():"page-generate"===e?P():"component-import"===e?D():_}function H(e){const t=q(e);return t||function(e){switch(e){case"all":v=!0,C=!0;break;case"page-generate":v=!0;break;case"component-import":C=!0;break;case"component-generate":_=!0}}(e),{loadedNow:!t,text:G(e)}}const z={name:"get_codify_guidelines",description:$.getCodifyGuidelines.description,inputSchema:n.object({scope:n.enum(["all","page-generate","component-import","component-generate"]).optional()}).describe($.getCodifyGuidelines.inputSchema),handler:async e=>{const t=e?.scope||"all",{text:n}=H(t);return{content:[{type:"text",text:n}]}}},R=process.argv.find(e=>e.startsWith("--url=")),U=R?R.slice(6):process.env.CODIFY_SERVER_URL||"https://mcp.codify-api.com",W=process.env.CODIFY_ACCESS_KEY,B=".codify",V="design",Y=c.join(B,"created"),Z="请确认已启动 MasterGo 并打开 Codify 插件(保持插件面板在线后重试)。",K=["/api/getTeamLibraryList","/api/getComponentInfo","/api/createPage","/api/getSelectionCode","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const Q=e=>null==e||""===e||"string"==typeof e&&""===e.trim();async function X(e,t,n=null){const r={};W&&(r.Authorization=`Bearer ${W}`);const o={method:e,url:`${U}${t}`,headers:r,...n&&{data:n}};try{return{data:(await l(o)).data,error:null}}catch(e){const n=e.response?.status,r=e.response?.data||{};let o=r.message||e.message;return 401===n&&(o="认证失败,请检查 CODIFY_ACCESS_KEY"),403===n&&(o=void 0!==r.mcp_get_limit||void 0!==r.mcp_generate_limit||o.includes("limit")?`配额不足: ${o}`:`权限不足: ${o}`),404===n&&(o="未找到 API 终结点或活跃连接"),function(e,t,n){const r=String(n||"").toLowerCase(),o=K.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes("Codify 插件")||e.includes("MasterGo")?e:`${e}\n\n${Z}`:Z}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function ee(e,t,n,r){if(Q(e))return 0;const i="string"==typeof e?JSON.parse(e):e,a=Object.keys(i);if(0===a.length)return 0;const d=c.join(t,n);o(d)||s(d,{recursive:!0});const u=Object.entries(i).map(async([e,t])=>{const n=e.match(/(.+)\.([a-zA-Z0-9]+)$/),o=(n?n[1]:e).replace(/[^a-zA-Z0-9_-]/g,"_"),i=n?n[2]:r,a=c.join(d,`${o}.${i}`);let s=t;if("string"==typeof s&&s.startsWith("http"))try{const e=await l.get(s,{responseType:"arraybuffer"});await p.writeFile(a,e.data)}catch(e){}else if("string"==typeof s&&s.startsWith("data:image/")){const e=s.split(";base64,");2===e.length&&await p.writeFile(a,e[1],"base64")}else{const e="object"==typeof s?JSON.stringify(s,null,2):s,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await p.writeFile(a,e,t)}});return await Promise.all(u),a.length}async function te({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:i,targetNodeId:a,nodeName:d,code:u,resourcePath:m,shape:l,svg:g,image:f,isSelection:y=!1}){let h=t?c.resolve(e,t):n&&r?c.join(e,B,V,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!i||y||a?"":"code"):y?c.join(e,Y,"selection"):i?c.join(e,Y,i):c.join(e,Y);o(h)||s(h,{recursive:!0});const b=String(a||Date.now()).replace(/:/g,"-"),x=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),$=y||a||d?`${x}-${b}.html`:`${i||"index"}.html`,I=c.join(h,$);u&&await p.writeFile(I,u,"utf8");const w=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!Q(e))try{const n="string"==typeof e?JSON.parse(e):e;n.image&&(t.image=n.image.replace(/^\.\//,"")),n.svg&&(t.svg=n.svg.replace(/^\.\//,"")),n.shape&&(t.shape=n.shape.replace(/^\.\//,""))}catch(e){}return t}(m),S=await Promise.all([ee(l,h,w.shape,"json"),ee(g,h,w.svg,"svg"),ee(f,h,w.image,"png")]);return{targetDir:h,htmlFileName:$,htmlPath:I,shapeCount:S[0],svgCount:S[1],imageCount:S[2],resourcePathMap:w}}const ne={name:"agent_create_component",description:$.createComponent.description,inputSchema:{code:n.string().describe($.createComponent.code)},handler:async e=>{const{code:t}=e,n=H("component-generate");if(n.loadedNow)return{content:[{type:"text",text:`🧭 已自动加载规则(component-generate):\n${n.text}\n\n请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。`}],isError:!0};const{error:r}=await X("POST","/api/createComponent",{code:t});return r?{content:[{type:"text",text:`❌ 创建失败: ${r.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向插件发送组件创建指令"}]}}};function re(e){const t=function(e){const t=String(e||"").trim(),n=t.match(/^```[a-zA-Z0-9_-]*\n([\s\S]*?)\n```$/);return n?.[1]?.trim()||t}(e);return function(e){const t=String(e||"");return/&(lt|gt);/i.test(t)||/&#x0*3c;|&#x0*3e;/i.test(t)||/&#0*60;|&#0*62;/i.test(t)}(t)?function(e){const t={"&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&apos;":"'","&amp;":"&","&nbsp;":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|&#39;/gi,e=>t[e.toLowerCase()]||e);return n=n.replace(/&#(\d+);/g,(e,t)=>{const n=Number.parseInt(t,10);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n=n.replace(/&#x([0-9a-fA-F]+);/g,(e,t)=>{const n=Number.parseInt(t,16);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n}(t).trim():t}function oe(e){const t=String(e||"").trim();if(!t)return t;const n=function(e){const t=String(e||"").match(/<main\b[\s\S]*?<\/main>/i);return(t?.[0]||"").trim()}(t);if(n)return n;const r=function(e){const t=[...String(e||"").matchAll(/```(?:html)?\s*([\s\S]*?)```/gi)];for(const e of t){const t=String(e[1]||"").trim();if(/<(main|div|section|article|aside|header|footer)\b/i.test(t))return t}return""}(t);if(r)return r;const o=t.search(/<(main|div|section|article|aside|header|footer)\b/i);return o>0?t.slice(o).trim():t}const ie={name:"agent_create_page",description:$.createPage.description,inputSchema:{code:n.string().optional().describe($.createPage.code),filePath:n.string().optional().describe($.createPage.filePath),projectDir:n.string().describe($.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe($.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=c.resolve(e.filePath);r=await p.readFile(t,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取文件失败: ${e.message}`}],isError:!0}}if(!r)return{content:[{type:"text",text:"参数错误: 请提供 code 或 filePath"}],isError:!0};const o=oe(re(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:i,error:a}=await X("POST","/api/createPage",{code:o});if(a)return{content:[{type:"text",text:`❌ 发送失败: ${a.message}`}],isError:!0};let s=function(e,t="操作"){const n="accepted"===e?.status;let r=n?`✅ ${t}请求已发送到 Codify 插件,画布仍在后台处理中`:`✅ ${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}(i,"设计稿生成");if(n)try{const e=t?c.resolve(t):process.cwd();s+=`\n- 代码已本地持久化至: ${(await te({baseDir:e,code:i.htmlCode||o,documentId:i.documentId,documentPageId:i.documentPageId,targetNodeId:i.targetNodeId,nodeName:i.nodeName,resourcePath:i.resourcePath,shape:i.shape,svg:i.svg,image:i.image})).htmlPath}`}catch(e){s+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:s}]}}};let ae=null;function se(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const ce="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 agent_create_page(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function de(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}const ue={name:"design",description:$.design.description,inputSchema:{requirement:n.string().describe($.design.requirement),code:n.string().optional().describe($.design.code),projectDir:n.string().optional().describe($.design.projectDir),useComponentLibrary:n.boolean().optional().describe($.design.useComponentLibrary),userConfirmedUseComponentLibrary:n.boolean().optional().default(!1).describe($.design.userConfirmedUseComponentLibrary),teamLibraryName:n.string().optional().describe($.design.teamLibraryName),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe($.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,useComponentLibrary:o,userConfirmedUseComponentLibrary:i=!1,teamLibraryName:a,buildStrategy:s}=e;if("string"==typeof n&&n.trim()){const e=r?c.resolve(r):process.cwd();return ie.handler({code:se(n),projectDir:e})}if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};const d=t.trim();if(ae&&ae.requirement===d||(ae={requirement:d,confirmed:!1}),!(ae.confirmed||"boolean"==typeof o&&i))return{content:[{type:"text",text:"⏸️ 继续设计前,请先让用户确认:是否使用组件库来生成页面?\n\n请二选一后再次调用 design:\n1) 使用组件库:useComponentLibrary: true\n2) 不使用组件库:useComponentLibrary: false\n\n并且必须同时传:userConfirmedUseComponentLibrary: true"}],isError:!1};if(ae.confirmed=!0,!o){I=!1,w=!1,S="",N="",j="";const{loadedNow:e,text:n}=H("page-generate"),r=e?`🧭 自动加载规则(page-generate):\n${n}\n\n`:"🧭 规则状态:page-generate 已加载,本次不重复展开。\n\n";return ae=null,{content:[{type:"text",text:`${r}📋 收到需求:${t}\n\n请直接按 page-generate 规则生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}`}],isError:!1}}if(!a?.trim())return{content:[{type:"text",text:'⏸️ 你已选择“使用组件库生成页面”,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 先检查本地 .codify/library 是否已有可用团队库快照\n2) 若本地没有,再调用 get_library_list 获取团队库列表\n3) 询问用户选择具体团队库(禁止助手自动选择)\n\n用户确认后再次调用 design,并传入:\n- useComponentLibrary: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!1};T(`使用 ${a} 团队库`);const u=O("design",r?c.resolve(r):void 0);if(u)return u;if(!s)return{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design:\n1) full-components(全组件设计模式:包括组件、样式变量、图标系统;样式优先使用 var(...))\n2) hybrid(混合设计模式:仅必要组件 + 全量使用组件库样式变量与图标系统;样式优先使用 var(...))`}],isError:!1};const{loadedNow:m,text:l}=H("all"),p=m?`🧭 自动加载规则(page-generate + component-import):\n${l}\n\n`:"🧭 规则状态:page-generate + component-import 已加载,本次不重复展开。\n\n",g="full-components"===s?"\n【full-components 执行约束】\n- 页面功能区必须优先使用 <ui-component>,禁止把可组件化区块改为普通 HTML 自绘\n- <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 样式必须优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因\n- 若某区块未组件化,必须是“本地组件快照中不存在可替代组件”,并在输出前自检逐项说明缺失原因":"\n【hybrid 执行约束】\n- 视觉优先,关键功能区使用组件\n- 已使用的 <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 非关键区可自由绘制,但必须全量使用组件库样式变量与图标系统\n- 样式必须优先使用 variable.json 的 var(...) token;var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因";return{content:[{type:"text",text:`${p}📋 收到需求:${t}\n- 团队库:${a}\n- 构建策略:${de(s)}\n${g}\n\n请严格基于落盘组件库 JSON(index/components/icons/variable)生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}\n\n${F()}\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 修改现有节点时默认优先使用 agent_update_node;如果修改未生效,先返回诊断信息并等待下一步指令。`}],isError:!1}}},me={name:"get_code",description:$.getCode.description,inputSchema:{contentId:n.string().describe($.getCode.contentId),documentId:n.string().optional().describe($.getCode.documentId),documentPageId:n.string().optional().describe($.getCode.documentPageId),projectDir:n.string().describe($.getCode.projectDir),outDir:n.string().describe($.getCode.outDir)},handler:async e=>{const{contentId:t,outDir:n,projectDir:r,documentId:o,documentPageId:i}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 contentId"}],isError:!0};const{data:a,error:s}=await X("GET",`/api/getCode/${t}`);if(s)return{content:[{type:"text",text:`❌ 获取失败: ${s.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?c.resolve(r):process.cwd(),u=await te({baseDir:d,outDir:n,documentId:o,documentPageId:i,contentId:t,code:a.code,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image});let m=`✅ 代码拉取完成\n- 目录: ${u.targetDir}\n- 文件: ${u.htmlFileName}\n`;return u.shapeCount>0&&(m+=`- Shape: ${u.shapeCount} 个\n`),u.svgCount>0&&(m+=`- SVG: ${u.svgCount} 个\n`),u.imageCount>0&&(m+=`- Image: ${u.imageCount} 个\n`),{content:[{type:"text",text:m}]}}},le={name:"get_code_list",description:$.getCodeList.description,inputSchema:n.object({}).describe($.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getCodeList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};if(!Array.isArray(e)||0===e.length)return{content:[{type:"text",text:"📋 代码列表为空"}]};const n=e.length,r=n>=10?e.slice(0,10):e;let o=`✅ 成功获取代码列表 (共 ${n} 项)\n`;return n>=10&&(o+="⚠️ 仅展示最近更新的前 10 条记录\n"),o+=`\n${JSON.stringify(r,null,2)}`,{content:[{type:"text",text:o}]}}},pe=e=>{const t=String(e?.parentDirection||e?.parentDirection||"").trim().toLowerCase();return"row"===t||"column"===t?t:""},ge=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`:""},fe=e=>{const{nodeName:t,targetNodeId:n,rootId:r,nodeInfo:o,htmlPath:i}=e;return`✅ 成功获取并保存根节点代码\n- 节点: ${t} (${n})\n- 根节点: ${r}\n${ge(o)?`${ge(o)}\n`:""}- 文件: ${i}\n- 上下文策略: 根节点代码未回显到对话中,请直接修改本地落盘文件;修改完成后,再通过网络请求同步到 Codify 插件。`},ye={name:"get_selection_code",description:$.getSelectionCode.description,inputSchema:{projectDir:n.string().describe($.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe($.getSelectionCode.targetNodeId),targetNodeIds:n.array(n.string()).optional().describe("[可选] 批量目标图层 ID 列表,传入后按列表顺序拉取代码。"),syncToBase:n.boolean().default(!0).describe($.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 s=r.filter(e=>"string"==typeof e&&e.trim().length>0),d=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}`:s.length>0?`/api/getSelectionCode?ids=${encodeURIComponent(s.join(","))}`:"/api/getSelectionCode",{data:u,error:m}=await X("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 l=Array.isArray(u.selections)?u.selections:[],f=l.length>1?"all":"primary";if(l.length>1){const e=[];e.push(`✅ 成功获取多选节点代码(共 ${l.length} 个)`),e.push(`- 模式: ${f}`);const n=[];for(const[r,o]of l.entries()){const i=o?.nodeInfo||{},a=i?.targetNodeId||i?.nodeId||`unknown-${r+1}`,s=i?.nodeName||`node-${r+1}`,d=i?.rootId,u=i?.documentId,m=i?.documentPageId,l=pe(i);e.push(`- [${r+1}] ${s} (${a})`);const p=ge(i);if(p&&e.push(` ${p.replace(/^- /,"")}`),l&&e.push(` parentDirection: ${l}`),a===d){let n="";if(u&&m){n=(await te({baseDir:t?c.resolve(t):process.cwd(),documentId:u,documentPageId:m,targetNodeId:d,nodeName:s,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}] ${s} (${a})\n\n\`\`\`html\n${o?.code||""}\n\`\`\``)}return{content:[{type:"text",text:`${e.join("\n")}\n\n${n.join("\n\n")}`}]}}const y=1===l.length?l[0]:null,h=u.nodeInfo||y?.nodeInfo||{},b=u.code??y?.code??"",x=u.resourcePath??y?.resourcePath,$=u.shape??y?.shape,I=u.svg??y?.svg,w=u.image??y?.image,S=t?c.resolve(t):process.cwd(),N=h||{},j=pe(N),{rootId:v,documentId:C,documentPageId:_,targetNodeId:P,nodeName:D,parentId:L}=N;if(P===v){const e=await te({baseDir:S,documentId:C,documentPageId:_,targetNodeId:v,nodeName:D,code:b,resourcePath:x,shape:$,svg:I,image:w});return{content:[{type:"text",text:fe({nodeName:D,targetNodeId:P,rootId:v,nodeInfo:N,htmlPath:e.htmlPath})}]}}{let e="",n="";if(i&&C&&_){const r=c.join(S,B,V,String(C).replace(/:/g,"-"),String(_).replace(/:/g,"-")),i=c.join(S,B,String(C).replace(/:/g,"-"),String(_).replace(/:/g,"-")),s=String(v).replace(/:/g,"-");let d="",u=r;const m=async()=>{const e=async e=>{if(!o(e))return"";const t=(await p.readdir(e)).filter(e=>e.endsWith(`-${s}.html`));if(0===t.length)return"";if(1===t.length)return t[0];const n=await Promise.all(t.map(async t=>({fileName:t,mtimeMs:(await p.stat(c.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return u=r,t;const n=await e(i);return n?(u=i,n):""};if(d=await m(),d||0!==a||(await ye.handler({projectDir:t,targetNodeId:v,syncToBase:!0,_depth:a+1}),d=await m()),d){const t=c.join(u,d);try{const n=await p.readFile(t,"utf8"),r=g(n),o=r.querySelector(`[data-node-id="${P}"]`);if(o)o.replaceWith(b);else if(L){const e=r.querySelector(`[data-node-id="${L}"]`);e&&e.insertAdjacentHTML("beforeend",b)}(o||L&&r.querySelector(`[data-node-id="${L}"]`))&&(await p.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const r=await te({baseDir:S,documentId:C,documentPageId:_,targetNodeId:P,nodeName:D,code:"",resourcePath:x,shape:$,svg:I,image:w});let s=`✅ 成功获取子节点代码\n- 节点: ${D} (${P})\n- 根节点: ${v}`;const d=ge(N);d&&(s+=`\n${d}`),j&&(s+=`\n- 父容器方向: ${j}`),s+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。可以直接针对返回代码进行修改。",n&&(s+=`\n- ${n}`),P!==v&&(s+=`\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&&(s+=`\n${u}`),{content:[{type:"text",text:`${s}\n\n代码内容:\n\n${b}`}]}}}},he={name:"get_user_info",description:$.getUserInfo.description,inputSchema:n.object({}).describe($.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getUserInfo");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};let n=`✅ 用户信息\n\n👤 用户: ${e.realname||e.userName||e.userId}\n`;if(e.quota){const t=Number(e?.quota?.plan??e?.plan??0),r=t>0?"不限量":String(e.quota.mcp_generate_limit||"无"),o=t>0?"不限量":String(e.quota.mcp_get_limit||"无");n+="\n📊 配额:\n",n+=` - 生成设计: ${e.quota.mcp_generate_count||0} / ${r}\n`,n+=` - 获取代码: ${e.quota.mcp_get_count||0} / ${o}\n`}return e.teams?.length&&(n+="\n👥 团队:\n",e.teams.forEach((e,t)=>n+=` ${t+1}. ${e.name} (ID: ${e.teamId})\n`)),{content:[{type:"text",text:n}]}}},be={name:"agent_update_node",description:$.updateNode.description,inputSchema:{documentId:n.string().optional().describe($.updateNode.documentId),documentPageId:n.string().optional().describe($.updateNode.documentPageId),targetNodeId:n.string().optional().describe($.updateNode.targetNodeId),code:n.string().describe($.updateNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止灾难性地覆盖当前选中的未知图层,本次操作已被拦截。请明确指定 targetNodeId 或在 HTML 包含 data-node-id。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});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 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}},xe={name:"agent_replace_node",description:$.replaceNode.description,inputSchema:{documentId:n.string().optional().describe($.replaceNode.documentId),documentPageId:n.string().optional().describe($.replaceNode.documentPageId),targetNodeId:n.string().optional().describe($.replaceNode.targetNodeId),code:n.string().describe($.replaceNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止把您当前极可能选中的大容器直接覆盖损毁,系统已拦截。请务必明确目标节点的 ID。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});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 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}};function $e(e){return String(e).replace(/:/g,"-")}async function Ie(e,t){if(!o(e))return"";const n=(await p.readdir(e,{withFileTypes:!0})).filter(e=>e.isFile()&&e.name.endsWith(`-${t}.html`));if(0===n.length)return"";const r=await Promise.all(n.map(async t=>{const n=c.join(e,t.name);return{fullPath:n,mtimeMs:(await p.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function we(e,t){if(!o(e))return"";const n=await p.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=c.join(e,o.name);if(o.isDirectory()){const e=await we(n,t);if(e){const t=await p.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await p.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const Se={name:"get_design_diff",description:$.getDesignDiff.description,inputSchema:{projectDir:n.string().describe($.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe($.getDesignDiff.targetNodeId),filePath:n.string().optional().describe($.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?c.resolve(t):process.cwd();let i=n,a="",s=r||"";if(s)try{const e=c.isAbsolute(s)?s:c.resolve(o,s);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 无法读取指定的 filePath: ${e.message}`}],isError:!0}}let d="";if(!a||!i){const e=i?`/api/getSelectionCode?id=${encodeURIComponent(i)}`:"/api/getSelectionCode",{data:t,error:n}=await X("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:u,documentId:m,documentPageId:l}=t.nodeInfo;if(d=r,i||(i=u),!a&&r){const e=String(r).replace(/:/g,"-"),t=m&&l?c.join(o,B,V,$e(m),$e(l)):"",n=m&&l?c.join(o,B,$e(m),$e(l)):"";if(t&&(s=await Ie(t,e)),!s&&n&&(s=await Ie(n,e)),!s){const t=c.join(o,B,V);s=await we(t,e)}if(!s){const t=c.join(o,B);s=await we(t,e)}s&&(a=await p.readFile(s,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 .codify 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:u,error:m}=await X("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(m)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${m.message}`}],isError:!0};let l=u.diffs||[];return l&&!Array.isArray(l)&&Array.isArray(l.diffs)&&(l=l.diffs),0===l.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${l.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(l,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 .codify 的基准文件。`}]}}},Ne={name:"agent_remove_node",description:$.removeNode.description,inputSchema:{documentId:n.string().optional().describe($.removeNode.documentId),documentPageId:n.string().optional().describe($.removeNode.documentPageId),targetNodeId:n.string().optional().describe($.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await X("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});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}`}]}}},je={name:"agent_sync_design",description:$.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe($.syncToDesign.documentId),documentPageId:n.string().optional().describe($.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe($.syncToDesign.targetNodeId),filePath:n.string().describe($.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe($.syncToDesign.userConfirmed)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r,filePath:o,userConfirmed:i=!1}=e;if(!i)return{content:[{type:"text",text:"⏸️ 已阻止自动同步到画布。\n\n仅当用户明确提出“请同步到画布”时,才允许调用 agent_sync_design。\n请先向用户确认,再以 userConfirmed=true 重新调用。"}],isError:!0};let a="";if(o)try{const e=c.resolve(o);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取本地文件失败: ${e.message}`}],isError:!0}}if(!a)return{content:[{type:"text",text:"❌ 同步失败: 未能从指定路径读取到有效代码"}],isError:!0};const{data:s,error:d}=await X("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});if(d)return{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0};const u="accepted"===s.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${s.targetNodeId?`\n- 根节点 ID: ${s.targetNodeId}`:""}${u}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 .codify 基准。`}]}}};function ve(e){return e.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)}function Ce(e){return String(e??"").trim().toLowerCase()}function _e(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Pe(e){if(!_e(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.ukey&&e.ukey.trim()?e.ukey:void 0,r="string"==typeof e.description?e.description:"",o=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||_e(e)?e:String(e)}(e.value);return void 0===o?{ukey:n,name:t,description:r}:{ukey:n,name:t,description:r,value:o}}function De(e){return function(e){return Array.isArray(e)?e:[]}(e).map(Pe).filter(e=>!!e)}function Le(e){const t=De(e.paints);return{boxShadow:De(e.effects),color:t,fill:t,borderRadius:De(e.cornerRadiuses),padding:De(e.paddings),gap:De(e.spacings),borderWidth:De(e.strokeWidths),typography:De(e.texts)}}function Ae(e){return null===e||["string","number","boolean"].includes(typeof e)}function Ee(e){const t=Number(e);return Number.isFinite(t)?t:0}function Te(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[Ee(t[0]),Ee(t[1])];const n=_e(e.style)?e.style:{};return[Ee(e.width??n.width),Ee(e.height??n.height)]}function ke(e){if(Array.isArray(e)){const t=e.filter(Ae);return t.length>0?t:void 0}if(_e(e)){const t=Array.isArray(e.enum)?e.enum.filter(Ae):void 0,n=Ae(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(Ae(e))return e}function Me(e){if(!_e(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=ke(r);void 0!==e&&(t[n]=e)}return t}function Oe(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function Fe(e){if(!_e(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 Je(e){return"string"==typeof e?e:""}function Ge(e){return"string"==typeof e&&e.trim()?e:void 0}function qe(e){const t=_e(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(_e(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o="string"==typeof r.id&&r.id.trim()?r.id.trim():void 0,i=`${o||""}::${e}`;n.has(i)||(n.add(i),t.push({id:o,name:e}))}}return t}(t.icons);for(const e of n){if(!_e(e))continue;const t="string"==typeof e.name?e.name:"",n=Je(e.description),o=Ge(e.id),i=Ge(e.ukey),a=_e(e.props)?e.props:_e(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:Ge(e.cover),size:Te(e),props:Me(a),slots:Oe(s),instance_swap:Fe(c)})}return{variables:_e(t.variables)?t.variables:_e(t.style)?Le(t.style):_e(t.styles)?Le(t.styles):_e(t.tokens)?t.tokens:{},components:r,icons:o}}function He(e,t){const n=ve(e||"component").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function ze(e){try{const t=await p.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function Re(e,t){await p.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(Ae(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(Ae)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(_e(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 Ue(e){if(!o(e))return[];const t=[],n=c.join(e,"catalog.json"),r=await ze(n);if(Array.isArray(r?.libraries))for(const n of r.libraries){const r=String(n?.id||"").trim(),i=String(n?.name||"").trim(),a=String(n?.slug||"").trim(),s=String(n?.indexPath||"").trim();if(!r||!s)continue;const d=c.join(e,s);o(d)&&t.push({id:r,name:i||a||r,slug:a||ve(i||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await p.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=c.join(e,r),a=await ze(c.join(i,"meta.json"));if(a?.paths?.index){const e=c.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.name||"").trim(),o=String(a?.teamLibrary?.slug||"").trim();t.push({id:r,name:n||o||r,slug:o||ve(n||r),indexAbsPath:e});continue}}const s=await p.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of s){if(!e.isDirectory())continue;const n=c.join(i,e.name,"index.json");if(!o(n))continue;const a=e.name;t.push({id:r,name:a,slug:a,indexAbsPath:n})}}const a=function(e){const t=new Map;for(const n of e)n.indexAbsPath&&t.set(n.indexAbsPath,n);return Array.from(t.values())}(t);for(const e of a){const t=await ze(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=ve(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 We(){const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function Be(e){return null==e||"string"==typeof e&&""===e.trim()}const Ve=[z,ue,me,le,he,{name:"get_library_list",description:$.getLibraryList.description,inputSchema:n.object({}).describe($.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};const n=e?.data??e;if(!Array.isArray(n)||0===n.length)return{content:[{type:"text",text:"📚 团队库列表为空(请确认插件已连接且当前文件已订阅团队库)"}]};const r=n.map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`✅ 当前文件团队库列表(共 ${r.length} 个)\n\n${JSON.stringify(r,null,2)}\n\n若用户要求“使用某组件库生成页面”,请按最短流程:\n1) 先检查 .codify/library 是否已有该团队库 JSON(优先本地)\n2) 若本地没有,再确定团队库(可回复:使用 <团队库名称> 团队库)\n3) 调用 get_component_info 拉取并落盘 index + 组件详情 JSON\n`}]}}},{name:"get_component_info",description:$.getComponentInfo.description,inputSchema:{projectDir:n.string().describe($.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe($.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe($.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe($.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:i=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const a=c.resolve(t),d=c.join(a,B,"library"),u=c.join(d,"catalog.json"),m=await Ue(d);let l=n,p=r;if(!l&&!p){if(m.length>0){const e=m.map(e=>({id:e.id,name:e.name,componentCount:e.componentCount??void 0,iconCount:e.iconCount??void 0,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`✅ 检测到本地 .codify/library 已有落盘团队库(共 ${e.length} 个),优先复用本地快照。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await We()).map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`⚠️ 你还没有指定团队库。\n\n当前文件团队库列表(共 ${e.length} 个):\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info 并传入 teamLibraryName。\n\n组件库生成页面的标准流程:先本地复用,未命中才远端拉取并落盘。`}]}}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}}const g=function(e,t,n){if(!e.length)return{status:"none"};let r=[...e];if(!Be(t)){const e=Ce(String(t)),n=Ce(ve(String(t))),o=r.filter(t=>{const r=Ce(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!Be(n)){const e=Ce(String(n)),t=Ce(ve(String(n))),o=r.filter(n=>{const r=Ce(n.name),o=Ce(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=Ce(t.name),r=Ce(t.slug);return n.includes(e)||e.includes(n)||r.includes(e)||e.includes(r)});if(!t.length)return{status:"none"};r=t}}return 1===r.length?{status:"matched",entry:r[0]}:{status:"ambiguous",candidates:r}}(m,l,p);if("matched"===g.status){const e=g.entry,t=await ze(e.indexAbsPath),n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||l||"",i=r||e.id||l||"",s=c.dirname(e.indexAbsPath),d=c.join(s,"icons.json"),u=c.join(s,"variable.json");return k({teamLibraryName:o||i,filePath:e.indexAbsPath,baseDir:a}),{content:[{type:"text",text:`♻️ 已命中本地团队库快照,跳过远端拉取。\n- 团队库: ${o} (${i})\n- 索引文件: ${e.indexAbsPath}\n- 图标文件: ${d}\n- 变量文件: ${u}\n- 组件目录: ${c.join(s,"components")}\n\n⚠️ 下一步生成页面前,必须先读取 index.json,再按 components/\${key}.json 读取组件详情,并读取 icons.json + variable.json。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 修改现有节点时默认优先使用 agent_update_node;如果修改未生效,先返回诊断信息并等待下一步指令。`}]}}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 .codify/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!l&&p)try{const e=await We(),t=Ce(p),n=e.filter(e=>Ce(e?.name)===t),r=e.filter(e=>!Be(e?.name)&&Ce(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 We()).find(e=>String(e?.id||"")===String(l));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(l)}&includePropertyDetails=${i?"true":"false"}`,{data:y,error:h}=await X("GET",f);if(h)return{content:[{type:"text",text:`❌ 获取失败: ${h.message}`}],isError:!0};const b=qe(y?.data??y),x=ve(l),$=ve(p||l),I=(new Date).toISOString(),w=c.join(d,x),S=c.join(w,$),N=c.join(S,"components");o(w)||s(w,{recursive:!0}),o(S)||s(S,{recursive:!0}),o(N)||s(N,{recursive:!0});const j=[],v=[],C=new Set;b.components.forEach((e,t)=>{let n=He(e.name,t),r=1;for(;C.has(n);)r+=1,n=`${He(e.name,t)}-${r}`;C.add(n),j.push({key:n,id:e.id,ukey:e.ukey,name:e.name,description:e.description||"",cover:e.cover}),v.push(Re(c.join(S,`components/${n}.json`),{id:e.id,ukey:e.ukey,name:e.name,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const _="icons.json",P="variable.json";await Promise.all([...v,Re(c.join(S,_),{icons:b.icons}),Re(c.join(S,P),{variables:b.variables})]);const D={schemaVersion:2,generatedAt:I,teamLibrary:{id:String(l),name:String(p||l)},summary:{componentCount:b.components.length,iconCount:b.icons.length,componentFileCount:j.length},components:j,files:{componentsDir:"components",pathTemplate:"components/{key}.json",icons:_,variables:P}},L=c.join(S,"index.json");await Re(L,D);const A={schemaVersion:1,updatedAt:I,teamLibrary:{id:String(l),name:String(p||l),slug:$},paths:{index:c.relative(w,L),icons:c.relative(w,c.join(S,_)),variables:c.relative(w,c.join(S,P)),componentsDir:c.relative(w,N)}};return await Re(c.join(w,"meta.json"),A),await async function(e,t){const n=await ze(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id);r.push(t),r.sort((e,t)=>e.name.localeCompare(t.name,"zh-Hans-CN"));const o={schemaVersion:1,updatedAt:t.updatedAt,libraries:r};await Re(e,o)}(u,{id:x,name:String(p||l),slug:$,updatedAt:I,componentCount:b.components.length,iconCount:b.icons.length,indexPath:c.relative(d,L)}),k({teamLibraryName:p||l,filePath:L,baseDir:a}),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${l})\n- 目录: ${S}\n- 索引文件: ${L}\n- 组件详情文件: ${j.length} 个\n- 图标文件: ${c.join(S,_)}\n- 变量文件: ${c.join(S,P)}\n- 全局目录索引: ${u}\n\n⚠️ 下一步生成页面前,必须先读取索引文件,再按固定路径 components/\${key}.json 读取所需组件详情,并读取 icons.json + variable.json:\n结构速记:components[].{id,name,description,size,props,slots,slot_layout,instance_swap} + icons[].{id,name} + variables{}。\n先确认构建策略:full-components 或 hybrid(推荐)。\n变量引用统一使用 var(...),例如 gap-[var(间距_index_10/sm)],变量名称应来自 variable.json 的 variables。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 修改现有节点时默认优先使用 agent_update_node;如果修改未生效,先返回诊断信息并等待下一步指令。`}]}}},ie,ne,ye,be,xe,je,Se,Ne];const Ye=new e({name:"Codify-MCP-Client",version:"1.0.23"},{capabilities:{resources:{},tools:{},prompts:{}}});var Ze;Ye.registerResource("rule","codify://rule",{description:"Codify 聚合规范(page-generate + component-import + 最终硬闸门)"},async e=>({contents:[{uri:e.toString(),text:G("all"),mimeType:"text/markdown"}]})),Ze=Ye,Ve.forEach(e=>{Ze.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await Ye.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)||/&#x0*3c;|&#x0*3e;/i.test(t)||/&#0*60;|&#0*62;/i.test(t)}(t)?function(e){const t={"&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&apos;":"'","&amp;":"&","&nbsp;":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|&#39;/gi,e=>t[e.toLowerCase()]||e);return n=n.replace(/&#(\d+);/g,(e,t)=>{const n=Number.parseInt(t,10);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n=n.replace(/&#x([0-9a-fA-F]+);/g,(e,t)=>{const n=Number.parseInt(t,16);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n}(t).trim():t}function Oe(e){const t=String(e||"").trim();if(!t)return t;const n=function(e){const t=String(e||"").match(/<main\b[\s\S]*?<\/main>/i);return(t?.[0]||"").trim()}(t);if(n)return n;const r=function(e){const t=[...String(e||"").matchAll(/```(?:html)?\s*([\s\S]*?)```/gi)];for(const e of t){const t=String(e[1]||"").trim();if(/<(main|div|section|article|aside|header|footer)\b/i.test(t))return t}return""}(t);if(r)return r;const o=t.search(/<(main|div|section|article|aside|header|footer)\b/i);return o>0?t.slice(o).trim():t}const 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)}
@@ -11,12 +11,13 @@
11
11
  3. 禁止依赖主题默认值:禁止 `w-1/2`、`bg-red-500` 等。
12
12
  4. 每个标签必须包含语义化 `data-name="..."`。
13
13
  5. 图片统一语义:`<img src="{{keyword}}" />`。
14
- 6. 图标统一使用 FontAwesome:`<i class="fas/far fa-xxx text-[size] text-[#hex]">`。
14
+ 6. 图标统一使用 FontAwesome:`<i class="fas/far/fab fa-xxx text-[size] text-[#hex]">`。
15
+ 7. 语境自适应与双轨渲染:底层视觉思维必须调用顶级英文设计模式(如 Bento, Glassmorphism, Swiss Minimalist)。**除非用户明确要求生成英文或其他语言**,否则默认界面可见文本必须采用**高质量的本土商业化中文文案**。
15
16
 
16
17
  ## 工作流与输出格式(强制)
17
18
  1. 先完成「构思与决策」:需求重构分析 + 视觉与架构策略。
18
19
  2. 若是普通文本回复场景:可先输出 Markdown 的「构思与决策」,再输出 HTML 代码块。
19
- 3. 若是工具调用场景(调用 `agent_create_page`):`code` 参数只能包含纯 HTML 根节点片段,禁止混入 Markdown、清单、解释文本、```代码围栏```。
20
+ 3. 若是工具调用场景(调用 `submit_page_to_canvas`):`code` 参数只能包含纯 HTML 根节点片段,禁止混入 Markdown、清单、解释文本、```代码围栏```。
20
21
  4. 不允许跳过步骤直接出代码,也不允许把“构思与决策”内容塞进 `code` 字段。
21
22
 
22
23
  ### 核心设计哲学(强制映射)
@@ -37,7 +38,9 @@
37
38
  2. 禁止 Grid;布局只允许 Flex。
38
39
  3. 禁止相对单位:`%`、`vw`、`vh`、`rem`、`em`、`calc()`。
39
40
  4. Flex 容器必须写全:`flex` + `flex-row/col` + `justify-*` + `items-*`。
40
- 5. 禁止比例 flex:`flex-[2.5]` 等;采用“主区 `flex-1` + 次区定宽(如 `w-[360px]`)”。
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%)]`,颜色与位置都明确写出。
41
44
 
42
45
  ## 图层原子化协议
43
46
  1. `<div>` 只做容器(背景/边框/阴影/布局)。
@@ -52,7 +55,7 @@
52
55
  - [x] 已选择并解释核心设计哲学(A-E)映射。
53
56
  - [x] 已确认无原生表单标签。
54
57
  - [x] 已确认无 margin、无 grid、无相对单位。
55
- - [x] 已确认多栏布局采用“次区定宽 + 主区 flex-1”。
58
+ - [x] 已确认多栏布局采用"次区定宽 + 主区 flex-1"。
56
59
  - [x] 已确认图片仅用 `<img src="{{keyword}}" />`。
57
60
  - [x] 已确认图标使用 FontAwesome 且非手绘。
58
61
  - [x] 已确认所有节点都有 `data-name` 且文案非占位词。
@@ -0,0 +1,189 @@
1
+ # 变量生成规则(写变量)
2
+
3
+ 用于 `get_variables` / `update_variables` / `agent_remove_variable`。
4
+
5
+ ## 规则边界(先读)
6
+ - 本文档只定义「如何创建 / 修改 / 删除变量」。
7
+ - 不涉及「如何在页面 HTML 中引用变量」—— 那部分见 `variable-import.md`。
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
+
22
+ ## 流程
23
+
24
+ 1. `get_variables` 落盘 `{{docDir}}/variable/{documentId}.json`
25
+ 2. 读取并修改落盘数组
26
+ 3. 创建整套变量时分两批:
27
+ - 第一批:基础变量
28
+ - 第二批:语义变量(**仅颜色**)
29
+ 4. 每次 `update_variables` 后都重新 `get_variables`
30
+
31
+ 禁止把"基础变量"和"引用基础变量的语义变量"放在同一批提交。
32
+
33
+ ## 数据格式
34
+
35
+ 只生成数组:
36
+
37
+ ```json
38
+ [
39
+ {
40
+ "collection": "基础",
41
+ "name": "基础色板/蓝色/600",
42
+ "type": "COLOR",
43
+ "mode": [{ "name": "默认", "value": "#2563eb" }]
44
+ }
45
+ ]
46
+ ```
47
+
48
+ 修改已有变量必须保留 `id`;传 `id + name` 可重命名。
49
+
50
+ ## Collection 结构
51
+
52
+ 默认只创建两个 collection:
53
+
54
+ ```txt
55
+ 基础
56
+ 语义
57
+ ```
58
+
59
+ 类型和用途写进 `name`。**`语义` collection 只放颜色**:
60
+
61
+ ```json
62
+ { "collection": "基础", "name": "基础色板/蓝色/600" }
63
+ { "collection": "基础", "name": "规范尺寸/8" }
64
+ { "collection": "基础", "name": "规范圆角/12" }
65
+ { "collection": "基础", "name": "规范字号/14" }
66
+ { "collection": "语义", "name": "颜色/填充/操作/主要" }
67
+ ```
68
+
69
+ ## 类型 type
70
+
71
+ 基础变量:
72
+
73
+ - `COLOR`
74
+ - `NUMBER`
75
+ - `STRING`
76
+ - `BOOLEAN`
77
+
78
+ 复合变量(语义变量):
79
+
80
+ - `PAINT`
81
+ - `TEXT`
82
+ - `EFFECT`
83
+ - `GRID`
84
+ - `STROKE_WIDTH`
85
+ - `RADIUS`
86
+ - `PADDING`
87
+
88
+ 新建变量必须显式写 `type`。
89
+
90
+ ## 颜色
91
+
92
+ 基础色板:
93
+
94
+ - `collection: "基础"`
95
+ - `type: "COLOR"`
96
+ - `name: "基础色板/..."`
97
+ - 只写 `value`
98
+ - 通常只用 `默认` mode
99
+ - 如果用户未提出要求,建议创建 蓝色、灰色、绿色、红色、橙色、紫色
100
+ - 每个颜色都要使用 10 个色阶
101
+
102
+ 示例:
103
+
104
+ ```json
105
+ [
106
+ {
107
+ "collection": "基础",
108
+ "name": "基础色板/蓝色/600",
109
+ "type": "COLOR",
110
+ "mode": [{ "name": "默认", "value": "#2563eb" }]
111
+ }
112
+ ]
113
+ ```
114
+
115
+ 语义颜色:
116
+
117
+ - `collection: "语义"`
118
+ - `type: "PAINT"`
119
+ - `name: "颜色/..."`
120
+ - 必须用 `reference` 引用基础色板
121
+ - 推荐同时创建 `亮色` / `暗色` 两个模式
122
+
123
+ 示例: 重新 `get_variables` 获取到基础色板的 `name`:
124
+
125
+ ```json
126
+ [
127
+ {
128
+ "collection": "语义",
129
+ "name": "颜色/填充/操作/主要",
130
+ "type": "PAINT",
131
+ "mode": [
132
+ { "name": "亮色", "reference": "基础色板/蓝色/600" },
133
+ { "name": "暗色", "reference": "基础色板/蓝色/400" }
134
+ ]
135
+ }
136
+ ]
137
+ ```
138
+
139
+ ## 尺寸
140
+
141
+ 基础尺寸:
142
+
143
+ - `collection: "基础"`
144
+ - `type: "NUMBER"`
145
+ - `name: "规范尺寸/..."`
146
+ - 通常只用 `默认` mode
147
+ - 优先使用 2 的倍数 (包含 2)
148
+ - 如果用户未提出要求,建议一并创建 规范尺寸、规范字号、规范圆角
149
+
150
+ 示例:
151
+
152
+ ```json
153
+ [
154
+ {
155
+ "collection": "基础",
156
+ "name": "规范尺寸/8",
157
+ "type": "NUMBER",
158
+ "mode": [{ "name": "默认", "value": 8 }]
159
+ }
160
+ ]
161
+ ```
162
+
163
+ ## 引用
164
+
165
+ - `reference` 写变量名字符串,不写 `var(...)`
166
+ - 引用目标必须已经存在
167
+ - 变量名不唯一时,改用 `{ "id": "..." }`
168
+
169
+ ## 删除
170
+
171
+ 删除变量是危险操作,必须使用单独的 `agent_remove_variable` 工具,并优先传 `id`:
172
+
173
+ ```json
174
+ {
175
+ "id": "7:07131"
176
+ }
177
+ ```
178
+
179
+ ## 自检
180
+
181
+ - 已读取最新 `{{docDir}}/variable/{documentId}.json`
182
+ - 基础和语义已分批
183
+ - 默认只用 `基础` / `语义` 两个 collection
184
+ - **`语义` collection 内只含颜色变量(`颜色/...` + `type: "PAINT"`),无任何 `间距/`、`圆角/`、`描边/`、`字号/` 路径**
185
+ - 修改已有变量保留 `id`
186
+ - 删除变量只调用 `agent_remove_variable`,不要在 `update_variables` 中写 `deleteVariable`
187
+ - 每个 `mode` 都有 `name`
188
+ - 同一个 mode 不同时写 `value` 和 `reference`
189
+ - 新建变量显式写 `type`