@codify-ai/mcp-client 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -11
- package/dist/component-generate.md +32 -6
- package/dist/component-import.md +32 -48
- package/dist/en.template.json +124 -0
- package/dist/index.js +1 -1
- package/dist/page-generate.md +10 -16
- package/dist/variable-generate.md +178 -0
- package/dist/variable-import.md +79 -0
- package/dist/zh.template.json +123 -0
- package/package.json +1 -1
- package/dist/en.json +0 -95
- package/dist/zh.json +0 -94
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 w=function(e){const t=u(x,`${e}.json`),n=r(t,"utf8");return JSON.parse(n)}("en"===process.env.CODIFY_LANG?"en":"zh");let $=!1,I=!1,S="",j="",v="",N=!1,_=!1,C=!1;function P(){return N}function L(){return _}function D(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function A(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function E(e){return A(e).replace(/[^a-z0-9\u4e00-\u9fa5]+/g,"")}function T(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return D(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?D(n[1]):""}(e),n=I&&S.trim()&&t.trim()&&(r=t,A(S)===A(r));var r;$=!0,n||(I=!1,j="",v="",S=t)}function k(e){I=!0,e.teamLibraryName?.trim()&&(S=e.teamLibraryName.trim()),e.filePath?.trim()&&(j=e.filePath.trim()),e.baseDir?.trim()&&(v=e.baseDir.trim())}function M(e){const t=c.join(e,".codify","library");if(!o(t))return"";const n=S.trim(),s=A(n),d=(u=n,u.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)).toLowerCase();var u;const m=E(n),l=function(e,t,n,i){const a=c.join(e,"catalog.json");if(!o(a))return"";try{const s=r(a,"utf8"),d=JSON.parse(s),u=Array.isArray(d?.libraries)?d.libraries:[];if(0===u.length)return"";const m=t=>{if(!t.indexPath)return"";const n=c.join(e,t.indexPath);return o(n)?n:""};if(!t)return 1!==u.length?"":m(u[0]);const l=u.find(e=>{const n=A(String(e.name||"")),r=A(String(e.slug||"")),o=A(String(e.id||""));return n===t||r===t||o===t});if(l)return m(l);const p=u.find(e=>{const r=String(e.name||""),o=String(e.slug||""),a=String(e.id||""),s=A(r),c=A(o),d=A(a),u=E(r),m=E(o);return s.includes(t)||t.includes(s)||c.includes(t)||t.includes(c)||d.includes(t)||r.toLowerCase().includes(n)||o.toLowerCase().includes(n)||u.includes(i)||i.includes(u)||m.includes(i)||i.includes(m)});if(p)return m(p)}catch{return""}return""}(t,s,d,m);if(l)return l;const p=i(t).flatMap(e=>{const n=c.join(t,e);try{const t=a(n);if(t.isFile()&&"index.json"===e.toLowerCase())return[n];if(!t.isDirectory())return[];const s=[],d=c.join(n,"meta.json");if(o(d))try{const e=r(d,"utf8"),t=JSON.parse(e),i=t?.paths?.index;if("string"==typeof i&&i.trim()){const e=c.join(n,i);o(e)&&s.push(e)}}catch{}for(const e of i(n)){const t=c.join(n,e);try{if(!a(t).isDirectory())continue;const e=c.join(t,"index.json");o(e)&&s.push(e)}catch{}}return s}catch{return[]}}).filter(e=>o(e));if(0===p.length)return"";const g=[...p.map(e=>({filePath:e,stem:c.basename(e,".json"),display:c.basename(c.dirname(e))}))].sort((e,t)=>{const n="index"===e.stem.toLowerCase();return n===("index"===t.stem.toLowerCase())?0:n?-1:1});if(s){const e=g.find(e=>{const t=A(e.stem),n=A(e.display);return e.stem.toLowerCase()===d||t===s||n===s});if(e)return e.filePath;const t=g.find(e=>{const t=A(e.stem),n=A(e.display),r=E(e.stem),o=E(e.display);return t.includes(s)||s.includes(t)||n.includes(s)||s.includes(n)||e.stem.toLowerCase().includes(d)||r.includes(m)||m.includes(r)||o.includes(m)||m.includes(o)});if(t)return t.filePath}return 1===p.length?p[0]:""}function O(e,t){if(!$||I)return null;if(function(e){if(!$||I)return!0;const t=M(e);return!!t&&(k({teamLibraryName:S,filePath:t}),!0)}(t||v||process.cwd()))return null;return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但在 .codify/library 未找到可用落盘数据。\n\n请先按顺序执行:\n1) 先检查 .codify/library 是否已有对应团队库 index.json\n2) 若没有,再调用 get_library_list(先询问用户并确认团队库)\n3) 调用 get_component_info(拉取并落盘 index + components/icons 分片)\n4) 然后再继续「${e}」当前流程\n${S?`\n- 目标团队库: ${S}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,否则高概率转译失败。`}],isError:!0}}function F(){if(!$||!I)return"";return`\n\n🧩 组件库模式已就绪,生成前必须先读取 index.json(name/description),再按 components/\${key}.json 读取组件详情:${S?`\n- 团队库: ${S}`:""}${j?`\n- 组件索引文件: ${j}`:""}\n- 先确认构建策略:full-components / hybrid\n- full-components 模式:可组件化区块必须使用 <ui-component>,仅在库中确实不存在时才可局部回退 HTML,并需说明缺失原因\n- hybrid 模式:视觉优先,关键功能区使用组件;并且必须使用组件库样式/变量与图标系统\n- 样式变量规则:优先使用 variable.json 中变量,关键样式属性使用 var(...)(如 bg/text/gap/padding/radius/border/shadow);var(...) 内必须是变量名(name/ukey),禁止直接写 value\n- <ui-component>.name/props 只能来自 components[]\n- 子实例替换规则:若组件使用 instance_swap(如“图标/前缀/尾部操作/状态子组件”),必须写在 <ui-component instance_swap='...'> 上;禁止用子节点伪造同一替换位\n- 若该替换位存在对应 props 开关(如“显示图标/显示右边图标/启用前缀”),使用替换时必须显式设为 true\n- <ui-component> 的布局样式按排版需要声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- <ui-icon>.name 只能来自 icons[]\n- 禁止臆造组件名、图标名或 props\n- 禁止在未读取 library 数据前直接生成组件引用`}const J=["# Final Hard Gate","","在调用提交类工具前,至少满足:","1) 仅输出根节点片段;禁止 html/head/body/script/style/link/meta/title","2) 所有节点包含 data-name;禁用 margin/grid/原生表单标签","3) 禁止相对单位(%/vw/vh/rem/em/calc),尺寸使用绝对像素","4) Flex 容器写全:flex + flex-row|flex-col + justify-* + items-*","5) 组件模式下,<ui-component>/<ui-icon> 仅可引用落盘 JSON 中存在的 name/props/slots/icon","6) 组件模式下,<ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]",'7) 若用户诉求包含“图标/icon/替换图标”,非组件库场景必须使用 FontAwesome <i class="fas/fa* fa-...">,禁止 div/svg/path 手绘图标(除非用户明确要求自定义 SVG)',"8) 组件库模式下,样式优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 中必须使用变量名(name/ukey),禁止直接抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true"].join("\n");function G(e){return"page-generate"===e?y:"component-generate"===e?["# Component Generate Rules (Master Component / Component Set)",b,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):"component-import"===e?["# Component Import Rules",h,"","# Base Page Generate Rules (Must Also Follow)",y].join("\n"):["# Page Generate Rules (Base, Must Follow)",y,"","# Component Import Rules (Apply Only In Component Mode)",h,"",J].join("\n")}function H(e){return"all"===e?P()&&L():"page-generate"===e?P():"component-import"===e?L():C}function q(e){const t=H(e);return t||function(e){switch(e){case"all":N=!0,_=!0;break;case"page-generate":N=!0;break;case"component-import":_=!0;break;case"component-generate":C=!0}}(e),{loadedNow:!t,text:G(e)}}const R={name:"get_codify_guidelines",description:w.getCodifyGuidelines.description,inputSchema:n.object({scope:n.enum(["all","page-generate","component-import","component-generate"]).optional()}).describe(w.getCodifyGuidelines.inputSchema),handler:async e=>{const t=e?.scope||"all",{text:n}=q(t);return{content:[{type:"text",text:n}]}}},z=process.argv.find(e=>e.startsWith("--url=")),U=z?z.slice(6):process.env.CODIFY_SERVER_URL||"https://mcp.codify-api.com",W=process.env.CODIFY_ACCESS_KEY,B=".codify",V="design",Y=".codify-output",Z="请确认已启动 MasterGo 并打开 Codify 插件(保持插件面板在线后重试)。",K=["/api/getTeamLibraryList","/api/getComponentInfo","/api/createPage","/api/getSelectionCode","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const Q=e=>null==e||""===e||"string"==typeof e&&""===e.trim();async function X(e,t,n=null){const r={};W&&(r.Authorization=`Bearer ${W}`);const o={method:e,url:`${U}${t}`,headers:r,...n&&{data:n}};try{return{data:(await l(o)).data,error:null}}catch(e){const n=e.response?.status,r=e.response?.data||{};let o=r.message||e.message;return 401===n&&(o="认证失败,请检查 CODIFY_ACCESS_KEY"),403===n&&(o=void 0!==r.mcp_get_limit||void 0!==r.mcp_generate_limit||o.includes("limit")?`配额不足: ${o}`:`权限不足: ${o}`),404===n&&(o="未找到 API 终结点或活跃连接"),function(e,t,n){const r=String(n||"").toLowerCase(),o=K.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes("Codify 插件")||e.includes("MasterGo")?e:`${e}\n\n${Z}`:Z}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function ee(e,t,n,r){if(Q(e))return 0;const i="string"==typeof e?JSON.parse(e):e,a=Object.keys(i);if(0===a.length)return 0;const d=c.join(t,n);o(d)||s(d,{recursive:!0});const u=Object.entries(i).map(async([e,t])=>{const n=e.match(/(.+)\.([a-zA-Z0-9]+)$/),o=(n?n[1]:e).replace(/[^a-zA-Z0-9_-]/g,"_"),i=n?n[2]:r,a=c.join(d,`${o}.${i}`);let s=t;if("string"==typeof s&&s.startsWith("http"))try{const e=await l.get(s,{responseType:"arraybuffer"});await p.writeFile(a,e.data)}catch(e){}else if("string"==typeof s&&s.startsWith("data:image/")){const e=s.split(";base64,");2===e.length&&await p.writeFile(a,e[1],"base64")}else{const e="object"==typeof s?JSON.stringify(s,null,2):s,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await p.writeFile(a,e,t)}});return await Promise.all(u),a.length}async function te({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:i,targetNodeId:a,nodeName:d,code:u,resourcePath:m,shape:l,svg:g,image:f,isSelection:y=!1}){let h=t?c.resolve(e,t):n&&r?c.join(e,B,V,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!i||y||a?"":"code"):y?c.join(e,Y,"selection"):c.join(e,`${Y}${i?"/"+i:""}`);o(h)||s(h,{recursive:!0});const b=String(a||Date.now()).replace(/:/g,"-"),x=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),w=y||a||d?`${x}-${b}.html`:`${i||"index"}.html`,$=c.join(h,w);u&&await p.writeFile($,u,"utf8");const I=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!Q(e))try{const n="string"==typeof e?JSON.parse(e):e;n.image&&(t.image=n.image.replace(/^\.\//,"")),n.svg&&(t.svg=n.svg.replace(/^\.\//,"")),n.shape&&(t.shape=n.shape.replace(/^\.\//,""))}catch(e){}return t}(m),S=await Promise.all([ee(l,h,I.shape,"json"),ee(g,h,I.svg,"svg"),ee(f,h,I.image,"png")]);return{targetDir:h,htmlFileName:w,htmlPath:$,shapeCount:S[0],svgCount:S[1],imageCount:S[2],resourcePathMap:I}}const ne={name:"agent_create_component",description:w.createComponent.description,inputSchema:{code:n.string().describe(w.createComponent.code)},handler:async e=>{const{code:t}=e,n=q("component-generate");if(n.loadedNow)return{content:[{type:"text",text:`🧭 已自动加载规则(component-generate):\n${n.text}\n\n请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。`}],isError:!0};const{error:r}=await X("POST","/api/createComponent",{code:t});return r?{content:[{type:"text",text:`❌ 创建失败: ${r.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向插件发送组件创建指令"}]}}};function re(e){const t=function(e){const t=String(e||"").trim(),n=t.match(/^```[a-zA-Z0-9_-]*\n([\s\S]*?)\n```$/);return n?.[1]?.trim()||t}(e);return function(e){const t=String(e||"");return/&(lt|gt);/i.test(t)||/�*3c;|�*3e;/i.test(t)||/�*60;|�*62;/i.test(t)}(t)?function(e){const t={"<":"<",">":">",""":'"',"'":"'","'":"'","&":"&"," ":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|'/gi,e=>t[e.toLowerCase()]||e);return n=n.replace(/&#(\d+);/g,(e,t)=>{const n=Number.parseInt(t,10);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n=n.replace(/&#x([0-9a-fA-F]+);/g,(e,t)=>{const n=Number.parseInt(t,16);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n}(t).trim():t}function oe(e){const t=String(e||"").trim();if(!t)return t;const n=function(e){const t=String(e||"").match(/<main\b[\s\S]*?<\/main>/i);return(t?.[0]||"").trim()}(t);if(n)return n;const r=function(e){const t=[...String(e||"").matchAll(/```(?:html)?\s*([\s\S]*?)```/gi)];for(const e of t){const t=String(e[1]||"").trim();if(/<(main|div|section|article|aside|header|footer)\b/i.test(t))return t}return""}(t);if(r)return r;const o=t.search(/<(main|div|section|article|aside|header|footer)\b/i);return o>0?t.slice(o).trim():t}const ie={name:"agent_create_page",description:w.createPage.description,inputSchema:{code:n.string().optional().describe(w.createPage.code),filePath:n.string().optional().describe(w.createPage.filePath),projectDir:n.string().describe(w.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe(w.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=c.resolve(e.filePath);r=await p.readFile(t,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取文件失败: ${e.message}`}],isError:!0}}if(!r)return{content:[{type:"text",text:"参数错误: 请提供 code 或 filePath"}],isError:!0};const o=oe(re(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:i,error:a}=await X("POST","/api/createPage",{code:o});if(a)return{content:[{type:"text",text:`❌ 发送失败: ${a.message}`}],isError:!0};let s=function(e,t="操作"){let n=`✅ ${t}已成功完成`;return e.targetNodeId&&(n+=`\n- 节点 ID: ${e.targetNodeId}`),e.documentId&&(n+=`\n- 文档: ${e.documentName||""} (${e.documentId})`),e.documentPageId&&(n+=`\n- 页面: ${e.documentPageName||""} (${e.documentPageId})`),n}(i,"设计稿生成");if(n)try{const e=t?c.resolve(t):process.cwd();s+=`\n- 代码已本地持久化至: ${(await te({baseDir:e,code:i.htmlCode||o,documentId:i.documentId,documentPageId:i.documentPageId,targetNodeId:i.targetNodeId,nodeName:i.nodeName,resourcePath:i.resourcePath,shape:i.shape,svg:i.svg,image:i.image})).htmlPath}`}catch(e){s+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:s}]}}};let ae=null;function se(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const ce="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 agent_create_page(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function de(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}const ue={name:"design",description:w.design.description,inputSchema:{requirement:n.string().describe(w.design.requirement),code:n.string().optional().describe(w.design.code),projectDir:n.string().optional().describe(w.design.projectDir),useComponentLibrary:n.boolean().optional().describe(w.design.useComponentLibrary),userConfirmedUseComponentLibrary:n.boolean().optional().default(!1).describe(w.design.userConfirmedUseComponentLibrary),teamLibraryName:n.string().optional().describe(w.design.teamLibraryName),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe(w.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,useComponentLibrary:o,userConfirmedUseComponentLibrary:i=!1,teamLibraryName:a,buildStrategy:s}=e;if("string"==typeof n&&n.trim()){const e=r?c.resolve(r):process.cwd();return ie.handler({code:se(n),projectDir:e})}if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};if(!P()||!L())return{content:[{type:"text",text:'⏸️ 执行 design 前,请先显式调用规则加载工具。\n\n请先调用:get_codify_guidelines({ scope: "all" })\n加载完成后,再次调用 design。'}],isError:!0};const d=t.trim();if(ae&&ae.requirement===d||(ae={requirement:d,confirmed:!1}),!(ae.confirmed||"boolean"==typeof o&&i))return{content:[{type:"text",text:"⏸️ 继续设计前,请先让用户确认:是否使用组件库来生成页面?\n\n请二选一后再次调用 design:\n1) 使用组件库:useComponentLibrary: true\n2) 不使用组件库:useComponentLibrary: false\n\n并且必须同时传:userConfirmedUseComponentLibrary: true"}],isError:!0};if(ae.confirmed=!0,!o){$=!1,I=!1,S="",j="",v="";const{loadedNow:e,text:n}=q("page-generate"),r=e?`🧭 自动加载规则(page-generate):\n${n}\n\n`:"🧭 规则状态:page-generate 已加载,本次不重复展开。\n\n";return ae=null,{content:[{type:"text",text:`${r}📋 收到需求:${t}\n\n请直接按 page-generate 规则生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}`}],isError:!1}}if(!a?.trim())return{content:[{type:"text",text:'⏸️ 你已选择“使用组件库生成页面”,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 先检查本地 .codify/library 是否已有可用团队库快照\n2) 若本地没有,再调用 get_library_list 获取团队库列表\n3) 询问用户选择具体团队库(禁止助手自动选择)\n\n用户确认后再次调用 design,并传入:\n- useComponentLibrary: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!0};T(`使用 ${a} 团队库`);const u=O("design",r?c.resolve(r):void 0);if(u)return u;if(!s)return{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design:\n1) full-components(全组件设计模式:包括组件、样式变量、图标系统;样式优先使用 var(...))\n2) hybrid(混合设计模式:仅必要组件 + 全量使用组件库样式变量与图标系统;样式优先使用 var(...))`}],isError:!0};const{loadedNow:m,text:l}=q("all"),p=m?`🧭 自动加载规则(page-generate + component-import):\n${l}\n\n`:"🧭 规则状态:page-generate + component-import 已加载,本次不重复展开。\n\n",g="full-components"===s?"\n【full-components 执行约束】\n- 页面功能区必须优先使用 <ui-component>,禁止把可组件化区块改为普通 HTML 自绘\n- <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 样式必须优先使用 variable.json 的 var(...) token(颜色/字体/间距/圆角/边框/阴影);var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因\n- 若某区块未组件化,必须是“本地组件快照中不存在可替代组件”,并在输出前自检逐项说明缺失原因":"\n【hybrid 执行约束】\n- 视觉优先,关键功能区使用组件\n- 已使用的 <ui-component> 布局按需声明:填充主区可用 flex-1/self-stretch,需要固定宽度时可用 w-[...]\n- 非关键区可自由绘制,但必须全量使用组件库样式变量与图标系统\n- 样式必须优先使用 variable.json 的 var(...) token;var(...) 内必须使用变量名(name/ukey),禁止直接写 value;仅 token 缺失时才可局部回退字面值并说明原因";return{content:[{type:"text",text:`${p}📋 收到需求:${t}\n- 团队库:${a}\n- 构建策略:${de(s)}\n${g}\n\n请严格基于落盘组件库 JSON(index/components/icons/variable)生成页面代码,并继续调用 agent_create_page 发送到画布。${ce}\n\n${F()}\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}],isError:!1}}},me={name:"get_code",description:w.getCode.description,inputSchema:{contentId:n.string().describe(w.getCode.contentId),documentId:n.string().optional().describe(w.getCode.documentId),documentPageId:n.string().optional().describe(w.getCode.documentPageId),projectDir:n.string().describe(w.getCode.projectDir),outDir:n.string().describe(w.getCode.outDir)},handler:async e=>{const{contentId:t,outDir:n,projectDir:r,documentId:o,documentPageId:i}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 contentId"}],isError:!0};const{data:a,error:s}=await X("GET",`/api/getCode/${t}`);if(s)return{content:[{type:"text",text:`❌ 获取失败: ${s.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?c.resolve(r):process.cwd(),u=await te({baseDir:d,outDir:n,documentId:o,documentPageId:i,contentId:t,code:a.code,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image});let m=`✅ 代码拉取完成\n- 目录: ${u.targetDir}\n- 文件: ${u.htmlFileName}\n`;return u.shapeCount>0&&(m+=`- Shape: ${u.shapeCount} 个\n`),u.svgCount>0&&(m+=`- SVG: ${u.svgCount} 个\n`),u.imageCount>0&&(m+=`- Image: ${u.imageCount} 个\n`),{content:[{type:"text",text:m}]}}},le={name:"get_code_list",description:w.getCodeList.description,inputSchema:n.object({}).describe(w.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getCodeList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};if(!Array.isArray(e)||0===e.length)return{content:[{type:"text",text:"📋 代码列表为空"}]};const n=e.length,r=n>=10?e.slice(0,10):e;let o=`✅ 成功获取代码列表 (共 ${n} 项)\n`;return n>=10&&(o+="⚠️ 仅展示最近更新的前 10 条记录\n"),o+=`\n${JSON.stringify(r,null,2)}`,{content:[{type:"text",text:o}]}}},pe={name:"get_selection_code",description:w.getSelectionCode.description,inputSchema:{projectDir:n.string().describe(w.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe(w.getSelectionCode.targetNodeId),syncToBase:n.boolean().default(!0).describe(w.getSelectionCode.syncToBase)},handler:async e=>{const{projectDir:t,targetNodeId:n,syncToBase:r=!0,_depth:i=0}=e;if(i>2)return{content:[{type:"text",text:"❌ 递归获取根节点深度过深,已停止。"}],isError:!0};const a=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}`:"/api/getSelectionCode",{data:s,error:d}=await X("GET",a);if(d)return 400===d.status&&"NoSelection"===d.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${d.message}`}],isError:!0};const u=t?c.resolve(t):process.cwd(),m=s.nodeInfo||{},{rootId:l,documentId:f,documentPageId:y,targetNodeId:h,nodeName:b,parentId:x}=m;if(h===l){return{content:[{type:"text",text:`✅ 成功获取并保存根节点代码\n- 节点: ${b} (${h})\n- 根节点: ${l}\n- 文件: ${(await te({baseDir:u,documentId:f,documentPageId:y,targetNodeId:l,nodeName:b,code:s.code,resourcePath:s.resourcePath,shape:s.shape,svg:s.svg,image:s.image})).htmlPath}。`}]}}{let e="",n="";if(r&&f&&y){const r=c.join(u,B,V,String(f).replace(/:/g,"-"),String(y).replace(/:/g,"-")),a=c.join(u,B,String(f).replace(/:/g,"-"),String(y).replace(/:/g,"-")),d=String(l).replace(/:/g,"-");let m="",b=r;const w=async()=>{const e=async e=>{if(!o(e))return"";const t=(await p.readdir(e)).filter(e=>e.endsWith(`-${d}.html`));if(0===t.length)return"";if(1===t.length)return t[0];const n=await Promise.all(t.map(async t=>({fileName:t,mtimeMs:(await p.stat(c.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return b=r,t;const n=await e(a);return n?(b=a,n):""};if(m=await w(),m||0!==i||(await pe.handler({projectDir:t,targetNodeId:l,syncToBase:!0,_depth:i+1}),m=await w()),m){const t=c.join(b,m);try{const n=await p.readFile(t,"utf8"),r=g(n),o=r.querySelector(`[data-node-id="${h}"]`);if(o)o.replaceWith(s.code);else if(x){const e=r.querySelector(`[data-node-id="${x}"]`);e&&e.insertAdjacentHTML("beforeend",s.code)}(o||x&&r.querySelector(`[data-node-id="${x}"]`))&&(await p.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const a=await te({baseDir:u,documentId:f,documentPageId:y,targetNodeId:h,nodeName:b,code:"",resourcePath:s.resourcePath,shape:s.shape,svg:s.svg,image:s.image});let d=`✅ 成功获取子节点代码\n- 节点: ${b} (${h})\n- 根节点: ${l}`;return d+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。可以直接针对返回代码进行修改。",n&&(d+=`\n- ${n}`),h!==l&&(d+=`\n- 资源同步: 图片(${a.imageCount}), 图标(${a.svgCount}), 图形(${a.shapeCount})`),{content:[{type:"text",text:`${d}\n\n代码内容:\n\n${s.code}`}]}}}},ge={name:"get_user_info",description:w.getUserInfo.description,inputSchema:n.object({}).describe(w.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getUserInfo");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};let n=`✅ 用户信息\n\n👤 用户: ${e.realname||e.userName||e.userId}\n`;if(e.quota){const t=Number(e?.quota?.plan??e?.plan??0),r=t>0?"不限量":String(e.quota.mcp_generate_limit||"无"),o=t>0?"不限量":String(e.quota.mcp_get_limit||"无");n+="\n📊 配额:\n",n+=` - 生成设计: ${e.quota.mcp_generate_count||0} / ${r}\n`,n+=` - 获取代码: ${e.quota.mcp_get_count||0} / ${o}\n`}return e.teams?.length&&(n+="\n👥 团队:\n",e.teams.forEach((e,t)=>n+=` ${t+1}. ${e.name} (ID: ${e.teamId})\n`)),{content:[{type:"text",text:n}]}}},fe={name:"agent_update_node",description:w.updateNode.description,inputSchema:{documentId:n.string().optional().describe(w.updateNode.documentId),documentPageId:n.string().optional().describe(w.updateNode.documentPageId),targetNodeId:n.string().optional().describe(w.updateNode.targetNodeId),code:n.string().describe(w.updateNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止灾难性地覆盖当前选中的未知图层,本次操作已被拦截。请明确指定 targetNodeId 或在 HTML 包含 data-node-id。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});return a?{content:[{type:"text",text:`❌ 局部更新失败: ${a.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [局部修改] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}},ye={name:"agent_replace_node",description:w.replaceNode.description,inputSchema:{documentId:n.string().optional().describe(w.replaceNode.documentId),documentPageId:n.string().optional().describe(w.replaceNode.documentPageId),targetNodeId:n.string().optional().describe(w.replaceNode.targetNodeId),code:n.string().describe(w.replaceNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=g(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止把您当前极可能选中的大容器直接覆盖损毁,系统已拦截。请务必明确目标节点的 ID。"}],isError:!0};const{data:i,error:a}=await X("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});return a?{content:[{type:"text",text:`❌ 结构替换失败: ${a.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [结构替换] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 .codify 基准文件。`}]}}};function he(e){return String(e).replace(/:/g,"-")}async function be(e,t){if(!o(e))return"";const n=(await p.readdir(e,{withFileTypes:!0})).filter(e=>e.isFile()&&e.name.endsWith(`-${t}.html`));if(0===n.length)return"";const r=await Promise.all(n.map(async t=>{const n=c.join(e,t.name);return{fullPath:n,mtimeMs:(await p.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function xe(e,t){if(!o(e))return"";const n=await p.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=c.join(e,o.name);if(o.isDirectory()){const e=await xe(n,t);if(e){const t=await p.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await p.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const we={name:"get_design_diff",description:w.getDesignDiff.description,inputSchema:{projectDir:n.string().describe(w.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe(w.getDesignDiff.targetNodeId),filePath:n.string().optional().describe(w.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?c.resolve(t):process.cwd();let i=n,a="",s=r||"";if(s)try{const e=c.isAbsolute(s)?s:c.resolve(o,s);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 无法读取指定的 filePath: ${e.message}`}],isError:!0}}let d="";if(!a||!i){const e=i?`/api/getSelectionCode?id=${encodeURIComponent(i)}`:"/api/getSelectionCode",{data:t,error:n}=await X("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:u,documentId:m,documentPageId:l}=t.nodeInfo;if(d=r,i||(i=u),!a&&r){const e=String(r).replace(/:/g,"-"),t=m&&l?c.join(o,B,V,he(m),he(l)):"",n=m&&l?c.join(o,B,he(m),he(l)):"";if(t&&(s=await be(t,e)),!s&&n&&(s=await be(n,e)),!s){const t=c.join(o,B,V);s=await xe(t,e)}if(!s){const t=c.join(o,B);s=await xe(t,e)}s&&(a=await p.readFile(s,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 .codify 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:u,error:m}=await X("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(m)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${m.message}`}],isError:!0};let l=u.diffs||[];return l&&!Array.isArray(l)&&Array.isArray(l.diffs)&&(l=l.diffs),0===l.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${l.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(l,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 .codify 的基准文件。`}]}}},$e={name:"agent_remove_node",description:w.removeNode.description,inputSchema:{documentId:n.string().optional().describe(w.removeNode.documentId),documentPageId:n.string().optional().describe(w.removeNode.documentPageId),targetNodeId:n.string().optional().describe(w.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await X("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});return i?{content:[{type:"text",text:`❌ 删除失败: ${i.message}`}],isError:!0}:{content:[{type:"text",text:`✅ 已成功发送删除指令\n- 目标节点 ID: ${o.targetNodeId||t||"当前选中图层"}`}]}}},Ie={name:"agent_sync_design",description:w.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe(w.syncToDesign.documentId),documentPageId:n.string().optional().describe(w.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe(w.syncToDesign.targetNodeId),filePath:n.string().describe(w.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe(w.syncToDesign.userConfirmed)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r,filePath:o,userConfirmed:i=!1}=e;if(!i)return{content:[{type:"text",text:"⏸️ 已阻止自动同步到画布。\n\n仅当用户明确提出“请同步到画布”时,才允许调用 agent_sync_design。\n请先向用户确认,再以 userConfirmed=true 重新调用。"}],isError:!0};let a="";if(o)try{const e=c.resolve(o);a=await p.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取本地文件失败: ${e.message}`}],isError:!0}}if(!a)return{content:[{type:"text",text:"❌ 同步失败: 未能从指定路径读取到有效代码"}],isError:!0};const{data:s,error:d}=await X("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});return d?{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0}:{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${s.targetNodeId?`\n- 根节点 ID: ${s.targetNodeId}`:""}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 .codify 基准。`}]}}};function Se(e){return e.trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)}function je(e){return String(e??"").trim().toLowerCase()}function ve(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ne(e){if(!ve(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.ukey&&e.ukey.trim()?e.ukey:void 0,r="string"==typeof e.description?e.description:"",o=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||ve(e)?e:String(e)}(e.value);return void 0===o?{ukey:n,name:t,description:r}:{ukey:n,name:t,description:r,value:o}}function _e(e){return function(e){return Array.isArray(e)?e:[]}(e).map(Ne).filter(e=>!!e)}function Ce(e){const t=_e(e.paints);return{boxShadow:_e(e.effects),color:t,fill:t,borderRadius:_e(e.cornerRadiuses),padding:_e(e.paddings),gap:_e(e.spacings),borderWidth:_e(e.strokeWidths),typography:_e(e.texts)}}function Pe(e){return null===e||["string","number","boolean"].includes(typeof e)}function Le(e){const t=Number(e);return Number.isFinite(t)?t:0}function De(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[Le(t[0]),Le(t[1])];const n=ve(e.style)?e.style:{};return[Le(e.width??n.width),Le(e.height??n.height)]}function Ae(e){if(Array.isArray(e)){const t=e.filter(Pe);return t.length>0?t:void 0}if(ve(e)){const t=Array.isArray(e.enum)?e.enum.filter(Pe):void 0,n=Pe(e.default)?e.default:void 0;if(t&&t.length>0){return 2===t.length&&t.includes(!1)&&t.includes(!0)&&"boolean"==typeof n?n:t}return void 0!==n?n:void 0}if(Pe(e))return e}function Ee(e){if(!ve(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=Ae(r);void 0!==e&&(t[n]=e)}return t}function Te(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function ke(e){if(!ve(e))return{};const t={},n=e=>{const t=String(e||"").trim();return!!t&&(t.includes("+")||/^[a-zA-Z0-9_-]+:\d+$/.test(t))};for(const[r,o]of Object.entries(e)){if("string"!=typeof o)continue;const e=r.trim(),i=o.trim();e&&i&&(n(i)||(t[e]=i))}return t}function Me(e){return"string"==typeof e?e:""}function Oe(e){return"string"==typeof e&&e.trim()?e:void 0}function Fe(e){const t=ve(e)?e:{},n=Array.isArray(t.components)?t.components:Array.isArray(t["component-set"])?t["component-set"]:[],r=[],o=function(e){if(!Array.isArray(e))return[];const t=[],n=new Set;for(const r of e){if("string"==typeof r&&r.trim()){const e=r.trim(),o=`::${e}`;n.has(o)||(n.add(o),t.push({name:e}));continue}if(ve(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o="string"==typeof r.id&&r.id.trim()?r.id.trim():void 0,i=`${o||""}::${e}`;n.has(i)||(n.add(i),t.push({id:o,name:e}))}}return t}(t.icons);for(const e of n){if(!ve(e))continue;const t="string"==typeof e.name?e.name:"",n=Me(e.description),o=Oe(e.id),i=Oe(e.ukey),a=ve(e.props)?e.props:ve(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:Oe(e.cover),size:De(e),props:Ee(a),slots:Te(s),instance_swap:ke(c)})}return{variables:ve(t.variables)?t.variables:ve(t.style)?Ce(t.style):ve(t.styles)?Ce(t.styles):ve(t.tokens)?t.tokens:{},components:r,icons:o}}function Je(e,t){const n=Se(e||"component").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function Ge(e){try{const t=await p.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function He(e,t){await p.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(Pe(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(Pe)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(ve(e)){const o=Object.entries(e).filter(([,e])=>void 0!==e);return 0===o.length?"{}":`{\n${o.map(([e,o])=>`${n(t+1)}${JSON.stringify(e)}: ${r(o,t+1)}`).join(",\n")}\n${n(t)}}`}return JSON.stringify(String(e))};return r(e,0)}(t)}\n`,"utf8")}async function qe(e){if(!o(e))return[];const t=[],n=c.join(e,"catalog.json"),r=await Ge(n);if(Array.isArray(r?.libraries))for(const n of r.libraries){const r=String(n?.id||"").trim(),i=String(n?.name||"").trim(),a=String(n?.slug||"").trim(),s=String(n?.indexPath||"").trim();if(!r||!s)continue;const d=c.join(e,s);o(d)&&t.push({id:r,name:i||a||r,slug:a||Se(i||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await p.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=c.join(e,r),a=await Ge(c.join(i,"meta.json"));if(a?.paths?.index){const e=c.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.name||"").trim(),o=String(a?.teamLibrary?.slug||"").trim();t.push({id:r,name:n||o||r,slug:o||Se(n||r),indexAbsPath:e});continue}}const s=await p.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of s){if(!e.isDirectory())continue;const n=c.join(i,e.name,"index.json");if(!o(n))continue;const a=e.name;t.push({id:r,name:a,slug:a,indexAbsPath:n})}}const a=function(e){const t=new Map;for(const n of e)n.indexAbsPath&&t.set(n.indexAbsPath,n);return Array.from(t.values())}(t);for(const e of a){const t=await Ge(e.indexAbsPath);if(!t)continue;const n=String(t?.teamLibrary?.id||"").trim(),r=String(t?.teamLibrary?.name||"").trim();n&&(e.id=n),r&&(e.name=r,e.slug=Se(r)),"number"==typeof t?.summary?.componentCount&&(e.componentCount=t.summary.componentCount),"number"==typeof t?.summary?.iconCount&&(e.iconCount=t.summary.iconCount)}return a}async function Re(){const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function ze(e){return null==e||"string"==typeof e&&""===e.trim()}const Ue=[R,ue,me,le,ge,{name:"get_library_list",description:w.getLibraryList.description,inputSchema:n.object({}).describe(w.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await X("GET","/api/getTeamLibraryList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};const n=e?.data??e;if(!Array.isArray(n)||0===n.length)return{content:[{type:"text",text:"📚 团队库列表为空(请确认插件已连接且当前文件已订阅团队库)"}]};const r=n.map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`✅ 当前文件团队库列表(共 ${r.length} 个)\n\n${JSON.stringify(r,null,2)}\n\n若用户要求“使用某组件库生成页面”,请按最短流程:\n1) 先检查 .codify/library 是否已有该团队库 JSON(优先本地)\n2) 若本地没有,再确定团队库(可回复:使用 <团队库名称> 团队库)\n3) 调用 get_component_info 拉取并落盘 index + 组件详情 JSON\n`}]}}},{name:"get_component_info",description:w.getComponentInfo.description,inputSchema:{projectDir:n.string().describe(w.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe(w.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe(w.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe(w.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:i=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const a=c.resolve(t),d=c.join(a,B,"library"),u=c.join(d,"catalog.json"),m=await qe(d);let l=n,p=r;if(!l&&!p){if(m.length>0){const e=m.map(e=>({id:e.id,name:e.name,componentCount:e.componentCount??void 0,iconCount:e.iconCount??void 0,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`✅ 检测到本地 .codify/library 已有落盘团队库(共 ${e.length} 个),优先复用本地快照。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await Re()).map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`⚠️ 你还没有指定团队库。\n\n当前文件团队库列表(共 ${e.length} 个):\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info 并传入 teamLibraryName。\n\n组件库生成页面的标准流程:先本地复用,未命中才远端拉取并落盘。`}]}}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}}const g=function(e,t,n){if(!e.length)return{status:"none"};let r=[...e];if(!ze(t)){const e=je(String(t)),n=je(Se(String(t))),o=r.filter(t=>{const r=je(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!ze(n)){const e=je(String(n)),t=je(Se(String(n))),o=r.filter(n=>{const r=je(n.name),o=je(n.slug);return r===e||o===e||r===t||o===t});if(o.length>0)r=o;else{const t=r.filter(t=>{const n=je(t.name),r=je(t.slug);return n.includes(e)||e.includes(n)||r.includes(e)||e.includes(r)});if(!t.length)return{status:"none"};r=t}}return 1===r.length?{status:"matched",entry:r[0]}:{status:"ambiguous",candidates:r}}(m,l,p);if("matched"===g.status){const e=g.entry,t=await Ge(e.indexAbsPath),n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||l||"",i=r||e.id||l||"",s=c.dirname(e.indexAbsPath),d=c.join(s,"icons.json"),u=c.join(s,"variable.json");return k({teamLibraryName:o||i,filePath:e.indexAbsPath,baseDir:a}),{content:[{type:"text",text:`♻️ 已命中本地团队库快照,跳过远端拉取。\n- 团队库: ${o} (${i})\n- 索引文件: ${e.indexAbsPath}\n- 图标文件: ${d}\n- 变量文件: ${u}\n- 组件目录: ${c.join(s,"components")}\n\n⚠️ 下一步生成页面前,必须先读取 index.json,再按 components/\${key}.json 读取组件详情,并读取 icons.json + variable.json。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}]}}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 .codify/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!l&&p)try{const e=await Re(),t=je(p),n=e.filter(e=>je(e?.name)===t),r=e.filter(e=>!ze(e?.name)&&je(e?.name).includes(t)),o=n.length>0?n:r;if(0===o.length)return{content:[{type:"text",text:`❌ 未找到匹配的团队库:${p}\n\n请先调用 get_library_list 查看可用团队库,或直接传 teamLibraryId。`}],isError:!0};if(o.length>1){const e=o.map(e=>({id:e?.id,name:e?.name}));return{content:[{type:"text",text:`⚠️ 团队库名称匹配到多个候选,请更精确地指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n你可以改用 teamLibraryId 直接调用 get_component_info。`}]}}l=o[0]?.id,p=o[0]?.name}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}if(!l)return{content:[{type:"text",text:"参数错误: 未能确定 teamLibraryId"}],isError:!0};if(!p)try{const e=(await Re()).find(e=>String(e?.id||"")===String(l));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(l)}&includePropertyDetails=${i?"true":"false"}`,{data:y,error:h}=await X("GET",f);if(h)return{content:[{type:"text",text:`❌ 获取失败: ${h.message}`}],isError:!0};const b=Fe(y?.data??y),x=Se(l),w=Se(p||l),$=(new Date).toISOString(),I=c.join(d,x),S=c.join(I,w),j=c.join(S,"components");o(I)||s(I,{recursive:!0}),o(S)||s(S,{recursive:!0}),o(j)||s(j,{recursive:!0});const v=[],N=[],_=new Set;b.components.forEach((e,t)=>{let n=Je(e.name,t),r=1;for(;_.has(n);)r+=1,n=`${Je(e.name,t)}-${r}`;_.add(n),v.push({key:n,id:e.id,ukey:e.ukey,name:e.name,description:e.description||"",cover:e.cover}),N.push(He(c.join(S,`components/${n}.json`),{id:e.id,ukey:e.ukey,name:e.name,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const C="icons.json",P="variable.json";await Promise.all([...N,He(c.join(S,C),{icons:b.icons}),He(c.join(S,P),{variables:b.variables})]);const L={schemaVersion:2,generatedAt:$,teamLibrary:{id:String(l),name:String(p||l)},summary:{componentCount:b.components.length,iconCount:b.icons.length,componentFileCount:v.length},components:v,files:{componentsDir:"components",pathTemplate:"components/{key}.json",icons:C,variables:P}},D=c.join(S,"index.json");await He(D,L);const A={schemaVersion:1,updatedAt:$,teamLibrary:{id:String(l),name:String(p||l),slug:w},paths:{index:c.relative(I,D),icons:c.relative(I,c.join(S,C)),variables:c.relative(I,c.join(S,P)),componentsDir:c.relative(I,j)}};return await He(c.join(I,"meta.json"),A),await async function(e,t){const n=await Ge(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id);r.push(t),r.sort((e,t)=>e.name.localeCompare(t.name,"zh-Hans-CN"));const o={schemaVersion:1,updatedAt:t.updatedAt,libraries:r};await He(e,o)}(u,{id:x,name:String(p||l),slug:w,updatedAt:$,componentCount:b.components.length,iconCount:b.icons.length,indexPath:c.relative(d,D)}),k({teamLibraryName:p||l,filePath:D,baseDir:a}),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${l})\n- 目录: ${S}\n- 索引文件: ${D}\n- 组件详情文件: ${v.length} 个\n- 图标文件: ${c.join(S,C)}\n- 变量文件: ${c.join(S,P)}\n- 全局目录索引: ${u}\n\n⚠️ 下一步生成页面前,必须先读取索引文件,再按固定路径 components/\${key}.json 读取所需组件详情,并读取 icons.json + variable.json:\n结构速记:components[].{id,name,description,size,props,slots,slot_layout,instance_swap} + icons[].{id,name} + variables{}。\n先确认构建策略:full-components 或 hybrid(推荐)。\n变量引用统一使用 var(...),例如 gap-[var(间距_index_10/sm)],变量名称应来自 variable.json 的 variables。\n\n组件库生成规则(统一):\n1) 仅在“明确要求使用团队库/组件库生成页面”时,才走组件库流程。\n2) 数据来源仅限落盘 JSON:index.json -> components/{key}.json -> icons.json -> variable.json。\n3) <ui-component>.name/props/instance_swap 必须来自组件详情;instance_swap 值仅允许传组件名(name)(禁止传 ukey、组件 id、画布节点 data-node-id)。\n4) instance_swap 是通用子实例替换通道,不仅限图标;当通过 instance_swap 替换子实例时,禁止在同一替换位再写子节点伪造替换;若存在对应 props 开关,必须显式设为 true。\n5) <ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]。\n6) <ui-icon>.name 必须来自 icons[].name。\n7) full-components 模式:可组件化区块必须组件化;仅在库中无对应组件时才允许局部 HTML 回退,并需标注缺失原因。\n8) hybrid 模式必须“视觉优先 + 关键功能区组件化”,且必须使用组件库样式/变量与图标系统。\n9) 样式变量必须优先来自 variable.json,并以 var(...) 表达;var(...) 必须使用变量名(name/ukey),禁止直接使用 value;仅当变量缺失时才允许局部字面值回退且需说明。\n10) slots 是白名单;slot 名称必须与 slots[] 完全一致;slot_layout 可用于 row/column 下的 flex-1/self-stretch 决策。\n11) 关键字段缺失时回退普通 HTML,禁止臆造组件引用。\n12) 组件库图标修改走 agent_update_node;非组件库资源(<i>/<img>)修改默认走 agent_replace_node,其中图标遵循 FontAwesome,图片换图遵循 <img src="{{keyword}}" /> 语义规范。`}]}}},ie,ne,pe,fe,ye,Ie,we,$e];const We=new e({name:"Codify-MCP-Client",version:"1.0.23"},{capabilities:{resources:{},tools:{},prompts:{}}});var Be;We.registerResource("rule","codify://rule",{description:"Codify 聚合规范(page-generate + component-import + 最终硬闸门)"},async e=>({contents:[{uri:e.toString(),text:G("all"),mimeType:"text/markdown"}]})),Be=We,Ue.forEach(e=>{Be.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await We.connect(e)}catch(e){process.exit(1)}
|
|
2
|
+
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as n}from"zod";import{readFileSync as r,existsSync as o,mkdirSync as i,statSync as a}from"fs";import s,{dirname as c,resolve as d}from"path";import{fileURLToPath as l}from"url";import u from"axios";import m,{mkdir as p,writeFile as g,readdir as f,stat as y,readFile as b}from"fs/promises";import{parse as h}from"node-html-parser";const v={serverName:"Codify-MCP-Client",displayName:"Codify",pluginName:"Codify 插件",defaultServerUrl:"https://mcp.codify-api.com",serverUrlEnv:"CODIFY_SERVER_URL",accessKeyEnv:"CODIFY_ACCESS_KEY",docDir:".codify",uriScheme:"codify"};function $(e){return e.replace(/\{\{displayName\}\}/g,v.displayName).replace(/\{\{pluginName\}\}/g,v.pluginName).replace(/\{\{docDir\}\}/g,v.docDir).replace(/\{\{uriScheme\}\}/g,v.uriScheme)}const I=c(l(import.meta.url));function x(e){return $(r(d(I,e),"utf-8"))}const w=x("page-generate.md"),S=x("component-import.md"),j=x("component-generate.md"),_=x("variable-import.md"),N=x("variable-generate.md"),P=c(l(import.meta.url));function D(e){return"string"==typeof e?$(e):Array.isArray(e)?e.map(D):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,t])=>[e,D(t)])):e}const C=function(e){const t=d(P,`${e}.template.json`),n=r(t,"utf8");return D(JSON.parse(n))}("en"===process.env.LANG?"en":"zh"),A=process.argv.find(e=>e.startsWith("--url=")),L=A?A.slice(6):process.env[v.serverUrlEnv]||v.defaultServerUrl,E=process.env.ACCESS_KEY||process.env[v.accessKeyEnv],T=v.docDir,M="design",O=s.join(T,"created"),V=`请确认已启动 MasterGo,并且 ${v.pluginName} 处于连接状态。`,F=["/api/getTeamLibraryList","/api/getComponentInfo","/api/getVariables","/api/updateVariables","/api/removeVariable","/api/createPage","/api/getSelectionCode","/api/getDesignDiff","/api/syncToDesign","/api/updateNode","/api/replaceNode","/api/removeNode","/api/getCode","/api/getCodeList"];const k=e=>null==e||""===e||"string"==typeof e&&""===e.trim();function J(e){return String(e??"unknown-document").trim().replace(/[\\/:"*?<>|]+/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^\.+/,"").slice(0,120)||"unknown-document"}function G(e){return e?.trim()?s.resolve(e.trim()):""}async function q(e,t,n=null){const r={};E&&(r.Authorization=`Bearer ${E}`);const o={method:e,url:`${L}${t}`,headers:r,...n&&{data:n}};try{return{data:(await u(o)).data,error:null}}catch(e){const n=e.response?.status,r=e.response?.data||{};let o=r.message||e.message;return 401===n&&(o="认证失败,请检查 ACCESS_KEY"),403===n&&(o=void 0!==r.mcp_get_limit||void 0!==r.mcp_generate_limit||o.includes("limit")?`配额不足: ${o}`:`权限不足: ${o}`),404===n&&(o="未找到 API 终结点或活跃连接"),function(e,t,n){const r=String(n||"").toLowerCase(),o=F.some(t=>e.startsWith(t)),i=404===t||r.includes("active connection")||r.includes("not connected")||r.includes("插件")||r.includes("mg")||r.includes("mastergo");return o||i}(t,n,o)&&(o=function(e){return e?e.includes(v.pluginName)||e.includes("mg")||e.includes("MasterGo")?e:`${e}\n\n${V}`:V}(o)),{data:null,error:{status:n,message:o,data:r}}}}async function z(e,t,n,r){if(k(e))return 0;const a="string"==typeof e?JSON.parse(e):e,c=Object.keys(a);if(0===c.length)return 0;const d=s.join(t,n);o(d)||i(d,{recursive:!0});const l=Object.entries(a).map(async([e,t])=>{const n=e.match(/(.+)\.([a-zA-Z0-9]+)$/),o=(n?n[1]:e).replace(/[^a-zA-Z0-9_-]/g,"_"),i=n?n[2]:r,a=s.join(d,`${o}.${i}`);let c=t;if("string"==typeof c&&c.startsWith("http"))try{const e=await u.get(c,{responseType:"arraybuffer"});await m.writeFile(a,e.data)}catch(e){}else if("string"==typeof c&&c.startsWith("data:image/")){const e=c.split(";base64,");2===e.length&&await m.writeFile(a,e[1],"base64")}else{const e="object"==typeof c?JSON.stringify(c,null,2):c,t="png"===i||"jpg"===i||"jpeg"===i?"base64":"utf8";await m.writeFile(a,e,t)}});return await Promise.all(l),c.length}async function R({baseDir:e,outDir:t,documentId:n,documentPageId:r,contentId:a,targetNodeId:c,nodeName:d,code:l,resourcePath:u,shape:p,svg:g,image:f,isSelection:y=!1}){let b=t?s.resolve(e,t):n&&r?s.join(e,T,M,String(n).replace(/:/g,"-"),String(r).replace(/:/g,"-"),!a||y||c?"":"code"):y?s.join(e,O,"selection"):a?s.join(e,O,a):s.join(e,O);o(b)||i(b,{recursive:!0});const h=String(c||Date.now()).replace(/:/g,"-"),v=String(d||(y?"selection":"node")).replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g,"-"),$=y||c||d?`${v}-${h}.html`:`${a||"index"}.html`,I=s.join(b,$);l&&await m.writeFile(I,l,"utf8");const x=function(e){const t={image:"asset/images",svg:"asset/icons",shape:"asset/shapes"};if(!k(e))try{const n="string"==typeof e?JSON.parse(e):e;n.image&&(t.image=n.image.replace(/^\.\//,"")),n.svg&&(t.svg=n.svg.replace(/^\.\//,"")),n.shape&&(t.shape=n.shape.replace(/^\.\//,""))}catch(e){}return t}(u),w=await Promise.all([z(p,b,x.shape,"json"),z(g,b,x.svg,"svg"),z(f,b,x.image,"png")]);return{targetDir:b,htmlFileName:$,htmlPath:I,shapeCount:w[0],svgCount:w[1],imageCount:w[2],resourcePathMap:x}}let U=!1,B=!1,W="",H="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",se=0,ce="";function de(e){const t=Date.now(),n=G(e);let r=!1;return(se>0&&t-se>18e5||!!n&&!!ce&&n!==ce)&&(U=!1,B=!1,W="",H="",K="",Y="",Z=!1,Q=!1,X=!1,ee=!1,te=!1,ne=!1,re=!1,oe="",ie="",ae="",Ie.clear(),r=!0),se=t,n&&(ce=n),{reset:r}}function le(e){switch(e){case"page-generate":Z=!0;break;case"component-import":Q=!0;break;case"component-generate":X=!0;break;case"variable-import":ee=!0;break;case"variable-generate":te=!0}}function ue(e){if(Array.isArray(e))return e.some(e=>ue(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.reference||Object.values(t).some(e=>ue(e))}function me(e){if(Array.isArray(e))return e.some(e=>me(e));if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.value||Object.values(t).some(e=>me(e))}function pe(e){const t=e.filter(e=>function(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=e;return"deleteVariable"!==String(t.action||"")&&void 0===t.id}(e));if(0===t.length)return null;const n=t.some(e=>ue(e)),r=t.some(e=>me(e));return n&&r?{content:[{type:"text",text:"❌ 本次变量批次同时包含 value 和 reference。\n\n请拆成两批:\n1) 先提交基础变量(只包含 value),调用 update_variables。\n2) 立即调用 get_variables,落盘最新变量和 ID。\n3) 再提交语义变量(通过 reference 引用基础变量)。\n4) 最后再次调用 get_variables 刷新落盘数据。"}],isError:!0}:null}function ge(e){return e.trim().replace(/^["'“”]+|["'“”]+$/g,"")}function fe(e){return e.trim().toLowerCase().replace(/[-_]/g," ").replace(/\s+/g," ")}function ye(e){const t=function(e){const t=e.match(/使用\s*[“"']?(.+?)[”"']?\s*(?:组件库|团队库|component\s*library|team\s*library|library)/i);if(t?.[1])return ge(t[1]);const n=e.match(/(?:use|using)\s+(.+?)\s+(?:component\s*library|team\s*library|library)/i);return n?.[1]?ge(n[1]):""}(e.requirement),n=e.teamLibraryId?.trim()||"",r=n&&H&&n===H,o=!n&&!H&&B&&W.trim()&&t.trim()&&(i=t,fe(W)===fe(i));var i;U=!0,B&&(r||o)||(B=!1,H="",K="",Y="",W=t)}function be(){U=!1,B=!1,W="",H="",K="",Y=""}function he(){if(!U||!B)return"";return`\n\n🧩 组件库模式已就绪。请按 index.md 导航读取快照,再生成页面:${W?`\n- 团队库: ${W}`:""}${K?`\n- 组件库索引说明: ${K}`:""}\n- 读取顺序:index.md -> components/*.json -> icons.json -> variable.json\n- 所有 name 必须原样来自落盘文件:components、icons、instance_swap、variable\n- 变量写 var(name),例如 {"name":"填充色/常规"} -> var(填充色/常规)\n- instance_swap 写在 <ui-component instance_swap='...'> 上;需要开关类 props 时同步设为 true\n- 不臆造组件名、图标名、props;完整细则以 component-import.md 为准`}function ve(e){const t=G(e);if(!t)return!1;const n=s.join(t,T,"variable");try{if(!o(n))return!1;const e=require("fs").readdirSync(n).filter(e=>e.toLowerCase().endsWith(".json"));for(const t of e){const{names:e}=xe(s.join(n,t));if(e.length>0)return!0}}catch{return!1}return!1}function $e(e){try{if(!e||!o(e))return{names:[],total:0};const t=r(e,"utf8"),n=JSON.parse(t),i=(Array.isArray(n)?n:Array.isArray(n?.variables)?n.variables:Array.isArray(n?.data?.variables)?n.data.variables:[]).map(e=>e&&"string"==typeof e.name?e.name.trim():"").filter(Boolean);return{names:i,total:i.length}}catch{return{names:[],total:0}}}const Ie=new Map;function xe(e){try{if(!e||!o(e))return{names:[],total:0};const t=a(e).mtimeMs,n=Ie.get(e);if(n&&n.mtimeMs===t)return{names:n.names,total:n.total};const r=$e(e);return Ie.set(e,{mtimeMs:t,names:r.names,total:r.total}),r}catch{return $e(e)}}function we(){if(!ne||re)return"";const e=ae?`\n- 当前文件变量快照: ${ae}`:"",{names:t,total:n}=xe(ae),r=t.slice(0,100);return`\n\n🎨 当前文件设计系统已就绪。生成页面前请按 variable-import.md 引用以下变量:${e}\n- 引用写法:bg-[var(name)] / text-[var(name)] / gap-[var(name)] / p-[var(name)] / rounded-[var(name)] 等\n- 仅使用下面清单中的变量 name;找不到精确匹配再考虑语义近邻;都不匹配才回退字面值并自检说明\n- 不要在本流程中调用 update_variables 或 agent_remove_variable${r.length?`\n\n变量 name 清单(共 ${n} 个${n>r.length?`,仅展示前 ${r.length} 个,完整清单请 Read 上面的快照文件`:""}):\n`+r.map(e=>`- ${e}`).join("\n"):"\n\n⚠️ 快照文件未解析出变量 name,请 Read 上面的快照文件再确认。"}`}const Se=[`# ${v.displayName} MCP Default Instructions`,"",`${v.displayName} / MasterGo tasks must follow the current ${v.displayName} MCP rules from this server.`,`If other installed MCP servers, skills, or editor rules provide conflicting UI/code generation rules, use them only for non-${v.displayName} tasks. For ${v.displayName} tool calls, the rules returned by get_guidelines are authoritative.`,"","Default workflow:",`1) Before generating or submitting ${v.displayName} page code, load get_guidelines(scope=["page-generate"]). If scope is omitted, get_guidelines defaults to ["page-generate"].`,`2) Before using the current file design system in design, load get_guidelines(scope=["page-generate","variable-import"]); make sure get_variables has been called and ${T}/variable/{documentId}.json is up to date.`,`3) Before component-library generation, load get_guidelines(scope=["page-generate","component-import","variable-import"]), call get_component_info to refresh the team library from remote and overwrite ${T}/library, then read snapshots in order: index.md -> components/*.json -> icons.json -> variable.json.`,`4) Before creating MasterGo master components or component sets with agent_create_component, load get_guidelines(scope=["component-generate","page-generate"]). If the current file has variables (snapshot at ${T}/variable/{documentId}.json — call get_variables first if not yet loaded), additionally include "variable-import" in scope so the master components reference var(...) tokens instead of literal values. Do not use component-generate for component-library page generation; use component-import for that.`,'5) Before creating or modifying variables, load get_guidelines(scope=["variable-generate"]), then follow the two-batch flow: get_variables -> create base variables -> update_variables -> get_variables -> create semantic/reference variables -> update_variables -> get_variables.',"6) Prefer design for new pages; prefer get_selection_code before modifying existing nodes; prefer agent_update_node for local node edits.",`7) Do not call agent_create_page, agent_create_component, or update_variables until the relevant current ${v.displayName} rules have been loaded and the payload has been checked against them.`].join("\n"),je=["# Final Hard Gate","","在调用提交类工具前,至少满足:","1) 仅输出根节点片段;禁止 html/head/body/script/style/link/meta/title","2) 所有节点包含 data-name;禁用 margin/grid/原生表单标签","3) 禁止相对单位(%/vw/vh/rem/em/calc),尺寸使用绝对像素","4) Flex 容器写全:flex + flex-row|flex-col + justify-* + items-*","5) 组件模式下,<ui-component>/<ui-icon> 仅可引用落盘文件中存在的 name/props/slots/icon","6) 组件模式下,<ui-component> 布局样式按排版需要声明:填充主区用 flex-1/self-stretch,需要固定宽度时可用 w-[...]",'7) 若用户诉求包含“图标/icon/替换图标”,非组件库场景必须使用 FontAwesome <i class="fas/fa* fa-...">,禁止 div/svg/path 手绘图标(除非用户明确要求自定义 SVG)',"8) 当上下文加载了 variable-import 时,样式优先使用变量 var(...) token;写变量 name,禁止改写或抄 value;仅变量缺失时才可局部回退字面值并说明原因","9) 组件库模式下,instance_swap 是通用子实例替换通道(不限图标);当组件依赖 instance_swap 时,禁止在同一替换位再用子节点伪造替换;若存在对应 props 开关需显式设为 true","10) 组件库模式下,所有 name 只能来自落盘文件,禁止改写:components/*.json、icons.json、instance_swap、variable.json","11) 变量创建必须分批:基础变量 update_variables 后先 get_variables 落盘,再创建引用基础变量的语义变量,最后再 get_variables"].join("\n"),_e={"page-generate":"# Page Generate Rules (Base, Must Follow)","component-import":"# Component Import Rules","component-generate":"# Component Generate Rules (Master Component / Component Set)","variable-import":"# Variable Import Rules (Read Variables)","variable-generate":"# Variable Generate Rules (Write Variables)"},Ne={"page-generate":w,"component-import":S,"component-generate":j,"variable-import":_,"variable-generate":N};function Pe(e){const t=new Set,n=[];e.includes("page-generate")&&(t.add("page-generate"),n.push("page-generate"));for(const r of e)t.has(r)||(t.add(r),n.push(r));return n}function De(e){switch(e){case"page-generate":return Z;case"component-import":return Q;case"component-generate":return X;case"variable-import":return ee;case"variable-generate":return te}}function Ce(e){const t=Pe(e);if(0===t.length)return w;if(1===t.length&&"component-generate"===t[0])return["# Scope Warning","component-generate is ONLY for agent_create_component: creating MasterGo master components or component sets. It is not for generating pages with an existing team/component library; use component-import for that.","",_e["component-generate"],j,"",_e["page-generate"],w].join("\n");const n=[];for(const e of t)n.push(_e[e]),n.push(Ne[e]),n.push("");return t.length>1&&n.push(je),n.join("\n").trim()}function Ae(e){const t=Pe(Array.isArray(e)?e:[e]);let n=!1;for(const e of t)De(e)||(le(e),n=!0);return{loadedNow:n,text:Ce(t),scopes:t}}function Le(e,t,n={}){de();const{loadedNow:r,text:o,scopes:i}=Ae(e);if(!r)return null;const a=i.join(" + "),s=n.followUp??`请基于以上规则自检并修正后,再次调用 ${t} 提交。`;return{content:[{type:"text",text:`🧭 已自动加载当前 ${v.displayName} MCP 规则(${a}):\n${o}\n\n${s}`}],isError:n.isError??!1}}const Ee={name:"get_guidelines",description:C.getGuidelines.description,inputSchema:n.object({scope:n.array(n.enum(["page-generate","component-import","component-generate","variable-import","variable-generate"])).optional()}).describe(C.getGuidelines.inputSchema),handler:async e=>{const t=e?.scope?.length?e.scope:["page-generate"],{text:n}=Ae(t);return{content:[{type:"text",text:n}]}}},Te={name:"agent_create_component",description:C.createComponent.description,inputSchema:{code:n.string().describe(C.createComponent.code),projectDir:n.string().optional().describe(C.createComponent.projectDir)},handler:async e=>{const{code:t,projectDir:n}=e,r=["component-generate"];n&&ve(s.resolve(n))&&r.push("variable-import");const o=Le(r,"agent_create_component",{followUp:"请基于以上规则检查并修正组件代码后,再次调用 agent_create_component 提交。"});if(o)return o;const{error:i}=await q("POST","/api/createComponent",{code:t});return i?{content:[{type:"text",text:`❌ 创建失败: ${i.message}`}],isError:!0}:{content:[{type:"text",text:"✅ 已成功向 MasterGo 发送组件创建指令"}]}}};function Me(e){const t=function(e){const t=String(e||"").trim(),n=t.match(/^```[a-zA-Z0-9_-]*\n([\s\S]*?)\n```$/);return n?.[1]?.trim()||t}(e);return function(e){const t=String(e||"");return/&(lt|gt);/i.test(t)||/�*3c;|�*3e;/i.test(t)||/�*60;|�*62;/i.test(t)}(t)?function(e){const t={"<":"<",">":">",""":'"',"'":"'","'":"'","&":"&"," ":" "};let n=e.replace(/&(lt|gt|quot|apos|amp|nbsp);|'/gi,e=>t[e.toLowerCase()]||e);return n=n.replace(/&#(\d+);/g,(e,t)=>{const n=Number.parseInt(t,10);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n=n.replace(/&#x([0-9a-fA-F]+);/g,(e,t)=>{const n=Number.parseInt(t,16);return Number.isNaN(n)?e:String.fromCodePoint(n)}),n}(t).trim():t}function Oe(e){const t=String(e||"").trim();if(!t)return t;const n=function(e){const t=String(e||"").match(/<main\b[\s\S]*?<\/main>/i);return(t?.[0]||"").trim()}(t);if(n)return n;const r=function(e){const t=[...String(e||"").matchAll(/```(?:html)?\s*([\s\S]*?)```/gi)];for(const e of t){const t=String(e[1]||"").trim();if(/<(main|div|section|article|aside|header|footer)\b/i.test(t))return t}return""}(t);if(r)return r;const o=t.search(/<(main|div|section|article|aside|header|footer)\b/i);return o>0?t.slice(o).trim():t}const Ve={name:"agent_create_page",description:C.createPage.description,inputSchema:{code:n.string().optional().describe(C.createPage.code),filePath:n.string().optional().describe(C.createPage.filePath),projectDir:n.string().describe(C.createPage.projectDir),saveCodeToLocal:n.boolean().default(!1).describe(C.createPage.saveCodeToLocal)},handler:async e=>{const{projectDir:t,saveCodeToLocal:n=!1}=e;let r=e.code||"";if(e.filePath)try{const t=s.resolve(e.filePath);r=await m.readFile(t,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取文件失败: ${e.message}`}],isError:!0}}if(!r)return{content:[{type:"text",text:"参数错误: 请提供 code 或 filePath"}],isError:!0};const o=Le("page-generate","agent_create_page",{followUp:"请基于以上规则重新自检并修正页面 HTML 后,再次调用 agent_create_page 提交。"});if(o)return o;const i=Oe(Me(r).replace(/<design_plan>[\s\S]*?<\/design_plan>/gi,"").trim()),{data:a,error:c}=await q("POST","/api/createPage",{code:i});if(c)return{content:[{type:"text",text:`❌ 发送失败: ${c.message}`}],isError:!0};let d=function(e,t="操作"){const n="accepted"===e?.status;let r=n?`✅ ${t}请求已发送到 ${v.pluginName},画布仍在后台处理中`:`✅ ${t}已成功完成`;return e.requestId&&(r+=`\n- 请求 ID: ${e.requestId}`),e.targetNodeId&&(r+=`\n- 节点 ID: ${e.targetNodeId}`),e.documentId&&(r+=`\n- 文档: ${e.documentName||""} (${e.documentId})`),e.documentPageId&&(r+=`\n- 页面: ${e.documentPageName||""} (${e.documentPageId})`),n&&(r+="\n- 状态: accepted(已受理,非最终渲染完成回执)"),r}(a,"设计稿生成");if(n)try{const e=t?s.resolve(t):process.cwd();d+=`\n- 代码已本地持久化至: ${(await R({baseDir:e,code:a.htmlCode||i,documentId:a.documentId,documentPageId:a.documentPageId,targetNodeId:a.targetNodeId,nodeName:a.nodeName,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image})).htmlPath}`}catch(e){d+=`\n- ⚠️ 本地保存失败: ${e.message}`}return{content:[{type:"text",text:d}]}}};let Fe=null;function ke(e){const t=String(e||""),n=t.match(/<main[\s\S]*?<\/main>/i);return(n?.[0]||t).trim()}const Je="\n【最终输出格式(强约束)】\n1) 先输出 Markdown「构思与决策」区块(必须包含:需求重构分析 + 视觉与架构策略 + 自检)\n2) 禁止在对话框回显 HTML 代码(包括 <main>)\n3) 禁止使用 <design_plan> 等自定义标签,直接使用普通 Markdown 标题\n4) 完成构思与自检后,直接调用 agent_create_page(code=完整页面HTML) 发送到画布;code 参数必须是纯 HTML 根节点片段,禁止混入任何 Markdown/解释/清单文本";function Ge(e){return"full-components"===e?"全部使用组件库组件":"混合模式(视觉优先,关键功能区使用组件,且必须使用组件库样式/变量与图标系统)"}function qe(e){const t=ve(e)?"current-file-variables":"free-draw",n=e=>e===t?"(推荐)":"";return`请让用户在以下三项中选择一个:\n1) free-draw${n("free-draw")} - 自由绘制:仅使用 page-generate 基础规则\n2) current-file-variables${n("current-file-variables")} - 使用当前文件的设计系统:会自动落盘当前文件变量到 ${T}/variable/{documentId}.json,并按 variable-import.md 在样式上引用变量\n3) component-library${n("component-library")} - 使用组件库:会要求选择具体团队库,按 component-import.md + variable-import.md 生成`}function ze(e){const{loadedNow:t,text:n}=Ae(e),r=e.join(" + ");return t?`🧭 自动加载规则(${r}):\n${n}\n\n`:`🧭 规则状态:${r} 已加载,本次不重复展开。\n\n`}const Re={name:"design",description:C.design.description,inputSchema:{requirement:n.string().describe(C.design.requirement),code:n.string().optional().describe(C.design.code),projectDir:n.string().optional().describe(C.design.projectDir),designSource:n.enum(["free-draw","current-file-variables","component-library"]).optional().describe(C.design.designSource),userConfirmedDesignSource:n.boolean().optional().default(!1).describe(C.design.userConfirmedDesignSource),teamLibraryName:n.string().optional().describe(C.design.teamLibraryName),teamLibraryId:n.string().optional().describe(C.design.teamLibraryId),buildStrategy:n.enum(["full-components","hybrid"]).optional().describe(C.design.buildStrategy)},handler:async e=>{const{requirement:t,code:n,projectDir:r,designSource:o,userConfirmedDesignSource:i=!1,teamLibraryName:a,teamLibraryId:c,buildStrategy:d}=e,l=r?s.resolve(r):process.cwd();if(de(l).reset&&(Fe=null),"string"==typeof n&&n.trim())return Ve.handler({code:ke(n),projectDir:l});if(!t)return{content:[{type:"text",text:"参数错误: 未提供需求描述"}],isError:!0};const u=`${t.trim()}|${o??""}`;if(Fe&&Fe.requirement===u||(Fe={requirement:u,sourceConfirmed:!1}),!(Fe.sourceConfirmed||o&&i))return{content:[{type:"text",text:`⏸️ 继续设计前,请先让用户确认设计来源(三选一)。\n\n${qe(l)}\n\n用户确认后再次调用 design,并同时传:\n- designSource: "free-draw" | "current-file-variables" | "component-library"\n- userConfirmedDesignSource: true`}],isError:!1};if(Fe.sourceConfirmed=!0,"free-draw"===o){be();const e=ze(["page-generate"]);return Fe=null,{content:[{type:"text",text:`${e}📋 收到需求:${t}\n- 设计来源:自由绘制(free-draw)\n\n请直接按 page-generate 规则生成页面代码,并继续调用 agent_create_page 发送到画布。${Je}`}],isError:!1}}if("current-file-variables"===o){be();const e=function(e){const t=G(e.projectDir);if(!ne||oe&&t&&oe!==t)return{content:[{type:"text",text:`❌ 你已选择「使用当前文件的设计系统」,但当前文件变量尚未获取并落盘。\n\n请按顺序执行:\n1) get_variables({ projectDir: "${t||"<你的项目根目录>"}" })\n2) 工具会写入 ${T}/variable/{documentId}.json\n3) 完成后再次调用 design,传 designSource: "current-file-variables"${t?`\n- 当前 projectDir: ${t}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 当前文件变量刚被 update_variables 修改过,使用设计系统生成页面前必须重新调用 get_variables 刷新快照。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:l});if(e)return e;const n=ze(["page-generate","variable-import"]);return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用当前文件的设计系统(current-file-variables)\n\n请按 page-generate + variable-import 规则生成页面代码:所有可匹配的样式属性必须以 var(...) 引用下面清单中的变量;找不到匹配再回退字面值并自检说明原因。${Je}${we()}`}],isError:!1}}if("component-library"===o){if(!a?.trim())return Fe=null,{content:[{type:"text",text:'⏸️ 你已选择「使用组件库生成页面」,请先让用户明确选择团队库(每次都要确认)。\n\n请按顺序执行:\n1) 调用 get_library_list 获取团队库列表\n2) 询问用户选择具体团队库(禁止助手自动选择)\n3) 用户确认后调用 get_component_info,重新远端拉取并覆盖落盘\n\n用户确认后再次调用 design,并传入:\n- designSource: "component-library"\n- userConfirmedDesignSource: true\n- teamLibraryName: "<用户确认的团队库名称>"'}],isError:!1};ye({requirement:`使用 ${a} 团队库`,teamLibraryId:c});const e=function(e,t){if(!U||B)return null;const n=t||Y||process.cwd();return{content:[{type:"text",text:`❌ 检测到你正在执行“组件库/团队库生成页面”流程,但本轮尚未刷新团队库快照。\n\n请先按顺序执行:\n1) 若未确认团队库,先调用 get_library_list 并让用户选择\n2) 调用 get_component_info(每次远端拉取并覆盖落盘 index.md + components/icons/variable)\n3) 然后再继续「${e}」当前流程\n${W?`\n- 目标团队库: ${W}`:""}${n?`\n- projectDir: ${n}`:""}\n\n在 get_component_info 完成前,禁止直接生成 HTML,避免使用过期组件库数据。`}],isError:!0}}("design",l);if(e)return e;if(!d)return Fe=null,{content:[{type:"text",text:`⏸️ 团队库已确认:${a}\n\n请让用户选择构建策略(二选一)后再次调用 design:\n1) full-components(全组件设计模式:组件、变量、图标全量使用;样式优先 var(...))\n2) hybrid(混合设计模式:关键区块使用组件,全量使用组件库变量与图标系统)`}],isError:!1};const n=ze(["page-generate","component-import","variable-import"]),r="full-components"===d?"\n【full-components 执行提示】\n- 可组件化区块优先使用 <ui-component>;缺组件才回退 HTML,并说明原因\n- 组件、图标、instance_swap 写法见 component-import.md;变量写法见 variable-import.md":"\n【hybrid 执行提示】\n- 关键功能区使用组件,非关键区可自由绘制\n- 非组件区块仍必须遵循 variable-import.md 的变量优先约束";return Fe=null,{content:[{type:"text",text:`${n}📋 收到需求:${t}\n- 设计来源:使用组件库(component-library)\n- 团队库:${a}\n- 构建策略:${Ge(d)}\n${r}\n\n请严格基于落盘组件库文件(index.md/components/*.json/icons.json/variable.json)生成页面代码,并继续调用 agent_create_page 发送到画布。${Je}\n\n${he()}`}],isError:!1}}return{content:[{type:"text",text:`❌ 未识别的 designSource:${String(o)}。合法取值:free-draw / current-file-variables / component-library。`}],isError:!0}}},Ue={name:"get_code",description:C.getCode.description,inputSchema:{contentId:n.string().describe(C.getCode.contentId),documentId:n.string().optional().describe(C.getCode.documentId),documentPageId:n.string().optional().describe(C.getCode.documentPageId),projectDir:n.string().describe(C.getCode.projectDir),outDir:n.string().describe(C.getCode.outDir)},handler:async e=>{const{contentId:t,outDir:n,projectDir:r,documentId:o,documentPageId:i}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 contentId"}],isError:!0};const{data:a,error:c}=await q("GET",`/api/getCode/${t}`);if(c)return{content:[{type:"text",text:`❌ 获取失败: ${c.message}`}],isError:!0};if(!a.code)return{content:[{type:"text",text:"⚠️ 未找到代码内容"}]};const d=r?s.resolve(r):process.cwd(),l=await R({baseDir:d,outDir:n,documentId:o,documentPageId:i,contentId:t,code:a.code,resourcePath:a.resourcePath,shape:a.shape,svg:a.svg,image:a.image});let u=`✅ 代码拉取完成\n- 目录: ${l.targetDir}\n- 文件: ${l.htmlFileName}\n`;return l.shapeCount>0&&(u+=`- Shape: ${l.shapeCount} 个\n`),l.svgCount>0&&(u+=`- SVG: ${l.svgCount} 个\n`),l.imageCount>0&&(u+=`- Image: ${l.imageCount} 个\n`),{content:[{type:"text",text:u}]}}},Be={name:"get_code_list",description:C.getCodeList.description,inputSchema:n.object({}).describe(C.getCodeList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("GET","/api/getCodeList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};if(!Array.isArray(e)||0===e.length)return{content:[{type:"text",text:"📋 代码列表为空"}]};const n=e.length,r=n>=10?e.slice(0,10):e;let o=`✅ 成功获取代码列表 (共 ${n} 项)\n`;return n>=10&&(o+="⚠️ 仅展示最近更新的前 10 条记录\n"),o+=`\n${JSON.stringify(r,null,2)}`,{content:[{type:"text",text:o}]}}},We=e=>e&&"string"==typeof e.base64&&0!==e.base64.length?{type:"image",data:e.base64,mimeType:e.mimeType||"image/webp"}:null,He=e=>String(e||"unknown").replace(/[:/\\?*"<>|]/g,"-"),Ke=async e=>{const{baseDir:t,documentId:n,documentPageId:r,nodeId:o,preview:i}=e;if(!i||"string"!=typeof i.base64||0===i.base64.length)return null;try{const e=((e,t)=>{const n=(e||"").toLowerCase();if(n.includes("webp"))return"webp";if(n.includes("jpeg")||n.includes("jpg"))return"jpg";if(n.includes("png"))return"png";const r=(t||"").toLowerCase();return"webp"===r?"webp":"jpg"===r||"jpeg"===r?"jpg":"png"})(i.mimeType,i.format),a=s.join(t,T,".preview",He(n),He(r));await m.mkdir(a,{recursive:!0});const c=`${He(o)}.${e}`,d=s.join(a,c),l=Buffer.from(i.base64,"base64");return await m.writeFile(d,l),{filePath:d,byteLength:l.byteLength}}catch(e){return console.error("[getSelectionCode] save preview to disk failed:",e),null}},Ye=e=>{if(!e)return"";const t=(e.byteLength/1024).toFixed(1);return`- 预览图(给 LLM 看的): ${e.filePath} (${t} KB)`},Ze=e=>{const t=String(e?.parentDirection||e?.parentDirection||"").trim().toLowerCase();return"row"===t||"column"===t?t:""},Qe=e=>{const t=Number(e?.nodeSize?.w??e?.width),n=Number(e?.nodeSize?.h??e?.height);return Number.isFinite(t)&&Number.isFinite(n)?`- 当前节点尺寸: ${t} x ${n}px`:""},Xe={name:"get_selection_code",description:C.getSelectionCode.description,inputSchema:{projectDir:n.string().describe(C.getSelectionCode.projectDir),targetNodeId:n.string().optional().describe(C.getSelectionCode.targetNodeId),targetNodeIds:n.array(n.string()).optional().describe("[可选] 批量目标图层 ID 列表,传入后按列表顺序拉取代码。"),syncToBase:n.boolean().default(!0).describe(C.getSelectionCode.syncToBase),referenceDesign:n.boolean().optional().describe(C.getSelectionCode.referenceDesign)},handler:async e=>{const{projectDir:t,targetNodeId:n,targetNodeIds:r=[],syncToBase:i=!0,referenceDesign:a=!1,_depth:c=0}=e;if(c>2)return{content:[{type:"text",text:"❌ 递归获取根节点深度过深,已停止。"}],isError:!0};const d=r.filter(e=>"string"==typeof e&&e.trim().length>0),l=a?"&referenceDesign=1":"",u=n?`/api/getSelectionCode?id=${encodeURIComponent(n)}${l}`:d.length>0?`/api/getSelectionCode?ids=${encodeURIComponent(d.join(","))}${l}`:"/api/getSelectionCode"+(l?`?${l.slice(1)}`:""),{data:p,error:g}=await q("GET",u);if(g)return 400===g.status&&"NoSelection"===g.data?.error?{content:[{type:"text",text:"❌ 获取失败: 没有选中任何图层。请先在 MasterGo 中选中一个图层。"}],isError:!0}:{content:[{type:"text",text:`❌ 获取失败: ${g.message}`}],isError:!0};const f=Array.isArray(p.selections)?p.selections:[],y=f.length>1?"all":"primary";if(f.length>1){const e=[];e.push(`✅ 成功获取多选节点代码(共 ${f.length} 个)`),e.push(`- 模式: ${y}`);const n=[],r=[];for(const[o,i]of f.entries()){const a=i?.nodeInfo||{},c=a?.targetNodeId||a?.nodeId||`unknown-${o+1}`,d=a?.nodeName||`node-${o+1}`,l=a?.rootId,u=a?.documentId,m=a?.documentPageId,p=Ze(a);e.push(`- [${o+1}] ${d} (${c})`);const g=Qe(a);g&&e.push(` ${g.replace(/^- /,"")}`),p&&e.push(` parentDirection: ${p}`);const f=We(i?.preview);f&&r.push(f);const y=await Ke({baseDir:t?s.resolve(t):process.cwd(),documentId:u,documentPageId:m,nodeId:c,preview:i?.preview}),b=Ye(y);if(b&&e.push(` ${b.replace(/^- /,"")}`),c===l){let n="";if(u&&m){n=(await R({baseDir:t?s.resolve(t):process.cwd(),documentId:u,documentPageId:m,targetNodeId:l,nodeName:d,code:i?.code||"",resourcePath:i?.resourcePath,shape:i?.shape,svg:i?.svg,image:i?.image})).htmlPath}e.push(" 根节点策略: 未回显代码正文"+(n?`,已落盘到 ${n}`:""));continue}n.push(`### [${o+1}] ${d} (${c})\n\n\`\`\`html\n${i?.code||""}\n\`\`\``)}return{content:[{type:"text",text:`${e.join("\n")}\n\n${n.join("\n\n")}`},...r]}}const b=1===f.length?f[0]:null,v=p.nodeInfo||b?.nodeInfo||{},$=p.code??b?.code??"",I=p.resourcePath??b?.resourcePath,x=p.shape??b?.shape,w=p.svg??b?.svg,S=p.image??b?.image,j=p.preview??b?.preview??null,_=We(j),N=t?s.resolve(t):process.cwd(),P=v||{},D=Ze(P),{rootId:C,documentId:A,documentPageId:L,targetNodeId:E,nodeName:O,parentId:V}=P;if(E===C){const e=await R({baseDir:N,documentId:A,documentPageId:L,targetNodeId:C,nodeName:O,code:$,resourcePath:I,shape:x,svg:w,image:S}),t=await Ke({baseDir:N,documentId:A,documentPageId:L,nodeId:E,preview:j}),n=(e=>{const{nodeName:t,targetNodeId:n,rootId:r,nodeInfo:o,htmlPath:i}=e;return`✅ 成功获取并保存根节点代码\n- 节点: ${t} (${n})\n- 根节点: ${r}\n${Qe(o)?`${Qe(o)}\n`:""}- 文件: ${i}\n- 上下文策略: 根节点代码未回显到对话中,本次仅完成基准文件落盘。\n- 后续动作: 当前仅做"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。`})({nodeName:O,targetNodeId:E,rootId:C,nodeInfo:P,htmlPath:e.htmlPath}),r=Ye(t);return{content:[{type:"text",text:r?`${n}\n${r}`:n},..._?[_]:[]]}}{let e="",n="";if(i&&A&&L){const r=s.join(N,T,M,String(A).replace(/:/g,"-"),String(L).replace(/:/g,"-")),i=s.join(N,T,String(A).replace(/:/g,"-"),String(L).replace(/:/g,"-")),d=String(C).replace(/:/g,"-");let l="",u=r;const p=async()=>{const e=async e=>{if(!o(e))return"";const t=(await m.readdir(e)).filter(e=>e.endsWith(`-${d}.html`));if(0===t.length)return"";if(1===t.length)return t[0];const n=await Promise.all(t.map(async t=>({fileName:t,mtimeMs:(await m.stat(s.join(e,t))).mtimeMs})));return n.sort((e,t)=>t.mtimeMs-e.mtimeMs),n[0].fileName},t=await e(r);if(t)return u=r,t;const n=await e(i);return n?(u=i,n):""};if(l=await p(),l||0!==c||(await Xe.handler({projectDir:t,targetNodeId:C,syncToBase:!0,referenceDesign:a,_depth:c+1}),l=await p()),l){const t=s.join(u,l);try{const n=await m.readFile(t,"utf8"),r=h(n),o=r.querySelector(`[data-node-id="${E}"]`);if(o)o.replaceWith($);else if(V){const e=r.querySelector(`[data-node-id="${V}"]`);e&&e.insertAdjacentHTML("beforeend",$)}(o||V&&r.querySelector(`[data-node-id="${V}"]`))&&(await m.writeFile(t,r.toString(),"utf8"),e=t)}catch(e){n=`⚠️ 子节点代码自动合并失败: ${e?.message||String(e)}`}}}const r=await R({baseDir:N,documentId:A,documentPageId:L,targetNodeId:E,nodeName:O,code:"",resourcePath:I,shape:x,svg:w,image:S});let d=`✅ 成功获取子节点代码\n- 节点: ${O} (${E})\n- 根节点: ${C}`;const l=Qe(P);l&&(d+=`\n${l}`),D&&(d+=`\n- 父容器方向: ${D}`),d+=e?`\n- 自动机制: 子节点最新代码已合并备份至本地基准 HTML: ${e}`:"\n- 提示: 子节点代码已提取到对话上下文中,未生成本地 HTML 碎片文件。",d+='\n- 后续动作: 本次仅完成"读取/同步上下文",请勿在没有用户具体修改诉求的情况下自动调用 agent_update_node / agent_replace_node / sync_to_design。等待用户明确说明要修改什么之后,再选择合适的修改工具。',n&&(d+=`\n- ${n}`),E!==C&&(d+=`\n- 资源同步: 图片(${r.imageCount}), 图标(${r.svgCount}), 图形(${r.shapeCount})`);const u=(e=>e?"row"===e?"- 充满语义提示(parentDirection=row): 宽度充满=主轴充满(优先 flex-1);高度充满=交叉轴充满(优先 self-stretch)。":"- 充满语义提示(parentDirection=column): 宽度充满=交叉轴充满(优先 self-stretch);高度充满=主轴充满(优先 flex-1)。":"")(D);u&&(d+=`\n${u}`);const p=await Ke({baseDir:N,documentId:A,documentPageId:L,nodeId:E,preview:j}),g=Ye(p);return g&&(d+=`\n${g}`),{content:[{type:"text",text:`${d}\n\n代码内容:\n\n${$}`},..._?[_]:[]]}}}},et={name:"get_user_info",description:C.getUserInfo.description,inputSchema:n.object({}).describe(C.getUserInfo.inputSchema),handler:async()=>{const{data:e,error:t}=await q("GET","/api/getUserInfo");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};let n=`✅ 用户信息\n\n👤 用户: ${e.realname||e.userName||e.userId}\n`;if(e.quota){const t=Number(e?.quota?.plan??e?.plan??0),r=t>0?"不限量":String(e.quota.mcp_generate_limit||"无"),o=t>0?"不限量":String(e.quota.mcp_get_limit||"无");n+="\n📊 配额:\n",n+=` - 生成设计: ${e.quota.mcp_generate_count||0} / ${r}\n`,n+=` - 获取代码: ${e.quota.mcp_get_count||0} / ${o}\n`}return e.teams?.length&&(n+="\n👥 团队:\n",e.teams.forEach((e,t)=>n+=` ${t+1}. ${e.name} (ID: ${e.teamId})\n`)),{content:[{type:"text",text:n}]}}},tt={name:"agent_update_node",description:C.updateNode.description,inputSchema:{documentId:n.string().optional().describe(C.updateNode.documentId),documentPageId:n.string().optional().describe(C.updateNode.documentPageId),targetNodeId:n.string().optional().describe(C.updateNode.targetNodeId),code:n.string().describe(C.updateNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=h(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止灾难性地覆盖当前选中的未知图层,本次操作已被拦截。请明确指定 targetNodeId 或在 HTML 包含 data-node-id。"}],isError:!0};const{data:i,error:a}=await q("POST","/api/updateNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 局部更新失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [局部修改] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}},nt={name:"agent_replace_node",description:C.replaceNode.description,inputSchema:{documentId:n.string().optional().describe(C.replaceNode.documentId),documentPageId:n.string().optional().describe(C.replaceNode.documentPageId),targetNodeId:n.string().optional().describe(C.replaceNode.targetNodeId),code:n.string().describe(C.replaceNode.code)},handler:async e=>{const{documentId:t,documentPageId:n,code:r}=e;let o=e.targetNodeId;if(!r)return{content:[{type:"text",text:"❌ 必须提供 code"}],isError:!0};if(!o)try{const e=h(r).querySelector("[data-node-id]");if(e){const t=e.getAttribute("data-node-id");t&&(o=t)}}catch(e){}if(!o)return{content:[{type:"text",text:"❌ 安全阻拦:未提供 targetNodeId,且无法从代码中解析出 data-node-id。为防止把您当前极可能选中的大容器直接覆盖损毁,系统已拦截。请务必明确目标节点的 ID。"}],isError:!0};const{data:i,error:a}=await q("POST","/api/replaceNode",{code:r,targetNodeId:o,documentId:t,documentPageId:n});if(a)return{content:[{type:"text",text:`❌ 结构替换失败: ${a.message}`}],isError:!0};const s="accepted"===i.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [结构替换] 指令已发送${i.targetNodeId?`\n- 节点 ID: ${i.targetNodeId}`:""}${s}\n💡 提示:若当前阶段修改已全部完成,建议调用 get_selection_code 重新拉取一次根节点,以自动同步最新代码到本地 ${T} 基准文件。`}]}}};function rt(e){return String(e).replace(/:/g,"-")}async function ot(e,t){if(!o(e))return"";const n=(await m.readdir(e,{withFileTypes:!0})).filter(e=>e.isFile()&&e.name.endsWith(`-${t}.html`));if(0===n.length)return"";const r=await Promise.all(n.map(async t=>{const n=s.join(e,t.name);return{fullPath:n,mtimeMs:(await m.stat(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0].fullPath}async function it(e,t){if(!o(e))return"";const n=await m.readdir(e,{withFileTypes:!0});let r=null;for(const o of n){const n=s.join(e,o.name);if(o.isDirectory()){const e=await it(n,t);if(e){const t=await m.stat(e);(!r||t.mtimeMs>r.mtimeMs)&&(r={fullPath:e,mtimeMs:t.mtimeMs})}continue}if(o.name.endsWith(`-${t}.html`)){const e=await m.stat(n);(!r||e.mtimeMs>r.mtimeMs)&&(r={fullPath:n,mtimeMs:e.mtimeMs})}}return r?.fullPath||""}const at={name:"get_design_diff",description:C.getDesignDiff.description,inputSchema:{projectDir:n.string().describe(C.getDesignDiff.projectDir),targetNodeId:n.string().optional().describe(C.getDesignDiff.targetNodeId),filePath:n.string().optional().describe(C.getDesignDiff.filePath)},handler:async e=>{const{projectDir:t,targetNodeId:n,filePath:r}=e,o=t?s.resolve(t):process.cwd();let i=n,a="",c=r||"";if(c)try{const e=s.isAbsolute(c)?c:s.resolve(o,c);a=await m.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 无法读取指定的 filePath: ${e.message}`}],isError:!0}}let d="";if(!a||!i){const e=i?`/api/getSelectionCode?id=${encodeURIComponent(i)}`:"/api/getSelectionCode",{data:t,error:n}=await q("GET",e);if(n||!t?.nodeInfo?.targetNodeId)return{content:[{type:"text",text:"❌ 无法获取图层信息,请确保已在 MasterGo 中选中图层。"}],isError:!0};const{rootId:r,targetNodeId:l,documentId:u,documentPageId:p}=t.nodeInfo;if(d=r,i||(i=l),!a&&r){const e=String(r).replace(/:/g,"-"),t=u&&p?s.join(o,T,M,rt(u),rt(p)):"",n=u&&p?s.join(o,T,rt(u),rt(p)):"";if(t&&(c=await ot(t,e)),!c&&n&&(c=await ot(n,e)),!c){const t=s.join(o,T,M);c=await it(t,e)}if(!c){const t=s.join(o,T);c=await it(t,e)}c&&(a=await m.readFile(c,"utf8"))}}if(!a)return{content:[{type:"text",text:`❌ 在本地 ${T} 目录中找不到 ID 为 ${d||i} 的基准代码文件。请确保您之前已使用 get_selection_code 拉取过该图层或其根节点的代码。`}],isError:!0};const{data:l,error:u}=await q("POST","/api/getDesignDiff",{code:a,targetNodeId:i});if(u)return{content:[{type:"text",text:`❌ 获取设计稿差异失败: ${u.message}`}],isError:!0};let p=l.diffs||[];return p&&!Array.isArray(p)&&Array.isArray(p.diffs)&&(p=p.diffs),0===p.length?{content:[{type:"text",text:"✅ 设计稿与本地代码完全一致,没有发现任何变更。"}]}:{content:[{type:"text",text:`✅ 成功获取设计稿差异 (共 ${p.length} 处变更):\n\n\`\`\`json\n${JSON.stringify(p,null,2)}\n\`\`\`\n\n请根据以上 Diff 数据,结合 data-node-id,将变更合并到用户的业务代码(无论是 Vue、React、原生代码)和 ${T} 的基准文件。`}]}}},st={name:"agent_remove_node",description:C.removeNode.description,inputSchema:{documentId:n.string().optional().describe(C.removeNode.documentId),documentPageId:n.string().optional().describe(C.removeNode.documentPageId),targetNodeId:n.string().optional().describe(C.removeNode.targetNodeId)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r}=e,{data:o,error:i}=await q("POST","/api/removeNode",{targetNodeId:t,documentId:n,documentPageId:r});if(i)return{content:[{type:"text",text:`❌ 删除失败: ${i.message}`}],isError:!0};const a="accepted"===o.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ 已成功发送删除指令\n- 目标节点 ID: ${o.targetNodeId||t||"当前选中图层"}${a}`}]}}},ct={name:"agent_remove_variable",description:C.removeVariable.description,inputSchema:{documentId:n.string().optional().describe(C.removeVariable.documentId),documentPageId:n.string().optional().describe(C.removeVariable.documentPageId),id:n.string().optional().describe(C.removeVariable.id),collection:n.string().optional().describe(C.removeVariable.collection),collectionId:n.string().optional().describe(C.removeVariable.collectionId),name:n.string().optional().describe(C.removeVariable.name),type:n.string().optional().describe(C.removeVariable.type),variables:n.any().optional().describe(C.removeVariable.variables),operations:n.any().optional().describe(C.removeVariable.variables)},handler:async e=>{const t=Le(["variable-generate"],"agent_remove_variable",{followUp:"⚠️ 删除变量为高危操作。请先按上述规则确认目标变量(优先使用 id)后再次调用 agent_remove_variable 提交。"});if(t)return t;const{data:n,error:r}=await q("POST","/api/removeVariable",e);if(r)return{content:[{type:"text",text:`❌ 变量删除失败: ${r.message}`}],isError:!0};const o=Array.isArray(n?.results)?n.results:[],i=o.filter(e=>!1===e?.success),a=!1!==n?.success&&0===i.length;return{content:[{type:"text",text:`${a?"✅ 变量删除指令已发送":"❌ 变量删除部分或全部失败"}\n\n结果:\n${JSON.stringify(o,null,2)}`+(i.length?`\n\n失败明细:\n${JSON.stringify(i,null,2)}`:"")}],isError:!a}}},dt={name:"agent_sync_design",description:C.syncToDesign.description,inputSchema:{documentId:n.string().optional().describe(C.syncToDesign.documentId),documentPageId:n.string().optional().describe(C.syncToDesign.documentPageId),targetNodeId:n.string().optional().describe(C.syncToDesign.targetNodeId),filePath:n.string().describe(C.syncToDesign.filePath),userConfirmed:n.boolean().optional().default(!1).describe(C.syncToDesign.userConfirmed)},handler:async e=>{const{targetNodeId:t,documentId:n,documentPageId:r,filePath:o,userConfirmed:i=!1}=e;if(!i)return{content:[{type:"text",text:"⏸️ 已阻止自动同步到画布。\n\n仅当用户明确提出“请同步到画布”时,才允许调用 agent_sync_design。\n请先向用户确认,再以 userConfirmed=true 重新调用。"}],isError:!0};let a="";if(o)try{const e=s.resolve(o);a=await m.readFile(e,"utf8")}catch(e){return{content:[{type:"text",text:`❌ 读取本地文件失败: ${e.message}`}],isError:!0}}if(!a)return{content:[{type:"text",text:"❌ 同步失败: 未能从指定路径读取到有效代码"}],isError:!0};const{data:c,error:d}=await q("POST","/api/syncToDesign",{code:a,targetNodeId:t,documentId:n,documentPageId:r});if(d)return{content:[{type:"text",text:`❌ [全量同步] 失败: ${d.message}`}],isError:!0};const l="accepted"===c.status?"\n- 状态: accepted(已受理,画布仍在后台处理中)":"";return{content:[{type:"text",text:`✅ [全量同步] 已成功推送至画布${c.targetNodeId?`\n- 根节点 ID: ${c.targetNodeId}`:""}${l}\n\n💡 建议:同步完成后,请立即通过 get_selection_code (传入 rootId) 重新拉取一次纯净 HTML,以刷新本地 ${T} 基准。`}]}}};function lt(e){return String(e??"").trim().toLowerCase()}function ut(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function mt(e){if(!ut(e))return null;const t="string"==typeof e.name?e.name:"";if(!t.trim())return null;const n="string"==typeof e.description?e.description:"",r=function(e){if(null!=e)return"string"==typeof e||"number"==typeof e||"boolean"==typeof e||Array.isArray(e)||ut(e)?e:String(e)}(e.value);return void 0===r?{name:t,description:n}:{name:t,description:n,value:r}}function pt(e){return function(e){return Array.isArray(e)?e:[]}(e).map(mt).filter(e=>!!e)}function gt(e){const t=pt(e.paints),n=pt(e.colors),r=function(e){const t=new Set,n=[];for(const r of e){const e=r.name;t.has(e)||(t.add(e),n.push(r))}return n}([...n,...t]),o=pt(e.texts);return{boxShadow:pt(e.effects),color:r,colors:n,fill:t,paints:t,borderRadius:pt(e.cornerRadiuses),padding:pt(e.paddings),gap:pt(e.spacings),borderWidth:pt(e.strokeWidths),typography:o,texts:o,bools:pt(e.bools),strings:pt(e.strings),grids:pt(e.grids)}}function ft(e){return null===e||["string","number","boolean"].includes(typeof e)}function yt(e){const t=Number(e);return Number.isFinite(t)?t:0}function bt(e){const t=e.size;if(Array.isArray(t)&&t.length>=2)return[yt(t[0]),yt(t[1])];const n=ut(e.style)?e.style:{};return[yt(e.width??n.width),yt(e.height??n.height)]}function ht(e){if(Array.isArray(e)){const t=e.filter(ft);return t.length>0?t:void 0}if(ut(e)){const t=Array.isArray(e.enum)?e.enum.filter(ft):void 0,n=ft(e.default)?e.default:void 0;if(t&&t.length>0){return 2===t.length&&t.includes(!1)&&t.includes(!0)&&"boolean"==typeof n?n:t}return void 0!==n?n:void 0}if(ft(e))return e}function vt(e){if(!ut(e))return{};const t={};for(const[n,r]of Object.entries(e)){const e=ht(r);void 0!==e&&(t[n]=e)}return t}function $t(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e).map(e=>e.trim()).filter(Boolean):[]}function It(e){if(!ut(e))return{};const t={},n=e=>{const t=String(e||"").trim();return!!t&&(t.includes("+")||/^[a-zA-Z0-9_-]+:\d+$/.test(t))};for(const[r,o]of Object.entries(e)){if("string"!=typeof o)continue;const e=r.trim(),i=o.trim();e&&i&&(n(i)||(t[e]=i))}return t}function xt(e){return"string"==typeof e?e:""}function wt(e){return"string"==typeof e&&e.trim()?e:void 0}function St(e){const t=ut(e)?e:{},n=Array.isArray(t.components)?t.components:Array.isArray(t["component-set"])?t["component-set"]:[],r=[],o=function(e){if(!Array.isArray(e))return[];const t=[],n=new Set;for(const r of e){if("string"==typeof r&&r.trim()){const e=r.trim(),o=e;n.has(o)||(n.add(o),t.push({name:e}));continue}if(ut(r)&&"string"==typeof r.name&&r.name.trim()){const e=r.name.trim(),o=e;n.has(o)||(n.add(o),t.push({name:e}))}}return t}(t.icons);for(const e of n){if(!ut(e))continue;const t="string"==typeof e.name?e.name:"",n=xt(e.description),o=wt(e.id),i=wt(e.ukey),a=ut(e.props)?e.props:ut(e.properties)?e.properties:{},s=e.slots,c=e.instance_swap;r.push({id:o,ukey:i,name:t,description:n,cover:wt(e.cover),size:bt(e),props:vt(a),slots:$t(s),instance_swap:It(c)})}return{variables:ut(t.variables)?t.variables:ut(t.style)?gt(t.style):ut(t.styles)?gt(t.styles):ut(t.tokens)?t.tokens:{},components:r,icons:o}}function jt(e){if(Array.isArray(e))return e.map(jt);if(ut(e)){const t={};for(const[n,r]of Object.entries(e))"ukey"!==n&&(t[n]=jt(r));return t}return e}function _t(e,t){const n=J(e||"component").replace(/^-+|-+$/g,"").toLowerCase()||"component";return`${String(t+1).padStart(3,"0")}-${n}`}async function Nt(e){try{const t=await m.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function Pt(e,t){await m.writeFile(e,`${function(e,t=2){const n=e=>" ".repeat(e*t),r=(e,t)=>{if(ft(e))return JSON.stringify(e);if(Array.isArray(e))return 0===e.length?"[]":e.every(ft)?`[${e.map(e=>JSON.stringify(e)).join(", ")}]`:`[\n${e.map(e=>`${n(t+1)}${r(e,t+1)}`).join(",\n")}\n${n(t)}]`;if(ut(e)){const o=Object.entries(e).filter(([,e])=>void 0!==e);return 0===o.length?"{}":`{\n${o.map(([e,o])=>`${n(t+1)}${JSON.stringify(e)}: ${r(o,t+1)}`).join(",\n")}\n${n(t)}}`}return JSON.stringify(String(e))};return r(e,0)}(t)}\n`,"utf8")}async function Dt(e,t){await m.writeFile(e,t.endsWith("\n")?t:`${t}\n`,"utf8")}async function Ct(e){try{await m.unlink(e)}catch(e){if("ENOENT"!==e?.code)throw e}}function At(e){return"index.json"===s.basename(e).toLowerCase()}function Lt(e){const t=s.join(e,"index.md");if(o(t))return t;const n=s.join(e,"index.json");return o(n)?n:""}async function Et(e){if(!o(e))return[];const t=[],n=s.join(e,"catalog.json"),r=await Nt(n);if(Array.isArray(r?.libraries))for(const n of r.libraries){const r=String(n?.id||"").trim(),i=String(n?.name||"").trim(),a=String(n?.slug||"").trim(),c=String(n?.indexPath||"").trim();if(!r||!c)continue;const d=s.join(e,c);if(!o(d))continue;const l=c.split(/[\\/]/)[0]||r,u=await Nt(s.join(e,l,"meta.json")),m=String(u?.teamLibrary?.id||"").trim(),p=String(u?.teamLibrary?.name||"").trim(),g=String(u?.teamLibrary?.slug||"").trim();t.push({id:m||r,name:p||i||a||m||r,slug:g||a||J(p||i||m||r),indexAbsPath:d,componentCount:Number(n?.componentCount||0),iconCount:Number(n?.iconCount||0)})}const i=await m.readdir(e,{withFileTypes:!0}).catch(()=>[]);for(const n of i){if(!n.isDirectory())continue;const r=n.name,i=s.join(e,r),a=await Nt(s.join(i,"meta.json"));if(a?.paths?.index){const e=s.join(i,a.paths.index);if(o(e)){const n=String(a?.teamLibrary?.id||"").trim(),o=String(a?.teamLibrary?.name||"").trim(),i=String(a?.teamLibrary?.slug||"").trim();t.push({id:n||r,name:o||i||n||r,slug:i||J(o||n||r),indexAbsPath:e});continue}}const c=await m.readdir(i,{withFileTypes:!0}).catch(()=>[]);for(const e of c){if(!e.isDirectory())continue;const n=Lt(s.join(i,e.name));if(!o(n))continue;const a=e.name;t.push({id:r,name:a,slug:a,indexAbsPath:n})}}const a=function(e){const t=new Map;for(const n of e)n.indexAbsPath&&t.set(n.indexAbsPath,n);return Array.from(t.values())}(t);for(const e of a){if(!At(e.indexAbsPath))continue;const t=await Nt(e.indexAbsPath);if(!t)continue;const n=String(t?.teamLibrary?.id||"").trim(),r=String(t?.teamLibrary?.name||"").trim();n&&(e.id=n),r&&(e.name=r,e.slug=J(r)),"number"==typeof t?.summary?.componentCount&&(e.componentCount=t.summary.componentCount),"number"==typeof t?.summary?.iconCount&&(e.iconCount=t.summary.iconCount)}return a}async function Tt(){const{data:e,error:t}=await q("GET","/api/getTeamLibraryList");if(t)throw new Error(t.message);const n=e?.data??e;return Array.isArray(n)?n:[]}function Mt(e){return null==e||"string"==typeof e&&""===e.trim()}function Ot(e){if(Array.isArray(e))return{documentName:"",documentId:"",variables:e};if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Ot(t.data);const n=Array.isArray(t.variables)?t.variables:[];return{documentName:String(t.documentName??""),documentId:String(t.documentId??""),variables:n}}return{documentName:"",documentId:"",variables:[]}}function Vt(e){if(Array.isArray(e))return e;if(e&&"object"==typeof e){const t=e;if(t.data&&"object"==typeof t.data)return Vt(t.data);if(Array.isArray(t.operations))return t.operations;if(Array.isArray(t.variables))return t.variables;if(t.name||t.id||t.action||t.collection||t.collectionId)return[t]}return[]}function Ft(e){if(Array.isArray(e))return e.some(e=>Ft(e));if(!e||"object"!=typeof e)return!1;const t=e;return"deleteVariable"===String(t.action||"")||(Array.isArray(t.operations)?Ft(t.operations):!!Array.isArray(t.variables)&&Ft(t.variables))}async function kt(e){if(void 0!==e.operations)return{operations:Vt(e.operations),source:"operations 参数",documentId:e.documentId};if(void 0!==e.variables)return{operations:Vt(e.variables),source:"variables 参数",documentId:e.documentId};const t=s.resolve(e.projectDir),n=e.filePath?s.resolve(e.filePath):e.documentId?s.join(t,T,"variable",`${J(e.documentId)}.json`):await async function(e){const t=s.join(e,T,"variable");let n=[];try{n=await f(t)}catch{return null}const r=await Promise.all(n.filter(e=>e.toLowerCase().endsWith(".json")).map(async e=>{const n=s.join(t,e);return{filePath:n,mtimeMs:(await y(n)).mtimeMs}}));return r.sort((e,t)=>t.mtimeMs-e.mtimeMs),r[0]?.filePath||null}(t);if(!n)throw new Error("未找到变量文件。请先调用 get_variables,或显式传入 filePath / variables / operations。");const r=await async function(e){const t=await b(e,"utf8");return JSON.parse(t)}(n),o=String(r?.documentId||e.documentId||"").trim()||void 0;return{operations:Vt(r),source:n,documentId:o}}const Jt=[Ee,Re,Ue,Be,et,{name:"get_library_list",description:C.getLibraryList.description,inputSchema:n.object({}).describe(C.getLibraryList.inputSchema),handler:async()=>{const{data:e,error:t}=await q("GET","/api/getTeamLibraryList");if(t)return{content:[{type:"text",text:`❌ 获取失败: ${t.message}`}],isError:!0};const n=e?.data??e;if(!Array.isArray(n)||0===n.length)return{content:[{type:"text",text:"📚 团队库列表为空(请确认 MCP 服务已连接且当前文件已订阅团队库)"}]};const r=n.map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`✅ 当前文件团队库列表(共 ${r.length} 个)\n\n${JSON.stringify(r,null,2)}\n\n若用户要求“使用某组件库生成页面”,请按最短流程:\n1) 让用户确定团队库(可回复:使用 <团队库名称> 团队库)\n2) 调用 get_component_info,每次从远端拉取并覆盖落盘 index.md + components/icons/variable\n`}]}}},{name:"get_component_info",description:C.getComponentInfo.description,inputSchema:{projectDir:n.string().describe(C.getComponentInfo.projectDir),teamLibraryId:n.string().optional().describe(C.getComponentInfo.teamLibraryId),teamLibraryName:n.string().optional().describe(C.getComponentInfo.teamLibraryName),includePropertyDetails:n.boolean().optional().default(!0).describe(C.getComponentInfo.includePropertyDetails)},handler:async e=>{const{projectDir:t,teamLibraryId:n,teamLibraryName:r,includePropertyDetails:a=!0}=e;if(!t)return{content:[{type:"text",text:"参数错误: 未提供 projectDir"}],isError:!0};const c=s.resolve(t);de(c);const d=s.join(c,T,"library"),l=s.join(d,"catalog.json"),u=await Et(d);let m=n,p=r;if(!m&&!p){if(u.length>0){const e=u.map(e=>({id:e.id,name:e.name,componentCount:e.componentCount??void 0,iconCount:e.iconCount??void 0,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`✅ 检测到本地 ${T}/library 已有落盘团队库(共 ${e.length} 个)。本地列表仅用于选择团队库;选定后仍会重新远端拉取并覆盖落盘。\n\n本地团队库列表:\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info(传 teamLibraryName 或 teamLibraryId)。`}]}}try{const e=(await Tt()).map(e=>({id:e?.id,name:e?.name,componentCount:e?.componentCount,styleCount:e?.styleCount}));return{content:[{type:"text",text:`⚠️ 你还没有指定团队库。\n\n当前文件团队库列表(共 ${e.length} 个):\n${JSON.stringify(e,null,2)}\n\n请回复:使用 <团队库名称> 团队库\n然后再次调用 get_component_info 并传入 teamLibraryName。\n\n组件库生成页面的标准流程:选定团队库后调用 get_component_info,每次都远端拉取并覆盖落盘。`}]}}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}}const g=function(e,t,n){if(!e.length)return{status:"none"};let r=[...e];if(!Mt(t)){const e=lt(String(t)),n=lt(J(String(t))),o=r.filter(t=>{const r=lt(t.id);return r===e||r===n});if(!o.length)return{status:"none"};r=o}if(!Mt(n)){const e=lt(String(n)),t=lt(J(String(n))),o=r.filter(n=>{const r=lt(n.name),o=lt(n.slug);return r===e||o===e||r===t||o===t});if(o.length>0)r=o;else{const t=r.filter(t=>{const n=lt(t.name),r=lt(t.slug);return n.includes(e)||e.includes(n)||r.includes(e)||e.includes(r)});if(!t.length)return{status:"none"};r=t}}return 1===r.length?{status:"matched",entry:r[0]}:{status:"ambiguous",candidates:r}}(u,m,p);if("matched"===g.status){const e=g.entry,t=At(e.indexAbsPath)?await Nt(e.indexAbsPath):null,n=String(t?.teamLibrary?.name||"").trim(),r=String(t?.teamLibrary?.id||"").trim(),o=n||e.name||p||m||"",i=r||e.id||m||"";m=i||m||e.id,p=o||p||e.name}if("ambiguous"===g.status){const e=g.candidates.map(e=>({id:e.id,name:e.name,indexPath:e.indexAbsPath}));return{content:[{type:"text",text:`⚠️ 本地 ${T}/library 匹配到多个团队库候选,请更精确指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n请改为传 teamLibraryId 再调用 get_component_info。`}]}}if(!m&&p)try{const e=await Tt(),t=lt(p),n=e.filter(e=>lt(e?.name)===t),r=e.filter(e=>!Mt(e?.name)&<(e?.name).includes(t)),o=n.length>0?n:r;if(0===o.length)return{content:[{type:"text",text:`❌ 未找到匹配的团队库:${p}\n\n请先调用 get_library_list 查看可用团队库,或直接传 teamLibraryId。`}],isError:!0};if(o.length>1){const e=o.map(e=>({id:e?.id,name:e?.name}));return{content:[{type:"text",text:`⚠️ 团队库名称匹配到多个候选,请更精确地指定。\n\n候选:\n${JSON.stringify(e,null,2)}\n\n你可以改用 teamLibraryId 直接调用 get_component_info。`}]}}m=o[0]?.id,p=o[0]?.name}catch(e){return{content:[{type:"text",text:`❌ 获取团队库列表失败: ${e?.message||String(e)}`}],isError:!0}}if(!m)return{content:[{type:"text",text:"参数错误: 未能确定 teamLibraryId"}],isError:!0};if(!p)try{const e=(await Tt()).find(e=>String(e?.id||"")===String(m));e?.name&&(p=e.name)}catch{}const f=`/api/getComponentInfo?teamLibraryId=${encodeURIComponent(m)}&includePropertyDetails=${a?"true":"false"}`,{data:y,error:b}=await q("GET",f);if(b)return{content:[{type:"text",text:`❌ 获取失败: ${b.message}`}],isError:!0};const h=St(y?.data??y),v=J(m),$=J(p||m),I=(new Date).toISOString(),x=s.join(d,v),w=s.join(x,$),S=s.join(w,"components");o(x)||i(x,{recursive:!0}),o(w)||i(w,{recursive:!0}),o(S)||i(S,{recursive:!0});const j=[],_=[],N=new Set;h.components.forEach((e,t)=>{let n=_t(e.name,t),r=1;for(;N.has(n);)r+=1,n=`${_t(e.name,t)}-${r}`;N.add(n),j.push(n),_.push(Pt(s.join(w,`components/${n}.json`),{id:e.id,name:e.name,description:e.description||"",cover:e.cover,size:e.size,props:e.props,slots:e.slots,instance_swap:e.instance_swap}))});const P="icons.json",D="variable.json";await Promise.all([..._,Pt(s.join(w,P),{icons:h.icons}),Pt(s.join(w,D),{variables:jt(h.variables)})]);const C=s.join(w,"index.md"),A=function(e){const{teamLibraryName:t,componentKeys:n,iconsFileName:r,variablesFileName:o}=e,i=n.length?n.map(e=>`- \`${e}.json\``).join("\n"):"- 暂无组件文件";return[`# ${a=t,String(a??"").replace(/\s+/g," ").trim()} 组件库索引`,"","## 快速定位","- 组件数据在 `components/*.json`。每个组件文件都自带 `name`、`description`、`cover`、`size`、`props`、`slots`、`instance_swap`。",`- 图标数据在 \`${r}\`,只能使用 \`icons[].name\` 中存在的图标名。`,`- 样式变量在 \`${o}\`,使用 \`var(...)\` 时写变量 \`name\`,例如 \`{ "name": "填充色/常规" }\` 写 \`var(填充色/常规)\`,禁止改写或直接写 \`value\`。`,"- 本文件只负责导航。生成页面时,以具体组件 JSON、图标 JSON、变量 JSON 为准。","","## 文件结构","- `components/` - 组件详情目录","- `components/{key}.json` - 单个组件详情",`- \`${r}\` - 图标列表`,`- \`${o}\` - 设计变量`,"","## 组件文件",i].join("\n");var a}({teamLibraryName:String(p||m),componentKeys:j,iconsFileName:P,variablesFileName:D});await Promise.all([Dt(C,A),Ct(s.join(w,"index.json"))]);const L={schemaVersion:1,updatedAt:I,teamLibrary:{id:String(m),name:String(p||m),slug:$},paths:{index:s.relative(x,C),icons:s.relative(x,s.join(w,P)),variables:s.relative(x,s.join(w,D)),componentsDir:s.relative(x,S)}};var E;return await Pt(s.join(x,"meta.json"),L),await async function(e,t){const n=await Nt(e),r=(Array.isArray(n?.libraries)?n.libraries:[]).filter(e=>e.id!==t.id&&e.indexPath!==t.indexPath);r.push(t),r.sort((e,t)=>e.name.localeCompare(t.name,"zh-Hans-CN"));const o={schemaVersion:1,updatedAt:t.updatedAt,libraries:r};await Pt(e,o)}(l,{id:String(m),name:String(p||m),slug:$,updatedAt:I,componentCount:h.components.length,iconCount:h.icons.length,indexPath:s.relative(d,C)}),E={teamLibraryId:String(m),teamLibraryName:p||String(m),filePath:C,baseDir:c},B=!0,E.teamLibraryId?.trim()&&(H=E.teamLibraryId.trim()),E.teamLibraryName?.trim()&&(W=E.teamLibraryName.trim()),E.filePath?.trim()&&(K=E.filePath.trim()),E.baseDir?.trim()&&(Y=E.baseDir.trim()),{content:[{type:"text",text:`✅ 已获取团队库组件信息并落盘\n- 团队库: ${p||""} (${m})\n- 目录: ${w}\n- 索引说明文件: ${C}\n- 组件详情文件: ${j.length} 个\n- 图标文件: ${s.join(w,P)}\n- 变量文件: ${s.join(w,D)}\n- 全局目录索引: ${l}\n\n下一步生成页面前:读取 index.md 导航,再读取 components/*.json、icons.json、variable.json;具体生成规则以 component-import.md 为准。`}]}}},{name:"get_variables",description:C.getVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.getVariables.projectDir)}).describe(C.getVariables.inputSchema),handler:async e=>{const{projectDir:t}=e,n=s.resolve(t);de(n);const{data:r,error:o}=await q("GET","/api/getVariables");if(o)return{content:[{type:"text",text:`❌ 获取失败: ${o.message}`}],isError:!0};const{documentName:i,documentId:a,variables:c}=Ot(r),d=s.join(n,T,"variable"),l=s.join(d,`${J(a)}.json`),u=new Set(c.map(e=>e?.collection).filter(Boolean)),m=new Set(c.flatMap(e=>Array.isArray(e?.mode)?e.mode:[]).map(e=>e?.name).filter(Boolean)),f={documentName:i,documentId:a,collections:u.size,modes:m.size,variables:c.length};let y="";try{await p(d,{recursive:!0}),await g(l,`${JSON.stringify(c,null,2)}\n`,"utf8"),ne=!0,re=!1,oe=G((b={projectDir:n,documentId:a,filePath:l}).projectDir),ie=String(b.documentId||"").trim(),ae=G(b.filePath),y=`\n\n已落盘: ${l}`}catch(e){y=`\n\n⚠️ 落盘失败: ${e?.message||String(e)}(路径: ${l})`}var b;return{content:[{type:"text",text:`✅ 当前文件变量已获取\n\n摘要:\n${JSON.stringify(f,null,2)}\n\n完整数据:\n${JSON.stringify({documentName:i,documentId:a,variables:c},null,2)}`+y}]}}},{name:"update_variables",description:C.updateVariables.description,inputSchema:n.object({projectDir:n.string().min(1).describe(C.updateVariables.projectDir),documentId:n.string().optional().describe(C.updateVariables.documentId),documentPageId:n.string().optional().describe(C.updateVariables.documentPageId),filePath:n.string().optional().describe(C.updateVariables.filePath),variables:n.any().optional().describe(C.updateVariables.variables),operations:n.any().optional().describe(C.updateVariables.operations)}).describe(C.updateVariables.inputSchema),handler:async e=>{de(e.projectDir);const t=Le(["variable-generate"],"update_variables",{followUp:`请先按以上规则读取/修改 ${T}/variable/{documentId}.json,再次调用 update_variables 提交。`});if(t)return t;let n;try{n=await kt(e)}catch(e){return{content:[{type:"text",text:`❌ 读取变量数据失败: ${e?.message||String(e)}`}],isError:!0}}const r=n.operations;if(Ft(r))return{content:[{type:"text",text:"❌ update_variables 不执行 deleteVariable。删除变量是危险操作,请改用 agent_remove_variable。"}],isError:!0};if(!r.length)return{content:[{type:"text",text:"❌ 没有可提交的变量操作。请传入数组、{ variables: [...] }、{ operations: [...] },或使用 get_variables 落盘后的变量 JSON。"}],isError:!0};const o=e.documentId||n.documentId,i=function(e){const t=G(e.projectDir),n=String(e.documentId||"").trim();if(!ne||oe&&t&&oe!==t||ie&&n&&ie!==n)return{content:[{type:"text",text:`❌ 变量更新前必须先调用 get_variables 获取并落盘当前文件变量。\n\n请先执行:\n1) get_variables({ projectDir })\n2) 读取 ${T}/variable/{documentId}.json\n3) 基于落盘数据生成本次变量变更\n4) 再调用 update_variables${t?`\n- 当前 projectDir: ${t}`:""}${n?`\n- 当前 documentId: ${n}`:""}`}],isError:!0};if(re)return{content:[{type:"text",text:"❌ 上一次 update_variables 已提交,继续提交前必须重新调用 get_variables 刷新变量快照。\n\n推荐顺序:基础变量 update_variables -> get_variables 落盘 -> 语义变量 update_variables -> get_variables 落盘。"+(ae?`\n- 上次快照: ${ae}`:"")}],isError:!0};return null}({projectDir:e.projectDir,documentId:o});if(i)return i;const a=pe(r);if(a)return a;const{data:s,error:c}=await q("POST","/api/updateVariables",{documentId:o,documentPageId:e.documentPageId,operations:r});if(c)return{content:[{type:"text",text:`❌ 变量更新失败: ${c.message}`}],isError:!0};const d=Array.isArray(s?.results)?s.results:[],l=d.filter(e=>!1===e?.success),u={source:n.source,documentId:o||"",status:s?.status||"completed",operations:r.length,succeeded:d.length?d.length-l.length:void 0,failed:l.length||void 0},m=!1===s?.success||l.length>0?`\n\n失败明细:\n${JSON.stringify(l.length?l:s?.error,null,2)}`:"",p=!1!==s?.success&&0===l.length;var g;return p&&(g={projectDir:e.projectDir,documentId:o},ne=!0,re=!0,oe=G(g.projectDir)||oe,ie=String(g.documentId||"").trim()||ie),{content:[{type:"text",text:`✅ 变量更新指令已发送\n\n摘要:\n${JSON.stringify(u,null,2)}\n\n结果:\n${JSON.stringify(d,null,2)}`+m+`\n\n建议:更新完成后再次调用 get_variables,刷新 ${T}/variable/{documentId}.json。`}],isError:!p}}},Ve,Te,Xe,tt,nt,dt,at,st,ct],Gt=v.enabledTools?Jt.filter(e=>v.enabledTools.includes(e.name)):Jt;const qt=new e({name:v.serverName,version:"1.0.23"},{instructions:Se,capabilities:{resources:{},tools:{},prompts:{}}});var zt;zt=qt,Gt.forEach(e=>{zt.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema},e.handler)});try{const e=new t;await qt.connect(e)}catch(e){process.exit(1)}
|
package/dist/page-generate.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## 角色与任务
|
|
4
4
|
你是一个双核运行系统,必须按顺序执行:
|
|
5
|
-
1. **顶级视觉架构师**:以 Dribbble/Behance
|
|
5
|
+
1. **顶级视觉架构师**:以 Dribbble/Behance 级商业审美,输出高完成度信息架构。
|
|
6
6
|
2. **MasterGo 协议编译器**:将视觉方案严格转译为可逆向解析代码,非标即失败。
|
|
7
7
|
|
|
8
8
|
## 最终产出硬标准
|
|
9
|
-
1.
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
13
|
-
5.
|
|
14
|
-
6.
|
|
15
|
-
7.
|
|
9
|
+
1. 页面代码必须在根节点内输出,整页优先 `<main data-name="...">`。
|
|
10
|
+
2. 必须使用 Tailwind Utility + Arbitrary Values,如 `w-[320px]`、`bg-[#F5F5F5]`。
|
|
11
|
+
3. 禁止依赖主题默认值:禁止 `w-1/2`、`bg-red-500` 等。
|
|
12
|
+
4. 每个标签必须包含语义化 `data-name="..."`。
|
|
13
|
+
5. 图片统一语义:`<img src="{{keyword}}" />`。
|
|
14
|
+
6. 图标统一使用 FontAwesome:`<i class="fas/far/fab fa-xxx text-[size] text-[#hex]">`。
|
|
15
|
+
7. 语境自适应与双轨渲染:底层视觉思维必须调用顶级英文设计模式(如 Bento, Glassmorphism, Swiss Minimalist)。**除非用户明确要求生成英文或其他语言**,否则默认界面可见文本必须采用**高质量的本土商业化中文文案**。
|
|
16
16
|
|
|
17
17
|
## 工作流与输出格式(强制)
|
|
18
18
|
1. 先完成「构思与决策」:需求重构分析 + 视觉与架构策略。
|
|
@@ -27,12 +27,6 @@
|
|
|
27
27
|
- D. **关怀与共鸣**(社区/生活):低饱和自然色,圆角与柔和阴影。
|
|
28
28
|
- E. **沉浸与表现**(娱乐/消费):拟物材质,情绪化强对比排版。
|
|
29
29
|
|
|
30
|
-
## 商业冗余与防塌陷约束
|
|
31
|
-
1. 导航/侧边功能项不少于 5 项,底部必须有账户或设置区。
|
|
32
|
-
2. 列表/看板单容器节点数控制在 4-8 个。
|
|
33
|
-
3. 页面内至少出现 3 种业务状态(如进行中/失败/完成)。
|
|
34
|
-
4. 禁止占位词:`Title`、`Item 1`、`Test`。
|
|
35
|
-
|
|
36
30
|
## 系统一致性约束
|
|
37
31
|
1. 间距与内边距仅可使用:`8/12/16/20/24/32/40`。
|
|
38
32
|
2. 圆角建议从 `rounded-[12px]` 起,使用离散值,不用随机数。
|
|
@@ -48,8 +42,8 @@
|
|
|
48
42
|
|
|
49
43
|
## 图层原子化协议
|
|
50
44
|
1. `<div>` 只做容器(背景/边框/阴影/布局)。
|
|
51
|
-
2. `<p>`
|
|
52
|
-
3. `<span>`
|
|
45
|
+
2. `<p>` 用于所有可能换行的文本,包括长标题、摘要、正文与说明,且需有宽度约束。
|
|
46
|
+
3. `<span>` 只用于绝对不换行的短文本;只要文本有换行可能,就必须改用 `<p>`。
|
|
53
47
|
4. 文本标签必须显式声明 `text-[size]`、`leading-[size]`、`font-[weight]`、`text-[#Hex]`、`text-left\center\right`。
|
|
54
48
|
5. 图标场景禁止手绘 `div/svg/path`(除非用户明确要求自定义 SVG)。
|
|
55
49
|
6. 禁用原生表单:`input/select/textarea/button/form`,改用静态容器模拟。
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# 变量生成规则(写变量)
|
|
2
|
+
|
|
3
|
+
用于 `get_variables` / `update_variables` / `agent_remove_variable`。
|
|
4
|
+
|
|
5
|
+
## 规则边界(先读)
|
|
6
|
+
- 本文档只定义「如何创建 / 修改 / 删除变量」。
|
|
7
|
+
- 不涉及「如何在页面 HTML 中引用变量」—— 那部分见 `variable-import.md`。
|
|
8
|
+
|
|
9
|
+
## 流程
|
|
10
|
+
|
|
11
|
+
1. `get_variables` 落盘 `{{docDir}}/variable/{documentId}.json`
|
|
12
|
+
2. 读取并修改落盘数组
|
|
13
|
+
3. 创建整套变量时分两批:
|
|
14
|
+
- 第一批:基础变量
|
|
15
|
+
- 第二批:语义变量
|
|
16
|
+
4. 每次 `update_variables` 后都重新 `get_variables`
|
|
17
|
+
|
|
18
|
+
禁止把“基础变量”和“引用基础变量的语义变量”放在同一批提交。
|
|
19
|
+
|
|
20
|
+
## 数据格式
|
|
21
|
+
|
|
22
|
+
只生成数组:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
"collection": "基础",
|
|
28
|
+
"name": "基础色板/蓝色/600",
|
|
29
|
+
"type": "COLOR",
|
|
30
|
+
"mode": [{ "name": "默认", "value": "#2563eb" }]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
修改已有变量必须保留 `id`;传 `id + name` 可重命名。
|
|
36
|
+
|
|
37
|
+
## Collection 结构
|
|
38
|
+
|
|
39
|
+
默认只创建两个 collection:
|
|
40
|
+
|
|
41
|
+
```txt
|
|
42
|
+
基础
|
|
43
|
+
语义
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
类型和用途写进 `name`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{ "collection": "基础", "name": "基础色板/蓝色/600" }
|
|
50
|
+
{ "collection": "基础", "name": "规范尺寸/8" }
|
|
51
|
+
{ "collection": "语义", "name": "颜色/填充/操作/主要" }
|
|
52
|
+
{ "collection": "语义", "name": "间距/组件/中" }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 类型 type
|
|
56
|
+
|
|
57
|
+
基础变量:
|
|
58
|
+
|
|
59
|
+
- `COLOR`
|
|
60
|
+
- `NUMBER`
|
|
61
|
+
- `STRING`
|
|
62
|
+
- `BOOLEAN`
|
|
63
|
+
|
|
64
|
+
复合变量(语义变量):
|
|
65
|
+
|
|
66
|
+
- `PAINT`
|
|
67
|
+
- `TEXT`
|
|
68
|
+
- `EFFECT`
|
|
69
|
+
- `GRID`
|
|
70
|
+
- `STROKE_WIDTH`
|
|
71
|
+
- `RADIUS`
|
|
72
|
+
- `PADDING`
|
|
73
|
+
|
|
74
|
+
新建变量必须显式写 `type`。
|
|
75
|
+
|
|
76
|
+
## 颜色
|
|
77
|
+
|
|
78
|
+
基础色板:
|
|
79
|
+
|
|
80
|
+
- `collection: "基础"`
|
|
81
|
+
- `type: "COLOR"`
|
|
82
|
+
- `name: "基础色板/..."`
|
|
83
|
+
- 只写 `value`
|
|
84
|
+
- 通常只用 `默认` mode
|
|
85
|
+
- 如果用户未提出要求,建议创建 蓝色、灰色、绿色、红色、橙色、紫色
|
|
86
|
+
- 每个颜色都要使用 10 个色阶
|
|
87
|
+
|
|
88
|
+
示例:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
"collection": "基础",
|
|
94
|
+
"name": "基础色板/蓝色/600",
|
|
95
|
+
"type": "COLOR",
|
|
96
|
+
"mode": [{ "name": "默认", "value": "#2563eb" }]
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
语义颜色:
|
|
102
|
+
|
|
103
|
+
- `collection: "语义"`
|
|
104
|
+
- `type: "PAINT"`
|
|
105
|
+
- `name: "颜色/..."`
|
|
106
|
+
- 必须用 `reference` 引用基础色板
|
|
107
|
+
- 推荐同时创建 `亮色` / `暗色` 两个模式
|
|
108
|
+
|
|
109
|
+
示例: 重新 `get_variables` 获取到基础色板的 `name`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
"collection": "语义",
|
|
115
|
+
"name": "颜色/填充/操作/主要",
|
|
116
|
+
"type": "PAINT",
|
|
117
|
+
"mode": [
|
|
118
|
+
{ "name": "亮色", "reference": "基础色板/蓝色/600" },
|
|
119
|
+
{ "name": "暗色", "reference": "基础色板/蓝色/400" }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 尺寸
|
|
126
|
+
|
|
127
|
+
基础尺寸:
|
|
128
|
+
|
|
129
|
+
- `collection: "基础"`
|
|
130
|
+
- `type: "NUMBER"`
|
|
131
|
+
- `name: "规范尺寸/..."`
|
|
132
|
+
- 通常只用 `默认` mode
|
|
133
|
+
- 优先使用 2 的倍数 (包含 2)
|
|
134
|
+
- 如果用户未提出要求,建议一并创建 规范尺寸、规范字号、规范圆角
|
|
135
|
+
|
|
136
|
+
示例:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
"collection": "基础",
|
|
142
|
+
"name": "规范尺寸/8",
|
|
143
|
+
"type": "NUMBER",
|
|
144
|
+
"mode": [{ "name": "默认", "value": 8 }]
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
语义尺寸:
|
|
150
|
+
|
|
151
|
+
- 不要创建任何语义尺寸
|
|
152
|
+
|
|
153
|
+
## 引用
|
|
154
|
+
|
|
155
|
+
- `reference` 写变量名字符串,不写 `var(...)`
|
|
156
|
+
- 引用目标必须已经存在
|
|
157
|
+
- 变量名不唯一时,改用 `{ "id": "..." }`
|
|
158
|
+
|
|
159
|
+
## 删除
|
|
160
|
+
|
|
161
|
+
删除变量是危险操作,必须使用单独的 `agent_remove_variable` 工具,并优先传 `id`:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"id": "7:07131"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 自检
|
|
170
|
+
|
|
171
|
+
- 已读取最新 `{{docDir}}/variable/{documentId}.json`
|
|
172
|
+
- 基础和语义已分批
|
|
173
|
+
- 默认只用 `基础` / `语义` 两个 collection
|
|
174
|
+
- 修改已有变量保留 `id`
|
|
175
|
+
- 删除变量只调用 `agent_remove_variable`,不要在 `update_variables` 中写 `deleteVariable`
|
|
176
|
+
- 每个 `mode` 都有 `name`
|
|
177
|
+
- 同一个 mode 不同时写 `value` 和 `reference`
|
|
178
|
+
- 新建变量显式写 `type`
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 变量引用规则(读变量)
|
|
2
|
+
|
|
3
|
+
## 规则边界(先读)
|
|
4
|
+
- 本文档只定义「如何在页面 HTML 中引用已存在的变量」。
|
|
5
|
+
- 不涉及变量的创建 / 修改 / 删除 —— 那部分见 `variable-generate.md`。
|
|
6
|
+
- 本文档必须与 `page-generate.md` 的全部硬规则同时满足;若冲突以 `page-generate.md` 为最高优先级。
|
|
7
|
+
|
|
8
|
+
## 模式入口
|
|
9
|
+
变量来源有且只有两种,由 design 工具的上下文明确告知:
|
|
10
|
+
|
|
11
|
+
1. 「使用当前文件设计系统」:变量来源为 `{{docDir}}/variable/{documentId}.json`
|
|
12
|
+
2. 「使用组件库」:变量来源为 `{{docDir}}/library/<team>/variable.json`
|
|
13
|
+
|
|
14
|
+
两种来源 JSON 结构一致,引用写法一致;下面统称「变量清单 JSON」。
|
|
15
|
+
|
|
16
|
+
## 数据来源(唯一事实)
|
|
17
|
+
- 变量清单 JSON 路径由 design 工具上下文给出,禁止臆造路径。
|
|
18
|
+
- 每个变量项的关键字段:
|
|
19
|
+
- `name`:变量名,**唯一用于 `var(...)` 引用的字段**
|
|
20
|
+
- `value` / `mode[].value`:仅供你理解语义,**禁止直接抄到 class 上**
|
|
21
|
+
- 引用时只能使用变量 `name`,禁止改写、禁止用 `id`、禁止用 `value`。
|
|
22
|
+
|
|
23
|
+
示例(变量项):
|
|
24
|
+
```json
|
|
25
|
+
{ "name": "色彩/brand/primary", "value": "#1677FF" }
|
|
26
|
+
{ "name": "间距/index_10/sm", "value": "8px" }
|
|
27
|
+
{ "name": "圆角/index_6/md", "value": "6px" }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 引用写法
|
|
31
|
+
统一格式 `var(变量 name)`,放入 Tailwind 任意值语法。按属性分类:
|
|
32
|
+
|
|
33
|
+
### 颜色
|
|
34
|
+
- 背景:`bg-[var(色彩/brand/primary)]`
|
|
35
|
+
- 文本:`text-[var(色彩/文本/主)]`
|
|
36
|
+
- 边框:`border-[var(色彩/边框/默认)]`
|
|
37
|
+
- 图标颜色:`text-[var(色彩/图标/常规)]`
|
|
38
|
+
|
|
39
|
+
### 文字
|
|
40
|
+
- 字号:`text-[var(文字/正文/14)]`
|
|
41
|
+
- 行高:`leading-[var(行高/正文/1.5)]`
|
|
42
|
+
- 字重:`font-[var(字重/medium)]`
|
|
43
|
+
|
|
44
|
+
### 间距
|
|
45
|
+
- 容器间距:`gap-[var(间距/index_10/sm)]`
|
|
46
|
+
- 内边距:`p-[var(间距/index_12/md)]`
|
|
47
|
+
- 单边内边距:`px-[var(间距/index_16/lg)]` / `py-[var(间距/index_8/sm)]`
|
|
48
|
+
|
|
49
|
+
### 圆角
|
|
50
|
+
- 容器圆角:`rounded-[var(圆角/index_6/md)]`
|
|
51
|
+
|
|
52
|
+
### 边框宽度
|
|
53
|
+
- `border-[var(边框/常规)]`
|
|
54
|
+
|
|
55
|
+
### 阴影
|
|
56
|
+
- `shadow-[var(阴影/卡片/默认)]`
|
|
57
|
+
|
|
58
|
+
完整示例:
|
|
59
|
+
```html
|
|
60
|
+
<div data-name="filter-wrap" class="flex flex-row justify-start items-center gap-[var(间距/index_10/sm)] p-[var(间距/index_12/md)] bg-[var(色彩/填充/容器)] rounded-[var(圆角/index_6/md)]">
|
|
61
|
+
<span data-name="filter-label" class="text-[var(文字/正文/14)] leading-[var(行高/正文/1.5)] font-[var(字重/medium)] text-[var(色彩/文本/主)]">筛选条件</span>
|
|
62
|
+
</div>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 强约束
|
|
66
|
+
1. 凡能在变量清单 JSON 里匹配到的样式属性,**必须**使用 `var(...)`。
|
|
67
|
+
2. 禁止把 `value` 直接写进 class(例如 `bg-[#1677FF]`、`gap-[8px]`、`rounded-[6px]`)。
|
|
68
|
+
3. `var(...)` 内只允许写变量 `name` 原文,禁止改写大小写 / 中英文 / 分隔符。
|
|
69
|
+
4. 找不到精确匹配 → 按语义最近邻选择(如 `色彩/文本/次` 缺失时可考虑 `色彩/文本/弱`)。
|
|
70
|
+
5. 都匹配不上才允许该属性局部回退字面值,并在自检中标注「属性 + 缺失变量原因」。
|
|
71
|
+
|
|
72
|
+
## 自检(输出前必走查)
|
|
73
|
+
- [ ] 已读取变量清单 JSON 全部 `name`
|
|
74
|
+
- [ ] 颜色(背景 / 文本 / 边框 / 图标)全部走查
|
|
75
|
+
- [ ] 文字(字号 / 行高 / 字重)全部走查
|
|
76
|
+
- [ ] 间距(gap / padding / 四向 padding)全部走查
|
|
77
|
+
- [ ] 圆角 / 边框宽度 / 阴影 全部走查
|
|
78
|
+
- [ ] 每个回退字面值的位置都已标注缺失原因
|
|
79
|
+
- [ ] 所有 `var(...)` 内 `name` 与落盘 JSON 完全一致,未改写
|