@fui-org/fui-cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fui.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{parseArgs as Qh}from"util";import{chmod as wu}from"fs/promises";import{homedir as yu}from"os";import{join as St,resolve as bu}from"path";var Oe={Ok:0,Runtime:1,Usage:2,Guard:3};class f extends Error{name="UsageError"}class $e extends Error{severity;logKind;name="GuardError";constructor(e,t="hard",n="guard"){super(e);this.severity=t;this.logKind=n}}class lt extends Error{report;name="ReportedFailure";constructor(e,t){super(e);this.report=t}}function Ms(e){if(e instanceof f)return Oe.Usage;if(e instanceof $e)return Oe.Guard;return Oe.Runtime}var no=["workdir","api-base","api-token","auto-update"];function $n(e){return e.HOME||yu()}function Wt(e,t=Bun.env,n=process.cwd()){let o=e.trim();if(o==="~")return $n(t);if(o.startsWith("~/"))return St($n(t),o.slice(2));return bu(n,o)}function Ze(e=Bun.env){let t=e.XDG_CONFIG_HOME||St($n(e),".config");return St(t,"fui","config.json")}async function ut(e=Bun.env){let t=Ze(e),n=Bun.file(t);if(!await n.exists())return{};try{let o=await n.json();if(o&&typeof o==="object"&&!Array.isArray(o))return o}catch{}throw new f(`${t} is not a JSON object; fix or delete it`)}async function Ko(e,t=Bun.env){let n=Ze(t);return await Bun.write(n,`${JSON.stringify(e,null,2)}
4
- `),await wu(n,384),n}function ku(e){return e.CLAUDE_CONFIG_DIR?St(e.CLAUDE_CONFIG_DIR,".claude.json"):St($n(e),".claude.json")}function $u(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function Fs(e,t){if(!e||typeof e!=="object")return;for(let[n,o]of Object.entries(e)){let s=o?.env?.[t];if(typeof s==="string"&&s.trim()&&$u(n,o))return s}return}async function Us(e,t=Bun.env,n=process.cwd()){let o=ku(t),s=Bun.file(o);if(!await s.exists())return null;let r;try{r=await s.json()}catch{return null}let i=Fs(r.projects?.[n]?.mcpServers,e)??Fs(r.mcpServers,e);return i?{value:i,file:o}:null}async function vu(e=Bun.env,t=process.cwd()){let n=await Us("FUI_MCP_WORKDIR",e,t);return n?{path:Wt(n.value,e,t),file:n.file}:null}var Go=St(".fui-mcp","workspaces");async function Bs(e,t=Bun.env,n=process.cwd()){if(e)return{path:Wt(e,t,n),source:"flag",detail:"--workdir"};if(t.FUI_MCP_WORKDIR)return{path:Wt(t.FUI_MCP_WORKDIR,t,n),source:"env",detail:"FUI_MCP_WORKDIR"};let o=await ut(t);if(typeof o.workdir==="string"&&o.workdir)return{path:Wt(o.workdir,t,n),source:"config",detail:Ze(t)};let s=await vu(t,n);if(s)return{path:s.path,source:"mcp",detail:s.file};return{path:St($n(t),Go),source:"default",detail:"built-in"}}async function Ls(e,t,n,o){let s=n[e];if(s)return{value:s,source:"env",detail:e};let i=(await ut(n))[t];if(typeof i==="string"&&i)return{value:i,source:"config",detail:Ze(n)};let a=await Us(e,n,o);if(a)return{value:a.value,source:"mcp",detail:a.file};return{value:void 0,source:"missing",detail:`set ${e} or run: fui config set ${t} ...`}}async function oo(e=Bun.env,t=process.cwd()){let[n,o]=await Promise.all([Ls("FUI_API_BASE","api-base",e,t),Ls("FUI_API_TOKEN","api-token",e,t)]);return{base:n,token:o}}function Hs(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(Hs(e))process.exit(0);throw e});function k(e){try{process.stdout.write(e)}catch(t){if(Hs(t))process.exit(0);throw t}}function E(e){process.stderr.write(e.endsWith(`
3
+ import{parseArgs as wg}from"util";import{chmod as Pu}from"fs/promises";import{homedir as Tu}from"os";import{join as $t,resolve as Eu}from"path";var Oe={Ok:0,Runtime:1,Usage:2,Guard:3};class h extends Error{name="UsageError"}class ve extends Error{severity;logKind;name="GuardError";constructor(e,t="hard",n="guard"){super(e);this.severity=t;this.logKind=n}}class ut extends Error{report;name="ReportedFailure";constructor(e,t){super(e);this.report=t}}function Ls(e){if(e instanceof h)return Oe.Usage;if(e instanceof ve)return Oe.Guard;return Oe.Runtime}var no=["workdir","api-base","api-token","auto-update"];function vn(e){return e.HOME||Tu()}function qt(e,t=Bun.env,n=process.cwd()){let o=e.trim();if(o==="~")return vn(t);if(o.startsWith("~/"))return $t(vn(t),o.slice(2));return Eu(n,o)}function et(e=Bun.env){let t=e.XDG_CONFIG_HOME||$t(vn(e),".config");return $t(t,"fui","config.json")}async function dt(e=Bun.env){let t=et(e),n=Bun.file(t);if(!await n.exists())return{};try{let o=await n.json();if(o&&typeof o==="object"&&!Array.isArray(o))return o}catch{}throw new h(`${t} is not a JSON object; fix or delete it`)}async function Go(e,t=Bun.env){let n=et(t);return await Bun.write(n,`${JSON.stringify(e,null,2)}
4
+ `),await Pu(n,384),n}function Nu(e){return e.CLAUDE_CONFIG_DIR?$t(e.CLAUDE_CONFIG_DIR,".claude.json"):$t(vn(e),".claude.json")}function Au(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function Us(e,t){if(!e||typeof e!=="object")return;for(let[n,o]of Object.entries(e)){let s=o?.env?.[t];if(typeof s==="string"&&s.trim()&&Au(n,o))return s}return}async function Bs(e,t=Bun.env,n=process.cwd()){let o=Nu(t),s=Bun.file(o);if(!await s.exists())return null;let r;try{r=await s.json()}catch{return null}let i=Us(r.projects?.[n]?.mcpServers,e)??Us(r.mcpServers,e);return i?{value:i,file:o}:null}async function _u(e=Bun.env,t=process.cwd()){let n=await Bs("FUI_MCP_WORKDIR",e,t);return n?{path:qt(n.value,e,t),file:n.file}:null}var zo=$t(".fui-mcp","workspaces");async function qs(e,t=Bun.env,n=process.cwd()){if(e)return{path:qt(e,t,n),source:"flag",detail:"--workdir"};if(t.FUI_MCP_WORKDIR)return{path:qt(t.FUI_MCP_WORKDIR,t,n),source:"env",detail:"FUI_MCP_WORKDIR"};let o=await dt(t);if(typeof o.workdir==="string"&&o.workdir)return{path:qt(o.workdir,t,n),source:"config",detail:et(t)};let s=await _u(t,n);if(s)return{path:s.path,source:"mcp",detail:s.file};return{path:$t(vn(t),zo),source:"default",detail:"built-in"}}async function Hs(e,t,n,o){let s=n[e];if(s)return{value:s,source:"env",detail:e};let i=(await dt(n))[t];if(typeof i==="string"&&i)return{value:i,source:"config",detail:et(n)};let a=await Bs(e,n,o);if(a)return{value:a.value,source:"mcp",detail:a.file};return{value:void 0,source:"missing",detail:`set ${e} or run: fui config set ${t} ...`}}async function oo(e=Bun.env,t=process.cwd()){let[n,o]=await Promise.all([Hs("FUI_API_BASE","api-base",e,t),Hs("FUI_API_TOKEN","api-token",e,t)]);return{base:n,token:o}}function Ws(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(Ws(e))process.exit(0);throw e});function k(e){try{process.stdout.write(e)}catch(t){if(Ws(t))process.exit(0);throw t}}function T(e){process.stderr.write(e.endsWith(`
5
5
  `)?e:`${e}
6
- `)}async function ro(){return Bun.stdin.text()}async function vn(e,t){if(t&&e)throw new f("pass SQL as an argument or with -f, not both");let n;if(t){let o=Bun.file(t);if(!await o.exists())throw new f(`file not found: ${t}`);n=await o.text()}else if(e==="-")n=await ro();else if(e!==void 0)n=e;else throw new f('missing SQL: pass "<sql>", -f <file>, or - for stdin');if(!n.trim())throw new f("SQL is empty");return n}async function Pe(e,t,n){if(e!==void 0&&t!==void 0)throw new f(`pass ${n} or ${n}-env, not both`);if(t!==void 0){let s=Bun.env[t];if(!s)throw new f(`env var ${t} is empty or unset`);return s.trim()}if(e===void 0)return;if(e!=="-")throw new f(`${n} takes "-" (stdin) only; use ${n}-env VAR for env vars`);let o=(await ro()).trim();if(!o)throw new f(`${n}: stdin is empty`);return o}var Ws=60000,qs=400;function Js(e){return e.length<=qs?e:`${e.slice(0,qs)}\u2026`}async function Su(e){let t;try{t=(await e.text()).trim()}catch{return""}if(!t)return"";try{let n=JSON.parse(t);for(let o of["Message","message","error","ExceptionMessage"]){let s=n?.[o];if(typeof s==="string"&&s.trim())return` \u2014 ${Js(s.trim())}`}}catch{}return` \u2014 ${Js(t)}`}function et(e){return e.data||[]}class Vs{base;token;constructor(e,t){this.base=e;this.token=t}async request(e,t){let n;try{n=await fetch(`${this.base}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8",Authorization:`Bearer ${this.token}`},body:t!==void 0?JSON.stringify(t):"{}",signal:AbortSignal.timeout(Ws)})}catch(o){let s=o?.name;if(s==="TimeoutError"||s==="AbortError")throw Error(`FUI API timeout: ${e} did not answer within ${Ws/1000}s`);throw Error(`FUI API network error: ${e} \u2014 ${o?.message??String(o)}`)}if(!n.ok)throw Error(`FUI API error: ${n.status} ${n.statusText}${await Su(n)}`);return await n.json()}async listProjects(){return et(await this.request("/fp/FUI_Dev_ProjectList"))}editProject(e){return this.request("/fp/FUI_Dev_ProjectEdit",{jsonObj:JSON.stringify(e)})}getProjectJson(e){return this.request(`/fp/obj/FUI_Dev_ProjectJson/${e}`,{})}async listProjectUserRights(e){return et(await this.request("/fp/FUI_Dev_UserRightList",{PID:e,MID:""}))}async searchUsers(e,t){return et(await this.request("/fp/FUI_Dev_UserSearch",{textsearch:e,useridlike:t}))}updateProjectUserRight(e,t,n){return this.request("/fp/FUI_Dev_UserRight_Update",{PID:e,MID:"",UserRight:t,UserList:n})}async getRuntimeModuleVersion(e,t,n){return et(await this.request("/fp/FUI_Runtime_ModuleVersionSelect",{domain:e,url1_ID:t,url2_ID:n}))}async listModules(e){return et(await this.request("/fp/FUI_Dev_modulelist",{pid:e}))}editModule(e,t){return this.request("/fp/FUI_Dev_ModuleEdit",{ProjectID:e,ModuleObj:JSON.stringify(t)})}deleteModule(e,t){return this.request("/fp/FUI_Dev_moduleDelete",{ProjectID:e,ModuleID:t})}async getModuleContent(e,t){let o=(await this.request(`/fp/obj/FUI_Dev_moduledata/module-load/${e}/${t}`,{})).data;if(!o.mHeader&&!o.mBody)E(`# warning: module-load ${e}/${t} returned empty mHeader and mBody`);return o}getModuleUI(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-ui/${e}/${t}`,{})}getModuleHtml(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-html/${e}/${t}`,{})}async getModuleScript(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-code/${e}/${t}`,{})).data?.FileContent||""}async getModuleCSS(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-css/${e}/${t}`,{})).data?.FileContent||""}updateModule(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-save/${e}/${t}`,n)}updateModuleUI(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-ui/${e}/${t}`,{FileContent:n})}updateModuleScript(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-code/${e}/${t}`,{FileContent:n})}updateModuleCSS(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-css/${e}/${t}`,{FileContent:n})}updateProjectData(e,t){return this.request(`/fp/obj/FUI_Dev_editorupdate/project/${e}/-`,{FileContent:JSON.stringify(t)})}async getModuleComponents(e,t){return et(await this.request("/fp/FUI_Dev_ComponentSelect",{ProjectID:e,...t&&{ModuleID:t}}))}updateComponent(e){return this.request("/fp/FUI_Dev_ComponentUpdate",e)}async getModuleDependencies(e,t){return et(await this.request("/fp/FUI_Dev_File/select",{ProjectID:e,ModuleID:t}))}async listFrameworkImportFiles(e){return et(await this.request("/fp/FUI_Dev_File/select",{frameWork:e}))}async listImportFiles(e,t){return et(await this.request("/fp/FUI_Dev_File/select",{...e&&{ProjectID:e},...t&&{ModuleID:t}}))}getFileContent(e){return this.request(`/fp/obj/FUI_Dev_File/getcontent/${e}`,{})}uploadFileContent(e,t){return this.request(`/fp/FUI_Dev_File/uploadcontent/${e}`,{FileContent:t})}insertImportFile(e){return this.request("/fp/FUI_Dev_File/insert",e)}updateImportFile(e,t){return this.request(`/fp/FUI_Dev_File/update/${e}`,t)}deleteImportFile(e){return this.request(`/fp/FUI_Dev_File/delete/${e}`,{})}}async function so(){let{base:e,token:t}=await oo();if(!e.value||!t.value){let n=[!e.value&&"FUI_API_BASE",!t.value&&"FUI_API_TOKEN"].filter(Boolean).join(", ");throw new f(`missing ${n}; set the env var, run fui config set api-base <url> / fui config set api-token -, or configure the Fui MCP server`)}return new Vs(e.value,t.value)}import{mkdir as Iu,rename as Cu}from"fs/promises";import{homedir as Ru}from"os";import{join as zo}from"path";var ju=new TextDecoder("utf-8",{ignoreBOM:!0});async function H(e){return ju.decode(await Bun.file(e).bytes())}async function pe(e){try{return await H(e)}catch{return null}}function Sn(e){return e?.code}var Pu=/^[A-Za-z0-9_-]+$/;function Eu(e=Bun.env){return e.FUI_MCP_SESSION_DIR||zo(e.HOME||Ru(),".fui-mcp","session")}function Tu(e=Bun.env){let t=e.FUI_SESSION||"default";if(!Pu.test(t))throw new f(`invalid FUI_SESSION "${t}": letters, digits, "_" or "-"`);return zo(Eu(e),`cli-${t}.json`)}class jn{file;constructor(e=Tu()){this.file=e}async read(){let e=await pe(this.file);if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async write(e){await Iu(zo(this.file,".."),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;await Bun.write(t,JSON.stringify(e,null,2)),await Cu(t,this.file)}async update(e){let t=await this.read();return e(t),await this.write(t),t}async getTarget(){return(await this.read()).target??null}async setTarget(e){let t={projectId:e.projectId,moduleId:e.moduleId,moduleDir:e.moduleDir,workspaceRoot:e.workspaceRoot,...e.target!==void 0?{target:e.target}:{}};await this.update((n)=>{n.target=t})}async clearTarget(){await this.update((e)=>{delete e.target})}async createApproval(e){let t={...e,token:crypto.randomUUID(),createdAt:new Date().toISOString()};return await this.update((n)=>{n.approval=t}),t}async getApproval(){return(await this.read()).approval??null}async clearApproval(){await this.update((e)=>{delete e.approval})}async markSkillLoaded(){await this.update((e)=>{e.skillLoadedAt=new Date().toISOString()})}async wasSkillLoaded(){return Boolean((await this.read()).skillLoadedAt)}async markSpVerified(e){await this.update((t)=>{let n=new Set(t.spVerified??[]);for(let o of e)n.add(o.toLowerCase());t.spVerified=[...n]})}async getVerifiedSpNames(){return new Set((await this.read()).spVerified??[])}}function qt(e=Bun.env){return["1","true","yes"].includes((e.FUI_MCP_STRICT??"").trim().toLowerCase())}import{readdir as Au}from"fs/promises";import{isAbsolute as Nu,join as Yo,relative as _u,sep as Du}from"path";var xu=/^[A-Za-z0-9._ -]+$/,Xo=20;function tt(e,t){if(!e||!xu.test(e)||/^\.+$/.test(e))throw new f(`invalid ${t} ${JSON.stringify(e)}: letters, digits, ".", "_", "-" and spaces only (used as a workspace folder name)`);return e}function Ou(e,t){let n=_u(e,t);if(!n||n.startsWith("..")||Nu(n))return null;let[o,s,r]=n.split(Du);if(!o||o.startsWith("_")||o.startsWith("."))return null;if(s==="modules"&&r)return{projectId:o,moduleId:r,moduleDir:Yo(e,o,"modules",r)};return{projectId:o}}async function Ks(e,t){try{return(await Au(e,{withFileTypes:!0})).filter((o)=>o.isDirectory()&&!(t&&(o.name.startsWith("_")||o.name.startsWith(".")))).map((o)=>o.name).sort()}catch{return[]}}function Gs(e){return e.length>Xo?[...e.slice(0,Xo),` ... and ${e.length-Xo} more`]:e}class Qo{paths;session;cwd;constructor(e,t,n=process.cwd()){this.paths=e;this.session=t;this.cwd=n}cwdTarget(){return Ou(this.paths.root,this.cwd)}async contextProjectId(){return this.cwdTarget()?.projectId??(await this.session.getTarget())?.projectId??void 0}async resolveProject(e){if(e)return{projectId:tt(e,"projectId"),source:"explicit"};let t=this.cwdTarget();if(t)return{projectId:t.projectId,source:"cwd"};let n=await this.session.getTarget();if(n?.projectId)return{projectId:n.projectId,source:"session"};throw new f(await this.noTargetMenu("project"))}async resolveProjectModule(e,t){if(e&&t)return tt(e,"projectId"),tt(t,"moduleId"),{projectId:e,moduleId:t,moduleDir:Yo(this.paths.root,e,"modules",t),source:"explicit"};if(e||t)throw new f("module commands need both -p <project> and -m <module>");let n=this.cwdTarget();if(n?.moduleId&&n.moduleDir)return{projectId:n.projectId,moduleId:n.moduleId,moduleDir:n.moduleDir,source:"cwd"};let o=await this.session.getTarget();if(o?.projectId&&o.moduleId&&o.moduleDir)return{projectId:o.projectId,moduleId:o.moduleId,moduleDir:o.moduleDir,source:"session"};throw new f(await this.noTargetMenu("module"))}async resolveModuleDir(e,t,n){if(e)return e;return(await this.resolveProjectModule(t,n)).moduleDir}async noTargetMenu(e){let t=await Ks(this.paths.root,!0),n=e==="project"?"no project: pass -p <project>, cd into a workspace project, or run: fui use <project>/<module>":"no module: pass -p <project> -m <module>, cd into a module folder, or run: fui use <project>/<module>";if(t.length===0)return`${n}
7
- workspace ${this.paths.root} has no projects; run: fui project list`;if(e==="module"&&t.length===1){let o=await Ks(Yo(this.paths.root,t[0],"modules"),!1);if(o.length>0)return[n,...Gs(o.map((s)=>` fui use ${t[0]}/${s}`))].join(`
8
- `)}return[n,...Gs(t.map((o)=>` -p ${o}`))].join(`
9
- `)}}function Zo(e){return new TextEncoder().encode(e).toBase64()}function In(e){try{return new TextDecoder("utf-8",{fatal:!0}).decode(Uint8Array.fromBase64(e.trim()))}catch{throw new f("token is not valid Base64 UTF-8")}}function Jt(e,t,n,o){return Zo(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function zs(e){let t=new Map;for(let n of e.split(";")){let o=n.indexOf("=");if(o<=0)continue;t.set(n.slice(0,o).trim().toLowerCase(),n.slice(o+1).trim())}return t}function Mu(e){let t=e.trim();if(/^postgres(?:ql)?:\/\//i.test(t))return"postgres";if(/^(?:mysql|mariadb):\/\//i.test(t))return"mysql";if(/^(?:sqlite|file):/i.test(t))return"sqlite";let n=zs(t);if(n.has("server")||n.has("data source"))return"mssql";throw new f("unsupported connection: expected postgres://, mysql://, mariadb://, sqlite:/file: URL or MSSQL Server=...; string")}function Cn(e){let t=In(e);return{engine:Mu(t),plain:t}}function io(e){let{engine:t,plain:n}=Cn(e);if(t==="mssql"){let s=zs(n);return{engine:t,server:s.get("server")??s.get("data source")??"",database:s.get("database")??s.get("initial catalog")??"",user:s.get("uid")??s.get("user id")??""}}if(t==="sqlite")return{engine:t,server:"",database:n.replace(/^(?:sqlite|file):(?:\/\/)?/i,""),user:""};let o=new URL(n);return{engine:t,server:o.port?`${o.hostname}:${o.port}`:o.hostname,database:decodeURIComponent(o.pathname.replace(/^\//,"")),user:decodeURIComponent(o.username)}}import{join as Ju}from"path";import{mkdir as Fu,readdir as Lu,writeFile as Uu}from"fs/promises";import{basename as Xs,dirname as Qs,extname as Zs,isAbsolute as Bu,join as er,relative as ei,sep as Hu}from"path";var Ys=50;function ao(e=new Date){let t=(n)=>String(n).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}_${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}function Wu(e,t,n={}){let o=ei(e.root,t),r=o!==""&&!o.startsWith("..")&&!Bu(o)?o:er("_external",Xs(t)),i=Zs(r),a=Xs(r,i),c=n.stamp??ao(),l=n.suffix?`_${n.suffix}`:"";return er(e.historyRoot(),Qs(r),`${a}_${c}${l}${i}`)}async function qu(e,t){let n=Zs(e),o=e.slice(0,e.length-n.length);for(let r=1;r<=Ys;r++){let i=r===1?e:`${o}_${r}${n}`;try{return await Uu(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(a){if(Sn(a)!=="EEXIST")throw a}}let s=`${o}_${Ys}${n}`;return await Bun.write(s,t),s}async function he(e,t,n={}){let o=n.content;if(o===void 0)try{o=await H(t)}catch(r){let i=Sn(r);if(i==="ENOENT"||i==="ENOTDIR")return null;throw r}let s=Wu(e,t,n);return await Fu(Qs(s),{recursive:!0}),qu(s,o)}function jt(e,t){let n=ei(e.root,t);return(n.startsWith("..")?t:n).split(Hu).join("/")}async function co(e,t,n={}){let o=n.stamp??ao(),s=new Set(n.skipDirNames??["_preview"]),r=[];async function i(a){let c;try{c=await Lu(a,{withFileTypes:!0})}catch{return}for(let l of c){let u=er(a,l.name);if(l.isDirectory()){if(!s.has(l.name))await i(u)}else if(l.isFile()){let d=await he(e,u,{suffix:n.suffix,stamp:o});if(d)r.push(d)}}}return await i(t),r}var Vu=/^[A-Za-z0-9_-]+$/,Ku=/^[A-Za-z0-9_.-]+$/;function Vt(e){if(!Vu.test(e))throw new f(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function tr(e){if(!Ku.test(e)||e.startsWith("_")||e.startsWith("."))throw new f(`invalid project id "${e}"`);return e}function ti(e){let t=e.split("/");if(t.length===1)return{alias:t[0]};if(t.length===2&&t[0]&&t[1])return{project:t[0],alias:t[1]};throw new f(`invalid --db "${e}": use <alias> or <project>/<alias>`)}function me(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class nt{paths;constructor(e){this.paths=e}async load(e){let t=this.paths.connectionsFile(e);if(!await Bun.file(t).exists())return{connections:{}};let o=await H(t);if(!o.trim())return{connections:{}};let s;try{s=JSON.parse(o)}catch(i){throw Error(`${t} is not valid JSON (${i.message}); fix it by hand before writing`)}if(!s||typeof s!=="object"||Array.isArray(s))throw Error(`${t} must be an object { default, connections }`);let r=s;if(r.connections===void 0)return{...r,connections:{}};if(!r.connections||typeof r.connections!=="object"||Array.isArray(r.connections))throw Error(`${t}: "connections" must be an object of alias -> config`);return r}async write(e,t){let n=this.paths.connectionsFile(e);await he(this.paths,n),await Bun.write(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let s=await this.load(e),r=me(s,t)??Vt(t);if(s.connections[r]=n,o||!s.default||!me(s,s.default))s.default=r;return await this.write(e,s),r}async setDefault(e,t){let n=await this.load(e),o=me(n,t);if(!o)throw new f(this.menu(e,n,`no connection "${t}" in project "${e}"`));return n.default=o,await this.write(e,n),o}async loadLegacy(e){let t=await pe(Ju(this.paths.dbDir(e),"config.json"));if(!t)return null;try{let n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))return null;let o=n;return typeof o.dbToken==="string"&&o.dbToken?o:null}catch{return null}}async listLegacyProjects(){let e=[];try{for await(let t of new Bun.Glob("*/_db/config.json").scan({cwd:this.paths.root,onlyFiles:!0})){let n=t.split(/[\\/]/)[0];if(!n.startsWith("_"))e.push(n)}}catch(t){if(t.code!=="ENOENT")throw t}return e.sort()}async listProjects(){let e=[],t=new Bun.Glob("*/_db/_connections.json");try{for await(let n of t.scan({cwd:this.paths.root,onlyFiles:!0})){let o=n.split(/[\\/]/)[0];if(!o.startsWith("_"))e.push(o)}}catch(n){if(n.code!=="ENOENT")throw n}return e.sort()}async resolveProject(e){let t=e.db?ti(e.db):void 0,n=t?.project??e.project??Bun.env.FUI_PROJECT??e.contextProject;if(n)return tr(n);let o=await this.listProjects();if(t){let r=[];for(let i of o)if(me(await this.load(i),t.alias))r.push(i);if(r.length===1)return r[0];if(r.length===0)throw new f(`no project has connection "${t.alias}"; run: fui db list`);throw new f(`connection "${t.alias}" exists in several projects; use one of:
6
+ `)}async function ro(){return Bun.stdin.text()}async function Sn(e,t){if(t&&e)throw new h("pass SQL as an argument or with -f, not both");let n;if(t){let o=Bun.file(t);if(!await o.exists())throw new h(`file not found: ${t}`);n=await o.text()}else if(e==="-")n=await ro();else if(e!==void 0)n=e;else throw new h('missing SQL: pass "<sql>", -f <file>, or - for stdin');if(!n.trim())throw new h("SQL is empty");return n}async function Pe(e,t,n){if(e!==void 0&&t!==void 0)throw new h(`pass ${n} or ${n}-env, not both`);if(t!==void 0){let s=Bun.env[t];if(!s)throw new h(`env var ${t} is empty or unset`);return s.trim()}if(e===void 0)return;if(e!=="-")throw new h(`${n} takes "-" (stdin) only; use ${n}-env VAR for env vars`);let o=(await ro()).trim();if(!o)throw new h(`${n}: stdin is empty`);return o}var Vs=60000,Js=400;function Ks(e){return e.length<=Js?e:`${e.slice(0,Js)}\u2026`}async function Du(e){let t;try{t=(await e.text()).trim()}catch{return""}if(!t)return"";try{let n=JSON.parse(t);for(let o of["Message","message","error","ExceptionMessage"]){let s=n?.[o];if(typeof s==="string"&&s.trim())return` \u2014 ${Ks(s.trim())}`}}catch{}return` \u2014 ${Ks(t)}`}function tt(e){return e.data||[]}class Gs{base;token;constructor(e,t){this.base=e;this.token=t}async request(e,t){let n;try{n=await fetch(`${this.base}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8",Authorization:`Bearer ${this.token}`},body:t!==void 0?JSON.stringify(t):"{}",signal:AbortSignal.timeout(Vs)})}catch(o){let s=o?.name;if(s==="TimeoutError"||s==="AbortError")throw Error(`FUI API timeout: ${e} did not answer within ${Vs/1000}s`);throw Error(`FUI API network error: ${e} \u2014 ${o?.message??String(o)}`)}if(!n.ok)throw Error(`FUI API error: ${n.status} ${n.statusText}${await Du(n)}`);return await n.json()}async listProjects(){return tt(await this.request("/fp/FUI_Dev_ProjectList"))}editProject(e){return this.request("/fp/FUI_Dev_ProjectEdit",{jsonObj:JSON.stringify(e)})}getProjectJson(e){return this.request(`/fp/obj/FUI_Dev_ProjectJson/${e}`,{})}async listProjectUserRights(e){return tt(await this.request("/fp/FUI_Dev_UserRightList",{PID:e,MID:""}))}async searchUsers(e,t){return tt(await this.request("/fp/FUI_Dev_UserSearch",{textsearch:e,useridlike:t}))}updateProjectUserRight(e,t,n){return this.request("/fp/FUI_Dev_UserRight_Update",{PID:e,MID:"",UserRight:t,UserList:n})}async getRuntimeModuleVersion(e,t,n){return tt(await this.request("/fp/FUI_Runtime_ModuleVersionSelect",{domain:e,url1_ID:t,url2_ID:n}))}async listModules(e){return tt(await this.request("/fp/FUI_Dev_modulelist",{pid:e}))}editModule(e,t){return this.request("/fp/FUI_Dev_ModuleEdit",{ProjectID:e,ModuleObj:JSON.stringify(t)})}deleteModule(e,t){return this.request("/fp/FUI_Dev_moduleDelete",{ProjectID:e,ModuleID:t})}async getModuleContent(e,t){let o=(await this.request(`/fp/obj/FUI_Dev_moduledata/module-load/${e}/${t}`,{})).data;if(!o.mHeader&&!o.mBody)T(`# warning: module-load ${e}/${t} returned empty mHeader and mBody`);return o}getModuleUI(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-ui/${e}/${t}`,{})}getModuleHtml(e,t){return this.request(`/fp/obj/FUI_Dev_moduledata/module-html/${e}/${t}`,{})}async getModuleScript(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-code/${e}/${t}`,{})).data?.FileContent||""}async getModuleCSS(e,t){return(await this.request(`/fp/obj/FUI_Dev_moduledata/module-css/${e}/${t}`,{})).data?.FileContent||""}updateModule(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-save/${e}/${t}`,n)}updateModuleUI(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-ui/${e}/${t}`,{FileContent:n})}updateModuleScript(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-code/${e}/${t}`,{FileContent:n})}updateModuleCSS(e,t,n){return this.request(`/fp/obj/FUI_Dev_editorupdate/module-css/${e}/${t}`,{FileContent:n})}updateProjectData(e,t){return this.request(`/fp/obj/FUI_Dev_editorupdate/project/${e}/-`,{FileContent:JSON.stringify(t)})}async getModuleComponents(e,t){return tt(await this.request("/fp/FUI_Dev_ComponentSelect",{ProjectID:e,...t&&{ModuleID:t}}))}updateComponent(e){return this.request("/fp/FUI_Dev_ComponentUpdate",e)}async getModuleDependencies(e,t){return tt(await this.request("/fp/FUI_Dev_File/select",{ProjectID:e,ModuleID:t}))}async listFrameworkImportFiles(e){return tt(await this.request("/fp/FUI_Dev_File/select",{frameWork:e}))}async listImportFiles(e,t){return tt(await this.request("/fp/FUI_Dev_File/select",{...e&&{ProjectID:e},...t&&{ModuleID:t}}))}getFileContent(e){return this.request(`/fp/obj/FUI_Dev_File/getcontent/${e}`,{})}uploadFileContent(e,t){return this.request(`/fp/FUI_Dev_File/uploadcontent/${e}`,{FileContent:t})}insertImportFile(e){return this.request("/fp/FUI_Dev_File/insert",e)}updateImportFile(e,t){return this.request(`/fp/FUI_Dev_File/update/${e}`,t)}deleteImportFile(e){return this.request(`/fp/FUI_Dev_File/delete/${e}`,{})}}async function so(){let{base:e,token:t}=await oo();if(!e.value||!t.value){let n=[!e.value&&"FUI_API_BASE",!t.value&&"FUI_API_TOKEN"].filter(Boolean).join(", ");throw new h(`missing ${n}; set the env var, run fui config set api-base <url> / fui config set api-token -, or configure the Fui MCP server`)}return new Gs(e.value,t.value)}import{mkdir as Ou,rename as Mu}from"fs/promises";import{homedir as Fu}from"os";import{join as Xo}from"path";var xu=new TextDecoder("utf-8",{ignoreBOM:!0});async function B(e){return xu.decode(await Bun.file(e).bytes())}async function le(e){try{return await B(e)}catch{return null}}function $n(e){return e?.code}var Lu=/^[A-Za-z0-9_-]+$/;function Uu(e=Bun.env){return e.FUI_MCP_SESSION_DIR||Xo(e.HOME||Fu(),".fui-mcp","session")}function Yo(e=Bun.env){let t=e.FUI_SESSION||"default";if(!Lu.test(t))throw new h(`invalid FUI_SESSION "${t}": letters, digits, "_" or "-"`);return Xo(Uu(e),`cli-${t}.json`)}class jn{file;constructor(e=Yo()){this.file=e}async read(){let e=await le(this.file);if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async write(e){await Ou(Xo(this.file,".."),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;await Bun.write(t,JSON.stringify(e,null,2)),await Mu(t,this.file)}async update(e){let t=await this.read();return e(t),await this.write(t),t}async getTarget(){return(await this.read()).target??null}async setTarget(e){let t={projectId:e.projectId,moduleId:e.moduleId,moduleDir:e.moduleDir,workspaceRoot:e.workspaceRoot,...e.target!==void 0?{target:e.target}:{}};await this.update((n)=>{n.target=t})}async clearTarget(){await this.update((e)=>{delete e.target})}async createApproval(e){let t={...e,token:crypto.randomUUID(),createdAt:new Date().toISOString()};return await this.update((n)=>{n.approval=t}),t}async getApproval(){return(await this.read()).approval??null}async clearApproval(){await this.update((e)=>{delete e.approval})}async markSkillLoaded(){await this.update((e)=>{e.skillLoadedAt=new Date().toISOString()})}async wasSkillLoaded(){return Boolean((await this.read()).skillLoadedAt)}async markSpVerified(e){await this.update((t)=>{let n=new Set(t.spVerified??[]);for(let o of e)n.add(o.toLowerCase());t.spVerified=[...n]})}async getVerifiedSpNames(){return new Set((await this.read()).spVerified??[])}}function Wt(e=Bun.env){return["1","true","yes"].includes((e.FUI_MCP_STRICT??"").trim().toLowerCase())}import{readdir as Hu}from"fs/promises";import{isAbsolute as Bu,join as Zo,relative as qu,sep as Wu}from"path";var Vu=/^[A-Za-z0-9._ -]+$/,Qo=20;function nt(e,t){if(!e||!Vu.test(e)||/^\.+$/.test(e))throw new h(`invalid ${t} ${JSON.stringify(e)}: letters, digits, ".", "_", "-" and spaces only (used as a workspace folder name)`);return e}function Ju(e,t){let n=qu(e,t);if(!n||n.startsWith("..")||Bu(n))return null;let[o,s,r]=n.split(Wu);if(!o||o.startsWith("_")||o.startsWith("."))return null;if(s==="modules"&&r)return{projectId:o,moduleId:r,moduleDir:Zo(e,o,"modules",r)};return{projectId:o}}async function zs(e,t){try{return(await Hu(e,{withFileTypes:!0})).filter((o)=>o.isDirectory()&&!(t&&(o.name.startsWith("_")||o.name.startsWith(".")))).map((o)=>o.name).sort()}catch{return[]}}function Xs(e){return e.length>Qo?[...e.slice(0,Qo),` ... and ${e.length-Qo} more`]:e}class er{paths;session;cwd;constructor(e,t,n=process.cwd()){this.paths=e;this.session=t;this.cwd=n}cwdTarget(){return Ju(this.paths.root,this.cwd)}async contextProjectId(){return this.cwdTarget()?.projectId??(await this.session.getTarget())?.projectId??void 0}async resolveProject(e){if(e)return{projectId:nt(e,"projectId"),source:"explicit"};let t=this.cwdTarget();if(t)return{projectId:t.projectId,source:"cwd"};let n=await this.session.getTarget();if(n?.projectId)return{projectId:n.projectId,source:"session"};throw new h(await this.noTargetMenu("project"))}async resolveProjectModule(e,t){if(e&&t)return nt(e,"projectId"),nt(t,"moduleId"),{projectId:e,moduleId:t,moduleDir:Zo(this.paths.root,e,"modules",t),source:"explicit"};if(e||t)throw new h("module commands need both -p <project> and -m <module>");let n=this.cwdTarget();if(n?.moduleId&&n.moduleDir)return{projectId:n.projectId,moduleId:n.moduleId,moduleDir:n.moduleDir,source:"cwd"};let o=await this.session.getTarget();if(o?.projectId&&o.moduleId&&o.moduleDir)return{projectId:o.projectId,moduleId:o.moduleId,moduleDir:o.moduleDir,source:"session"};throw new h(await this.noTargetMenu("module"))}async resolveModuleDir(e,t,n){if(e)return e;return(await this.resolveProjectModule(t,n)).moduleDir}async noTargetMenu(e){let t=await zs(this.paths.root,!0),n=e==="project"?"no project: pass -p <project>, cd into a workspace project, or run: fui use <project>/<module>":"no module: pass -p <project> -m <module>, cd into a module folder, or run: fui use <project>/<module>";if(t.length===0)return`${n}
7
+ workspace ${this.paths.root} has no projects; run: fui project list`;if(e==="module"&&t.length===1){let o=await zs(Zo(this.paths.root,t[0],"modules"),!1);if(o.length>0)return[n,...Xs(o.map((s)=>` fui use ${t[0]}/${s}`))].join(`
8
+ `)}return[n,...Xs(t.map((o)=>` -p ${o}`))].join(`
9
+ `)}}function tr(e){return new TextEncoder().encode(e).toBase64()}function In(e){try{return new TextDecoder("utf-8",{fatal:!0}).decode(Uint8Array.fromBase64(e.trim()))}catch{throw new h("token is not valid Base64 UTF-8")}}function Vt(e,t,n,o){return tr(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function Ys(e){let t=new Map;for(let n of e.split(";")){let o=n.indexOf("=");if(o<=0)continue;t.set(n.slice(0,o).trim().toLowerCase(),n.slice(o+1).trim())}return t}function Ku(e){let t=e.trim();if(/^postgres(?:ql)?:\/\//i.test(t))return"postgres";if(/^(?:mysql|mariadb):\/\//i.test(t))return"mysql";if(/^(?:sqlite|file):/i.test(t))return"sqlite";let n=Ys(t);if(n.has("server")||n.has("data source"))return"mssql";throw new h("unsupported connection: expected postgres://, mysql://, mariadb://, sqlite:/file: URL or MSSQL Server=...; string")}function Cn(e){let t=In(e);return{engine:Ku(t),plain:t}}function io(e){let{engine:t,plain:n}=Cn(e);if(t==="mssql"){let s=Ys(n);return{engine:t,server:s.get("server")??s.get("data source")??"",database:s.get("database")??s.get("initial catalog")??"",user:s.get("uid")??s.get("user id")??""}}if(t==="sqlite")return{engine:t,server:"",database:n.replace(/^(?:sqlite|file):(?:\/\/)?/i,""),user:""};let o=new URL(n);return{engine:t,server:o.port?`${o.hostname}:${o.port}`:o.hostname,database:decodeURIComponent(o.pathname.replace(/^\//,"")),user:decodeURIComponent(o.username)}}import{join as td}from"path";import{mkdir as Gu,readdir as zu,writeFile as Xu}from"fs/promises";import{basename as Qs,dirname as ei,extname as ti,isAbsolute as Yu,join as nr,relative as ni,sep as Qu}from"path";var Zs=50;function ao(e=new Date){let t=(n)=>String(n).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}_${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}function Zu(e,t,n={}){let o=ni(e.root,t),r=o!==""&&!o.startsWith("..")&&!Yu(o)?o:nr("_external",Qs(t)),i=ti(r),a=Qs(r,i),c=n.stamp??ao(),l=n.suffix?`_${n.suffix}`:"";return nr(e.historyRoot(),ei(r),`${a}_${c}${l}${i}`)}async function ed(e,t){let n=ti(e),o=e.slice(0,e.length-n.length);for(let r=1;r<=Zs;r++){let i=r===1?e:`${o}_${r}${n}`;try{return await Xu(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(a){if($n(a)!=="EEXIST")throw a}}let s=`${o}_${Zs}${n}`;return await Bun.write(s,t),s}async function fe(e,t,n={}){let o=n.content;if(o===void 0)try{o=await B(t)}catch(r){let i=$n(r);if(i==="ENOENT"||i==="ENOTDIR")return null;throw r}let s=Zu(e,t,n);return await Gu(ei(s),{recursive:!0}),ed(s,o)}function jt(e,t){let n=ni(e.root,t);return(n.startsWith("..")?t:n).split(Qu).join("/")}async function co(e,t,n={}){let o=n.stamp??ao(),s=new Set(n.skipDirNames??["_preview"]),r=[];async function i(a){let c;try{c=await zu(a,{withFileTypes:!0})}catch{return}for(let l of c){let u=nr(a,l.name);if(l.isDirectory()){if(!s.has(l.name))await i(u)}else if(l.isFile()){let d=await fe(e,u,{suffix:n.suffix,stamp:o});if(d)r.push(d)}}}return await i(t),r}var nd=/^[A-Za-z0-9_-]+$/,od=/^[A-Za-z0-9_.-]+$/;function Jt(e){if(!nd.test(e))throw new h(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function or(e){if(!od.test(e)||e.startsWith("_")||e.startsWith("."))throw new h(`invalid project id "${e}"`);return e}function oi(e){let t=e.split("/");if(t.length===1)return{alias:t[0]};if(t.length===2&&t[0]&&t[1])return{project:t[0],alias:t[1]};throw new h(`invalid --db "${e}": use <alias> or <project>/<alias>`)}function me(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class ot{paths;constructor(e){this.paths=e}async load(e){let t=this.paths.connectionsFile(e);if(!await Bun.file(t).exists())return{connections:{}};let o=await B(t);if(!o.trim())return{connections:{}};let s;try{s=JSON.parse(o)}catch(i){throw Error(`${t} is not valid JSON (${i.message}); fix it by hand before writing`)}if(!s||typeof s!=="object"||Array.isArray(s))throw Error(`${t} must be an object { default, connections }`);let r=s;if(r.connections===void 0)return{...r,connections:{}};if(!r.connections||typeof r.connections!=="object"||Array.isArray(r.connections))throw Error(`${t}: "connections" must be an object of alias -> config`);return r}async write(e,t){let n=this.paths.connectionsFile(e);await fe(this.paths,n),await Bun.write(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let s=await this.load(e),r=me(s,t)??Jt(t);if(s.connections[r]=n,o||!s.default||!me(s,s.default))s.default=r;return await this.write(e,s),r}async setDefault(e,t){let n=await this.load(e),o=me(n,t);if(!o)throw new h(this.menu(e,n,`no connection "${t}" in project "${e}"`));return n.default=o,await this.write(e,n),o}async loadLegacy(e){let t=await le(td(this.paths.dbDir(e),"config.json"));if(!t)return null;try{let n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))return null;let o=n;return typeof o.dbToken==="string"&&o.dbToken?o:null}catch{return null}}async listLegacyProjects(){let e=[];try{for await(let t of new Bun.Glob("*/_db/config.json").scan({cwd:this.paths.root,onlyFiles:!0})){let n=t.split(/[\\/]/)[0];if(!n.startsWith("_"))e.push(n)}}catch(t){if(t.code!=="ENOENT")throw t}return e.sort()}async listProjects(){let e=[],t=new Bun.Glob("*/_db/_connections.json");try{for await(let n of t.scan({cwd:this.paths.root,onlyFiles:!0})){let o=n.split(/[\\/]/)[0];if(!o.startsWith("_"))e.push(o)}}catch(n){if(n.code!=="ENOENT")throw n}return e.sort()}async resolveProject(e){let t=e.db?oi(e.db):void 0,n=t?.project??e.project??Bun.env.FUI_PROJECT??e.contextProject;if(n)return or(n);let o=await this.listProjects();if(t){let r=[];for(let i of o)if(me(await this.load(i),t.alias))r.push(i);if(r.length===1)return r[0];if(r.length===0)throw new h(`no project has connection "${t.alias}"; run: fui db list`);throw new h(`connection "${t.alias}" exists in several projects; use one of:
10
10
  `+r.map((i)=>` --db ${i}/${t.alias}`).join(`
11
- `))}let s=[];for(let r of o)if(Object.keys((await this.load(r)).connections).length>0)s.push(r);if(s.length===1)return s[0];if(s.length===0)throw new f("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new f(`several projects have connections; pass -p <project> or --db <project>/<alias>:
11
+ `))}let s=[];for(let r of o)if(Object.keys((await this.load(r)).connections).length>0)s.push(r);if(s.length===1)return s[0];if(s.length===0)throw new h("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new h(`several projects have connections; pass -p <project> or --db <project>/<alias>:
12
12
  `+s.map((r)=>` -p ${r}`).join(`
13
- `))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new f(`project "${t}" has no DB connections; run: fui db add <alias> -p ${t}`);let{alias:s,note:r}=await this.pickAlias(t,n,e),i=n.connections[s];if(!i.dbToken)throw new f(`connection ${t}/${s} has no dbToken; re-add it`);let{engine:a}=Cn(i.dbToken);if(a==="mssql"&&!i.apiDomain)throw new f(`connection ${t}/${s} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:s,config:i,engine:a,dbToken:i.dbToken,note:r}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:r}=ti(n.db),i=me(t,r);if(!i)throw new f(this.menu(e,t,`no connection "${r}" in project "${e}"`));return{alias:i,note:null}}if(o.length===1)return{alias:o[0],note:null};if(n.objectName){let r=Array.isArray(n.objectName)?n.objectName:[n.objectName],i=r[0],a=r.map((l)=>l.toLowerCase()),c=[];for(let l of o){let u=await this.knownObjectNames(e,l);if(a.some((d)=>u.has(d)))c.push(l)}if(c.length===1)return{alias:c[0],note:`connection "${c[0]}" (inferred from "${i}")`};throw new f(this.menu(e,t,c.length===0?`cannot infer the connection for "${i}": it is in no connection of project "${e}"`:`"${i}" exists in several connections (${c.join(", ")}); pick one`))}if(n.write)throw new f(this.menu(e,t,`project "${e}" has ${o.length} connections; this command needs --db`));let s=t.default?me(t,t.default):null;if(s)return{alias:s,note:`using default connection "${s}" (${o.length} connections)`};throw new f(this.menu(e,t,`project "${e}" has several connections and no default`))}async knownObjectNames(e,t){let n=new Set,o=Bun.file(this.paths.schemaFile(e,t));if(await o.exists())try{let s=await o.json();for(let r of[s.tables,s.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof r==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(r):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let s of await this.localSqlNames(e,t))n.add(s.toLowerCase());return n}async localSqlNames(e,t){let n=[];try{for await(let o of new Bun.Glob("*.sql").scan({cwd:this.paths.connDir(e,t)}))n.push(o.slice(0,-4))}catch(o){if(o.code!=="ENOENT")throw o}return n.sort()}menu(e,t,n){let o=Object.keys(t.connections).map((s)=>` --db ${e}/${s}${t.default===s?" (default)":""}`);return[n,...o].join(`
14
- `)}}import{join as It}from"path";class Rn{root;constructor(e){this.root=e}projectDir(e){return It(this.root,e)}dbDir(e){return It(this.projectDir(e),"_db")}connectionsFile(e){return It(this.dbDir(e),"_connections.json")}connDir(e,t){return It(this.dbDir(e),t)}schemaFile(e,t){return It(this.connDir(e,t),"schema.json")}spFile(e,t,n){return It(this.connDir(e,t),`${n}.sql`)}historyRoot(){return It(this.root,"_history")}}var ni={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},"no-header":{type:"boolean"},filter:{type:"string"},where:{type:"string",multiple:!0},fields:{type:"string"},help:{type:"boolean",short:"h"}};async function oi(e,t){let n=await Bs(h(e,"workdir")),o=new Rn(n.path),s=e.json===!0,r={json:s,header:e["no-header"]!==!0,filter:h(e,"filter"),where:Gu(e.where),fields:h(e,"fields")?.split(",").map((c)=>c.trim()).filter(Boolean)},i=new jn,a=new Qo(o,i);return{paths:o,session:i,targets:a,workdir:n,store:new nt(o),values:e,positionals:t,json:s,render:r}}function Gu(e){if(e===void 0)return;return(Array.isArray(e)?e:[e]).map((t)=>{let n=String(t),o=n.indexOf("=");if(o<=0)throw new f(`--where expects column=value, got "${n}"`);return[n.slice(0,o),n.slice(o+1)]})}function h(e,t){let n=e[t];return typeof n==="string"?n:void 0}async function z(e){return{store:e.store,session:e.session,project:h(e.values,"project"),contextProject:await e.targets.contextProjectId()}}function O(e){let t;return{paths:e.paths,session:e.session,targets:e.targets,api:()=>t??=so()}}function zu(e){if(e===null||e===void 0)return"NULL";if(e instanceof Date)return e.toISOString();if(e instanceof Uint8Array)return`0x${e.toHex()}`;return(typeof e==="object"?JSON.stringify(e):String(e)).replace(/\\/g,"\\\\").replace(/\t/g,"\\t").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function si(e,t){let n=[...new Set(e.flatMap((s)=>Object.keys(s)))],o=e.map((s)=>n.map((r)=>zu(s[r])));return t?[n,...o]:o}function Xu(e,t=!0){if(e.length===0)return"";return`${si(e,t).map((n)=>n.join("\t")).join(`
13
+ `))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new h(`project "${t}" has no DB connections; run: fui db add <alias> -p ${t}`);let{alias:s,note:r}=await this.pickAlias(t,n,e),i=n.connections[s];if(!i.dbToken)throw new h(`connection ${t}/${s} has no dbToken; re-add it`);let{engine:a}=Cn(i.dbToken);if(a==="mssql"&&!i.apiDomain)throw new h(`connection ${t}/${s} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:s,config:i,engine:a,dbToken:i.dbToken,note:r}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:r}=oi(n.db),i=me(t,r);if(!i)throw new h(this.menu(e,t,`no connection "${r}" in project "${e}"`));return{alias:i,note:null}}if(o.length===1)return{alias:o[0],note:null};if(n.objectName){let r=Array.isArray(n.objectName)?n.objectName:[n.objectName],i=r[0],a=r.map((l)=>l.toLowerCase()),c=[];for(let l of o){let u=await this.knownObjectNames(e,l);if(a.some((d)=>u.has(d)))c.push(l)}if(c.length===1)return{alias:c[0],note:`connection "${c[0]}" (inferred from "${i}")`};throw new h(this.menu(e,t,c.length===0?`cannot infer the connection for "${i}": it is in no connection of project "${e}"`:`"${i}" exists in several connections (${c.join(", ")}); pick one`))}if(n.write)throw new h(this.menu(e,t,`project "${e}" has ${o.length} connections; this command needs --db`));let s=t.default?me(t,t.default):null;if(s)return{alias:s,note:`using default connection "${s}" (${o.length} connections)`};throw new h(this.menu(e,t,`project "${e}" has several connections and no default`))}async knownObjectNames(e,t){let n=new Set,o=Bun.file(this.paths.schemaFile(e,t));if(await o.exists())try{let s=await o.json();for(let r of[s.tables,s.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof r==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(r):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let s of await this.localSqlNames(e,t))n.add(s.toLowerCase());return n}async localSqlNames(e,t){let n=[];try{for await(let o of new Bun.Glob("*.sql").scan({cwd:this.paths.connDir(e,t)}))n.push(o.slice(0,-4))}catch(o){if(o.code!=="ENOENT")throw o}return n.sort()}menu(e,t,n){let o=Object.keys(t.connections).map((s)=>` --db ${e}/${s}${t.default===s?" (default)":""}`);return[n,...o].join(`
14
+ `)}}import{join as It}from"path";class Rn{root;constructor(e){this.root=e}projectDir(e){return It(this.root,e)}dbDir(e){return It(this.projectDir(e),"_db")}connectionsFile(e){return It(this.dbDir(e),"_connections.json")}connDir(e,t){return It(this.dbDir(e),t)}schemaFile(e,t){return It(this.connDir(e,t),"schema.json")}spFile(e,t,n){return It(this.connDir(e,t),`${n}.sql`)}historyRoot(){return It(this.root,"_history")}}var ri={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},"no-header":{type:"boolean"},filter:{type:"string"},where:{type:"string",multiple:!0},fields:{type:"string"},help:{type:"boolean",short:"h"}};async function si(e,t){let n=await qs(f(e,"workdir")),o=new Rn(n.path),s=e.json===!0,r={json:s,header:e["no-header"]!==!0,filter:f(e,"filter"),where:rd(e.where),fields:f(e,"fields")?.split(",").map((c)=>c.trim()).filter(Boolean)},i=new jn,a=new er(o,i);return{paths:o,session:i,targets:a,workdir:n,store:new ot(o),values:e,positionals:t,json:s,render:r}}function rd(e){if(e===void 0)return;return(Array.isArray(e)?e:[e]).map((t)=>{let n=String(t),o=n.indexOf("=");if(o<=0)throw new h(`--where expects column=value, got "${n}"`);return[n.slice(0,o),n.slice(o+1)]})}function f(e,t){let n=e[t];return typeof n==="string"?n:void 0}async function z(e){return{store:e.store,session:e.session,project:f(e.values,"project"),contextProject:await e.targets.contextProjectId()}}function O(e){let t;return{paths:e.paths,session:e.session,targets:e.targets,api:()=>t??=so()}}function sd(e){if(e===null||e===void 0)return"NULL";if(e instanceof Date)return e.toISOString();if(e instanceof Uint8Array)return`0x${e.toHex()}`;return(typeof e==="object"?JSON.stringify(e):String(e)).replace(/\\/g,"\\\\").replace(/\t/g,"\\t").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function ai(e,t){let n=[...new Set(e.flatMap((s)=>Object.keys(s)))],o=e.map((s)=>n.map((r)=>sd(s[r])));return t?[n,...o]:o}function id(e,t=!0){if(e.length===0)return"";return`${ai(e,t).map((n)=>n.join("\t")).join(`
15
15
  `)}
16
- `}function Yu(e,t=!0){if(e.length===0)return"";let n=si(e,t),o=n[0].map((r,i)=>Math.max(...n.map((a)=>Bun.stringWidth(a[i]))));return`${n.map((r)=>r.map((i,a)=>a===r.length-1?i:i+" ".repeat(o[a]-Bun.stringWidth(i))).join(" ")).join(`
16
+ `}function ad(e,t=!0){if(e.length===0)return"";let n=ai(e,t),o=n[0].map((r,i)=>Math.max(...n.map((a)=>Bun.stringWidth(a[i]))));return`${n.map((r)=>r.map((i,a)=>a===r.length-1?i:i+" ".repeat(o[a]-Bun.stringWidth(i))).join(" ")).join(`
17
17
  `)}
18
- `}function nr(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
19
- `}function ri(e){return e===null||e===void 0?"":typeof e==="object"?JSON.stringify(e):String(e)}function Qu(e,{filter:t,where:n,fields:o}){let s=[...new Set(e.flatMap((a)=>Object.keys(a)))],r=(a)=>{let c=s.find((l)=>l.toLowerCase()===a.toLowerCase());if(!c&&e.length>0)throw new f(`unknown column "${a}"; columns: ${s.join(", ")}`);return c},i=e;if(t){let a=t.toLowerCase();i=i.filter((c)=>Object.values(c).some((l)=>ri(l).toLowerCase().includes(a)))}for(let[a,c]of n??[]){let l=r(a);if(l)i=i.filter((u)=>ri(u[l]).toLowerCase()===c.toLowerCase())}if(o?.length){let a=o.map(r).filter((c)=>Boolean(c));i=i.map((c)=>Object.fromEntries(a.map((l)=>[l,c[l]])))}return i}function X(e,t){let{json:n,header:o,isTty:s=process.stdout.isTTY===!0}=t,r=Qu(e,t);if(n)return nr(r);return s?Yu(r,o):Xu(r,o)}function ii(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}import{readdir as ed}from"fs/promises";import{join as ui}from"path";function dt(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function Ee(e){let t=dt(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function Zu(e){try{let t=JSON.parse(e),n=t.Message??t.message??t.error??t.ExceptionMessage;if(typeof n==="string"&&n)return n.slice(0,400)}catch{}return e.slice(0,400)}async function ai(e,t,n,o){let s;try{s=await fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${n}`},body:JSON.stringify({sqlcmd:o}),signal:AbortSignal.timeout(30000)})}catch(i){if(i instanceof Error&&i.name==="TimeoutError")throw Error("tAPI timed out after 30s; narrow the query with TOP/WHERE");throw i}let r=await s.text();if(!s.ok)throw Error(`tAPI HTTP ${s.status}: ${Zu(r)||s.statusText}`);try{return JSON.parse(r)}catch{throw Error(`tAPI returned non-JSON: ${r.slice(0,400)}`)}}class ot{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await ai(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return ai(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function or(e,t,n,o,s){let r=`${e}/help/${t}/${n?"auth/":""}${o}`,i={};if(!n&&s)i.authorization=`Bearer ${s}`;let a=await fetch(r,{headers:i,signal:AbortSignal.timeout(15000)});return{url:r,status:a.status,ok:a.ok,text:await a.text()}}var td=20000;async function Pn(e,t,n,o){if(!t)throw new f('no userToken: run fui db user-token first (a developer token with access to the permission database, alias "acc")');let s=await fetch(`${Ee(e)}/acc/${n}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${t}`},body:JSON.stringify(o),signal:AbortSignal.timeout(td)}),r=await s.json().catch(()=>null);if(!s.ok||r&&r.Message&&!r.data)throw Error(r?.Message??`HTTP ${s.status}: ${s.statusText}`);return r??{}}async function di(e){try{return(await ed(e,{withFileTypes:!0})).filter((t)=>t.isDirectory()&&!t.name.startsWith("_")).map((t)=>t.name)}catch{return[]}}async function rr(e){let t=[];for(let n of await di(e.paths.root)){let o=await e.load(n);for(let[s,r]of Object.entries(o.connections))if(r?.userToken)t.push({projectId:n,alias:s,userToken:r.userToken,apiDomain:r.apiDomain?dt(r.apiDomain):""})}return t}function sr(e){let t=new Map;for(let n of e){let o=t.get(n.userToken)??[];o.push({projectId:n.projectId,alias:n.alias,apiDomain:n.apiDomain??""}),t.set(n.userToken,o)}return[...t.entries()].map(([n,o])=>({token:n,sources:o}))}function ci(e){return e.map((t)=>`${t.projectId}/${t.alias}`).join(", ")}function pi(e){return e.map((t)=>`${t.projectId}/${t.alias} (${t.apiDomain||"?"})`).join(", ")}function mi(e){return new Bun.CryptoHasher("sha256").update(e).digest("hex").slice(-8)}function fi(e,t,n){let o=t.split("/").filter(Boolean);if(o.length===2)return{projectId:o[0],alias:o[1]};if(o.length===1)return{projectId:e,alias:o[0]};throw new f(`${n} must be "<project>/<alias>" or "<alias>" (a connection of this project); got "${t}"`)}async function hi(e,t,n){let{projectId:o,alias:s}=fi(t,n,"--from"),r=await e.load(o),i=me(r,s);if(!i)throw new f(`no connection "${s}" in project "${o}"; see: fui db list -p ${o}`);let a=r.connections[i].userToken;if(!a)throw new f(`connection ${o}/${i} has no userToken to borrow; set one there first: fui db user-token -p ${o} --db ${i} ...`);return{userToken:a,source:`${o}/${i}`}}async function nd(e,t){let n=await pe(ui(e.projectDir(t),"project.json")),o;try{o=n===null?null:JSON.parse(n)}catch{return null}if(!o||typeof o!=="object")return null;let s=o,i=(s.data&&typeof s.data==="object"&&!Array.isArray(s.data)?s.data:s).apiDomain;if(typeof i!=="string"||!i.trim())return null;return dt(i)}async function ir(e,t,n){if(n){let o=me(await e.load(t),n);if(o)return e.resolve({project:t,db:o})}return e.resolve({project:t})}async function lo(e,t,n){let o=n.apiDomain?dt(n.apiDomain):void 0;if(o&&n.userToken)return{apiDomain:o,userToken:n.userToken,note:null};try{let{alias:i,config:a}=await ir(e,t,n.apiName),c=o??(a.apiDomain?dt(a.apiDomain):a.apiDomain),l=n.userToken??a.userToken;if(c&&l)return{apiDomain:c,userToken:l,note:`using apiDomain + userToken of connection "${i}" (project ${t})`}}catch{}let s=o??await nd(e.paths,t);if(!s)throw new f(`cannot tell the apiDomain of the acc system for project "${t}"; pass --api-domain <host> (and a userToken if the project has no connection), or run: fui project sync -p ${t}`);if(n.userToken)return{apiDomain:s,userToken:n.userToken,note:null};let r=sr((await rr(e)).filter((i)=>i.apiDomain===s));if(r.length===1){let i=r[0];return{apiDomain:s,userToken:i.token,note:`project "${t}" has no connection; borrowing the userToken of ${ci(i.sources)} (same apiDomain ${s})`}}if(r.length===0)throw new f(`no userToken for apiDomain "${s}" in the workspace; pass --user-token-env VAR, or run fui db user-token on a project connected to that domain`);throw new f([`${r.length} different userTokens share apiDomain "${s}"; not picking one (a wrong token lists another system's data that still looks valid)`,"sources (one line per distinct token):",...r.map((i)=>` ${ci(i.sources)}`),"pass --user-token-env VAR with the token of the source you mean"].join(`
20
- `))}async function En(e){return(await Pn(e.apiDomain,e.userToken,"SM_Modules_ManagerSelect",{})).data??[]}function ar(e){return e.map((t)=>({apiName:String(t.APIName??""),sqlServer:String(t.SQLServer??""),database:String(t.DatabaseName??""),enable:t.Enable===!1?"no":"yes",moduleName:String(t.ModuleName??"")}))}function li(e){let t;try{t=In(e)}catch{return null}let n=/(?:^|;)\s*UID\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim(),o=/(?:^|;)\s*PWD\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim();if(!n||!o)return null;return{uid:n,password:o}}async function gi(e,t,n,o){let s=async(a,c)=>{let l=await e.load(a),u=me(l,c);if(!u)throw new f(`no connection "${c}" in project "${a}"; see: fui db list -p ${a}`);let d=li(l.connections[u].dbToken??"");if(!d)throw new f(`the dbToken of ${a}/${u} has no UID/PWD to borrow (not a standard connection string); use fui db add with your own token`);return{creds:d,source:`${a}/${u}`}};if(n){let{projectId:a,alias:c}=fi(t,n,"--borrow-from");return s(a,c)}let r=[];for(let a of await di(e.paths.root)){let c=await e.load(a);for(let[l,u]of Object.entries(c.connections)){if(u.apiDomain!==o||!u.dbToken)continue;let d=li(u.dbToken);if(d)r.push({source:`${a}/${l}`,creds:d})}}let i=new Map;for(let a of r){let c=JSON.stringify([a.creds.uid,a.creds.password]);if(!i.has(c))i.set(c,a)}if(i.size===1)return[...i.values()][0];if(i.size===0)throw new f(`no connection in the workspace uses apiDomain "${o}" to borrow a SQL login from; use fui db add <alias> --api-domain ${o} --token -`);throw new f([`${i.size} different SQL logins on apiDomain "${o}"; not picking one (each database group has its own login)`,"sources:",...[...i.values()].map((a)=>` --borrow-from ${a.source}`)].join(`
21
- `))}async function wi(e,t){let n,o;try{let i=me(await e.load(t),"acc");if(!i)throw Error("no acc alias");let a=await e.resolve({project:t,db:i});n=Ee(a.config.apiDomain),o=a.dbToken}catch{throw new f(`cannot compute a new ModuleID: project "${t}" has no connection to the permission database (alias "acc"). ModuleID must be MAX(ModuleID)+1 over ALL of acc.tblModules. connect it: fui db add-by-name acc, or pass --module-id <n>`)}let s=await new ot(n,o).query("SELECT ISNULL(MAX(ModuleID), 0) + 1 AS NextID FROM tblModules"),r=Number(s[0]?.NextID);if(!Number.isInteger(r)||r<=0)throw Error(`could not read MAX(ModuleID)+1 from acc.tblModules (got ${JSON.stringify(s[0])})`);return r}async function cr(e,t,n,o){try{let s=ui(e.dbDir(t),"rights.json"),r={};try{r=JSON.parse(await pe(s)??"")}catch{}let i=Object.keys(r).find((c)=>c.toLowerCase()===n.toLowerCase())??n,a=r[i]??{};r[i]={...a,...o.system?{systemRights:o.system,systemFetchedAt:new Date().toISOString()}:{},...o.functions?{functionRights:o.functions,functionFetchedAt:new Date().toISOString()}:{}},await Bun.write(s,JSON.stringify(r,null,2))}catch{}}import{mkdir as Wd,rename as qd,unlink as Jd}from"fs/promises";import{dirname as Vd}from"path";var{SQL:od}=globalThis.Bun;class lr{engine;sql;constructor(e,t){this.engine=e;this.sql=new od(t)}async query(e){return[...await this.sql.unsafe(e)]}async exec(e){return{count:(await this.sql.unsafe(e)).count??null}}close(){return this.sql.close()}}function ur(e){if(e.engine==="mssql")return new ot(Ee(e.config.apiDomain),e.dbToken);return new lr(e.engine,In(e.dbToken))}async function ve(e,t){let n=ur(e);try{return await t(n)}finally{await n.close()}}import{mkdir as jd,readdir as Id,unlink as Cd}from"fs/promises";import{dirname as Rd,join as Pd}from"path";var bi=`
18
+ `}function rr(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
19
+ `}function ii(e){return e===null||e===void 0?"":typeof e==="object"?JSON.stringify(e):String(e)}function cd(e,{filter:t,where:n,fields:o}){let s=[...new Set(e.flatMap((a)=>Object.keys(a)))],r=(a)=>{let c=s.find((l)=>l.toLowerCase()===a.toLowerCase());if(!c&&e.length>0)throw new h(`unknown column "${a}"; columns: ${s.join(", ")}`);return c},i=e;if(t){let a=t.toLowerCase();i=i.filter((c)=>Object.values(c).some((l)=>ii(l).toLowerCase().includes(a)))}for(let[a,c]of n??[]){let l=r(a);if(l)i=i.filter((u)=>ii(u[l]).toLowerCase()===c.toLowerCase())}if(o?.length){let a=o.map(r).filter((c)=>Boolean(c));i=i.map((c)=>Object.fromEntries(a.map((l)=>[l,c[l]])))}return i}function X(e,t){let{json:n,header:o,isTty:s=process.stdout.isTTY===!0}=t,r=cd(e,t);if(n)return rr(r);return s?ad(r,o):id(r,o)}function ci(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}import{readdir as ud}from"fs/promises";import{join as pi}from"path";function pt(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function Te(e){let t=pt(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function ld(e){try{let t=JSON.parse(e),n=t.Message??t.message??t.error??t.ExceptionMessage;if(typeof n==="string"&&n)return n.slice(0,400)}catch{}return e.slice(0,400)}async function li(e,t,n,o){let s;try{s=await fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${n}`},body:JSON.stringify({sqlcmd:o}),signal:AbortSignal.timeout(30000)})}catch(i){if(i instanceof Error&&i.name==="TimeoutError")throw Error("tAPI timed out after 30s; narrow the query with TOP/WHERE");throw i}let r=await s.text();if(!s.ok)throw Error(`tAPI HTTP ${s.status}: ${ld(r)||s.statusText}`);try{return JSON.parse(r)}catch{throw Error(`tAPI returned non-JSON: ${r.slice(0,400)}`)}}class rt{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await li(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return li(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function sr(e,t,n,o,s){let r=`${e}/help/${t}/${n?"auth/":""}${o}`,i={};if(!n&&s)i.authorization=`Bearer ${s}`;let a=await fetch(r,{headers:i,signal:AbortSignal.timeout(15000)});return{url:r,status:a.status,ok:a.ok,text:await a.text()}}var dd=20000;async function Pn(e,t,n,o){if(!t)throw new h('no userToken: run fui db user-token first (a developer token with access to the permission database, alias "acc")');let s=await fetch(`${Te(e)}/acc/${n}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${t}`},body:JSON.stringify(o),signal:AbortSignal.timeout(dd)}),r=await s.json().catch(()=>null);if(!s.ok||r&&r.Message&&!r.data)throw Error(r?.Message??`HTTP ${s.status}: ${s.statusText}`);return r??{}}async function mi(e){try{return(await ud(e,{withFileTypes:!0})).filter((t)=>t.isDirectory()&&!t.name.startsWith("_")).map((t)=>t.name)}catch{return[]}}async function ir(e){let t=[];for(let n of await mi(e.paths.root)){let o=await e.load(n);for(let[s,r]of Object.entries(o.connections))if(r?.userToken)t.push({projectId:n,alias:s,userToken:r.userToken,apiDomain:r.apiDomain?pt(r.apiDomain):""})}return t}function ar(e){let t=new Map;for(let n of e){let o=t.get(n.userToken)??[];o.push({projectId:n.projectId,alias:n.alias,apiDomain:n.apiDomain??""}),t.set(n.userToken,o)}return[...t.entries()].map(([n,o])=>({token:n,sources:o}))}function ui(e){return e.map((t)=>`${t.projectId}/${t.alias}`).join(", ")}function hi(e){return e.map((t)=>`${t.projectId}/${t.alias} (${t.apiDomain||"?"})`).join(", ")}function fi(e){return new Bun.CryptoHasher("sha256").update(e).digest("hex").slice(-8)}function gi(e,t,n){let o=t.split("/").filter(Boolean);if(o.length===2)return{projectId:o[0],alias:o[1]};if(o.length===1)return{projectId:e,alias:o[0]};throw new h(`${n} must be "<project>/<alias>" or "<alias>" (a connection of this project); got "${t}"`)}async function wi(e,t,n){let{projectId:o,alias:s}=gi(t,n,"--from"),r=await e.load(o),i=me(r,s);if(!i)throw new h(`no connection "${s}" in project "${o}"; see: fui db list -p ${o}`);let a=r.connections[i].userToken;if(!a)throw new h(`connection ${o}/${i} has no userToken to borrow; set one there first: fui db user-token -p ${o} --db ${i} ...`);return{userToken:a,source:`${o}/${i}`}}async function pd(e,t){let n=await le(pi(e.projectDir(t),"project.json")),o;try{o=n===null?null:JSON.parse(n)}catch{return null}if(!o||typeof o!=="object")return null;let s=o,i=(s.data&&typeof s.data==="object"&&!Array.isArray(s.data)?s.data:s).apiDomain;if(typeof i!=="string"||!i.trim())return null;return pt(i)}async function cr(e,t,n){if(n){let o=me(await e.load(t),n);if(o)return e.resolve({project:t,db:o})}return e.resolve({project:t})}async function lo(e,t,n){let o=n.apiDomain?pt(n.apiDomain):void 0;if(o&&n.userToken)return{apiDomain:o,userToken:n.userToken,note:null};try{let{alias:i,config:a}=await cr(e,t,n.apiName),c=o??(a.apiDomain?pt(a.apiDomain):a.apiDomain),l=n.userToken??a.userToken;if(c&&l)return{apiDomain:c,userToken:l,note:`using apiDomain + userToken of connection "${i}" (project ${t})`}}catch{}let s=o??await pd(e.paths,t);if(!s)throw new h(`cannot tell the apiDomain of the acc system for project "${t}"; pass --api-domain <host> (and a userToken if the project has no connection), or run: fui project sync -p ${t}`);if(n.userToken)return{apiDomain:s,userToken:n.userToken,note:null};let r=ar((await ir(e)).filter((i)=>i.apiDomain===s));if(r.length===1){let i=r[0];return{apiDomain:s,userToken:i.token,note:`project "${t}" has no connection; borrowing the userToken of ${ui(i.sources)} (same apiDomain ${s})`}}if(r.length===0)throw new h(`no userToken for apiDomain "${s}" in the workspace; pass --user-token-env VAR, or run fui db user-token on a project connected to that domain`);throw new h([`${r.length} different userTokens share apiDomain "${s}"; not picking one (a wrong token lists another system's data that still looks valid)`,"sources (one line per distinct token):",...r.map((i)=>` ${ui(i.sources)}`),"pass --user-token-env VAR with the token of the source you mean"].join(`
20
+ `))}async function Tn(e){return(await Pn(e.apiDomain,e.userToken,"SM_Modules_ManagerSelect",{})).data??[]}function lr(e){return e.map((t)=>({apiName:String(t.APIName??""),sqlServer:String(t.SQLServer??""),database:String(t.DatabaseName??""),enable:t.Enable===!1?"no":"yes",moduleName:String(t.ModuleName??"")}))}function di(e){let t;try{t=In(e)}catch{return null}let n=/(?:^|;)\s*UID\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim(),o=/(?:^|;)\s*PWD\s*=\s*([^;]*)/i.exec(t)?.[1]?.trim();if(!n||!o)return null;return{uid:n,password:o}}async function yi(e,t,n,o){let s=async(a,c)=>{let l=await e.load(a),u=me(l,c);if(!u)throw new h(`no connection "${c}" in project "${a}"; see: fui db list -p ${a}`);let d=di(l.connections[u].dbToken??"");if(!d)throw new h(`the dbToken of ${a}/${u} has no UID/PWD to borrow (not a standard connection string); use fui db add with your own token`);return{creds:d,source:`${a}/${u}`}};if(n){let{projectId:a,alias:c}=gi(t,n,"--borrow-from");return s(a,c)}let r=[];for(let a of await mi(e.paths.root)){let c=await e.load(a);for(let[l,u]of Object.entries(c.connections)){if(u.apiDomain!==o||!u.dbToken)continue;let d=di(u.dbToken);if(d)r.push({source:`${a}/${l}`,creds:d})}}let i=new Map;for(let a of r){let c=JSON.stringify([a.creds.uid,a.creds.password]);if(!i.has(c))i.set(c,a)}if(i.size===1)return[...i.values()][0];if(i.size===0)throw new h(`no connection in the workspace uses apiDomain "${o}" to borrow a SQL login from; use fui db add <alias> --api-domain ${o} --token -`);throw new h([`${i.size} different SQL logins on apiDomain "${o}"; not picking one (each database group has its own login)`,"sources:",...[...i.values()].map((a)=>` --borrow-from ${a.source}`)].join(`
21
+ `))}async function bi(e,t){let n,o;try{let i=me(await e.load(t),"acc");if(!i)throw Error("no acc alias");let a=await e.resolve({project:t,db:i});n=Te(a.config.apiDomain),o=a.dbToken}catch{throw new h(`cannot compute a new ModuleID: project "${t}" has no connection to the permission database (alias "acc"). ModuleID must be MAX(ModuleID)+1 over ALL of acc.tblModules. connect it: fui db add-by-name acc, or pass --module-id <n>`)}let s=await new rt(n,o).query("SELECT ISNULL(MAX(ModuleID), 0) + 1 AS NextID FROM tblModules"),r=Number(s[0]?.NextID);if(!Number.isInteger(r)||r<=0)throw Error(`could not read MAX(ModuleID)+1 from acc.tblModules (got ${JSON.stringify(s[0])})`);return r}async function ur(e,t,n,o){try{let s=pi(e.dbDir(t),"rights.json"),r={};try{r=JSON.parse(await le(s)??"")}catch{}let i=Object.keys(r).find((c)=>c.toLowerCase()===n.toLowerCase())??n,a=r[i]??{};r[i]={...a,...o.system?{systemRights:o.system,systemFetchedAt:new Date().toISOString()}:{},...o.functions?{functionRights:o.functions,functionFetchedAt:new Date().toISOString()}:{}},await Bun.write(s,JSON.stringify(r,null,2))}catch{}}import{mkdir as Zd,rename as ep,unlink as tp}from"fs/promises";import{dirname as np}from"path";var{SQL:md}=globalThis.Bun;class dr{engine;sql;constructor(e,t){this.engine=e;this.sql=new md(t)}async query(e){return[...await this.sql.unsafe(e)]}async exec(e){return{count:(await this.sql.unsafe(e)).count??null}}close(){return this.sql.close()}}function pr(e){if(e.engine==="mssql")return new rt(Te(e.config.apiDomain),e.dbToken);return new dr(e.engine,In(e.dbToken))}async function Se(e,t){let n=pr(e);try{return await t(n)}finally{await n.close()}}import{mkdir as Dd,readdir as xd,unlink as Od}from"fs/promises";import{dirname as Md,join as Fd}from"path";var vi=`
22
22
  SELECT
23
23
  o.type AS ObjectTypeCode, o.name AS ObjectName, c.name AS ColumnName, t.name AS DataType,
24
24
  c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity,
@@ -42,7 +42,7 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('U','V') AND s.name NOT IN ('sys','INFO
42
42
  AND o.name NOT LIKE 'sys%' AND o.name NOT LIKE 'dt[_]%'
43
43
  AND o.name NOT LIKE 'queue[_]%' AND o.name NOT LIKE 'filestream[_]%' AND o.name NOT LIKE 'cdc.%'
44
44
  ORDER BY ObjectTypeCode, ObjectName
45
- `.trim(),ki=`
45
+ `.trim(),Si=`
46
46
  SELECT
47
47
  o.type AS ObjectTypeCode, o.name AS ObjectName,
48
48
  p.name AS ParameterName, pt.name AS ParameterType,
@@ -57,42 +57,42 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('P','FN','TF','IF') AND s.name NOT IN (
57
57
  AND o.name NOT IN ('fn_diagramobjects','sp_alterdiagram','sp_creatediagram','sp_dropdiagram',
58
58
  'sp_helpdiagramdefinition','sp_helpdiagrams','sp_renamediagram','sp_upgraddiagrams')
59
59
  ORDER BY ObjectTypeCode, ObjectName
60
- `.trim(),rd=new Set(["nvarchar","nchar","ntext"]);function $i(e,t,n,o){let s=e.toLowerCase();if(t===-1)return`${e}(max)`;if(s==="decimal"||s==="numeric")return o!==void 0?`${e}(${n},${o})`:`${e}(${n})`;let r=rd.has(s)?t/2:t;return r>0?`${e}(${r})`:e}function sd(e){let t=[String(e.ColumnName),$i(String(e.DataType??""),Number(e.max_length??0),Number(e.precision??0),Number(e.scale??0)),e.is_nullable?"NULL":"NOT NULL"];if(e.IsPrimaryKey)t.push("PRIMARY KEY");if(e.is_identity)t.push("IDENTITY");if(e.ForeignKeyName)t.push(`REFERENCES ${String(e.RefTable??"")}(${String(e.RefColumn??"")})`);return t.join(" ")}function dr(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function vi(e,t){let n=new Map;for(let s of e){let r=String(s.ObjectName??"");if(!r)continue;let i=n.get(r);if(!i)i={keyword:String(s.ObjectTypeCode??"").trim()==="V"?"VIEW":"TABLE",columns:[]},n.set(r,i);if(s.ColumnName)i.columns.push(sd(s))}let o=new Map;for(let s of t){let r=String(s.ObjectName??""),i=String(s.ObjectTypeCode??"").trim();if(!r||!["P","FN","TF","IF"].includes(i))continue;let a=o.get(r);if(!a)a={keyword:i==="P"?"PROCEDURE":"FUNCTION",params:[]},o.set(r,a);if(s.ParameterName){let c=$i(String(s.ParameterType??""),Number(s.ParameterMaxLength??0),Number(s.ParameterPrecision??0));a.params.push(`@${String(s.ParameterName).replace(/^@/,"")} ${c}`)}}return{fetchedAt:new Date().toISOString(),tables:[...n].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.columns.join(",")});`),procedures:[...o].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.params.join(",")});`)}}function Ct(e){if(!e||typeof e!=="object")return!1;let t=e,n=(o)=>Array.isArray(o)&&o.every((s)=>typeof s==="string");return n(t.tables)&&n(t.procedures)}function Si(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}var pr=["Table","View","API","API File","Procedure","Function"];function mr(e){if(/^CREATE TABLE/i.test(e))return"Table";if(/^CREATE VIEW/i.test(e))return"View";return null}function fr(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+)/i);return t?dr(t[2],t[1].toUpperCase()==="FUNCTION"):null}var id=/^(\S+)\s+(\S+)\s+(NULL|NOT NULL)(\s+PRIMARY KEY)?(\s+IDENTITY)?(?:\s+REFERENCES\s+(\S+)\((\S+)\))?$/,yi=/^@(\S+)\s+(\S+)$/;function ji(e){let t=[],n=0,o="";for(let s of e){if(s==="(")n++;else if(s===")")n--;if(s===","&&n===0)t.push(o),o="";else o+=s}if(o.trim())t.push(o);return t}function hr(e){let t=e.match(/^CREATE (TABLE|VIEW) (\S+) \((.*)\);?$/s),n=t?ji(t[3]):null;if(t&&n.every((s)=>id.test(s.trim())))return{name:t[2],objectType:t[1]==="VIEW"?"View":"Table",count:n.length};let o=Si(e);if(!o)throw Error(`Cannot parse table DDL: ${e.slice(0,120)}`);return{name:o,objectType:mr(e)??"Table",count:0,opaque:!0}}function uo(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+) \((.*)\);?$/s),n=t?ji(t[3]):null;if(t&&n.every((r)=>yi.test(r.trim())))return{name:t[2],objectType:dr(t[2],t[1]==="FUNCTION"),count:n.length,parameters:n.map((r)=>yi.exec(r.trim())[1])};let o=Si(e);if(!o)throw Error(`Cannot parse proc DDL: ${e.slice(0,120)}`);let s=/^CREATE\s+FUNCTION/i.test(e);return{name:o,objectType:fr(e)??dr(o,s),count:0,parameters:[],opaque:!0}}import{mkdir as md,stat as fd}from"fs/promises";import{basename as hd,dirname as gd,join as wr,resolve as Ri,sep as wd}from"path";import{mkdir as ad,readdir as cd}from"fs/promises";import{join as Kt,sep as ld}from"path";var Gt="_publishState.json";function ke(e){return e.split(ld).join("/")}function Rt(e){return`sha1:${new Bun.CryptoHasher("sha1").update(e.replace(/\r\n/g,`
61
- `)).digest("hex")}`}async function po(e){try{let t=JSON.parse(await H(Kt(e,Gt)));return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async function Ii(e,t){await ad(e,{recursive:!0}),await Bun.write(Kt(e,Gt),JSON.stringify(t,null,2))}async function Te(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);try{n[r]=Rt(await H(Kt(e,r))),o=!0}catch{if(r in n)delete n[r],o=!0}}if(o)await Ii(e,n)}async function Ci(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);if(r in n)delete n[r],o=!0}if(o)await Ii(e,n)}var ud=["module.json","header.html","body.html","script.js","style.css","project.json"];async function dd(e){try{return(await cd(Kt(e,"components"),{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.toLowerCase().endsWith(".vue")).map((n)=>ke(Kt("components",n.name)))}catch{return[]}}async function Tn(e){let t=await po(e),n=[];for(let o of[...ud,...await dd(e)]){let s;try{s=await H(Kt(e,o))}catch{continue}let r=t[o];if(r===void 0)n.push({relPath:o,reason:"new"});else if(r!==Rt(s))n.push({relPath:o,reason:"changed"})}return n}async function zt(e,t,n){let o=(await po(e))[ke(t)];return o!==void 0&&Rt(n)===o}var pd={"module.json":"fui module publish json","header.html":"fui module publish html","body.html":"fui module publish html","script.js":"fui module publish script","style.css":"fui module publish css"};function mo(e){if(e.startsWith("components/"))return"fui component update";return pd[e]??"fui module publish json"}function gr(e){if(e.length===0)return null;return`unpublished local files: ${e.map((n)=>`${n.relPath} (${mo(n.relPath)})`).join(", ")}`}var yd=172800000,bd=new Set(["_moduleInfo.json","_projectInfo.json","_modules.json","_components.json","_imports.json",Gt]);function Pi(e){return bd.has(hd(e))}function kd(e,t){return e.replace(/\r\n/g,`
60
+ `.trim(),hd=new Set(["nvarchar","nchar","ntext"]);function $i(e,t,n,o){let s=e.toLowerCase();if(t===-1)return`${e}(max)`;if(s==="decimal"||s==="numeric")return o!==void 0?`${e}(${n},${o})`:`${e}(${n})`;let r=hd.has(s)?t/2:t;return r>0?`${e}(${r})`:e}function fd(e){let t=[String(e.ColumnName),$i(String(e.DataType??""),Number(e.max_length??0),Number(e.precision??0),Number(e.scale??0)),e.is_nullable?"NULL":"NOT NULL"];if(e.IsPrimaryKey)t.push("PRIMARY KEY");if(e.is_identity)t.push("IDENTITY");if(e.ForeignKeyName)t.push(`REFERENCES ${String(e.RefTable??"")}(${String(e.RefColumn??"")})`);return t.join(" ")}function mr(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function ji(e,t){let n=new Map;for(let s of e){let r=String(s.ObjectName??"");if(!r)continue;let i=n.get(r);if(!i)i={keyword:String(s.ObjectTypeCode??"").trim()==="V"?"VIEW":"TABLE",columns:[]},n.set(r,i);if(s.ColumnName)i.columns.push(fd(s))}let o=new Map;for(let s of t){let r=String(s.ObjectName??""),i=String(s.ObjectTypeCode??"").trim();if(!r||!["P","FN","TF","IF"].includes(i))continue;let a=o.get(r);if(!a)a={keyword:i==="P"?"PROCEDURE":"FUNCTION",params:[]},o.set(r,a);if(s.ParameterName){let c=$i(String(s.ParameterType??""),Number(s.ParameterMaxLength??0),Number(s.ParameterPrecision??0));a.params.push(`@${String(s.ParameterName).replace(/^@/,"")} ${c}`)}}return{fetchedAt:new Date().toISOString(),tables:[...n].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.columns.join(",")});`),procedures:[...o].map(([s,r])=>`CREATE ${r.keyword} ${s} (${r.params.join(",")});`)}}function Ct(e){if(!e||typeof e!=="object")return!1;let t=e,n=(o)=>Array.isArray(o)&&o.every((s)=>typeof s==="string");return n(t.tables)&&n(t.procedures)}function Ii(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}var hr=["Table","View","API","API File","Procedure","Function"];function fr(e){if(/^CREATE TABLE/i.test(e))return"Table";if(/^CREATE VIEW/i.test(e))return"View";return null}function gr(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+)/i);return t?mr(t[2],t[1].toUpperCase()==="FUNCTION"):null}var gd=/^(\S+)\s+(\S+)\s+(NULL|NOT NULL)(\s+PRIMARY KEY)?(\s+IDENTITY)?(?:\s+REFERENCES\s+(\S+)\((\S+)\))?$/,ki=/^@(\S+)\s+(\S+)$/;function Ci(e){let t=[],n=0,o="";for(let s of e){if(s==="(")n++;else if(s===")")n--;if(s===","&&n===0)t.push(o),o="";else o+=s}if(o.trim())t.push(o);return t}function wr(e){let t=e.match(/^CREATE (TABLE|VIEW) (\S+) \((.*)\);?$/s),n=t?Ci(t[3]):null;if(t&&n.every((s)=>gd.test(s.trim())))return{name:t[2],objectType:t[1]==="VIEW"?"View":"Table",count:n.length};let o=Ii(e);if(!o)throw Error(`Cannot parse table DDL: ${e.slice(0,120)}`);return{name:o,objectType:fr(e)??"Table",count:0,opaque:!0}}function uo(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+) \((.*)\);?$/s),n=t?Ci(t[3]):null;if(t&&n.every((r)=>ki.test(r.trim())))return{name:t[2],objectType:mr(t[2],t[1]==="FUNCTION"),count:n.length,parameters:n.map((r)=>ki.exec(r.trim())[1])};let o=Ii(e);if(!o)throw Error(`Cannot parse proc DDL: ${e.slice(0,120)}`);let s=/^CREATE\s+FUNCTION/i.test(e);return{name:o,objectType:gr(e)??mr(o,s),count:0,parameters:[],opaque:!0}}import{mkdir as $d,stat as jd}from"fs/promises";import{basename as Id,dirname as Cd,join as br,resolve as Ti,sep as Rd}from"path";import{mkdir as wd,readdir as yd}from"fs/promises";import{join as Kt,sep as bd}from"path";var Gt="_publishState.json";function ke(e){return e.split(bd).join("/")}function Rt(e){return`sha1:${new Bun.CryptoHasher("sha1").update(e.replace(/\r\n/g,`
61
+ `)).digest("hex")}`}async function po(e){try{let t=JSON.parse(await B(Kt(e,Gt)));return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async function Ri(e,t){await wd(e,{recursive:!0}),await Bun.write(Kt(e,Gt),JSON.stringify(t,null,2))}async function Ee(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);try{n[r]=Rt(await B(Kt(e,r))),o=!0}catch{if(r in n)delete n[r],o=!0}}if(o)await Ri(e,n)}async function Pi(e,t){if(t.length===0)return;let n=await po(e),o=!1;for(let s of t){let r=ke(s);if(r in n)delete n[r],o=!0}if(o)await Ri(e,n)}var kd=["module.json","header.html","body.html","script.js","style.css","project.json"];async function vd(e){try{return(await yd(Kt(e,"components"),{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.toLowerCase().endsWith(".vue")).map((n)=>ke(Kt("components",n.name)))}catch{return[]}}async function En(e){let t=await po(e),n=[];for(let o of[...kd,...await vd(e)]){let s;try{s=await B(Kt(e,o))}catch{continue}let r=t[o];if(r===void 0)n.push({relPath:o,reason:"new"});else if(r!==Rt(s))n.push({relPath:o,reason:"changed"})}return n}async function zt(e,t,n){let o=(await po(e))[ke(t)];return o!==void 0&&Rt(n)===o}var Sd={"module.json":"fui module publish json","header.html":"fui module publish html","body.html":"fui module publish html","script.js":"fui module publish script","style.css":"fui module publish css"};function mo(e){if(e.startsWith("components/"))return"fui component update";return Sd[e]??"fui module publish json"}function yr(e){if(e.length===0)return null;return`unpublished local files: ${e.map((n)=>`${n.relPath} (${mo(n.relPath)})`).join(", ")}`}var Pd=172800000,Td=new Set(["_moduleInfo.json","_projectInfo.json","_modules.json","_components.json","_imports.json",Gt]);function Ei(e){return Td.has(Id(e))}function Ed(e,t){return e.replace(/\r\n/g,`
62
62
  `)===t.replace(/\r\n/g,`
63
- `)}function $d(e,t,n){let o=Ri(e),s=Ri(t);if(s!==o&&!s.startsWith(o+wd))throw Error(`server file path escapes the target folder: ${JSON.stringify(n)} -> ${s} (must be inside ${o}); nothing written`)}async function Ae(e,t,n){let o=[],s=[],r=[],i=ao(),a=[];for(let c of Object.keys(n)){let l=ke(c);$d(t,wr(t,l),l)}for(let[c,l]of Object.entries(n)){let u=ke(c),d=wr(t,u),p=await pe(d);if(p!==null&&kd(p,l))s.push(u);else if(p!==null&&!Pi(u)){let m=await he(e,d,{stamp:i});if(m)r.push({relPath:u,historyPath:jt(e,m)})}if(await md(gd(d),{recursive:!0}),await Bun.write(d,l),o.push(d),!Pi(u))a.push(u)}if(a.length>0)await Te(t,a).catch(()=>{return});return{written:o,unchanged:s,archived:r}}function yr(e){let t=e.written.length-e.unchanged.length,n=[`wrote ${e.written.length}`,`changed ${t}`,`unchanged ${e.unchanged.length}`];if(e.archived.length>0)n.push(`archived ${e.archived.length}`);return n.join(", ")}var vd=8;function br(e){if(e.length===0)return[];let t=e.slice(0,vd).map((o)=>o.relPath),n=e.length-t.length;return[`archived ${e.length} local file(s) that differed from the server: ${t.join(", ")}${n>0?` (+${n} more)`:""}`,`restore by copying back from ${e[0].historyPath}`]}async function Sd(e,t){try{return(await fd(wr(e,t))).mtime}catch{return null}}async function Pt(e,t,n,o,s=Date.now()){let r=await Sd(e,o);if(!r)return null;let i=s-r.getTime();if(i<yd)return null;let a=Math.floor(i/3600000),c=a>=48?`${Math.floor(a/24)} days`:`${a} hours`;return`warning: ${t} last synced ${c} ago; the server may have changed. run: ${n}`}var Ed=/^[A-Za-z0-9_]+$/,Ei=50;class fo extends Error{name="SchemaFormatError"}async function Td(e){try{return JSON.parse(await H(e))}catch{return null}}async function kr(e,t,n){return Td(e.schemaFile(t,n))}async function $r(e,t,n){let o=await kr(e,t,n);if(!o)return null;if(!Ct(o))throw new fo(`_db/${n}/schema.json of project "${t}" is not in the current compact format`);try{return{fetchedAt:o.fetchedAt,tables:o.tables.map(hr),procedures:o.procedures.map(uo)}}catch(s){throw new fo(`_db/${n}/schema.json of project "${t}": ${s.message}`)}}async function Ti(e){try{return(await Id(e)).filter((t)=>t.toLowerCase().endsWith(".sql"))}catch{return null}}async function Ad(e,t){try{let n=await H(t);return await he(e,t,{content:n,suffix:"deleted"}),await Cd(t),!0}catch{return!1}}async function Nd(e,t,n,o,s,r){let i=[],a=[],c=await Ti(e.connDir(t,n));if(!c)return{removed:i,localOnly:a};let l=(p)=>new Set([...p].map((m)=>m.toLowerCase())),u=l(o),d=l(s);for(let p of c){let m=p.slice(0,-4).toLowerCase();if(d.has(m))continue;if(u.has(m)||r){if(await Ad(e,Pd(e.connDir(t,n),p)))i.push(p)}else a.push(p)}return{removed:i,localOnly:a}}async function _d(e,t,n,o){let s=await Ti(e.connDir(n,o));if(!s||s.length===0)return{sync:null,skipped:[],missing:[]};let r=[],i=[];for(let d of s){let p=d.slice(0,-4);if(Ed.test(p))r.push(p);else i.push(d)}let a=new Map;for(let d=0;d<r.length;d+=Ei){let p=r.slice(d,d+Ei).map((w)=>`N'${w}'`).join(","),m=await t.query(`SELECT o.name AS name, ISNULL(OBJECT_DEFINITION(o.object_id), '') AS sqlText
64
- FROM sys.objects o WHERE o.name IN (${p})`);for(let w of m){let y=String(w.name??""),v=String(w.sqlText??"");if(y&&v.trim())a.set(y.toLowerCase(),v)}}let c={},l=[],u=new Set(i);for(let d of s){if(u.has(d))continue;let p=a.get(d.slice(0,-4).toLowerCase());if(p===void 0)l.push(d);else c[`${o}/${d}`]=p}if(Object.keys(c).length===0)return{sync:null,skipped:i,missing:l};return{sync:await Ae(e,e.dbDir(n),c),skipped:i,missing:l}}async function ho(e,t,n,o,s=!1){let r=null;try{r=await $r(e,n,o)}catch{}let i=new Set((r?.procedures??[]).map((w)=>w.name)),[a,c]=await Promise.all([t.query(bi),t.query(ki)]),l=vi(a,c),u=e.schemaFile(n,o);await jd(Rd(u),{recursive:!0}),await Bun.write(u,JSON.stringify(l,null,2));let d=new Set(l.procedures.map((w)=>uo(w).name)),p=await Nd(e,n,o,i,d,s),m=await _d(e,t,n,o);return{schemaFile:u,schema:l,refresh:m.sync,skipped:m.skipped,missingOnServer:m.missing,removed:p.removed,localOnly:p.localOnly}}async function go(e,t,n,o){try{return await $r(e,t,n)}catch(s){if(!(s instanceof fo))throw s;return await o(),$r(e,t,n)}}async function Ai(e,t,n,o){let s=await kr(e,t,n);if(!s)return null;if(Ct(s))return s;await o();let r=await kr(e,t,n);if(!Ct(r))throw Error("refetched the schema but it is still not in the compact format");return r}function Ni(e){let t={Table:0,View:0,API:0,"API File":0,Procedure:0,Function:0};for(let n of e.tables)t[hr(n).objectType]+=1;for(let n of e.procedures)t[uo(n).objectType]+=1;return t}var Dd=/token|passw|pwd|secret|api[-_]?key|credential|authorization/i,_i=[/\b(?:PWD|Password)\s*=\s*[^;]+/i,/^Bearer\s+\S+/i,/:\/\/[^/\s:@]+:[^@\s]+@/];function Di(e){return`<redacted ${e.length} chars>`}function xi(e){if(_i.some((t)=>t.test(e)))return!0;if(e.length<40||!/^[A-Za-z0-9+/=]+$/.test(e))return!1;try{return _i.some((t)=>t.test(atob(e)))}catch{return!1}}function Xt(e){if(Array.isArray(e))return e.map((t)=>Xt(t));if(e&&typeof e==="object")return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n==="string"&&(Dd.test(t)||xi(n))?Di(n):Xt(n)]));if(typeof e==="string"&&xi(e))return Di(e);return e}function Ue(e,t={}){let n=t.strings!==!1,o=e.split(""),s=0;while(s<e.length)if(e[s]==="-"&&e[s+1]==="-")while(s<e.length&&e[s]!==`
65
- `)o[s++]=" ";else if(e[s]==="/"&&e[s+1]==="*"){let r=e.indexOf("*/",s+2),i=r===-1?e.length:r+2;while(s<i)o[s++]=" "}else if(e[s]==="'"){if(n)o[s]=" ";s++;while(s<e.length){if(e[s]==="'"){if(n)o[s]=" ";if(s++,e[s]==="'"){if(n)o[s]=" ";s++;continue}break}if(n)o[s]=" ";s++}}else s++;return o.join("")}function Et(e){return e.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ").replace(/N?'(?:[^']|'')*'/gi,"''")}function vr(e){return Ue(e,{strings:!1})}function Sr(e){let t=Ue(e),n=[],o=0;for(let s of t.matchAll(/;|^[ \t]*GO[ \t]*$/gim))n.push({start:o,end:s.index}),o=s.index+s[0].length;return n.push({start:o,end:e.length}),n.filter((s)=>s.end>s.start)}function An(e,t=","){let n=[],o=0,s="";for(let r of e){if(r==="(")o++;else if(r===")")o--;if(r===t&&o===0)n.push(s),s="";else s+=r}if(s.trim())n.push(s);return n}function Tt(e){let t=e.match(/^(spAPIFILE_UPLOAD_|spAPIFILE_|spAPI_)(AUTH_)?(.+)$/i);if(!t)return null;return{prefix:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}function jr(e){let t=Ue(e).match(/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[([^\]]+)\]|(\w+))/i);if(!t)return null;return(t[1]??t[2])||null}function Oi(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(Ue(e))}function Mi(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...Ue(e).matchAll(/\b(CREATE|ALTER)(\s+)(?:PROCEDURE|PROC|FUNCTION)\b/gi)];if(o.length!==1)return n;let s=o[0],r=s[1].toUpperCase(),i=t?"ALTER":"CREATE";if(r===i)return n;return{sql:e.slice(0,s.index)+i+e.slice(s.index+s[1].length),changed:!0,from:r,to:i}}function Ir(e){let t=Ue(e),n=t.match(/(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[[^\]]+\]|\w+)\s*\(?/i);if(!n||n.index===void 0)return[];let o=n.index+n[0].length,s=n[0].endsWith("(")?1:0,r=t.length,i=/\bAS\b/gi;i.lastIndex=o;for(let d=i.exec(t);d;d=i.exec(t)){let p=t.slice(o,d.index);if((p.match(/\(/g)??[]).length+s===(p.match(/\)/g)??[]).length){r=d.index;break}}let a=(d)=>d.replace(/^\(/,"").replace(/\)\s*$/,""),c=An(a(t.slice(o,r))),l=An(a(e.slice(o,r))),u=[];return c.forEach((d,p)=>{if(/^\s*@\w+[\s\S]*=/.test(d))u.push((l[p]??d).trim())}),u}function xd(e){return`N'${e.replace(/'/g,"''")}'`}function Nn(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function Fi(e){return`SELECT granted = COUNT(*) FROM sys.database_permissions p JOIN sys.database_principals u ON u.principal_id = p.grantee_principal_id WHERE p.class = 1 AND p.major_id = OBJECT_ID(${xd(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var Od=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,Md=new Set(["ADD","ALTER","DROP"]);function Fd(e){let t=vr(e),n=null;for(let o of Sr(e)){let s=t.slice(o.start,o.end),r=e.slice(o.start,o.end).trim();if(/\bDROP\s+TABLE\b/i.test(s))return{severity:"block",reason:"DROP TABLE",statement:r};if(!/\bALTER\s+TABLE\b/i.test(s))continue;if(/\bDROP\s+COLUMN\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... DROP COLUMN",statement:r};if(/\bSWITCH\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... SWITCH",statement:r};let i=Od.exec(s)?.[1]?.toUpperCase();if(!i||!Md.has(i))return{severity:"block",reason:`ALTER TABLE ... ${i??"(unknown action)"}`,statement:r};if(!n&&/\bALTER\s+COLUMN\b/i.test(s))n={severity:"confirm",reason:"ALTER TABLE ... ALTER COLUMN (narrowing numeric/date precision loses data silently)",statement:r}}return n}function Li(e,t){let o=(t==="DELETE"?/\bDELETE\s+(?:TOP\s*\([^)]*\)\s*)?(?:FROM\s+)?(#{1,2}\w+|@\w+|\w+)/i:/\bUPDATE\s+(?:TOP\s*\([^)]*\)\s*)?(#{1,2}\w+|@\w+|\w+)/i).exec(e)?.[1];return Boolean(o&&(o.startsWith("#")||o.startsWith("@")))}function Ld(e){let t=vr(e);for(let{start:n,end:o}of Sr(e)){let s=t.slice(n,o);if(/\bWHERE\b/i.test(s))continue;let r=e.slice(n,o).trim();if(/\bDELETE\b/i.test(s)&&!Li(s,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:r};if(/\bUPDATE\b/i.test(s)&&/\bSET\b/i.test(s)&&!Li(s,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:r}}return null}function Ui(e,t){if(/\bINSERT\b/i.test(e))t.add("INSERT");if(/\bUPDATE\b/i.test(e)&&/\bSET\b/i.test(e))t.add("UPDATE");if(/\bDELETE\b/i.test(e))t.add("DELETE");if(/\bMERGE\b/i.test(e))t.add("MERGE");if(/\bTRUNCATE\b/i.test(e))t.add("TRUNCATE")}var Ud=["INSERT","UPDATE","DELETE","MERGE","TRUNCATE"];function Yt(e){let t=new Set;return Ui(Et(e),t),Ui(Ue(e),t),Ud.filter((n)=>t.has(n))}function Cr(e,t,n){let o=Fd(e);if(o&&(o.severity==="block"||!t))return o;if(n==="nonquery"){let r=Ld(e);if(r)return r}let s=Yt(e);if(s.length>0&&!t)return{severity:"confirm",reason:`writes real data (${s.join(", ")})`,statement:e.trim()};return null}function Bd(e){let t=e.trim(),n=t.match(/\bAS\s+\[?(\w+)\]?\s*$/i);if(n)return n[1];let o=t.match(/^(.+?)\s+\[?(\w+)\]?$/);if(o){let i=o[1].trim();if(!/[+\-*/,(]$/.test(i)&&!/^(AND|OR|ON|WHEN|THEN|ELSE)$/i.test(i))return o[2]}let s=t.match(/\.\s*\[?(\w+)\]?\s*$/);if(s)return s[1];let r=t.match(/^\[?(\w+)\]?$/);if(r)return r[1];return t.length>30?`${t.slice(0,30)}\u2026`:t}function Rr(e){let t=[],n=0,o=e.length;for(let s=0;s<o;s++){let r=e[s];if(r==="("){n++;continue}if(r===")"){n--;continue}if(n!==0)continue;if(r!=="S"&&r!=="s")continue;if(s>0&&/[A-Za-z0-9_]/.test(e[s-1]))continue;if(!/^SELECT\b/i.test(e.slice(s,s+7)))continue;let i=0,a=o;for(let y=s+6;y<o;y++){let v=e[y];if(v==="("){i++;continue}if(v===")"){i--;continue}if(i!==0)continue;if(v===";"){a=y;break}if(/[A-Za-z]/.test(v)&&(y===0||!/[A-Za-z0-9_]/.test(e[y-1]))){let T=e.slice(y).match(/^[A-Za-z]+/)?.[0]?.toUpperCase();if(T&&["INSERT","UPDATE","DELETE","MERGE","SELECT"].includes(T)){a=y;break}}}let c=e.slice(s,a),l=/\bINTO\s+[#@[\w]/i.test(c),u=0,d=-1;for(let y=6;y<c.length;y++){let v=c[y];if(v==="("){u++;continue}if(v===")"){u--;continue}if(u!==0)continue;if(/^FROM\b/i.test(c.slice(y))&&(y===0||!/[A-Za-z0-9_]/.test(c[y-1]))){d=y;break}}let p=(d>=0?c.slice(6,d):c.slice(6)).trim();p=p.replace(/^(TOP\s*\(?\s*\d+\s*\)?\s*(PERCENT)?\s*)/i,"").replace(/^DISTINCT\s+/i,"").trim();let m=/(^|,)\s*\*\s*($|,)/.test(p)||p==="*",w=null;if(!m&&p)w=An(p,",").map(Bd).filter(Boolean);t.push({columns:w,hasStar:m,isInto:l})}return t}function Bi(e){return/RAISERROR\s*\([^)]*\)[^;]*;?\s*(\r?\n)?\s*RETURN\b/i.test(e)}function Pr(e){let t=e.toLowerCase();if(/(^|_)id$/.test(t)||t==="id")return 1;if(/date|time/.test(t))return"2026-01-01";if(/(count|qty|quantity|amount|price|total|sum|so[_ ]?luong)/.test(t))return 0;if(/(code|ma[_ ]?)/.test(t))return"C001";return`Sample ${e}`}function Hi(e){let t=Rr(Et(e)).filter((o)=>!o.isInto)[0];if(!t||t.hasStar||!t.columns||t.columns.length===0)return{data:[]};let n={};for(let o of t.columns)n[o]=Pr(o);return{data:[n,{...n}]}}function Hd(e){let t=Et(e),n=[];if(/\b(?:EXEC|EXECUTE)\s*\(/i.test(t)||/\bsp_executesql\b/i.test(t))n.push("dynamic SQL (EXEC(...)/sp_executesql)");if(/\b(?:EXEC|EXECUTE)\s+(?:@\w+\s*=\s*)?[A-Za-z_#[]/i.test(t))n.push("calls another SP (EXEC <proc>)");if(/\bINTO\s+(?!#|@)[A-Za-z_[]/i.test(t)&&/\bSELECT\b/i.test(t)&&!/\bINSERT\b/i.test(t))n.push("SELECT ... INTO a real table");return n}function Wi(e){if(e===null)return"unknown";if(Yt(e).length>0)return"unsafe-write";if(Hd(e).length>0)return"opaque";return"safe-read"}var Kd=/^[A-Za-z0-9_]+$/,qi=200,Ji=2000;function pt(e){if(!Kd.test(e))throw new f(`invalid object name "${e}": letters, digits and "_" only (it becomes _db/<alias>/<name>.sql)`);return e}function Se(e,t){return{project:e.project,contextProject:e.contextProject,...t}}function rt(e,t){if(e.engine!=="mssql")throw new f(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}function Vi(e){let t=e.statement.length>400?`${e.statement.slice(0,400)}...`:e.statement,n=e.severity==="block"?"blocked: no flag unlocks this; the user must run it by hand":"needs --confirm-write; pass it only after the user approved this write";return new $e(`${e.reason}
63
+ `)}function Nd(e,t,n){let o=Ti(e),s=Ti(t);if(s!==o&&!s.startsWith(o+Rd))throw Error(`server file path escapes the target folder: ${JSON.stringify(n)} -> ${s} (must be inside ${o}); nothing written`)}async function Ne(e,t,n){let o=[],s=[],r=[],i=ao(),a=[];for(let c of Object.keys(n)){let l=ke(c);Nd(t,br(t,l),l)}for(let[c,l]of Object.entries(n)){let u=ke(c),d=br(t,u),p=await le(d);if(p!==null&&Ed(p,l))s.push(u);else if(p!==null&&!Ei(u)){let m=await fe(e,d,{stamp:i});if(m)r.push({relPath:u,historyPath:jt(e,m)})}if(await $d(Cd(d),{recursive:!0}),await Bun.write(d,l),o.push(d),!Ei(u))a.push(u)}if(a.length>0)await Ee(t,a).catch(()=>{return});return{written:o,unchanged:s,archived:r}}function kr(e){let t=e.written.length-e.unchanged.length,n=[`wrote ${e.written.length}`,`changed ${t}`,`unchanged ${e.unchanged.length}`];if(e.archived.length>0)n.push(`archived ${e.archived.length}`);return n.join(", ")}var Ad=8;function vr(e){if(e.length===0)return[];let t=e.slice(0,Ad).map((o)=>o.relPath),n=e.length-t.length;return[`archived ${e.length} local file(s) that differed from the server: ${t.join(", ")}${n>0?` (+${n} more)`:""}`,`restore by copying back from ${e[0].historyPath}`]}async function _d(e,t){try{return(await jd(br(e,t))).mtime}catch{return null}}async function Pt(e,t,n,o,s=Date.now()){let r=await _d(e,o);if(!r)return null;let i=s-r.getTime();if(i<Pd)return null;let a=Math.floor(i/3600000),c=a>=48?`${Math.floor(a/24)} days`:`${a} hours`;return`warning: ${t} last synced ${c} ago; the server may have changed. run: ${n}`}var Ld=/^[A-Za-z0-9_]+$/,Ni=50;class ho extends Error{name="SchemaFormatError"}async function Ud(e){try{return JSON.parse(await B(e))}catch{return null}}async function Sr(e,t,n){return Ud(e.schemaFile(t,n))}async function $r(e,t,n){let o=await Sr(e,t,n);if(!o)return null;if(!Ct(o))throw new ho(`_db/${n}/schema.json of project "${t}" is not in the current compact format`);try{return{fetchedAt:o.fetchedAt,tables:o.tables.map(wr),procedures:o.procedures.map(uo)}}catch(s){throw new ho(`_db/${n}/schema.json of project "${t}": ${s.message}`)}}async function Ai(e){try{return(await xd(e)).filter((t)=>t.toLowerCase().endsWith(".sql"))}catch{return null}}async function Hd(e,t){try{let n=await B(t);return await fe(e,t,{content:n,suffix:"deleted"}),await Od(t),!0}catch{return!1}}async function Bd(e,t,n,o,s,r){let i=[],a=[],c=await Ai(e.connDir(t,n));if(!c)return{removed:i,localOnly:a};let l=(p)=>new Set([...p].map((m)=>m.toLowerCase())),u=l(o),d=l(s);for(let p of c){let m=p.slice(0,-4).toLowerCase();if(d.has(m))continue;if(u.has(m)||r){if(await Hd(e,Fd(e.connDir(t,n),p)))i.push(p)}else a.push(p)}return{removed:i,localOnly:a}}async function qd(e,t,n,o){let s=await Ai(e.connDir(n,o));if(!s||s.length===0)return{sync:null,skipped:[],missing:[]};let r=[],i=[];for(let d of s){let p=d.slice(0,-4);if(Ld.test(p))r.push(p);else i.push(d)}let a=new Map;for(let d=0;d<r.length;d+=Ni){let p=r.slice(d,d+Ni).map((w)=>`N'${w}'`).join(","),m=await t.query(`SELECT o.name AS name, ISNULL(OBJECT_DEFINITION(o.object_id), '') AS sqlText
64
+ FROM sys.objects o WHERE o.name IN (${p})`);for(let w of m){let y=String(w.name??""),v=String(w.sqlText??"");if(y&&v.trim())a.set(y.toLowerCase(),v)}}let c={},l=[],u=new Set(i);for(let d of s){if(u.has(d))continue;let p=a.get(d.slice(0,-4).toLowerCase());if(p===void 0)l.push(d);else c[`${o}/${d}`]=p}if(Object.keys(c).length===0)return{sync:null,skipped:i,missing:l};return{sync:await Ne(e,e.dbDir(n),c),skipped:i,missing:l}}async function fo(e,t,n,o,s=!1){let r=null;try{r=await $r(e,n,o)}catch{}let i=new Set((r?.procedures??[]).map((w)=>w.name)),[a,c]=await Promise.all([t.query(vi),t.query(Si)]),l=ji(a,c),u=e.schemaFile(n,o);await Dd(Md(u),{recursive:!0}),await Bun.write(u,JSON.stringify(l,null,2));let d=new Set(l.procedures.map((w)=>uo(w).name)),p=await Bd(e,n,o,i,d,s),m=await qd(e,t,n,o);return{schemaFile:u,schema:l,refresh:m.sync,skipped:m.skipped,missingOnServer:m.missing,removed:p.removed,localOnly:p.localOnly}}async function go(e,t,n,o){try{return await $r(e,t,n)}catch(s){if(!(s instanceof ho))throw s;return await o(),$r(e,t,n)}}async function _i(e,t,n,o){let s=await Sr(e,t,n);if(!s)return null;if(Ct(s))return s;await o();let r=await Sr(e,t,n);if(!Ct(r))throw Error("refetched the schema but it is still not in the compact format");return r}function Di(e){let t={Table:0,View:0,API:0,"API File":0,Procedure:0,Function:0};for(let n of e.tables)t[wr(n).objectType]+=1;for(let n of e.procedures)t[uo(n).objectType]+=1;return t}var Wd=/token|passw|pwd|secret|api[-_]?key|credential|authorization/i,xi=[/\b(?:PWD|Password)\s*=\s*[^;]+/i,/^Bearer\s+\S+/i,/:\/\/[^/\s:@]+:[^@\s]+@/];function Oi(e){return`<redacted ${e.length} chars>`}function Mi(e){if(xi.some((t)=>t.test(e)))return!0;if(e.length<40||!/^[A-Za-z0-9+/=]+$/.test(e))return!1;try{return xi.some((t)=>t.test(atob(e)))}catch{return!1}}function Xt(e){if(Array.isArray(e))return e.map((t)=>Xt(t));if(e&&typeof e==="object")return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n==="string"&&(Wd.test(t)||Mi(n))?Oi(n):Xt(n)]));if(typeof e==="string"&&Mi(e))return Oi(e);return e}function Ue(e,t={}){let n=t.strings!==!1,o=e.split(""),s=0;while(s<e.length)if(e[s]==="-"&&e[s+1]==="-")while(s<e.length&&e[s]!==`
65
+ `)o[s++]=" ";else if(e[s]==="/"&&e[s+1]==="*"){let r=e.indexOf("*/",s+2),i=r===-1?e.length:r+2;while(s<i)o[s++]=" "}else if(e[s]==="'"){if(n)o[s]=" ";s++;while(s<e.length){if(e[s]==="'"){if(n)o[s]=" ";if(s++,e[s]==="'"){if(n)o[s]=" ";s++;continue}break}if(n)o[s]=" ";s++}}else s++;return o.join("")}function Tt(e){return e.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ").replace(/N?'(?:[^']|'')*'/gi,"''")}function jr(e){return Ue(e,{strings:!1})}function Ir(e){let t=Ue(e),n=[],o=0;for(let s of t.matchAll(/;|^[ \t]*GO[ \t]*$/gim))n.push({start:o,end:s.index}),o=s.index+s[0].length;return n.push({start:o,end:e.length}),n.filter((s)=>s.end>s.start)}function Nn(e,t=","){let n=[],o=0,s="";for(let r of e){if(r==="(")o++;else if(r===")")o--;if(r===t&&o===0)n.push(s),s="";else s+=r}if(s.trim())n.push(s);return n}function Et(e){let t=e.match(/^(spAPIFILE_UPLOAD_|spAPIFILE_|spAPI_)(AUTH_)?(.+)$/i);if(!t)return null;return{prefix:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}function Cr(e){let t=Ue(e).match(/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[([^\]]+)\]|(\w+))/i);if(!t)return null;return(t[1]??t[2])||null}function Fi(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(Ue(e))}function Li(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...Ue(e).matchAll(/\b(CREATE|ALTER)(\s+)(?:PROCEDURE|PROC|FUNCTION)\b/gi)];if(o.length!==1)return n;let s=o[0],r=s[1].toUpperCase(),i=t?"ALTER":"CREATE";if(r===i)return n;return{sql:e.slice(0,s.index)+i+e.slice(s.index+s[1].length),changed:!0,from:r,to:i}}function Rr(e){let t=Ue(e),n=t.match(/(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC|FUNCTION)\s+(?:(?:\[[^\]]+\]|\w+)\s*\.\s*){0,2}(?:\[[^\]]+\]|\w+)\s*\(?/i);if(!n||n.index===void 0)return[];let o=n.index+n[0].length,s=n[0].endsWith("(")?1:0,r=t.length,i=/\bAS\b/gi;i.lastIndex=o;for(let d=i.exec(t);d;d=i.exec(t)){let p=t.slice(o,d.index);if((p.match(/\(/g)??[]).length+s===(p.match(/\)/g)??[]).length){r=d.index;break}}let a=(d)=>d.replace(/^\(/,"").replace(/\)\s*$/,""),c=Nn(a(t.slice(o,r))),l=Nn(a(e.slice(o,r))),u=[];return c.forEach((d,p)=>{if(/^\s*@\w+[\s\S]*=/.test(d))u.push((l[p]??d).trim())}),u}function Vd(e){return`N'${e.replace(/'/g,"''")}'`}function An(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function Ui(e){return`SELECT granted = COUNT(*) FROM sys.database_permissions p JOIN sys.database_principals u ON u.principal_id = p.grantee_principal_id WHERE p.class = 1 AND p.major_id = OBJECT_ID(${Vd(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var Jd=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,Kd=new Set(["ADD","ALTER","DROP"]);function Gd(e){let t=jr(e),n=null;for(let o of Ir(e)){let s=t.slice(o.start,o.end),r=e.slice(o.start,o.end).trim();if(/\bDROP\s+TABLE\b/i.test(s))return{severity:"block",reason:"DROP TABLE",statement:r};if(!/\bALTER\s+TABLE\b/i.test(s))continue;if(/\bDROP\s+COLUMN\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... DROP COLUMN",statement:r};if(/\bSWITCH\b/i.test(s))return{severity:"block",reason:"ALTER TABLE ... SWITCH",statement:r};let i=Jd.exec(s)?.[1]?.toUpperCase();if(!i||!Kd.has(i))return{severity:"block",reason:`ALTER TABLE ... ${i??"(unknown action)"}`,statement:r};if(!n&&/\bALTER\s+COLUMN\b/i.test(s))n={severity:"confirm",reason:"ALTER TABLE ... ALTER COLUMN (narrowing numeric/date precision loses data silently)",statement:r}}return n}function Hi(e,t){let o=(t==="DELETE"?/\bDELETE\s+(?:TOP\s*\([^)]*\)\s*)?(?:FROM\s+)?(#{1,2}\w+|@\w+|\w+)/i:/\bUPDATE\s+(?:TOP\s*\([^)]*\)\s*)?(#{1,2}\w+|@\w+|\w+)/i).exec(e)?.[1];return Boolean(o&&(o.startsWith("#")||o.startsWith("@")))}function zd(e){let t=jr(e);for(let{start:n,end:o}of Ir(e)){let s=t.slice(n,o);if(/\bWHERE\b/i.test(s))continue;let r=e.slice(n,o).trim();if(/\bDELETE\b/i.test(s)&&!Hi(s,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:r};if(/\bUPDATE\b/i.test(s)&&/\bSET\b/i.test(s)&&!Hi(s,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:r}}return null}function Bi(e,t){if(/\bINSERT\b/i.test(e))t.add("INSERT");if(/\bUPDATE\b/i.test(e)&&/\bSET\b/i.test(e))t.add("UPDATE");if(/\bDELETE\b/i.test(e))t.add("DELETE");if(/\bMERGE\b/i.test(e))t.add("MERGE");if(/\bTRUNCATE\b/i.test(e))t.add("TRUNCATE")}var Xd=["INSERT","UPDATE","DELETE","MERGE","TRUNCATE"];function Yt(e){let t=new Set;return Bi(Tt(e),t),Bi(Ue(e),t),Xd.filter((n)=>t.has(n))}function Pr(e,t,n){let o=Gd(e);if(o&&(o.severity==="block"||!t))return o;if(n==="nonquery"){let r=zd(e);if(r)return r}let s=Yt(e);if(s.length>0&&!t)return{severity:"confirm",reason:`writes real data (${s.join(", ")})`,statement:e.trim()};return null}function Yd(e){let t=e.trim(),n=t.match(/\bAS\s+\[?(\w+)\]?\s*$/i);if(n)return n[1];let o=t.match(/^(.+?)\s+\[?(\w+)\]?$/);if(o){let i=o[1].trim();if(!/[+\-*/,(]$/.test(i)&&!/^(AND|OR|ON|WHEN|THEN|ELSE)$/i.test(i))return o[2]}let s=t.match(/\.\s*\[?(\w+)\]?\s*$/);if(s)return s[1];let r=t.match(/^\[?(\w+)\]?$/);if(r)return r[1];return t.length>30?`${t.slice(0,30)}\u2026`:t}function Tr(e){let t=[],n=0,o=e.length;for(let s=0;s<o;s++){let r=e[s];if(r==="("){n++;continue}if(r===")"){n--;continue}if(n!==0)continue;if(r!=="S"&&r!=="s")continue;if(s>0&&/[A-Za-z0-9_]/.test(e[s-1]))continue;if(!/^SELECT\b/i.test(e.slice(s,s+7)))continue;let i=0,a=o;for(let y=s+6;y<o;y++){let v=e[y];if(v==="("){i++;continue}if(v===")"){i--;continue}if(i!==0)continue;if(v===";"){a=y;break}if(/[A-Za-z]/.test(v)&&(y===0||!/[A-Za-z0-9_]/.test(e[y-1]))){let E=e.slice(y).match(/^[A-Za-z]+/)?.[0]?.toUpperCase();if(E&&["INSERT","UPDATE","DELETE","MERGE","SELECT"].includes(E)){a=y;break}}}let c=e.slice(s,a),l=/\bINTO\s+[#@[\w]/i.test(c),u=0,d=-1;for(let y=6;y<c.length;y++){let v=c[y];if(v==="("){u++;continue}if(v===")"){u--;continue}if(u!==0)continue;if(/^FROM\b/i.test(c.slice(y))&&(y===0||!/[A-Za-z0-9_]/.test(c[y-1]))){d=y;break}}let p=(d>=0?c.slice(6,d):c.slice(6)).trim();p=p.replace(/^(TOP\s*\(?\s*\d+\s*\)?\s*(PERCENT)?\s*)/i,"").replace(/^DISTINCT\s+/i,"").trim();let m=/(^|,)\s*\*\s*($|,)/.test(p)||p==="*",w=null;if(!m&&p)w=Nn(p,",").map(Yd).filter(Boolean);t.push({columns:w,hasStar:m,isInto:l})}return t}function qi(e){return/RAISERROR\s*\([^)]*\)[^;]*;?\s*(\r?\n)?\s*RETURN\b/i.test(e)}function Er(e){let t=e.toLowerCase();if(/(^|_)id$/.test(t)||t==="id")return 1;if(/date|time/.test(t))return"2026-01-01";if(/(count|qty|quantity|amount|price|total|sum|so[_ ]?luong)/.test(t))return 0;if(/(code|ma[_ ]?)/.test(t))return"C001";return`Sample ${e}`}function Wi(e){let t=Tr(Tt(e)).filter((o)=>!o.isInto)[0];if(!t||t.hasStar||!t.columns||t.columns.length===0)return{data:[]};let n={};for(let o of t.columns)n[o]=Er(o);return{data:[n,{...n}]}}function Qd(e){let t=Tt(e),n=[];if(/\b(?:EXEC|EXECUTE)\s*\(/i.test(t)||/\bsp_executesql\b/i.test(t))n.push("dynamic SQL (EXEC(...)/sp_executesql)");if(/\b(?:EXEC|EXECUTE)\s+(?:@\w+\s*=\s*)?[A-Za-z_#[]/i.test(t))n.push("calls another SP (EXEC <proc>)");if(/\bINTO\s+(?!#|@)[A-Za-z_[]/i.test(t)&&/\bSELECT\b/i.test(t)&&!/\bINSERT\b/i.test(t))n.push("SELECT ... INTO a real table");return n}function Vi(e){if(e===null)return"unknown";if(Yt(e).length>0)return"unsafe-write";if(Qd(e).length>0)return"opaque";return"safe-read"}var op=/^[A-Za-z0-9_]+$/,Ji=200,Ki=2000;function mt(e){if(!op.test(e))throw new h(`invalid object name "${e}": letters, digits and "_" only (it becomes _db/<alias>/<name>.sql)`);return e}function $e(e,t){return{project:e.project,contextProject:e.contextProject,...t}}function st(e,t){if(e.engine!=="mssql")throw new h(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}function Gi(e){let t=e.statement.length>400?`${e.statement.slice(0,400)}...`:e.statement,n=e.severity==="block"?"blocked: no flag unlocks this; the user must run it by hand":"needs --confirm-write; pass it only after the user approved this write";return new ve(`${e.reason}
66
66
  ${n}
67
67
  ---
68
- ${t}`,e.severity==="block"?"hard":"soft")}function Ki(e,t){let n=Ni(t.schema),o=[`tables ${n.Table} views ${n.View} api ${n.API} api-file ${n["API File"]} procedures ${n.Procedure} functions ${n.Function}`],s=t.refresh;if(s)o.push(`sql bodies in _db/${e}/: wrote ${s.written.length}, unchanged ${s.unchanged.length}, archived ${s.archived.length}`);if(t.skipped.length>0)o.push(`not refreshed (file name is not a SQL identifier): ${t.skipped.join(" ")}`);if(t.removed.length>0)o.push(`archived to _history (dropped on server): ${t.removed.join(" ")}`);if(t.localOnly.length>0)o.push(`local only, not on server: ${t.localOnly.join(" ")}; undeployed draft -> keep or deploy; stale -> fui schema pull --db ${e} --prune-local-only`);return o}async function _n(e,t,n){Vt(n.alias);let{engine:o}=Cn(n.dbToken);if(o==="mssql"&&!n.apiDomain)throw new f("MSSQL connections need --api-domain <host>");let s=n.apiDomain?dt(n.apiDomain):void 0,r={projectId:t,alias:n.alias,config:{apiDomain:s,dbToken:n.dbToken},engine:o,dbToken:n.dbToken,note:null};if(n.test)await ve(r,(w)=>w.query("SELECT 1 AS ok"));let i=await e.store.load(t),a=me(i,n.alias),c=a?i.connections[a]:void 0,l=n.userToken??c?.userToken,u={...s?{apiDomain:s}:{},...n.sqlServer?{sqlServer:n.sqlServer}:{},...n.database?{database:n.database}:{},dbToken:n.dbToken,...l?{userToken:l}:{}},d=await e.store.save(t,n.alias,u,n.makeDefault),m=[`saved ${t}/${d} ${o}${o==="mssql"&&!l?" (no userToken; tAPI calls need it: fui db add ... --user-token-env VAR)":""}`];if(o==="mssql"){let w={...r,alias:d,config:u},y=await ve(w,(v)=>ho(e.store.paths,v,t,d));m.push(...Ki(d,y))}return{notes:m}}async function Gi(e,t,n){let o=await e.store.resolve(Se(e,{db:t}));rt(o,"schema pull");let s=await ve(o,(r)=>ho(e.store.paths,r,o.projectId,o.alias,n));return{notes:[...o.note?[o.note]:[],`ok ${o.projectId}/${o.alias} schema refreshed`,...Ki(o.alias,s)]}}function wo(e,t){return()=>ve(t,(n)=>ho(e.store.paths,n,t.projectId,t.alias))}async function zi(e,t){let n=await e.store.resolve(Se(e,{db:t.db}));rt(n,"schema");let{projectId:o,alias:s}=n,r=await Ai(e.store.paths,o,s,wo(e,n));if(!r)throw new f(`no local schema for ${o}/${s}; run: fui schema pull --db ${o}/${s}`);let i=t.search?.toLowerCase(),a=(y)=>!i||y.toLowerCase().includes(i),c=t.objectType,l=!c||c==="Table"||c==="View",u=!c||c!=="Table"&&c!=="View",d=l?r.tables.filter((y)=>(!c||mr(y)===c)&&a(y)):[],p=u?r.procedures.filter((y)=>(!c||fr(y)===c)&&a(y)):[],m=await Pt(e.store.paths.connDir(o,s),`schema of ${o}/${s}`,`fui schema pull --db ${o}/${s}`,"schema.json"),w=[...d,...p];return{notes:[...n.note?[n.note]:[],...m?[m]:[],`${o}/${s} fetchedAt=${r.fetchedAt} tables/views=${d.length} procs/functions=${p.length}`],data:w.length>0?`${w.join(`
68
+ ${t}`,e.severity==="block"?"hard":"soft")}function zi(e,t){let n=Di(t.schema),o=[`tables ${n.Table} views ${n.View} api ${n.API} api-file ${n["API File"]} procedures ${n.Procedure} functions ${n.Function}`],s=t.refresh;if(s)o.push(`sql bodies in _db/${e}/: wrote ${s.written.length}, unchanged ${s.unchanged.length}, archived ${s.archived.length}`);if(t.skipped.length>0)o.push(`not refreshed (file name is not a SQL identifier): ${t.skipped.join(" ")}`);if(t.removed.length>0)o.push(`archived to _history (dropped on server): ${t.removed.join(" ")}`);if(t.localOnly.length>0)o.push(`local only, not on server: ${t.localOnly.join(" ")}; undeployed draft -> keep or deploy; stale -> fui schema pull --db ${e} --prune-local-only`);return o}async function _n(e,t,n){Jt(n.alias);let{engine:o}=Cn(n.dbToken);if(o==="mssql"&&!n.apiDomain)throw new h("MSSQL connections need --api-domain <host>");let s=n.apiDomain?pt(n.apiDomain):void 0,r={projectId:t,alias:n.alias,config:{apiDomain:s,dbToken:n.dbToken},engine:o,dbToken:n.dbToken,note:null};if(n.test)await Se(r,(w)=>w.query("SELECT 1 AS ok"));let i=await e.store.load(t),a=me(i,n.alias),c=a?i.connections[a]:void 0,l=n.userToken??c?.userToken,u={...s?{apiDomain:s}:{},...n.sqlServer?{sqlServer:n.sqlServer}:{},...n.database?{database:n.database}:{},dbToken:n.dbToken,...l?{userToken:l}:{}},d=await e.store.save(t,n.alias,u,n.makeDefault),m=[`saved ${t}/${d} ${o}${o==="mssql"&&!l?" (no userToken; tAPI calls need it: fui db add ... --user-token-env VAR)":""}`];if(o==="mssql"){let w={...r,alias:d,config:u},y=await Se(w,(v)=>fo(e.store.paths,v,t,d));m.push(...zi(d,y))}return{notes:m}}async function Xi(e,t,n){let o=await e.store.resolve($e(e,{db:t}));st(o,"schema pull");let s=await Se(o,(r)=>fo(e.store.paths,r,o.projectId,o.alias,n));return{notes:[...o.note?[o.note]:[],`ok ${o.projectId}/${o.alias} schema refreshed`,...zi(o.alias,s)]}}function wo(e,t){return()=>Se(t,(n)=>fo(e.store.paths,n,t.projectId,t.alias))}async function Yi(e,t){let n=await e.store.resolve($e(e,{db:t.db}));st(n,"schema");let{projectId:o,alias:s}=n,r=await _i(e.store.paths,o,s,wo(e,n));if(!r)throw new h(`no local schema for ${o}/${s}; run: fui schema pull --db ${o}/${s}`);let i=t.search?.toLowerCase(),a=(y)=>!i||y.toLowerCase().includes(i),c=t.objectType,l=!c||c==="Table"||c==="View",u=!c||c!=="Table"&&c!=="View",d=l?r.tables.filter((y)=>(!c||fr(y)===c)&&a(y)):[],p=u?r.procedures.filter((y)=>(!c||gr(y)===c)&&a(y)):[],m=await Pt(e.store.paths.connDir(o,s),`schema of ${o}/${s}`,`fui schema pull --db ${o}/${s}`,"schema.json"),w=[...d,...p];return{notes:[...n.note?[n.note]:[],...m?[m]:[],`${o}/${s} fetchedAt=${r.fetchedAt} tables/views=${d.length} procs/functions=${p.length}`],data:w.length>0?`${w.join(`
69
69
  `)}
70
- `:""}}async function Xi(e,t){let n=await e.store.resolveProject(Se(e,{db:t.db})),o=await e.store.load(n);if(Object.keys(o.connections).length===0)throw new f(`project "${n}" has no DB connections; run: fui db add <alias> -p ${n}`);let s=t.db?[(await e.store.resolve(Se(e,{db:t.db}))).alias]:Object.keys(o.connections),r=t.search?.toLowerCase(),i=(u)=>!r||u.toLowerCase().includes(r),a=t.objectType,c=[],l=[];for(let u of s){let d;try{let m=await e.store.resolve({project:n,db:u});d=await go(e.store.paths,n,u,wo(e,m))}catch(m){l.push(`${u}: schema unreadable: ${m.message}`);continue}if(!d){l.push(`${u}: no local schema; run: fui schema pull --db ${n}/${u}`);continue}l.push(`${u} fetchedAt=${d.fetchedAt}`);let p=`${n}/${u}`;if(!a||a==="Table"||a==="View"){for(let m of d.tables)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}if(!a||a!=="Table"&&a!=="View"){for(let m of d.procedures)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}}return{rows:c,notes:l}}var Er=(e)=>e.replace(/'/g,"''");async function Yi(e,t,n){pt(t);let o=await e.store.resolve(Se(e,{db:n,objectName:t}));rt(o,"sp get");let s=await ve(o,(l)=>l.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(t)}')),'') AS sqlText`)),r=String(s[0]?.sqlText??"");if(!r.trim())throw Error(`object "${t}" not found or has no definition in the database`);let i=e.store.paths,a=await Ae(i,i.dbDir(o.projectId),{[`${o.alias}/${t}.sql`]:r}),c=o.note?[o.note]:[];for(let l of a.archived)c.push(`old local copy archived: ${l.historyPath}`);return{path:i.spFile(o.projectId,o.alias,t),body:r,sync:a,notes:c}}async function Qi(e,t,n,o){pt(t);let s=await e.store.resolve(Se(e,{db:o,objectName:t})),r=e.store.paths.spFile(s.projectId,s.alias,t);return await Wd(Vd(r),{recursive:!0}),await Bun.write(r,n),{path:r,alias:s.alias,notes:s.note?[s.note]:[]}}function Gd(e){return/(CREATE\s+PROCEDURE|CREATE\s+PROC|CREATE\s+FUNCTION)/im.test(e)}async function Tr(e,t){try{let n=await e.query(Fi(t)),o=Number(n[0]?.granted??0);return Number.isFinite(o)?o>0:null}catch{return null}}async function zd(e){return await e.wasSkillLoaded()?[]:["warning: skill not loaded this session; read the fui-skill references (tapi-reference, db-workflow), then run: fui skill loaded"]}async function Xd(e,t){if(!qt()||await e.wasSkillLoaded())return;throw new $e(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read tapi-reference.md and db-workflow.md, run fui skill loaded, then retry`,"hard","error")}async function Zi(e,t,n){await Xd(e.session,"sp deploy"),pt(t);let o=await e.store.resolve(Se(e,{db:n,objectName:t}));rt(o,"sp deploy");let{projectId:s,alias:r}=o,i=e.store.paths,a=i.spFile(s,r,t),c;try{c=await H(a)}catch{throw new f(`file not found: ${a}; run fui sp save or fui sp get first`)}if(!c.trim())throw new f(`file is empty: ${a}`);let l=jr(c)??t,u=Tt(l);if(u){let m=Ir(c);if(m.length>0)throw new $e(`tAPI does not support parameter default values; remove "= ..." and save before deploying:
70
+ `:""}}async function Qi(e,t){let n=await e.store.resolveProject($e(e,{db:t.db})),o=await e.store.load(n);if(Object.keys(o.connections).length===0)throw new h(`project "${n}" has no DB connections; run: fui db add <alias> -p ${n}`);let s=t.db?[(await e.store.resolve($e(e,{db:t.db}))).alias]:Object.keys(o.connections),r=t.search?.toLowerCase(),i=(u)=>!r||u.toLowerCase().includes(r),a=t.objectType,c=[],l=[];for(let u of s){let d;try{let m=await e.store.resolve({project:n,db:u});d=await go(e.store.paths,n,u,wo(e,m))}catch(m){l.push(`${u}: schema unreadable: ${m.message}`);continue}if(!d){l.push(`${u}: no local schema; run: fui schema pull --db ${n}/${u}`);continue}l.push(`${u} fetchedAt=${d.fetchedAt}`);let p=`${n}/${u}`;if(!a||a==="Table"||a==="View"){for(let m of d.tables)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}if(!a||a!=="Table"&&a!=="View"){for(let m of d.procedures)if((!a||m.objectType===a)&&i(m.name))c.push({db:p,name:m.name,kind:m.objectType,count:m.count})}}return{rows:c,notes:l}}var Nr=(e)=>e.replace(/'/g,"''");async function Zi(e,t,n){mt(t);let o=await e.store.resolve($e(e,{db:n,objectName:t}));st(o,"sp get");let s=await Se(o,(l)=>l.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Nr(t)}')),'') AS sqlText`)),r=String(s[0]?.sqlText??"");if(!r.trim())throw Error(`object "${t}" not found or has no definition in the database`);let i=e.store.paths,a=await Ne(i,i.dbDir(o.projectId),{[`${o.alias}/${t}.sql`]:r}),c=o.note?[o.note]:[];for(let l of a.archived)c.push(`old local copy archived: ${l.historyPath}`);return{path:i.spFile(o.projectId,o.alias,t),body:r,sync:a,notes:c}}async function ea(e,t,n,o){mt(t);let s=await e.store.resolve($e(e,{db:o,objectName:t})),r=e.store.paths.spFile(s.projectId,s.alias,t);return await Zd(np(r),{recursive:!0}),await Bun.write(r,n),{path:r,alias:s.alias,notes:s.note?[s.note]:[]}}function rp(e){return/(CREATE\s+PROCEDURE|CREATE\s+PROC|CREATE\s+FUNCTION)/im.test(e)}async function Ar(e,t){try{let n=await e.query(Ui(t)),o=Number(n[0]?.granted??0);return Number.isFinite(o)?o>0:null}catch{return null}}async function sp(e){return await e.wasSkillLoaded()?[]:["warning: skill not loaded this session; read the fui-skill references (tapi-reference, db-workflow), then run: fui skill loaded"]}async function ip(e,t){if(!Wt()||await e.wasSkillLoaded())return;throw new ve(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read tapi-reference.md and db-workflow.md, run fui skill loaded, then retry`,"hard","error")}async function ta(e,t,n){await ip(e.session,"sp deploy"),mt(t);let o=await e.store.resolve($e(e,{db:n,objectName:t}));st(o,"sp deploy");let{projectId:s,alias:r}=o,i=e.store.paths,a=i.spFile(s,r,t),c;try{c=await B(a)}catch{throw new h(`file not found: ${a}; run fui sp save or fui sp get first`)}if(!c.trim())throw new h(`file is empty: ${a}`);let l=Cr(c)??t,u=Et(l);if(u){let m=Rr(c);if(m.length>0)throw new ve(`tAPI does not support parameter default values; remove "= ..." and save before deploying:
71
71
  `+m.map((w)=>` - ${w}`).join(`
72
- `),"hard","error")}let d=`${s}/${r}`,p=[];return await ve(o,async(m)=>{let w=null,y="";try{let b=await m.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(l)}')), '') AS def`);y=String(b[0]?.def??"").trim(),w=y.length>0}catch{}let v="";if(y)try{v=`rollback copy: ${await he(i,a,{content:y})}`}catch(b){throw Error(`could not save the rollback copy (${b.message}); not deploying`)}let T=Mi(c,w);if(await m.exec(T.sql),o.note)p.push(o.note);if(p.push(`ok deployed ${t} to ${d}`,`file: ${a}`),T.changed)p.push(`ran as ${T.to} because "${l}" ${T.to==="ALTER"?"exists":"does not exist"}; file unchanged`);if(v)p.push(v);let I=w===null?Gd(c):!w,g=Oi(c);if(I&&!g){let b=Nn(l);try{await m.exec(b),p.push(`GRANT EXECUTE ON [dbo].[${l}] TO [public]: applied`)}catch(R){p.push(`deploy OK but grant FAILED (${R.message}); do not redeploy, run: fui exec "${b}" --db ${d}`)}}if(!g){let b=await Tr(m,l);if(b===!1)p.push(`[public] lacks EXECUTE on "${l}"; tAPI calls will fail. run: fui exec "${Nn(l)}" --db ${d}`);else if(b===!0&&!I)p.push(`[public] has EXECUTE on "${l}"`)}if(u)try{let b=await or(Ee(o.config.apiDomain),r,u.isAuth,u.fnName,o.config.userToken);p.push(b.ok?`tAPI cache cleared (GET ${b.url} -> HTTP ${b.status})`:`tAPI /help HTTP ${b.status}; ignored, deploy done. only if the endpoint still takes old params: fui sp help ${l}`)}catch(b){p.push(`tAPI /help failed (${b.message}); ignored, deploy done`)}}),{notes:[...await zd(e.session),...p]}}async function ea(e,t,n,o){let s=[t,`spAPI_${t}`,`spAPI_AUTH_${t}`,`spAPIFILE_${t}`,`spAPIFILE_UPLOAD_${t}`],r=await e.store.resolve(Se(e,{db:n,objectName:s}));rt(r,"sp help");let i=null;try{i=await go(e.store.paths,r.projectId,r.alias,wo(e,r))}catch{}let a=t,c=i?.procedures.find((m)=>s.some((w)=>w.toLowerCase()===m.name.toLowerCase()));if(c)a=c.name;let l=Tt(a);if(!l)throw new f(`"${a}" is not an API SP (spAPI_/spAPIFILE_ prefix); only those have a /help route. `+(i?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let u=r.note?[r.note]:[],d;try{d=await or(Ee(r.config.apiDomain),r.alias,l.isAuth,l.fnName,r.config.userToken)}catch(m){return u.push(`/help for "${a}" failed (${m.message}); ignored, does not affect the deployed SP`),{notes:u}}if(u.push(`GET ${d.url} -> HTTP ${d.status}`),!d.ok)return u.push("/help unavailable for this function; ignored. it only shows params and clears the tAPI cache. do not try other URL shapes"),{notes:u};u.push("tAPI cache cleared for this function");let p;try{let m=JSON.stringify(Xt(JSON.parse(d.text)));p=o>0&&m.length>o?`${m.slice(0,o)}\u2026(truncated)`:m}catch{p=o>0?d.text.slice(0,o):d.text}return{notes:u,data:`${p}
73
- `}}async function ta(e,t,n){let o=Cr(t,n.confirmWrite,"query");if(o)throw Vi(o);let s=await e.store.resolve(Se(e,{db:n.db,write:!n.db&&Yt(t).length>0}));return{rows:await ve(s,(i)=>i.query(t)),connection:s}}function Yd(e){if(!/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\b/i.test(Ue(e)))return null;return jr(e)}async function na(e,t,n){let o=Cr(t,n.confirmWrite,"nonquery");if(o)throw Vi(o);let s=await e.store.resolve(Se(e,{db:n.db,write:!n.db})),r=ur(s),i=[];try{let a=await r.exec(t),c=s.engine==="mssql"?Yd(t):null;if(c){let l=`${s.projectId}/${s.alias}`,u=await Tr(r,c);if(u===!1)i.push(`[public] lacks EXECUTE on "${c}"; tAPI calls will fail. run: fui exec "${Nn(c)}" --db ${l} (fui sp save + sp deploy grants automatically)`);else if(u===!0)i.push(`[public] has EXECUTE on "${c}"`);if(Tt(c))i.push(`"${c}" is an API SP: the tAPI param cache is not cleared here; run: fui sp help ${c}`)}return{result:a,connection:s,notes:i}}finally{await r.close()}}async function oa(e,t){let{name:n}=t,o=[n,`spAPI_${n}`,`spAPI_AUTH_${n}`,`spAPIFILE_${n}`,`spAPIFILE_UPLOAD_${n}`],s=await e.store.resolve(Se(e,{db:t.db,objectName:o}));rt(s,"sp verify");let{projectId:r,alias:i,config:a}=s,c=e.store.paths,l=null;try{l=await go(c,r,i,wo(e,s))}catch{}let u=n,d=l?.procedures.find((B)=>o.some((ae)=>ae.toLowerCase()===B.name.toLowerCase()));if(d)u=d.name;let p=Tt(u);if(!p)throw new f(`"${u}" is not an API SP (spAPI_/spAPIFILE_ prefix); plain SPs have no HTTP endpoint. `+(l?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let{prefix:m,isAuth:w,fnName:y}=p,v=/^spAPIFILE_UPLOAD_/i.test(m),T=/^spAPIFILE_/i.test(m)&&!v,I=await Qd(e,s,u);if(!I)throw Error(`cannot read the real body of "${u}": no _db/${i}/${u}.sql and the live OBJECT_DEFINITION lookup failed (no permission or no such object). sp verify never guesses from the name; fetch it first: fui sp get ${u} --db ${r}/${i}`);await e.session.markSpVerified([u,y]);let g=I.source==="local"?await Pt(c.connDir(r,i),`_db/${i}/${u}.sql`,`fui schema pull --db ${r}/${i}`,"schema.json"):null,b=I.source==="local"?`_db/${i}/${u}.sql`:"OBJECT_DEFINITION (live definition read, no call)",R=v?"API File Upload":T?"API File Get":"API",C=T||v?"GET/POST":"POST",S=[...s.note?[s.note]:[],...g?[g]:[],`verify ${u} (from ${b})`,`kind: ${R}${w?" (AUTH_: public, no Bearer)":""}`,`url (reference only, not called; copy verbatim): ${C} https://${a.apiDomain}/${i}/${w?"auth/":""}${y}`,"","## params"],N=0;if(d){let B=[];for(let ge of d.parameters??[]){let We=ge.replace(/^@/,"");if(/^sys_/i.test(We))continue;if(/^url\d+_/i.test(We)){N++;continue}B.push(We)}if(S.push(`route params (@url*_): ${N}`,`body params: ${B.length?B.join(", "):"(none)"}`),t.params){let ge=new Set(Object.keys(t.params).map((Ce)=>Ce.toLowerCase())),We=B.filter((Ce)=>!ge.has(Ce.toLowerCase())),Ye=Object.keys(t.params).filter((Ce)=>!/^@?sys_/i.test(Ce)&&!B.some(($t)=>$t.toLowerCase()===Ce.replace(/^@/,"").toLowerCase()));if(We.length)S.push(` warning: missing from --params: ${We.join(", ")}`);if(Ye.length)S.push(` warning: not declared by the SP: ${Ye.join(", ")}`)}let ae=(t.urlParams??[]).length;if(t.urlParams&&ae!==N)S.push(` warning: the SP declares ${N} route params, --url-param gave ${ae}`)}else S.push(`warning: "${u}" is not in the local schema; params not checked. just deployed? run: fui schema pull --db ${r}/${i}`);if(t.checkGrant){let B=await ve(s,(ae)=>Tr(ae,u));S.push(B===!0?`[public] has EXECUTE on "${u}"`:B===!1?`[public] LACKS EXECUTE on "${u}"; the endpoint fails with a permission error. fix: fui exec "${Nn(u)}" --db ${r}/${i}`:"warning: could not read sys.database_permissions; EXECUTE grant unknown")}let D=Ir(I.sql);if(D.length)S.push(`warning: tAPI does not support default values; sp deploy will block: ${D.join("; ")}`);S.push("","## response shape (static, best effort)");let A=Rr(Et(I.sql)),G=A.filter((B)=>!B.isInto),_=A.length-G.length;if(_>0)S.push(`(${_} SELECT ... INTO #temp/@table not returned to the client; not counted)`);if(G.length===0)S.push("warning: no result-returning SELECT; the SP may only write (check any OUT binding in module.json)");else if(G.length===1){let B=G[0];if(S.push("1 SELECT: data is an array (one resultset)"),B.hasStar)S.push("warning: SELECT *: columns unknown from the body; check fui schema --search <table>");else if(B.columns)S.push(`columns: ${B.columns.join(", ")}`)}else S.push(`${G.length} result SELECTs: data may be array-of-arrays (data[0], data[1], ...)`),G.forEach((B,ae)=>{S.push(` data[${ae}]: ${B.hasStar?"SELECT * (columns unknown)":B.columns?B.columns.join(", "):"(unknown)"}`)});S.push(Bi(I.sql)?"error contract { Message }: RAISERROR(...) + RETURN present":"warning: no clear RAISERROR(...) + RETURN; error branches must RAISERROR level 16 then RETURN"),S.push("","## read/write");let le=Yt(I.sql);if(le.length===0)S.push("read API: no INSERT/UPDATE+SET/DELETE/MERGE in the body");else S.push(`WRITE API: ${le.join(", ")}`,"never call it to test; check the body: WHERE on the key/route param, multi-step writes in a TRANSACTION, RAISERROR when the row is missing",`test the UI flow with fake data: fui module simulate --mocks '{"${y}":{"data":[...]}}'`);S.push("","## apiMocks snippet (fake data)");let Ie=G[0];if(Ie&&!Ie.hasStar&&Ie.columns?.length){let B={};for(let ae of Ie.columns)B[ae]=Pr(ae);S.push(JSON.stringify({apiMocks:{[y]:{data:[B,B]}}},null,2))}else S.push("(not generated: SELECT * or unknown columns; write apiMocks from fui schema --search <table>)");return{notes:S}}async function Qd(e,t,n){try{let o=await H(e.store.paths.spFile(t.projectId,t.alias,n));if(o.trim())return{sql:o,source:"local"}}catch{}try{let o=await ve(t,(r)=>r.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Er(n)}')),'') AS sqlText`)),s=String(o[0]?.sqlText??"");if(s.trim())return{sql:s,source:"live"}}catch{}return null}async function ra(e,t,n,o){pt(t);let s=await e.store.resolve(Se(e,{db:o,objectName:t}));rt(s,"sp delete");let{projectId:r,alias:i}=s,a=n==="Function"?`DROP FUNCTION [dbo].[${t}]`:`DROP PROCEDURE [dbo].[${t}]`;await ve(s,(u)=>u.exec(a));let c=[...s.note?[s.note]:[],`ok dropped ${n} [dbo].[${t}] on ${r}/${i}`],l=e.store.paths.spFile(r,i,t);try{let u=await H(l),d=await he(e.store.paths,l,{content:u,suffix:"deleted"});await Jd(l),c.push(`local file archived: ${d?jt(e.store.paths,d):""} (removed from _db/${i}/)`)}catch{}return{notes:c}}async function sa(e,t,n,o){pt(t),pt(n);let s=await e.store.resolve(Se(e,{db:o,objectName:t}));rt(s,"sp rename");let{projectId:r,alias:i}=s;await ve(s,(c)=>c.exec(`EXEC sp_rename '[dbo].[${t}]', '${n.replace(/'/g,"''")}'`));let a=[...s.note?[s.note]:[],`ok renamed [dbo].[${t}] -> ${n} on ${r}/${i}`,`refresh the local schema: fui schema pull --db ${r}/${i}`];try{await qd(e.store.paths.spFile(r,i,t),e.store.paths.spFile(r,i,n)),a.push(`local file renamed: _db/${i}/${t}.sql -> _db/${i}/${n}.sql`)}catch{}return{notes:a}}async function ia(e,t){let n=await e.store.resolve(Se(e,{db:t.db,write:!t.db}));rt(n,"db token-rebuild");let{projectId:o,alias:s,config:r}=n;if(!r.sqlServer||!r.database)throw new f(`connection ${o}/${s} has no sqlServer/database saved; re-add it: fui db add ${s} -p ${o} --sql-server <host> --database <name> ...`);let i=Jt(r.sqlServer,r.database,t.uid,t.password);return await new ot(Ee(r.apiDomain),i).query("SELECT 1 AS ok"),await e.store.save(o,s,{...r,dbToken:i},!1),{notes:["ok dbToken rebuilt and tested",`connection: ${o}/${s}`,`sql server: ${r.sqlServer}`,`database: ${r.database}`,`uid: ${t.uid}`,`config: ${e.store.paths.connectionsFile(o)}`]}}async function Dn(e){return e.store.resolveProject({project:e.project,contextProject:e.contextProject})}async function aa(e,t){let n=await Dn(e),o=e.store.paths.connectionsFile(n);if(t.tokenSource){let{userToken:r,source:i}=await hi(e.store,n,t.tokenSource),a=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,a.alias,{...a.config,userToken:r},!1),{notes:[...a.note?[a.note]:[],`ok borrowed the userToken of ${i} into ${n}/${a.alias}`,`config: ${o}`]}}if(!t.userToken){let r=await rr(e.store);if(r.length===0)return{notes:["no userToken in any project of this workspace; pass one: fui db user-token --token -"]};let i=sr(r);return{notes:[`${i.length} distinct userToken(s) across ${r.length} connection(s); pick one (same apiDomain preferred):`,...i.map((a)=>`fingerprint ${mi(a.token)} sources: ${pi(a.sources)}`),"then: fui db user-token --from <project/alias> (the value is copied internally, never printed)"]}}let s=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,s.alias,{...s.config,userToken:t.userToken},!1),{notes:[...s.note?[s.note]:[],`ok userToken saved for ${n}/${s.alias}`,`config: ${o}`]}}async function ca(e,t){let n=await Dn(e),o=await lo(e.store,n,t),s=await En(o),r=t.search?.trim().toLowerCase(),i=r?s.filter((c)=>[c.APIName,c.DatabaseName,c.ModuleName].some((l)=>String(l??"").toLowerCase().includes(r))):s,a=o.note?[o.note]:[];if(i.length===0)a.push(r?`no alias matches "${t.search}" among the ${s.length} databases you may manage; drop --search to see all`:`empty: this token manages no database on ${o.apiDomain}`);else a.push(`${i.length}/${s.length} databases you may manage on ${o.apiDomain}; connect one: fui db add-by-name ${String(i[0].APIName??"<alias>")}`);return{rows:ar(i),notes:a,total:s.length}}async function la(e,t){Vt(t.apiName);let n=await Dn(e),o=await lo(e.store,n,{...t}),s=(await En(o)).find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s)throw new f(`alias "${t.apiName}" already exists on ${o.apiDomain} (ModuleID ${s.ModuleID}, database ${s.DatabaseName}); db alias new only registers NEW aliases. see: fui db alias list --search ${t.apiName}`);let r=t.moduleId??await wi(e.store,n);await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,ModuleName:t.moduleName,APIName:t.apiName,Enable:1,UpdateConnection:0}),await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,SQLServer:t.sqlServer,DatabaseName:t.database,Username:t.username,Password:t.password,UpdateConnection:1});let a=(await En(o).catch(()=>null))?.find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase()),c=a?ar([a])[0]:null;return{notes:[...o.note?[o.note]:[],`ok registered alias "${t.apiName}" (ModuleID ${r}) on ${o.apiDomain}`,c?`post-check: ${c.apiName} ${c.sqlServer} ${c.database} enable=${c.enable} ${c.moduleName}`:`warning: post-check: alias not in the managed list yet; check: fui db alias list --search ${t.apiName}`,`login stored: ${t.username} (password not printed)`,`connect it: fui db add-by-name ${t.apiName}`]}}async function ua(e,t){let n=await Dn(e);Vt(t.apiName);let o=await lo(e.store,n,t),s=(await En(o)).filter((u)=>String(u.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s.length===0)throw new f(`alias "${t.apiName}" not found on ${o.apiDomain}: it does not exist, or your account may not manage it (the list is scoped to your rights). see: fui db alias list`);if(s.length>1)throw new f(`alias "${t.apiName}" matches ${s.length} acc records; not picking one. use fui db add with an explicit token`);let r=s[0];if(!r.SQLServer||!r.DatabaseName)throw new f(`the acc record of "${t.apiName}" has no SQLServer/DatabaseName; use fui db add with your own token`);let{creds:i,source:a}=await gi(e.store,n,t.borrowFrom,o.apiDomain),c=Jt(r.SQLServer,r.DatabaseName,i.uid,i.password);try{await new ot(Ee(o.apiDomain),c).query("SELECT 1 AS ok")}catch(u){throw Error(`test connection to ${r.SQLServer}/${r.DatabaseName} failed; nothing saved.
72
+ `),"hard","error")}let d=`${s}/${r}`,p=[];return await Se(o,async(m)=>{let w=null,y="";try{let b=await m.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Nr(l)}')), '') AS def`);y=String(b[0]?.def??"").trim(),w=y.length>0}catch{}let v="";if(y)try{v=`rollback copy: ${await fe(i,a,{content:y})}`}catch(b){throw Error(`could not save the rollback copy (${b.message}); not deploying`)}let E=Li(c,w);if(await m.exec(E.sql),o.note)p.push(o.note);if(p.push(`ok deployed ${t} to ${d}`,`file: ${a}`),E.changed)p.push(`ran as ${E.to} because "${l}" ${E.to==="ALTER"?"exists":"does not exist"}; file unchanged`);if(v)p.push(v);let I=w===null?rp(c):!w,g=Fi(c);if(I&&!g){let b=An(l);try{await m.exec(b),p.push(`GRANT EXECUTE ON [dbo].[${l}] TO [public]: applied`)}catch(R){p.push(`deploy OK but grant FAILED (${R.message}); do not redeploy, run: fui exec "${b}" --db ${d}`)}}if(!g){let b=await Ar(m,l);if(b===!1)p.push(`[public] lacks EXECUTE on "${l}"; tAPI calls will fail. run: fui exec "${An(l)}" --db ${d}`);else if(b===!0&&!I)p.push(`[public] has EXECUTE on "${l}"`)}if(u)try{let b=await sr(Te(o.config.apiDomain),r,u.isAuth,u.fnName,o.config.userToken);p.push(b.ok?`tAPI cache cleared (GET ${b.url} -> HTTP ${b.status})`:`tAPI /help HTTP ${b.status}; ignored, deploy done. only if the endpoint still takes old params: fui sp help ${l}`)}catch(b){p.push(`tAPI /help failed (${b.message}); ignored, deploy done`)}}),{notes:[...await sp(e.session),...p]}}async function na(e,t,n,o){let s=[t,`spAPI_${t}`,`spAPI_AUTH_${t}`,`spAPIFILE_${t}`,`spAPIFILE_UPLOAD_${t}`],r=await e.store.resolve($e(e,{db:n,objectName:s}));st(r,"sp help");let i=null;try{i=await go(e.store.paths,r.projectId,r.alias,wo(e,r))}catch{}let a=t,c=i?.procedures.find((m)=>s.some((w)=>w.toLowerCase()===m.name.toLowerCase()));if(c)a=c.name;let l=Et(a);if(!l)throw new h(`"${a}" is not an API SP (spAPI_/spAPIFILE_ prefix); only those have a /help route. `+(i?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let u=r.note?[r.note]:[],d;try{d=await sr(Te(r.config.apiDomain),r.alias,l.isAuth,l.fnName,r.config.userToken)}catch(m){return u.push(`/help for "${a}" failed (${m.message}); ignored, does not affect the deployed SP`),{notes:u}}if(u.push(`GET ${d.url} -> HTTP ${d.status}`),!d.ok)return u.push("/help unavailable for this function; ignored. it only shows params and clears the tAPI cache. do not try other URL shapes"),{notes:u};u.push("tAPI cache cleared for this function");let p;try{let m=JSON.stringify(Xt(JSON.parse(d.text)));p=o>0&&m.length>o?`${m.slice(0,o)}\u2026(truncated)`:m}catch{p=o>0?d.text.slice(0,o):d.text}return{notes:u,data:`${p}
73
+ `}}async function oa(e,t,n){let o=Pr(t,n.confirmWrite,"query");if(o)throw Gi(o);let s=await e.store.resolve($e(e,{db:n.db,write:!n.db&&Yt(t).length>0}));return{rows:await Se(s,(i)=>i.query(t)),connection:s}}function ap(e){if(!/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\b/i.test(Ue(e)))return null;return Cr(e)}async function ra(e,t,n){let o=Pr(t,n.confirmWrite,"nonquery");if(o)throw Gi(o);let s=await e.store.resolve($e(e,{db:n.db,write:!n.db})),r=pr(s),i=[];try{let a=await r.exec(t),c=s.engine==="mssql"?ap(t):null;if(c){let l=`${s.projectId}/${s.alias}`,u=await Ar(r,c);if(u===!1)i.push(`[public] lacks EXECUTE on "${c}"; tAPI calls will fail. run: fui exec "${An(c)}" --db ${l} (fui sp save + sp deploy grants automatically)`);else if(u===!0)i.push(`[public] has EXECUTE on "${c}"`);if(Et(c))i.push(`"${c}" is an API SP: the tAPI param cache is not cleared here; run: fui sp help ${c}`)}return{result:a,connection:s,notes:i}}finally{await r.close()}}async function sa(e,t){let{name:n}=t,o=[n,`spAPI_${n}`,`spAPI_AUTH_${n}`,`spAPIFILE_${n}`,`spAPIFILE_UPLOAD_${n}`],s=await e.store.resolve($e(e,{db:t.db,objectName:o}));st(s,"sp verify");let{projectId:r,alias:i,config:a}=s,c=e.store.paths,l=null;try{l=await go(c,r,i,wo(e,s))}catch{}let u=n,d=l?.procedures.find((H)=>o.some((ae)=>ae.toLowerCase()===H.name.toLowerCase()));if(d)u=d.name;let p=Et(u);if(!p)throw new h(`"${u}" is not an API SP (spAPI_/spAPIFILE_ prefix); plain SPs have no HTTP endpoint. `+(l?"check the name: fui sp list --type API":"no local schema; run: fui schema pull"));let{prefix:m,isAuth:w,fnName:y}=p,v=/^spAPIFILE_UPLOAD_/i.test(m),E=/^spAPIFILE_/i.test(m)&&!v,I=await cp(e,s,u);if(!I)throw Error(`cannot read the real body of "${u}": no _db/${i}/${u}.sql and the live OBJECT_DEFINITION lookup failed (no permission or no such object). sp verify never guesses from the name; fetch it first: fui sp get ${u} --db ${r}/${i}`);await e.session.markSpVerified([u,y]);let g=I.source==="local"?await Pt(c.connDir(r,i),`_db/${i}/${u}.sql`,`fui schema pull --db ${r}/${i}`,"schema.json"):null,b=I.source==="local"?`_db/${i}/${u}.sql`:"OBJECT_DEFINITION (live definition read, no call)",R=v?"API File Upload":E?"API File Get":"API",C=E||v?"GET/POST":"POST",S=[...s.note?[s.note]:[],...g?[g]:[],`verify ${u} (from ${b})`,`kind: ${R}${w?" (AUTH_: public, no Bearer)":""}`,`url (reference only, not called; copy verbatim): ${C} https://${a.apiDomain}/${i}/${w?"auth/":""}${y}`,"","## params"],A=0;if(d){let H=[];for(let ge of d.parameters??[]){let We=ge.replace(/^@/,"");if(/^sys_/i.test(We))continue;if(/^url\d+_/i.test(We)){A++;continue}H.push(We)}if(S.push(`route params (@url*_): ${A}`,`body params: ${H.length?H.join(", "):"(none)"}`),t.params){let ge=new Set(Object.keys(t.params).map((Ce)=>Ce.toLowerCase())),We=H.filter((Ce)=>!ge.has(Ce.toLowerCase())),Qe=Object.keys(t.params).filter((Ce)=>!/^@?sys_/i.test(Ce)&&!H.some((vt)=>vt.toLowerCase()===Ce.replace(/^@/,"").toLowerCase()));if(We.length)S.push(` warning: missing from --params: ${We.join(", ")}`);if(Qe.length)S.push(` warning: not declared by the SP: ${Qe.join(", ")}`)}let ae=(t.urlParams??[]).length;if(t.urlParams&&ae!==A)S.push(` warning: the SP declares ${A} route params, --url-param gave ${ae}`)}else S.push(`warning: "${u}" is not in the local schema; params not checked. just deployed? run: fui schema pull --db ${r}/${i}`);if(t.checkGrant){let H=await Se(s,(ae)=>Ar(ae,u));S.push(H===!0?`[public] has EXECUTE on "${u}"`:H===!1?`[public] LACKS EXECUTE on "${u}"; the endpoint fails with a permission error. fix: fui exec "${An(u)}" --db ${r}/${i}`:"warning: could not read sys.database_permissions; EXECUTE grant unknown")}let D=Rr(I.sql);if(D.length)S.push(`warning: tAPI does not support default values; sp deploy will block: ${D.join("; ")}`);S.push("","## response shape (static, best effort)");let N=Tr(Tt(I.sql)),G=N.filter((H)=>!H.isInto),_=N.length-G.length;if(_>0)S.push(`(${_} SELECT ... INTO #temp/@table not returned to the client; not counted)`);if(G.length===0)S.push("warning: no result-returning SELECT; the SP may only write (check any OUT binding in module.json)");else if(G.length===1){let H=G[0];if(S.push("1 SELECT: data is an array (one resultset)"),H.hasStar)S.push("warning: SELECT *: columns unknown from the body; check fui schema --search <table>");else if(H.columns)S.push(`columns: ${H.columns.join(", ")}`)}else S.push(`${G.length} result SELECTs: data may be array-of-arrays (data[0], data[1], ...)`),G.forEach((H,ae)=>{S.push(` data[${ae}]: ${H.hasStar?"SELECT * (columns unknown)":H.columns?H.columns.join(", "):"(unknown)"}`)});S.push(qi(I.sql)?"error contract { Message }: RAISERROR(...) + RETURN present":"warning: no clear RAISERROR(...) + RETURN; error branches must RAISERROR level 16 then RETURN"),S.push("","## read/write");let ue=Yt(I.sql);if(ue.length===0)S.push("read API: no INSERT/UPDATE+SET/DELETE/MERGE in the body");else S.push(`WRITE API: ${ue.join(", ")}`,"never call it to test; check the body: WHERE on the key/route param, multi-step writes in a TRANSACTION, RAISERROR when the row is missing",`test the UI flow with fake data: fui module simulate --mocks '{"${y}":{"data":[...]}}'`);S.push("","## apiMocks snippet (fake data)");let Ie=G[0];if(Ie&&!Ie.hasStar&&Ie.columns?.length){let H={};for(let ae of Ie.columns)H[ae]=Er(ae);S.push(JSON.stringify({apiMocks:{[y]:{data:[H,H]}}},null,2))}else S.push("(not generated: SELECT * or unknown columns; write apiMocks from fui schema --search <table>)");return{notes:S}}async function cp(e,t,n){try{let o=await B(e.store.paths.spFile(t.projectId,t.alias,n));if(o.trim())return{sql:o,source:"local"}}catch{}try{let o=await Se(t,(r)=>r.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Nr(n)}')),'') AS sqlText`)),s=String(o[0]?.sqlText??"");if(s.trim())return{sql:s,source:"live"}}catch{}return null}async function ia(e,t,n,o){mt(t);let s=await e.store.resolve($e(e,{db:o,objectName:t}));st(s,"sp delete");let{projectId:r,alias:i}=s,a=n==="Function"?`DROP FUNCTION [dbo].[${t}]`:`DROP PROCEDURE [dbo].[${t}]`;await Se(s,(u)=>u.exec(a));let c=[...s.note?[s.note]:[],`ok dropped ${n} [dbo].[${t}] on ${r}/${i}`],l=e.store.paths.spFile(r,i,t);try{let u=await B(l),d=await fe(e.store.paths,l,{content:u,suffix:"deleted"});await tp(l),c.push(`local file archived: ${d?jt(e.store.paths,d):""} (removed from _db/${i}/)`)}catch{}return{notes:c}}async function aa(e,t,n,o){mt(t),mt(n);let s=await e.store.resolve($e(e,{db:o,objectName:t}));st(s,"sp rename");let{projectId:r,alias:i}=s;await Se(s,(c)=>c.exec(`EXEC sp_rename '[dbo].[${t}]', '${n.replace(/'/g,"''")}'`));let a=[...s.note?[s.note]:[],`ok renamed [dbo].[${t}] -> ${n} on ${r}/${i}`,`refresh the local schema: fui schema pull --db ${r}/${i}`];try{await ep(e.store.paths.spFile(r,i,t),e.store.paths.spFile(r,i,n)),a.push(`local file renamed: _db/${i}/${t}.sql -> _db/${i}/${n}.sql`)}catch{}return{notes:a}}async function ca(e,t){let n=await e.store.resolve($e(e,{db:t.db,write:!t.db}));st(n,"db token-rebuild");let{projectId:o,alias:s,config:r}=n;if(!r.sqlServer||!r.database)throw new h(`connection ${o}/${s} has no sqlServer/database saved; re-add it: fui db add ${s} -p ${o} --sql-server <host> --database <name> ...`);let i=Vt(r.sqlServer,r.database,t.uid,t.password);return await new rt(Te(r.apiDomain),i).query("SELECT 1 AS ok"),await e.store.save(o,s,{...r,dbToken:i},!1),{notes:["ok dbToken rebuilt and tested",`connection: ${o}/${s}`,`sql server: ${r.sqlServer}`,`database: ${r.database}`,`uid: ${t.uid}`,`config: ${e.store.paths.connectionsFile(o)}`]}}async function Dn(e){return e.store.resolveProject({project:e.project,contextProject:e.contextProject})}async function la(e,t){let n=await Dn(e),o=e.store.paths.connectionsFile(n);if(t.tokenSource){let{userToken:r,source:i}=await wi(e.store,n,t.tokenSource),a=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,a.alias,{...a.config,userToken:r},!1),{notes:[...a.note?[a.note]:[],`ok borrowed the userToken of ${i} into ${n}/${a.alias}`,`config: ${o}`]}}if(!t.userToken){let r=await ir(e.store);if(r.length===0)return{notes:["no userToken in any project of this workspace; pass one: fui db user-token --token -"]};let i=ar(r);return{notes:[`${i.length} distinct userToken(s) across ${r.length} connection(s); pick one (same apiDomain preferred):`,...i.map((a)=>`fingerprint ${fi(a.token)} sources: ${hi(a.sources)}`),"then: fui db user-token --from <project/alias> (the value is copied internally, never printed)"]}}let s=await e.store.resolve({project:n,db:t.db});return await e.store.save(n,s.alias,{...s.config,userToken:t.userToken},!1),{notes:[...s.note?[s.note]:[],`ok userToken saved for ${n}/${s.alias}`,`config: ${o}`]}}async function ua(e,t){let n=await Dn(e),o=await lo(e.store,n,t),s=await Tn(o),r=t.search?.trim().toLowerCase(),i=r?s.filter((c)=>[c.APIName,c.DatabaseName,c.ModuleName].some((l)=>String(l??"").toLowerCase().includes(r))):s,a=o.note?[o.note]:[];if(i.length===0)a.push(r?`no alias matches "${t.search}" among the ${s.length} databases you may manage; drop --search to see all`:`empty: this token manages no database on ${o.apiDomain}`);else a.push(`${i.length}/${s.length} databases you may manage on ${o.apiDomain}; connect one: fui db add-by-name ${String(i[0].APIName??"<alias>")}`);return{rows:lr(i),notes:a,total:s.length}}async function da(e,t){Jt(t.apiName);let n=await Dn(e),o=await lo(e.store,n,{...t}),s=(await Tn(o)).find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s)throw new h(`alias "${t.apiName}" already exists on ${o.apiDomain} (ModuleID ${s.ModuleID}, database ${s.DatabaseName}); db alias new only registers NEW aliases. see: fui db alias list --search ${t.apiName}`);let r=t.moduleId??await bi(e.store,n);await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,ModuleName:t.moduleName,APIName:t.apiName,Enable:1,UpdateConnection:0}),await Pn(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,SQLServer:t.sqlServer,DatabaseName:t.database,Username:t.username,Password:t.password,UpdateConnection:1});let a=(await Tn(o).catch(()=>null))?.find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase()),c=a?lr([a])[0]:null;return{notes:[...o.note?[o.note]:[],`ok registered alias "${t.apiName}" (ModuleID ${r}) on ${o.apiDomain}`,c?`post-check: ${c.apiName} ${c.sqlServer} ${c.database} enable=${c.enable} ${c.moduleName}`:`warning: post-check: alias not in the managed list yet; check: fui db alias list --search ${t.apiName}`,`login stored: ${t.username} (password not printed)`,`connect it: fui db add-by-name ${t.apiName}`]}}async function pa(e,t){let n=await Dn(e);Jt(t.apiName);let o=await lo(e.store,n,t),s=(await Tn(o)).filter((u)=>String(u.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s.length===0)throw new h(`alias "${t.apiName}" not found on ${o.apiDomain}: it does not exist, or your account may not manage it (the list is scoped to your rights). see: fui db alias list`);if(s.length>1)throw new h(`alias "${t.apiName}" matches ${s.length} acc records; not picking one. use fui db add with an explicit token`);let r=s[0];if(!r.SQLServer||!r.DatabaseName)throw new h(`the acc record of "${t.apiName}" has no SQLServer/DatabaseName; use fui db add with your own token`);let{creds:i,source:a}=await yi(e.store,n,t.borrowFrom,o.apiDomain),c=Vt(r.SQLServer,r.DatabaseName,i.uid,i.password);try{await new rt(Te(o.apiDomain),c).query("SELECT 1 AS ok")}catch(u){throw Error(`test connection to ${r.SQLServer}/${r.DatabaseName} failed; nothing saved.
74
74
  ${u.message}
75
- likely the login borrowed from ${a} has no access to this database (other group); pass --borrow-from <project/alias> or use fui db add`)}let l=await _n(e,n,{alias:t.apiName,dbToken:c,apiDomain:o.apiDomain,sqlServer:r.SQLServer,database:r.DatabaseName,userToken:o.userToken,makeDefault:t.makeDefault,test:!1});return{notes:[...o.note?[o.note]:[],...l.notes,`sql server: ${r.SQLServer} database: ${r.DatabaseName} login borrowed from ${a} (not printed)`]}}async function da(e,t){let n=await Dn(e),o=await ir(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Pn(s,r,i,a),target:{ModuleID:0,APIName:t??o.alias}}}async function pa(e,t,n){let{projectId:o,call:s,target:r}=await da(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await cr(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function ma(e,t,n){let{projectId:o,call:s,target:r}=await da(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new f(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await cr(e.store.paths,o,r.APIName,t.kind==="system"?{system:l}:{functions:l});return{apiName:r.APIName,notes:[a,`current list (${l.length}):`],rows:l}}var Nr={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function _r(e){return{apiDomain:h(e.values,"api-domain"),userToken:await Pe(h(e.values,"user-token"),h(e.values,"user-token-env"),"--user-token")}}function xn(e){k(`${e.join(`
75
+ likely the login borrowed from ${a} has no access to this database (other group); pass --borrow-from <project/alias> or use fui db add`)}let l=await _n(e,n,{alias:t.apiName,dbToken:c,apiDomain:o.apiDomain,sqlServer:r.SQLServer,database:r.DatabaseName,userToken:o.userToken,makeDefault:t.makeDefault,test:!1});return{notes:[...o.note?[o.note]:[],...l.notes,`sql server: ${r.SQLServer} database: ${r.DatabaseName} login borrowed from ${a} (not printed)`]}}async function ma(e,t){let n=await Dn(e),o=await cr(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Pn(s,r,i,a),target:{ModuleID:0,APIName:t??o.alias}}}async function ha(e,t,n){let{projectId:o,call:s,target:r}=await ma(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await ur(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function fa(e,t,n){let{projectId:o,call:s,target:r}=await ma(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new h(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await ur(e.store.paths,o,r.APIName,t.kind==="system"?{system:l}:{functions:l});return{apiName:r.APIName,notes:[a,`current list (${l.length}):`],rows:l}}var Dr={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function xr(e){return{apiDomain:f(e.values,"api-domain"),userToken:await Pe(f(e.values,"user-token"),f(e.values,"user-token-env"),"--user-token")}}function xn(e){k(`${e.join(`
76
76
  `)}
77
- `)}var Zd={name:"db user-token",mcpName:"db_user_token_set",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Pe(h(e.values,"token"),h(e.values,"token-env"),"--token"),n=h(e.values,"from");if(n&&t)throw new f("pass --from or --token/--token-env, not both");let o=await aa(await z(e),{db:h(e.values,"db"),userToken:t,tokenSource:n});xn(o.notes)}},ep={name:"db token-rebuild",mcpName:"db_token_rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=h(e.values,"uid");if(!t)throw new f("missing --uid");let n=await Pe(h(e.values,"pwd"),h(e.values,"pwd-env"),"--pwd");if(!n)throw new f("missing --pwd - or --pwd-env VAR");xn((await ia(await z(e),{db:h(e.values,"db"),uid:t,password:n})).notes)}},tp={name:"db alias list",mcpName:"db_alias_list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...Nr},async run(e){let t=await ca(await z(e),{...await _r(e),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);if(t.rows.length>0)k(X(t.rows,e.render))}},np={name:"db alias new",mcpName:"db_alias_new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...Nr},async run(e){let{values:t}=e,n=e.positionals[0],o=h(t,"module-name"),s=h(t,"sql-server"),r=h(t,"database"),i=h(t,"username");if(!n||!o||!s||!r||!i)throw new f("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Pe(h(t,"password"),h(t,"password-env"),"--password");if(!a)throw new f("missing --password - or --password-env VAR");let c=h(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new f("--module-id must be an integer");let u=await la(await z(e),{...await _r(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});xn(u.notes)}},op={name:"db add-by-name",mcpName:"db_connect_by_name",usage:"fui db add-by-name <apiName> [-p P] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{"borrow-from":{type:"string"},default:{type:"boolean"},...Nr},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <apiName>; see: fui db alias list");let n=await ua(await z(e),{...await _r(e),apiName:t,borrowFrom:h(e.values,"borrow-from"),makeDefault:e.values.default===!0});xn(n.notes)}};function ga(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function wa(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var ya={"api-name":{type:"string"}};function Ar(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new f(`${t} must be ${o}`);return s}function fa(e,t,n){if(e!==void 0&&e.length>t)throw new f(`${n} is at most ${t} characters`);return e}function ha(e){return{name:`right ${e} list`,mcpName:`right_${e}_list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:ya,async run(t){let{apiName:n,rows:o}=await pa(await z(t),e,h(t.values,"api-name"));if(E(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){E(`# none defined; create one: fui right ${e} new ...`);return}k(X(e==="system"?ga(o):wa(o),t.render))}}}function Qt(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,mcpName:`right_${e}_${t}`,usage:n,summary:o[e][t],options:{...ya,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:Ar(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:fa(h(r,"note"),50,"--note")};else{let l=h(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:Ar(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:Ar(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:fa(h(r,"name"),100,"--name")}}let c=await ma(await z(s),a,h(r,"api-name"));xn(c.notes),k(X(e==="system"?ga(c.rows):wa(c.rows),s.render))}}}var ba=[Zd,ep,tp,np,op,ha("system"),Qt("system","new"),Qt("system","update"),Qt("system","delete"),ha("function"),Qt("function","new"),Qt("function","update"),Qt("function","delete")];import{mkdir as up}from"fs/promises";import{realpathSync as sp}from"fs";import{join as ip,sep as Zt}from"path";var je="1.0.1";var On="@fui-org/fui-cli",ap=3600000,cp=5000;function ka(e=Bun.env){return ip(Ze(e),"..","update.json")}async function $a(e=Bun.env){try{let t=await Bun.file(ka(e)).json();if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function At(e,t=Bun.env){await Bun.write(ka(t),`${JSON.stringify(e,null,2)}
78
- `)}function va(e=Bun.main){let t;try{t=sp(e)}catch{return null}let n=`${Zt}install${Zt}global${Zt}node_modules${Zt}@fui-org${Zt}fui-cli${Zt}`,o=t.indexOf(n);return o<0?null:t.slice(0,o+n.length-1)}async function Dr(e=Bun.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await ut(e))["auto-update"]!=="off"}catch{return!0}}async function xr(e=Bun.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${On.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(cp),headers:{accept:"application/json"}});if(!n.ok)throw Error(`registry answered HTTP ${n.status}`);let o=await n.json();if(typeof o.version!=="string")throw Error("registry answer has no version");return o.version}function Or(e,t=je){return Bun.semver.order(e,t)===1}async function lp(e){let t=await Bun.$`${process.execPath} add -g ${`${On}@${e}`}`.quiet().nothrow();return{ok:t.exitCode===0,output:`${t.stdout}${t.stderr}`.trim()}}async function Mr(e=Bun.env){let t=await $a(e),n=new Date().toISOString(),o;try{o=await xr(e)}catch(r){throw await At({...t,checkedAt:n,error:r.message},e),r}if(!Or(o))return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};if(!va())return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`bun add -g ${On}@${o}`};let s=await lp(o);if(!s.ok)return await At({...t,checkedAt:n,latest:o,error:s.output.slice(-500)},e),{latest:o,action:"failed",detail:s.output};return await At({checkedAt:n,latest:o,updatedFrom:je,updatedTo:o,noticeShown:!1},e),{latest:o,action:"installed"}}async function Sa(e=Bun.env){if(!va()||!await Dr(e))return null;let t=await $a(e),n=null;if(t.updatedTo===je&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${je}; refresh installed skills: fui skill install`,t.noticeShown=!0,await At(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<ap)return n;return await At({...t,checkedAt:new Date().toISOString()},e),Bun.spawn([process.execPath,Bun.main,"update","--background"],{stdio:["ignore","ignore","ignore"],env:{...e,FUI_AUTO_UPDATE:"off"}}).unref(),n}function ja(e){let t=e[0];if(!t)throw new f(`missing <key>; keys: ${no.join(", ")}`);if(!no.includes(t))throw new f(`unknown key "${t}"; keys: ${no.join(", ")}`);return t}async function dp(){let e=await Dr(),t=Bun.env.FUI_AUTO_UPDATE?.trim(),n=(await ut())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?Ze():"built-in (global bun installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var pp={name:"config",usage:"fui config",summary:"Show settings with their effective value and source",async run({workdir:e,render:t}){let{base:n,token:o}=await oo();E(`# config file: ${Ze()}`),k(X([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await dp()],t))}},mp={name:"config set",usage:"fui config set workdir <path> | api-base <url> | api-token - | auto-update on|off",summary:"Save a setting to the fui config file (api-token only from stdin)",async run({positionals:e}){let t=ja(e),n=e[1];if(!n)throw new f(`missing <value> for ${t}`);let o;if(t==="workdir")o=Wt(n),await up(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new f("auto-update takes on or off");o=n}else if(t==="api-token")o=await Pe(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await ut();s[t]=o;let r=await Ko(s),i={workdir:"FUI_MCP_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(Bun.env[i])E(`# note: ${i} is set in this shell and overrides the config file`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
79
- `)}},fp={name:"config unset",usage:"fui config unset <workdir|api-base|api-token|auto-update>",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=ja(e),n=await ut();delete n[t];let o=await Ko(n);k(`unset ${t} in ${o}
80
- `)}},Ia=[pp,mp,fp];var hp={name:"db list",mcpName:"db_config_read",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=h(t,"project"),s=o?[tr(o)]:[...new Set([...await e.listProjects(),...await e.listLegacyProjects()])].sort(),r=[];for(let i of s){let a=await e.load(i),c=await e.loadLegacy(i),l=c?.apiName??"legacy";if(c&&!me(a,l))r.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),E(`# ${i}: _db/config.json is the pre-1.4 format and unused; run: fui db import-legacy -p ${i}`);for(let[u,d]of Object.entries(a.connections)){let p="invalid",m=d.database??"";try{if(d.dbToken){let w=io(d.dbToken);p=w.engine,m||=w.database}}catch{}r.push({db:`${i}/${u}`,engine:p,default:a.default===u?"*":"",apiDomain:d.apiDomain??"",database:m,userToken:d.userToken?"yes":"no"})}}if(r.length===0){E("no connections; add one: fui db add <alias> -p <project> --token -");return}k(X(r,n))}},gp={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
81
- [--api-domain host] [--sql-server host] [--database name] [--user-token-env VAR] [--default] [--no-test]`,summary:"Add or replace a connection: test, save, and (MSSQL) fetch the schema",options:{token:{type:"string"},"token-env":{type:"string"},conn:{type:"string"},"conn-env":{type:"string"},"api-domain":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},"user-token-env":{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,positionals:n,targets:o}=e,s=n[0];if(!s)throw new f("missing <alias>");let r=(await o.resolveProject(h(t,"project"))).projectId,i=await Pe(h(t,"token"),h(t,"token-env"),"--token"),a=await Pe(h(t,"conn"),h(t,"conn-env"),"--conn");if(i===void 0===(a===void 0))throw new f("pass exactly one of --token/--token-env or --conn/--conn-env");let c=await _n(await z(e),r,{alias:s,dbToken:i??Zo(a),apiDomain:h(t,"api-domain"),sqlServer:h(t,"sql-server"),database:h(t,"database"),userToken:await Pe(void 0,h(t,"user-token-env"),"--user-token"),makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${c.notes.join(`
77
+ `)}var lp={name:"db user-token",mcpName:"db_user_token_set",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Pe(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new h("pass --from or --token/--token-env, not both");let o=await la(await z(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});xn(o.notes)}},up={name:"db token-rebuild",mcpName:"db_token_rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=f(e.values,"uid");if(!t)throw new h("missing --uid");let n=await Pe(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new h("missing --pwd - or --pwd-env VAR");xn((await ca(await z(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},dp={name:"db alias list",mcpName:"db_alias_list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...Dr},async run(e){let t=await ua(await z(e),{...await xr(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)k(X(t.rows,e.render))}},pp={name:"db alias new",mcpName:"db_alias_new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...Dr},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new h("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Pe(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new h("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new h("--module-id must be an integer");let u=await da(await z(e),{...await xr(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});xn(u.notes)}},mp={name:"db add-by-name",mcpName:"db_connect_by_name",usage:"fui db add-by-name <apiName> [-p P] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{"borrow-from":{type:"string"},default:{type:"boolean"},...Dr},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <apiName>; see: fui db alias list");let n=await pa(await z(e),{...await xr(e),apiName:t,borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});xn(n.notes)}};function ya(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function ba(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var ka={"api-name":{type:"string"}};function _r(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new h(`${t} must be ${o}`);return s}function ga(e,t,n){if(e!==void 0&&e.length>t)throw new h(`${n} is at most ${t} characters`);return e}function wa(e){return{name:`right ${e} list`,mcpName:`right_${e}_list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:ka,async run(t){let{apiName:n,rows:o}=await ha(await z(t),e,f(t.values,"api-name"));if(T(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){T(`# none defined; create one: fui right ${e} new ...`);return}k(X(e==="system"?ya(o):ba(o),t.render))}}}function Qt(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,mcpName:`right_${e}_${t}`,usage:n,summary:o[e][t],options:{...ka,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:_r(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:ga(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:_r(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:_r(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:ga(f(r,"name"),100,"--name")}}let c=await fa(await z(s),a,f(r,"api-name"));xn(c.notes),k(X(e==="system"?ya(c.rows):ba(c.rows),s.render))}}}var va=[lp,up,dp,pp,mp,wa("system"),Qt("system","new"),Qt("system","update"),Qt("system","delete"),wa("function"),Qt("function","new"),Qt("function","update"),Qt("function","delete")];import{mkdir as kp}from"fs/promises";import{realpathSync as fp}from"fs";import{join as gp,sep as Zt}from"path";var je="1.0.3";var On="@fui-org/fui-cli",wp=3600000,yp=5000;function Sa(e=Bun.env){return gp(et(e),"..","update.json")}async function $a(e=Bun.env){try{let t=await Bun.file(Sa(e)).json();if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function Nt(e,t=Bun.env){await Bun.write(Sa(t),`${JSON.stringify(e,null,2)}
78
+ `)}function ja(e=Bun.main){let t;try{t=fp(e)}catch{return null}let n=`${Zt}install${Zt}global${Zt}node_modules${Zt}@fui-org${Zt}fui-cli${Zt}`,o=t.indexOf(n);return o<0?null:t.slice(0,o+n.length-1)}async function Or(e=Bun.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await dt(e))["auto-update"]!=="off"}catch{return!0}}async function Mr(e=Bun.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${On.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(yp),headers:{accept:"application/json"}});if(!n.ok)throw Error(`registry answered HTTP ${n.status}`);let o=await n.json();if(typeof o.version!=="string")throw Error("registry answer has no version");return o.version}function Fr(e,t=je){return Bun.semver.order(e,t)===1}async function bp(e){let t=await Bun.$`${process.execPath} add -g ${`${On}@${e}`}`.quiet().nothrow();return{ok:t.exitCode===0,output:`${t.stdout}${t.stderr}`.trim()}}async function Lr(e=Bun.env){let t=await $a(e),n=new Date().toISOString(),o;try{o=await Mr(e)}catch(r){throw await Nt({...t,checkedAt:n,error:r.message},e),r}if(!Fr(o))return await Nt({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};if(!ja())return await Nt({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`bun add -g ${On}@${o}`};let s=await bp(o);if(!s.ok)return await Nt({...t,checkedAt:n,latest:o,error:s.output.slice(-500)},e),{latest:o,action:"failed",detail:s.output};return await Nt({checkedAt:n,latest:o,updatedFrom:je,updatedTo:o,noticeShown:!1},e),{latest:o,action:"installed"}}async function Ia(e=Bun.env){if(!ja()||!await Or(e))return null;let t=await $a(e),n=null;if(t.updatedTo===je&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${je}; refresh installed skills: fui skill install`,t.noticeShown=!0,await Nt(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<wp)return n;return await Nt({...t,checkedAt:new Date().toISOString()},e),Bun.spawn([process.execPath,Bun.main,"update","--background"],{stdio:["ignore","ignore","ignore"],env:{...e,FUI_AUTO_UPDATE:"off"}}).unref(),n}function Ca(e){let t=e[0];if(!t)throw new h(`missing <key>; keys: ${no.join(", ")}`);if(!no.includes(t))throw new h(`unknown key "${t}"; keys: ${no.join(", ")}`);return t}async function vp(){let e=await Or(),t=Bun.env.FUI_AUTO_UPDATE?.trim(),n=(await dt())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?et():"built-in (global bun installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var Sp={name:"config",usage:"fui config",summary:"Show settings with their effective value and source",async run({workdir:e,render:t}){let{base:n,token:o}=await oo();T(`# config file: ${et()}`),k(X([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await vp()],t))}},$p={name:"config set",usage:"fui config set workdir <path> | api-base <url> | api-token - | auto-update on|off",summary:"Save a setting to the fui config file (api-token only from stdin)",async run({positionals:e}){let t=Ca(e),n=e[1];if(!n)throw new h(`missing <value> for ${t}`);let o;if(t==="workdir")o=qt(n),await kp(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new h("auto-update takes on or off");o=n}else if(t==="api-token")o=await Pe(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await dt();s[t]=o;let r=await Go(s),i={workdir:"FUI_MCP_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(Bun.env[i])T(`# note: ${i} is set in this shell and overrides the config file`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
79
+ `)}},jp={name:"config unset",usage:"fui config unset <workdir|api-base|api-token|auto-update>",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=Ca(e),n=await dt();delete n[t];let o=await Go(n);k(`unset ${t} in ${o}
80
+ `)}},Ra=[Sp,$p,jp];var Ip={name:"db list",mcpName:"db_config_read",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=f(t,"project"),s=o?[or(o)]:[...new Set([...await e.listProjects(),...await e.listLegacyProjects()])].sort(),r=[];for(let i of s){let a=await e.load(i),c=await e.loadLegacy(i),l=c?.apiName??"legacy";if(c&&!me(a,l))r.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),T(`# ${i}: _db/config.json is the pre-1.4 format and unused; run: fui db import-legacy -p ${i}`);for(let[u,d]of Object.entries(a.connections)){let p="invalid",m=d.database??"";try{if(d.dbToken){let w=io(d.dbToken);p=w.engine,m||=w.database}}catch{}r.push({db:`${i}/${u}`,engine:p,default:a.default===u?"*":"",apiDomain:d.apiDomain??"",database:m,userToken:d.userToken?"yes":"no"})}}if(r.length===0){T("no connections; add one: fui db add <alias> -p <project> --token -");return}k(X(r,n))}},Cp={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
81
+ [--api-domain host] [--sql-server host] [--database name] [--user-token-env VAR] [--default] [--no-test]`,summary:"Add or replace a connection: test, save, and (MSSQL) fetch the schema",options:{token:{type:"string"},"token-env":{type:"string"},conn:{type:"string"},"conn-env":{type:"string"},"api-domain":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},"user-token-env":{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,positionals:n,targets:o}=e,s=n[0];if(!s)throw new h("missing <alias>");let r=(await o.resolveProject(f(t,"project"))).projectId,i=await Pe(f(t,"token"),f(t,"token-env"),"--token"),a=await Pe(f(t,"conn"),f(t,"conn-env"),"--conn");if(i===void 0===(a===void 0))throw new h("pass exactly one of --token/--token-env or --conn/--conn-env");let c=await _n(await z(e),r,{alias:s,dbToken:i??tr(a),apiDomain:f(t,"api-domain"),sqlServer:f(t,"sql-server"),database:f(t,"database"),userToken:await Pe(void 0,f(t,"user-token-env"),"--user-token"),makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${c.notes.join(`
82
82
  `)}
83
- `)}},wp={name:"db default",usage:"fui db default <project>/<alias>",summary:"Set the project default connection",async run(e){let t=e.positionals[0]??h(e.values,"db");if(!t)throw new f("missing <project>/<alias>");let n=await z(e),o=await e.store.resolveProject({db:t,project:n.project,contextProject:n.contextProject}),s=await e.store.setDefault(o,t.split("/").pop());k(`default ${o}/${s}
84
- `)}},yp={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run(e){let t=await z(e),n=await e.store.resolve({db:h(e.values,"db"),project:t.project,contextProject:t.contextProject}),o=Bun.nanoseconds();await ve(n,(r)=>r.query("SELECT 1 AS ok"));let s=Math.round((Bun.nanoseconds()-o)/1e6);k(`ok ${n.projectId}/${n.alias} ${n.engine} ${s}ms
85
- `)}},bp={name:"db import-legacy",usage:"fui db import-legacy -p <project> [--alias name] [--default] [--no-test]",summary:"Import a pre-1.4 _db/config.json as a named connection (same flow as db add; secrets never printed)",options:{alias:{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,targets:n,store:o}=e,s=(await n.resolveProject(h(t,"project"))).projectId,r=await o.loadLegacy(s);if(!r)throw new f(`project "${s}" has no legacy _db/config.json with a dbToken`);let i=h(t,"alias")??r.apiName;if(!i)throw new f("legacy config has no apiName; pass --alias <name> (the tAPI alias in module.json API paths)");let a=await _n(await z(e),s,{alias:i,dbToken:r.dbToken,apiDomain:r.apiDomain,sqlServer:r.sqlServer,database:r.database,userToken:r.userToken,makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${a.notes.join(`
83
+ `)}},Rp={name:"db default",usage:"fui db default <project>/<alias>",summary:"Set the project default connection",async run(e){let t=e.positionals[0]??f(e.values,"db");if(!t)throw new h("missing <project>/<alias>");let n=await z(e),o=await e.store.resolveProject({db:t,project:n.project,contextProject:n.contextProject}),s=await e.store.setDefault(o,t.split("/").pop());k(`default ${o}/${s}
84
+ `)}},Pp={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run(e){let t=await z(e),n=await e.store.resolve({db:f(e.values,"db"),project:t.project,contextProject:t.contextProject}),o=Bun.nanoseconds();await Se(n,(r)=>r.query("SELECT 1 AS ok"));let s=Math.round((Bun.nanoseconds()-o)/1e6);k(`ok ${n.projectId}/${n.alias} ${n.engine} ${s}ms
85
+ `)}},Tp={name:"db import-legacy",usage:"fui db import-legacy -p <project> [--alias name] [--default] [--no-test]",summary:"Import a pre-1.4 _db/config.json as a named connection (same flow as db add; secrets never printed)",options:{alias:{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run(e){let{values:t,targets:n,store:o}=e,s=(await n.resolveProject(f(t,"project"))).projectId,r=await o.loadLegacy(s);if(!r)throw new h(`project "${s}" has no legacy _db/config.json with a dbToken`);let i=f(t,"alias")??r.apiName;if(!i)throw new h("legacy config has no apiName; pass --alias <name> (the tAPI alias in module.json API paths)");let a=await _n(await z(e),s,{alias:i,dbToken:r.dbToken,apiDomain:r.apiDomain,sqlServer:r.sqlServer,database:r.database,userToken:r.userToken,makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${a.notes.join(`
86
86
  `)}
87
87
  legacy _db/config.json left unchanged
88
- `)}},Ca=[hp,gp,bp,wp,yp];import{readdir as Rp}from"fs/promises";import{join as Lr}from"path";import{readdir as kp}from"fs/promises";import{basename as $p,dirname as vp,extname as Sp,isAbsolute as jp,join as bo,relative as yo,resolve as Ip,sep as Cp}from"path";function ko(e,t,n=process.cwd()){let o=Ip(n,t),s=yo(e.root,o);if(!s||s.startsWith("..")||jp(s))return null;let r=s.split(Cp),i=r[0];if(i.startsWith("_")||r.length<2)return null;if(r[1]==="modules"&&r.length>=4){let c=r[2],l=bo(e.root,i,"modules",c);return{abs:o,baseDir:l,rel:ke(yo(l,o)),projectId:i,moduleId:c}}let a=bo(e.root,i);return{abs:o,baseDir:a,rel:ke(yo(a,o)),projectId:i}}var Ra=(e)=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function Fr(e,t){let n=yo(e.root,t),o=Sp(n),s=$p(n,o),r=bo(e.historyRoot(),vp(n)),i=new RegExp(`^${Ra(s)}_(\\d{8}_\\d{6})((?:_[A-Za-z0-9-]+)*)${Ra(o)}$`),a;try{a=await kp(r)}catch{return[]}return a.map((c)=>{let l=i.exec(c);return l?{stamp:l[1],suffix:l[2].replace(/^_/,""),path:bo(r,c)}:null}).filter((c)=>c!==null).sort((c,l)=>l.stamp.localeCompare(c.stamp)||l.path.localeCompare(c.path))}function Ur(e,t){let n=ko(e.paths,t);if(!n)throw new f(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Pp={name:"file where",usage:"fui file where [-p project -m module]",summary:"Show the workspace, resolved project/module, their folders and where each came from",options:{module:{type:"string",short:"m"}},async run(e){let{paths:t,workdir:n,session:o,targets:s,values:r,render:i}=e,a=[{key:"workspace",value:t.root,source:n.source},{key:"session",value:o.file,source:"FUI_SESSION"}];try{let c=await s.resolveProjectModule(h(r,"project"),h(r,"module"));a.push({key:"project",value:c.projectId,source:c.source},{key:"module",value:c.moduleId,source:c.source},{key:"module-dir",value:c.moduleDir,source:c.source})}catch{try{let c=await s.resolveProject(h(r,"project"));a.push({key:"project",value:c.projectId,source:c.source})}catch{a.push({key:"project",value:"",source:"none"})}}k(X(a,i))}};async function Ep(e){try{return(await Rp(Lr(e,"modules"),{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>Lr(e,"modules",n.name))}catch{return[]}}var Tp={name:"file status",usage:"fui file status [-p project -m module] [--all]",summary:"Local files not matching the last publish/sync (ledger); --all = every checked-out module of the project",options:{module:{type:"string",short:"m"},all:{type:"boolean"}},async run(e){let{values:t,targets:n,paths:o,render:s}=e,r=[];if(t.all===!0){let{projectId:a}=await n.resolveProject(h(t,"project"));r.push(o.projectDir(a),...await Ep(o.projectDir(a)))}else{let a=await n.resolveProjectModule(h(t,"project"),h(t,"module"));r.push(a.moduleDir)}let i=[];for(let a of r)for(let c of await Tn(a))i.push({dir:a,path:c.relPath,reason:c.reason,push:mo(c.relPath)});E(`# ${i.length} unpublished file(s)`),k(X(i,s))}},Ap={name:"file history",usage:"fui file history <path>",summary:"List archived versions of a workspace file (newest first)",async run(e){let t=e.positionals[0];if(!t)throw new f("missing <path>");let n=Ur(e,t),o=await Fr(e.paths,n.abs);E(`# ${o.length} version(s) of ${n.rel}`),k(X(o.map((s)=>({...s,path:jt(e.paths,s.path)})),e.render))}},Np={name:"file restore",usage:"fui file restore <path> [--at YYYYMMDD_HHMMSS]",summary:"Copy an archived version back (current file archived first); local only, ledger untouched",options:{at:{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <path>");let n=Ur(e,t),o=await Fr(e.paths,n.abs),s=h(e.values,"at"),r=s?o.find((c)=>c.stamp===s):o[0];if(!r)throw new f(s?`no version ${s} of ${n.rel}; run: fui file history ${t}`:`no archived versions of ${n.rel}`);let i=await pe(n.abs),a=i===null?null:await he(e.paths,n.abs,{content:i});if(await Bun.write(n.abs,await H(r.path)),a)E(`# previous content archived: ${jt(e.paths,a)}`);k(`restored ${n.rel} from ${r.stamp}; push it with: ${mo(n.rel)}
89
- `)}},_p={name:"file ledger",usage:"fui file ledger <path...> [--mark | --forget]",summary:"Show, mark or forget publish-ledger entries (_publishState.json)",options:{mark:{type:"boolean"},forget:{type:"boolean"}},async run(e){let{positionals:t,values:n,render:o}=e;if(t.length===0)throw new f("missing <path>");if(n.mark===!0&&n.forget===!0)throw new f("pass --mark or --forget, not both");let s=t.map((a)=>Ur(e,a)),r=Map.groupBy(s,(a)=>a.baseDir);for(let[a,c]of r){let l=c.map((u)=>u.rel);if(n.mark===!0)await Te(a,l);if(n.forget===!0)await Ci(a,l)}let i=[];for(let[a,c]of r){let l={};try{l=JSON.parse(await H(Lr(a,Gt)))}catch{}for(let u of c){let d=await pe(u.abs),p=l[u.rel]??"",m=d===null?"missing":!p?"new":p===Rt(d)?"published":"changed";i.push({path:u.rel,state:m,ledger:p,dir:a})}}k(X(i,o))}},Pa=[Pp,Tp,Ap,Np,_p];import{rename as om}from"fs/promises";import{basename as rm,dirname as es,join as Be}from"path";function en(e){return e==="text/css"||e==="text/javascript"}function Dp(e){return(e||"").trim().replace(/[<>:"/\\|?*\x00-\x1F]/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"import-file"}function Mn(e,t,n){let o=n.startsWith(".")?n:`.${n}`;return`${Dp(e)}-${t}${o}`}function mt(e){return e.map((t)=>({name:t.FileName,type:t.FileType,contentType:t.ContentType,fileID:t.fileID,scope:t.Scope,sort:t.FileSort??null,...en(t.ContentType)?{filePath:`imports/${Mn(t.FileName,t.fileID,t.FileType)}`}:{}}))}function tn(e,t,n=[],o="",s="",r=[]){let i={},a={ModuleName:e.ModuleName,data:[],watch:{},controls:[],set:{}},c=t.moduleUI;if(c&&c.trim())try{a=JSON.parse(c)}catch(l){throw Error(`module.json of module "${e.ModuleID}" from the server is not valid JSON: ${l.message}; nothing written, local copy kept. ${c.length} chars, starts with ${JSON.stringify(c.slice(0,200))}`)}i["module.json"]=JSON.stringify(a,null,2),i["script.js"]=o??"",i["style.css"]=s??"",i["header.html"]=t.mHeader??"",i["body.html"]=t.mBody??"",i["imports/_imports.json"]=JSON.stringify(mt(r.filter((l)=>l.Scope!=="project")),null,2),i["components/_components.json"]=JSON.stringify(n.map((l)=>({comID:l.comID,comName:l.comName})),null,2);for(let l of n)i[`components/${l.comName}.vue`]=l.comCode;return i}function Br(e,t,n,o,s){let r={};r["_projectInfo.json"]=JSON.stringify(e,null,2),r["project.json"]=JSON.stringify(t,null,2),r["modules/_modules.json"]=JSON.stringify(n,null,2),r["imports/_imports.json"]=JSON.stringify(mt(s),null,2),r["components/_components.json"]=JSON.stringify(o.map((i)=>({comID:i.comID,comName:i.comName})),null,2);for(let i of o)r[`components/${i.comName}.vue`]=i.comCode;return r}function Ea(e,t){let n=[`${e}/`],o=new Set;for(let s of Object.keys(t).sort()){let r=s.split("/");for(let i=0;i<r.length-1;i++){let a=r.slice(0,i+1).join("/");if(!o.has(a))o.add(a),n.push(`${" ".repeat(i+1)}${r[i]}/`)}n.push(`${" ".repeat(r.length)}${r[r.length-1]}`)}return n.join(`
90
- `)}import{mkdir as nn,readdir as Nt,rm as xp,stat as Op,unlink as Hr}from"fs/promises";import{basename as Ta,dirname as $o,join as se}from"path";function _t(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function F(e,t=""){try{return await H(e)}catch{return t}}async function Wr(e,t){try{return await H(e)}catch(n){if(Sn(n)==="ENOENT")throw new f(`local ${t} does not exist (${e}); run fui module get first, or check -p/-m. Nothing published (an empty publish would wipe the server copy)`);throw Error(`cannot read ${t} at ${e}: ${n.message}`)}}async function U(e,t){try{return JSON.parse(await H(e))}catch{return t}}async function ht(e){try{return(await Op(e)).isDirectory()}catch{return!1}}async function Me(e){try{return(await Nt(e,{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.endsWith(".vue")).map((n)=>n.name)}catch{return[]}}async function Fn(e,t){let n=[];for(let[o,s]of Object.entries(t)){let r=se(e,o);await nn($o(r),{recursive:!0}),await Bun.write(r,s),n.push(r)}return n}async function Aa(e,t,n){let o=[],s;try{s=await Nt(t,{withFileTypes:!0})}catch{return o}for(let r of s){if(!r.isFile()||!r.name.endsWith(".vue")||n.has(r.name))continue;let i=se(t,r.name);await he(e,i,{suffix:"deleted"}),await Hr(i),o.push(i)}return o}function Na(e){return new Set(Object.keys(e).filter((t)=>t.startsWith("components/")&&t.endsWith(".vue")).map((t)=>Ta(t)))}async function qr(e,t,n){return await nn(t,{recursive:!0}),await Aa(e,se(t,"components"),Na(n)),Ae(e,t,n)}async function _a(e,t,n,o,s){await nn(t,{recursive:!0});let r={"_moduleInfo.json":JSON.stringify({...n,HTMLOnly:o.HTMLOnly,Framework:o.Framework,mTitle:o.mTitle},null,2),...s},i=[];for(let l of["header.html","body.html"])if(!r[l])try{let u=await H(se(t,l));if(u.trim())i.push(`${l}: server returned empty; local content (${u.length} chars) was overwritten. check the server SP if unexpected`)}catch{}let a=await Aa(e,se(t,"components"),Na(r)),c=await Ae(e,t,r);return{moduleDir:t,removedFiles:a,warnings:i,sync:c}}async function Da(e,t,n){let o=await U(se(t,"components","_components.json"),[]),s=[];if(!Array.isArray(o))return s;for(let r of o)if(r?.comName&&!n.has(r.comName)){let i=se(t,"components",`${r.comName}.vue`);try{await he(e,i,{suffix:"deleted"}),await Hr(i),s.push(`${r.comName}.vue`)}catch{}}return s}async function Ln(e,t,n){let o=se(t,"imports"),s=[],r;try{r=await Nt(o,{withFileTypes:!0})}catch{return s}let i=new Set([...n].map((a)=>Ta(a)));for(let a of r){if(!a.isFile()||a.name==="_imports.json"||i.has(a.name))continue;let c=se(o,a.name);try{await he(e,c,{suffix:"deleted"}),await Hr(c),s.push(`imports/${a.name}`)}catch{}}return s}async function xa(e,t,n){let o=se(t,"modules"),s=[],r;try{r=await Nt(o,{withFileTypes:!0})}catch{return s}for(let i of r){if(!i.isDirectory()||n.has(i.name))continue;let a=se(o,i.name);await co(e,a,{suffix:"deleted"}),await xp(a,{recursive:!0,force:!0}),s.push(i.name)}return s}function Jr(e){if(!e||typeof e!=="object")return!1;let t=e;return typeof t.ModuleID==="string"&&t.mDomain!==void 0&&typeof t.ProjectID==="string"}function Vr(e,t){return se(e.projectDir(t),"modules","_modules.json")}async function Oa(e,t){let n=await U(Vr(e,t),[]);return Array.isArray(n)?n.filter(Jr):[]}async function Un(e,t,n){let o=e.projectDir(t);await Fn(o,{"modules/_modules.json":JSON.stringify(n,null,2)});for(let s of n){let r=se(o,"modules",s.ModuleID,"_moduleInfo.json"),i=await U(r,null);if(!i)continue;await Bun.write(r,JSON.stringify({...i,...s},null,2))}return n.length}async function vo(e,t,n,o){let s=Vr(e,t),r=await U(s,[]),i=Array.isArray(r)?r.filter(Jr):[],a=o??n.ModuleID,{mTitle:c,...l}=n,u=i.findIndex((d)=>d.ModuleID===a||n.id&&d.id===n.id);if(u>=0)i[u]={...i[u],...l};else i.push(l);await nn($o(s),{recursive:!0}),await Bun.write(s,JSON.stringify(i,null,2))}async function Ma(e,t,n){let o=Vr(e,t),s=await U(o,[]),r=Array.isArray(s)?s.filter(Jr):[],i=r.findIndex((a)=>a.ModuleID===n);if(i<0)return!1;return r.splice(i,1),await nn($o(o),{recursive:!0}),await Bun.write(o,JSON.stringify(r,null,2)),!0}async function Fa(e){try{return(await Nt(e.root,{withFileTypes:!0})).filter((n)=>n.isDirectory()&&!n.name.startsWith("_")).map((n)=>n.name)}catch{return[]}}async function La(e){let t=[],n;try{n=await Nt(e.root,{withFileTypes:!0})}catch{return t}for(let o of n){if(!o.isDirectory()||o.name.startsWith("_"))continue;let s=se(e.root,o.name);t.push({baseDir:s,projectId:o.name});try{for(let r of await Nt(se(s,"modules"),{withFileTypes:!0}))if(r.isDirectory())t.push({baseDir:se(s,"modules",r.name),projectId:o.name,moduleId:r.name})}catch{}}return t}async function Kr(e,t){let n=[];for(let o of await La(e)){let s=await U(se(o.baseDir,"imports/_imports.json"),[]),r=Array.isArray(s)?s.find((i)=>i?.fileID===t):void 0;if(r)n.push({...o,entry:r})}return n}async function Gr(e,t,n){let o=[];for(let{baseDir:s}of await La(e)){let r=await U(se(s,"imports/_imports.json"),[]),i=Array.isArray(r)?r.find((c)=>c?.fileID===t&&c?.filePath):void 0;if(!i?.filePath)continue;let a=se(s,i.filePath);try{await nn($o(a),{recursive:!0}),await Bun.write(a,n),o.push(a)}catch{}}return o}var Mp=20,Fp=50;function zr(e,t){let n=t==="module"?Fp:Mp;if(e===void 0)return{effectiveSort:n,note:`fileSort not given; using ${n} (${t} imports must load after the framework)`};if(e<n)return{effectiveSort:n,note:`fileSort ${e} raised to ${n}: lower values load before the framework (~1-10) and can break the page`};return{effectiveSort:e,note:""}}function gt(e){return JSON.stringify(e??{},null,2)}function ft(e){return e.replace(/\r\n/g,`
91
- `).trim()}var Lp=["module.json","header.html","body.html","script.js","style.css"];async function So(e){let t=[];for(let o of Lp)t.push(`${o}
88
+ `)}},Pa=[Ip,Cp,Tp,Rp,Pp];import{readdir as Mp}from"fs/promises";import{join as Hr}from"path";import{readdir as Ep}from"fs/promises";import{basename as Np,dirname as Ap,extname as _p,isAbsolute as Dp,join as bo,relative as yo,resolve as xp,sep as Op}from"path";function ko(e,t,n=process.cwd()){let o=xp(n,t),s=yo(e.root,o);if(!s||s.startsWith("..")||Dp(s))return null;let r=s.split(Op),i=r[0];if(i.startsWith("_")||r.length<2)return null;if(r[1]==="modules"&&r.length>=4){let c=r[2],l=bo(e.root,i,"modules",c);return{abs:o,baseDir:l,rel:ke(yo(l,o)),projectId:i,moduleId:c}}let a=bo(e.root,i);return{abs:o,baseDir:a,rel:ke(yo(a,o)),projectId:i}}var Ta=(e)=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function Ur(e,t){let n=yo(e.root,t),o=_p(n),s=Np(n,o),r=bo(e.historyRoot(),Ap(n)),i=new RegExp(`^${Ta(s)}_(\\d{8}_\\d{6})((?:_[A-Za-z0-9-]+)*)${Ta(o)}$`),a;try{a=await Ep(r)}catch{return[]}return a.map((c)=>{let l=i.exec(c);return l?{stamp:l[1],suffix:l[2].replace(/^_/,""),path:bo(r,c)}:null}).filter((c)=>c!==null).sort((c,l)=>l.stamp.localeCompare(c.stamp)||l.path.localeCompare(c.path))}function Br(e,t){let n=ko(e.paths,t);if(!n)throw new h(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Fp={name:"file where",usage:"fui file where [-p project -m module]",summary:"Show the workspace, resolved project/module, their folders and where each came from",options:{module:{type:"string",short:"m"}},async run(e){let{paths:t,workdir:n,session:o,targets:s,values:r,render:i}=e,a=[{key:"workspace",value:t.root,source:n.source},{key:"session",value:o.file,source:"FUI_SESSION"}];try{let c=await s.resolveProjectModule(f(r,"project"),f(r,"module"));a.push({key:"project",value:c.projectId,source:c.source},{key:"module",value:c.moduleId,source:c.source},{key:"module-dir",value:c.moduleDir,source:c.source})}catch{try{let c=await s.resolveProject(f(r,"project"));a.push({key:"project",value:c.projectId,source:c.source})}catch{a.push({key:"project",value:"",source:"none"})}}k(X(a,i))}};async function Lp(e){try{return(await Mp(Hr(e,"modules"),{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>Hr(e,"modules",n.name))}catch{return[]}}var Up={name:"file status",usage:"fui file status [-p project -m module] [--all]",summary:"Local files not matching the last publish/sync (ledger); --all = every checked-out module of the project",options:{module:{type:"string",short:"m"},all:{type:"boolean"}},async run(e){let{values:t,targets:n,paths:o,render:s}=e,r=[];if(t.all===!0){let{projectId:a}=await n.resolveProject(f(t,"project"));r.push(o.projectDir(a),...await Lp(o.projectDir(a)))}else{let a=await n.resolveProjectModule(f(t,"project"),f(t,"module"));r.push(a.moduleDir)}let i=[];for(let a of r)for(let c of await En(a))i.push({dir:a,path:c.relPath,reason:c.reason,push:mo(c.relPath)});T(`# ${i.length} unpublished file(s)`),k(X(i,s))}},Hp={name:"file history",usage:"fui file history <path>",summary:"List archived versions of a workspace file (newest first)",async run(e){let t=e.positionals[0];if(!t)throw new h("missing <path>");let n=Br(e,t),o=await Ur(e.paths,n.abs);T(`# ${o.length} version(s) of ${n.rel}`),k(X(o.map((s)=>({...s,path:jt(e.paths,s.path)})),e.render))}},Bp={name:"file restore",usage:"fui file restore <path> [--at YYYYMMDD_HHMMSS]",summary:"Copy an archived version back (current file archived first); local only, ledger untouched",options:{at:{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <path>");let n=Br(e,t),o=await Ur(e.paths,n.abs),s=f(e.values,"at"),r=s?o.find((c)=>c.stamp===s):o[0];if(!r)throw new h(s?`no version ${s} of ${n.rel}; run: fui file history ${t}`:`no archived versions of ${n.rel}`);let i=await le(n.abs),a=i===null?null:await fe(e.paths,n.abs,{content:i});if(await Bun.write(n.abs,await B(r.path)),a)T(`# previous content archived: ${jt(e.paths,a)}`);k(`restored ${n.rel} from ${r.stamp}; push it with: ${mo(n.rel)}
89
+ `)}},qp={name:"file ledger",usage:"fui file ledger <path...> [--mark | --forget]",summary:"Show, mark or forget publish-ledger entries (_publishState.json)",options:{mark:{type:"boolean"},forget:{type:"boolean"}},async run(e){let{positionals:t,values:n,render:o}=e;if(t.length===0)throw new h("missing <path>");if(n.mark===!0&&n.forget===!0)throw new h("pass --mark or --forget, not both");let s=t.map((a)=>Br(e,a)),r=Map.groupBy(s,(a)=>a.baseDir);for(let[a,c]of r){let l=c.map((u)=>u.rel);if(n.mark===!0)await Ee(a,l);if(n.forget===!0)await Pi(a,l)}let i=[];for(let[a,c]of r){let l={};try{l=JSON.parse(await B(Hr(a,Gt)))}catch{}for(let u of c){let d=await le(u.abs),p=l[u.rel]??"",m=d===null?"missing":!p?"new":p===Rt(d)?"published":"changed";i.push({path:u.rel,state:m,ledger:p,dir:a})}}k(X(i,o))}},Ea=[Fp,Up,Hp,Bp,qp];import{rename as mm}from"fs/promises";import{basename as hm,dirname as ns,join as He}from"path";function en(e){return e==="text/css"||e==="text/javascript"}function Wp(e){return(e||"").trim().replace(/[<>:"/\\|?*\x00-\x1F]/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"import-file"}function Mn(e,t,n){let o=n.startsWith(".")?n:`.${n}`;return`${Wp(e)}-${t}${o}`}function ht(e){return e.map((t)=>({name:t.FileName,type:t.FileType,contentType:t.ContentType,fileID:t.fileID,scope:t.Scope,sort:t.FileSort??null,...en(t.ContentType)?{filePath:`imports/${Mn(t.FileName,t.fileID,t.FileType)}`}:{}}))}function tn(e,t,n=[],o="",s="",r=[]){let i={},a={ModuleName:e.ModuleName,data:[],watch:{},controls:[],set:{}},c=t.moduleUI;if(c&&c.trim())try{a=JSON.parse(c)}catch(l){throw Error(`module.json of module "${e.ModuleID}" from the server is not valid JSON: ${l.message}; nothing written, local copy kept. ${c.length} chars, starts with ${JSON.stringify(c.slice(0,200))}`)}i["module.json"]=JSON.stringify(a,null,2),i["script.js"]=o??"",i["style.css"]=s??"",i["header.html"]=t.mHeader??"",i["body.html"]=t.mBody??"",i["imports/_imports.json"]=JSON.stringify(ht(r.filter((l)=>l.Scope!=="project")),null,2),i["components/_components.json"]=JSON.stringify(n.map((l)=>({comID:l.comID,comName:l.comName})),null,2);for(let l of n)i[`components/${l.comName}.vue`]=l.comCode;return i}function qr(e,t,n,o,s){let r={};r["_projectInfo.json"]=JSON.stringify(e,null,2),r["project.json"]=JSON.stringify(t,null,2),r["modules/_modules.json"]=JSON.stringify(n,null,2),r["imports/_imports.json"]=JSON.stringify(ht(s),null,2),r["components/_components.json"]=JSON.stringify(o.map((i)=>({comID:i.comID,comName:i.comName})),null,2);for(let i of o)r[`components/${i.comName}.vue`]=i.comCode;return r}function Na(e,t){let n=[`${e}/`],o=new Set;for(let s of Object.keys(t).sort()){let r=s.split("/");for(let i=0;i<r.length-1;i++){let a=r.slice(0,i+1).join("/");if(!o.has(a))o.add(a),n.push(`${" ".repeat(i+1)}${r[i]}/`)}n.push(`${" ".repeat(r.length)}${r[r.length-1]}`)}return n.join(`
90
+ `)}import{mkdir as nn,readdir as At,rm as Vp,stat as Jp,unlink as Wr}from"fs/promises";import{basename as Aa,dirname as vo,join as se}from"path";function _t(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function F(e,t=""){try{return await B(e)}catch{return t}}async function Vr(e,t){try{return await B(e)}catch(n){if($n(n)==="ENOENT")throw new h(`local ${t} does not exist (${e}); run fui module get first, or check -p/-m. Nothing published (an empty publish would wipe the server copy)`);throw Error(`cannot read ${t} at ${e}: ${n.message}`)}}async function U(e,t){try{return JSON.parse(await B(e))}catch{return t}}async function gt(e){try{return(await Jp(e)).isDirectory()}catch{return!1}}async function Me(e){try{return(await At(e,{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.endsWith(".vue")).map((n)=>n.name)}catch{return[]}}async function Fn(e,t){let n=[];for(let[o,s]of Object.entries(t)){let r=se(e,o);await nn(vo(r),{recursive:!0}),await Bun.write(r,s),n.push(r)}return n}async function _a(e,t,n){let o=[],s;try{s=await At(t,{withFileTypes:!0})}catch{return o}for(let r of s){if(!r.isFile()||!r.name.endsWith(".vue")||n.has(r.name))continue;let i=se(t,r.name);await fe(e,i,{suffix:"deleted"}),await Wr(i),o.push(i)}return o}function Da(e){return new Set(Object.keys(e).filter((t)=>t.startsWith("components/")&&t.endsWith(".vue")).map((t)=>Aa(t)))}async function Jr(e,t,n){return await nn(t,{recursive:!0}),await _a(e,se(t,"components"),Da(n)),Ne(e,t,n)}async function xa(e,t,n,o,s){await nn(t,{recursive:!0});let r={"_moduleInfo.json":JSON.stringify({...n,HTMLOnly:o.HTMLOnly,Framework:o.Framework,mTitle:o.mTitle},null,2),...s},i=[];for(let l of["header.html","body.html"])if(!r[l])try{let u=await B(se(t,l));if(u.trim())i.push(`${l}: server returned empty; local content (${u.length} chars) was overwritten. check the server SP if unexpected`)}catch{}let a=await _a(e,se(t,"components"),Da(r)),c=await Ne(e,t,r);return{moduleDir:t,removedFiles:a,warnings:i,sync:c}}async function Oa(e,t,n){let o=await U(se(t,"components","_components.json"),[]),s=[];if(!Array.isArray(o))return s;for(let r of o)if(r?.comName&&!n.has(r.comName)){let i=se(t,"components",`${r.comName}.vue`);try{await fe(e,i,{suffix:"deleted"}),await Wr(i),s.push(`${r.comName}.vue`)}catch{}}return s}async function Ln(e,t,n){let o=se(t,"imports"),s=[],r;try{r=await At(o,{withFileTypes:!0})}catch{return s}let i=new Set([...n].map((a)=>Aa(a)));for(let a of r){if(!a.isFile()||a.name==="_imports.json"||i.has(a.name))continue;let c=se(o,a.name);try{await fe(e,c,{suffix:"deleted"}),await Wr(c),s.push(`imports/${a.name}`)}catch{}}return s}async function Ma(e,t,n){let o=se(t,"modules"),s=[],r;try{r=await At(o,{withFileTypes:!0})}catch{return s}for(let i of r){if(!i.isDirectory()||n.has(i.name))continue;let a=se(o,i.name);await co(e,a,{suffix:"deleted"}),await Vp(a,{recursive:!0,force:!0}),s.push(i.name)}return s}function Kr(e){if(!e||typeof e!=="object")return!1;let t=e;return typeof t.ModuleID==="string"&&t.mDomain!==void 0&&typeof t.ProjectID==="string"}function Gr(e,t){return se(e.projectDir(t),"modules","_modules.json")}async function Fa(e,t){let n=await U(Gr(e,t),[]);return Array.isArray(n)?n.filter(Kr):[]}async function Un(e,t,n){let o=e.projectDir(t);await Fn(o,{"modules/_modules.json":JSON.stringify(n,null,2)});for(let s of n){let r=se(o,"modules",s.ModuleID,"_moduleInfo.json"),i=await U(r,null);if(!i)continue;await Bun.write(r,JSON.stringify({...i,...s},null,2))}return n.length}async function So(e,t,n,o){let s=Gr(e,t),r=await U(s,[]),i=Array.isArray(r)?r.filter(Kr):[],a=o??n.ModuleID,{mTitle:c,...l}=n,u=i.findIndex((d)=>d.ModuleID===a||n.id&&d.id===n.id);if(u>=0)i[u]={...i[u],...l};else i.push(l);await nn(vo(s),{recursive:!0}),await Bun.write(s,JSON.stringify(i,null,2))}async function La(e,t,n){let o=Gr(e,t),s=await U(o,[]),r=Array.isArray(s)?s.filter(Kr):[],i=r.findIndex((a)=>a.ModuleID===n);if(i<0)return!1;return r.splice(i,1),await nn(vo(o),{recursive:!0}),await Bun.write(o,JSON.stringify(r,null,2)),!0}async function Ua(e){try{return(await At(e.root,{withFileTypes:!0})).filter((n)=>n.isDirectory()&&!n.name.startsWith("_")).map((n)=>n.name)}catch{return[]}}async function Ha(e){let t=[],n;try{n=await At(e.root,{withFileTypes:!0})}catch{return t}for(let o of n){if(!o.isDirectory()||o.name.startsWith("_"))continue;let s=se(e.root,o.name);t.push({baseDir:s,projectId:o.name});try{for(let r of await At(se(s,"modules"),{withFileTypes:!0}))if(r.isDirectory())t.push({baseDir:se(s,"modules",r.name),projectId:o.name,moduleId:r.name})}catch{}}return t}async function zr(e,t){let n=[];for(let o of await Ha(e)){let s=await U(se(o.baseDir,"imports/_imports.json"),[]),r=Array.isArray(s)?s.find((i)=>i?.fileID===t):void 0;if(r)n.push({...o,entry:r})}return n}async function Xr(e,t,n){let o=[];for(let{baseDir:s}of await Ha(e)){let r=await U(se(s,"imports/_imports.json"),[]),i=Array.isArray(r)?r.find((c)=>c?.fileID===t&&c?.filePath):void 0;if(!i?.filePath)continue;let a=se(s,i.filePath);try{await nn(vo(a),{recursive:!0}),await Bun.write(a,n),o.push(a)}catch{}}return o}var Kp=20,Gp=50;function Yr(e,t){let n=t==="module"?Gp:Kp;if(e===void 0)return{effectiveSort:n,note:`fileSort not given; using ${n} (${t} imports must load after the framework)`};if(e<n)return{effectiveSort:n,note:`fileSort ${e} raised to ${n}: lower values load before the framework (~1-10) and can break the page`};return{effectiveSort:e,note:""}}function wt(e){return JSON.stringify(e??{},null,2)}function ft(e){return e.replace(/\r\n/g,`
91
+ `).trim()}var zp=["module.json","header.html","body.html","script.js","style.css"];async function $o(e){let t=[];for(let o of zp)t.push(`${o}
92
92
  ${await F(se(e,o))}`);let n=se(e,"components");for(let o of(await Me(n)).slice().sort())t.push(`components/${o}
93
93
  ${await F(se(n,o))}`);return Rt(t.join(`
94
94
  \x00
95
- `))}function Ua(e,t,n){let o=e.trim(),s=t.trim(),r=n,i=o.match(/^<head[^>]*>([\s\S]*)<\/head>\s*$/i);if(i)o=i[1].trim();let a=o.match(/<title[^>]*>([\s\S]*?)<\/title>/i);if(a)r=a[1].replace(/\s+/g," ").trim(),o=o.replace(/<title[^>]*>[\s\S]*?<\/title>\s*/i,"").trim();let c=s.match(/^<body[^>]*>([\s\S]*)<\/body>\s*$/i);if(c)s=c[1].trim();return{pageHeader:o,pageBody:s,mTitle:r}}function Ba(e,t){let n=[],o=new Map(e.map((r)=>[r.comName,r.comCode])),s=new Set(Object.keys(t));for(let[r,i]of Object.entries(t)){let a=o.get(r);if(a===void 0)n.push(`added component ${r}`);else if(ft(a)!==ft(i))n.push(`updated component ${r}`)}for(let r of e)if(!s.has(r.comName))n.push(`deleted component ${r.comName}`);return n}function Xr(e){if(!_t(e))throw new f("project.json must be a JSON object at the top level");if(_t(e.data))return{config:e.data,wasEnveloped:!0};return{config:e,wasEnveloped:!1}}function jo(e){let t=0,n=0,o=new Set;try{let s=e.moduleUI?JSON.parse(e.moduleUI):{};t=(s.controls||[]).length,n=(s.data||[]).length;for(let r of s.controls||[])for(let i of r.rows||[])for(let a of i.cols||[])if(a.el)o.add(a.el)}catch{}return[`framework ${e.Framework}, htmlOnly ${e.HTMLOnly?"yes":"no"}, controls ${t}, data keys ${n}, referenced components ${o.size}`]}import{join as ie}from"path";function wt(e){if(!e)throw Error("component source is empty");let t=/<template(\s[^>]*)?>/i.exec(e);if(!t)throw Error("no <template> in the source");let n=t.index+t[0].length,o=e.toLowerCase(),s=Bp(e,o,n);if(s===-1)throw Error("no </template> closing the template block");let r=o.lastIndexOf("</script");if(r===-1)throw Error("no <script> in the source");let i=/<script(\s[^>]*)?>/gi;i.lastIndex=s;let a=i.exec(e);if(!a||a.index>=r)throw Error("no <script> in the source");let c=e.substring(n,s).trim(),l=Hp(e.substring(a.index+a[0].length,r).trim()),u=Up(e,o,r);return{template:c,script:l,style:u}}function Up(e,t,n){let o=/<style(\s[^>]*)?>/i.exec(e.slice(n));if(!o)return"";let s=n+o.index+o[0].length,r=t.lastIndexOf("</style");if(r===-1||r<s)return"";return e.substring(s,r).trim()}function Bp(e,t,n){let o=/<!--|<\/?([a-zA-Z][a-zA-Z0-9-]*)/g;o.lastIndex=n;let s=1,r=e.length;for(let i=o.exec(e);i!==null;i=o.exec(e)){if(i[0]==="<!--"){let d=e.indexOf("-->",o.lastIndex);if(d===-1)return-1;o.lastIndex=d+3;continue}let a=e.charAt(i.index+1)==="/",c=i[1].toLowerCase(),l=o.lastIndex,u=!1;while(l<r){let d=e.charAt(l);if(d==='"'||d==="'"){let p=e.indexOf(d,l+1);if(p===-1)return-1;l=p+1;continue}if(d===">"){u=e.charAt(l-1)==="/",l++;break}l++}if(o.lastIndex=l,c==="template"){if(a){if(s--,s===0)return i.index}else if(!u)s++;continue}if(!a&&(c==="script"||c==="style"||c==="textarea"||c==="title")){let d=t.indexOf(`</${c}`,l);if(d===-1)return-1;let p=e.indexOf(">",d);if(p===-1)return-1;o.lastIndex=p+1}}return-1}function Hp(e){let t=e.match(/export\s+default\s*\{/);if(!t)throw Error('no "export default {" in the script');let n=e.lastIndexOf("}");if(n<=t.index)throw Error('no "}" closing "export default {"');return e.substring(t.index+t[0].length,n).trim()}function on(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Ha(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function Wp(e){let t=e.trim();if(/^(https?:)?\/\//i.test(t)){let{host:o,path:s}=Ha(t);return o?{host:o,path:s}:null}let n=t.split("/")[0];if(n&&n.includes(".")){let{host:o,path:s}=Ha(`//${t}`);return o?{host:o,path:s}:null}return null}function Wa(e){let n=e.trim().replace(/^https?:\/\/[^/]+/i,"").split(/[?#]/,1)[0].replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);if(n.length<2)throw new f(`target "${e}" must look like /projectId/moduleId`);return{projectHint:n[0],moduleHint:n.slice(1).join("/")}}function qp(e){let t=[e.ModuleID,e.ModuleName,e.mDomain,e.url].filter((n)=>Boolean(n));return[...new Set(t.flatMap((n)=>[n.toLowerCase(),on(n),n.replace(/^\/+|\/+$/g,"").toLowerCase()]))]}function Yr(e,t){let n=t.trim().toLowerCase().replace(/^\/+|\/+$/g,""),o=on(t);return e.filter((s)=>{let r=qp(s);return r.includes(n)||r.includes(o)})}function rn(e,t,n){if(n?.refreshed)return new f(`module "${t}" does not exist in project "${e}" (the module list was just refreshed from the server). check the name: fui module list -p ${e}, or create it: fui module new`);let o=n?.refreshError?` refreshing the list also failed: ${n.refreshError}.`:"";return new f(`module "${t}" is not in the local cache (${e}/modules/_modules.json).${o} run: fui module list -p ${e}`)}class Bn{paths;api;constructor(e,t){this.paths=e;this.api=t}baseDir(e,t){return t?ie(this.paths.projectDir(e),"modules",t):this.paths.projectDir(e)}async lookupModuleInfo(e,t){let n=t(await Oa(this.paths,e));if(n)return{found:n,refreshed:!1};let o;try{o=await this.api.listModules(e),await Un(this.paths,e,o)}catch(s){return{found:null,refreshed:!1,refreshError:s.message}}return{found:t(o),refreshed:!0}}resolveModuleInfoFromCache(e,t){return this.lookupModuleInfo(e,(n)=>n.find((o)=>o.ModuleID===t)??null)}async refreshProjectInfoCache(e){let t=this.paths.projectDir(e);if(!await ht(t))return null;let n=(await this.api.listProjects()).find((o)=>o.ProjectID===e);if(!n)return null;return await Fn(t,{"_projectInfo.json":JSON.stringify({...n,fetchedAt:new Date().toISOString()},null,2)}),e}async projectInfoRefreshNote(e){if(typeof e!=="string"||!e.trim())return null;try{return await this.refreshProjectInfoCache(e)?`refreshed ${e}/_projectInfo.json`:null}catch{return`could not refresh ${e}/_projectInfo.json; run fui project sync -p ${e} if needed`}}async resolveProjectIdFromHint(e){let t=e.trim().toLowerCase(),n=on(e),o=(await Fa(this.paths)).filter((a)=>a.toLowerCase()===t||on(a)===n);if(o.length>1)throw new f(`project hint "${e}" matched several local project folders: ${o.join(", ")}`);if(o.length===1)return o[0];let s;try{s=await this.api.listProjects()}catch{return e}let r=s.filter((a)=>on(a.ProjectName)===n||a.ProjectName.trim().toLowerCase()===t);if(r.length===1)return r[0].ProjectID;if(r.length>1)throw new f(`project hint "${e}" matched several projects by name: ${r.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);let i=s.filter((a)=>a.ProjectName.toLowerCase().includes(t)||on(a.ProjectName).includes(n)).slice(0,10);if(i.length>0)throw new f(`no project matches "${e}" exactly; did you mean: ${i.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);return e}async resolveModuleTargetByUrl(e,t){let n=(t??"").split("/").filter(Boolean),s=(await this.api.getRuntimeModuleVersion(e,n[0]??"",n[1]??""))[0];if(!s||s.Status!==1||!s.ProjectID||!s.ModuleID)throw new f(`cannot resolve a module from domain "${e}"${t?` path "${t}"`:""} via FUI_Runtime_ModuleVersionSelect${s?` (Status=${s.Status})`:" (no result)"}; check the URL or look it up: fui project list / fui module list`);let r=await this.resolveModuleInfoFromCache(s.ProjectID,s.ModuleID);if(!r.found)throw rn(s.ProjectID,s.ModuleID,r);return{projectId:s.ProjectID,moduleId:s.ModuleID,moduleInfo:r.found,matchedBy:"runtime_lookup"}}async resolveProjectAndModuleHint(e){let t=Wp(e);if(t)try{let o=await this.resolveModuleTargetByUrl(t.host,t.path);return{projectId:o.projectId,moduleHint:o.moduleId,matchedBy:o.matchedBy,moduleInfo:o.moduleInfo}}catch(o){try{let s=Wa(e);return{projectId:await this.resolveProjectIdFromHint(s.projectHint),moduleHint:s.moduleHint,matchedBy:"target_path"}}catch(s){throw new f(`cannot resolve target "${e}": as URL/domain: ${o.message}; as /projectId/moduleId: ${s.message}`)}}let n=Wa(e);return{projectId:await this.resolveProjectIdFromHint(n.projectHint),moduleHint:n.moduleHint,matchedBy:"target_path"}}async resolveModuleTarget(e,t,n){if(t&&n){let r=await this.resolveModuleInfoFromCache(t,n);if(!r.found)throw rn(t,n,r);return{projectId:t,moduleId:n,moduleInfo:r.found,matchedBy:"explicit_ids"}}if(!e)throw new f("give a target (/project/module or a URL) or both -p and -m");let o=await this.resolveProjectAndModuleHint(e);if(o.moduleInfo)return{projectId:o.projectId,moduleId:o.moduleInfo.ModuleID,moduleInfo:o.moduleInfo,matchedBy:o.matchedBy};let s=await this.lookupModuleInfo(o.projectId,(r)=>{let i=Yr(r,o.moduleHint);if(i.length>1)throw new f(`target "${e}" matched several modules in project ${o.projectId}: ${i.map((a)=>a.ModuleID).join(", ")}`);return i[0]??null});if(!s.found)throw rn(o.projectId,o.moduleHint,s);return{projectId:o.projectId,moduleId:s.found.ModuleID,moduleInfo:s.found,matchedBy:"target_path"}}async fetchModuleBundle(e){let{ProjectID:t,ModuleID:n}=e,[o,s,r,i,a]=await Promise.all([this.api.getModuleContent(t,n),this.api.getModuleComponents(t,n),this.api.getModuleScript(t,n),this.api.getModuleCSS(t,n),this.api.getModuleDependencies(t,n)]);return{moduleInfo:e,content:o,components:s,script:r,css:i,dependencies:a}}async fetchProjectBundle(e){let[t,n,o,s,r]=await Promise.all([this.api.listProjects(),this.api.listModules(e),this.api.getProjectJson(e),this.api.getModuleComponents(e),this.api.listImportFiles(e)]);return{projectInfo:{...t.find((c)=>c.ProjectID===e)??{ProjectID:e,ProjectName:e},fetchedAt:new Date().toISOString()},modules:n,projectJson:o,projectComponents:s,projectImports:r}}async downloadImportContents(e){let t={},n=[];for(let o of e){if(!en(o.ContentType))continue;try{let r=(await this.api.getFileContent(o.fileID))?.data?.FileContent??"";t[`imports/${Mn(o.FileName,o.fileID,o.FileType)}`]=r}catch(s){n.push(`${o.FileName} (fileID ${o.fileID}): ${s?.message??String(s)}`)}}return{files:t,failures:n}}async uploadImportContents(e){let t=await U(ie(e,"imports/_imports.json"),[]);for(let n of t){if(!n.fileID||!n.filePath||!en(n.contentType??""))continue;let o=await F(ie(e,n.filePath));if(o)await this.api.uploadFileContent(n.fileID,o)}}async syncComponentsLocal(e,t){let n=await this.api.getModuleComponents(e,t),o=n.map((i)=>({comID:i.comID,comName:i.comName})),s=this.baseDir(e,t),r=await Da(this.paths,s,new Set(o.map((i)=>i.comName)));return await Fn(s,{"components/_components.json":JSON.stringify(o,null,2)}),{count:n.length,removed:r}}async syncImportsLocal(e,t){let n=await this.api.listImportFiles(e,t),o=t?n.filter((c)=>c.Scope!=="project"):n,s=this.baseDir(e,t),r=mt(o),i=new Set(r.map((c)=>c.filePath).filter((c)=>!!c)),a=await Ln(this.paths,s,i);return await Fn(s,{"imports/_imports.json":JSON.stringify(r,null,2)}),{list:n,count:o.length,removed:a}}async publishStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new f(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await F(ie(e,"module.json")),o;try{o=JSON.parse(n)}catch(g){throw new f(`module.json has invalid JSON syntax: ${g.message}; fix it before publishing`)}let s=await F(ie(e,"header.html")),r=await F(ie(e,"body.html")),i=await F(ie(e,"script.js")),a=await F(ie(e,"style.css")),{pageHeader:c,pageBody:l,mTitle:u}=Ua(s,r,t.mTitle??""),{ProjectID:d,ModuleID:p}=t;if(await this.api.updateModule(d,p,{moduleUI:o,title:u,pageHeader:c,pageBody:l}),i.trim())await this.api.updateModuleScript(d,p,i);if(a.trim())await this.api.updateModuleCSS(d,p,a);let m=ie(e,"components"),w=await U(ie(m,"_components.json"),[]),y=new Map(w.map((g)=>[g.comName,g.comID])),v=await Me(m),T=new Set(v.map((g)=>g.replace(/\.vue$/i,"")));for(let g of v){let b=g.replace(/\.vue$/i,""),{template:R,script:C,style:S}=wt(await F(ie(m,g)));await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:y.get(b),ComName:b,ComTemplate:R,ComScript:C,ComCSS:S})}for(let g of w)if(!T.has(g.comName))await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:g.comID,ComName:"",ComTemplate:"",ComScript:""});await this.syncComponentsLocal(d,p),await this.uploadImportContents(e);let I=["module.json","header.html","body.html"];if(i.trim())I.push("script.js");if(a.trim())I.push("style.css");for(let g of v)I.push(ke(ie("components",g)));return await Te(e,I).catch(()=>{return}),t}async previewStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new f(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await this.fetchModuleBundle(t),o=tn(n.moduleInfo,n.content,n.components,n.script,n.css,n.dependencies),s;try{s=JSON.parse(await F(ie(e,"module.json")))}catch(p){throw new f(`module.json has invalid JSON syntax: ${p.message}; fix it before previewing`)}let r=await F(ie(e,"header.html")),i=await F(ie(e,"body.html")),a=await F(ie(e,"script.js")),c=await F(ie(e,"style.css")),l=await U(ie(e,"imports/_imports.json"),[]),u={};for(let p of await Me(ie(e,"components")))u[p.replace(/\.vue$/i,"")]=await F(ie(e,"components",p));let d=[];if(gt(s)!==gt(JSON.parse(o["module.json"])))d.push("module.json will be updated");for(let p of["header.html","body.html","script.js","style.css"]){let m={"header.html":r,"body.html":i,"script.js":a,"style.css":c}[p];if(ft(m)!==ft(o[p]||""))d.push(`${p} will be updated`)}if(gt(l)!==gt(JSON.parse(o["imports/_imports.json"]||"[]")))d.push("imports/_imports.json differs locally (metadata only, not sent)");return d.push(...Ba(n.components,u)),{moduleInfo:t,changes:d,remoteSummary:jo(n.content)}}}import{cp as Jp,mkdir as Qr,readdir as Vp,rm as Kp}from"fs/promises";import{dirname as Gp,isAbsolute as zp,join as Ne,relative as Xp,sep as Yp}from"path";var Dt="fui-skill",Qp=["references/controls-patterns.md","references/module-structure.md","references/INDEX.md"],qa=`# FUI Skill Summary
95
+ `))}function Ba(e,t,n){let o=e.trim(),s=t.trim(),r=n,i=o.match(/^<head[^>]*>([\s\S]*)<\/head>\s*$/i);if(i)o=i[1].trim();let a=o.match(/<title[^>]*>([\s\S]*?)<\/title>/i);if(a)r=a[1].replace(/\s+/g," ").trim(),o=o.replace(/<title[^>]*>[\s\S]*?<\/title>\s*/i,"").trim();let c=s.match(/^<body[^>]*>([\s\S]*)<\/body>\s*$/i);if(c)s=c[1].trim();return{pageHeader:o,pageBody:s,mTitle:r}}function qa(e,t){let n=[],o=new Map(e.map((r)=>[r.comName,r.comCode])),s=new Set(Object.keys(t));for(let[r,i]of Object.entries(t)){let a=o.get(r);if(a===void 0)n.push(`added component ${r}`);else if(ft(a)!==ft(i))n.push(`updated component ${r}`)}for(let r of e)if(!s.has(r.comName))n.push(`deleted component ${r.comName}`);return n}function Qr(e){if(!_t(e))throw new h("project.json must be a JSON object at the top level");if(_t(e.data))return{config:e.data,wasEnveloped:!0};return{config:e,wasEnveloped:!1}}function jo(e){let t=0,n=0,o=new Set;try{let s=e.moduleUI?JSON.parse(e.moduleUI):{};t=(s.controls||[]).length,n=(s.data||[]).length;for(let r of s.controls||[])for(let i of r.rows||[])for(let a of i.cols||[])if(a.el)o.add(a.el)}catch{}return[`framework ${e.Framework}, htmlOnly ${e.HTMLOnly?"yes":"no"}, controls ${t}, data keys ${n}, referenced components ${o.size}`]}import{join as ie}from"path";function yt(e){if(!e)throw Error("component source is empty");let t=/<template(\s[^>]*)?>/i.exec(e);if(!t)throw Error("no <template> in the source");let n=t.index+t[0].length,o=e.toLowerCase(),s=Yp(e,o,n);if(s===-1)throw Error("no </template> closing the template block");let r=o.lastIndexOf("</script");if(r===-1)throw Error("no <script> in the source");let i=/<script(\s[^>]*)?>/gi;i.lastIndex=s;let a=i.exec(e);if(!a||a.index>=r)throw Error("no <script> in the source");let c=e.substring(n,s).trim(),l=Qp(e.substring(a.index+a[0].length,r).trim()),u=Xp(e,o,r);return{template:c,script:l,style:u}}function Xp(e,t,n){let o=/<style(\s[^>]*)?>/i.exec(e.slice(n));if(!o)return"";let s=n+o.index+o[0].length,r=t.lastIndexOf("</style");if(r===-1||r<s)return"";return e.substring(s,r).trim()}function Yp(e,t,n){let o=/<!--|<\/?([a-zA-Z][a-zA-Z0-9-]*)/g;o.lastIndex=n;let s=1,r=e.length;for(let i=o.exec(e);i!==null;i=o.exec(e)){if(i[0]==="<!--"){let d=e.indexOf("-->",o.lastIndex);if(d===-1)return-1;o.lastIndex=d+3;continue}let a=e.charAt(i.index+1)==="/",c=i[1].toLowerCase(),l=o.lastIndex,u=!1;while(l<r){let d=e.charAt(l);if(d==='"'||d==="'"){let p=e.indexOf(d,l+1);if(p===-1)return-1;l=p+1;continue}if(d===">"){u=e.charAt(l-1)==="/",l++;break}l++}if(o.lastIndex=l,c==="template"){if(a){if(s--,s===0)return i.index}else if(!u)s++;continue}if(!a&&(c==="script"||c==="style"||c==="textarea"||c==="title")){let d=t.indexOf(`</${c}`,l);if(d===-1)return-1;let p=e.indexOf(">",d);if(p===-1)return-1;o.lastIndex=p+1}}return-1}function Qp(e){let t=e.match(/export\s+default\s*\{/);if(!t)throw Error('no "export default {" in the script');let n=e.lastIndexOf("}");if(n<=t.index)throw Error('no "}" closing "export default {"');return e.substring(t.index+t[0].length,n).trim()}function on(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Wa(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function Zp(e){let t=e.trim();if(/^(https?:)?\/\//i.test(t)){let{host:o,path:s}=Wa(t);return o?{host:o,path:s}:null}let n=t.split("/")[0];if(n&&n.includes(".")){let{host:o,path:s}=Wa(`//${t}`);return o?{host:o,path:s}:null}return null}function Va(e){let n=e.trim().replace(/^https?:\/\/[^/]+/i,"").split(/[?#]/,1)[0].replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);if(n.length<2)throw new h(`target "${e}" must look like /projectId/moduleId`);return{projectHint:n[0],moduleHint:n.slice(1).join("/")}}function em(e){let t=[e.ModuleID,e.ModuleName,e.mDomain,e.url].filter((n)=>Boolean(n));return[...new Set(t.flatMap((n)=>[n.toLowerCase(),on(n),n.replace(/^\/+|\/+$/g,"").toLowerCase()]))]}function Zr(e,t){let n=t.trim().toLowerCase().replace(/^\/+|\/+$/g,""),o=on(t);return e.filter((s)=>{let r=em(s);return r.includes(n)||r.includes(o)})}function rn(e,t,n){if(n?.refreshed)return new h(`module "${t}" does not exist in project "${e}" (the module list was just refreshed from the server). check the name: fui module list -p ${e}, or create it: fui module new`);let o=n?.refreshError?` refreshing the list also failed: ${n.refreshError}.`:"";return new h(`module "${t}" is not in the local cache (${e}/modules/_modules.json).${o} run: fui module list -p ${e}`)}class Hn{paths;api;constructor(e,t){this.paths=e;this.api=t}baseDir(e,t){return t?ie(this.paths.projectDir(e),"modules",t):this.paths.projectDir(e)}async lookupModuleInfo(e,t){let n=t(await Fa(this.paths,e));if(n)return{found:n,refreshed:!1};let o;try{o=await this.api.listModules(e),await Un(this.paths,e,o)}catch(s){return{found:null,refreshed:!1,refreshError:s.message}}return{found:t(o),refreshed:!0}}resolveModuleInfoFromCache(e,t){return this.lookupModuleInfo(e,(n)=>n.find((o)=>o.ModuleID===t)??null)}async refreshProjectInfoCache(e){let t=this.paths.projectDir(e);if(!await gt(t))return null;let n=(await this.api.listProjects()).find((o)=>o.ProjectID===e);if(!n)return null;return await Fn(t,{"_projectInfo.json":JSON.stringify({...n,fetchedAt:new Date().toISOString()},null,2)}),e}async projectInfoRefreshNote(e){if(typeof e!=="string"||!e.trim())return null;try{return await this.refreshProjectInfoCache(e)?`refreshed ${e}/_projectInfo.json`:null}catch{return`could not refresh ${e}/_projectInfo.json; run fui project sync -p ${e} if needed`}}async resolveProjectIdFromHint(e){let t=e.trim().toLowerCase(),n=on(e),o=(await Ua(this.paths)).filter((a)=>a.toLowerCase()===t||on(a)===n);if(o.length>1)throw new h(`project hint "${e}" matched several local project folders: ${o.join(", ")}`);if(o.length===1)return o[0];let s;try{s=await this.api.listProjects()}catch{return e}let r=s.filter((a)=>on(a.ProjectName)===n||a.ProjectName.trim().toLowerCase()===t);if(r.length===1)return r[0].ProjectID;if(r.length>1)throw new h(`project hint "${e}" matched several projects by name: ${r.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);let i=s.filter((a)=>a.ProjectName.toLowerCase().includes(t)||on(a.ProjectName).includes(n)).slice(0,10);if(i.length>0)throw new h(`no project matches "${e}" exactly; did you mean: ${i.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);return e}async resolveModuleTargetByUrl(e,t){let n=(t??"").split("/").filter(Boolean),s=(await this.api.getRuntimeModuleVersion(e,n[0]??"",n[1]??""))[0];if(!s||s.Status!==1||!s.ProjectID||!s.ModuleID)throw new h(`cannot resolve a module from domain "${e}"${t?` path "${t}"`:""} via FUI_Runtime_ModuleVersionSelect${s?` (Status=${s.Status})`:" (no result)"}; check the URL or look it up: fui project list / fui module list`);let r=await this.resolveModuleInfoFromCache(s.ProjectID,s.ModuleID);if(!r.found)throw rn(s.ProjectID,s.ModuleID,r);return{projectId:s.ProjectID,moduleId:s.ModuleID,moduleInfo:r.found,matchedBy:"runtime_lookup"}}async resolveProjectAndModuleHint(e){let t=Zp(e);if(t)try{let o=await this.resolveModuleTargetByUrl(t.host,t.path);return{projectId:o.projectId,moduleHint:o.moduleId,matchedBy:o.matchedBy,moduleInfo:o.moduleInfo}}catch(o){try{let s=Va(e);return{projectId:await this.resolveProjectIdFromHint(s.projectHint),moduleHint:s.moduleHint,matchedBy:"target_path"}}catch(s){throw new h(`cannot resolve target "${e}": as URL/domain: ${o.message}; as /projectId/moduleId: ${s.message}`)}}let n=Va(e);return{projectId:await this.resolveProjectIdFromHint(n.projectHint),moduleHint:n.moduleHint,matchedBy:"target_path"}}async resolveModuleTarget(e,t,n){if(t&&n){let r=await this.resolveModuleInfoFromCache(t,n);if(!r.found)throw rn(t,n,r);return{projectId:t,moduleId:n,moduleInfo:r.found,matchedBy:"explicit_ids"}}if(!e)throw new h("give a target (/project/module or a URL) or both -p and -m");let o=await this.resolveProjectAndModuleHint(e);if(o.moduleInfo)return{projectId:o.projectId,moduleId:o.moduleInfo.ModuleID,moduleInfo:o.moduleInfo,matchedBy:o.matchedBy};let s=await this.lookupModuleInfo(o.projectId,(r)=>{let i=Zr(r,o.moduleHint);if(i.length>1)throw new h(`target "${e}" matched several modules in project ${o.projectId}: ${i.map((a)=>a.ModuleID).join(", ")}`);return i[0]??null});if(!s.found)throw rn(o.projectId,o.moduleHint,s);return{projectId:o.projectId,moduleId:s.found.ModuleID,moduleInfo:s.found,matchedBy:"target_path"}}async fetchModuleBundle(e){let{ProjectID:t,ModuleID:n}=e,[o,s,r,i,a]=await Promise.all([this.api.getModuleContent(t,n),this.api.getModuleComponents(t,n),this.api.getModuleScript(t,n),this.api.getModuleCSS(t,n),this.api.getModuleDependencies(t,n)]);return{moduleInfo:e,content:o,components:s,script:r,css:i,dependencies:a}}async fetchProjectBundle(e){let[t,n,o,s,r]=await Promise.all([this.api.listProjects(),this.api.listModules(e),this.api.getProjectJson(e),this.api.getModuleComponents(e),this.api.listImportFiles(e)]);return{projectInfo:{...t.find((c)=>c.ProjectID===e)??{ProjectID:e,ProjectName:e},fetchedAt:new Date().toISOString()},modules:n,projectJson:o,projectComponents:s,projectImports:r}}async downloadImportContents(e){let t={},n=[];for(let o of e){if(!en(o.ContentType))continue;try{let r=(await this.api.getFileContent(o.fileID))?.data?.FileContent??"";t[`imports/${Mn(o.FileName,o.fileID,o.FileType)}`]=r}catch(s){n.push(`${o.FileName} (fileID ${o.fileID}): ${s?.message??String(s)}`)}}return{files:t,failures:n}}async uploadImportContents(e){let t=await U(ie(e,"imports/_imports.json"),[]);for(let n of t){if(!n.fileID||!n.filePath||!en(n.contentType??""))continue;let o=await F(ie(e,n.filePath));if(o)await this.api.uploadFileContent(n.fileID,o)}}async syncComponentsLocal(e,t){let n=await this.api.getModuleComponents(e,t),o=n.map((i)=>({comID:i.comID,comName:i.comName})),s=this.baseDir(e,t),r=await Oa(this.paths,s,new Set(o.map((i)=>i.comName)));return await Fn(s,{"components/_components.json":JSON.stringify(o,null,2)}),{count:n.length,removed:r}}async syncImportsLocal(e,t){let n=await this.api.listImportFiles(e,t),o=t?n.filter((c)=>c.Scope!=="project"):n,s=this.baseDir(e,t),r=ht(o),i=new Set(r.map((c)=>c.filePath).filter((c)=>!!c)),a=await Ln(this.paths,s,i);return await Fn(s,{"imports/_imports.json":JSON.stringify(r,null,2)}),{list:n,count:o.length,removed:a}}async publishStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new h(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await F(ie(e,"module.json")),o;try{o=JSON.parse(n)}catch(g){throw new h(`module.json has invalid JSON syntax: ${g.message}; fix it before publishing`)}let s=await F(ie(e,"header.html")),r=await F(ie(e,"body.html")),i=await F(ie(e,"script.js")),a=await F(ie(e,"style.css")),{pageHeader:c,pageBody:l,mTitle:u}=Ba(s,r,t.mTitle??""),{ProjectID:d,ModuleID:p}=t;if(await this.api.updateModule(d,p,{moduleUI:o,title:u,pageHeader:c,pageBody:l}),i.trim())await this.api.updateModuleScript(d,p,i);if(a.trim())await this.api.updateModuleCSS(d,p,a);let m=ie(e,"components"),w=await U(ie(m,"_components.json"),[]),y=new Map(w.map((g)=>[g.comName,g.comID])),v=await Me(m),E=new Set(v.map((g)=>g.replace(/\.vue$/i,"")));for(let g of v){let b=g.replace(/\.vue$/i,""),{template:R,script:C,style:S}=yt(await F(ie(m,g)));await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:y.get(b),ComName:b,ComTemplate:R,ComScript:C,ComCSS:S})}for(let g of w)if(!E.has(g.comName))await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:g.comID,ComName:"",ComTemplate:"",ComScript:""});await this.syncComponentsLocal(d,p),await this.uploadImportContents(e);let I=["module.json","header.html","body.html"];if(i.trim())I.push("script.js");if(a.trim())I.push("style.css");for(let g of v)I.push(ke(ie("components",g)));return await Ee(e,I).catch(()=>{return}),t}async previewStagedModuleDir(e){let t=await U(ie(e,"_moduleInfo.json"),null);if(!t)throw new h(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await this.fetchModuleBundle(t),o=tn(n.moduleInfo,n.content,n.components,n.script,n.css,n.dependencies),s;try{s=JSON.parse(await F(ie(e,"module.json")))}catch(p){throw new h(`module.json has invalid JSON syntax: ${p.message}; fix it before previewing`)}let r=await F(ie(e,"header.html")),i=await F(ie(e,"body.html")),a=await F(ie(e,"script.js")),c=await F(ie(e,"style.css")),l=await U(ie(e,"imports/_imports.json"),[]),u={};for(let p of await Me(ie(e,"components")))u[p.replace(/\.vue$/i,"")]=await F(ie(e,"components",p));let d=[];if(wt(s)!==wt(JSON.parse(o["module.json"])))d.push("module.json will be updated");for(let p of["header.html","body.html","script.js","style.css"]){let m={"header.html":r,"body.html":i,"script.js":a,"style.css":c}[p];if(ft(m)!==ft(o[p]||""))d.push(`${p} will be updated`)}if(wt(l)!==wt(JSON.parse(o["imports/_imports.json"]||"[]")))d.push("imports/_imports.json differs locally (metadata only, not sent)");return d.push(...qa(n.components,u)),{moduleInfo:t,changes:d,remoteSummary:jo(n.content)}}}import{cp as tm,mkdir as es,readdir as nm,rm as om}from"fs/promises";import{dirname as rm,isAbsolute as sm,join as Ae,relative as im,sep as am}from"path";var Dt="fui-skill",cm=["references/controls-patterns.md","references/module-structure.md","references/INDEX.md"],Ja=`# FUI Skill Summary
96
96
 
97
97
  Use these rules whenever editing a FUI module:
98
98
 
@@ -106,27 +106,27 @@ Use these rules whenever editing a FUI module:
106
106
  - Run module_preview first, then ask the user if they want to push. Only call module_publish_html (with the returned approvalToken) \u2014 or module_publish_json / module_publish_script / component_update for their respective files \u2014 after explicit approval.
107
107
  - Build fullstack modules UI-first: design the UI with hardcoded/fake data and verify it visually (module_simulate renderUI) before designing the API contract and wiring real bindings.
108
108
  - Never call a live API/SP endpoint to verify it \u2014 read the SP body and reason about it (db_sp_verify) instead; that rule does not apply to db_sql_execute/db_sql_execute_nonquery, which remain the user's direct data-edit tools.
109
- `,Zp="https://github.com/fui-org/fui-skill";function em(){return`${qa}
109
+ `,lm="https://github.com/fui-org/fui-skill";function um(){return`${Ja}
110
110
 
111
111
  ## Source
112
112
 
113
- - Bundled with fui-mcp from ${Zp}
113
+ - Bundled with fui-mcp from ${lm}
114
114
 
115
115
  ## Notes
116
116
 
117
117
  - This fallback file exists so every editor can read a project-local skill snapshot after checkout.
118
118
  - fui-mcp copies the package-level src/skills/ folder into each staged project workspace.
119
- - If the bundled src/skills/fui-skill folder is missing from this MCP package, this summary still gives the agent the critical FUI rules.`}async function tm(){let e=import.meta.dir;for(let t of[Ne(e,"..","..","..","skills"),Ne(e,"..","skills")])if(await Bun.file(Ne(t,Dt,"SKILL.md")).exists())return t;return Ne(e,"..","..","..","skills")}async function sn(){return Ne(await tm(),Dt)}function nm(e){return Ne(e,"_skills",Dt,"SKILL.md")}async function Io(e){let t=Ne(e,"_skills"),n=Ne(t,Dt);await Qr(e,{recursive:!0});try{let o=await sn();if(!await Bun.file(Ne(o,"SKILL.md")).exists())throw Error(`bundled skill missing at ${o}`);return await Kp(t,{recursive:!0,force:!0}),await Qr(t,{recursive:!0}),await Jp(o,n,{recursive:!0,filter:(s)=>!s.split(Yp).includes(".git")}),{workspaceRoot:e,skillDir:n,source:"bundled-copy"}}catch(o){let s=`Could not copy bundled skills/ into ${n}: ${o.message}`;return await Qr(n,{recursive:!0}),await Bun.write(Ne(n,"SKILL.md"),`${em()}
119
+ - If the bundled src/skills/fui-skill folder is missing from this MCP package, this summary still gives the agent the critical FUI rules.`}async function dm(){let e=import.meta.dir;for(let t of[Ae(e,"..","..","..","skills"),Ae(e,"..","skills")])if(await Bun.file(Ae(t,Dt,"SKILL.md")).exists())return t;return Ae(e,"..","..","..","skills")}async function sn(){return Ae(await dm(),Dt)}function pm(e){return Ae(e,"_skills",Dt,"SKILL.md")}async function Io(e){let t=Ae(e,"_skills"),n=Ae(t,Dt);await es(e,{recursive:!0});try{let o=await sn();if(!await Bun.file(Ae(o,"SKILL.md")).exists())throw Error(`bundled skill missing at ${o}`);return await om(t,{recursive:!0,force:!0}),await es(t,{recursive:!0}),await tm(o,n,{recursive:!0,filter:(s)=>!s.split(am).includes(".git")}),{workspaceRoot:e,skillDir:n,source:"bundled-copy"}}catch(o){let s=`Could not copy bundled skills/ into ${n}: ${o.message}`;return await es(n,{recursive:!0}),await Bun.write(Ae(n,"SKILL.md"),`${um()}
120
120
 
121
121
  ## Sync Warning
122
122
 
123
123
  ${s}
124
- `),{workspaceRoot:e,skillDir:n,source:"bundled"}}}async function Ja(e){if(e){let t=nm(e),n=await pe(t);if(n&&n.trim()){let o=[n];for(let s of Qp){let r=await pe(Ne(Gp(t),s));if(r?.trim())o.push(`
124
+ `),{workspaceRoot:e,skillDir:n,source:"bundled"}}}async function Ka(e){if(e){let t=pm(e),n=await le(t);if(n&&n.trim()){let o=[n];for(let s of cm){let r=await le(Ae(rm(t),s));if(r?.trim())o.push(`
125
125
 
126
126
  ---
127
127
  <!-- ${s} -->
128
128
 
129
- ${r}`)}return{text:o.join(""),source:t}}}return{text:qa.trim(),source:"bundled-summary"}}async function Va(e,t){return(await Promise.all(t.map(async(o)=>{let s=Ne(e,o),r=Xp(e,s);if(r.startsWith("..")||zp(r))return`
129
+ ${r}`)}return{text:o.join(""),source:t}}}return{text:Ja.trim(),source:"bundled-summary"}}async function Ga(e,t){return(await Promise.all(t.map(async(o)=>{let s=Ae(e,o),r=im(e,s);if(r.startsWith("..")||sm(r))return`
130
130
 
131
131
  ---
132
132
  <!-- ${o} -->
@@ -136,53 +136,63 @@ invalid path (outside the skill folder)`;try{return`
136
136
  ---
137
137
  <!-- ${o} -->
138
138
 
139
- ${await H(s)}`}catch{return`
139
+ ${await B(s)}`}catch{return`
140
140
 
141
141
  ---
142
142
  <!-- ${o} -->
143
143
 
144
- not found at ${s}`}}))).join("")}async function Zr(){let e=Ne(await sn(),"design-md");return(await Vp(e,{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>n.name).sort()}async function ce(e){return new Bn(e.paths,await e.api())}function Ka(e){if(e.length===0)return[];return[`${e.length} import content(s) NOT downloaded; their local copies are stale even though imports/_imports.json is fresh. do not edit and publish them:`,...e.map((t)=>` - ${t}`),"re-run the sync, or fetch one: fui import get <fileId>"]}async function xt(e,t){let n=await e.wasSkillLoaded();if(!n&&qt())throw new $e(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read the fui-skill references, run fui skill loaded, then retry`,"hard","error");return n?[]:["warning: skill not loaded this session; read fui-skill (fui skill get / references), then run: fui skill loaded"]}async function Ga(e,t){let n=await(await ce(e)).resolveModuleTarget(t);return{notes:[],data:`project ${n.projectId}
144
+ not found at ${s}`}}))).join("")}async function ts(){let e=Ae(await sn(),"design-md");return(await nm(e,{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>n.name).sort()}async function ce(e){return new Hn(e.paths,await e.api())}function za(e){if(e.length===0)return[];return[`${e.length} import content(s) NOT downloaded; their local copies are stale even though imports/_imports.json is fresh. do not edit and publish them:`,...e.map((t)=>` - ${t}`),"re-run the sync, or fetch one: fui import get <fileId>"]}async function xt(e,t){let n=await e.wasSkillLoaded();if(!n&&Wt())throw new ve(`[FUI_MCP_STRICT] ${t} blocked: skill not loaded this session; read the fui-skill references, run fui skill loaded, then retry`,"hard","error");return n?[]:["warning: skill not loaded this session; read fui-skill (fui skill get / references), then run: fui skill loaded"]}async function Xa(e,t){let n=await(await ce(e)).resolveModuleTarget(t);return{notes:[],data:`project ${n.projectId}
145
145
  module ${n.moduleId}
146
146
  matchedBy ${n.matchedBy}
147
147
  moduleDir ${e.paths.projectDir(n.projectId)}/modules/${n.moduleId}
148
- `}}async function za(e,t){let{projectId:n,moduleId:o}=t,s=t.moduleDir,r="module_dir";if(!s){if(!t.target&&!(n&&o))throw new f("give a target (/project/module or URL), -p and -m, or --module-dir");let a=await(await ce(e)).resolveModuleTarget(t.target,n,o);n=a.projectId,o=a.moduleId,s=Be(e.paths.projectDir(n),"modules",o),r=a.matchedBy}else{let a=await U(Be(s,"_moduleInfo.json"),null);if(!a)throw new f(`missing _moduleInfo.json in ${s}`);n=a.ProjectID,o=a.ModuleID}let i=t.moduleDir?es(es(es(s))):e.paths.root;return await e.session.setTarget({projectId:n??"",moduleId:o??"",moduleDir:s,workspaceRoot:i,target:t.target}),{notes:[`active ${n}/${o} (${r}) -> ${s}`,`session ${e.session.file}`]}}async function Xa(e){return(await e.api()).listProjects()}async function Ya(e,t){let n=await ce(e),o=e.paths.projectDir(t),s=await n.fetchProjectBundle(t),r=Br(s.projectInfo,s.projectJson,s.modules,s.projectComponents,s.projectImports),{"modules/_modules.json":i,...a}=r,c=new Set(mt(s.projectImports).map((y)=>y.filePath).filter((y)=>!!y)),l=await Ln(e.paths,o,c),u=await n.downloadImportContents(s.projectImports),d=await qr(e.paths,o,{...a,...u.files}),p=await xa(e.paths,o,new Set(s.modules.map((y)=>y.ModuleID)));await Un(e.paths,t,s.modules);let m=!1,w=await e.session.getTarget();if(w?.projectId===t&&w.moduleId&&p.includes(w.moduleId))await e.session.clearTarget(),m=!0;return{notes:[`ok synced project ${t} -> ${o}`,`modules ${s.modules.length}, project components ${s.projectComponents.length}, project imports ${s.projectImports.length} (contents ${Object.keys(u.files).length})`,`files: ${yr(d)}`,...l.length>0?[`removed orphaned import files: ${l.join(", ")}`]:[],...p.length>0?[`removed orphaned module dirs (archived): ${p.join(", ")}`]:[],...m?["session target cleared: it pointed to a removed module; run fui use / fui module get again"]:[],...br(d.archived),...Ka(u.failures)]}}async function Qa(e,t){let n=await(await e.api()).listModules(t),o;try{await Un(e.paths,t,n),o="cache updated: modules/_modules.json (+ _moduleInfo.json of checked-out modules)"}catch(s){o=`warning: could not write the local cache: ${s.message}`}return{rows:n,notes:[o]}}async function Za(e,t){let n=await ce(e),o=(await n.resolveModuleInfoFromCache(t.projectId,t.moduleId)).found;if(o)throw new f(`module "${t.projectId}/${t.moduleId}" already exists ("${o.ModuleName}"); edit it: fui module update -p ${t.projectId} -m ${t.moduleId}, or pick another id`);let s={...t.extra,Framework:t.framework??"V2",mDomain:t.mDomain??"",ModuleID:t.moduleId,ModuleName:t.moduleName,HTMLOnly:t.htmlOnly??!1,dialogModel:"new"},i=(await n.api.editModule(t.projectId,s)).data??[],a=i.find((l)=>l.ModuleID===t.moduleId)??i[0],c=[`ok created module ${a?.id??`${t.projectId}/${t.moduleId}`} url ${a?.url??`/${t.projectId}/${t.moduleId}`} framework ${s.Framework}`];if(a)try{await vo(e.paths,t.projectId,a),c.push("modules/_modules.json updated")}catch{c.push("warning: module created but modules/_modules.json update failed")}return c.push(`next: fui module get -p ${t.projectId} -m ${t.moduleId}`),{notes:c}}async function ec(e,t){let n=await ce(e),{projectId:o,moduleId:s,newModuleId:r}=t,i=Be(e.paths.projectDir(o),"modules",s),a=await U(Be(i,"_moduleInfo.json"),null),c=a?null:await n.resolveModuleInfoFromCache(o,s),l=a??c?.found;if(!l)throw rn(o,s,c??void 0);let u=[];if(t.framework&&t.framework!==l.Framework)u.push(`warning: framework ${l.Framework} -> ${t.framework} switches the CDN set (V2/V3); re-check header/body/script and components`);if(r&&r!==s)u.push(`warning: ModuleID "${s}" -> "${r}" changes the module URL; links and menus to the old URL break`);let{mTitle:d,...p}=l,m={...p,...t.extra,Framework:t.framework??l.Framework,mDomain:t.mDomain??l.mDomain??"",ModuleID:r??s,ModuleName:t.moduleName??l.ModuleName,HTMLOnly:t.htmlOnly??l.HTMLOnly,dialogModel:"edit"},y=((await n.api.editModule(o,m)).data??[]).find((I)=>I.ModuleID===m.ModuleID),v={...l,Framework:m.Framework,mDomain:m.mDomain,ModuleID:m.ModuleID,ModuleName:m.ModuleName,HTMLOnly:m.HTMLOnly};if(r&&r!==s)v.id=`${o}/${r}`,v.url=`/${o}/${r}`;if(y)v={...v,...y};await vo(e.paths,o,v,s);let T=["modules/_modules.json updated"];if(a){let I=i;if(r&&r!==s){let b=Be(e.paths.projectDir(o),"modules",r);if(await ht(b))u.push(`warning: cannot rename the local folder: ${b} exists; _moduleInfo.json updated in place`);else await om(i,b),I=b,T.push(`local folder renamed: modules/${s} -> modules/${r}`)}await Bun.write(Be(I,"_moduleInfo.json"),JSON.stringify({...a,...v},null,2)),T.push("_moduleInfo.json updated");let g=await e.session.getTarget();if(g&&g.projectId===o&&g.moduleId===s)await e.session.setTarget({projectId:o,moduleId:v.ModuleID,moduleDir:I,workspaceRoot:g.workspaceRoot,target:g.target}),T.push("session target updated")}return{notes:[`ok updated ${v.id} url ${v.url} name ${v.ModuleName} framework ${v.Framework} htmlOnly ${v.HTMLOnly}`,...T,...u]}}async function tc(e,t,n){await(await e.api()).deleteModule(t,n);let o=[`ok deleted module ${n} from project ${t}`];try{o.push(await Ma(e.paths,t,n)?"entry removed from modules/_modules.json":"no entry in modules/_modules.json (already absent)")}catch(i){o.push(`warning: could not remove the cache entry: ${i.message}`)}let s=Be(e.paths.projectDir(t),"modules",n);if(await ht(s)){await co(e.paths,s,{suffix:"deleted"});let{rm:i}=await import("fs/promises");await i(s,{recursive:!0,force:!0}),o.push(`local folder removed (archived to _history first): modules/${n}`)}let r=await e.session.getTarget();if(r&&r.projectId===t&&r.moduleId===n)await e.session.clearTarget(),o.push("session target cleared (it pointed to the deleted module)");return{notes:o}}async function nc(e,t){let n=await ce(e),o,s,r,i;if(t.projectId&&t.moduleId)o=t.projectId,s=t.moduleId,r="explicit_ids";else if(t.target){let _=await n.resolveProjectAndModuleHint(t.target);o=_.projectId,s=_.moduleInfo?.ModuleID??_.moduleHint,r=_.matchedBy,i=_.moduleInfo}else{let _=await e.targets.resolveProjectModule();o=_.projectId,s=_.moduleId,r=_.source==="cwd"?"cwd":"active_session"}let a=e.paths.projectDir(o),c=await U(Be(a,"_projectInfo.json"),null)!==null,l=[];if(!c){let _=await n.fetchProjectBundle(o);await qr(e.paths,a,Br(_.projectInfo,_.projectJson,_.modules,_.projectComponents,_.projectImports));let le=await n.downloadImportContents(_.projectImports);if(l.push(...le.failures),Object.keys(le.files).length>0)await Ae(e.paths,a,le.files)}let u=i??null;if(!u){let _=await n.lookupModuleInfo(o,(le)=>{let Ie=le.find((ae)=>ae.ModuleID===s);if(Ie||r!=="target_path")return Ie??null;let B=Yr(le,s);if(B.length>1)throw new f(`target "${t.target}" matched several modules in project ${o}: ${B.map((ae)=>ae.ModuleID).join(", ")}`);return B[0]??null});if(u=_.found,!u)throw rn(o,s,_)}let{content:d,components:p,script:m,css:w,dependencies:y}=await n.fetchModuleBundle(u),v=tn(u,d,p,m,w,y),T=Be(e.paths.projectDir(u.ProjectID),"modules",u.ModuleID),I=new Set(mt(y.filter((_)=>_.Scope!=="project")).map((_)=>_.filePath).filter((_)=>!!_)),g=await Ln(e.paths,T,I),[b,R]=await Promise.all([n.downloadImportContents(y.filter((_)=>_.Scope==="module")),n.downloadImportContents(c?y.filter((_)=>_.Scope==="project"):[])]);l.push(...b.failures,...R.failures);let C=await _a(e.paths,T,u,d,{...v,...b.files}),S=Object.keys(R.files).length>0?await Ae(e.paths,a,R.files):null;await vo(e.paths,o,{...u,Framework:d.Framework,HTMLOnly:d.HTMLOnly});let N=await Io(e.paths.root);await e.session.setTarget({projectId:u.ProjectID,moduleId:u.ModuleID,moduleDir:C.moduleDir,workspaceRoot:e.paths.root,target:t.target});let D=[...C.removedFiles.map((_)=>rm(_)),...g],A=[...C.sync.archived,...S?.archived??[]],G=c?await Pt(a,"project data (project.json, components, imports)",`fui project sync -p ${u.ProjectID}`,"_projectInfo.json"):null;return{notes:[`ok fetched ${u.ProjectID}/${u.ModuleID} (${r}) -> ${C.moduleDir}`,`files: ${yr(C.sync)}`,...D.length>0?[`removed orphaned local files: ${D.join(", ")}`]:[],...br(A),c?"project data already local (not fetched); refresh: fui project sync":"project data bootstrapped (project.json, components, imports, modules/_modules.json)",...G?[G]:[],`skills: ${N.skillDir}${N.source==="bundled"?" (fallback summary; copy failed)":""}`,...C.warnings.map((_)=>`warning: ${_}`),...Ka(l),...jo(d),"now active for fui module preview / publish / validate"],data:`${Ea(u.ModuleID,{"_moduleInfo.json":"{}",...v})}
149
- `}}async function oc(e,t,n,o){let s=await e.api(),r=o==="ui"?await s.getModuleUI(t,n):await s.getModuleHtml(t,n),i=r.data,a=Be(e.paths.projectDir(t),"modules",n),c=[];if(o==="ui"&&i?.moduleUI!==void 0&&i.moduleUI!==null&&await ht(a)){let l;if(typeof i.moduleUI==="string")try{l=JSON.stringify(JSON.parse(i.moduleUI),null,2)}catch{l=i.moduleUI}else l=JSON.stringify(i.moduleUI,null,2);await Ae(e.paths,a,{"module.json":l}),c.push(`module.json written to ${a}`)}if(o==="html"&&typeof i?.mBody==="string"&&await ht(a))await Ae(e.paths,a,{"body.html":i.mBody}),c.push(`body.html written to ${a}`);return{notes:c,data:`${JSON.stringify(r)}
150
- `}}async function an(e,t,n){let o=t,s=n;if(!o){let a=e.targets.cwdTarget()??await e.session.getTarget();if(!a?.projectId)throw new f("no project: pass -p <project> (and -m <module> for module scope), or cd into a module");o=a.projectId,s=n||a.moduleId||void 0}let r=s?Be(e.paths.projectDir(o),"modules",s):e.paths.projectDir(o);return{projectId:o,moduleId:s,baseDir:r}}async function rc(e,t,n){let o=await(await e.api()).getModuleComponents(t.projectId,t.moduleId),s=o.find((a)=>a.comName===n),r=t.moduleId?`module ${t.moduleId}`:`project ${t.projectId} (global)`;if(!s)throw new f(`component "${n}" not found in ${r}; available: ${o.map((a)=>a.comName).join(", ")}`);let i=[`comID ${s.comID} scope ${t.moduleId?`module:${t.moduleId}`:"project (global)"}`];if(await ht(t.baseDir))await Ae(e.paths,t.baseDir,{[`components/${n}.vue`]:s.comCode}),i.push(`${n}.vue written to ${t.baseDir}/components/`);return{notes:i,data:s.comCode}}function cn(e){if(e===void 0)return;let t=Array.isArray(e)?e:[e],n={};for(let o of t){let s=String(o),r=s.indexOf("=");if(r<=0)throw new f(`--set expects key=value, got "${s}"`);let i=s.slice(r+1);try{n[s.slice(0,r)]=JSON.parse(i)}catch{n[s.slice(0,r)]=i}}return n}async function ts(e){let t=typeof e.data==="string"?e.data:void 0,n=typeof e["data-file"]==="string"?e["data-file"]:void 0;if(t===void 0===(n===void 0))throw new f("pass exactly one of --data '<json>' | --data - | --data-file <path>");let o=n!==void 0?await Bun.file(n).text():t==="-"?await Bun.stdin.text():t,s;try{s=JSON.parse(o)}catch(r){throw new f(`--data is not valid JSON: ${r.message}`)}if(!s||typeof s!=="object"||Array.isArray(s))throw new f("--data must be a JSON object");return s}async function yt(e,t,n){let o=typeof e[t]==="string"?e[t]:void 0,s=typeof e[`${t}-file`]==="string"?e[`${t}-file`]:void 0;if(o===void 0&&s===void 0)return;if(o!==void 0&&s!==void 0)throw new f(`pass --${t} or --${t}-file, not both`);let r=s!==void 0?await Bun.file(s).text():o==="-"?await Bun.stdin.text():o,i;try{i=JSON.parse(r)}catch(c){throw new f(`--${t} is not valid JSON: ${c.message}`)}if(!(n==="array"?Array.isArray(i):!!i&&typeof i==="object"&&!Array.isArray(i)))throw new f(`--${t} must be a JSON ${n}`);return i}function ns(...e){return Object.fromEntries(e.flatMap((t)=>[[t,{type:"string"}],[`${t}-file`,{type:"string"}]]))}var ln={module:{type:"string",short:"m"}};function Co(e){for(let t of e.notes)E(`# ${t}`);if(e.data)k(e.data)}function Hn(e){if(k(`${e.notes.join(`
148
+ `}}async function Ya(e,t){let{projectId:n,moduleId:o}=t,s=t.moduleDir,r="module_dir";if(!s){if(!t.target&&!(n&&o))throw new h("give a target (/project/module or URL), -p and -m, or --module-dir");let a=await(await ce(e)).resolveModuleTarget(t.target,n,o);n=a.projectId,o=a.moduleId,s=He(e.paths.projectDir(n),"modules",o),r=a.matchedBy}else{let a=await U(He(s,"_moduleInfo.json"),null);if(!a)throw new h(`missing _moduleInfo.json in ${s}`);n=a.ProjectID,o=a.ModuleID}let i=t.moduleDir?ns(ns(ns(s))):e.paths.root;return await e.session.setTarget({projectId:n??"",moduleId:o??"",moduleDir:s,workspaceRoot:i,target:t.target}),{notes:[`active ${n}/${o} (${r}) -> ${s}`,`session ${e.session.file}`]}}async function Qa(e){return(await e.api()).listProjects()}async function Za(e,t){let n=await ce(e),o=e.paths.projectDir(t),s=await n.fetchProjectBundle(t),r=qr(s.projectInfo,s.projectJson,s.modules,s.projectComponents,s.projectImports),{"modules/_modules.json":i,...a}=r,c=new Set(ht(s.projectImports).map((y)=>y.filePath).filter((y)=>!!y)),l=await Ln(e.paths,o,c),u=await n.downloadImportContents(s.projectImports),d=await Jr(e.paths,o,{...a,...u.files}),p=await Ma(e.paths,o,new Set(s.modules.map((y)=>y.ModuleID)));await Un(e.paths,t,s.modules);let m=!1,w=await e.session.getTarget();if(w?.projectId===t&&w.moduleId&&p.includes(w.moduleId))await e.session.clearTarget(),m=!0;return{notes:[`ok synced project ${t} -> ${o}`,`modules ${s.modules.length}, project components ${s.projectComponents.length}, project imports ${s.projectImports.length} (contents ${Object.keys(u.files).length})`,`files: ${kr(d)}`,...l.length>0?[`removed orphaned import files: ${l.join(", ")}`]:[],...p.length>0?[`removed orphaned module dirs (archived): ${p.join(", ")}`]:[],...m?["session target cleared: it pointed to a removed module; run fui use / fui module get again"]:[],...vr(d.archived),...za(u.failures)]}}async function ec(e,t){let n=await(await e.api()).listModules(t),o;try{await Un(e.paths,t,n),o="cache updated: modules/_modules.json (+ _moduleInfo.json of checked-out modules)"}catch(s){o=`warning: could not write the local cache: ${s.message}`}return{rows:n,notes:[o]}}async function tc(e,t){let n=await ce(e),o=(await n.resolveModuleInfoFromCache(t.projectId,t.moduleId)).found;if(o)throw new h(`module "${t.projectId}/${t.moduleId}" already exists ("${o.ModuleName}"); edit it: fui module update -p ${t.projectId} -m ${t.moduleId}, or pick another id`);let s={...t.extra,Framework:t.framework??"V2",mDomain:t.mDomain??"",ModuleID:t.moduleId,ModuleName:t.moduleName,HTMLOnly:t.htmlOnly??!1,dialogModel:"new"},i=(await n.api.editModule(t.projectId,s)).data??[],a=i.find((l)=>l.ModuleID===t.moduleId)??i[0],c=[`ok created module ${a?.id??`${t.projectId}/${t.moduleId}`} url ${a?.url??`/${t.projectId}/${t.moduleId}`} framework ${s.Framework}`];if(a)try{await So(e.paths,t.projectId,a),c.push("modules/_modules.json updated")}catch{c.push("warning: module created but modules/_modules.json update failed")}return c.push(`next: fui module get -p ${t.projectId} -m ${t.moduleId}`),{notes:c}}async function nc(e,t){let n=await ce(e),{projectId:o,moduleId:s,newModuleId:r}=t,i=He(e.paths.projectDir(o),"modules",s),a=await U(He(i,"_moduleInfo.json"),null),c=a?null:await n.resolveModuleInfoFromCache(o,s),l=a??c?.found;if(!l)throw rn(o,s,c??void 0);let u=[];if(t.framework&&t.framework!==l.Framework)u.push(`warning: framework ${l.Framework} -> ${t.framework} switches the CDN set (V2/V3); re-check header/body/script and components`);if(r&&r!==s)u.push(`warning: ModuleID "${s}" -> "${r}" changes the module URL; links and menus to the old URL break`);let{mTitle:d,...p}=l,m={...p,...t.extra,Framework:t.framework??l.Framework,mDomain:t.mDomain??l.mDomain??"",ModuleID:r??s,ModuleName:t.moduleName??l.ModuleName,HTMLOnly:t.htmlOnly??l.HTMLOnly,dialogModel:"edit"},y=((await n.api.editModule(o,m)).data??[]).find((I)=>I.ModuleID===m.ModuleID),v={...l,Framework:m.Framework,mDomain:m.mDomain,ModuleID:m.ModuleID,ModuleName:m.ModuleName,HTMLOnly:m.HTMLOnly};if(r&&r!==s)v.id=`${o}/${r}`,v.url=`/${o}/${r}`;if(y)v={...v,...y};await So(e.paths,o,v,s);let E=["modules/_modules.json updated"];if(a){let I=i;if(r&&r!==s){let b=He(e.paths.projectDir(o),"modules",r);if(await gt(b))u.push(`warning: cannot rename the local folder: ${b} exists; _moduleInfo.json updated in place`);else await mm(i,b),I=b,E.push(`local folder renamed: modules/${s} -> modules/${r}`)}await Bun.write(He(I,"_moduleInfo.json"),JSON.stringify({...a,...v},null,2)),E.push("_moduleInfo.json updated");let g=await e.session.getTarget();if(g&&g.projectId===o&&g.moduleId===s)await e.session.setTarget({projectId:o,moduleId:v.ModuleID,moduleDir:I,workspaceRoot:g.workspaceRoot,target:g.target}),E.push("session target updated")}return{notes:[`ok updated ${v.id} url ${v.url} name ${v.ModuleName} framework ${v.Framework} htmlOnly ${v.HTMLOnly}`,...E,...u]}}async function oc(e,t,n){await(await e.api()).deleteModule(t,n);let o=[`ok deleted module ${n} from project ${t}`];try{o.push(await La(e.paths,t,n)?"entry removed from modules/_modules.json":"no entry in modules/_modules.json (already absent)")}catch(i){o.push(`warning: could not remove the cache entry: ${i.message}`)}let s=He(e.paths.projectDir(t),"modules",n);if(await gt(s)){await co(e.paths,s,{suffix:"deleted"});let{rm:i}=await import("fs/promises");await i(s,{recursive:!0,force:!0}),o.push(`local folder removed (archived to _history first): modules/${n}`)}let r=await e.session.getTarget();if(r&&r.projectId===t&&r.moduleId===n)await e.session.clearTarget(),o.push("session target cleared (it pointed to the deleted module)");return{notes:o}}async function rc(e,t){let n=await ce(e),o,s,r,i;if(t.projectId&&t.moduleId)o=t.projectId,s=t.moduleId,r="explicit_ids";else if(t.target){let _=await n.resolveProjectAndModuleHint(t.target);o=_.projectId,s=_.moduleInfo?.ModuleID??_.moduleHint,r=_.matchedBy,i=_.moduleInfo}else{let _=await e.targets.resolveProjectModule();o=_.projectId,s=_.moduleId,r=_.source==="cwd"?"cwd":"active_session"}let a=e.paths.projectDir(o),c=await U(He(a,"_projectInfo.json"),null)!==null,l=[];if(!c){let _=await n.fetchProjectBundle(o);await Jr(e.paths,a,qr(_.projectInfo,_.projectJson,_.modules,_.projectComponents,_.projectImports));let ue=await n.downloadImportContents(_.projectImports);if(l.push(...ue.failures),Object.keys(ue.files).length>0)await Ne(e.paths,a,ue.files)}let u=i??null;if(!u){let _=await n.lookupModuleInfo(o,(ue)=>{let Ie=ue.find((ae)=>ae.ModuleID===s);if(Ie||r!=="target_path")return Ie??null;let H=Zr(ue,s);if(H.length>1)throw new h(`target "${t.target}" matched several modules in project ${o}: ${H.map((ae)=>ae.ModuleID).join(", ")}`);return H[0]??null});if(u=_.found,!u)throw rn(o,s,_)}let{content:d,components:p,script:m,css:w,dependencies:y}=await n.fetchModuleBundle(u),v=tn(u,d,p,m,w,y),E=He(e.paths.projectDir(u.ProjectID),"modules",u.ModuleID),I=new Set(ht(y.filter((_)=>_.Scope!=="project")).map((_)=>_.filePath).filter((_)=>!!_)),g=await Ln(e.paths,E,I),[b,R]=await Promise.all([n.downloadImportContents(y.filter((_)=>_.Scope==="module")),n.downloadImportContents(c?y.filter((_)=>_.Scope==="project"):[])]);l.push(...b.failures,...R.failures);let C=await xa(e.paths,E,u,d,{...v,...b.files}),S=Object.keys(R.files).length>0?await Ne(e.paths,a,R.files):null;await So(e.paths,o,{...u,Framework:d.Framework,HTMLOnly:d.HTMLOnly});let A=await Io(e.paths.root);await e.session.setTarget({projectId:u.ProjectID,moduleId:u.ModuleID,moduleDir:C.moduleDir,workspaceRoot:e.paths.root,target:t.target});let D=[...C.removedFiles.map((_)=>hm(_)),...g],N=[...C.sync.archived,...S?.archived??[]],G=c?await Pt(a,"project data (project.json, components, imports)",`fui project sync -p ${u.ProjectID}`,"_projectInfo.json"):null;return{notes:[`ok fetched ${u.ProjectID}/${u.ModuleID} (${r}) -> ${C.moduleDir}`,`files: ${kr(C.sync)}`,...D.length>0?[`removed orphaned local files: ${D.join(", ")}`]:[],...vr(N),c?"project data already local (not fetched); refresh: fui project sync":"project data bootstrapped (project.json, components, imports, modules/_modules.json)",...G?[G]:[],`skills: ${A.skillDir}${A.source==="bundled"?" (fallback summary; copy failed)":""}`,...C.warnings.map((_)=>`warning: ${_}`),...za(l),...jo(d),"now active for fui module preview / publish / validate"],data:`${Na(u.ModuleID,{"_moduleInfo.json":"{}",...v})}
149
+ `}}async function sc(e,t,n,o){let s=await e.api(),r=o==="ui"?await s.getModuleUI(t,n):await s.getModuleHtml(t,n),i=r.data,a=He(e.paths.projectDir(t),"modules",n),c=[];if(o==="ui"&&i?.moduleUI!==void 0&&i.moduleUI!==null&&await gt(a)){let l;if(typeof i.moduleUI==="string")try{l=JSON.stringify(JSON.parse(i.moduleUI),null,2)}catch{l=i.moduleUI}else l=JSON.stringify(i.moduleUI,null,2);await Ne(e.paths,a,{"module.json":l}),c.push(`module.json written to ${a}`)}if(o==="html"&&typeof i?.mBody==="string"&&await gt(a))await Ne(e.paths,a,{"body.html":i.mBody}),c.push(`body.html written to ${a}`);return{notes:c,data:`${JSON.stringify(r)}
150
+ `}}async function an(e,t,n){let o=t,s=n;if(!o){let a=e.targets.cwdTarget()??await e.session.getTarget();if(!a?.projectId)throw new h("no project: pass -p <project> (and -m <module> for module scope), or cd into a module");o=a.projectId,s=n||a.moduleId||void 0}let r=s?He(e.paths.projectDir(o),"modules",s):e.paths.projectDir(o);return{projectId:o,moduleId:s,baseDir:r}}async function ic(e,t,n){let o=await(await e.api()).getModuleComponents(t.projectId,t.moduleId),s=o.find((a)=>a.comName===n),r=t.moduleId?`module ${t.moduleId}`:`project ${t.projectId} (global)`;if(!s)throw new h(`component "${n}" not found in ${r}; available: ${o.map((a)=>a.comName).join(", ")}`);let i=[`comID ${s.comID} scope ${t.moduleId?`module:${t.moduleId}`:"project (global)"}`];if(await gt(t.baseDir))await Ne(e.paths,t.baseDir,{[`components/${n}.vue`]:s.comCode}),i.push(`${n}.vue written to ${t.baseDir}/components/`);return{notes:i,data:s.comCode}}function cn(e){if(e===void 0)return;let t=Array.isArray(e)?e:[e],n={};for(let o of t){let s=String(o),r=s.indexOf("=");if(r<=0)throw new h(`--set expects key=value, got "${s}"`);let i=s.slice(r+1);try{n[s.slice(0,r)]=JSON.parse(i)}catch{n[s.slice(0,r)]=i}}return n}async function os(e){let t=typeof e.data==="string"?e.data:void 0,n=typeof e["data-file"]==="string"?e["data-file"]:void 0;if(t===void 0===(n===void 0))throw new h("pass exactly one of --data '<json>' | --data - | --data-file <path>");let o=n!==void 0?await Bun.file(n).text():t==="-"?await Bun.stdin.text():t,s;try{s=JSON.parse(o)}catch(r){throw new h(`--data is not valid JSON: ${r.message}`)}if(!s||typeof s!=="object"||Array.isArray(s))throw new h("--data must be a JSON object");return s}async function Co(e,t,n){let o=typeof e[t]==="string"?e[t]:void 0,s=typeof e[`${t}-file`]==="string"?e[`${t}-file`]:void 0;if(o===void 0&&s===void 0)return;if(o!==void 0&&s!==void 0)throw new h(`pass --${t} or --${t}-file, not both`);let r=s!==void 0?await Bun.file(s).text():o==="-"?await Bun.stdin.text():o,i;try{i=JSON.parse(r)}catch(c){throw new h(`--${t} is not valid JSON: ${c.message}`)}if(!(n==="array"?Array.isArray(i):!!i&&typeof i==="object"&&!Array.isArray(i)))throw new h(`--${t} must be a JSON ${n}`);return i}function rs(...e){return Object.fromEntries(e.flatMap((t)=>[[t,{type:"string"}],[`${t}-file`,{type:"string"}]]))}var ln={module:{type:"string",short:"m"}};function Ro(e){for(let t of e.notes)T(`# ${t}`);if(e.data)k(e.data)}function Bn(e){if(k(`${e.notes.join(`
151
151
  `)}
152
- `),e.data)k(e.data)}async function os(e){return(await e.targets.resolveProject(h(e.values,"project"))).projectId}async function ic(e){return e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module"))}function sm(e,t){if(e===void 0)return;if(e==="true")return!0;if(e==="false")return!1;throw new f(`${t} takes true or false`)}function ac(e){if(e===void 0)return;let t=e.toUpperCase();if(t!=="V2"&&t!=="V3")throw new f("--framework must be V2 or V3");return t}var im={name:"use",mcpName:"session_set",usage:"fui use [<target> | -p P -m M | --module-dir D] | fui use --clear",summary:"Show, set or clear the session target (target: /project/module, a live URL, or a display name)",options:{...ln,"module-dir":{type:"string"},clear:{type:"boolean"}},async run(e){let{values:t,positionals:n,session:o,render:s}=e;if(t.clear===!0){await o.clearTarget(),k(`session target cleared
153
- `);return}let r=n[0],i=h(t,"project"),a=h(t,"module"),c=h(t,"module-dir");if(!r&&!i&&!a&&!c){let l=await o.getTarget();if(!l){E("# no session target; set one: fui use <project>/<module>");return}k(X([{...l,session:o.file}],s));return}if(i&&!a||!i&&a)throw new f("pass both -p and -m");Hn(await za(O(e),{target:r,projectId:i,moduleId:a,moduleDir:c}))}},am={name:"resolve",mcpName:"session_resolve",usage:"fui resolve <target>",summary:"Resolve /project/module, a live module URL or a display name to ids (no session change)",async run(e){let t=e.positionals[0];if(!t)throw new f("missing <target>");Co(await Ga(O(e),t))}},cm={name:"project list",mcpName:"project_list",usage:"fui project list",summary:"List projects you can access",async run(e){let t=(await Xa(O(e))).map((n)=>({group:n.GroupName||"Khac",id:n.ProjectID,name:n.ProjectName,framework:n.Framework??"",domain:n.pDomain??""}));k(X(t,e.render))}},lm={name:"project sync",mcpName:"project_sync",usage:"fui project sync [-p project]",summary:"Re-fetch project data (project.json, components, imports, module list); archives what changed or vanished",async run(e){Hn(await Ya(O(e),await os(e)))}},um={name:"module list",mcpName:"module_list",usage:"fui module list [-p project]",summary:"List modules from the server and refresh the local module cache",async run(e){let{rows:t,notes:n}=await Qa(O(e),await os(e));for(let o of n)E(`# ${o}`);k(X(t.map((o)=>({id:o.ModuleID,name:o.ModuleName,framework:o.Framework??"",domain:o.mDomain??"",htmlOnly:o.HTMLOnly?"yes":"no",updated:o.UpdateTime??"",by:o.UpdateUser??""})),e.render))}},dm={name:"module new",mcpName:"module_new",usage:"fui module new <moduleId> -p <project> --name <name> [--framework V2|V3] [--html-only] [--domain d] [--set key=json ...]",summary:"Create an empty module on the server (then fui module get)",options:{name:{type:"string"},framework:{type:"string"},"html-only":{type:"boolean"},domain:{type:"string"},set:{type:"string",multiple:!0}},async run(e){let t=e.positionals[0],n=h(e.values,"name");if(!t||!n)throw new f("need <moduleId> and --name");Hn(await Za(O(e),{projectId:await os(e),moduleId:t,moduleName:n,framework:ac(h(e.values,"framework")),htmlOnly:e.values["html-only"]===!0?!0:void 0,mDomain:h(e.values,"domain"),extra:cn(e.values.set)}))}},pm={name:"module update",mcpName:"module_update",usage:"fui module update [-p P -m M] [--name n] [--framework V2|V3] [--html-only true|false] [--domain d] [--new-id id] [--set key=json ...]",summary:"Edit metadata of an existing module (name, id, framework, domain, HTMLOnly)",options:{...ln,name:{type:"string"},framework:{type:"string"},"html-only":{type:"string"},domain:{type:"string"},"new-id":{type:"string"},set:{type:"string",multiple:!0}},async run(e){let{projectId:t,moduleId:n}=await ic(e);Hn(await ec(O(e),{projectId:t,moduleId:n,moduleName:h(e.values,"name"),framework:ac(h(e.values,"framework")),htmlOnly:sm(h(e.values,"html-only"),"--html-only"),mDomain:h(e.values,"domain"),newModuleId:h(e.values,"new-id"),extra:cn(e.values.set)}))}},mm={name:"module delete",mcpName:"module_delete",usage:"fui module delete -p <project> -m <module>",summary:"Delete a module on the server and archive its local folder (ask the user first; ids must be explicit)",options:ln,async run(e){let t=h(e.values,"project"),n=h(e.values,"module");if(!t||!n)throw new f("module delete needs explicit -p <project> -m <module>");Hn(await tc(O(e),t,n))}},fm={name:"module get",mcpName:"module_get",usage:"fui module get [<target> | -p P -m M]",summary:"Check out a module into the workspace (archives differing local files) and make it the session target",options:ln,async run(e){let t=h(e.values,"project"),n=h(e.values,"module");if(t&&!n||!t&&n)throw new f("pass both -p and -m, or a target");Co(await nc(O(e),{target:e.positionals[0],projectId:t,moduleId:n}))}};function sc(e){return{name:`module get-${e}`,mcpName:`module_get_${e}`,usage:`fui module get-${e} [-p P -m M]`,summary:`Fetch only the module ${e==="ui"?"UI JSON (writes module.json":"HTML body (writes body.html"} if checked out)`,options:ln,async run(t){let{projectId:n,moduleId:o}=await ic(t);Co(await oc(O(t),n,o,e))}}}var hm={name:"component get",mcpName:"component_get",usage:"fui component get <name> [-p P [-m M]]",summary:"Print a component's .vue source (writes it locally when the scope folder exists); -p without -m = project scope",options:ln,async run(e){let t=e.positionals[0];if(!t)throw new f("missing <name>");let n=O(e),o=await an(n,h(e.values,"project"),h(e.values,"module"));Co(await rc(n,o,t))}},cc=[im,am,cm,lm,um,dm,pm,mm,fm,sc("ui"),sc("html"),hm];import{join as it}from"path";import st from"path";import{join as lc}from"path";async function Ot(e,t){let n={functionRights:null,systemRights:null},o;try{o=JSON.parse(await H(lc(e,"_db","rights.json")))}catch{return n}let s=Object.keys(o??{});if(s.length===0)return n;let r=(l)=>s.find((u)=>u.toLowerCase()===l.toLowerCase())??null,i;if(t)i=r(t);else if(s.length===1)i=s[0];else{let l=await U(lc(e,"_db","_connections.json"),null);i=l?.default?r(l.default):null}if(!i)return n;let a=o[i],c=(l,u)=>Array.isArray(l)?new Set(l.map((d)=>d&&typeof d==="object"?String(d[u]??""):"").filter(Boolean)):null;return{functionRights:c(a?.functionRights,"FunctionCode"),systemRights:c(a?.systemRights,"SysRight")}}function Mt(e){return dc("FunctionRight",e)}function uc(e){return dc("SystemRight",e)}function Wn(e,t){if(e.length===0)return null;if(t===null)return`Ch\u01B0a c\xF3 cache _db/rights.json \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c ${e.length} m\xE3 FunctionRight (${e.join(", ")}) `+"c\xF3 th\u1EADt hay kh\xF4ng. Preview c\u1EA5p s\u1EB5n \u0111\xFAng nh\u1EEFng m\xE3 n\xE0y n\xEAn control v\u1EABn hi\u1EC7n \u1EDF \u0110\xC2Y; user th\u1EADt ch\u1EC9 th\u1EA5y n\u1EBFu m\xE3 \u0111\xE3 "+"\u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a. Ch\u1EA1y right_function_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i render l\u1EA1i.";let n=e.filter((o)=>!t.has(o));if(n.length===0)return null;return`\uD83D\uDEA8 FunctionRight CH\u01AFA \u0110\u01AF\u1EE2C \u0110\u1ECANH NGH\u0128A tr\xEAn h\u1EC7 th\u1ED1ng acc: ${n.join(", ")} \u2014 preview c\u1EA5p s\u1EB5n n\xEAn control v\u1EABn `+"hi\u1EC7n \u1EDF \u0110\xC2Y, nh\u01B0ng ch\u1EA1y th\u1EADt th\xEC rightTest() lu\xF4n tr\u1EA3 false \u2192 control/menu bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng c\xF3 l\u1ED7i JS n\xE0o. "+'T\u1EA1o m\xE3: right_function_new({ functionCode: \u2026, functionName: "\u2026" }); ho\u1EB7c right_function_list n\u1EBFu cache c\u0169.'}function dc(e,t){let n=new Set,o=new RegExp(`${e}['"]?\\s*:\\s*\\[([^\\]]*)\\]`,"g"),s=new RegExp(`${e}\\s*\\.\\s*includes\\(\\s*['"]([^'"]+)['"]`,"g");for(let r of t){if(!r)continue;let i;o.lastIndex=0;while(i=o.exec(r))for(let a of i[1].split(",")){let c=a.trim().replace(/^['"]|['"]$/g,"");if(c)n.add(c)}s.lastIndex=0;while(i=s.exec(r))n.add(i[1])}return Array.from(n).sort()}var gm=new Set(["f-table","f-dialog","f-window","f-button","f-date","f-time","f-time-counter","f-search","f-menu","f-radiobox","f-file-upload","f-image-update","f-excel-reader","f-qrcode","f-qrcode-reader","f-editor","f-editor-dialog","f-echart","f-chart","f-sheet","f-pdfmake","f-label","f-box","f-header","f-title","f-slider","fp-profile","f-dynamic-component","f-openwindow-control","f-table-item-component"]),pc=new Set(["t-html","t-label","t-num","t-time","t-boolean","t-check","t-text","t-select","t-combobox","t-menu","t-link","t-button"]),wm={"f-pdfmake":["pdfmake.min.js","print.min.js","vfs_fonts.js"],"f-file-upload":["plupload.full.min.js"],"f-image-update":["index.umd.js","style.css"],"f-qrcode":["qrcode.min.js"],"f-qrcode-reader":["qrcode-reader.min.js"],"f-echart":["echarts.min.js","fechart.js"],"f-sheet":["ag-grid-community.min.js","fsheet.js"],"f-editor":["ckeditor.js"],"f-editor-dialog":["ckeditor.js"]};var un=new Set(["API","CALL","IF","FUN","EXE","MESS","MESSBOX","ARRAY","CONFIRM","IN","OUT","THEN","ELSE","CALLBACK","ERROROUT","ERRORCALLBACK","CANCEL","METHOD","HEADER","COL"]),mc=new Set(["item","index","v_old","user","vueData","p_router","p_routers","v_Set","moment"]);function j(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function is(e){if(Array.isArray(e))return e.length>0&&e.every((t)=>j(t)||typeof t==="string")&&e.some((t)=>j(t)&&Object.keys(t).some((n)=>un.has(n)));if(!j(e))return!1;return Object.keys(e).some((t)=>un.has(t))}function Ge(e){let t=e.trim();if(t.startsWith("vueData."))t=t.slice(8);let n=t.match(/^[A-Za-z_$][\w$]*/);return n?n[0]:t}function qn(e){return!!e&&!e.includes(" ")&&!e.includes("{{")&&!e.includes("(")&&!e.startsWith("`")&&!e.startsWith("#")&&!e.startsWith("!")&&/^[A-Za-z_$]/.test(e.replace(/^vueData\./,""))}function ym(e){let t=e.trim();if(!t)return!0;if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")return!0;if(/^'[^']*'$/.test(t)||/^"[^"]*"$/.test(t))return!0;return/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(t)}function bm(e){let t=e.split(""),n=null;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(s==="\\"){if(t[o]=" ",o+1<e.length)t[o+1]=" ",o++;continue}if(s===n){n=null;continue}t[o]=" "}else if(s==="'"||s==='"'||s==="`")n=s}return t.join("")}var km=[{re:/\bfunction\s*\(/,what:'khai b\xE1o "function(...)"'},{re:/\bfunction\s+[A-Za-z_$]/,what:'khai b\xE1o "function ten(...)"'},{re:/\breturn\b/,what:'l\u1EC7nh "return"'},{re:/\bvar\s+[A-Za-z_$]/,what:'khai b\xE1o "var"'},{re:/\blet\s+[A-Za-z_$]/,what:'khai b\xE1o "let"'},{re:/\bconst\s+[A-Za-z_$]/,what:'khai b\xE1o "const"'}];function $m(e,t,n,o){if(typeof t!=="string")return;if(!(e.startsWith(":")&&!e.startsWith("::")||e.startsWith("v-bind:")))return;let r=bm(t),i=[];for(let{re:a,what:c}of km)if(a.test(r))i.push(c);if(r.replace(/;\s*$/,"").includes(";"))i.push('d\u1EA5u ";" n\u1ED1i nhi\u1EC1u c\xE2u l\u1EC7nh');if(i.length===0)return;o.push(`attr "${e}" (t\u1EA1i ${n}) ch\u1EE9a ${i.join(" + ")} \u2014 gi\xE1 tr\u1ECB binding \u0111i th\u1EB3ng v\xE0o template compiler c\u1EE7a Vue, `+"ch\u1EC9 nh\u1EADn M\u1ED8T BI\u1EC2U TH\u1EE8C thu\u1EA7n (object/array literal, ternary, chu\u1ED7i .map()/.filter() arrow, truy c\u1EADp thu\u1ED9c t\xEDnh). "+"Statement \u1EDF \u0111\xE2y l\xE0m compile template h\u1ECFng \u2192 TRANG TR\u1EAENG m\xE0 kh\xF4ng c\xF3 JS error hay [Vue warn] n\xE0o. "+'C\xE1ch s\u1EEDa: \u0111\u01B0a logic v\xE0o m\u1ED9t h\xE0m trong script.js (function buildX(){ \u2026 vueData.x = \u2026; }), g\u1ECDi qua "EXE": "buildX()" '+`trong CALLBACK c\u1EE7a action n\u1EA1p d\u1EEF li\u1EC7u, r\u1ED3i "${e}" ch\u1EC9 tr\u1ECF t\u1EDBi bi\u1EBFn vueData \u0111\xF3 (vd "${e}": "x"). `+"Chi ti\u1EBFt: references/advanced-techniques.md.")}function Fe(e,t,n="$",o=!1){if(Array.isArray(e))e.forEach((s,r)=>Fe(s,t,`${n}[${r}]`,o));else if(j(e)){t(e,n,o);for(let[s,r]of Object.entries(e))Fe(r,t,`${n}.${s}`,o||s.startsWith("#"))}}var vm="(chu\u1EA9n m\u1EB7c \u0111\u1ECBnh fui/DESIGN.md \u2014 ch\u1EC9 gi\u1EEF nguy\xEAn n\u1EBFu user y\xEAu c\u1EA7u r\xF5 ki\u1EC3u kh\xE1c)",Sm=new Set([400,550,600,650,800]),jm=/(x\u00F3a|xo\u00E1|\bdelete\b|\bremove\b)/i,Im=/(delete|remove|xoa)/i,Cm=new Set(["v-text-field","v-textarea","v-select","v-combobox","v-autocomplete","f-date","f-time","f-search"]),fc=new Set(["9"]);function Rm(e,t,n){let o=JSON.stringify(e),s=Mt([o]),r=uc([o]).filter((l)=>!fc.has(l)),i=s.length+r.length;if(i===0)return 0;let a=t.definedFunctionRights??null,c=t.definedSystemRights??null;if(a===null&&c===null)return n.push(`Module g\xE1c quy\u1EC1n b\u1EB1ng ${i} m\xE3 literal (${[...s,...r].join(", ")}) nh\u01B0ng ch\u01B0a c\xF3 cache _db/rights.json `+"\u0111\u1EC3 \u0111\u1ED1i chi\u1EBFu \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c m\xE3 n\xE0o c\xF3 th\u1EADt. M\xE3 kh\xF4ng t\u1ED3n t\u1EA1i l\xE0m rightTest() lu\xF4n tr\u1EA3 false: "+"control/menu bi\u1EBFn m\u1EA5t im l\u1EB7ng, kh\xF4ng l\u1ED7i JS. Ch\u1EA1y right_function_list + right_system_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i validate l\u1EA1i."),i;if(a&&s.length>0){let l=a,u=s.filter((d)=>!l.has(d));for(let d of u)n.push(`FunctionRight "${d}" ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3 ${l.size} m\xE3`+`${l.size?`: ${Array.from(l).slice(0,15).join(", ")}${l.size>15?", \u2026":""}`:""}). `+"rightTest() s\u1EBD lu\xF4n tr\u1EA3 false \u2192 control/menu g\xE1c b\u1EB1ng m\xE3 n\xE0y bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng b\xE1o l\u1ED7i. "+`T\u1EA1o m\xE3: right_function_new({ functionCode: ${/^\d+$/.test(d)?d:`"${d}"`}, functionName: "\u2026" }); `+"ho\u1EB7c ch\u1EA1y right_function_list n\u1EBFu m\xE3 \u0111\xE3 c\xF3 tr\xEAn server m\xE0 cache c\u0169.")}if(c&&r.length>0){let l=c,u=r.filter((d)=>!l.has(d));for(let d of u)n.push(`SystemRight ${d} ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3: `+`${l.size?Array.from(l).sort().join(", "):"(ch\u01B0a m\u1EE9c n\xE0o)"}${fc.size?" \u2014 m\u1EE9c 9 lu\xF4n h\u1EE3p l\u1EC7":""}). `+"Kh\xF4ng ai \u0111\u01B0\u1EE3c c\u1EA5p m\u1EE9c n\xE0y th\xEC control g\xE1c b\u1EB1ng n\xF3 bi\u1EBFn m\u1EA5t im l\u1EB7ng. "+`\u0110\u1ECBnh ngh\u0129a: right_system_new({ sysRight: ${d}, note: "\u2026" }); ho\u1EB7c ch\u1EA1y right_system_list n\u1EBFu cache c\u0169.`)}return i}function Pm(e,t){let n=(a)=>t.push(`[design] ${a} ${vm}`),o=(a)=>typeof a==="string"?a:"";Fe(e,(a,c)=>{let l=a.API;if(typeof l==="string"&&Im.test(l)&&a.CONFIRM===void 0)n(`Action g\u1ECDi API "${l}" (t\u1EA1i ${c}) c\xF3 v\u1EBB l\xE0 thao t\xE1c X\xD3A nh\u01B0ng thi\u1EBFu CONFIRM \u2014 m\u1ECDi action x\xF3a ph\u1EA3i h\u1ECFi x\xE1c nh\u1EADn.`)});let s=new Set;Fe(e,(a,c)=>{if(s.has(a))return;let l=j(a.attr)?a.attr:a;if(l!==a)s.add(l);let u=o(l.label)||o(l.text)||(typeof a.innerHTML==="string"?a.innerHTML:""),d=o(l["icon-text"]);if(!jm.test(u)&&d!=="mdi-delete")return;let p=o(l.color)||o(l[":color"]);if(/\b(error|red)\b/i.test(p)&&!p.includes("?"))n(`N\xFAt/m\u1EE5c x\xF3a (t\u1EA1i ${c}) \u0111ang m\xE0u "${p}" \u2014 n\xFAt x\xF3a d\xF9ng m\xE0u "warning" (cam), kh\xF4ng d\xF9ng error/\u0111\u1ECF.`)});let r=(a,c,l,u)=>{if(Array.isArray(a)){a.forEach((I,g)=>r(I,`${c}[${g}]`,l,u));return}if(!j(a))return;let d=o(a.el),p=o(a.prop),m=j(a.attr)?a.attr:{},w=l||d==="v-dialog"||d==="f-dialog"||d==="hidden-container"||p.includes("hidden-container"),y=u||d==="v-card-actions";if(d==="v-toolbar"&&["color",":color","dark",":dark"].some((I)=>(I in m)))n(`v-toolbar (t\u1EA1i ${c}) c\xF3 color/dark \u2014 toolbar ti\xEAu \u0111\u1EC1 ph\u1EA3i "dense flat" c\xF9ng m\xE0u n\u1EC1n v\u1EDBi th\xE2n dialog, kh\xF4ng t\xF4 m\xE0u/dark ri\xEAng.`);if(d==="v-dialog"||d==="f-dialog"){let I=m.width??m[":width"],g=typeof I==="number"?I:typeof I==="string"&&/^\d+$/.test(I.trim())?parseInt(I,10):NaN;if(!Number.isNaN(g)&&!Sm.has(g))n(`Dialog (t\u1EA1i ${c}) width=${g} \u2014 b\u1ED9 width chu\u1EA9n: 400 / 550 / 600 / 650 / 800.`)}if(w&&d==="v-card"&&["elevation",":elevation","outlined",":outlined"].some((I)=>(I in m)))n(`v-card trong dialog (t\u1EA1i ${c}) c\xF3 elevation/outlined \u2014 b\u1ECF \u0111i: v-dialog \u0111\xE3 t\u1EF1 c\xF3 b\xF3ng \u0111\u1ED5, th\xEAm n\u1EEFa l\xE0 b\xF3ng ch\u1ED3ng b\xF3ng. v-card ch\u1EC9 c\u1EA7n class "pa-0" (tri\u1EC7t \u0111\u1EC7m \u0111\u1EC3 toolbar ti\xEAu \u0111\u1EC1 s\xE1t m\xE9p).`);let v=o(m.class),T=v.includes("flex-md-grow-0")&&v.includes("ml-2");if(T)n(`Control (t\u1EA1i ${c}) mang class "flex-md-grow-0 ml-2" \u2014 d\u1EA5u hi\u1EC7u pattern label-div r\u1EDDi \u0111\xE3 lo\u1EA1i b\u1ECF; form d\xF9ng floating label ("label" trong attr c\u1EE7a field).`);if(w&&!T&&Cm.has(d)&&!("label"in m)&&!(":label"in m)&&!("placeholder"in m))n(`${d} trong dialog (t\u1EA1i ${c}) thi\u1EBFu "label" \u2014 form dialog d\xF9ng floating label.`);if(d==="v-snackbar"||d==="v-alert")n(`${d} (t\u1EA1i ${c}) \u2014 ph\u1EA3n h\u1ED3i/th\xF4ng b\xE1o d\xF9ng h\u1EC7 th\u1ED1ng c\xF3 s\u1EB5n c\u1EE7a FUI: MESS (toast) / MESSBOX (h\u1ED9p tho\u1EA1i) trong action, CONFIRM \u0111\u1EC3 x\xE1c nh\u1EADn \u2014 kh\xF4ng t\u1EF1 d\u1EF1ng ${d} trong controls.`);if(d==="f-table"||d==="f-sheet"){let I=m[":height"]??m.height;if(I===void 0)n(`${d} (t\u1EA1i ${c}) kh\xF4ng c\xF3 ":height" \u2014 b\u1EA3ng s\u1EBD t\u1EF1 gi\xE3n theo s\u1ED1 d\xF2ng v\xE0 l\xE0m c\u1EA3 TRANG cu\u1ED9n. B\u1EA3ng data ch\xEDnh n\xEAn \u0111\u1EB7t \`":height": -16\` (fill t\u1EDBi \u0111\xE1y m\xE0n h\xECnh, ch\u1EEBa 16px \u2014 component t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt, kh\xF4ng c\u1EA7n t\xEDnh tay); trong dialog d\xF9ng s\u1ED1 d\u01B0\u01A1ng (px c\u1ED1 \u0111\u1ECBnh, vd 360). B\u1EA3ng ng\u1EAFn/lookup ch\u1EE7 \xFD t\u1EF1 gi\xE3n th\xEC b\u1ECF qua.`);else if(typeof I==="string"&&/\$\s*\(\s*window\s*\)\s*\.\s*height\s*\(\s*\)/.test(I))n(`${d} (t\u1EA1i ${c}) t\xEDnh ":height" th\u1EE7 c\xF4ng theo viewport (\`${I}\`) \u2014 kh\xF4ng c\u1EA7n n\u1EEFa: ${d} t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt c\u1EE7a b\u1EA3ng sau layout v\xE0 t\u1EF1 c\u1EADp nh\u1EADt khi resize. Thay b\u1EB1ng \`":height": -16\` (s\u1ED1 \xE2m = kho\u1EA3ng ch\u1EEBa \u1EDF \u0111\xE1y).`)}if(y&&(d==="f-button"||d==="v-btn")){let I=o(m.label)||(typeof a.innerHTML==="string"?a.innerHTML:"");if(/^\s*(\u0111\u00F3ng|dong|close)\s*$/i.test(I))n(`N\xFAt "${I.trim()}" trong v-card-actions (t\u1EA1i ${c}) \u2014 dialog chu\u1EA9n ch\u1EC9 d\xF9ng icon close tr\xEAn toolbar, kh\xF4ng th\xEAm n\xFAt \u0110\xF3ng \u1EDF actions.`)}for(let[I,g]of Object.entries(a))r(g,`${c}.${I}`,w,y)};(Array.isArray(e.controls)?e.controls:[]).forEach((a,c)=>r(a,`$.controls[${c}]`,!1,!1))}var Em=12,rs=25,gc=/\b(shrink|flex-grow-0)\b/;function wc(e){return typeof e==="string"?e:j(e)?[e.class,e[":class"]].filter((t)=>typeof t==="string").join(" "):""}function Tm(e){return e.w===void 0||e.w===null||e.w===""||e.w===0}function Am(e,t){Fe(e.controls,(n,o)=>{if(!Array.isArray(n.cols))return;Nm(n.cols,o,t),n.cols.forEach((s,r)=>{if(!j(s))return;let i=s,a=i.w,c=typeof a==="number"?a:typeof a==="string"&&/^\d+$/.test(a.trim())?parseInt(a.trim(),10):NaN;if(Number.isNaN(c)||c<=Em)return;let l=`${o}.cols[${r}]${typeof i.el==="string"?` (el: ${i.el})`:""}`;if(c<rs){t.push(`${l} c\xF3 "w": ${c} \u2014 v\xF9ng ch\u1EBFt 13\u201324: V2 hi\u1EC3u l\xE0 ${c}px, c\xF2n V3 sinh class "v-col-${c}" m\xE0 Vuetify 3 kh\xF4ng \u0111\u1ECBnh ngh\u0129a (ch\u1EC9 c\xF3 .v-col-1 \u2026 .v-col-12) n\xEAn c\u1ED9t KH\xD4NG c\xF3 b\u1EC1 r\u1ED9ng n\xE0o, r\u01A1i v\u1EC1 chia \u0111\u1EC1u. D\xF9ng 1\u201312 (chia c\u1ED9t) ho\u1EB7c >= ${rs} (px, k\xE8m \`"col": { "class": "shrink" }\`).`);return}let u=i.col;if(gc.test(wc(u)))return;t.push(j(u)&&"shrink"in u?`${l} c\xF3 "w": ${c} (px) k\xE8m \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn n\xF3 tr\u01A1 v\xE0 c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng. Vi\u1EBFt \`"col": { "class": "shrink" }\` \u0111\u1EC3 \u0111\xFAng \u1EDF c\u1EA3 hai framework.`:`${l} c\xF3 "w": ${c} \u2014 s\u1ED1 >= ${rs} ngh\u0129a l\xE0 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh px, nh\u01B0ng c\u1ED9t grid m\u1EB7c \u0111\u1ECBnh flex-grow:1 n\xEAn flex-basis ${c}px KH\xD4NG c\xF3 t\xE1c d\u1EE5ng: c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng (\u0111o th\u1EADt: w=300 ra 687px \u1EDF V2, 738px \u1EDF V3). Th\xEAm \`"col": { "class": "shrink" }\`.`)})},"$.controls")}function Nm(e,t,n){let o=(s)=>j(s)&&s.el==="v-spacer";if(!e.some(o))return;e.forEach((s,r)=>{if(!j(s))return;let i=s;if(typeof i.el!=="string"||!i.el)return;let a=`${t}.cols[${r}] (el: ${i.el})`,c=i.col,l=gc.test(wc(c));if(i.el==="v-spacer"){if(l)n.push(`${a} \u2014 \`v-spacer\` m\xE0 c\xF3 \`shrink\` th\xEC co l\u1EA1i c\xF2n 0px (\u0111o th\u1EADt: 24px \u1EDF V3, 0px \u1EDF V2), kh\xF4ng \u0111\u1EA9y \u0111\u01B0\u1EE3c nh\xF3m sau sang ph\u1EA3i n\u1EEFa v\xE0 c\u1EA3 h\xE0ng d\u1ED3n v\u1EC1 tr\xE1i. B\u1ECF \`shrink\` kh\u1ECFi ri\xEAng c\u1ED9t n\xE0y.`);return}if(!Tm(i)||l)return;n.push(j(c)&&"shrink"in c?`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\`, \`w\` r\u1ED7ng v\xE0 ch\u1EC9 c\xF3 \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn c\u1ED9t v\u1EABn gi\xE3n v\xE0 chi\u1EBFm m\u1ED9t ph\u1EA7n \u0111\u1EC1u. Vi\u1EBFt \`"col": "shrink"\` (ho\u1EB7c \`{ "class": "shrink" }\`).`:`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\` nh\u01B0ng thi\u1EBFu \`shrink\` \u2014 \`w\` r\u1ED7ng KH\xD4NG c\xF3 ngh\u0129a "t\u1EF1 co theo n\u1ED9i dung": runtime b\u1ECDc ph\u1EA7n b\u1EC1 r\u1ED9ng trong \`if (c.w)\` n\xEAn c\u1ED9t n\xE0y kh\xF4ng c\xF3 class grid l\u1EABn flex-basis, c\xF2n tr\u01A1 \`flex-grow: 1\` v\xE0 \u0111\u01B0\u1EE3c CHIA PH\u1EA6N TR\u1ED0NG y nh\u01B0 \`v-spacer\`. H\u1EC7 qu\u1EA3: control b\u1ECB k\xE9o d\xE3n, h\u1EDF kho\u1EA3ng gi\u1EEFa c\xE1c n\xFAt, nh\xF3m cu\u1ED1i h\xE0ng kh\xF4ng s\xE1t m\xE9p ph\u1EA3i. Chu\u1EA9n cho h\xE0ng nhi\u1EC1u control: M\u1ECCI c\u1ED9t \`"col": "shrink"\`, tr\u1EEB \u0111\xFAng \`v-spacer\`.`)})}var hc=["API","CALL","EXE","FUN","IF","MESS","MESSBOX"],_m=new Set(["IN","OUT","HEADER","METHOD","CALLBACK","ERROROUT","ERRORCALLBACK","THEN","ELSE","CONFIRM","CANCEL","ARRAY","COL","MAP"]);function Dm(e,t,n){for(let[o,s]of Object.entries(e)){if(o==="set")continue;Fe(s,(r,i)=>{let a=Object.keys(r),c=hc.filter((m)=>a.includes(m));if(c.length===0)return;let l=c[0],u=new Set([l]);if(l==="API"&&c.includes("MESS"))u.add("MESS");let d=c.filter((m)=>!u.has(m));if(d.length>0){t.push(`${i} g\u1ED9p ${c.length} l\u1EC7nh trong M\u1ED8T object \`{}\` (${c.join(", ")}) \u2014 ch\u1EC9 \`${l}\` ch\u1EA1y, c\xF2n ${d.map((m)=>`\`${m}\``).join(", ")} b\u1ECB b\u1ECF IM L\u1EB6NG (kh\xF4ng JS error, kh\xF4ng [Vue warn]). \`vueAction\` d\xF2 key l\u1EC7nh theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh API \u2192 CALL \u2192 EXE \u2192 FUN \u2192 IF \u2192 MESS \u2192 MESSBOX v\xE0 \`return\` ngay \u1EDF nh\xE1nh \u0111\u1EA7u ti\xEAn kh\u1EDBp, n\xEAn th\u1EE9 t\u1EF1 vi\u1EBFt kh\xF4ng c\xF3 t\xE1c d\u1EE5ng. Nhi\u1EC1u l\u1EC7nh c\xF3 th\u1EE9 t\u1EF1 tr\u01B0\u1EDBc sau th\xEC ph\u1EA3i l\xE0 M\u1EA2NG, m\u1ED7i l\u1EC7nh m\u1ED9t object: \`[{ "${c[0]}": \u2026 }, { "${c[1]}": \u2026 }]\`.`);return}let p=a.filter((m)=>!u.has(m)&&!_m.has(m)&&!hc.includes(m));if(p.length>0)n.push(`${i} c\xF3 l\u1EC7nh \`${l}\` n\u1EB1m chung object v\u1EDBi key g\xE1n d\u1EEF li\u1EC7u ${p.map((m)=>`\`${m}\``).join(", ")} \u2014 fragment g\xE1n ch\u1EA1y \u1EDF nh\xE1nh CU\u1ED0I c\u1EE7a \`vueAction\`, nh\xE1nh \u0111\xF3 ch\u1EC9 t\u1EDBi l\u01B0\u1EE3t khi KH\xD4NG key l\u1EC7nh n\xE0o kh\u1EDBp, n\xEAn c\xE1c key n\xE0y kh\xF4ng bao gi\u1EDD \u0111\u01B0\u1EE3c g\xE1n. T\xE1ch th\xE0nh m\u1EA3ng: \`[{ ${p.map((m)=>`"${m}": \u2026`).join(", ")} }, { "${l}": \u2026 }]\`.`)},`$.${o}`)}}function xm(e,t){let n=[],o=[];if(t.schemaWarning)o.push(t.schemaWarning);if(!j(e))return{errors:["module.json g\u1ED1c ph\u1EA3i l\xE0 m\u1ED9t object v\u1EDBi c\xE1c kh\u1ED1i data/watch/controls/set."],warnings:o,stats:ss()};let s=e,r=new Set(["data","watch","controls","set"]);for(let g of Object.keys(s))if(!r.has(g))o.push(`Kh\u1ED1i c\u1EA5p 1 kh\xF4ng chu\u1EA9n: "${g}" \u2014 module.json ch\u1EC9 c\xF3 data/watch/controls/set (xem module-json-anatomy.md).`);if(s.data!==void 0&&!Array.isArray(s.data))n.push(`"data" ph\u1EA3i l\xE0 M\u1EA2NG (array), hi\u1EC7n l\xE0 ${typeof s.data}.`);if(s.watch!==void 0&&!j(s.watch))n.push(`"watch" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.watch)?"array":typeof s.watch}.`);if(s.controls!==void 0&&!Array.isArray(s.controls))n.push(`"controls" ph\u1EA3i l\xE0 M\u1EA2NG container, hi\u1EC7n l\xE0 ${typeof s.controls}.`);if(s.set!==void 0&&!j(s.set))n.push(`"set" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.set)?"array":typeof s.set}.`);let i=new Set(mc),a=new Set,c=Array.isArray(s.data)?s.data:[];for(let g of c){if(!j(g)){o.push(`data[] ch\u1EE9a ph\u1EA7n t\u1EED kh\xF4ng ph\u1EA3i object: ${JSON.stringify(g)?.slice(0,60)}`);continue}for(let[b,R]of Object.entries(g)){if(un.has(b))continue;if(b.startsWith("#"))continue;if(is(R))a.add(b);else i.add(Ge(b))}}let l=0;Fe(s,(g,b,R)=>{if(R)return;for(let C of["OUT","ERROROUT"]){let S=g[C];if(typeof S==="string"){if(qn(S))i.add(Ge(S));else if(S.trim())l++}else if(j(S))for(let N of Object.keys(S))i.add(Ge(N))}});let u=new Map,d=new Map;Fe(s,(g,b,R)=>{let C=g.CALL;if(typeof C==="string"){if(qn(C))(R?d:u).set(Ge(C),b);else if(C.trim()&&!R)l++}let S=g.API;if(typeof S==="string"&&S.trim()&&!S.startsWith("/")&&!qn(S))l++;for(let[N,D]of Object.entries(g)){if(typeof D!=="string")continue;let A=R||N.startsWith("#")?d:u;for(let G of D.matchAll(/\bCALL\(\s*([A-Za-z_$][\w$.]*)\s*\)/g))A.set(Ge(G[1]),b)}});for(let[g,b]of u)if(!a.has(g))n.push(`CALL "${g}" (t\u1EA1i ${b}) kh\xF4ng kh\u1EDBp named action n\xE0o trong data[]. Named actions hi\u1EC7n c\xF3: ${[...a].join(", ")||"(kh\xF4ng c\xF3)"}.`);let p=0,m=0,w=Array.isArray(s.controls)?s.controls:[];for(let g=0;g<w.length;g++){let b=w[g];if(p++,!j(b)||!Array.isArray(b.rows)){n.push(`controls[${g}] ph\u1EA3i l\xE0 container c\xF3 "rows" (grid b\u1EAFt bu\u1ED9c: container > rows > cols).`);continue}for(let R=0;R<b.rows.length;R++){let C=b.rows[R];if(!j(C)||!Array.isArray(C.cols)){n.push(`controls[${g}].rows[${R}] ph\u1EA3i c\xF3 "cols" (m\u1EA3ng Control Object).`);continue}for(let S=0;S<C.cols.length;S++){let N=C.cols[S];if(m++,!j(N))continue;if(N.w===void 0&&N.el!==void 0)o.push(`controls[${g}].rows[${R}].cols[${S}] (el: ${String(N.el)}) thi\u1EBFu "w" \u2014 b\u1EAFt bu\u1ED9c cho item trong cols.`)}}}let y=new Set;if(Fe(s,(g,b)=>{if("children"in g&&(("el"in g)||("rows"in g)||("cols"in g)))n.push(`"children" kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 (t\u1EA1i ${b}) \u2014 d\xF9ng "innerHTML" (m\u1EA3ng Control Object) \u0111\u1EC3 l\u1ED3ng.`);let R=g.el;if(typeof R==="string"&&R){if(y.add(R),(R.startsWith("f-")||R.startsWith("fp-"))&&!gm.has(R)&&!t.localComponents.has(R))o.push(`Component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong registry FUI (component-quickref.md) v\xE0 kh\xF4ng th\u1EA5y trong components local \u2014 ki\u1EC3m tra ch\xEDnh t\u1EA3 ho\u1EB7c ch\u1EA1y component_list.`);else if(R.startsWith("t-")&&!pc.has(R))o.push(`Cell component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong danh s\xE1ch t-* h\u1EE3p l\u1EC7: ${[...pc].join(", ")}.`);else if(R.startsWith("uc-")&&!t.localComponents.has(R))o.push(`Custom component "${R}" (t\u1EA1i ${b}) ch\u01B0a th\u1EA5y file .vue/local metadata \u2014 t\u1EA1o component tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y component_list.`)}let C=g.attr;if(j(C))for(let[S,N]of Object.entries(C)){if(S.startsWith("@"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 shorthand "@"; d\xF9ng "v-on:${S.slice(1)}".`);else if(S.startsWith("#"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 slot shorthand "#"; d\xF9ng "v-slot:${S.slice(1)}".`);else if((S==="style"||S===":style")&&typeof N==="string"){if(N.split(";").map((A)=>A.trim()).filter(Boolean).filter((A)=>!/^(max-|min-)?width\s*:/i.test(A)).filter((A)=>!(g.el==="v-card"&&/^padding\s*:\s*0(px)?$/i.test(A))).length>0)o.push(`attr style "${N}" (t\u1EA1i ${b}) \u2014 module th\u1EF1c t\u1EBF g\u1EA7n nh\u01B0 kh\xF4ng d\xF9ng style ngo\xE0i width (controls-styling-vocabulary.md). C\xE2n nh\u1EAFc b\u1ECF.`)}else if((S.startsWith(":")||S.startsWith("v-bind:"))&&typeof N==="string"&&!ym(N))l++;else if(S==="v-model"&&typeof N==="string"){if(qn(N)){let D=Ge(N);if(!i.has(D)&&!a.has(D))o.push(`v-model "${N}" (t\u1EA1i ${b}) \u2014 bi\u1EBFn g\u1ED1c "${D}" ch\u01B0a khai b\xE1o trong data[]. Khai b\xE1o state tr\u01B0\u1EDBc \u0111\u1EC3 reactive ho\u1EA1t \u0111\u1ED9ng \u0111\xFAng.`)}else if(N.trim())l++}$m(S,N,b,n)}}),j(s.watch)){let g=(b,R)=>{for(let C of Object.keys(b)){if(C==="deep-watch"){if(j(b[C]))g(b[C],`${R}.deep-watch`);continue}let S=Ge(C);if(!i.has(S))o.push(`watch "${C}" (${R}) \u2014 bi\u1EBFn "${S}" ch\u01B0a khai b\xE1o trong data[], watcher s\u1EBD kh\xF4ng bao gi\u1EDD ch\u1EA1y.`)}};g(s.watch,"watch")}for(let g of y){let b=wm[g];if(!b)continue;let R=b.filter((C)=>!t.importsText.includes(C.toLowerCase()));if(R.length>0)n.push(`"${g}" c\u1EA7n import: ${R.join(", ")} \u2014 ch\u01B0a th\u1EA5y trong _imports.json (module l\u1EABn project). D\xF9ng file_import_list/file_import_new (xem component-quickref.md).`)}let v=0,T=new Set;if(t.schemaProcs){let g=new Set;for(let b of t.schemaProcs.values())for(let R of b)g.add(R);if(Fe(s,(b,R)=>{let C=b.API;if(typeof C!=="string"||!C||C.includes("{{"))return;v++;let S=null,N=null;if(C.startsWith("/")){let A=C.replace(/^\/+/,"").split("/").filter(Boolean);if(A.length<2)return;if(N=t.schemaProcs.get(A[0].toLowerCase())??null,!N){T.add(A[0]);return}S=A[1]==="auth"?A[2]??null:A[1]}else if(qn(C))S=C,N=g;if(!S||S.includes("`")||!N)return;if(![`spAPI_${S}`,`spAPI_AUTH_${S}`,`spAPIFILE_${S}`,`spAPIFILE_UPLOAD_${S}`,S].some((A)=>N.has(A)))o.push(`API "${C}" (t\u1EA1i ${R}) \u2014 kh\xF4ng t\xECm th\u1EA5y spAPI_${S} trong schema local. Deploy SP tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y db_schema_get (b\u1ECF qua n\u1EBFu API thu\u1ED9c backend kh\xE1c).`)}),T.size>0){let b=[...T].sort();o.push(`${b.length} alias \u0111\u01B0\u1EE3c g\u1ECDi nh\u01B0ng ch\u01B0a c\xF3 k\u1EBFt n\u1ED1i local \u2014 KH\xD4NG \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c t\xEAn SP: ${b.join(", ")}. `+`K\u1EBFt n\u1ED1i t\u1EEBng c\xE1i b\u1EB1ng db_connect({ apiName: "${b[0]}", apiDomain: "\u2026", dbToken: "\u2026" }) \u0111\u1EC3 b\u01B0\u1EDBc ki\u1EC3m n\xE0y ch\u1EA1y (b\u1ECF qua n\u1EBFu alias thu\u1ED9c backend c\u1EE7a nh\xF3m kh\xE1c).`)}}let I=Rm(s,t,o);return Pm(s,o),Am(s,o),Dm(s,n,o),{errors:n,warnings:o,stats:{stateKeys:i.size-mc.size,namedActions:a.size,containers:p,controls:m,callRefs:u.size,apiRefs:v,crossWindowCalls:d.size,dynamicRefs:l,rightRefs:I}}}function ss(){return{stateKeys:0,namedActions:0,containers:0,controls:0,callRefs:0,apiRefs:0,crossWindowCalls:0,dynamicRefs:0,rightRefs:0}}function Om(e){return e.match(/^CREATE (?:PROCEDURE|FUNCTION)\s+(\S+)\s*\(/i)?.[1]??null}async function Mm(e){let t=st.dirname(st.dirname(e)),n=new Set;for(let c of[st.join(e,"components"),st.join(t,"components")]){for(let u of await Me(c))n.add(u.replace(/\.vue$/i,""));let l=await U(st.join(c,"_components.json"),[]);for(let u of l)if(u?.comName)n.add(u.comName)}let o=[];for(let c of[e,t])o.push(await F(st.join(c,"imports","_imports.json")));let s=await U(st.join(t,"_db","_connections.json"),null),r=new Map,i=[];for(let c of Object.keys(s?.connections??{})){let l=await U(st.join(t,"_db",c,"schema.json"),null),u=Array.isArray(l?.procedures)?l.procedures:null;if(u===null)continue;if(!u.every((d)=>typeof d==="string")){i.push(c);continue}r.set(c.toLowerCase(),new Set(u.map(Om).filter((d)=>d!==null)))}let a=await Ot(t);return{localComponents:n,importsText:o.join(`
154
- `).toLowerCase(),schemaProcs:r.size>0?r:null,schemaWarning:i.length>0?`_db/${i.join("/schema.json, _db/")}/schema.json sai \u0111\u1ECBnh d\u1EA1ng (b\u1EA3n cache c\u0169) \u2014 b\u1ECF qua b\u01B0\u1EDBc \u0111\u1ED1i chi\u1EBFu t\xEAn API v\u1EDBi stored procedure \u1EDF ${i.length>1?"c\xE1c k\u1EBFt n\u1ED1i n\xE0y":"k\u1EBFt n\u1ED1i n\xE0y"}. Ch\u1EA1y db_schema_get \u0111\u1EC3 l\u1EA5y b\u1EA3n m\u1EDBi.`:null,definedFunctionRights:a.functionRights,definedSystemRights:a.systemRights}}async function Ro(e){let t=await F(st.join(e,"module.json"));if(!t.trim())return{errors:[`module.json r\u1ED7ng ho\u1EB7c kh\xF4ng t\u1ED3n t\u1EA1i trong ${e}.`],warnings:[],stats:ss()};let n;try{n=JSON.parse(t)}catch(s){return{errors:[`module.json c\xF3 l\u1ED7i JSON syntax: ${s.message}`],warnings:[],stats:ss()}}let o=await Mm(e);return xm(n,o)}function Po(e){let t=[];if(e.errors.length===0&&e.warnings.length===0)t.push("\u2705 module.json h\u1EE3p l\u1EC7 \u2014 kh\xF4ng ph\xE1t hi\u1EC7n l\u1ED7i ng\u1EEF ngh\u0129a.");else{if(e.errors.length>0)t.push(`\u274C ${e.errors.length} l\u1ED7i (s\u1EBD h\u1ECFng khi ch\u1EA1y):`),t.push(...e.errors.map((o)=>` - ${o}`));if(e.warnings.length>0){if(t.length)t.push("");t.push(`\u26A0\uFE0F ${e.warnings.length} c\u1EA3nh b\xE1o (n\xEAn xem l\u1EA1i):`),t.push(...e.warnings.map((o)=>` - ${o}`))}}let n=e.stats;if(t.push(""),n.dynamicRefs>0)t.push(`\u2139\uFE0F ${n.dynamicRefs} bi\u1EC3u th\u1EE9c \u0111\u1ED9ng (\`:attr\` ph\u1EE9c t\u1EA1p, CALL/API/v-model/OUT d\u1EA1ng \`{{\u2026}}\`/g\u1ECDi h\xE0m/template string) KH\xD4NG ki\u1EC3m t\u0129nh \u0111\u01B0\u1EE3c \u2014 bi\u1EBFn b\xEAn trong ch\xFAng kh\xF4ng \u0111\u01B0\u1EE3c \u0111\u1ED1i chi\u1EBFu v\u1EDBi data[]. "H\u1EE3p l\u1EC7" \u1EDF \u0111\xE2y KH\xD4NG c\xF3 ngh\u0129a l\xE0 ch\u1EA1y \u0111\xFAng: d\xF9ng module_simulate \u0111\u1EC3 ph\u1EE7 ph\u1EA7n n\xE0y.`);return t.push(`Th\u1ED1ng k\xEA: ${n.stateKeys} state \xB7 ${n.namedActions} named action \xB7 ${n.containers} container / ${n.controls} control \xB7 ${n.callRefs} CALL ref${n.apiRefs?` \xB7 ${n.apiRefs} API ref \u0111\u1ED1i chi\u1EBFu schema`:""}${n.crossWindowCalls?` \xB7 ${n.crossWindowCalls} CALL xuy\xEAn c\u1EEDa s\u1ED5 (thu\u1ED9c module kh\xE1c, kh\xF4ng ki\u1EC3m \u0111\u01B0\u1EE3c t\u1EEB \u0111\xE2y)`:""}${n.rightRefs?` \xB7 ${n.rightRefs} m\xE3 quy\u1EC1n \u0111\u1ED1i chi\u1EBFu _db/rights.json`:""}`),t.join(`
155
- `)}function ee(e,t){let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t)+"\u2026":n}function Jn(e){if(e===null)return"null";if(Array.isArray(e))return e.length===0?"[]":`[${e.length} items]`;if(j(e)){let t=Object.keys(e);return t.length===0?"{}":`{${ee(t.join(","),40)}}`}if(typeof e==="string")return`"${ee(e,30)}"`;return String(e)}function _e(e,t=0){if(t>3)return"\u2026";if(typeof e==="string")return ee(e,70);if(Array.isArray(e))return e.map((o)=>_e(o,t+1)).join(" ; ");if(!j(e))return ee(JSON.stringify(e)??"",60);let n=[];if(e.CONFIRM!==void 0)n.push(`CONFIRM "${ee(String(e.CONFIRM),40)}"`);if(e.IF!==void 0)n.push(`IF(${ee(String(e.IF),50)})`);if(e.API!==void 0)n.push(`API ${ee(String(e.API),60)}`);if(e.METHOD!==void 0)n.push(`[${String(e.METHOD)}]`);if(e.IN!==void 0)n.push(`IN{${j(e.IN)?ee(Object.keys(e.IN).join(","),50):ee(String(e.IN),40)}}`);if(e.OUT!==void 0)n.push(`OUT\u2192${j(e.OUT)?Object.keys(e.OUT).join(","):String(e.OUT)}`);if(e.ARRAY!==void 0)n.push(`ARRAY ${ee(String(e.ARRAY),30)}${e.COL!==void 0?` COL ${ee(String(e.COL),30)}`:""}`);if(e.CALL!==void 0)n.push(`CALL ${ee(String(e.CALL),40)}`);if(e.FUN!==void 0)n.push(`FUN ${ee(String(e.FUN),40)}`);if(e.EXE!==void 0)n.push(`EXE ${ee(String(e.EXE),70)}`);if(e.MESS!==void 0)n.push(`MESS "${ee(String(e.MESS),40)}"`);if(e.MESSBOX!==void 0)n.push(`MESSBOX "${ee(String(e.MESSBOX),40)}"`);if(e.THEN!==void 0)n.push(`THEN[${_e(e.THEN,t+1)}]`);if(e.ELSE!==void 0)n.push(`ELSE[${_e(e.ELSE,t+1)}]`);if(e.CALLBACK!==void 0)n.push(`CALLBACK[${_e(e.CALLBACK,t+1)}]`);if(e.ERROROUT!==void 0)n.push(`ERROROUT\u2192${String(e.ERROROUT)}`);if(e.ERRORCALLBACK!==void 0)n.push(`ERRORCALLBACK[${_e(e.ERRORCALLBACK,t+1)}]`);if(e.CANCEL!==void 0)n.push(`CANCEL[${_e(e.CANCEL,t+1)}]`);return n.join(" ")||ee(JSON.stringify(e)??"",60)}var Fm=["v-model","v-if","v-show","v-for","label",":label","placeholder",":items",":data",":headers",":config",":option",":options","value",":value","readonly","disabled",":disabled","multiple","type"];function Lm(e){let t=[],n=e.w!==void 0?`w=${String(e.w)}`:"w=?",o=typeof e.el==="string"&&e.el?e.el:e.rows?"(container)":"(text)";t.push(`${n} ${o}`);let s=j(e.attr)?e.attr:{};for(let r of Fm){let i=s[r];if(i===void 0)continue;t.push(`${r}=${typeof i==="string"?ee(i,45):Jn(i)}`)}for(let[r,i]of Object.entries(s)){if(!r.startsWith("v-on:"))continue;let a=r.slice(5);t.push(`${a}\u2192${typeof i==="string"?ee(i,60):_e(i)}`)}for(let r of["innerText","html","text"])if(typeof e[r]==="string"&&e[r])t.push(`"${ee(String(e[r]),35)}"`);return t.join(" ")}function yc(e,t,n,o){let s=o?"\u2514\u2500":"\u251C\u2500",r=t+(o?" ":"\u2502 ");if(!j(e)){n.push(`${t}${s} ${ee(JSON.stringify(e)??String(e),60)}`);return}n.push(`${t}${s} ${Lm(e)}`);let i=e.innerHTML;if(Array.isArray(i))i.forEach((a,c)=>yc(a,r,n,c===i.length-1));if(Array.isArray(e.rows))bc(e.rows,r,n)}function bc(e,t,n){e.forEach((o,s)=>{if(!j(o)||!Array.isArray(o.cols)){n.push(`${t}row ${s+1}: \u274C thi\u1EBFu "cols"`);return}let r=o.cols,a=r.map((u)=>j(u)?u.w:void 0).filter((u)=>typeof u==="number"),c="";if(r.length>1&&a.length===r.length){let u=a.reduce((d,p)=>d+p,0);c=` (\u03A3w=${u}${u>12?" \u26A0 v\u01B0\u1EE3t 12":""})`}let l=[];for(let u of["v-if","v-show","prop"])if(typeof o[u]==="string")l.push(`${u}=${ee(String(o[u]),40)}`);n.push(`${t}row ${s+1}${l.length?` [${l.join(" ")}]`:""}${c}`),r.forEach((u,d)=>yc(u,t+" ",n,d===r.length-1))})}function kc(e){if(!j(e))return"\u274C module.json g\u1ED1c kh\xF4ng ph\u1EA3i object.";let t=e,n=[],o=new Map,s=new Map,r=[],i=Array.isArray(t.data)?t.data:[];for(let d of i){if(!j(d))continue;let p=Object.keys(d);if(p.length>0&&p.every((m)=>un.has(m))){r.push(d);continue}for(let[m,w]of Object.entries(d)){if(un.has(m)){r.push({[m]:w});continue}if(m.startsWith("#"))continue;if(is(w))s.set(m,w);else o.set(Ge(m),w)}}let a=JSON.stringify({c:t.controls,w:t.watch,s:t.set,a:[...s.values()],u:r})??"",c=(d)=>new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(a);n.push("\u2550\u2550\u2550 LAYOUT (controls) \u2550\u2550\u2550");let l=Array.isArray(t.controls)?t.controls:[];if(l.length===0)n.push("(kh\xF4ng c\xF3 controls)");let u=[];if(l.forEach((d,p)=>{if(!j(d)){n.push(`[container ${p+1}] \u274C kh\xF4ng ph\u1EA3i object`);return}let m=typeof d.el==="string"?d.el:"",w=m==="hidden-container",y=j(d.attr)?d.attr:{},v=[];if(m)v.push(`el=${m}`);if(typeof d.prop==="string")v.push(`prop="${ee(d.prop,40)}"`);for(let T of["v-if","v-show","v-model"]){let I=d[T]??y[T];if(typeof I==="string")v.push(`${T}=${ee(I,40)}`)}if(n.push(`[container ${p+1}${w?" \u2014 DIALOG":""}] ${v.join(" ")||"(m\u1EB7c \u0111\u1ECBnh)"}`),w){let T=d["v-model"]??y["v-model"];if(typeof T==="string")u.push({vModel:Ge(T),where:`container ${p+1}`});else u.push({vModel:"",where:`container ${p+1}`})}if(Array.isArray(d.rows))bc(d.rows," ",n);else n.push(' \u274C thi\u1EBFu "rows"')}),n.push("","\u2550\u2550\u2550 STATE (data) \u2550\u2550\u2550"),o.size===0)n.push("(kh\xF4ng c\xF3 state)");for(let[d,p]of o){let m=c(d);n.push(`${d} = ${Jn(p)}${m?"":" \u26A0 kh\xF4ng th\u1EA5y d\xF9ng \u1EDF controls/watch/actions"}`)}if(n.push("","\u2550\u2550\u2550 ACTIONS \u2550\u2550\u2550"),s.size===0&&r.length===0)n.push("(kh\xF4ng c\xF3 action)");for(let[d,p]of s){let m=JSON.stringify({c:t.controls,w:t.watch,s:t.set,u:r,a:[...s.entries()].filter(([y])=>y!==d).map(([,y])=>y)})??"",w=new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(m);n.push(`${d}: ${_e(p)}${w?"":" \u26A0 kh\xF4ng th\u1EA5y n\u01A1i g\u1ECDi (CALL/watch/v-on)"}`)}if(r.forEach((d,p)=>{n.push(`(auto-startup ${p+1}): ${_e(d)}`)}),j(t.watch)&&Object.keys(t.watch).length>0){n.push("","\u2550\u2550\u2550 WATCH \u2550\u2550\u2550");let d=(p,m)=>{for(let[w,y]of Object.entries(p)){if(w==="deep-watch"&&j(y)){d(y,"deep ");continue}n.push(`${m}${w} \u2192 ${_e(y)}`)}};d(t.watch,"")}if(u.length>0){n.push("","\u2550\u2550\u2550 DIALOGS \u2550\u2550\u2550");let d=JSON.stringify(t)??"";for(let p of u){if(!p.vModel){n.push(`${p.where}: \u26A0 hidden-container kh\xF4ng c\xF3 v-model \u2014 kh\xF4ng \u0111i\u1EC1u khi\u1EC3n m\u1EDF/\u0111\xF3ng \u0111\u01B0\u1EE3c`);continue}let m=p.vModel.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),w=new RegExp(`\\b${m}\\b\\s*=\\s*true`).test(d);n.push(`${p.where}: v-model=${p.vModel}${w?` \u2014 c\xF3 l\u1EC7nh m\u1EDF (${p.vModel} = true)`:` \u26A0 kh\xF4ng th\u1EA5y l\u1EC7nh m\u1EDF "${p.vModel} = true" \u1EDF \u0111\xE2u`}`)}}if(j(t.set)&&Object.keys(t.set).length>0){n.push("","\u2550\u2550\u2550 SET (override) \u2550\u2550\u2550");for(let[d,p]of Object.entries(t.set))n.push(`${d} = ${typeof p==="string"?ee(p,60):Jn(p)}`)}return n.push("",`Th\u1ED1ng k\xEA: ${o.size} state \xB7 ${s.size} named action \xB7 ${r.length} auto-startup \xB7 ${l.length} container \xB7 ${u.length} dialog`),n.join(`
156
- `)}async function pn(e){let t=gr(await Tn(e));return t?[t]:[]}async function $c(e,t){let n=await Ro(e);if(n.errors.length===0&&n.warnings.length===0)return[];let o=Po(n);if(n.errors.length>0&&qt())throw new $e(`[FUI_MCP_STRICT] ${t} blocked: module.json has semantic errors; fix them and retry
157
-
158
- ${o}`,"hard","error");return["--- module validate ---",o]}async function Ft(e){let t=await U(it(e,"_moduleInfo.json"),null),n=t?.ProjectID&&t?.ModuleID?`fui module get -p ${t.ProjectID} -m ${t.ModuleID}`:"fui module get";return Pt(e,"local module",n,"_moduleInfo.json")}async function vc(e){let t=await Ft(e),n=await Ro(e);return{notes:[...t?[t]:[],...await pn(e)],data:`${Po(n)}
159
- `,errorCount:n.errors.length}}async function Sc(e,t){let o=await(await ce(e)).previewStagedModuleDir(t),s=await e.session.createApproval({projectId:o.moduleInfo.ProjectID,moduleId:o.moduleInfo.ModuleID,moduleDir:t,contentHash:await So(t)}),r=await Ft(t),i=await Ro(t);return{token:s.token,notes:[...r?[r]:[],`preview of ${o.moduleInfo.ProjectID}/${o.moduleInfo.ModuleID} (${t})`,`remote: ${o.remoteSummary.join("; ")}`,"changes that would be pushed:",...o.changes.length===0?[" none"]:o.changes.map((a)=>` ${a}`),"--- module validate ---",Po(i),`approval token: ${s.token}`,"ask the user; only after explicit approval run: fui module publish html --approval <token>"]}}async function jc(e,t,n){let o=await xt(e.session,"module publish html"),s=(c)=>new $e(c,"hard","error");if(!n)throw s("missing --approval: run fui module preview, show it to the user, and publish only after explicit approval");let r=await e.session.getApproval();if(!r)throw s("no pending publish approval; run fui module preview first");if(r.token!==n)throw s("invalid approval token; run fui module preview again and use the latest token after approval");if(r.moduleDir!==t)throw s(`approval token is for ${r.moduleDir}, not ${t}`);if(await So(t)!==r.contentHash)throw s("files changed since the preview (module.json, header.html, body.html, script.js, style.css or components/*.vue); run fui module preview again and get a fresh approval");let i=await $c(t,"module publish html"),a=await(await ce(e)).publishStagedModuleDir(t);return await e.session.clearApproval(),{notes:[...o,`ok published ${a.ProjectID}/${a.ModuleID} (${t})`,...i,...await pn(t)]}}async function To(e,t){let n=await xt(e.session,"module publish json"),o=await F(it(t.moduleDir,"module.json"));if(!o.trim())throw new f("local module.json is empty or missing");try{JSON.parse(o)}catch(r){throw new f(`module.json has invalid JSON syntax: ${r.message}`)}if(await zt(t.moduleDir,"module.json",o))return{notes:[...n,`skip module.json unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let s=await $c(t.moduleDir,"module publish json");return await(await e.api()).updateModuleUI(t.projectId,t.moduleId,o),await Te(t.moduleDir,["module.json"]).catch(()=>{return}),{notes:[...n,`ok published module.json to ${t.projectId}/${t.moduleId}`,...s,...await pn(t.moduleDir)]}}async function Ao(e,t,n){let o=`module publish ${n==="script.js"?"script":"css"}`,s=await xt(e.session,o),r=await Wr(it(t.moduleDir,n),n);if(await zt(t.moduleDir,n,r))return{notes:[...s,`skip ${n} unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let i=await e.api();if(n==="script.js")await i.updateModuleScript(t.projectId,t.moduleId,r);else await i.updateModuleCSS(t.projectId,t.moduleId,r);return await Te(t.moduleDir,[n]).catch(()=>{return}),{notes:[...s,`ok published ${n} to ${t.projectId}/${t.moduleId}`,...await pn(t.moduleDir)]}}function dn(e){return e.moduleId?`module (${e.moduleId})`:"project (global, loaded in all modules)"}async function Ic(e,t){let n=await F(it(e.baseDir,"components",`${t}.vue`));if(!n.trim())throw new f(`${t}.vue not found in ${e.baseDir}/components/${e.moduleId?"":"; for module scope pass -m <module>"}`);return n}async function Eo(e){return U(it(e,"components","_components.json"),[])}async function Cc(e,t){let{count:n,removed:o}=await(await ce(e)).syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`${n} component(s) in ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`}; _components.json refreshed`,...o.length>0?[`removed orphaned local files: ${o.join(", ")}`]:[]]}}async function No(e,t,n){let o=await xt(e.session,"component new"),s=await Ic(t,n),r=await ce(e);await r.syncComponentsLocal(t.projectId,t.moduleId);let i=(await Eo(t.baseDir)).find((m)=>m.comName===n);if(i)throw new f(`component "${n}" already exists in ${dn(t)} (ComID ${i.comID}); overwrite it: fui component update ${n}, or pick another name`);let{template:a,script:c,style:l}=wt(s);await r.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:u,removed:d}=await r.syncComponentsLocal(t.projectId,t.moduleId),p=(await Eo(t.baseDir)).find((m)=>m.comName===n);return await Te(t.baseDir,[ke(it("components",`${n}.vue`))]).catch(()=>{return}),{notes:[...o,`ok created component ${n} in ${dn(t)} (ComID ${p?.comID??"not visible yet; run fui component list"})`,`${u} component(s) synced${d.length>0?`; removed orphaned: ${d.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function _o(e,t,n){let o=await xt(e.session,"component update"),s=await Ic(t,n),r=(await Eo(t.baseDir)).find((m)=>m.comName===n)?.comID;if(r===void 0)throw new f(`component "${n}" is not in _components.json of ${dn(t)}, so there is no ComID to overwrite. new component: fui component new ${n}; stale cache: fui component list, then retry`);let i=ke(it("components",`${n}.vue`));if(await zt(t.baseDir,i,s))return{notes:[...o,`skip ${n}.vue unchanged since the last publish/sync`]};let{template:a,script:c,style:l}=wt(s),u=await ce(e);await u.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComID:r,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:d,removed:p}=await u.syncComponentsLocal(t.projectId,t.moduleId);return await Te(t.baseDir,[i]).catch(()=>{return}),{notes:[...o,`ok updated component ${n} in ${dn(t)} (ComID ${r})`,`${d} component(s) synced${p.length>0?`; removed orphaned: ${p.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function Rc(e,t,n){let o=(await Eo(t.baseDir)).find((a)=>a.comName===n);if(!o)throw new f(`component "${n}" not in the local _components.json of ${dn(t)}; run fui component list first`);let s=await ce(e);await s.api.updateComponent({ProjectID:t.projectId,...t.moduleId?{ModuleID:t.moduleId}:{},ComID:o.comID,ComName:"",ComTemplate:"",ComScript:""});let{count:r,removed:i}=await s.syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`ok deleted component ${n} from ${dn(t)}; ${r} remain`,...i.length>0?[`local files removed (archived): ${i.join(", ")}`]:[]]}}async function Pc(e,t){let{id:n,...o}=t,s=await ce(e),r=await s.api.editProject(o),i=await s.projectInfoRefreshNote(Tc(o,r));return{notes:["ok project created",...n!==void 0?[`warning: projectData had id="${String(n)}"; removed to create a new project (use fui project update to edit)`]:[],...i?[i]:[]],data:`${JSON.stringify(r)}
160
- `}}async function Ec(e,t){let n=t.id;if(n===void 0||n===null||n==="")throw new f("projectData.id is required to update (set it to the ProjectID); to create a project use fui project new");let o=await ce(e),s=await o.api.editProject(t),r=await o.projectInfoRefreshNote(Tc(t,s));return{notes:["ok project updated",...r?[r]:[]],data:`${JSON.stringify(s)}
161
- `}}function Tc(e,t){let n=_t(e)?e.id??e.ProjectID:void 0;if(typeof n==="string"&&n.trim())return n;let o=_t(t)?t.data:void 0,s=Array.isArray(o)?o[0]:o,r=_t(s)?s.ProjectID??s.id:void 0;return typeof r==="string"&&r.trim()?r:null}async function Do(e,t){let n=await xt(e.session,"project publish-config"),o=e.paths.projectDir(t),s=await F(it(o,"project.json"));if(!s.trim())throw new f(`cannot read ${t}/project.json (missing or empty); run fui project sync -p ${t}, edit it, then publish`);let r;try{r=JSON.parse(s)}catch(l){throw new f(`${t}/project.json has invalid JSON: ${l.message}; nothing published`)}let{config:i,wasEnveloped:a}=Xr(r);if(await zt(o,"project.json",s))return{notes:[...n,`skip ${t}/project.json unchanged since the last publish/sync`]};let c=await(await e.api()).updateProjectData(t,i);return await Te(o,["project.json"]).catch(()=>{return}),{notes:[...n,`ok published ${t}/project.json (${Object.keys(i).length} keys)`,...a?["the file is an API envelope { data: {...} }; the data layer was sent, the file is unchanged"]:[]],data:`${JSON.stringify(c)}
162
- `}}async function Um(e,t){let n=await F(it(e,"module.json"));if(!n.trim())throw new f(`module.json is empty or missing in ${e}`);try{return JSON.parse(n)}catch(o){throw new f(`module.json has a JSON syntax error: ${o.message}; fix it, ${t} needs valid JSON`)}}async function Ac(e){let t=await Um(e,"outline"),n=await Ft(e);return{notes:n?[n]:[],data:`Outline: ${e}
163
-
164
- ${kc(t)}
165
- `}}import{basename as Df}from"path";import{join as Bm}from"path";var Lt=8,xo=5000;function Hm(e){let t=e.split(/[?#]/)[0],n=t.split("/").filter(Boolean);return(n.length?n[n.length-1]:t).toLowerCase()}function _c(e,t,n){let o=new Map;for(let r of t){o.set(r.toLowerCase(),r);let i=Tt(r);if(i)o.set(i.fnName.toLowerCase(),r)}let s=[];for(let r of e){let i=o.get(Hm(r));if(i&&!n.has(i.toLowerCase()))s.push({mockKey:r,spName:i})}return s}function Vn(e){let t=e.match(/^\/([A-Za-z0-9_-]+)\/(auth\/)?([A-Za-z0-9_]+)/);if(!t)return null;return{alias:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}async function as(e){try{return JSON.parse(await H(e))}catch{return null}}function Nc(e){if(!Ct(e))return[];let t=[];for(let n of e.procedures){let o=/^CREATE\s+(?:PROCEDURE|FUNCTION)\s+(\S+)/i.exec(n);if(o)t.push(o[1])}return t}function mn(e,t){let n=e.paths,o=(r,i,a)=>Bm(n.projectDir(r),"_testdata",i,`${a}.json`),s=async(r,i,a)=>{let c=await pe(n.spFile(r,i,a));return c!==null&&c.trim()?c:null};return{async loadAllSchemaProcNames(r){let i=new Set;for(let a of Object.keys((await e.load(r)).connections))for(let c of Nc(await as(n.schemaFile(r,a))))i.add(c);return[...i]},getVerifiedSpNames:()=>t.getVerifiedSpNames(),async getConnectionAlias(r,i){return me(await e.load(r),i)},async resolveApiSpName(r,i,a){let c=await as(n.schemaFile(r,i));if(!Ct(c))return null;let l=Nc(c),u=[a,`spAPI_${a}`,`spAPI_AUTH_${a}`,`spAPIFILE_${a}`,`spAPIFILE_UPLOAD_${a}`];for(let d of u){let p=l.find((m)=>m.toLowerCase()===d.toLowerCase());if(p)return p}return null},async classifySpSafety(r,i,a){return Wi(await s(r,i,a))},async callLiveApiSp(r,i,a,c,l="GET",u,d=xo){try{let{config:p}=await e.resolve({project:r,db:i}),m=`${Ee(p.apiDomain)}/${i}/${a?"auth/":""}${c}`,w={"content-type":"application/json"};if(!a&&p.userToken)w.authorization=`Bearer ${p.userToken}`;let y=await fetch(m,{method:l,headers:w,body:u!==void 0?JSON.stringify(u):void 0,signal:AbortSignal.timeout(d)}),v=await y.text(),T;try{T=JSON.parse(v)}catch{return{ok:!1,status:y.status,error:"Ph\u1EA3n h\u1ED3i kh\xF4ng ph\u1EA3i JSON"}}if(!y.ok)return{ok:!1,status:y.status,data:T,error:`HTTP ${y.status}`};return{ok:!0,status:y.status,data:T}}catch(p){return{ok:!1,error:p instanceof Error?p.message:String(p)}}},async readTestData(r,i,a){return await as(o(r,i,a))},async writeTestData(r,i,a,c,l){try{let u={source:c,savedAt:new Date().toISOString(),data:l};await Bun.write(o(r,i,a),JSON.stringify(u,null,2))}catch{}},async generateMockForLocalSp(r,i,a){let c=await s(r,i,a);return c===null?null:Hi(c)}}}import{promises as ze}from"fs";import ue from"path";import{homedir as Wm}from"os";import{join as ls}from"path";var cs=null;function us(e){cs={...e,live:mn(e.store,e.session)}}function He(){if(!cs){let e=Bun.env.FUI_MCP_WORKDIR||ls(Wm(),Go),t=new jn;us({store:new nt(new Rn(e)),session:t,api:so,activeTarget:()=>t.getTarget()})}return cs}var fn=()=>He().store.paths.root,hn=(e)=>ls(He().store.paths.root,tt(e,"projectId")),ds=(e,t)=>ls(hn(e),"modules",tt(t,"moduleId")),Dc=()=>He().activeTarget();async function xc(e){return await(await He().api()).listFrameworkImportFiles(e)}async function Oc(e,t){let n=await He().store.resolve({project:e,db:t.db});return{alias:n.alias,config:{...n.config,apiName:n.alias},note:n.note}}var Mc=(...e)=>He().live.getConnectionAlias(...e),Fc=(...e)=>He().live.resolveApiSpName(...e),Lc=(...e)=>He().live.classifySpSafety(...e),Uc=(...e)=>He().live.readTestData(...e),ps=(...e)=>He().live.writeTestData(...e),Bc=(...e)=>He().live.generateMockForLocalSp(...e);import{promises as at}from"fs";import{createHash as rl}from"crypto";import K from"path";import{promises as ms}from"fs";import Jc from"path";var qm=86400000,Hc={V2:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/include/vuetify.min.css","/include/toast/vue-toast-notification.css","/include/jquery-confirm.min.css","/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/moment/moment-vi.min.js","/include/numeral.min.js","/include/vue@2.js","/include/vuetify.min.js","/include/toast/vue-toast-notification.js","/defaultfunction.js","/componentTable.js","/component.js","/fastproject.js"]},V3:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/Framework/V3/vuetify@3.7.min.css","/Framework/V3/vuetify-labs@3.7.min.css","/Framework/V3/vue-toast-notification@3.1.3.theme-bootstrap.css","/include/jquery-confirm.min.css","/Framework/V3/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/numeral.min.js","/Framework/V3/dayjs-vi.min.js","/Framework/V3/vue.global.min.3.5.js","/Framework/V3/vuetify@3.7.min.js","/Framework/V3/vuetify-labs@3.7.min.js","/Framework/V3/vue-toast-notification@3.1.3.js","/Framework/V3/defaultfunction-3.0.js","/Framework/V3/fastproject-3.0.js","/Framework/V3/component-3.0.js","/Framework/V3/componentTable-3.0.js"]}};function Jm(e){return Jc.join(fn(),"_cache","framework",`${e}.json`)}function Vm(e,t){let n=Hc[e]??Hc.V3,o=(s,r)=>s.map((i,a)=>({fileID:"",ProjectID:"",ModuleID:"",FileType:r,ContentType:"link",EditContent:0,FileName:/^https?:\/\//i.test(i)?i:`${t}${i}`,FileSort:a,CreateTime:"",CreateUser:"",UpdateTime:null,UpdateUser:null,Scope:"framework"}));return[...o(n.styles,".css"),...o(n.scripts,".js")]}async function Vc(e,t){let n=Jm(e),o;try{o=JSON.parse(await ms.readFile(n,"utf-8"))}catch{}let s=o?.fetchedAt?Date.now()-Date.parse(o.fetchedAt):Number.POSITIVE_INFINITY;if(Array.isArray(o?.data)&&o.data.length>0&&s<qm)return{deps:o.data,source:`cache (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`};try{let r=await xc(e);if(r.length===0)throw Error("API tr\u1EA3 danh m\u1EE5c r\u1ED7ng");try{await ms.mkdir(Jc.dirname(n),{recursive:!0}),await ms.writeFile(n,JSON.stringify({fetchedAt:new Date().toISOString(),data:r},null,2),"utf-8")}catch{}return{deps:r,source:`API /fp/FUI_Dev_File/select {frameWork:"${e}"} (${r.length} file)`}}catch(r){if(Array.isArray(o?.data)&&o.data.length>0)return{deps:o.data,source:`cache C\u0168 (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) \u2014 d\xF9ng cache c\u0169 tr\xEAn \u0111\u0129a.`};let i=Vm(e,t);return{deps:i,source:`danh s\xE1ch d\u1EF1 ph\xF2ng hardcode (${i.length} file)`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) v\xE0 ch\u01B0a c\xF3 cache \u2014 `+"d\xF9ng danh s\xE1ch hardcode trong renderAssets.ts. Danh s\xE1ch n\xE0y c\xF3 th\u1EC3 \u0111\xE3 l\u1EC7ch v\u1EDBi server."}}}function Km(e){if(e===".css")return"css";if(e===".js")return"js";return}function Kc(e){return[...e].sort((t,n)=>t.sort-n.sort||(t.name<n.name?-1:t.name>n.name?1:0))}function Gc(e,t){let n=Km(e.FileType);if(!n)return;let o=(e.FileName??"").trim();if(!o)return;return{label:`[framework] ${o}`,scope:"framework",kind:n,sort:typeof e.FileSort==="number"?e.FileSort:0,name:o,fileID:e.fileID,contentType:e.ContentType,domain:t.replace(/^https?:\/\//,""),fixHint:"file_import_list"}}var Wc="render";async function qc(e){try{let t=await fetch(e,{headers:{Accept:"*/*"}});if(!t.ok)return{ok:!1,reason:`HTTP ${t.status} ${t.statusText}`};return{ok:!0,text:await t.text()}}catch(t){return{ok:!1,reason:t.message}}}async function zc(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/+$/,""),o=`https://${n}/_aset/fp/auth/projectdata/${e}@${n.replace(/\//g,"")}/${Wc}.js`,s=`https://${n}/_aset/fp/auth/StyleCSS/${e}/${Wc}.css`,r=[],i=[],[a,c]=await Promise.all([qc(o),qc(s)]),l;if(a.ok)if(/\$projectData\s*=/.test(a.text))l=a.text,r.push(`projectdata.js \u2190 ${o} (${a.text.length} bytes)`);else i.push(`projectdata.js t\u1EA3i v\u1EC1 t\u1EEB ${o} kh\xF4ng ch\u1EE9a "$projectData =" (${a.text.length} bytes) \u2014 `+"kh\xF4ng d\xF9ng, quay v\u1EC1 d\u1EF1ng $projectData + component project t\u1EEB workspace local.");else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${o}: ${a.reason} \u2014 quay v\u1EC1 d\u1EF1ng t\u1EEB workspace local.`);let u;if(c.ok)if(/<html|<!DOCTYPE/i.test(c.text.slice(0,200)))i.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 t\u1EEB ${s} l\xE0 HTML ch\u1EE9 kh\xF4ng ph\u1EA3i CSS \u2014 b\u1ECF qua.`);else if(c.text.trim())u=c.text,r.push(`StyleCSS project \u2190 ${s} (${c.text.length} bytes)`);else r.push(`StyleCSS project r\u1ED7ng (${s}) \u2014 project ch\u01B0a c\xF3 component n\xE0o khai style.`);else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${s}: ${c.reason} \u2014 CSS component c\u1EA5p project s\u1EBD d\u1EF1ng t\u1EEB local.`);return{js:l,css:u,jsUrl:o,cssUrl:s,notes:r,problems:i}}function fs(e,t){let n=[],o=new RegExp(`<${t}(\\s[^>]*)?>`,"gi"),s=new RegExp(`</${t}>`,"gi"),r=0;while(r<e.length){o.lastIndex=r;let i=o.exec(e);if(!i)break;let a=1,c=o.lastIndex;while(a>0){s.lastIndex=c;let l=s.exec(e);if(!l){r=e.length;break}o.lastIndex=c;let u=o.exec(e);if(u&&u.index<l.index){a+=1,c=o.lastIndex;continue}if(a-=1,c=s.lastIndex,a===0)n.push(e.slice(i.index+i[0].length,l.index)),r=s.lastIndex}}return n}function Gm(e){return e.replace(/[\r\n\t]+/g," ").replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function zm(e){let t=/export\s+default\s*\{([\s\S]*)\}\s*;?\s*$/.exec(e.trim());return t?t[1].trim():void 0}function Xc(e,t){let n=fs(t,"template")[0]??"",o=fs(t,"script")[0]??"",s=fs(t,"style").map((c)=>c.trim()).filter(Boolean).join(`
166
- `);if(!n&&!o&&t.includes("Vue.component("))return{compiled:{name:e,script:t,css:s}};let r=zm(o),i=Gm(n);if(r!==void 0)return{compiled:{name:e,script:`Vue.component('${e}',{template:\`${i}\`,${r}})`,css:s}};let a=/module\.exports\s*=/.test(o)?o.replace(/module\.exports\s*=/,"return "):o.trim()?o:"return {};";return{compiled:{name:e,script:`;(() => {
152
+ `),e.data)k(e.data)}async function ss(e){return(await e.targets.resolveProject(f(e.values,"project"))).projectId}async function cc(e){return e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module"))}function fm(e,t){if(e===void 0)return;if(e==="true")return!0;if(e==="false")return!1;throw new h(`${t} takes true or false`)}function lc(e){if(e===void 0)return;let t=e.toUpperCase();if(t!=="V2"&&t!=="V3")throw new h("--framework must be V2 or V3");return t}var gm={name:"use",mcpName:"session_set",usage:"fui use [<target> | -p P -m M | --module-dir D] | fui use --clear",summary:"Show, set or clear the session target (target: /project/module, a live URL, or a display name)",options:{...ln,"module-dir":{type:"string"},clear:{type:"boolean"}},async run(e){let{values:t,positionals:n,session:o,render:s}=e;if(t.clear===!0){await o.clearTarget(),k(`session target cleared
153
+ `);return}let r=n[0],i=f(t,"project"),a=f(t,"module"),c=f(t,"module-dir");if(!r&&!i&&!a&&!c){let l=await o.getTarget();if(!l){T("# no session target; set one: fui use <project>/<module>");return}k(X([{...l,session:o.file}],s));return}if(i&&!a||!i&&a)throw new h("pass both -p and -m");Bn(await Ya(O(e),{target:r,projectId:i,moduleId:a,moduleDir:c}))}},wm={name:"resolve",mcpName:"session_resolve",usage:"fui resolve <target>",summary:"Resolve /project/module, a live module URL or a display name to ids (no session change)",async run(e){let t=e.positionals[0];if(!t)throw new h("missing <target>");Ro(await Xa(O(e),t))}},ym={name:"project list",mcpName:"project_list",usage:"fui project list",summary:"List projects you can access",async run(e){let t=(await Qa(O(e))).map((n)=>({group:n.GroupName||"Khac",id:n.ProjectID,name:n.ProjectName,framework:n.Framework??"",domain:n.pDomain??""}));k(X(t,e.render))}},bm={name:"project sync",mcpName:"project_sync",usage:"fui project sync [-p project]",summary:"Re-fetch project data (project.json, components, imports, module list); archives what changed or vanished",async run(e){Bn(await Za(O(e),await ss(e)))}},km={name:"module list",mcpName:"module_list",usage:"fui module list [-p project]",summary:"List modules from the server and refresh the local module cache",async run(e){let{rows:t,notes:n}=await ec(O(e),await ss(e));for(let o of n)T(`# ${o}`);k(X(t.map((o)=>({id:o.ModuleID,name:o.ModuleName,framework:o.Framework??"",domain:o.mDomain??"",htmlOnly:o.HTMLOnly?"yes":"no",updated:o.UpdateTime??"",by:o.UpdateUser??""})),e.render))}},vm={name:"module new",mcpName:"module_new",usage:"fui module new <moduleId> -p <project> --name <name> [--framework V2|V3] [--html-only] [--domain d] [--set key=json ...]",summary:"Create an empty module on the server (then fui module get)",options:{name:{type:"string"},framework:{type:"string"},"html-only":{type:"boolean"},domain:{type:"string"},set:{type:"string",multiple:!0}},async run(e){let t=e.positionals[0],n=f(e.values,"name");if(!t||!n)throw new h("need <moduleId> and --name");Bn(await tc(O(e),{projectId:await ss(e),moduleId:t,moduleName:n,framework:lc(f(e.values,"framework")),htmlOnly:e.values["html-only"]===!0?!0:void 0,mDomain:f(e.values,"domain"),extra:cn(e.values.set)}))}},Sm={name:"module update",mcpName:"module_update",usage:"fui module update [-p P -m M] [--name n] [--framework V2|V3] [--html-only true|false] [--domain d] [--new-id id] [--set key=json ...]",summary:"Edit metadata of an existing module (name, id, framework, domain, HTMLOnly)",options:{...ln,name:{type:"string"},framework:{type:"string"},"html-only":{type:"string"},domain:{type:"string"},"new-id":{type:"string"},set:{type:"string",multiple:!0}},async run(e){let{projectId:t,moduleId:n}=await cc(e);Bn(await nc(O(e),{projectId:t,moduleId:n,moduleName:f(e.values,"name"),framework:lc(f(e.values,"framework")),htmlOnly:fm(f(e.values,"html-only"),"--html-only"),mDomain:f(e.values,"domain"),newModuleId:f(e.values,"new-id"),extra:cn(e.values.set)}))}},$m={name:"module delete",mcpName:"module_delete",usage:"fui module delete -p <project> -m <module>",summary:"Delete a module on the server and archive its local folder (ask the user first; ids must be explicit)",options:ln,async run(e){let t=f(e.values,"project"),n=f(e.values,"module");if(!t||!n)throw new h("module delete needs explicit -p <project> -m <module>");Bn(await oc(O(e),t,n))}},jm={name:"module get",mcpName:"module_get",usage:"fui module get [<target> | -p P -m M]",summary:"Check out a module into the workspace (archives differing local files) and make it the session target",options:ln,async run(e){let t=f(e.values,"project"),n=f(e.values,"module");if(t&&!n||!t&&n)throw new h("pass both -p and -m, or a target");Ro(await rc(O(e),{target:e.positionals[0],projectId:t,moduleId:n}))}};function ac(e){return{name:`module get-${e}`,mcpName:`module_get_${e}`,usage:`fui module get-${e} [-p P -m M]`,summary:`Fetch only the module ${e==="ui"?"UI JSON (writes module.json":"HTML body (writes body.html"} if checked out)`,options:ln,async run(t){let{projectId:n,moduleId:o}=await cc(t);Ro(await sc(O(t),n,o,e))}}}var Im={name:"component get",mcpName:"component_get",usage:"fui component get <name> [-p P [-m M]]",summary:"Print a component's .vue source (writes it locally when the scope folder exists); -p without -m = project scope",options:ln,async run(e){let t=e.positionals[0];if(!t)throw new h("missing <name>");let n=O(e),o=await an(n,f(e.values,"project"),f(e.values,"module"));Ro(await ic(n,o,t))}},uc=[gm,wm,ym,bm,km,vm,Sm,$m,jm,ac("ui"),ac("html"),Im];import{resolve as Qh}from"path";import{join as at}from"path";import it from"path";import{join as dc}from"path";async function Ot(e,t){let n={functionRights:null,systemRights:null},o;try{o=JSON.parse(await B(dc(e,"_db","rights.json")))}catch{return n}let s=Object.keys(o??{});if(s.length===0)return n;let r=(l)=>s.find((u)=>u.toLowerCase()===l.toLowerCase())??null,i;if(t)i=r(t);else if(s.length===1)i=s[0];else{let l=await U(dc(e,"_db","_connections.json"),null);i=l?.default?r(l.default):null}if(!i)return n;let a=o[i],c=(l,u)=>Array.isArray(l)?new Set(l.map((d)=>d&&typeof d==="object"?String(d[u]??""):"").filter(Boolean)):null;return{functionRights:c(a?.functionRights,"FunctionCode"),systemRights:c(a?.systemRights,"SysRight")}}function Mt(e){return mc("FunctionRight",e)}function pc(e){return mc("SystemRight",e)}function qn(e,t){if(e.length===0)return null;if(t===null)return`Ch\u01B0a c\xF3 cache _db/rights.json \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c ${e.length} m\xE3 FunctionRight (${e.join(", ")}) `+"c\xF3 th\u1EADt hay kh\xF4ng. Preview c\u1EA5p s\u1EB5n \u0111\xFAng nh\u1EEFng m\xE3 n\xE0y n\xEAn control v\u1EABn hi\u1EC7n \u1EDF \u0110\xC2Y; user th\u1EADt ch\u1EC9 th\u1EA5y n\u1EBFu m\xE3 \u0111\xE3 "+"\u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a. Ch\u1EA1y right_function_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i render l\u1EA1i.";let n=e.filter((o)=>!t.has(o));if(n.length===0)return null;return`\uD83D\uDEA8 FunctionRight CH\u01AFA \u0110\u01AF\u1EE2C \u0110\u1ECANH NGH\u0128A tr\xEAn h\u1EC7 th\u1ED1ng acc: ${n.join(", ")} \u2014 preview c\u1EA5p s\u1EB5n n\xEAn control v\u1EABn `+"hi\u1EC7n \u1EDF \u0110\xC2Y, nh\u01B0ng ch\u1EA1y th\u1EADt th\xEC rightTest() lu\xF4n tr\u1EA3 false \u2192 control/menu bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng c\xF3 l\u1ED7i JS n\xE0o. "+'T\u1EA1o m\xE3: right_function_new({ functionCode: \u2026, functionName: "\u2026" }); ho\u1EB7c right_function_list n\u1EBFu cache c\u0169.'}function mc(e,t){let n=new Set,o=new RegExp(`${e}['"]?\\s*:\\s*\\[([^\\]]*)\\]`,"g"),s=new RegExp(`${e}\\s*\\.\\s*includes\\(\\s*['"]([^'"]+)['"]`,"g");for(let r of t){if(!r)continue;let i;o.lastIndex=0;while(i=o.exec(r))for(let a of i[1].split(",")){let c=a.trim().replace(/^['"]|['"]$/g,"");if(c)n.add(c)}s.lastIndex=0;while(i=s.exec(r))n.add(i[1])}return Array.from(n).sort()}var Cm=new Set(["f-table","f-dialog","f-window","f-button","f-date","f-time","f-time-counter","f-search","f-menu","f-radiobox","f-file-upload","f-image-update","f-excel-reader","f-qrcode","f-qrcode-reader","f-editor","f-editor-dialog","f-echart","f-chart","f-sheet","f-pdfmake","f-label","f-box","f-header","f-title","f-slider","fp-profile","f-dynamic-component","f-openwindow-control","f-table-item-component"]),hc=new Set(["t-html","t-label","t-num","t-time","t-boolean","t-check","t-text","t-select","t-combobox","t-menu","t-link","t-button"]),Rm={"f-pdfmake":["pdfmake.min.js","print.min.js","vfs_fonts.js"],"f-file-upload":["plupload.full.min.js"],"f-image-update":["index.umd.js","style.css"],"f-qrcode":["qrcode.min.js"],"f-qrcode-reader":["qrcode-reader.min.js"],"f-echart":["echarts.min.js","fechart.js"],"f-sheet":["ag-grid-community.min.js","fsheet.js"],"f-editor":["ckeditor.js"],"f-editor-dialog":["ckeditor.js"]};var un=new Set(["API","CALL","IF","FUN","EXE","MESS","MESSBOX","ARRAY","CONFIRM","IN","OUT","THEN","ELSE","CALLBACK","ERROROUT","ERRORCALLBACK","CANCEL","METHOD","HEADER","COL"]),fc=new Set(["item","index","v_old","user","vueData","p_router","p_routers","v_Set","moment"]);function j(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function cs(e){if(Array.isArray(e))return e.length>0&&e.every((t)=>j(t)||typeof t==="string")&&e.some((t)=>j(t)&&Object.keys(t).some((n)=>un.has(n)));if(!j(e))return!1;return Object.keys(e).some((t)=>un.has(t))}function ze(e){let t=e.trim();if(t.startsWith("vueData."))t=t.slice(8);let n=t.match(/^[A-Za-z_$][\w$]*/);return n?n[0]:t}function Wn(e){return!!e&&!e.includes(" ")&&!e.includes("{{")&&!e.includes("(")&&!e.startsWith("`")&&!e.startsWith("#")&&!e.startsWith("!")&&/^[A-Za-z_$]/.test(e.replace(/^vueData\./,""))}function Pm(e){let t=e.trim();if(!t)return!0;if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")return!0;if(/^'[^']*'$/.test(t)||/^"[^"]*"$/.test(t))return!0;return/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(t)}function Tm(e){let t=e.split(""),n=null;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(s==="\\"){if(t[o]=" ",o+1<e.length)t[o+1]=" ",o++;continue}if(s===n){n=null;continue}t[o]=" "}else if(s==="'"||s==='"'||s==="`")n=s}return t.join("")}var Em=[{re:/\bfunction\s*\(/,what:'khai b\xE1o "function(...)"'},{re:/\bfunction\s+[A-Za-z_$]/,what:'khai b\xE1o "function ten(...)"'},{re:/\breturn\b/,what:'l\u1EC7nh "return"'},{re:/\bvar\s+[A-Za-z_$]/,what:'khai b\xE1o "var"'},{re:/\blet\s+[A-Za-z_$]/,what:'khai b\xE1o "let"'},{re:/\bconst\s+[A-Za-z_$]/,what:'khai b\xE1o "const"'}];function Nm(e,t,n,o){if(typeof t!=="string")return;if(!(e.startsWith(":")&&!e.startsWith("::")||e.startsWith("v-bind:")))return;let r=Tm(t),i=[];for(let{re:a,what:c}of Em)if(a.test(r))i.push(c);if(r.replace(/;\s*$/,"").includes(";"))i.push('d\u1EA5u ";" n\u1ED1i nhi\u1EC1u c\xE2u l\u1EC7nh');if(i.length===0)return;o.push(`attr "${e}" (t\u1EA1i ${n}) ch\u1EE9a ${i.join(" + ")} \u2014 gi\xE1 tr\u1ECB binding \u0111i th\u1EB3ng v\xE0o template compiler c\u1EE7a Vue, `+"ch\u1EC9 nh\u1EADn M\u1ED8T BI\u1EC2U TH\u1EE8C thu\u1EA7n (object/array literal, ternary, chu\u1ED7i .map()/.filter() arrow, truy c\u1EADp thu\u1ED9c t\xEDnh). "+"Statement \u1EDF \u0111\xE2y l\xE0m compile template h\u1ECFng \u2192 TRANG TR\u1EAENG m\xE0 kh\xF4ng c\xF3 JS error hay [Vue warn] n\xE0o. "+'C\xE1ch s\u1EEDa: \u0111\u01B0a logic v\xE0o m\u1ED9t h\xE0m trong script.js (function buildX(){ \u2026 vueData.x = \u2026; }), g\u1ECDi qua "EXE": "buildX()" '+`trong CALLBACK c\u1EE7a action n\u1EA1p d\u1EEF li\u1EC7u, r\u1ED3i "${e}" ch\u1EC9 tr\u1ECF t\u1EDBi bi\u1EBFn vueData \u0111\xF3 (vd "${e}": "x"). `+"Chi ti\u1EBFt: references/advanced-techniques.md.")}function Fe(e,t,n="$",o=!1){if(Array.isArray(e))e.forEach((s,r)=>Fe(s,t,`${n}[${r}]`,o));else if(j(e)){t(e,n,o);for(let[s,r]of Object.entries(e))Fe(r,t,`${n}.${s}`,o||s.startsWith("#"))}}var Am="(chu\u1EA9n m\u1EB7c \u0111\u1ECBnh fui/DESIGN.md \u2014 ch\u1EC9 gi\u1EEF nguy\xEAn n\u1EBFu user y\xEAu c\u1EA7u r\xF5 ki\u1EC3u kh\xE1c)",_m=new Set([400,550,600,650,800]),Dm=/(x\u00F3a|xo\u00E1|\bdelete\b|\bremove\b)/i,xm=/(delete|remove|xoa)/i,Om=new Set(["v-text-field","v-textarea","v-select","v-combobox","v-autocomplete","f-date","f-time","f-search"]),gc=new Set(["9"]);function Mm(e,t,n){let o=JSON.stringify(e),s=Mt([o]),r=pc([o]).filter((l)=>!gc.has(l)),i=s.length+r.length;if(i===0)return 0;let a=t.definedFunctionRights??null,c=t.definedSystemRights??null;if(a===null&&c===null)return n.push(`Module g\xE1c quy\u1EC1n b\u1EB1ng ${i} m\xE3 literal (${[...s,...r].join(", ")}) nh\u01B0ng ch\u01B0a c\xF3 cache _db/rights.json `+"\u0111\u1EC3 \u0111\u1ED1i chi\u1EBFu \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c m\xE3 n\xE0o c\xF3 th\u1EADt. M\xE3 kh\xF4ng t\u1ED3n t\u1EA1i l\xE0m rightTest() lu\xF4n tr\u1EA3 false: "+"control/menu bi\u1EBFn m\u1EA5t im l\u1EB7ng, kh\xF4ng l\u1ED7i JS. Ch\u1EA1y right_function_list + right_system_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i validate l\u1EA1i."),i;if(a&&s.length>0){let l=a,u=s.filter((d)=>!l.has(d));for(let d of u)n.push(`FunctionRight "${d}" ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3 ${l.size} m\xE3`+`${l.size?`: ${Array.from(l).slice(0,15).join(", ")}${l.size>15?", \u2026":""}`:""}). `+"rightTest() s\u1EBD lu\xF4n tr\u1EA3 false \u2192 control/menu g\xE1c b\u1EB1ng m\xE3 n\xE0y bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng b\xE1o l\u1ED7i. "+`T\u1EA1o m\xE3: right_function_new({ functionCode: ${/^\d+$/.test(d)?d:`"${d}"`}, functionName: "\u2026" }); `+"ho\u1EB7c ch\u1EA1y right_function_list n\u1EBFu m\xE3 \u0111\xE3 c\xF3 tr\xEAn server m\xE0 cache c\u0169.")}if(c&&r.length>0){let l=c,u=r.filter((d)=>!l.has(d));for(let d of u)n.push(`SystemRight ${d} ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3: `+`${l.size?Array.from(l).sort().join(", "):"(ch\u01B0a m\u1EE9c n\xE0o)"}${gc.size?" \u2014 m\u1EE9c 9 lu\xF4n h\u1EE3p l\u1EC7":""}). `+"Kh\xF4ng ai \u0111\u01B0\u1EE3c c\u1EA5p m\u1EE9c n\xE0y th\xEC control g\xE1c b\u1EB1ng n\xF3 bi\u1EBFn m\u1EA5t im l\u1EB7ng. "+`\u0110\u1ECBnh ngh\u0129a: right_system_new({ sysRight: ${d}, note: "\u2026" }); ho\u1EB7c ch\u1EA1y right_system_list n\u1EBFu cache c\u0169.`)}return i}function Fm(e,t){let n=(a)=>t.push(`[design] ${a} ${Am}`),o=(a)=>typeof a==="string"?a:"";Fe(e,(a,c)=>{let l=a.API;if(typeof l==="string"&&xm.test(l)&&a.CONFIRM===void 0)n(`Action g\u1ECDi API "${l}" (t\u1EA1i ${c}) c\xF3 v\u1EBB l\xE0 thao t\xE1c X\xD3A nh\u01B0ng thi\u1EBFu CONFIRM \u2014 m\u1ECDi action x\xF3a ph\u1EA3i h\u1ECFi x\xE1c nh\u1EADn.`)});let s=new Set;Fe(e,(a,c)=>{if(s.has(a))return;let l=j(a.attr)?a.attr:a;if(l!==a)s.add(l);let u=o(l.label)||o(l.text)||(typeof a.innerHTML==="string"?a.innerHTML:""),d=o(l["icon-text"]);if(!Dm.test(u)&&d!=="mdi-delete")return;let p=o(l.color)||o(l[":color"]);if(/\b(error|red)\b/i.test(p)&&!p.includes("?"))n(`N\xFAt/m\u1EE5c x\xF3a (t\u1EA1i ${c}) \u0111ang m\xE0u "${p}" \u2014 n\xFAt x\xF3a d\xF9ng m\xE0u "warning" (cam), kh\xF4ng d\xF9ng error/\u0111\u1ECF.`)});let r=(a,c,l,u)=>{if(Array.isArray(a)){a.forEach((I,g)=>r(I,`${c}[${g}]`,l,u));return}if(!j(a))return;let d=o(a.el),p=o(a.prop),m=j(a.attr)?a.attr:{},w=l||d==="v-dialog"||d==="f-dialog"||d==="hidden-container"||p.includes("hidden-container"),y=u||d==="v-card-actions";if(d==="v-toolbar"&&["color",":color","dark",":dark"].some((I)=>(I in m)))n(`v-toolbar (t\u1EA1i ${c}) c\xF3 color/dark \u2014 toolbar ti\xEAu \u0111\u1EC1 ph\u1EA3i "dense flat" c\xF9ng m\xE0u n\u1EC1n v\u1EDBi th\xE2n dialog, kh\xF4ng t\xF4 m\xE0u/dark ri\xEAng.`);if(d==="v-dialog"||d==="f-dialog"){let I=m.width??m[":width"],g=typeof I==="number"?I:typeof I==="string"&&/^\d+$/.test(I.trim())?parseInt(I,10):NaN;if(!Number.isNaN(g)&&!_m.has(g))n(`Dialog (t\u1EA1i ${c}) width=${g} \u2014 b\u1ED9 width chu\u1EA9n: 400 / 550 / 600 / 650 / 800.`)}if(w&&d==="v-card"&&["elevation",":elevation","outlined",":outlined"].some((I)=>(I in m)))n(`v-card trong dialog (t\u1EA1i ${c}) c\xF3 elevation/outlined \u2014 b\u1ECF \u0111i: v-dialog \u0111\xE3 t\u1EF1 c\xF3 b\xF3ng \u0111\u1ED5, th\xEAm n\u1EEFa l\xE0 b\xF3ng ch\u1ED3ng b\xF3ng. v-card ch\u1EC9 c\u1EA7n class "pa-0" (tri\u1EC7t \u0111\u1EC7m \u0111\u1EC3 toolbar ti\xEAu \u0111\u1EC1 s\xE1t m\xE9p).`);let v=o(m.class),E=v.includes("flex-md-grow-0")&&v.includes("ml-2");if(E)n(`Control (t\u1EA1i ${c}) mang class "flex-md-grow-0 ml-2" \u2014 d\u1EA5u hi\u1EC7u pattern label-div r\u1EDDi \u0111\xE3 lo\u1EA1i b\u1ECF; form d\xF9ng floating label ("label" trong attr c\u1EE7a field).`);if(w&&!E&&Om.has(d)&&!("label"in m)&&!(":label"in m)&&!("placeholder"in m))n(`${d} trong dialog (t\u1EA1i ${c}) thi\u1EBFu "label" \u2014 form dialog d\xF9ng floating label.`);if(d==="v-snackbar"||d==="v-alert")n(`${d} (t\u1EA1i ${c}) \u2014 ph\u1EA3n h\u1ED3i/th\xF4ng b\xE1o d\xF9ng h\u1EC7 th\u1ED1ng c\xF3 s\u1EB5n c\u1EE7a FUI: MESS (toast) / MESSBOX (h\u1ED9p tho\u1EA1i) trong action, CONFIRM \u0111\u1EC3 x\xE1c nh\u1EADn \u2014 kh\xF4ng t\u1EF1 d\u1EF1ng ${d} trong controls.`);if(d==="f-table"||d==="f-sheet"){let I=m[":height"]??m.height;if(I===void 0)n(`${d} (t\u1EA1i ${c}) kh\xF4ng c\xF3 ":height" \u2014 b\u1EA3ng s\u1EBD t\u1EF1 gi\xE3n theo s\u1ED1 d\xF2ng v\xE0 l\xE0m c\u1EA3 TRANG cu\u1ED9n. B\u1EA3ng data ch\xEDnh n\xEAn \u0111\u1EB7t \`":height": -16\` (fill t\u1EDBi \u0111\xE1y m\xE0n h\xECnh, ch\u1EEBa 16px \u2014 component t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt, kh\xF4ng c\u1EA7n t\xEDnh tay); trong dialog d\xF9ng s\u1ED1 d\u01B0\u01A1ng (px c\u1ED1 \u0111\u1ECBnh, vd 360). B\u1EA3ng ng\u1EAFn/lookup ch\u1EE7 \xFD t\u1EF1 gi\xE3n th\xEC b\u1ECF qua.`);else if(typeof I==="string"&&/\$\s*\(\s*window\s*\)\s*\.\s*height\s*\(\s*\)/.test(I))n(`${d} (t\u1EA1i ${c}) t\xEDnh ":height" th\u1EE7 c\xF4ng theo viewport (\`${I}\`) \u2014 kh\xF4ng c\u1EA7n n\u1EEFa: ${d} t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt c\u1EE7a b\u1EA3ng sau layout v\xE0 t\u1EF1 c\u1EADp nh\u1EADt khi resize. Thay b\u1EB1ng \`":height": -16\` (s\u1ED1 \xE2m = kho\u1EA3ng ch\u1EEBa \u1EDF \u0111\xE1y).`)}if(y&&(d==="f-button"||d==="v-btn")){let I=o(m.label)||(typeof a.innerHTML==="string"?a.innerHTML:"");if(/^\s*(\u0111\u00F3ng|dong|close)\s*$/i.test(I))n(`N\xFAt "${I.trim()}" trong v-card-actions (t\u1EA1i ${c}) \u2014 dialog chu\u1EA9n ch\u1EC9 d\xF9ng icon close tr\xEAn toolbar, kh\xF4ng th\xEAm n\xFAt \u0110\xF3ng \u1EDF actions.`)}for(let[I,g]of Object.entries(a))r(g,`${c}.${I}`,w,y)};(Array.isArray(e.controls)?e.controls:[]).forEach((a,c)=>r(a,`$.controls[${c}]`,!1,!1))}var Lm=12,is=25,yc=/\b(shrink|flex-grow-0)\b/;function bc(e){return typeof e==="string"?e:j(e)?[e.class,e[":class"]].filter((t)=>typeof t==="string").join(" "):""}function Um(e){return e.w===void 0||e.w===null||e.w===""||e.w===0}function Hm(e,t){Fe(e.controls,(n,o)=>{if(!Array.isArray(n.cols))return;Bm(n.cols,o,t),n.cols.forEach((s,r)=>{if(!j(s))return;let i=s,a=i.w,c=typeof a==="number"?a:typeof a==="string"&&/^\d+$/.test(a.trim())?parseInt(a.trim(),10):NaN;if(Number.isNaN(c)||c<=Lm)return;let l=`${o}.cols[${r}]${typeof i.el==="string"?` (el: ${i.el})`:""}`;if(c<is){t.push(`${l} c\xF3 "w": ${c} \u2014 v\xF9ng ch\u1EBFt 13\u201324: V2 hi\u1EC3u l\xE0 ${c}px, c\xF2n V3 sinh class "v-col-${c}" m\xE0 Vuetify 3 kh\xF4ng \u0111\u1ECBnh ngh\u0129a (ch\u1EC9 c\xF3 .v-col-1 \u2026 .v-col-12) n\xEAn c\u1ED9t KH\xD4NG c\xF3 b\u1EC1 r\u1ED9ng n\xE0o, r\u01A1i v\u1EC1 chia \u0111\u1EC1u. D\xF9ng 1\u201312 (chia c\u1ED9t) ho\u1EB7c >= ${is} (px, k\xE8m \`"col": { "class": "shrink" }\`).`);return}let u=i.col;if(yc.test(bc(u)))return;t.push(j(u)&&"shrink"in u?`${l} c\xF3 "w": ${c} (px) k\xE8m \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn n\xF3 tr\u01A1 v\xE0 c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng. Vi\u1EBFt \`"col": { "class": "shrink" }\` \u0111\u1EC3 \u0111\xFAng \u1EDF c\u1EA3 hai framework.`:`${l} c\xF3 "w": ${c} \u2014 s\u1ED1 >= ${is} ngh\u0129a l\xE0 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh px, nh\u01B0ng c\u1ED9t grid m\u1EB7c \u0111\u1ECBnh flex-grow:1 n\xEAn flex-basis ${c}px KH\xD4NG c\xF3 t\xE1c d\u1EE5ng: c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng (\u0111o th\u1EADt: w=300 ra 687px \u1EDF V2, 738px \u1EDF V3). Th\xEAm \`"col": { "class": "shrink" }\`.`)})},"$.controls")}function Bm(e,t,n){let o=(s)=>j(s)&&s.el==="v-spacer";if(!e.some(o))return;e.forEach((s,r)=>{if(!j(s))return;let i=s;if(typeof i.el!=="string"||!i.el)return;let a=`${t}.cols[${r}] (el: ${i.el})`,c=i.col,l=yc.test(bc(c));if(i.el==="v-spacer"){if(l)n.push(`${a} \u2014 \`v-spacer\` m\xE0 c\xF3 \`shrink\` th\xEC co l\u1EA1i c\xF2n 0px (\u0111o th\u1EADt: 24px \u1EDF V3, 0px \u1EDF V2), kh\xF4ng \u0111\u1EA9y \u0111\u01B0\u1EE3c nh\xF3m sau sang ph\u1EA3i n\u1EEFa v\xE0 c\u1EA3 h\xE0ng d\u1ED3n v\u1EC1 tr\xE1i. B\u1ECF \`shrink\` kh\u1ECFi ri\xEAng c\u1ED9t n\xE0y.`);return}if(!Um(i)||l)return;n.push(j(c)&&"shrink"in c?`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\`, \`w\` r\u1ED7ng v\xE0 ch\u1EC9 c\xF3 \`"col": { "shrink": ... }\` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn c\u1ED9t v\u1EABn gi\xE3n v\xE0 chi\u1EBFm m\u1ED9t ph\u1EA7n \u0111\u1EC1u. Vi\u1EBFt \`"col": "shrink"\` (ho\u1EB7c \`{ "class": "shrink" }\`).`:`${a} n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi \`v-spacer\` nh\u01B0ng thi\u1EBFu \`shrink\` \u2014 \`w\` r\u1ED7ng KH\xD4NG c\xF3 ngh\u0129a "t\u1EF1 co theo n\u1ED9i dung": runtime b\u1ECDc ph\u1EA7n b\u1EC1 r\u1ED9ng trong \`if (c.w)\` n\xEAn c\u1ED9t n\xE0y kh\xF4ng c\xF3 class grid l\u1EABn flex-basis, c\xF2n tr\u01A1 \`flex-grow: 1\` v\xE0 \u0111\u01B0\u1EE3c CHIA PH\u1EA6N TR\u1ED0NG y nh\u01B0 \`v-spacer\`. H\u1EC7 qu\u1EA3: control b\u1ECB k\xE9o d\xE3n, h\u1EDF kho\u1EA3ng gi\u1EEFa c\xE1c n\xFAt, nh\xF3m cu\u1ED1i h\xE0ng kh\xF4ng s\xE1t m\xE9p ph\u1EA3i. Chu\u1EA9n cho h\xE0ng nhi\u1EC1u control: M\u1ECCI c\u1ED9t \`"col": "shrink"\`, tr\u1EEB \u0111\xFAng \`v-spacer\`.`)})}var wc=["API","CALL","EXE","FUN","IF","MESS","MESSBOX"],qm=new Set(["IN","OUT","HEADER","METHOD","CALLBACK","ERROROUT","ERRORCALLBACK","THEN","ELSE","CONFIRM","CANCEL","ARRAY","COL","MAP"]);function Wm(e,t,n){for(let[o,s]of Object.entries(e)){if(o==="set")continue;Fe(s,(r,i)=>{let a=Object.keys(r),c=wc.filter((m)=>a.includes(m));if(c.length===0)return;let l=c[0],u=new Set([l]);if(l==="API"&&c.includes("MESS"))u.add("MESS");let d=c.filter((m)=>!u.has(m));if(d.length>0){t.push(`${i} g\u1ED9p ${c.length} l\u1EC7nh trong M\u1ED8T object \`{}\` (${c.join(", ")}) \u2014 ch\u1EC9 \`${l}\` ch\u1EA1y, c\xF2n ${d.map((m)=>`\`${m}\``).join(", ")} b\u1ECB b\u1ECF IM L\u1EB6NG (kh\xF4ng JS error, kh\xF4ng [Vue warn]). \`vueAction\` d\xF2 key l\u1EC7nh theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh API \u2192 CALL \u2192 EXE \u2192 FUN \u2192 IF \u2192 MESS \u2192 MESSBOX v\xE0 \`return\` ngay \u1EDF nh\xE1nh \u0111\u1EA7u ti\xEAn kh\u1EDBp, n\xEAn th\u1EE9 t\u1EF1 vi\u1EBFt kh\xF4ng c\xF3 t\xE1c d\u1EE5ng. Nhi\u1EC1u l\u1EC7nh c\xF3 th\u1EE9 t\u1EF1 tr\u01B0\u1EDBc sau th\xEC ph\u1EA3i l\xE0 M\u1EA2NG, m\u1ED7i l\u1EC7nh m\u1ED9t object: \`[{ "${c[0]}": \u2026 }, { "${c[1]}": \u2026 }]\`.`);return}let p=a.filter((m)=>!u.has(m)&&!qm.has(m)&&!wc.includes(m));if(p.length>0)n.push(`${i} c\xF3 l\u1EC7nh \`${l}\` n\u1EB1m chung object v\u1EDBi key g\xE1n d\u1EEF li\u1EC7u ${p.map((m)=>`\`${m}\``).join(", ")} \u2014 fragment g\xE1n ch\u1EA1y \u1EDF nh\xE1nh CU\u1ED0I c\u1EE7a \`vueAction\`, nh\xE1nh \u0111\xF3 ch\u1EC9 t\u1EDBi l\u01B0\u1EE3t khi KH\xD4NG key l\u1EC7nh n\xE0o kh\u1EDBp, n\xEAn c\xE1c key n\xE0y kh\xF4ng bao gi\u1EDD \u0111\u01B0\u1EE3c g\xE1n. T\xE1ch th\xE0nh m\u1EA3ng: \`[{ ${p.map((m)=>`"${m}": \u2026`).join(", ")} }, { "${l}": \u2026 }]\`.`)},`$.${o}`)}}function Vm(e,t){let n=[],o=[];if(t.schemaWarning)o.push(t.schemaWarning);if(!j(e))return{errors:["module.json g\u1ED1c ph\u1EA3i l\xE0 m\u1ED9t object v\u1EDBi c\xE1c kh\u1ED1i data/watch/controls/set."],warnings:o,stats:as()};let s=e,r=new Set(["data","watch","controls","set"]);for(let g of Object.keys(s))if(!r.has(g))o.push(`Kh\u1ED1i c\u1EA5p 1 kh\xF4ng chu\u1EA9n: "${g}" \u2014 module.json ch\u1EC9 c\xF3 data/watch/controls/set (xem module-json-anatomy.md).`);if(s.data!==void 0&&!Array.isArray(s.data))n.push(`"data" ph\u1EA3i l\xE0 M\u1EA2NG (array), hi\u1EC7n l\xE0 ${typeof s.data}.`);if(s.watch!==void 0&&!j(s.watch))n.push(`"watch" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.watch)?"array":typeof s.watch}.`);if(s.controls!==void 0&&!Array.isArray(s.controls))n.push(`"controls" ph\u1EA3i l\xE0 M\u1EA2NG container, hi\u1EC7n l\xE0 ${typeof s.controls}.`);if(s.set!==void 0&&!j(s.set))n.push(`"set" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(s.set)?"array":typeof s.set}.`);let i=new Set(fc),a=new Set,c=Array.isArray(s.data)?s.data:[];for(let g of c){if(!j(g)){o.push(`data[] ch\u1EE9a ph\u1EA7n t\u1EED kh\xF4ng ph\u1EA3i object: ${JSON.stringify(g)?.slice(0,60)}`);continue}for(let[b,R]of Object.entries(g)){if(un.has(b))continue;if(b.startsWith("#"))continue;if(cs(R))a.add(b);else i.add(ze(b))}}let l=0;Fe(s,(g,b,R)=>{if(R)return;for(let C of["OUT","ERROROUT"]){let S=g[C];if(typeof S==="string"){if(Wn(S))i.add(ze(S));else if(S.trim())l++}else if(j(S))for(let A of Object.keys(S))i.add(ze(A))}});let u=new Map,d=new Map;Fe(s,(g,b,R)=>{let C=g.CALL;if(typeof C==="string"){if(Wn(C))(R?d:u).set(ze(C),b);else if(C.trim()&&!R)l++}let S=g.API;if(typeof S==="string"&&S.trim()&&!S.startsWith("/")&&!Wn(S))l++;for(let[A,D]of Object.entries(g)){if(typeof D!=="string")continue;let N=R||A.startsWith("#")?d:u;for(let G of D.matchAll(/\bCALL\(\s*([A-Za-z_$][\w$.]*)\s*\)/g))N.set(ze(G[1]),b)}});for(let[g,b]of u)if(!a.has(g))n.push(`CALL "${g}" (t\u1EA1i ${b}) kh\xF4ng kh\u1EDBp named action n\xE0o trong data[]. Named actions hi\u1EC7n c\xF3: ${[...a].join(", ")||"(kh\xF4ng c\xF3)"}.`);let p=0,m=0,w=Array.isArray(s.controls)?s.controls:[];for(let g=0;g<w.length;g++){let b=w[g];if(p++,!j(b)||!Array.isArray(b.rows)){n.push(`controls[${g}] ph\u1EA3i l\xE0 container c\xF3 "rows" (grid b\u1EAFt bu\u1ED9c: container > rows > cols).`);continue}for(let R=0;R<b.rows.length;R++){let C=b.rows[R];if(!j(C)||!Array.isArray(C.cols)){n.push(`controls[${g}].rows[${R}] ph\u1EA3i c\xF3 "cols" (m\u1EA3ng Control Object).`);continue}for(let S=0;S<C.cols.length;S++){let A=C.cols[S];if(m++,!j(A))continue;if(A.w===void 0&&A.el!==void 0)o.push(`controls[${g}].rows[${R}].cols[${S}] (el: ${String(A.el)}) thi\u1EBFu "w" \u2014 b\u1EAFt bu\u1ED9c cho item trong cols.`)}}}let y=new Set;if(Fe(s,(g,b)=>{if("children"in g&&(("el"in g)||("rows"in g)||("cols"in g)))n.push(`"children" kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 (t\u1EA1i ${b}) \u2014 d\xF9ng "innerHTML" (m\u1EA3ng Control Object) \u0111\u1EC3 l\u1ED3ng.`);let R=g.el;if(typeof R==="string"&&R){if(y.add(R),(R.startsWith("f-")||R.startsWith("fp-"))&&!Cm.has(R)&&!t.localComponents.has(R))o.push(`Component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong registry FUI (component-quickref.md) v\xE0 kh\xF4ng th\u1EA5y trong components local \u2014 ki\u1EC3m tra ch\xEDnh t\u1EA3 ho\u1EB7c ch\u1EA1y component_list.`);else if(R.startsWith("t-")&&!hc.has(R))o.push(`Cell component "${R}" (t\u1EA1i ${b}) kh\xF4ng c\xF3 trong danh s\xE1ch t-* h\u1EE3p l\u1EC7: ${[...hc].join(", ")}.`);else if(R.startsWith("uc-")&&!t.localComponents.has(R))o.push(`Custom component "${R}" (t\u1EA1i ${b}) ch\u01B0a th\u1EA5y file .vue/local metadata \u2014 t\u1EA1o component tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y component_list.`)}let C=g.attr;if(j(C))for(let[S,A]of Object.entries(C)){if(S.startsWith("@"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 shorthand "@"; d\xF9ng "v-on:${S.slice(1)}".`);else if(S.startsWith("#"))n.push(`attr "${S}" (t\u1EA1i ${b}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 slot shorthand "#"; d\xF9ng "v-slot:${S.slice(1)}".`);else if((S==="style"||S===":style")&&typeof A==="string"){if(A.split(";").map((N)=>N.trim()).filter(Boolean).filter((N)=>!/^(max-|min-)?width\s*:/i.test(N)).filter((N)=>!(g.el==="v-card"&&/^padding\s*:\s*0(px)?$/i.test(N))).length>0)o.push(`attr style "${A}" (t\u1EA1i ${b}) \u2014 module th\u1EF1c t\u1EBF g\u1EA7n nh\u01B0 kh\xF4ng d\xF9ng style ngo\xE0i width (controls-styling-vocabulary.md). C\xE2n nh\u1EAFc b\u1ECF.`)}else if((S.startsWith(":")||S.startsWith("v-bind:"))&&typeof A==="string"&&!Pm(A))l++;else if(S==="v-model"&&typeof A==="string"){if(Wn(A)){let D=ze(A);if(!i.has(D)&&!a.has(D))o.push(`v-model "${A}" (t\u1EA1i ${b}) \u2014 bi\u1EBFn g\u1ED1c "${D}" ch\u01B0a khai b\xE1o trong data[]. Khai b\xE1o state tr\u01B0\u1EDBc \u0111\u1EC3 reactive ho\u1EA1t \u0111\u1ED9ng \u0111\xFAng.`)}else if(A.trim())l++}Nm(S,A,b,n)}}),j(s.watch)){let g=(b,R)=>{for(let C of Object.keys(b)){if(C==="deep-watch"){if(j(b[C]))g(b[C],`${R}.deep-watch`);continue}let S=ze(C);if(!i.has(S))o.push(`watch "${C}" (${R}) \u2014 bi\u1EBFn "${S}" ch\u01B0a khai b\xE1o trong data[], watcher s\u1EBD kh\xF4ng bao gi\u1EDD ch\u1EA1y.`)}};g(s.watch,"watch")}for(let g of y){let b=Rm[g];if(!b)continue;let R=b.filter((C)=>!t.importsText.includes(C.toLowerCase()));if(R.length>0)n.push(`"${g}" c\u1EA7n import: ${R.join(", ")} \u2014 ch\u01B0a th\u1EA5y trong _imports.json (module l\u1EABn project). D\xF9ng file_import_list/file_import_new (xem component-quickref.md).`)}let v=0,E=new Set;if(t.schemaProcs){let g=new Set;for(let b of t.schemaProcs.values())for(let R of b)g.add(R);if(Fe(s,(b,R)=>{let C=b.API;if(typeof C!=="string"||!C||C.includes("{{"))return;v++;let S=null,A=null;if(C.startsWith("/")){let N=C.replace(/^\/+/,"").split("/").filter(Boolean);if(N.length<2)return;if(A=t.schemaProcs.get(N[0].toLowerCase())??null,!A){E.add(N[0]);return}S=N[1]==="auth"?N[2]??null:N[1]}else if(Wn(C))S=C,A=g;if(!S||S.includes("`")||!A)return;if(![`spAPI_${S}`,`spAPI_AUTH_${S}`,`spAPIFILE_${S}`,`spAPIFILE_UPLOAD_${S}`,S].some((N)=>A.has(N)))o.push(`API "${C}" (t\u1EA1i ${R}) \u2014 kh\xF4ng t\xECm th\u1EA5y spAPI_${S} trong schema local. Deploy SP tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y db_schema_get (b\u1ECF qua n\u1EBFu API thu\u1ED9c backend kh\xE1c).`)}),E.size>0){let b=[...E].sort();o.push(`${b.length} alias \u0111\u01B0\u1EE3c g\u1ECDi nh\u01B0ng ch\u01B0a c\xF3 k\u1EBFt n\u1ED1i local \u2014 KH\xD4NG \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c t\xEAn SP: ${b.join(", ")}. `+`K\u1EBFt n\u1ED1i t\u1EEBng c\xE1i b\u1EB1ng db_connect({ apiName: "${b[0]}", apiDomain: "\u2026", dbToken: "\u2026" }) \u0111\u1EC3 b\u01B0\u1EDBc ki\u1EC3m n\xE0y ch\u1EA1y (b\u1ECF qua n\u1EBFu alias thu\u1ED9c backend c\u1EE7a nh\xF3m kh\xE1c).`)}}let I=Mm(s,t,o);return Fm(s,o),Hm(s,o),Wm(s,n,o),{errors:n,warnings:o,stats:{stateKeys:i.size-fc.size,namedActions:a.size,containers:p,controls:m,callRefs:u.size,apiRefs:v,crossWindowCalls:d.size,dynamicRefs:l,rightRefs:I}}}function as(){return{stateKeys:0,namedActions:0,containers:0,controls:0,callRefs:0,apiRefs:0,crossWindowCalls:0,dynamicRefs:0,rightRefs:0}}function Jm(e){return e.match(/^CREATE (?:PROCEDURE|FUNCTION)\s+(\S+)\s*\(/i)?.[1]??null}async function Km(e){let t=it.dirname(it.dirname(e)),n=new Set;for(let c of[it.join(e,"components"),it.join(t,"components")]){for(let u of await Me(c))n.add(u.replace(/\.vue$/i,""));let l=await U(it.join(c,"_components.json"),[]);for(let u of l)if(u?.comName)n.add(u.comName)}let o=[];for(let c of[e,t])o.push(await F(it.join(c,"imports","_imports.json")));let s=await U(it.join(t,"_db","_connections.json"),null),r=new Map,i=[];for(let c of Object.keys(s?.connections??{})){let l=await U(it.join(t,"_db",c,"schema.json"),null),u=Array.isArray(l?.procedures)?l.procedures:null;if(u===null)continue;if(!u.every((d)=>typeof d==="string")){i.push(c);continue}r.set(c.toLowerCase(),new Set(u.map(Jm).filter((d)=>d!==null)))}let a=await Ot(t);return{localComponents:n,importsText:o.join(`
154
+ `).toLowerCase(),schemaProcs:r.size>0?r:null,schemaWarning:i.length>0?`_db/${i.join("/schema.json, _db/")}/schema.json sai \u0111\u1ECBnh d\u1EA1ng (b\u1EA3n cache c\u0169) \u2014 b\u1ECF qua b\u01B0\u1EDBc \u0111\u1ED1i chi\u1EBFu t\xEAn API v\u1EDBi stored procedure \u1EDF ${i.length>1?"c\xE1c k\u1EBFt n\u1ED1i n\xE0y":"k\u1EBFt n\u1ED1i n\xE0y"}. Ch\u1EA1y db_schema_get \u0111\u1EC3 l\u1EA5y b\u1EA3n m\u1EDBi.`:null,definedFunctionRights:a.functionRights,definedSystemRights:a.systemRights}}async function Po(e){let t=await F(it.join(e,"module.json"));if(!t.trim())return{errors:[`module.json r\u1ED7ng ho\u1EB7c kh\xF4ng t\u1ED3n t\u1EA1i trong ${e}.`],warnings:[],stats:as()};let n;try{n=JSON.parse(t)}catch(s){return{errors:[`module.json c\xF3 l\u1ED7i JSON syntax: ${s.message}`],warnings:[],stats:as()}}let o=await Km(e);return Vm(n,o)}function To(e){let t=[];if(e.errors.length===0&&e.warnings.length===0)t.push("\u2705 module.json h\u1EE3p l\u1EC7 \u2014 kh\xF4ng ph\xE1t hi\u1EC7n l\u1ED7i ng\u1EEF ngh\u0129a.");else{if(e.errors.length>0)t.push(`\u274C ${e.errors.length} l\u1ED7i (s\u1EBD h\u1ECFng khi ch\u1EA1y):`),t.push(...e.errors.map((o)=>` - ${o}`));if(e.warnings.length>0){if(t.length)t.push("");t.push(`\u26A0\uFE0F ${e.warnings.length} c\u1EA3nh b\xE1o (n\xEAn xem l\u1EA1i):`),t.push(...e.warnings.map((o)=>` - ${o}`))}}let n=e.stats;if(t.push(""),n.dynamicRefs>0)t.push(`\u2139\uFE0F ${n.dynamicRefs} bi\u1EC3u th\u1EE9c \u0111\u1ED9ng (\`:attr\` ph\u1EE9c t\u1EA1p, CALL/API/v-model/OUT d\u1EA1ng \`{{\u2026}}\`/g\u1ECDi h\xE0m/template string) KH\xD4NG ki\u1EC3m t\u0129nh \u0111\u01B0\u1EE3c \u2014 bi\u1EBFn b\xEAn trong ch\xFAng kh\xF4ng \u0111\u01B0\u1EE3c \u0111\u1ED1i chi\u1EBFu v\u1EDBi data[]. "H\u1EE3p l\u1EC7" \u1EDF \u0111\xE2y KH\xD4NG c\xF3 ngh\u0129a l\xE0 ch\u1EA1y \u0111\xFAng: d\xF9ng module_simulate \u0111\u1EC3 ph\u1EE7 ph\u1EA7n n\xE0y.`);return t.push(`Th\u1ED1ng k\xEA: ${n.stateKeys} state \xB7 ${n.namedActions} named action \xB7 ${n.containers} container / ${n.controls} control \xB7 ${n.callRefs} CALL ref${n.apiRefs?` \xB7 ${n.apiRefs} API ref \u0111\u1ED1i chi\u1EBFu schema`:""}${n.crossWindowCalls?` \xB7 ${n.crossWindowCalls} CALL xuy\xEAn c\u1EEDa s\u1ED5 (thu\u1ED9c module kh\xE1c, kh\xF4ng ki\u1EC3m \u0111\u01B0\u1EE3c t\u1EEB \u0111\xE2y)`:""}${n.rightRefs?` \xB7 ${n.rightRefs} m\xE3 quy\u1EC1n \u0111\u1ED1i chi\u1EBFu _db/rights.json`:""}`),t.join(`
155
+ `)}function ee(e,t){let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t)+"\u2026":n}function Vn(e){if(e===null)return"null";if(Array.isArray(e))return e.length===0?"[]":`[${e.length} items]`;if(j(e)){let t=Object.keys(e);return t.length===0?"{}":`{${ee(t.join(","),40)}}`}if(typeof e==="string")return`"${ee(e,30)}"`;return String(e)}function _e(e,t=0){if(t>3)return"\u2026";if(typeof e==="string")return ee(e,70);if(Array.isArray(e))return e.map((o)=>_e(o,t+1)).join(" ; ");if(!j(e))return ee(JSON.stringify(e)??"",60);let n=[];if(e.CONFIRM!==void 0)n.push(`CONFIRM "${ee(String(e.CONFIRM),40)}"`);if(e.IF!==void 0)n.push(`IF(${ee(String(e.IF),50)})`);if(e.API!==void 0)n.push(`API ${ee(String(e.API),60)}`);if(e.METHOD!==void 0)n.push(`[${String(e.METHOD)}]`);if(e.IN!==void 0)n.push(`IN{${j(e.IN)?ee(Object.keys(e.IN).join(","),50):ee(String(e.IN),40)}}`);if(e.OUT!==void 0)n.push(`OUT\u2192${j(e.OUT)?Object.keys(e.OUT).join(","):String(e.OUT)}`);if(e.ARRAY!==void 0)n.push(`ARRAY ${ee(String(e.ARRAY),30)}${e.COL!==void 0?` COL ${ee(String(e.COL),30)}`:""}`);if(e.CALL!==void 0)n.push(`CALL ${ee(String(e.CALL),40)}`);if(e.FUN!==void 0)n.push(`FUN ${ee(String(e.FUN),40)}`);if(e.EXE!==void 0)n.push(`EXE ${ee(String(e.EXE),70)}`);if(e.MESS!==void 0)n.push(`MESS "${ee(String(e.MESS),40)}"`);if(e.MESSBOX!==void 0)n.push(`MESSBOX "${ee(String(e.MESSBOX),40)}"`);if(e.THEN!==void 0)n.push(`THEN[${_e(e.THEN,t+1)}]`);if(e.ELSE!==void 0)n.push(`ELSE[${_e(e.ELSE,t+1)}]`);if(e.CALLBACK!==void 0)n.push(`CALLBACK[${_e(e.CALLBACK,t+1)}]`);if(e.ERROROUT!==void 0)n.push(`ERROROUT\u2192${String(e.ERROROUT)}`);if(e.ERRORCALLBACK!==void 0)n.push(`ERRORCALLBACK[${_e(e.ERRORCALLBACK,t+1)}]`);if(e.CANCEL!==void 0)n.push(`CANCEL[${_e(e.CANCEL,t+1)}]`);return n.join(" ")||ee(JSON.stringify(e)??"",60)}var Gm=["v-model","v-if","v-show","v-for","label",":label","placeholder",":items",":data",":headers",":config",":option",":options","value",":value","readonly","disabled",":disabled","multiple","type"];function zm(e){let t=[],n=e.w!==void 0?`w=${String(e.w)}`:"w=?",o=typeof e.el==="string"&&e.el?e.el:e.rows?"(container)":"(text)";t.push(`${n} ${o}`);let s=j(e.attr)?e.attr:{};for(let r of Gm){let i=s[r];if(i===void 0)continue;t.push(`${r}=${typeof i==="string"?ee(i,45):Vn(i)}`)}for(let[r,i]of Object.entries(s)){if(!r.startsWith("v-on:"))continue;let a=r.slice(5);t.push(`${a}\u2192${typeof i==="string"?ee(i,60):_e(i)}`)}for(let r of["innerText","html","text"])if(typeof e[r]==="string"&&e[r])t.push(`"${ee(String(e[r]),35)}"`);return t.join(" ")}function kc(e,t,n,o){let s=o?"\u2514\u2500":"\u251C\u2500",r=t+(o?" ":"\u2502 ");if(!j(e)){n.push(`${t}${s} ${ee(JSON.stringify(e)??String(e),60)}`);return}n.push(`${t}${s} ${zm(e)}`);let i=e.innerHTML;if(Array.isArray(i))i.forEach((a,c)=>kc(a,r,n,c===i.length-1));if(Array.isArray(e.rows))vc(e.rows,r,n)}function vc(e,t,n){e.forEach((o,s)=>{if(!j(o)||!Array.isArray(o.cols)){n.push(`${t}row ${s+1}: \u274C thi\u1EBFu "cols"`);return}let r=o.cols,a=r.map((u)=>j(u)?u.w:void 0).filter((u)=>typeof u==="number"),c="";if(r.length>1&&a.length===r.length){let u=a.reduce((d,p)=>d+p,0);c=` (\u03A3w=${u}${u>12?" \u26A0 v\u01B0\u1EE3t 12":""})`}let l=[];for(let u of["v-if","v-show","prop"])if(typeof o[u]==="string")l.push(`${u}=${ee(String(o[u]),40)}`);n.push(`${t}row ${s+1}${l.length?` [${l.join(" ")}]`:""}${c}`),r.forEach((u,d)=>kc(u,t+" ",n,d===r.length-1))})}function Sc(e){if(!j(e))return"\u274C module.json g\u1ED1c kh\xF4ng ph\u1EA3i object.";let t=e,n=[],o=new Map,s=new Map,r=[],i=Array.isArray(t.data)?t.data:[];for(let d of i){if(!j(d))continue;let p=Object.keys(d);if(p.length>0&&p.every((m)=>un.has(m))){r.push(d);continue}for(let[m,w]of Object.entries(d)){if(un.has(m)){r.push({[m]:w});continue}if(m.startsWith("#"))continue;if(cs(w))s.set(m,w);else o.set(ze(m),w)}}let a=JSON.stringify({c:t.controls,w:t.watch,s:t.set,a:[...s.values()],u:r})??"",c=(d)=>new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(a);n.push("\u2550\u2550\u2550 LAYOUT (controls) \u2550\u2550\u2550");let l=Array.isArray(t.controls)?t.controls:[];if(l.length===0)n.push("(kh\xF4ng c\xF3 controls)");let u=[];if(l.forEach((d,p)=>{if(!j(d)){n.push(`[container ${p+1}] \u274C kh\xF4ng ph\u1EA3i object`);return}let m=typeof d.el==="string"?d.el:"",w=m==="hidden-container",y=j(d.attr)?d.attr:{},v=[];if(m)v.push(`el=${m}`);if(typeof d.prop==="string")v.push(`prop="${ee(d.prop,40)}"`);for(let E of["v-if","v-show","v-model"]){let I=d[E]??y[E];if(typeof I==="string")v.push(`${E}=${ee(I,40)}`)}if(n.push(`[container ${p+1}${w?" \u2014 DIALOG":""}] ${v.join(" ")||"(m\u1EB7c \u0111\u1ECBnh)"}`),w){let E=d["v-model"]??y["v-model"];if(typeof E==="string")u.push({vModel:ze(E),where:`container ${p+1}`});else u.push({vModel:"",where:`container ${p+1}`})}if(Array.isArray(d.rows))vc(d.rows," ",n);else n.push(' \u274C thi\u1EBFu "rows"')}),n.push("","\u2550\u2550\u2550 STATE (data) \u2550\u2550\u2550"),o.size===0)n.push("(kh\xF4ng c\xF3 state)");for(let[d,p]of o){let m=c(d);n.push(`${d} = ${Vn(p)}${m?"":" \u26A0 kh\xF4ng th\u1EA5y d\xF9ng \u1EDF controls/watch/actions"}`)}if(n.push("","\u2550\u2550\u2550 ACTIONS \u2550\u2550\u2550"),s.size===0&&r.length===0)n.push("(kh\xF4ng c\xF3 action)");for(let[d,p]of s){let m=JSON.stringify({c:t.controls,w:t.watch,s:t.set,u:r,a:[...s.entries()].filter(([y])=>y!==d).map(([,y])=>y)})??"",w=new RegExp(`\\b${d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`).test(m);n.push(`${d}: ${_e(p)}${w?"":" \u26A0 kh\xF4ng th\u1EA5y n\u01A1i g\u1ECDi (CALL/watch/v-on)"}`)}if(r.forEach((d,p)=>{n.push(`(auto-startup ${p+1}): ${_e(d)}`)}),j(t.watch)&&Object.keys(t.watch).length>0){n.push("","\u2550\u2550\u2550 WATCH \u2550\u2550\u2550");let d=(p,m)=>{for(let[w,y]of Object.entries(p)){if(w==="deep-watch"&&j(y)){d(y,"deep ");continue}n.push(`${m}${w} \u2192 ${_e(y)}`)}};d(t.watch,"")}if(u.length>0){n.push("","\u2550\u2550\u2550 DIALOGS \u2550\u2550\u2550");let d=JSON.stringify(t)??"";for(let p of u){if(!p.vModel){n.push(`${p.where}: \u26A0 hidden-container kh\xF4ng c\xF3 v-model \u2014 kh\xF4ng \u0111i\u1EC1u khi\u1EC3n m\u1EDF/\u0111\xF3ng \u0111\u01B0\u1EE3c`);continue}let m=p.vModel.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),w=new RegExp(`\\b${m}\\b\\s*=\\s*true`).test(d);n.push(`${p.where}: v-model=${p.vModel}${w?` \u2014 c\xF3 l\u1EC7nh m\u1EDF (${p.vModel} = true)`:` \u26A0 kh\xF4ng th\u1EA5y l\u1EC7nh m\u1EDF "${p.vModel} = true" \u1EDF \u0111\xE2u`}`)}}if(j(t.set)&&Object.keys(t.set).length>0){n.push("","\u2550\u2550\u2550 SET (override) \u2550\u2550\u2550");for(let[d,p]of Object.entries(t.set))n.push(`${d} = ${typeof p==="string"?ee(p,60):Vn(p)}`)}return n.push("",`Th\u1ED1ng k\xEA: ${o.size} state \xB7 ${s.size} named action \xB7 ${r.length} auto-startup \xB7 ${l.length} container \xB7 ${u.length} dialog`),n.join(`
156
+ `)}var Xm=[[/c\u0169 (\d+) ng\u00E0y/g,"$1 days old"],[/c\u0169 (\d+) gi\u1EDD/g,"$1 h old"],[/\(control trong f-table \u2014 d\u00F9ng row (\d+) c\u1EE7a /g,"(control in f-table; using row $1 of "],[/(\u2718 STEP \d+) l\u1ED7i: /g,"$1 error: "]],Ym={project_sync:"fui project sync",module_get:"fui module get",file_import_list:"fui import list"},Qm={'khai b\xE1o "function(...)"':'"function(...)" declaration','khai b\xE1o "function ten(...)"':'"function name(...)" declaration','l\u1EC7nh "return"':'"return" statement','khai b\xE1o "var"':'"var" declaration','khai b\xE1o "let"':'"let" declaration','khai b\xE1o "const"':'"const" declaration','d\u1EA5u ";" n\u1ED1i nhi\u1EC1u c\xE2u l\u1EC7nh':'";" joining statements','" (t\u1EA1i ':'" (at ',") ch\u1EE9a ":") contains "," \u2014 gi\xE1 tr\u1ECB binding \u0111i th\u1EB3ng v\xE0o template compiler c\u1EE7a Vue, ":" \u2014 Vue compiles a binding as ONE expression ","ch\u1EC9 nh\u1EADn M\u1ED8T BI\u1EC2U TH\u1EE8C thu\u1EA7n (object/array literal, ternary, chu\u1ED7i .map()/.filter() arrow, truy c\u1EADp thu\u1ED9c t\xEDnh). ":"(literal, ternary, .map()/.filter() arrow, property access). ","Statement \u1EDF \u0111\xE2y l\xE0m compile template h\u1ECFng \u2192 TRANG TR\u1EAENG m\xE0 kh\xF4ng c\xF3 JS error hay [Vue warn] n\xE0o. ":"A statement breaks the template: BLANK PAGE with no JS error or [Vue warn]. ",'C\xE1ch s\u1EEDa: \u0111\u01B0a logic v\xE0o m\u1ED9t h\xE0m trong script.js (function buildX(){ \u2026 vueData.x = \u2026; }), g\u1ECDi qua "EXE": "buildX()" ':'Fix: move the logic into a script.js function (function buildX(){ \u2026 vueData.x = \u2026; }), call it with "EXE": "buildX()" ','trong CALLBACK c\u1EE7a action n\u1EA1p d\u1EEF li\u1EC7u, r\u1ED3i "':'in the load action CALLBACK, and point "','" ch\u1EC9 tr\u1ECF t\u1EDBi bi\u1EBFn vueData \u0111\xF3 (vd "':'" at that vueData var (e.g. "',"Chi ti\u1EBFt: references/advanced-techniques.md.":"See: fui skill ref references/advanced-techniques.md.","(chu\u1EA9n m\u1EB7c \u0111\u1ECBnh fui/DESIGN.md \u2014 ch\u1EC9 gi\u1EEF nguy\xEAn n\u1EBFu user y\xEAu c\u1EA7u r\xF5 ki\u1EC3u kh\xE1c)":"(fui/DESIGN.md default; keep only if the user asked for another style)","Module g\xE1c quy\u1EC1n b\u1EB1ng ":"Module gates on "," m\xE3 literal (":" literal right codes (",") nh\u01B0ng ch\u01B0a c\xF3 cache _db/rights.json ":") but _db/rights.json is not cached, ","\u0111\u1EC3 \u0111\u1ED1i chi\u1EBFu \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c m\xE3 n\xE0o c\xF3 th\u1EADt. M\xE3 kh\xF4ng t\u1ED3n t\u1EA1i l\xE0m rightTest() lu\xF4n tr\u1EA3 false: ":"so none can be checked. An unknown code makes rightTest() always false: ","control/menu bi\u1EBFn m\u1EA5t im l\u1EB7ng, kh\xF4ng l\u1ED7i JS. Ch\u1EA1y right_function_list + right_system_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i validate l\u1EA1i.":"the control/menu silently disappears. Run fui right function list and fui right system list (once per project), then validate again.",'" ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3 ':'" is not defined for the project (_db/rights.json has '," m\xE3: ":" codes: "," m\xE3). ":" codes). ","rightTest() s\u1EBD lu\xF4n tr\u1EA3 false \u2192 control/menu g\xE1c b\u1EB1ng m\xE3 n\xE0y bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng b\xE1o l\u1ED7i. ":"rightTest() is always false, so controls gated by it silently disappear. ","T\u1EA1o m\xE3: right_function_new({ functionCode: ":"Create: fui right function new --code ",', functionName: "\u2026" }); ho\u1EB7c ch\u1EA1y right_function_list n\u1EBFu m\xE3 \u0111\xE3 c\xF3 tr\xEAn server m\xE0 cache c\u0169.':' --name "\u2026", or fui right function list if the cache is stale.',"ho\u1EB7c ch\u1EA1y right_function_list n\u1EBFu m\xE3 \u0111\xE3 c\xF3 tr\xEAn server m\xE0 cache c\u0169.":"or fui right function list if the cache is stale."," ch\u01B0a \u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a cho project (_db/rights.json c\xF3: ":" is not defined for the project (_db/rights.json has: ","(ch\u01B0a m\u1EE9c n\xE0o)":"(none)"," \u2014 m\u1EE9c 9 lu\xF4n h\u1EE3p l\u1EC7":"; 9 is always valid","Kh\xF4ng ai \u0111\u01B0\u1EE3c c\u1EA5p m\u1EE9c n\xE0y th\xEC control g\xE1c b\u1EB1ng n\xF3 bi\u1EBFn m\u1EA5t im l\u1EB7ng. ":"Nobody holds this level, so gated controls silently disappear. ","\u0110\u1ECBnh ngh\u0129a: right_system_new({ sysRight: ":"Define: fui right system new ",', note: "\u2026" }); ho\u1EB7c ch\u1EA1y right_system_list n\u1EBFu cache c\u0169.':' --note "\u2026", or fui right system list if the cache is stale.','Action g\u1ECDi API "':'Action calling API "',") c\xF3 v\u1EBB l\xE0 thao t\xE1c X\xD3A nh\u01B0ng thi\u1EBFu CONFIRM \u2014 m\u1ECDi action x\xF3a ph\u1EA3i h\u1ECFi x\xE1c nh\u1EADn.":") looks like a DELETE without CONFIRM; every delete action must confirm.","N\xFAt/m\u1EE5c x\xF3a (t\u1EA1i ":"Delete button/item (at ",') \u0111ang m\xE0u "':') is color "','" \u2014 n\xFAt x\xF3a d\xF9ng m\xE0u "warning" (cam), kh\xF4ng d\xF9ng error/\u0111\u1ECF.':'"; delete buttons use "warning" (orange), not error/red.',"v-toolbar (t\u1EA1i ":"v-toolbar (at ",') c\xF3 color/dark \u2014 toolbar ti\xEAu \u0111\u1EC1 ph\u1EA3i "dense flat" c\xF9ng m\xE0u n\u1EC1n v\u1EDBi th\xE2n dialog, kh\xF4ng t\xF4 m\xE0u/dark ri\xEAng.':') has color/dark; a title toolbar must be "dense flat" on the dialog background, no own color/dark.',"Dialog (t\u1EA1i ":"Dialog (at "," \u2014 b\u1ED9 width chu\u1EA9n: 400 / 550 / 600 / 650 / 800.":"; standard widths: 400 / 550 / 600 / 650 / 800.","v-card trong dialog (t\u1EA1i ":"v-card in dialog (at ",') c\xF3 elevation/outlined \u2014 b\u1ECF \u0111i: v-dialog \u0111\xE3 t\u1EF1 c\xF3 b\xF3ng \u0111\u1ED5, th\xEAm n\u1EEFa l\xE0 b\xF3ng ch\u1ED3ng b\xF3ng. v-card ch\u1EC9 c\u1EA7n class "pa-0" (tri\u1EC7t \u0111\u1EC7m \u0111\u1EC3 toolbar ti\xEAu \u0111\u1EC1 s\xE1t m\xE9p).':') has elevation/outlined; remove it (v-dialog already casts a shadow). v-card needs only class "pa-0".',"Control (t\u1EA1i ":"Control (at ",') mang class "flex-md-grow-0 ml-2" \u2014 d\u1EA5u hi\u1EC7u pattern label-div r\u1EDDi \u0111\xE3 lo\u1EA1i b\u1ECF; form d\xF9ng floating label ("label" trong attr c\u1EE7a field).':') has class "flex-md-grow-0 ml-2", the retired separate label-div pattern; forms use floating labels ("label" in the field attr).'," trong dialog (t\u1EA1i ":" in dialog (at ",') thi\u1EBFu "label" \u2014 form dialog d\xF9ng floating label.':') lacks "label"; dialog forms use floating labels.'," (t\u1EA1i ":" (at ",") \u2014 ph\u1EA3n h\u1ED3i/th\xF4ng b\xE1o d\xF9ng h\u1EC7 th\u1ED1ng c\xF3 s\u1EB5n c\u1EE7a FUI: MESS (toast) / MESSBOX (h\u1ED9p tho\u1EA1i) trong action, CONFIRM \u0111\u1EC3 x\xE1c nh\u1EADn \u2014 kh\xF4ng t\u1EF1 d\u1EF1ng ":"); use FUI feedback instead: MESS (toast) / MESSBOX (dialog) in actions, CONFIRM to confirm; do not build "," trong controls.":" in controls.",') kh\xF4ng c\xF3 ":height" \u2014 b\u1EA3ng s\u1EBD t\u1EF1 gi\xE3n theo s\u1ED1 d\xF2ng v\xE0 l\xE0m c\u1EA3 TRANG cu\u1ED9n. B\u1EA3ng data ch\xEDnh n\xEAn \u0111\u1EB7t `":height": -16` (fill t\u1EDBi \u0111\xE1y m\xE0n h\xECnh, ch\u1EEBa 16px \u2014 component t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt, kh\xF4ng c\u1EA7n t\xEDnh tay); trong dialog d\xF9ng s\u1ED1 d\u01B0\u01A1ng (px c\u1ED1 \u0111\u1ECBnh, vd 360). B\u1EA3ng ng\u1EAFn/lookup ch\u1EE7 \xFD t\u1EF1 gi\xE3n th\xEC b\u1ECF qua.':') has no ":height", so it grows with its rows and scrolls the whole PAGE. Main data table: `":height": -16` (fills to the screen bottom, self-measured); in a dialog a positive px (e.g. 360). Skip for short lookup tables.',') t\xEDnh ":height" th\u1EE7 c\xF4ng theo viewport (`':') computes ":height" from the viewport (`',"`) \u2014 kh\xF4ng c\u1EA7n n\u1EEFa: ":"`); not needed: ",' t\u1EF1 \u0111o v\u1ECB tr\xED th\u1EADt c\u1EE7a b\u1EA3ng sau layout v\xE0 t\u1EF1 c\u1EADp nh\u1EADt khi resize. Thay b\u1EB1ng `":height": -16` (s\u1ED1 \xE2m = kho\u1EA3ng ch\u1EEBa \u1EDF \u0111\xE1y).':' measures itself after layout and on resize. Use `":height": -16` (negative = bottom gap).','N\xFAt "':'Button "','" trong v-card-actions (t\u1EA1i ':'" in v-card-actions (at ',") \u2014 dialog chu\u1EA9n ch\u1EC9 d\xF9ng icon close tr\xEAn toolbar, kh\xF4ng th\xEAm n\xFAt \u0110\xF3ng \u1EDF actions.":"); standard dialogs close via the toolbar icon only, no Close button in actions.",' c\xF3 "w": ':' has "w": '," \u2014 v\xF9ng ch\u1EBFt 13\u201324: V2 hi\u1EC3u l\xE0 ":" \u2014 dead zone 13\u201324: V2 reads ",'px, c\xF2n V3 sinh class "v-col-':'px, V3 emits class "v-col-','" m\xE0 Vuetify 3 kh\xF4ng \u0111\u1ECBnh ngh\u0129a (ch\u1EC9 c\xF3 .v-col-1 \u2026 .v-col-12) n\xEAn c\u1ED9t KH\xD4NG c\xF3 b\u1EC1 r\u1ED9ng n\xE0o, r\u01A1i v\u1EC1 chia \u0111\u1EC1u. D\xF9ng 1\u201312 (chia c\u1ED9t) ho\u1EB7c >= ':'", which Vuetify 3 lacks (only .v-col-1 \u2026 .v-col-12), so the column gets NO width. Use 1\u201312 (grid) or >= ',' (px, k\xE8m `"col": { "class": "shrink" }`).':' (px, with `"col": { "class": "shrink" }`).',' (px) k\xE8m `"col": { "shrink": ... }` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn n\xF3 tr\u01A1 v\xE0 c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng. Vi\u1EBFt `"col": { "class": "shrink" }` \u0111\u1EC3 \u0111\xFAng \u1EDF c\u1EA3 hai framework.':' (px) with `"col": { "shrink": ... }`: a bare v-flex prop (Vuetify 2), inert in Vuetify 3, so the column still grows. Write `"col": { "class": "shrink" }` for both.'," \u2014 s\u1ED1 >= ":" \u2014 >= "," ngh\u0129a l\xE0 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh px, nh\u01B0ng c\u1ED9t grid m\u1EB7c \u0111\u1ECBnh flex-grow:1 n\xEAn flex-basis ":" means fixed px, but grid columns default to flex-grow:1, so flex-basis ",'px KH\xD4NG c\xF3 t\xE1c d\u1EE5ng: c\u1ED9t v\u1EABn gi\xE3n h\u1EBFt ch\u1ED7 tr\u1ED1ng (\u0111o th\u1EADt: w=300 ra 687px \u1EDF V2, 738px \u1EDF V3). Th\xEAm `"col": { "class": "shrink" }`.':'px has NO effect and the column still grows (measured: w=300 gives 687px V2, 738px V3). Add `"col": { "class": "shrink" }`.'," \u2014 `v-spacer` m\xE0 c\xF3 `shrink` th\xEC co l\u1EA1i c\xF2n 0px (\u0111o th\u1EADt: 24px \u1EDF V3, 0px \u1EDF V2), kh\xF4ng \u0111\u1EA9y \u0111\u01B0\u1EE3c nh\xF3m sau sang ph\u1EA3i n\u1EEFa v\xE0 c\u1EA3 h\xE0ng d\u1ED3n v\u1EC1 tr\xE1i. B\u1ECF `shrink` kh\u1ECFi ri\xEAng c\u1ED9t n\xE0y.":" \u2014 `v-spacer` with `shrink` collapses to 0px and no longer pushes the next group right. Remove `shrink` from this column.",' n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi `v-spacer`, `w` r\u1ED7ng v\xE0 ch\u1EC9 c\xF3 `"col": { "shrink": ... }` \u2014 d\u1EA1ng thu\u1ED9c t\xEDnh tr\u1EA7n l\xE0 prop c\u1EE7a v-flex (Vuetify 2); Vuetify 3 kh\xF4ng c\xF3 prop \u0111\xF3 n\xEAn c\u1ED9t v\u1EABn gi\xE3n v\xE0 chi\u1EBFm m\u1ED9t ph\u1EA7n \u0111\u1EC1u. Vi\u1EBFt `"col": "shrink"` (ho\u1EB7c `{ "class": "shrink" }`).':' shares a row with `v-spacer`, has empty `w` and only `"col": { "shrink": ... }`: a bare v-flex prop (Vuetify 2), inert in Vuetify 3, so the column grows. Write `"col": "shrink"` (or `{ "class": "shrink" }`).',' n\u1EB1m c\xF9ng h\xE0ng v\u1EDBi `v-spacer` nh\u01B0ng thi\u1EBFu `shrink` \u2014 `w` r\u1ED7ng KH\xD4NG c\xF3 ngh\u0129a "t\u1EF1 co theo n\u1ED9i dung": runtime b\u1ECDc ph\u1EA7n b\u1EC1 r\u1ED9ng trong `if (c.w)` n\xEAn c\u1ED9t n\xE0y kh\xF4ng c\xF3 class grid l\u1EABn flex-basis, c\xF2n tr\u01A1 `flex-grow: 1` v\xE0 \u0111\u01B0\u1EE3c CHIA PH\u1EA6N TR\u1ED0NG y nh\u01B0 `v-spacer`. H\u1EC7 qu\u1EA3: control b\u1ECB k\xE9o d\xE3n, h\u1EDF kho\u1EA3ng gi\u1EEFa c\xE1c n\xFAt, nh\xF3m cu\u1ED1i h\xE0ng kh\xF4ng s\xE1t m\xE9p ph\u1EA3i. Chu\u1EA9n cho h\xE0ng nhi\u1EC1u control: M\u1ECCI c\u1ED9t `"col": "shrink"`, tr\u1EEB \u0111\xFAng `v-spacer`.':' shares a row with `v-spacer` but lacks `shrink`: empty `w` is NOT fit-content, the column keeps `flex-grow: 1` and SPLITS free space like `v-spacer` (stretched controls, gaps, last group off the right edge). In multi-control rows EVERY column is `"col": "shrink"` except `v-spacer`.'," g\u1ED9p ":" puts "," l\u1EC7nh trong M\u1ED8T object `{}` (":" commands in ONE object `{}` (",") \u2014 ch\u1EC9 `":"); only `","` ch\u1EA1y, c\xF2n ":"` runs, ",' b\u1ECB b\u1ECF IM L\u1EB6NG (kh\xF4ng JS error, kh\xF4ng [Vue warn]). `vueAction` d\xF2 key l\u1EC7nh theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh API \u2192 CALL \u2192 EXE \u2192 FUN \u2192 IF \u2192 MESS \u2192 MESSBOX v\xE0 `return` ngay \u1EDF nh\xE1nh \u0111\u1EA7u ti\xEAn kh\u1EDBp, n\xEAn th\u1EE9 t\u1EF1 vi\u1EBFt kh\xF4ng c\xF3 t\xE1c d\u1EE5ng. Nhi\u1EC1u l\u1EC7nh c\xF3 th\u1EE9 t\u1EF1 tr\u01B0\u1EDBc sau th\xEC ph\u1EA3i l\xE0 M\u1EA2NG, m\u1ED7i l\u1EC7nh m\u1ED9t object: `[{ "':' are SILENTLY dropped: `vueAction` checks API \u2192 CALL \u2192 EXE \u2192 FUN \u2192 IF \u2192 MESS \u2192 MESSBOX and runs only the first match. Ordered commands need an ARRAY, one per object: `[{ "'," c\xF3 l\u1EC7nh `":" has command `","` n\u1EB1m chung object v\u1EDBi key g\xE1n d\u1EEF li\u1EC7u ":"` in the same object as data keys "," \u2014 fragment g\xE1n ch\u1EA1y \u1EDF nh\xE1nh CU\u1ED0I c\u1EE7a `vueAction`, nh\xE1nh \u0111\xF3 ch\u1EC9 t\u1EDBi l\u01B0\u1EE3t khi KH\xD4NG key l\u1EC7nh n\xE0o kh\u1EDBp, n\xEAn c\xE1c key n\xE0y kh\xF4ng bao gi\u1EDD \u0111\u01B0\u1EE3c g\xE1n. T\xE1ch th\xE0nh m\u1EA3ng: `[{ ":" \u2014 `vueAction` assigns only when NO command key matches, so these keys are never set. Split into an array: `[{ ","module.json g\u1ED1c ph\u1EA3i l\xE0 m\u1ED9t object v\u1EDBi c\xE1c kh\u1ED1i data/watch/controls/set.":"module.json root must be an object with data/watch/controls/set.",'Kh\u1ED1i c\u1EA5p 1 kh\xF4ng chu\u1EA9n: "':'Unknown top-level key: "','" \u2014 module.json ch\u1EC9 c\xF3 data/watch/controls/set (xem module-json-anatomy.md).':'"; module.json has only data/watch/controls/set (see module-json-anatomy.md).','"data" ph\u1EA3i l\xE0 M\u1EA2NG (array), hi\u1EC7n l\xE0 ':'"data" must be an array, got ','"watch" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ':'"watch" must be an object, got ','"controls" ph\u1EA3i l\xE0 M\u1EA2NG container, hi\u1EC7n l\xE0 ':'"controls" must be an array of containers, got ','"set" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ':'"set" must be an object, got ',"data[] ch\u1EE9a ph\u1EA7n t\u1EED kh\xF4ng ph\u1EA3i object: ":"data[] has a non-object item: ",") kh\xF4ng kh\u1EDBp named action n\xE0o trong data[]. Named actions hi\u1EC7n c\xF3: ":") matches no named action in data[]. Named actions: ","(kh\xF4ng c\xF3)":"(none)",'] ph\u1EA3i l\xE0 container c\xF3 "rows" (grid b\u1EAFt bu\u1ED9c: container > rows > cols).':'] must be a container with "rows" (container > rows > cols).','] ph\u1EA3i c\xF3 "cols" (m\u1EA3ng Control Object).':'] needs "cols" (array of Control Objects).',') thi\u1EBFu "w" \u2014 b\u1EAFt bu\u1ED9c cho item trong cols.':') lacks "w" (required in cols).','"children" kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 (t\u1EA1i ':'"children" is not supported (at ',') \u2014 d\xF9ng "innerHTML" (m\u1EA3ng Control Object) \u0111\u1EC3 l\u1ED3ng.':'); nest with "innerHTML" (array of Control Objects).',") kh\xF4ng c\xF3 trong registry FUI (component-quickref.md) v\xE0 kh\xF4ng th\u1EA5y trong components local \u2014 ki\u1EC3m tra ch\xEDnh t\u1EA3 ho\u1EB7c ch\u1EA1y component_list.":") is neither in the FUI registry (component-quickref.md) nor a local component; check spelling or run fui component list.",") kh\xF4ng c\xF3 trong danh s\xE1ch t-* h\u1EE3p l\u1EC7: ":") is not a valid t-* cell: ",") ch\u01B0a th\u1EA5y file .vue/local metadata \u2014 t\u1EA1o component tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y component_list.":") has no .vue/local metadata; create it first or run fui component list.",') \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 shorthand "@"; d\xF9ng "v-on:':'); module.json has no "@" shorthand; use "v-on:',') \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 slot shorthand "#"; d\xF9ng "v-slot:':'); module.json has no "#" slot shorthand; use "v-slot:',") \u2014 module th\u1EF1c t\u1EBF g\u1EA7n nh\u01B0 kh\xF4ng d\xF9ng style ngo\xE0i width (controls-styling-vocabulary.md). C\xE2n nh\u1EAFc b\u1ECF.":"); real modules rarely style beyond width (controls-styling-vocabulary.md). Consider removing it.",') \u2014 bi\u1EBFn g\u1ED1c "':'); root var "','" ch\u01B0a khai b\xE1o trong data[]. Khai b\xE1o state tr\u01B0\u1EDBc \u0111\u1EC3 reactive ho\u1EA1t \u0111\u1ED9ng \u0111\xFAng.':'" is not declared in data[]; declare it so reactivity works.',') \u2014 bi\u1EBFn "':') \u2014 var "','" ch\u01B0a khai b\xE1o trong data[], watcher s\u1EBD kh\xF4ng bao gi\u1EDD ch\u1EA1y.':'" is not declared in data[]; the watcher never runs.','" c\u1EA7n import: ':'" needs import: '," \u2014 ch\u01B0a th\u1EA5y trong _imports.json (module l\u1EABn project). D\xF9ng file_import_list/file_import_new (xem component-quickref.md).":", missing from _imports.json (module and project). Use fui import list / fui import new (see component-quickref.md).",") \u2014 kh\xF4ng t\xECm th\u1EA5y spAPI_":"); no spAPI_"," trong schema local. Deploy SP tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y db_schema_get (b\u1ECF qua n\u1EBFu API thu\u1ED9c backend kh\xE1c).":" in the local schema. Deploy the SP or run fui schema pull (ignore if another backend)."," alias \u0111\u01B0\u1EE3c g\u1ECDi nh\u01B0ng ch\u01B0a c\xF3 k\u1EBFt n\u1ED1i local \u2014 KH\xD4NG \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c t\xEAn SP: ":" called alias(es) have no local connection, so SP names are NOT checked: ",'K\u1EBFt n\u1ED1i t\u1EEBng c\xE1i b\u1EB1ng db_connect({ apiName: "':"Connect each: fui db add ",'", apiDomain: "\u2026", dbToken: "\u2026" }) \u0111\u1EC3 b\u01B0\u1EDBc ki\u1EC3m n\xE0y ch\u1EA1y (b\u1ECF qua n\u1EBFu alias thu\u1ED9c backend c\u1EE7a nh\xF3m kh\xE1c).':" -p <project> --token - --api-domain \u2026 (ignore if another team's backend).","/schema.json sai \u0111\u1ECBnh d\u1EA1ng (b\u1EA3n cache c\u0169) \u2014 b\u1ECF qua b\u01B0\u1EDBc \u0111\u1ED1i chi\u1EBFu t\xEAn API v\u1EDBi stored procedure \u1EDF ":"/schema.json has an old format; API names were not checked against SPs for ","c\xE1c k\u1EBFt n\u1ED1i n\xE0y":"these connections","k\u1EBFt n\u1ED1i n\xE0y":"this connection",". Ch\u1EA1y db_schema_get \u0111\u1EC3 l\u1EA5y b\u1EA3n m\u1EDBi.":". Run fui schema pull.","module.json r\u1ED7ng ho\u1EB7c kh\xF4ng t\u1ED3n t\u1EA1i trong ":"module.json is empty or missing in ","module.json c\xF3 l\u1ED7i JSON syntax: ":"module.json JSON syntax error: ","\u2705 module.json h\u1EE3p l\u1EC7 \u2014 kh\xF4ng ph\xE1t hi\u1EC7n l\u1ED7i ng\u1EEF ngh\u0129a.":"\u2705 module.json valid: no semantic errors."," l\u1ED7i (s\u1EBD h\u1ECFng khi ch\u1EA1y):":" error(s) (break at runtime):"," c\u1EA3nh b\xE1o (n\xEAn xem l\u1EA1i):":" warning(s) (review):",' bi\u1EC3u th\u1EE9c \u0111\u1ED9ng (`:attr` ph\u1EE9c t\u1EA1p, CALL/API/v-model/OUT d\u1EA1ng `{{\u2026}}`/g\u1ECDi h\xE0m/template string) KH\xD4NG ki\u1EC3m t\u0129nh \u0111\u01B0\u1EE3c \u2014 bi\u1EBFn b\xEAn trong ch\xFAng kh\xF4ng \u0111\u01B0\u1EE3c \u0111\u1ED1i chi\u1EBFu v\u1EDBi data[]. "H\u1EE3p l\u1EC7" \u1EDF \u0111\xE2y KH\xD4NG c\xF3 ngh\u0129a l\xE0 ch\u1EA1y \u0111\xFAng: d\xF9ng module_simulate \u0111\u1EC3 ph\u1EE7 ph\u1EA7n n\xE0y.':' dynamic expression(s) (complex `:attr`, `{{\u2026}}` CALL/API/v-model/OUT, calls, template strings) are NOT checked statically. "Valid" does NOT mean it runs: use fui module simulate.',"Th\u1ED1ng k\xEA: ":"Stats: "," API ref \u0111\u1ED1i chi\u1EBFu schema":" API ref checked against schema"," CALL xuy\xEAn c\u1EEDa s\u1ED5 (thu\u1ED9c module kh\xE1c, kh\xF4ng ki\u1EC3m \u0111\u01B0\u1EE3c t\u1EEB \u0111\xE2y)":" cross-window CALL (other module, not checked)"," m\xE3 quy\u1EC1n \u0111\u1ED1i chi\u1EBFu _db/rights.json":" right code checked against _db/rights.json","Ch\u01B0a c\xF3 cache _db/rights.json \u2014 KH\xD4NG ki\u1EC3m \u0111\u01B0\u1EE3c ":"_db/rights.json not cached: cannot check whether "," m\xE3 FunctionRight (":" FunctionRight code(s) (","c\xF3 th\u1EADt hay kh\xF4ng. Preview c\u1EA5p s\u1EB5n \u0111\xFAng nh\u1EEFng m\xE3 n\xE0y n\xEAn control v\u1EABn hi\u1EC7n \u1EDF \u0110\xC2Y; user th\u1EADt ch\u1EC9 th\u1EA5y n\u1EBFu m\xE3 \u0111\xE3 ":"exist. The preview grants them, so controls show HERE; real users see them only if the codes are ","\u0111\u01B0\u1EE3c \u0111\u1ECBnh ngh\u0129a. Ch\u1EA1y right_function_list (m\u1ED9t l\u1EA7n cho m\u1ED7i project) r\u1ED3i render l\u1EA1i.":"defined. Run fui right function list (once per project), then render again.","\uD83D\uDEA8 FunctionRight CH\u01AFA \u0110\u01AF\u1EE2C \u0110\u1ECANH NGH\u0128A tr\xEAn h\u1EC7 th\u1ED1ng acc: ":"\uD83D\uDEA8 FunctionRight NOT DEFINED in acc: "," \u2014 preview c\u1EA5p s\u1EB5n n\xEAn control v\u1EABn ":" \u2014 the preview grants them, so controls ","hi\u1EC7n \u1EDF \u0110\xC2Y, nh\u01B0ng ch\u1EA1y th\u1EADt th\xEC rightTest() lu\xF4n tr\u1EA3 false \u2192 control/menu bi\u1EBFn m\u1EA5t m\xE0 kh\xF4ng c\xF3 l\u1ED7i JS n\xE0o. ":"show HERE, but live rightTest() is false and they silently disappear. ",'T\u1EA1o m\xE3: right_function_new({ functionCode: \u2026, functionName: "\u2026" }); ho\u1EB7c right_function_list n\u1EBFu cache c\u0169.':'Create: fui right function new --code \u2026 --name "\u2026", or fui right function list if the cache is stale.',"\uD83D\uDEA8 C\u1EA4U TR\xDAC MOCK CH\u01AFA \u0110\u01AF\u1EE2C KI\u1EC2M CH\u1EE8NG \u2014 nghi ng\u1EDD t\u1EF1 suy di\u1EC5n thay v\xEC \u0111\u1ECDc t\u1EEB SP th\u1EADt:":"\uD83D\uDEA8 UNVERIFIED MOCK SHAPE: likely guessed instead of read from the real SP:",'"] kh\u1EDBp t\xEAn SP C\xD3 TH\u1EACT "':'"] matches REAL SP "','" trong schema local, nh\u01B0ng SP \u0111\xF3 ch\u01B0a t\u1EEBng \u0111\u01B0\u1EE3c db_sp_verify \u0111\u1ECDc th\xE2n trong phi\xEAn n\xE0y.':'", whose body fui sp verify has not read in this session.',' \u2192 Ch\u1EA1y db_sp_verify({ name: "':" \u2192 Run fui sp verify ",'" }) \u0111\u1EC3 \u0111\u1ECDc th\xE2n SP th\u1EADt r\u1ED3i COPY NGUY\xCAN V\u0102N snippet apiMocks n\xF3 sinh s\u1EB5n \u2014 kh\xF4ng t\u1EF1 \u0111o\xE1n t\xEAn c\u1ED9t/shape cho SP \u0111\xE3 t\u1ED3n t\u1EA1i, k\u1EBFt qu\u1EA3 m\xF4 ph\u1ECFng s\u1EBD l\u1EC7ch v\u1EDBi th\u1EF1c t\u1EBF.':" and copy its apiMocks snippet VERBATIM; do not guess columns/shape of an existing SP.",") \u2014 g\u1ECDi tAPI th\u1EADt th\xE0nh c\xF4ng":"): live tAPI call OK",") g\u1ECDi th\u1EADt th\u1EA5t b\u1EA1i (":") live call failed (",") \u2014 r\u01A1i xu\u1ED1ng cache/t\u1EF1 sinh":"); fell back to cache/generated",") c\xF3 l\u1EC7nh ghi \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh":") writes: NOT called live, cache/generated used",") c\xF3 \u0111o\u1EA1n \u0111\u1ECDc t\u0129nh kh\xF4ng nh\xECn xuy\xEAn qua \u0111\u01B0\u1EE3c (SQL \u0111\u1ED9ng / g\u1ECDi SP kh\xE1c) \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh":") has opaque reads (dynamic SQL / nested SP): NOT called live, cache/generated used",") ch\u01B0a c\xF3 th\xE2n SP local \u0111\u1EC3 ph\xE2n lo\u1EA1i an to\xE0n \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh":") has no local body to classify: NOT called live, cache/generated used",") \u0111\xE3 \u0111\u1EA1t tr\u1EA7n ":") hit the cap of "," l\u01B0\u1EE3t g\u1ECDi th\u1EADt/l\u01B0\u1EE3t simulate \u2014 d\xF9ng cache/t\u1EF1 sinh":" live calls per run; cache/generated used",") \u2014 d\xF9ng _testdata cache (ngu\u1ED3n: ":"): _testdata cache (source: ",", l\u01B0u l\xFAc ":", saved ",") \u2014 t\u1EF1 sinh mock t\u1EEB c\u1EA5u tr\xFAc SELECT, \u0111\xE3 l\u01B0u cache":"): mock generated from the SELECT shape, cached","\u2026 (trace b\u1ECB c\u1EAFt \u2014 qu\xE1 d\xE0i)":"\u2026 (trace truncated)",") \u2014 stub, kh\xF4ng m\xF4 ph\u1ECFng UI th\u1EADt":"): stub, UI not simulated"," ch\u01B0a h\u1ED7 tr\u1EE3 trong simulator \u2014 tr\u1EA3 undefined":" unsupported in the simulator: undefined"," \u2014 stub (sub-window kh\xF4ng m\xF4 ph\u1ECFng)":": stub (sub-window not simulated)","(gi\u1EA3 l\u1EADp user \u0111\xF3ng dialog \u2192 ch\u1EA1y onclose)":"(simulated dialog close \u2192 onclose)",'" \u2192 OK (m\u1EB7c \u0111\u1ECBnh simulator)':'" \u2192 OK (simulator default)','" \u2192 H\u1EE6Y':'" \u2192 CANCEL','\u0111\u1ECDc cross-window "':'cross-window read "','" \u2014 kh\xF4ng m\xF4 ph\u1ECFng \u0111\u01B0\u1EE3c, g\xE1n undefined':'": not simulated, undefined'," (kh\xF4ng m\xF4 ph\u1ECFng)":" (not simulated)",'" undefined \u2014 CALL tr\u1ECF t\u1EDBi action kh\xF4ng t\u1ED3n t\u1EA1i?':'" undefined: CALL to a missing action?',"L\u1ED7i khi ch\u1EA1y action: ":"Action error: ",'" kh\xF4ng t\u1ED3n t\u1EA1i (simulator ch\u1EC9 c\xF3 c\xE1c h\xE0m FUI chu\u1EA9n)':'" does not exist (simulator has only standard FUI functions)','" l\u1ED7i eval: ':'" eval error: '," \u2192 coi nh\u01B0 false":" \u2192 treated as false",'(mock kh\u1EDBp "':'(mock matched "','kh\xF4ng c\xF3 mock cho API "':'no mock for API "','" \u2014 tr\u1EA3 m\u1EB7c \u0111\u1ECBnh {data: []}':'": default {data: []}'," (mock l\u1ED7i)":" (error mock)",'" l\u1ED7i kh\xF4ng \u0111\u01B0\u1EE3c b\u1EAFt (kh\xF4ng c\xF3 ERROROUT) \u2192 hi\u1EC7n ERROR DIALOG m\u1EB7c \u0111\u1ECBnh: ':'" error not handled (no ERROROUT) \u2192 default ERROR DIALOG: ',"\u2190 (kh\xF4ng c\xF3 OUT)":"\u2190 (no OUT)"," thay \u0111\u1ED5i \u2192 ":" changed \u2192 ","watch cascade v\u01B0\u1EE3t ":"watch cascade over "," v\xF2ng \u2014 nguy c\u01A1 V\xD2NG L\u1EB6P V\xD4 H\u1EA0N gi\u1EEFa c\xE1c watch/action":" rounds: possible INFINITE LOOP between watch/actions","M\u1EDE \uD83D\uDD32":"OPEN \uD83D\uDD32","\u0110\xD3NG \u25AA":"CLOSED \u25AA","L\u1ED7i khi init data[]: ":"data[] init error: ",'kh\xF4ng t\xECm th\u1EA5y control c\xF3 label "':'no control labeled "','" k\xE8m v-on:click/:action':'" with v-on:click/:action',"(kh\u1EDBp ":"(matched "," control \u2014 d\xF9ng #":" controls; using #",'control n\u1EB1m trong f-table nh\u01B0ng "':'control is in an f-table but "','" r\u1ED7ng \u2014 item = {} (truy\u1EC1n "item" trong event \u0111\u1EC3 ch\u1EC9 \u0111\u1ECBnh)':'" is empty: item = {} (pass "item" in the event)',"exe l\u1ED7i: ":"exe error: "," kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert":" invalid: needs one of set / click / call / exe / assert","SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)":"SystemRight=9 (default, highest)","] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json)":"] (default: scanned from module.json)","FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o trong module.json)":"FunctionRight=[] (default: no codes found in module.json)",". rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD tr\u1EA3 false \u2014 truy\u1EC1n tham s\u1ED1 `user` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.":". rightTest()/v-if on other FunctionRight codes is false; pass --user to test a narrower role.","\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550":"\u2550\u2550\u2550 RESULT \u2550\u2550\u2550","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500":"\u2500\u2500 Warnings \u2500\u2500","(stub \u0111\xE3 d\xF9ng \u2014 k\u1EBFt qu\u1EA3 c\xE1c h\xE0m n\xE0y KH\xD4NG ph\u1EA3n \xE1nh runtime th\u1EADt: ":"(stubs used; their results do NOT reflect the real runtime: ","\u2500\u2500 STATE CU\u1ED0I \u2500\u2500":"\u2500\u2500 FINAL STATE \u2500\u2500"," assert \u0111\u1EA1t":" assert passed"," \xB7 dialog \u0111ang m\u1EDF: ":" \xB7 open dialogs: ","\n\n\u2500\u2500 Live/Cache data (SP ch\u01B0a c\xF3 mock, \u0111\xE3 t\u1EF1 resolve) \u2500\u2500\n":`
157
+
158
+ \u2500\u2500 Live/Cache data (SPs without mocks, auto-resolved) \u2500\u2500
159
+ `,"Ph\u1EA3n h\u1ED3i kh\xF4ng ph\u1EA3i JSON":"Response is not JSON",': \u274C thi\u1EBFu "cols"':': \u274C missing "cols"'," \u26A0 v\u01B0\u1EE3t 12":" \u26A0 over 12","\u274C module.json g\u1ED1c kh\xF4ng ph\u1EA3i object.":"\u274C module.json root is not an object.","(kh\xF4ng c\xF3 controls)":"(no controls)","] \u274C kh\xF4ng ph\u1EA3i object":"] \u274C not an object","(m\u1EB7c \u0111\u1ECBnh)":"(default)",' \u274C thi\u1EBFu "rows"':' \u274C missing "rows"',"(kh\xF4ng c\xF3 state)":"(no state)"," \u26A0 kh\xF4ng th\u1EA5y d\xF9ng \u1EDF controls/watch/actions":" \u26A0 unused in controls/watch/actions","(kh\xF4ng c\xF3 action)":"(no actions)"," \u26A0 kh\xF4ng th\u1EA5y n\u01A1i g\u1ECDi (CALL/watch/v-on)":" \u26A0 never called (CALL/watch/v-on)",": \u26A0 hidden-container kh\xF4ng c\xF3 v-model \u2014 kh\xF4ng \u0111i\u1EC1u khi\u1EC3n m\u1EDF/\u0111\xF3ng \u0111\u01B0\u1EE3c":": \u26A0 hidden-container without v-model: cannot open/close"," \u2014 c\xF3 l\u1EC7nh m\u1EDF (":" \u2014 opened by (",' \u26A0 kh\xF4ng th\u1EA5y l\u1EC7nh m\u1EDF "':' \u26A0 no opener "',' = true" \u1EDF \u0111\xE2u':' = true" found'," file, t\u1EA3i l\xFAc ":" files, fetched ","API tr\u1EA3 danh m\u1EE5c r\u1ED7ng":"API returned an empty list","cache C\u0168 (":"STALE cache (","Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ":"Framework list API failed for ",") \u2014 d\xF9ng cache c\u0169 tr\xEAn \u0111\u0129a.":"); using the stale disk cache.","danh s\xE1ch d\u1EF1 ph\xF2ng hardcode (":"hardcoded fallback list (",") v\xE0 ch\u01B0a c\xF3 cache \u2014 ":") and no cache: ","d\xF9ng danh s\xE1ch hardcode trong renderAssets.ts. Danh s\xE1ch n\xE0y c\xF3 th\u1EC3 \u0111\xE3 l\u1EC7ch v\u1EDBi server.":"using the hardcoded list, which may differ from the server.","projectdata.js t\u1EA3i v\u1EC1 t\u1EEB ":"projectdata.js from ",' kh\xF4ng ch\u1EE9a "$projectData =" (':' lacks "$projectData =" (',"kh\xF4ng d\xF9ng, quay v\u1EC1 d\u1EF1ng $projectData + component project t\u1EEB workspace local.":"ignored; building $projectData + project components from the local workspace.","Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ":"Cannot fetch "," \u2014 quay v\u1EC1 d\u1EF1ng t\u1EEB workspace local.":"; building from the local workspace.","StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 t\u1EEB ":"Project StyleCSS from "," l\xE0 HTML ch\u1EE9 kh\xF4ng ph\u1EA3i CSS \u2014 b\u1ECF qua.":" is HTML, not CSS; skipped.","StyleCSS project r\u1ED7ng (":"Project StyleCSS empty (",") \u2014 project ch\u01B0a c\xF3 component n\xE0o khai style.":"): no project component declares a style."," \u2014 CSS component c\u1EA5p project s\u1EBD d\u1EF1ng t\u1EEB local.":"; project component CSS comes from local.",'.vue" kh\xF4ng c\xF3 khu\xF4n `export default { \u2026 }` \u2014 server bi\xEAn d\u1ECBch b\u1EB1ng c\xE1ch ':'.vue" lacks `export default { \u2026 }`; the server ',"n\u1ED1i th\u1EB3ng ph\u1EA7n <script> v\xE0o `{template:`\u2026`, \u2026}` n\xEAn b\u1EA3n tr\xEAn server c\xF3 th\u1EC3 l\u1ED7i c\xFA ph\xE1p. ":"splices <script> into `{template:`\u2026`, \u2026}`, so the server build may have a syntax error. ","renderUI \u0111\xE3 b\u1ECDc an to\xE0n \u0111\u1EC3 ch\u1EA1y ti\u1EBFp, nh\u01B0ng n\xEAn s\u1EEDa .vue v\u1EC1 \u0111\xFAng khu\xF4n.":"The render wraps it to continue; fix the .vue shape."," (bundle server kh\xF4ng theo khu\xF4n ComponentBuild)":" (server bundle not in ComponentBuild shape)"," (ch\u1EC9 \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c template, kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c th\xE2n script)":" (template checked only; script body not extractable)",'": FUI kh\xF4ng nh\u1EADn c\xFA ph\xE1p @/# trong JSON \u2014 \u0111\u1ED5i th\xE0nh "v-on:"/"v-slot:". \u0110\xE3 b\u1ECF qua.':'": FUI does not accept @/# in JSON; use "v-on:"/"v-slot:". Skipped.',"Kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c projectId. Truy\u1EC1n projectId ho\u1EB7c ch\u1EA1y session_set tr\u01B0\u1EDBc.":"No project: pass -p or run fui use first.","Kh\xF4ng t\xECm th\u1EA5y ":"Not found: ",".vue trong ":".vue in "," C\xF3 s\u1EB5n: ":" Available: "," Th\u01B0 m\u1EE5c components r\u1ED7ng.":" The components folder is empty."," N\u1EBFu \u0111\xE2y l\xE0 component c\u1EA5p module, truy\u1EC1n th\xEAm moduleId.":" For a module-level component, also pass -m."," l\u1EA7n render, v\u01B0\u1EE3t gi\u1EDBi h\u1EA1n ":" renders, over the limit of ","M\u1ED7i l\u1EA7n render l\xE0 m\u1ED9t phi\xEAn browser th\u1EADt \u2014 chia nh\u1ECF th\xE0nh nhi\u1EC1u l\u1EA7n g\u1ECDi.":"Each render is a real browser session; split into several calls.","Ngu\u1ED3n: ":"Source: ","(kh\xF4ng t\xEAn)":"(unnamed)"," l\u1EA7n render":" renders","\u1EA2nh: ":"Screenshots: ","Kh\xF4ng gian ri\xEAng: ch\u1EC9 component n\xE0y + CSS c\u1EE7a ch\xEDnh n\xF3 + theme/import c\u1EA5p project.":"Isolated: this component + its own CSS + project theme/imports.","style.css c\u1EE7a module KH\xD4NG n\u1EA1p \u2014 component ph\u1EA3i t\u1EF1 \u0111\u1EE9ng \u0111\u01B0\u1EE3c. T\u1EA7ng project d\u1EF1ng t\u1EEB workspace":"Module style.css is NOT loaded; the component must stand alone. The project layer comes from the workspace,","local n\xEAn b\u1EA3n ch\u01B0a push v\u1EABn hi\u1EC7n; ch\u1EA1y `project_sync` n\u1EBFu nghi file local \u0111\xE3 c\u0169.":"so unpushed edits show; run `fui project sync` if local files look stale.","\u26A0 Ghi ch\xFA d\u1EF1ng harness:":"\u26A0 Harness notes:","file m\xF4i tr\u01B0\u1EDDng thi\u1EBFu":"missing env files","text nghi v\u1EA5n":"suspect text","\u1EA3nh v\u1EE1":"broken images","canvas ch\u1EBFt":"dead canvas","text d\xEDnh/ch\u1ED3ng l\u1EA5n":"text collisions","assert h\u1ECFng":"failed asserts","app render R\u1ED6NG (tr\u01B0\u1EDBc c\xF3 n\u1ED9i dung) \u2191":"app EMPTY (had content before) \u2191","app \u0111\xE3 c\xF3 n\u1ED9i dung \u2193":"app has content now \u2193","tr\xE0n ngang (tr\u01B0\u1EDBc kh\xF4ng) \u2191":"horizontal overflow (none before) \u2191","h\u1EBFt tr\xE0n ngang \u2193":"no horizontal overflow \u2193","\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (":"\u0394 vs previous run (","): KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i \u2014 thay \u0111\u1ED5i v\u1EEBa r\u1ED3i kh\xF4ng ch\u1EA1m t\u1EDBi l\u1ED7i \u0111ang c\xF3. \u0110\u1ED5i h\u01B0\u1EDBng ch\u1EA9n \u0111o\xE1n, \u0111\u1EEBng render l\u1EA1i c\xF9ng m\u1ED9t c\xE1ch.":"): NO metric changed; the last edit missed the failure. Change the diagnosis, do not render again the same way.","): kh\xF4ng \u0111\u1ED5i \u2014 c\u1EA3 hai l\u01B0\u1EE3t \u0111\u1EC1u s\u1EA1ch.":"): unchanged; both runs clean.","\uD83D\uDED1 \u0110ANG GI\u1EACM CH\xC2N \u2014 ":"\uD83D\uDED1 STUCK: "," l\u01B0\u1EE3t render h\u1ECFng li\xEAn ti\u1EBFp m\xE0 KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i.":" failed renders in a row with NO metric change."," Render l\u1EA1i l\u1EA7n n\u1EEFa theo c\xF9ng h\u01B0\u1EDBng s\u1EBD cho \u0111\xFAng k\u1EBFt qu\u1EA3 n\xE0y. Ch\u1ECDn M\u1ED8T trong ba:":" Rendering again the same way gives the same result. Pick ONE:"," 1. Ch\u1EA1y t\u1EA7ng r\u1EBB h\u01A1n \u0111\u01B0\u1EE3c n\xEAu \u1EDF d\xF2ng \u21AA tr\xEAn \u2014 n\xF3 ch\u1EC9 th\u1EB3ng v\xE0o ch\u1ED7 h\u1ECFng.":" 1. Run the cheaper check on the \u21AA line above; it points at the fault."," 1. L\xF9i v\u1EC1 t\u1EA7ng t\u0129nh: module_validate (l\u1ED7i quy t\u1EAFc) \u2192 module_outline (c\u1EA5u tr\xFAc) \u2192 module_simulate kh\xF4ng renderUI (action/watch).":" 1. Step back: fui module validate (rules) \u2192 fui module outline (structure) \u2192 fui module simulate without --render (actions/watch)."," 2. \u0110\u1ECDc l\u1EA1i screenshot + kh\u1ED1i l\u1ED7i \u1EDF cu\u1ED1i report b\u1EB1ng m\u1EAFt, \u0111\u1EEBng ch\u1EC9 \u0111\u1ECDc con s\u1ED1.":" 2. Look at the screenshot and the error block at the end, not only the numbers."," 3. H\u1ECEI NG\u01AF\u1EDCI D\xD9NG. Ba l\u01B0\u1EE3t kh\xF4ng nh\xFAc nh\xEDch th\u01B0\u1EDDng ngh\u0129a l\xE0 gi\u1EA3 \u0111\u1ECBnh ban \u0111\u1EA7u sai, kh\xF4ng ph\u1EA3i s\u1EEDa ch\u01B0a \u0111\u1EE7.":" 3. ASK THE USER. Three stuck runs usually mean a wrong assumption, not an incomplete fix.","\u2550\u2550\u2550 CH\u1EA8N \u0110O\xC1N \u2550\u2550\u2550":"\u2550\u2550\u2550 DIAGNOSIS \u2550\u2550\u2550","[M\xD4I TR\u01AF\u1EDCNG] ":"[ENV] "," file import/asset KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c \u2192 s\u1EEDa ph\u1EA7n n\xE0y TR\u01AF\u1EDAC (project_sync / module_get / file_import_*). L\u1ED7i \u1EDF hai nh\xF3m d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3.":" import/asset file(s) NOT loaded; fix this FIRST (fui project sync / fui module get / fui import). Errors below may only be consequences.","[M\xD4I TR\u01AF\u1EDCNG] \u2713 m\u1ECDi import/asset n\u1EA1p \u0111\u1EE7.":"[ENV] \u2713 all imports/assets loaded."," l\xFAc init)":" at init)"," assert h\u1ECFng":" failed asserts"," \u2192 l\u1ED7i n\u1EB1m trong module.json / script.js / component.":" \u2192 fault is in module.json / script.js / component.","[MODULE] \u2713 kh\xF4ng JS error, kh\xF4ng Vue warn.":"[MODULE] \u2713 no JS error, no Vue warn.","app render R\u1ED6NG":"app EMPTY","tr\xE0n ngang viewport":"horizontal viewport overflow"," text nghi v\u1EA5n (undefined/NaN)":" suspect text (undefined/NaN)"," \u1EA3nh v\u1EE1":" broken images"," ch\u1ED7 text d\xEDnh/ch\u1ED3ng l\u1EA5n":" text collisions","[HI\u1EC2N TH\u1ECA] ":"[VIEW] "," \u2192 xem screenshot; \u0111\xE2y l\xE0 nh\xF3m CH\u1EC8 renderUI th\u1EA5y \u0111\u01B0\u1EE3c.":" \u2192 check the screenshot; only --render sees these.","[HI\u1EC2N TH\u1ECA] \u2713 DOM audit s\u1EA1ch.":"[VIEW] \u2713 DOM audit clean.","`\u2026 is not defined` = m\u1ED9t \u0111\u1ECBnh danh tr\u1EA7n kh\xF4ng c\xF3 tr\xEAn window. Ca th\u01B0\u1EDDng g\u1EB7p nh\u1EA5t: `EXE`/`FUN` g\u1ECDi h\xE0m ch\u01B0a vi\u1EBFt trong script.js \u2014 ho\u1EB7c vi\u1EBFt r\u1ED3i m\xE0 qu\xEAn `module_publish_script` (module_publish_json KH\xD4NG \u0111\u1EA9y script.js). So\xE1t t\xEAn h\xE0m trong script.js tr\u01B0\u1EDBc khi \u0111i t\xECm ch\u1ED7 kh\xE1c.":"`\u2026 is not defined` = a bare name missing on window. Usually `EXE`/`FUN` calls a function not in script.js, or not pushed with `fui module publish script` (publish json does NOT push script.js). Check script.js names first.","Trang tr\u1EAFng m\xE0 KH\xD4NG c\xF3 JS error/Vue warn l\xE0 d\u1EA5u hi\u1EC7u kinh \u0111i\u1EC3n c\u1EE7a statement trong m\u1ED9t `:attr` (Vue nu\u1ED1t l\u1ED7i compile template) \u2014 ch\u1EA1y module_validate, n\xF3 b\u1EAFt t\u0129nh \u0111\u01B0\u1EE3c ca n\xE0y.":"A blank page with NO JS error/Vue warn usually means a statement in a `:attr` (Vue swallows the template error); fui module validate catches it.",'[Vue warn] ki\u1EC3u "property is not defined" = bi\u1EBFn d\xF9ng trong template ch\u01B0a khai trong data[] \u2014 module_validate b\u1EAFt \u0111\u01B0\u1EE3c (v-model/watch/state), kh\xF4ng c\u1EA7n browser.':'[Vue warn] "property is not defined" = a template var missing from data[]; fui module validate catches it (v-model/watch/state).','Text "undefined"/"NaN" th\u01B0\u1EDDng l\xE0 OUT/mapData tr\u1ECF sai kho\xE1 ch\u1EE9 kh\xF4ng ph\u1EA3i l\u1ED7i hi\u1EC3n th\u1ECB \u2014 module_simulate (engine vm) ch\u1EC9 ra ch\u1ED7 g\xE1n, ch\u1EA1y nhanh h\u01A1n nhi\u1EC1u.':'Text "undefined"/"NaN" usually means OUT/mapData uses a wrong key; fui module simulate (no --render) shows the assignment, much faster.',"kh\xF4ng r\xF5 tu\u1ED5i":"unknown age","c\u0169 d\u01B0\u1EDBi 1 gi\u1EDD":"under 1 h old","offline: ch\u01B0a c\xF3 b\u1EA3n cache n\xE0o cho URL n\xE0y":"offline: no cached copy of this URL","offline \u2014 d\xF9ng cache ":"offline: cache ","n\u1ED9i dung \u0110\u1ED4I so v\u1EDBi b\u1EA3n cache ":"content CHANGED vs cache ","kh\xF4ng revalidate \u0111\u01B0\u1EE3c (":"revalidate failed (",") \u2014 d\xF9ng cache ":"); cache ","_moduleInfo.json kh\xF4ng c\xF3 url k\xE8m host \u2014 origin l\u1EA5y t\u1EEB ":"_moduleInfo.json has no url with a host; origin from ","domain asset m\u1EB7c \u0111\u1ECBnh":"default asset domain","Ch\u1EA1y `module_get` \u0111\u1EC3 c\xF3 url th\u1EADt n\u1EBFu render sai domain.":"Run `fui module get` for the real url if the domain is wrong.","Project/module ph\u1EE5c v\u1EE5 tr\xEAn nhi\u1EC1u domain (":"Project/module served on several domains (",') \u2014 l\u1EA7n render n\xE0y ch\u1EA1y \u1EDF "':'); this render uses "',"v_Set.domainSetting c\u1EE7a domain kh\xE1c KH\xD4NG \u0111\u01B0\u1EE3c \xE1p d\u1EE5ng; mu\u1ED1n th\u1EED domain kh\xE1c th\xEC render module c\xF3 url thu\u1ED9c domain \u0111\xF3.":"v_Set.domainSetting of other domains is NOT applied; to test another domain, render a module whose url is on it.","(tr\u1ED1ng)":"(empty)",'" kh\xF4ng ph\u1EA3i .js/.css \u2014 kh\xF4ng n\u1EA1p v\xE0o trang render':'" is not .js/.css; not loaded'," import c\u1EA5p ":" import(s), scope "," kh\xF4ng c\xF3 FileSort trong _imports.json (":", lack FileSort in _imports.json (",") \u2192 t\u1EA1m x\u1EBFp cu\u1ED1i ":") \u2192 sorted last ","). Th\u1EE9 t\u1EF1 n\u1EA1p c\xF3 th\u1EC3 kh\xE1c production; ch\u1EA1y `":"). Load order may differ from production; run `","` \u0111\u1EC3 l\u1EA5y FileSort th\u1EADt.":"` for the real FileSort.",': file n\u1ED9i b\u1ED9 "':': inline file "','" thi\u1EBFu/r\u1ED7ng v\xE0 kh\xF4ng c\xF3 fileID \u0111\u1EC3 l\u1EA5y b\u1EA3n server \u2192 KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c. ':'" missing/empty and no fileID to fetch it \u2192 NOT loaded. ',"Ch\u1EA1y `":"Run `","`. L\u1ED7i JS/hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3 c\u1EE7a vi\u1EC7c n\xE0y.":"`. JS/display errors below may only be consequences.",": ch\u01B0a c\xF3 b\u1EA3n local \u2192 d\xF9ng b\u1EA3n tr\xEAn server. Ch\u1EA1y `":": no local copy \u2192 server copy used. Run `","` n\u1EBFu c\u1EA7n s\u1EEDa file n\xE0y.":"` to edit it.",": kh\xF4ng c\xF3 file n\u1ED9i b\u1ED9 v\xE0 t\xEAn kh\xF4ng ph\u1EA3i URL/\u0111\u01B0\u1EDDng d\u1EABn tuy\u1EC7t \u0111\u1ED1i \u2192 kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c ngu\u1ED3n \u0111\u1EC3 n\u1EA1p. ":": no inline file and the name is not a URL/absolute path \u2192 nothing to load. ","N\u1EBFu \u0111\xE2y l\xE0 file t\u1EF1 t\u1EA1o, ch\u1EA1y `":"If you created it, run `"," \u2014 s\u1EEDa tr\u01B0\u1EDBc khi renderUI.":"; fix it before --render.","module.json r\u1ED7ng \u2014 render shell v\u1EDBi module tr\u1ED1ng":"module.json empty: rendering an empty shell","(b\u1ECF qua \u2014 HTMLOnly)":"(skipped: HTMLOnly)",": KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)":": NOT embedded (HTMLOnly=true, same as the server)",": danh m\u1EE5c t\u1EEB ":": list from ","HTMLOnly=true \u2192 render KH\xD4NG nh\xFAng framework ":"HTMLOnly=true \u2192 no framework ",", KH\xD4NG nh\xFAng projectdata/StyleCSS c\u1EA5p project ":", no project projectdata/StyleCSS ","v\xE0 KH\xD4NG d\u1EF1ng #fastProjectAPP \u2014 gi\u1ED1ng h\u1EC7t trang production. Th\u01B0 vi\u1EC7n n\xE0o module c\u1EA7n (Vue, jQuery, Vuetify, ":"and no #fastProjectAPP, exactly like production. Libraries the module needs (Vue, jQuery, Vuetify, ","lodash\u2026) ph\u1EA3i do CH\xCDNH module khai l\xE0m import; thi\u1EBFu l\xE0 tr\u1EAFng trang th\u1EADt, kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a render.":"lodash\u2026) must be module imports; a missing one blanks the real page too.","T\u1EA7ng project d\u1EF1ng t\u1EEB workspace LOCAL (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component) \u2014 kh\xF4ng d\xF9ng bundle server. ":"Project layer built from the LOCAL workspace (component preview), not the server bundle. ","Ch\u1EA1y `project_sync` n\u1EBFu nghi file project trong workspace \u0111\xE3 c\u0169.":"Run `fui project sync` if workspace project files look stale."," (\u0111\xE3 s\u1EEDa local, ch\u01B0a push)":" (edited locally, not pushed)"," (ch\u01B0a c\xF3 tr\xEAn server)":" (not on the server)"," (<style> \u0111\xE3 s\u1EEDa local, ch\u01B0a push)":" (<style> edited locally, not pushed)","\u26D4 Component c\u1EA5p project l\u1EC7ch v\u1EDBi b\u1EA3n tr\xEAn server: ":"\u26D4 Project components differ from the server: ","renderUI d\u1EF1ng trang b\u1EB1ng bundle project T\u1EA2I T\u1EEA SERVER (_aset/.../projectdata) n\xEAn b\u1EA3n local ch\u01B0a push ":"The render uses the project bundle FROM THE SERVER (_aset/.../projectdata), so unpushed local edits ","kh\xF4ng xu\u1EA5t hi\u1EC7n \u2014 n\u1EBFu v\u1EABn render, t\u1EA5m \u1EA3nh b\u1EA1n nh\u1EADn \u0111\u01B0\u1EE3c l\xE0 B\u1EA2N C\u0168, kh\xF4ng ph\u1EA3i b\u1EA3n v\u1EEBa s\u1EEDa.\n\n":`do not show; a render now shows the OLD version.
160
+
161
+ `,"\u2022 N\u1EBFu b\u1EA3n local l\xE0 b\u1EA3n b\u1EA1n v\u1EEBa s\u1EEDa \u2192 `component_preview` \u0111\u1EC3 ki\u1EC3m ch\u1EE9ng ri\xEAng component (ch\u1EA1y \u0111\u01B0\u1EE3c b\u1EB1ng ":"\u2022 Local is your edit \u2192 `fui component preview` checks the component alone (uses ","file local, kh\xF4ng c\u1EA7n push), r\u1ED3i `component_update` t\u1EEBng component, r\u1ED3i render l\u1EA1i.\n":"local files), then `fui component update` each one, then render again.\n","\u2022 N\u1EBFu b\u1EA3n tr\xEAn server m\u1EDBi h\u01A1n (ai \u0111\xF3 s\u1EEDa tr\xEAn web IDE) \u2192 `project_sync` \u0111\u1EC3 l\u1EA5y v\u1EC1, \u0110\u1EEANG push \u0111\xE8.":"\u2022 Server is newer (edited in the web IDE) \u2192 `fui project sync`; do NOT overwrite it.","Kh\xF4ng \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c component c\u1EA5p project: ":"Cannot compare project components: ","Kh\xF4ng ch\u1EB7n render, nh\u01B0ng n\u1EBFu giao di\u1EC7n kh\xE1c mong \u0111\u1EE3i th\xEC \u0111\xE2y l\xE0 ch\u1ED7 \u0111\xE1ng nghi \u0111\u1EA7u ti\xEAn.":"Render continues; if the UI looks wrong, suspect this first.","StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 R\u1ED6NG nh\u01B0ng component local c\xF3 khai <style>: ":"Project StyleCSS is EMPTY but local components declare <style>: ","Ho\u1EB7c c\xE1c component \u0111\xF3 ch\u01B0a push (`component_update`), ho\u1EB7c route StyleCSS \u0111ang h\u1ECFng \u2014 ":"Either they are not pushed (`fui component update`) or the StyleCSS route is broken; ","d\xF9 nguy\xEAn nh\xE2n n\xE0o th\xEC style c\u1EA5p project trong l\u1EA7n render n\xE0y c\u0169ng \u0111ang thi\u1EBFu.":"either way project styles are missing in this render.","Kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c `var $projectData` kh\u1ECFi bundle projectdata t\u1EA3i t\u1EEB server \u2014 apiDomain/userInfo ":"Cannot extract `var $projectData` from the server projectdata bundle; apiDomain/userInfo ","trong l\u1EA7n render n\xE0y suy ra t\u1EEB project.json local, c\xF3 th\u1EC3 l\u1EC7ch v\u1EDBi b\u1EA3n trang th\u1EADt \u0111ang ch\u1EA1y.":"come from local project.json and may differ from the live page.",'project.json local c\xF3 apiDomain "':'Local project.json apiDomain "','" nh\u01B0ng b\u1EA3n tr\xEAn server l\xE0 "':'" but the server has "',"render d\xF9ng b\u1EA3n server (gi\u1ED1ng trang th\u1EADt). Ch\u1EA1y `project_sync` \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 file local.":"render uses the server value (like the live page). Run `fui project sync`.","(kh\xF4ng t\u1EA7ng n\xE0o khai)":"(not set anywhere)",'Kh\xF4ng t\u1EA7ng n\xE0o ($projectGroupSetting \u2192 $projectData \u2192 module.json set \u2192 domainSetting["':'No layer ($projectGroupSetting \u2192 $projectData \u2192 module.json set \u2192 domainSetting["','"]) khai apiDomain cho "':'"]) sets apiDomain for "','" \u2014 API s\u1EBD g\u1ECDi b\u1EB1ng \u0111\u01B0\u1EDDng d\u1EABn t\u01B0\u01A1ng \u0111\u1ED1i v\xE0 apiMocks kh\u1EDBp theo URL ':'"; APIs use relative paths, so URL-matched mocks ',"g\u1EA7n nh\u01B0 ch\u1EAFc ch\u1EAFn tr\u01B0\u1EE3t. Ch\u1EA1y `project_sync` n\u1EBFu d\u1EEF li\u1EC7u project \u0111\xE3 c\u0169.":"almost surely miss. Run `fui project sync` if project data is stale.","] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json/project)":"] (default: scanned from module.json/project)","FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o)":"FunctionRight=[] (default: no codes found)",". rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD \u1EA9n \u2014 truy\u1EC1n tham s\u1ED1 `user` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.":". rightTest()/v-if on other FunctionRight codes hides; pass --user to test a narrower role.","projectdata/StyleCSS: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)":"projectdata/StyleCSS: NOT embedded (HTMLOnly=true, same as the server)","projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component \u2014 b\u1EA3n local ch\u01B0a push v\u1EABn hi\u1EC7n)":"projectdata/StyleCSS: from the local workspace (component preview; unpushed edits show)","projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n server)":"projectdata/StyleCSS: from the local workspace (server copy unavailable)","g\u1ECDi tAPI th\u1EADt (SP ":"live tAPI call (SP ",", ngu\u1ED3n: ":", source: ","t\u1EF1 sinh mock (SP ":"generated mock (SP ","), \u0111\xE3 l\u01B0u cache":"), cached","Kh\xF4ng launch \u0111\u01B0\u1EE3c Chromium: ":"Cannot launch Chromium: ","\n\u2192 Ch\u1EA1y m\u1ED9t l\u1EA7n: npx playwright install chromium":`
162
+ \u2192 Run once: bunx playwright install chromium`,'runtime \u0111i\u1EC1u h\u01B0\u1EDBng ra "':'runtime navigated to "','" \u2014 \u0111\xE3 ch\u1EB7n (th\u01B0\u1EDDng do login gate / redirect trong action)':'"; blocked (usually a login gate / redirect in an action)','" t\u1EA3i th\u1EA5t b\u1EA1i (':'" failed to load ('," \u2014 tr\u1EA3 404 (offline + ch\u01B0a cache?)":"; answered 404 (offline, not cached?)"," \u2192 user preview (userInfo c\u1EE7a project, auto-mock)":" \u2192 preview user (project userInfo, auto-mock)",'" \u2014 tr\u1EA3 {data: []}':'": {data: []}'," \u2192 {data:[]} (kh\xF4ng c\xF3 mock)":" \u2192 {data:[]} (no mock)","module m\u1EDF WebSocket t\u1EDBi backend th\u1EADt \u2014 \u0111\xE3 ch\u1EB7n (renderUI ch\u1EC9 cho ph\xE9p g\u1ECDi th\u1EADt qua tAPI SP \u0111\xE3 x\xE1c nh\u1EADn an to\xE0n, kh\xF4ng qua WebSocket)":"module opened a WebSocket to the real backend; blocked (only verified-safe tAPI SPs run live)","Vue ch\u01B0a mount sau 15s \u2014 trang ch\u01B0a init xong (xem JS error / asset h\u1ECFng / API treo b\xEAn d\u01B0\u1EDBi)":"Vue not mounted after 15s; page init unfinished (see JS errors / broken assets / hung APIs below)"," C\u1EACP NH\u1EACT":" UPDATED"," t\u1EA3i m\u1EDBi":" fetched"," g\u1ECDi th\u1EADt":" live"," t\u1EF1 sinh":" generated"," (API kh\xF4ng-mock: ":" (unmocked API: ","trang load xong sau ":"page loaded in ",': kh\xF4ng t\xECm th\u1EA5y ph\u1EA7n t\u1EED c\xF3 text "':': no element with text "','" tr\xEAn DOM':'" in the DOM'," ph\u1EA7n t\u1EED \u2014 d\xF9ng #":" elements; using #",'CONFIRM \u2192 b\u1EA5m "':'CONFIRM \u2192 click "',"CONFIRM \u2192 b\u1EA5m n\xFAt cu\u1ED1i (kh\xF4ng nh\u1EADn di\u1EC7n \u0111\u01B0\u1EE3c label)":"CONFIRM \u2192 click the last button (label not recognized)","CONFIRM \u2192 b\u1EA5m n\xFAt \u0111\u1EA7u (kh\xF4ng nh\u1EADn di\u1EC7n \u0111\u01B0\u1EE3c label)":"CONFIRM \u2192 click the first button (label not recognized)","global CALL kh\xF4ng t\u1ED3n t\u1EA1i trong runtime n\xE0y":"global CALL missing in this runtime","ch\u1ED3ng l\u1EA5n":"overlap","d\xEDnh s\xE1t (gap ":"touching (gap ","canvas trong ":"canvas in ","\u2718 <body> kh\xF4ng render \u0111\u01B0\u1EE3c ph\u1EA7n t\u1EED n\xE0o (HTMLOnly \u2014 body.html L\xC0 c\u1EA3 trang)":"\u2718 <body> rendered nothing (HTMLOnly: body.html IS the page)","\u2718 #fastProjectAPP r\u1ED7ng \u2014 controls kh\xF4ng render":"\u2718 #fastProjectAPP empty: controls not rendered"," (C\xD3 file to\xE0n c\u1EE5c n\u1EA1p thi\u1EBFu \u2014 xem m\u1EE5c M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE tr\u01B0\u1EDBc khi nghi module.json)":" (global files FAILED to load; see MISSING ENV FILES before suspecting module.json)"," (ki\u1EC3m tra pageerror / module.json)":" (check pageerror / module.json)","\u26A0 trang tr\xE0n ngang viewport ":"\u26A0 page overflows the viewport ","px \u2014 ph\u1EA7n t\u1EED tr\xE0n: ":"px; overflowing: ","(kh\xF4ng x\xE1c \u0111\u1ECBnh)":"(unknown)","\u26A0 text nghi v\u1EA5n hi\u1EC3n th\u1ECB tr\xEAn trang: ":"\u26A0 suspect text on page: "," (binding thi\u1EBFu data?)":" (binding missing data?)","\u26A0 \u1EA3nh v\u1EE1 (load fail nh\u01B0ng \u0111ang chi\u1EBFm ch\u1ED7): ":"\u26A0 broken images (failed but taking space): ","\u26A0 canvas chart 0-size \u2014 chart init khi container ch\u01B0a c\xF3 k\xEDch th\u01B0\u1EDBc: ":"\u26A0 0-size chart canvas (init before the container had a size): ","\u26A0 text d\xEDnh/ch\u1ED3ng l\u1EA5n (th\u1EA9m m\u1EF9 \u2014 PH\u1EA2I s\u1EEDa, kh\xF4ng \u0111\u01B0\u1EE3c b\u1ECF qua): ":"\u26A0 text collisions (visual; MUST fix): ","RENDER (browser th\u1EADt): ":"RENDER (real browser): "," \xB7 HTMLOnly=true (KH\xD4NG nh\xFAng framework/projectdata \u2014 nh\u01B0 production)":" \xB7 HTMLOnly=true (no framework/projectdata, like production)","Origin trang: ":"Page origin: ","Import (framework \u222A project \u222A module, sort theo FileSort/FileName): ":"Imports (framework \u222A project \u222A module, by FileSort/FileName): "," n\u1EA1p \u0111\u01B0\u1EE3c":" loaded"," KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c":" NOT loaded","\u2500\u2500 \u26D4 M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE (\u0111\u1ECDc TR\u01AF\u1EDAC khi s\u1EEDa module) \u2500\u2500":"\u2500\u2500 \u26D4 MISSING ENV FILES (read BEFORE fixing the module) \u2500\u2500","C\xE1c file d\u01B0\u1EDBi \u0111\xE2y kh\xF4ng n\u1EA1p \u0111\u01B0\u1EE3c v\xE0o trang test. H\xE0m/bi\u1EBFn/style to\xE0n c\u1EE5c do ch\xFAng":"These files did not load. Their global functions/vars/styles","\u0111\u1ECBnh ngh\u0129a s\u1EBD kh\xF4ng t\u1ED3n t\u1EA1i \u2192 l\u1ED7i JS v\xE0 l\u1ED7i hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi C\xD3 TH\u1EC2 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3,":"are missing, so JS and display errors below MAY only be consequences,","kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a module. Kh\u1EAFc ph\u1EE5c ph\u1EA7n n\xE0y tr\u01B0\u1EDBc r\u1ED3i render l\u1EA1i.":"not module faults. Fix this first, then render again.","\u2500\u2500 \u26D4 ASSET C\u0168, KH\xD4NG X\xC1C MINH \u0110\u01AF\u1EE2C (\u0111\u1ECDc TR\u01AF\u1EDAC khi k\u1EBFt lu\u1EADn v\u1EC1 platform) \u2500\u2500":"\u2500\u2500 \u26D4 STALE, UNVERIFIED ASSETS (read BEFORE blaming the platform) \u2500\u2500","Kh\xF4ng h\u1ECFi \u0111\u01B0\u1EE3c server xem c\xE1c file d\u01B0\u1EDBi \u0111\xE2y c\xF2n \u0111\xFAng kh\xF4ng, n\xEAn trang test \u0111ang ch\u1EA1y":"The server could not confirm these files, so the page ran",'b\u1EB1ng b\u1EA3n tr\xEAn \u0111\u0129a. M\u1ECDi k\u1EBFt lu\u1EADn ki\u1EC3u "th\u01B0 vi\u1EC7n X c\xF3 bug" r\xFAt ra t\u1EEB l\u01B0\u1EE3t render n\xE0y':'on disk copies. Any "library X has a bug" conclusion from this render',"\u0111\u1EC1u KH\xD4NG d\xF9ng \u0111\u01B0\u1EE3c \u2014 b\u1EA3n th\u1EADt tr\xEAn CDN c\xF3 th\u1EC3 \u0111\xE3 kh\xE1c. N\u1ED1i m\u1EA1ng r\u1ED3i render l\u1EA1i,":"is NOT valid; the CDN copy may differ. Go online and render again,","ho\u1EB7c \xE9p t\u1EA3i m\u1EDBi b\u1EB1ng refreshAssets: true.":"or force a fetch with --refresh-assets.","\u2500\u2500 \u2139\uFE0F ASSET PLATFORM \u0110\xC3 \u0110\u1ED4I so v\u1EDBi l\u01B0\u1EE3t render tr\u01B0\u1EDBc \u2500\u2500":"\u2500\u2500 \u2139\uFE0F PLATFORM ASSETS CHANGED since the previous render \u2500\u2500","H\xE0nh vi \u0111\u1ED5i m\xE0 module.json kh\xF4ng \u0111\u1EE5ng t\u1EDBi c\xF3 th\u1EC3 \u0111\u1EBFn t\u1EEB \u0111\xE2y, kh\xF4ng ph\u1EA3i t\u1EEB module.":"Behavior changes not caused by module.json may come from here.","\u2500\u2500 STATE CU\u1ED0I (vueData) \u2500\u2500":"\u2500\u2500 FINAL STATE (vueData) \u2500\u2500"," (\u0111\xEDnh k\xE8m b\xEAn d\u01B0\u1EDBi)":" (attached below)"," (qu\xE1 l\u1EDBn \u0111\u1EC3 inline \u2014 d\xF9ng Read tool \u0111\u1EC3 xem)":" (too large to inline; open it with the Read tool)","Dialog \u0111ang m\u1EDF \u2014 ch\u1EE5p ri\xEAng: ":"Open dialog screenshot: "," API call (t\u1EA5t c\u1EA3 mock) \xB7 ":" API call (all mocked) \xB7 "," l\xFAc init) \xB7 ":" at init) \xB7 "," \xB7 dialog \u0111ang m\u1EDF":" \xB7 open dialog"},Zm=Object.entries(Qm).sort(([e],[t])=>t.length-e.length);function Be(e){let t=e;for(let[n,o]of Xm)t=t.replace(n,o);for(let[n,o]of Zm)t=t.replaceAll(n,o);return t.replace(/`(project_sync|module_get|file_import_list)`/g,(n,o)=>`\`${Ym[o]}\``)}async function pn(e){let t=yr(await En(e));return t?[t]:[]}async function $c(e,t){let n=await Po(e);if(n.errors.length===0&&n.warnings.length===0)return[];let o=Be(To(n));if(n.errors.length>0&&Wt())throw new ve(`[FUI_MCP_STRICT] ${t} blocked: module.json has semantic errors; fix them and retry
163
+
164
+ ${o}`,"hard","error");return["--- module validate ---",o]}async function Ft(e){let t=await U(at(e,"_moduleInfo.json"),null),n=t?.ProjectID&&t?.ModuleID?`fui module get -p ${t.ProjectID} -m ${t.ModuleID}`:"fui module get";return Pt(e,"local module",n,"_moduleInfo.json")}async function jc(e){let t=await Ft(e),n=await Po(e);return{notes:[...t?[t]:[],...await pn(e)],data:`${Be(To(n))}
165
+ `,errorCount:n.errors.length}}async function Ic(e,t){let o=await(await ce(e)).previewStagedModuleDir(t),s=await e.session.createApproval({projectId:o.moduleInfo.ProjectID,moduleId:o.moduleInfo.ModuleID,moduleDir:t,contentHash:await $o(t)}),r=await Ft(t),i=await Po(t);return{token:s.token,notes:[...r?[r]:[],`preview of ${o.moduleInfo.ProjectID}/${o.moduleInfo.ModuleID} (${t})`,`remote: ${o.remoteSummary.join("; ")}`,"changes that would be pushed:",...o.changes.length===0?[" none"]:o.changes.map((a)=>` ${a}`),"--- module validate ---",Be(To(i)),`approval token: ${s.token}`,"ask the user; only after explicit approval run: fui module publish html --approval <token>"]}}async function Cc(e,t,n){let o=await xt(e.session,"module publish html"),s=(c)=>new ve(c,"hard","error");if(!n)throw s("missing --approval: run fui module preview, show it to the user, and publish only after explicit approval");let r=await e.session.getApproval();if(!r)throw s("no pending publish approval; run fui module preview first");if(r.token!==n)throw s("invalid approval token; run fui module preview again and use the latest token after approval");if(r.moduleDir!==t)throw s(`approval token is for ${r.moduleDir}, not ${t}`);if(await $o(t)!==r.contentHash)throw s("files changed since the preview (module.json, header.html, body.html, script.js, style.css or components/*.vue); run fui module preview again and get a fresh approval");let i=await $c(t,"module publish html"),a=await(await ce(e)).publishStagedModuleDir(t);return await e.session.clearApproval(),{notes:[...o,`ok published ${a.ProjectID}/${a.ModuleID} (${t})`,...i,...await pn(t)]}}async function No(e,t){let n=await xt(e.session,"module publish json"),o=await F(at(t.moduleDir,"module.json"));if(!o.trim())throw new h("local module.json is empty or missing");try{JSON.parse(o)}catch(r){throw new h(`module.json has invalid JSON syntax: ${r.message}`)}if(await zt(t.moduleDir,"module.json",o))return{notes:[...n,`skip module.json unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let s=await $c(t.moduleDir,"module publish json");return await(await e.api()).updateModuleUI(t.projectId,t.moduleId,o),await Ee(t.moduleDir,["module.json"]).catch(()=>{return}),{notes:[...n,`ok published module.json to ${t.projectId}/${t.moduleId}`,...s,...await pn(t.moduleDir)]}}async function Ao(e,t,n){let o=`module publish ${n==="script.js"?"script":"css"}`,s=await xt(e.session,o),r=await Vr(at(t.moduleDir,n),n);if(await zt(t.moduleDir,n,r))return{notes:[...s,`skip ${n} unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let i=await e.api();if(n==="script.js")await i.updateModuleScript(t.projectId,t.moduleId,r);else await i.updateModuleCSS(t.projectId,t.moduleId,r);return await Ee(t.moduleDir,[n]).catch(()=>{return}),{notes:[...s,`ok published ${n} to ${t.projectId}/${t.moduleId}`,...await pn(t.moduleDir)]}}function dn(e){return e.moduleId?`module (${e.moduleId})`:"project (global, loaded in all modules)"}async function Rc(e,t){let n=await F(at(e.baseDir,"components",`${t}.vue`));if(!n.trim())throw new h(`${t}.vue not found in ${e.baseDir}/components/${e.moduleId?"":"; for module scope pass -m <module>"}`);return n}async function Eo(e){return U(at(e,"components","_components.json"),[])}async function Pc(e,t){let{count:n,removed:o}=await(await ce(e)).syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`${n} component(s) in ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`}; _components.json refreshed`,...o.length>0?[`removed orphaned local files: ${o.join(", ")}`]:[]]}}async function _o(e,t,n){let o=await xt(e.session,"component new"),s=await Rc(t,n),r=await ce(e);await r.syncComponentsLocal(t.projectId,t.moduleId);let i=(await Eo(t.baseDir)).find((m)=>m.comName===n);if(i)throw new h(`component "${n}" already exists in ${dn(t)} (ComID ${i.comID}); overwrite it: fui component update ${n}, or pick another name`);let{template:a,script:c,style:l}=yt(s);await r.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:u,removed:d}=await r.syncComponentsLocal(t.projectId,t.moduleId),p=(await Eo(t.baseDir)).find((m)=>m.comName===n);return await Ee(t.baseDir,[ke(at("components",`${n}.vue`))]).catch(()=>{return}),{notes:[...o,`ok created component ${n} in ${dn(t)} (ComID ${p?.comID??"not visible yet; run fui component list"})`,`${u} component(s) synced${d.length>0?`; removed orphaned: ${d.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function Do(e,t,n){let o=await xt(e.session,"component update"),s=await Rc(t,n),r=(await Eo(t.baseDir)).find((m)=>m.comName===n)?.comID;if(r===void 0)throw new h(`component "${n}" is not in _components.json of ${dn(t)}, so there is no ComID to overwrite. new component: fui component new ${n}; stale cache: fui component list, then retry`);let i=ke(at("components",`${n}.vue`));if(await zt(t.baseDir,i,s))return{notes:[...o,`skip ${n}.vue unchanged since the last publish/sync`]};let{template:a,script:c,style:l}=yt(s),u=await ce(e);await u.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComID:r,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:d,removed:p}=await u.syncComponentsLocal(t.projectId,t.moduleId);return await Ee(t.baseDir,[i]).catch(()=>{return}),{notes:[...o,`ok updated component ${n} in ${dn(t)} (ComID ${r})`,`${d} component(s) synced${p.length>0?`; removed orphaned: ${p.join(", ")}`:""}`,...await pn(t.baseDir)]}}async function Tc(e,t,n){let o=(await Eo(t.baseDir)).find((a)=>a.comName===n);if(!o)throw new h(`component "${n}" not in the local _components.json of ${dn(t)}; run fui component list first`);let s=await ce(e);await s.api.updateComponent({ProjectID:t.projectId,...t.moduleId?{ModuleID:t.moduleId}:{},ComID:o.comID,ComName:"",ComTemplate:"",ComScript:""});let{count:r,removed:i}=await s.syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`ok deleted component ${n} from ${dn(t)}; ${r} remain`,...i.length>0?[`local files removed (archived): ${i.join(", ")}`]:[]]}}async function Ec(e,t){let{id:n,...o}=t,s=await ce(e),r=await s.api.editProject(o),i=await s.projectInfoRefreshNote(Ac(o,r));return{notes:["ok project created",...n!==void 0?[`warning: projectData had id="${String(n)}"; removed to create a new project (use fui project update to edit)`]:[],...i?[i]:[]],data:`${JSON.stringify(r)}
166
+ `}}async function Nc(e,t){let n=t.id;if(n===void 0||n===null||n==="")throw new h("projectData.id is required to update (set it to the ProjectID); to create a project use fui project new");let o=await ce(e),s=await o.api.editProject(t),r=await o.projectInfoRefreshNote(Ac(t,s));return{notes:["ok project updated",...r?[r]:[]],data:`${JSON.stringify(s)}
167
+ `}}function Ac(e,t){let n=_t(e)?e.id??e.ProjectID:void 0;if(typeof n==="string"&&n.trim())return n;let o=_t(t)?t.data:void 0,s=Array.isArray(o)?o[0]:o,r=_t(s)?s.ProjectID??s.id:void 0;return typeof r==="string"&&r.trim()?r:null}async function xo(e,t){let n=await xt(e.session,"project publish-config"),o=e.paths.projectDir(t),s=await F(at(o,"project.json"));if(!s.trim())throw new h(`cannot read ${t}/project.json (missing or empty); run fui project sync -p ${t}, edit it, then publish`);let r;try{r=JSON.parse(s)}catch(l){throw new h(`${t}/project.json has invalid JSON: ${l.message}; nothing published`)}let{config:i,wasEnveloped:a}=Qr(r);if(await zt(o,"project.json",s))return{notes:[...n,`skip ${t}/project.json unchanged since the last publish/sync`]};let c=await(await e.api()).updateProjectData(t,i);return await Ee(o,["project.json"]).catch(()=>{return}),{notes:[...n,`ok published ${t}/project.json (${Object.keys(i).length} keys)`,...a?["the file is an API envelope { data: {...} }; the data layer was sent, the file is unchanged"]:[]],data:`${JSON.stringify(c)}
168
+ `}}async function eh(e,t){let n=await F(at(e,"module.json"));if(!n.trim())throw new h(`module.json is empty or missing in ${e}`);try{return JSON.parse(n)}catch(o){throw new h(`module.json has a JSON syntax error: ${o.message}; fix it, ${t} needs valid JSON`)}}async function _c(e){let t=await eh(e,"outline"),n=await Ft(e);return{notes:n?[n]:[],data:`Outline: ${e}
169
+
170
+ ${Be(Sc(t))}
171
+ `}}var th=["\u2718","\u2714","\u274C","\u26A0","\u2757","\uD83D\uDEA8","Stats:","Screenshot:","screenshot:","Full-page:"],nh=["Simulate:","Render UI:","live ","skip ","cache ","gen "];function Dc(e){let t=e.split(`
172
+ `).filter((o)=>o.trim()),n=t.filter((o)=>th.some((s)=>o.includes(s))||nh.some((s)=>o.startsWith(s)));return{text:`${n.join(`
173
+ `)}
174
+ `,kept:n.length,total:t.length}}import{join as oh}from"path";function xc(e,t){let n=Yo(),o=new Bun.CryptoHasher("sha1").update(`${t}
175
+ ${e}`).digest("hex").slice(0,16);return oh(n.replace(/\.json$/,""),"last-input",`${o}.json`)}async function Oc(e,t){let n=await le(xc(e,t));if(!n)return null;try{let o=JSON.parse(n);return o&&typeof o==="object"&&!Array.isArray(o)?o.flags??null:null}catch{return null}}async function Mc(e,t,n){await Bun.write(xc(e,t),JSON.stringify({moduleDir:e,savedAt:new Date().toISOString(),flags:n},null,2))}function Fc(e,t,n){let o={...e},s=[];for(let r of n){if(r.some((i)=>e[i]!==void 0))continue;for(let i of r)if(t[i]!==void 0)o[i]=t[i],s.push(i)}return{merged:o,reused:s}}import{basename as Xh}from"path";import{join as rh}from"path";var Lt=8,Oo=5000;function sh(e){let t=e.split(/[?#]/)[0],n=t.split("/").filter(Boolean);return(n.length?n[n.length-1]:t).toLowerCase()}function Uc(e,t,n){let o=new Map;for(let r of t){o.set(r.toLowerCase(),r);let i=Et(r);if(i)o.set(i.fnName.toLowerCase(),r)}let s=[];for(let r of e){let i=o.get(sh(r));if(i&&!n.has(i.toLowerCase()))s.push({mockKey:r,spName:i})}return s}function Jn(e){let t=e.match(/^\/([A-Za-z0-9_-]+)\/(auth\/)?([A-Za-z0-9_]+)/);if(!t)return null;return{alias:t[1],isAuth:Boolean(t[2]),fnName:t[3]}}async function ls(e){try{return JSON.parse(await B(e))}catch{return null}}function Lc(e){if(!Ct(e))return[];let t=[];for(let n of e.procedures){let o=/^CREATE\s+(?:PROCEDURE|FUNCTION)\s+(\S+)/i.exec(n);if(o)t.push(o[1])}return t}function mn(e,t){let n=e.paths,o=(r,i,a)=>rh(n.projectDir(r),"_testdata",i,`${a}.json`),s=async(r,i,a)=>{let c=await le(n.spFile(r,i,a));return c!==null&&c.trim()?c:null};return{async loadAllSchemaProcNames(r){let i=new Set;for(let a of Object.keys((await e.load(r)).connections))for(let c of Lc(await ls(n.schemaFile(r,a))))i.add(c);return[...i]},getVerifiedSpNames:()=>t.getVerifiedSpNames(),async getConnectionAlias(r,i){return me(await e.load(r),i)},async resolveApiSpName(r,i,a){let c=await ls(n.schemaFile(r,i));if(!Ct(c))return null;let l=Lc(c),u=[a,`spAPI_${a}`,`spAPI_AUTH_${a}`,`spAPIFILE_${a}`,`spAPIFILE_UPLOAD_${a}`];for(let d of u){let p=l.find((m)=>m.toLowerCase()===d.toLowerCase());if(p)return p}return null},async classifySpSafety(r,i,a){return Vi(await s(r,i,a))},async callLiveApiSp(r,i,a,c,l="GET",u,d=Oo){try{let{config:p}=await e.resolve({project:r,db:i}),m=`${Te(p.apiDomain)}/${i}/${a?"auth/":""}${c}`,w={"content-type":"application/json"};if(!a&&p.userToken)w.authorization=`Bearer ${p.userToken}`;let y=await fetch(m,{method:l,headers:w,body:u!==void 0?JSON.stringify(u):void 0,signal:AbortSignal.timeout(d)}),v=await y.text(),E;try{E=JSON.parse(v)}catch{return{ok:!1,status:y.status,error:"Ph\u1EA3n h\u1ED3i kh\xF4ng ph\u1EA3i JSON"}}if(!y.ok)return{ok:!1,status:y.status,data:E,error:`HTTP ${y.status}`};return{ok:!0,status:y.status,data:E}}catch(p){return{ok:!1,error:p instanceof Error?p.message:String(p)}}},async readTestData(r,i,a){return await ls(o(r,i,a))},async writeTestData(r,i,a,c,l){try{let u={source:c,savedAt:new Date().toISOString(),data:l};await Bun.write(o(r,i,a),JSON.stringify(u,null,2))}catch{}},async generateMockForLocalSp(r,i,a){let c=await s(r,i,a);return c===null?null:Wi(c)}}}import{promises as Xe}from"fs";import de from"path";import{homedir as ih}from"os";import{join as ds}from"path";var us=null;function ps(e){us={...e,live:mn(e.store,e.session)}}function qe(){if(!us){let e=Bun.env.FUI_MCP_WORKDIR||ds(ih(),zo),t=new jn;ps({store:new ot(new Rn(e)),session:t,api:so,activeTarget:()=>t.getTarget()})}return us}var hn=()=>qe().store.paths.root,fn=(e)=>ds(qe().store.paths.root,nt(e,"projectId")),ms=(e,t)=>ds(fn(e),"modules",nt(t,"moduleId")),Hc=()=>qe().activeTarget();async function Bc(e){return await(await qe().api()).listFrameworkImportFiles(e)}async function qc(e,t){let n=await qe().store.resolve({project:e,db:t.db});return{alias:n.alias,config:{...n.config,apiName:n.alias},note:n.note}}var Wc=(...e)=>qe().live.getConnectionAlias(...e),Vc=(...e)=>qe().live.resolveApiSpName(...e),Jc=(...e)=>qe().live.classifySpSafety(...e),Kc=(...e)=>qe().live.readTestData(...e),hs=(...e)=>qe().live.writeTestData(...e),Gc=(...e)=>qe().live.generateMockForLocalSp(...e);import{promises as ct}from"fs";import{createHash as dl}from"crypto";import K from"path";import{promises as fs}from"fs";import Qc from"path";var ah=86400000,zc={V2:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/include/vuetify.min.css","/include/toast/vue-toast-notification.css","/include/jquery-confirm.min.css","/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/moment/moment-vi.min.js","/include/numeral.min.js","/include/vue@2.js","/include/vuetify.min.js","/include/toast/vue-toast-notification.js","/defaultfunction.js","/componentTable.js","/component.js","/fastproject.js"]},V3:{styles:["https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900","/include/icons/mdi/materialdesignicons.min.css","/Framework/V3/vuetify@3.7.min.css","/Framework/V3/vuetify-labs@3.7.min.css","/Framework/V3/vue-toast-notification@3.1.3.theme-bootstrap.css","/include/jquery-confirm.min.css","/Framework/V3/projectdefaultstyle.css"],scripts:["/include/jquery-3.6.0.min.js","/include/jquery-confirm.min.js","/include/lodash.4.17.min.js","/include/numeral.min.js","/Framework/V3/dayjs-vi.min.js","/Framework/V3/vue.global.min.3.5.js","/Framework/V3/vuetify@3.7.min.js","/Framework/V3/vuetify-labs@3.7.min.js","/Framework/V3/vue-toast-notification@3.1.3.js","/Framework/V3/defaultfunction-3.0.js","/Framework/V3/fastproject-3.0.js","/Framework/V3/component-3.0.js","/Framework/V3/componentTable-3.0.js"]}};function ch(e){return Qc.join(hn(),"_cache","framework",`${e}.json`)}function lh(e,t){let n=zc[e]??zc.V3,o=(s,r)=>s.map((i,a)=>({fileID:"",ProjectID:"",ModuleID:"",FileType:r,ContentType:"link",EditContent:0,FileName:/^https?:\/\//i.test(i)?i:`${t}${i}`,FileSort:a,CreateTime:"",CreateUser:"",UpdateTime:null,UpdateUser:null,Scope:"framework"}));return[...o(n.styles,".css"),...o(n.scripts,".js")]}async function Zc(e,t){let n=ch(e),o;try{o=JSON.parse(await fs.readFile(n,"utf-8"))}catch{}let s=o?.fetchedAt?Date.now()-Date.parse(o.fetchedAt):Number.POSITIVE_INFINITY;if(Array.isArray(o?.data)&&o.data.length>0&&s<ah)return{deps:o.data,source:`cache (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`};try{let r=await Bc(e);if(r.length===0)throw Error("API tr\u1EA3 danh m\u1EE5c r\u1ED7ng");try{await fs.mkdir(Qc.dirname(n),{recursive:!0}),await fs.writeFile(n,JSON.stringify({fetchedAt:new Date().toISOString(),data:r},null,2),"utf-8")}catch{}return{deps:r,source:`API /fp/FUI_Dev_File/select {frameWork:"${e}"} (${r.length} file)`}}catch(r){if(Array.isArray(o?.data)&&o.data.length>0)return{deps:o.data,source:`cache C\u0168 (${o.data.length} file, t\u1EA3i l\xFAc ${o.fetchedAt})`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) \u2014 d\xF9ng cache c\u0169 tr\xEAn \u0111\u0129a.`};let i=lh(e,t);return{deps:i,source:`danh s\xE1ch d\u1EF1 ph\xF2ng hardcode (${i.length} file)`,warning:`Kh\xF4ng g\u1ECDi \u0111\u01B0\u1EE3c API danh m\u1EE5c framework ${e} (${r.message}) v\xE0 ch\u01B0a c\xF3 cache \u2014 `+"d\xF9ng danh s\xE1ch hardcode trong renderAssets.ts. Danh s\xE1ch n\xE0y c\xF3 th\u1EC3 \u0111\xE3 l\u1EC7ch v\u1EDBi server."}}}function uh(e){if(e===".css")return"css";if(e===".js")return"js";return}function el(e){return[...e].sort((t,n)=>t.sort-n.sort||(t.name<n.name?-1:t.name>n.name?1:0))}function tl(e,t){let n=uh(e.FileType);if(!n)return;let o=(e.FileName??"").trim();if(!o)return;return{label:`[framework] ${o}`,scope:"framework",kind:n,sort:typeof e.FileSort==="number"?e.FileSort:0,name:o,fileID:e.fileID,contentType:e.ContentType,domain:t.replace(/^https?:\/\//,""),fixHint:"file_import_list"}}var Xc="render";async function Yc(e){try{let t=await fetch(e,{headers:{Accept:"*/*"}});if(!t.ok)return{ok:!1,reason:`HTTP ${t.status} ${t.statusText}`};return{ok:!0,text:await t.text()}}catch(t){return{ok:!1,reason:t.message}}}async function nl(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/+$/,""),o=`https://${n}/_aset/fp/auth/projectdata/${e}@${n.replace(/\//g,"")}/${Xc}.js`,s=`https://${n}/_aset/fp/auth/StyleCSS/${e}/${Xc}.css`,r=[],i=[],[a,c]=await Promise.all([Yc(o),Yc(s)]),l;if(a.ok)if(/\$projectData\s*=/.test(a.text))l=a.text,r.push(`projectdata.js \u2190 ${o} (${a.text.length} bytes)`);else i.push(`projectdata.js t\u1EA3i v\u1EC1 t\u1EEB ${o} kh\xF4ng ch\u1EE9a "$projectData =" (${a.text.length} bytes) \u2014 `+"kh\xF4ng d\xF9ng, quay v\u1EC1 d\u1EF1ng $projectData + component project t\u1EEB workspace local.");else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${o}: ${a.reason} \u2014 quay v\u1EC1 d\u1EF1ng t\u1EEB workspace local.`);let u;if(c.ok)if(/<html|<!DOCTYPE/i.test(c.text.slice(0,200)))i.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 t\u1EEB ${s} l\xE0 HTML ch\u1EE9 kh\xF4ng ph\u1EA3i CSS \u2014 b\u1ECF qua.`);else if(c.text.trim())u=c.text,r.push(`StyleCSS project \u2190 ${s} (${c.text.length} bytes)`);else r.push(`StyleCSS project r\u1ED7ng (${s}) \u2014 project ch\u01B0a c\xF3 component n\xE0o khai style.`);else i.push(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c ${s}: ${c.reason} \u2014 CSS component c\u1EA5p project s\u1EBD d\u1EF1ng t\u1EEB local.`);return{js:l,css:u,jsUrl:o,cssUrl:s,notes:r,problems:i}}function gs(e,t){let n=[],o=new RegExp(`<${t}(\\s[^>]*)?>`,"gi"),s=new RegExp(`</${t}>`,"gi"),r=0;while(r<e.length){o.lastIndex=r;let i=o.exec(e);if(!i)break;let a=1,c=o.lastIndex;while(a>0){s.lastIndex=c;let l=s.exec(e);if(!l){r=e.length;break}o.lastIndex=c;let u=o.exec(e);if(u&&u.index<l.index){a+=1,c=o.lastIndex;continue}if(a-=1,c=s.lastIndex,a===0)n.push(e.slice(i.index+i[0].length,l.index)),r=s.lastIndex}}return n}function dh(e){return e.replace(/[\r\n\t]+/g," ").replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function ph(e){let t=/export\s+default\s*\{([\s\S]*)\}\s*;?\s*$/.exec(e.trim());return t?t[1].trim():void 0}function ol(e,t){let n=gs(t,"template")[0]??"",o=gs(t,"script")[0]??"",s=gs(t,"style").map((c)=>c.trim()).filter(Boolean).join(`
176
+ `);if(!n&&!o&&t.includes("Vue.component("))return{compiled:{name:e,script:t,css:s}};let r=ph(o),i=dh(n);if(r!==void 0)return{compiled:{name:e,script:`Vue.component('${e}',{template:\`${i}\`,${r}})`,css:s}};let a=/module\.exports\s*=/.test(o)?o.replace(/module\.exports\s*=/,"return "):o.trim()?o:"return {};";return{compiled:{name:e,script:`;(() => {
167
177
  const component = (() => {
168
178
  ${a}
169
179
  })() || {};
170
180
  component.template = \`${i}\`;
171
181
  Vue.component(${JSON.stringify(e)}, component);
172
- })();`,css:s},warning:`component "${e}.vue" kh\xF4ng c\xF3 khu\xF4n \`export default { \u2026 }\` \u2014 server bi\xEAn d\u1ECBch b\u1EB1ng c\xE1ch `+"n\u1ED1i th\u1EB3ng ph\u1EA7n <script> v\xE0o `{template:`\u2026`, \u2026}` n\xEAn b\u1EA3n tr\xEAn server c\xF3 th\u1EC3 l\u1ED7i c\xFA ph\xE1p. "+"renderUI \u0111\xE3 b\u1ECDc an to\xE0n \u0111\u1EC3 ch\u1EA1y ti\u1EBFp, nh\u01B0ng n\xEAn s\u1EEDa .vue v\u1EC1 \u0111\xFAng khu\xF4n."}}function gn(e){return e.replace(/\s+/g," ").trim()}function Xm(e){let t="";for(let n=0;n<e.length;n+=1){let o=e[n];if(o==="\\"&&n+1<e.length){let s=e[n+1];if(s==="\\"||s==="`"||s==="$"){t+=s,n+=1;continue}}t+=o}return t}function Ym(e){let t=new Map,n=/Vue\.component\(\s*(['"])([^'"]+)\1\s*,\s*\{\s*template\s*:\s*`/g,o=[],s;while((s=n.exec(e))!==null)o.push({name:s[2],templateStart:n.lastIndex,matchStart:s.index});for(let r=0;r<o.length;r+=1){let{name:i,templateStart:a}=o[r],c=a,l=-1;while(c<e.length){let w=e[c];if(w==="\\"){c+=2;continue}if(w==="`"){l=c;break}c+=1}if(l===-1)continue;let u=Xm(e.slice(a,l)),d=r+1<o.length?o[r+1].matchStart:e.length,p,m=e.slice(l+1,d).trim();if(m.startsWith(",")){let w=m.replace(/[;\s]+$/,"");if(w.endsWith("})"))p=w.slice(1,w.length-2).trim()}t.set(i,{template:u,body:p})}return t}function Yc(e,t,n){let o={missing:[],changed:[],changedCss:[],unverifiable:[]},s=Ym(t),r=gn(n);for(let i of e){if(i.parseError){o.unverifiable.push(`${i.name} (${i.parseError})`);continue}if(!new RegExp(`Vue\\.component\\(\\s*(['"])${i.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\1`).test(t)){o.missing.push(i.name);continue}let c=s.get(i.name);if(!c){o.unverifiable.push(`${i.name} (bundle server kh\xF4ng theo khu\xF4n ComponentBuild)`);continue}if(gn(i.template??"")!==gn(c.template)){o.changed.push(i.name);continue}if(c.body===void 0)o.unverifiable.push(`${i.name} (ch\u1EC9 \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c template, kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c th\xE2n script)`);else if(gn(i.script??"")!==gn(c.body)){o.changed.push(i.name);continue}let l=gn(i.css??"");if(l&&!r.includes(l))o.changedCss.push(i.name)}return o}function hs(e){if(e.length===0)return"";return`${e.map((t)=>t.script).join(`;
182
+ })();`,css:s},warning:`component "${e}.vue" kh\xF4ng c\xF3 khu\xF4n \`export default { \u2026 }\` \u2014 server bi\xEAn d\u1ECBch b\u1EB1ng c\xE1ch `+"n\u1ED1i th\u1EB3ng ph\u1EA7n <script> v\xE0o `{template:`\u2026`, \u2026}` n\xEAn b\u1EA3n tr\xEAn server c\xF3 th\u1EC3 l\u1ED7i c\xFA ph\xE1p. "+"renderUI \u0111\xE3 b\u1ECDc an to\xE0n \u0111\u1EC3 ch\u1EA1y ti\u1EBFp, nh\u01B0ng n\xEAn s\u1EEDa .vue v\u1EC1 \u0111\xFAng khu\xF4n."}}function gn(e){return e.replace(/\s+/g," ").trim()}function mh(e){let t="";for(let n=0;n<e.length;n+=1){let o=e[n];if(o==="\\"&&n+1<e.length){let s=e[n+1];if(s==="\\"||s==="`"||s==="$"){t+=s,n+=1;continue}}t+=o}return t}function hh(e){let t=new Map,n=/Vue\.component\(\s*(['"])([^'"]+)\1\s*,\s*\{\s*template\s*:\s*`/g,o=[],s;while((s=n.exec(e))!==null)o.push({name:s[2],templateStart:n.lastIndex,matchStart:s.index});for(let r=0;r<o.length;r+=1){let{name:i,templateStart:a}=o[r],c=a,l=-1;while(c<e.length){let w=e[c];if(w==="\\"){c+=2;continue}if(w==="`"){l=c;break}c+=1}if(l===-1)continue;let u=mh(e.slice(a,l)),d=r+1<o.length?o[r+1].matchStart:e.length,p,m=e.slice(l+1,d).trim();if(m.startsWith(",")){let w=m.replace(/[;\s]+$/,"");if(w.endsWith("})"))p=w.slice(1,w.length-2).trim()}t.set(i,{template:u,body:p})}return t}function rl(e,t,n){let o={missing:[],changed:[],changedCss:[],unverifiable:[]},s=hh(t),r=gn(n);for(let i of e){if(i.parseError){o.unverifiable.push(`${i.name} (${i.parseError})`);continue}if(!new RegExp(`Vue\\.component\\(\\s*(['"])${i.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\1`).test(t)){o.missing.push(i.name);continue}let c=s.get(i.name);if(!c){o.unverifiable.push(`${i.name} (bundle server kh\xF4ng theo khu\xF4n ComponentBuild)`);continue}if(gn(i.template??"")!==gn(c.template)){o.changed.push(i.name);continue}if(c.body===void 0)o.unverifiable.push(`${i.name} (ch\u1EC9 \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c template, kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c th\xE2n script)`);else if(gn(i.script??"")!==gn(c.body)){o.changed.push(i.name);continue}let l=gn(i.css??"");if(l&&!r.includes(l))o.changedCss.push(i.name)}return o}function ws(e){if(e.length===0)return"";return`${e.map((t)=>t.script).join(`;
173
183
  `)};
174
- `}function gs(e){let t=e.map((n)=>n.css).filter((n)=>n.trim());if(t.length===0)return"";return`${t.join(`
184
+ `}function ys(e){let t=e.map((n)=>n.css).filter((n)=>n.trim());if(t.length===0)return"";return`${t.join(`
175
185
  `)}
176
- `}var Mo="https://fui.vn",Qm=45000,Zm=700,ws=4000,Qc=1200000,ef=new Set(["script","stylesheet","image","font","media","manifest","texttrack"]),tf=new Set([".js",".css",".map",".woff",".woff2",".ttf",".otf",".eot",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp"]),Zc={project:90000,module:95000},nf={desktop:{viewport:{width:1366,height:768}},tablet:{viewport:{width:768,height:1024},hasTouch:!0,userAgent:"Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"},mobile:{viewport:{width:390,height:844},isMobile:!0,hasTouch:!0,userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"}},of=3;function sl(e,t){return K.join(e,`last-vitals-${t}.json`)}async function rf(e,t){try{let n=await at.readFile(sl(e,t),"utf8"),o=JSON.parse(n);return typeof o?.jsErrors==="number"?o:null}catch{return null}}function il(e,t){let n=[],o=(s,r,i)=>{if(r!==i)n.push(`${s} ${r}\u2192${i}${i<r?" \u2193":" \u2191"}`)};if(o("JS error",e.jsErrors,t.jsErrors),o("init error",e.initErrors,t.initErrors),o("Vue warn",e.vueWarns,t.vueWarns),o("file m\xF4i tr\u01B0\u1EDDng thi\u1EBFu",e.envMissing,t.envMissing),o("text nghi v\u1EA5n",e.suspectTexts,t.suspectTexts),o("\u1EA3nh v\u1EE1",e.brokenImages,t.brokenImages),o("canvas ch\u1EBFt",e.deadCanvas,t.deadCanvas),o("text d\xEDnh/ch\u1ED3ng l\u1EA5n",e.textCollisions,t.textCollisions),o("assert h\u1ECFng",e.assertFail,t.assertFail),e.appEmpty!==t.appEmpty)n.push(t.appEmpty?"app render R\u1ED6NG (tr\u01B0\u1EDBc c\xF3 n\u1ED9i dung) \u2191":"app \u0111\xE3 c\xF3 n\u1ED9i dung \u2193");if(e.overflowX!==t.overflowX)n.push(t.overflowX?"tr\xE0n ngang (tr\u01B0\u1EDBc kh\xF4ng) \u2191":"h\u1EBFt tr\xE0n ngang \u2193");return n}function sf(e,t){let n=il(e,t),o=e.ts.replace("T"," ").slice(0,19);if(n.length>0)return`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): ${n.join(" \xB7 ")}`;return ks(t)?`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i \u2014 thay \u0111\u1ED5i v\u1EEBa r\u1ED3i kh\xF4ng ch\u1EA1m t\u1EDBi l\u1ED7i \u0111ang c\xF3. \u0110\u1ED5i h\u01B0\u1EDBng ch\u1EA9n \u0111o\xE1n, \u0111\u1EEBng render l\u1EA1i c\xF9ng m\u1ED9t c\xE1ch.`:`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): kh\xF4ng \u0111\u1ED5i \u2014 c\u1EA3 hai l\u01B0\u1EE3t \u0111\u1EC1u s\u1EA1ch.`}function el(e,t){let n=new Set;for(let o of e){let s=String(o).split(`
177
- `)[0].trim();if(s)n.add(s.length>200?s.slice(0,200)+"\u2026":s);if(n.size>=t)break}return[...n]}function af(e,t){return["",`\uD83D\uDED1 \u0110ANG GI\u1EACM CH\xC2N \u2014 ${e} l\u01B0\u1EE3t render h\u1ECFng li\xEAn ti\u1EBFp m\xE0 KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i.`," Render l\u1EA1i l\u1EA7n n\u1EEFa theo c\xF9ng h\u01B0\u1EDBng s\u1EBD cho \u0111\xFAng k\u1EBFt qu\u1EA3 n\xE0y. Ch\u1ECDn M\u1ED8T trong ba:",t.length>0?" 1. Ch\u1EA1y t\u1EA7ng r\u1EBB h\u01A1n \u0111\u01B0\u1EE3c n\xEAu \u1EDF d\xF2ng \u21AA tr\xEAn \u2014 n\xF3 ch\u1EC9 th\u1EB3ng v\xE0o ch\u1ED7 h\u1ECFng.":" 1. L\xF9i v\u1EC1 t\u1EA7ng t\u0129nh: module_validate (l\u1ED7i quy t\u1EAFc) \u2192 module_outline (c\u1EA5u tr\xFAc) \u2192 module_simulate kh\xF4ng renderUI (action/watch)."," 2. \u0110\u1ECDc l\u1EA1i screenshot + kh\u1ED1i l\u1ED7i \u1EDF cu\u1ED1i report b\u1EB1ng m\u1EAFt, \u0111\u1EEBng ch\u1EC9 \u0111\u1ECDc con s\u1ED1."," 3. H\u1ECEI NG\u01AF\u1EDCI D\xD9NG. Ba l\u01B0\u1EE3t kh\xF4ng nh\xFAc nh\xEDch th\u01B0\u1EDDng ngh\u0129a l\xE0 gi\u1EA3 \u0111\u1ECBnh ban \u0111\u1EA7u sai, kh\xF4ng ph\u1EA3i s\u1EEDa ch\u01B0a \u0111\u1EE7."]}function ks(e){return e.jsErrors>0||e.vueWarns>0||e.envMissing>0||e.appEmpty||e.overflowX||e.suspectTexts>0||e.brokenImages>0||e.deadCanvas>0||e.textCollisions>0||e.assertFail>0}function cf(e,t,n,o){let s=["\u2550\u2550\u2550 CH\u1EA8N \u0110O\xC1N \u2550\u2550\u2550"];s.push(e.envMissing>0?`[M\xD4I TR\u01AF\u1EDCNG] ${e.envMissing} file import/asset KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c \u2192 s\u1EEDa ph\u1EA7n n\xE0y TR\u01AF\u1EDAC (project_sync / module_get / file_import_*). L\u1ED7i \u1EDF hai nh\xF3m d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3.`:"[M\xD4I TR\u01AF\u1EDCNG] \u2713 m\u1ECDi import/asset n\u1EA1p \u0111\u1EE7.");let r=[];if(e.jsErrors>0)r.push(`${e.jsErrors} JS error${e.initErrors?` (${e.initErrors} l\xFAc init)`:""}`);if(e.vueWarns>0)r.push(`${e.vueWarns} [Vue warn]`);if(e.assertFail>0)r.push(`${e.assertFail} assert h\u1ECFng`);s.push(r.length>0?`[MODULE] ${r.join(" \xB7 ")} \u2192 l\u1ED7i n\u1EB1m trong module.json / script.js / component.`:"[MODULE] \u2713 kh\xF4ng JS error, kh\xF4ng Vue warn.");for(let a of el(n,3))s.push(` \u2718 ${a}`);for(let a of el(o,2))s.push(` \u26A0 ${a}`);let i=[];if(e.appEmpty)i.push("app render R\u1ED6NG");if(e.overflowX)i.push("tr\xE0n ngang viewport");if(e.suspectTexts>0)i.push(`${e.suspectTexts} text nghi v\u1EA5n (undefined/NaN)`);if(e.brokenImages>0)i.push(`${e.brokenImages} \u1EA3nh v\u1EE1`);if(e.deadCanvas>0)i.push(`${e.deadCanvas} canvas chart 0-size`);if(e.textCollisions>0)i.push(`${e.textCollisions} ch\u1ED7 text d\xEDnh/ch\u1ED3ng l\u1EA5n`);s.push(i.length>0?`[HI\u1EC2N TH\u1ECA] ${i.join(" \xB7 ")} \u2192 xem screenshot; \u0111\xE2y l\xE0 nh\xF3m CH\u1EC8 renderUI th\u1EA5y \u0111\u01B0\u1EE3c.`:"[HI\u1EC2N TH\u1ECA] \u2713 DOM audit s\u1EA1ch.");for(let a of t)s.push(` \u21AA ${a}`);return s}function lf(e,t,n){let o=[];if(n.some((s)=>/\bis not defined\b/.test(s)))o.push("`\u2026 is not defined` = m\u1ED9t \u0111\u1ECBnh danh tr\u1EA7n kh\xF4ng c\xF3 tr\xEAn window. Ca th\u01B0\u1EDDng g\u1EB7p nh\u1EA5t: `EXE`/`FUN` g\u1ECDi h\xE0m ch\u01B0a vi\u1EBFt trong script.js \u2014 ho\u1EB7c vi\u1EBFt r\u1ED3i m\xE0 qu\xEAn `module_publish_script` (module_publish_json KH\xD4NG \u0111\u1EA9y script.js). So\xE1t t\xEAn h\xE0m trong script.js tr\u01B0\u1EDBc khi \u0111i t\xECm ch\u1ED7 kh\xE1c.");if(e.appEmpty&&e.jsErrors===0&&e.vueWarns===0&&e.envMissing===0)o.push("Trang tr\u1EAFng m\xE0 KH\xD4NG c\xF3 JS error/Vue warn l\xE0 d\u1EA5u hi\u1EC7u kinh \u0111i\u1EC3n c\u1EE7a statement trong m\u1ED9t `:attr` (Vue nu\u1ED1t l\u1ED7i compile template) \u2014 ch\u1EA1y module_validate, n\xF3 b\u1EAFt t\u0129nh \u0111\u01B0\u1EE3c ca n\xE0y.");if(t.some((s)=>/is not defined|Property or method/i.test(s)))o.push('[Vue warn] ki\u1EC3u "property is not defined" = bi\u1EBFn d\xF9ng trong template ch\u01B0a khai trong data[] \u2014 module_validate b\u1EAFt \u0111\u01B0\u1EE3c (v-model/watch/state), kh\xF4ng c\u1EA7n browser.');if(e.suspectTexts>0&&e.jsErrors===0)o.push('Text "undefined"/"NaN" th\u01B0\u1EDDng l\xE0 OUT/mapData tr\u1ECF sai kho\xE1 ch\u1EE9 kh\xF4ng ph\u1EA3i l\u1ED7i hi\u1EC3n th\u1ECB \u2014 module_simulate (engine vm) ch\u1EC9 ra ch\u1ED7 g\xE1n, ch\u1EA1y nhanh h\u01A1n nhi\u1EC1u.');return o}async function uf(){for(let e of["playwright","playwright-core"])for(let t of[void 0,process.cwd()])try{return await(t?import(Bun.resolveSync(e,t)):import(e))}catch{}throw Error(`rendering needs Playwright (optional, not installed). Install once:
186
+ `}var Fo="https://fui.vn",fh=45000,gh=700,bs=4000,sl=1200000,wh=new Set(["script","stylesheet","image","font","media","manifest","texttrack"]),yh=new Set([".js",".css",".map",".woff",".woff2",".ttf",".otf",".eot",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp"]),il={project:90000,module:95000},bh={desktop:{viewport:{width:1366,height:768}},tablet:{viewport:{width:768,height:1024},hasTouch:!0,userAgent:"Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"},mobile:{viewport:{width:390,height:844},isMobile:!0,hasTouch:!0,userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"}},kh=3;function pl(e,t){return K.join(e,`last-vitals-${t}.json`)}async function vh(e,t){try{let n=await ct.readFile(pl(e,t),"utf8"),o=JSON.parse(n);return typeof o?.jsErrors==="number"?o:null}catch{return null}}function ml(e,t){let n=[],o=(s,r,i)=>{if(r!==i)n.push(`${s} ${r}\u2192${i}${i<r?" \u2193":" \u2191"}`)};if(o("JS error",e.jsErrors,t.jsErrors),o("init error",e.initErrors,t.initErrors),o("Vue warn",e.vueWarns,t.vueWarns),o("file m\xF4i tr\u01B0\u1EDDng thi\u1EBFu",e.envMissing,t.envMissing),o("text nghi v\u1EA5n",e.suspectTexts,t.suspectTexts),o("\u1EA3nh v\u1EE1",e.brokenImages,t.brokenImages),o("canvas ch\u1EBFt",e.deadCanvas,t.deadCanvas),o("text d\xEDnh/ch\u1ED3ng l\u1EA5n",e.textCollisions,t.textCollisions),o("assert h\u1ECFng",e.assertFail,t.assertFail),e.appEmpty!==t.appEmpty)n.push(t.appEmpty?"app render R\u1ED6NG (tr\u01B0\u1EDBc c\xF3 n\u1ED9i dung) \u2191":"app \u0111\xE3 c\xF3 n\u1ED9i dung \u2193");if(e.overflowX!==t.overflowX)n.push(t.overflowX?"tr\xE0n ngang (tr\u01B0\u1EDBc kh\xF4ng) \u2191":"h\u1EBFt tr\xE0n ngang \u2193");return n}function Sh(e,t){let n=ml(e,t),o=e.ts.replace("T"," ").slice(0,19);if(n.length>0)return`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): ${n.join(" \xB7 ")}`;return Ss(t)?`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i \u2014 thay \u0111\u1ED5i v\u1EEBa r\u1ED3i kh\xF4ng ch\u1EA1m t\u1EDBi l\u1ED7i \u0111ang c\xF3. \u0110\u1ED5i h\u01B0\u1EDBng ch\u1EA9n \u0111o\xE1n, \u0111\u1EEBng render l\u1EA1i c\xF9ng m\u1ED9t c\xE1ch.`:`\u0394 so v\u1EDBi l\u01B0\u1EE3t tr\u01B0\u1EDBc (${o}): kh\xF4ng \u0111\u1ED5i \u2014 c\u1EA3 hai l\u01B0\u1EE3t \u0111\u1EC1u s\u1EA1ch.`}function al(e,t){let n=new Set;for(let o of e){let s=String(o).split(`
187
+ `)[0].trim();if(s)n.add(s.length>200?s.slice(0,200)+"\u2026":s);if(n.size>=t)break}return[...n]}function $h(e,t){return["",`\uD83D\uDED1 \u0110ANG GI\u1EACM CH\xC2N \u2014 ${e} l\u01B0\u1EE3t render h\u1ECFng li\xEAn ti\u1EBFp m\xE0 KH\xD4NG ch\u1EC9 s\u1ED1 n\xE0o \u0111\u1ED5i.`," Render l\u1EA1i l\u1EA7n n\u1EEFa theo c\xF9ng h\u01B0\u1EDBng s\u1EBD cho \u0111\xFAng k\u1EBFt qu\u1EA3 n\xE0y. Ch\u1ECDn M\u1ED8T trong ba:",t.length>0?" 1. Ch\u1EA1y t\u1EA7ng r\u1EBB h\u01A1n \u0111\u01B0\u1EE3c n\xEAu \u1EDF d\xF2ng \u21AA tr\xEAn \u2014 n\xF3 ch\u1EC9 th\u1EB3ng v\xE0o ch\u1ED7 h\u1ECFng.":" 1. L\xF9i v\u1EC1 t\u1EA7ng t\u0129nh: module_validate (l\u1ED7i quy t\u1EAFc) \u2192 module_outline (c\u1EA5u tr\xFAc) \u2192 module_simulate kh\xF4ng renderUI (action/watch)."," 2. \u0110\u1ECDc l\u1EA1i screenshot + kh\u1ED1i l\u1ED7i \u1EDF cu\u1ED1i report b\u1EB1ng m\u1EAFt, \u0111\u1EEBng ch\u1EC9 \u0111\u1ECDc con s\u1ED1."," 3. H\u1ECEI NG\u01AF\u1EDCI D\xD9NG. Ba l\u01B0\u1EE3t kh\xF4ng nh\xFAc nh\xEDch th\u01B0\u1EDDng ngh\u0129a l\xE0 gi\u1EA3 \u0111\u1ECBnh ban \u0111\u1EA7u sai, kh\xF4ng ph\u1EA3i s\u1EEDa ch\u01B0a \u0111\u1EE7."]}function Ss(e){return e.jsErrors>0||e.vueWarns>0||e.envMissing>0||e.appEmpty||e.overflowX||e.suspectTexts>0||e.brokenImages>0||e.deadCanvas>0||e.textCollisions>0||e.assertFail>0}function jh(e,t,n,o){let s=["\u2550\u2550\u2550 CH\u1EA8N \u0110O\xC1N \u2550\u2550\u2550"];s.push(e.envMissing>0?`[M\xD4I TR\u01AF\u1EDCNG] ${e.envMissing} file import/asset KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c \u2192 s\u1EEDa ph\u1EA7n n\xE0y TR\u01AF\u1EDAC (project_sync / module_get / file_import_*). L\u1ED7i \u1EDF hai nh\xF3m d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3.`:"[M\xD4I TR\u01AF\u1EDCNG] \u2713 m\u1ECDi import/asset n\u1EA1p \u0111\u1EE7.");let r=[];if(e.jsErrors>0)r.push(`${e.jsErrors} JS error${e.initErrors?` (${e.initErrors} l\xFAc init)`:""}`);if(e.vueWarns>0)r.push(`${e.vueWarns} [Vue warn]`);if(e.assertFail>0)r.push(`${e.assertFail} assert h\u1ECFng`);s.push(r.length>0?`[MODULE] ${r.join(" \xB7 ")} \u2192 l\u1ED7i n\u1EB1m trong module.json / script.js / component.`:"[MODULE] \u2713 kh\xF4ng JS error, kh\xF4ng Vue warn.");for(let a of al(n,3))s.push(` \u2718 ${a}`);for(let a of al(o,2))s.push(` \u26A0 ${a}`);let i=[];if(e.appEmpty)i.push("app render R\u1ED6NG");if(e.overflowX)i.push("tr\xE0n ngang viewport");if(e.suspectTexts>0)i.push(`${e.suspectTexts} text nghi v\u1EA5n (undefined/NaN)`);if(e.brokenImages>0)i.push(`${e.brokenImages} \u1EA3nh v\u1EE1`);if(e.deadCanvas>0)i.push(`${e.deadCanvas} canvas chart 0-size`);if(e.textCollisions>0)i.push(`${e.textCollisions} ch\u1ED7 text d\xEDnh/ch\u1ED3ng l\u1EA5n`);s.push(i.length>0?`[HI\u1EC2N TH\u1ECA] ${i.join(" \xB7 ")} \u2192 xem screenshot; \u0111\xE2y l\xE0 nh\xF3m CH\u1EC8 renderUI th\u1EA5y \u0111\u01B0\u1EE3c.`:"[HI\u1EC2N TH\u1ECA] \u2713 DOM audit s\u1EA1ch.");for(let a of t)s.push(` \u21AA ${a}`);return s}function Ih(e,t,n){let o=[];if(n.some((s)=>/\bis not defined\b/.test(s)))o.push("`\u2026 is not defined` = m\u1ED9t \u0111\u1ECBnh danh tr\u1EA7n kh\xF4ng c\xF3 tr\xEAn window. Ca th\u01B0\u1EDDng g\u1EB7p nh\u1EA5t: `EXE`/`FUN` g\u1ECDi h\xE0m ch\u01B0a vi\u1EBFt trong script.js \u2014 ho\u1EB7c vi\u1EBFt r\u1ED3i m\xE0 qu\xEAn `module_publish_script` (module_publish_json KH\xD4NG \u0111\u1EA9y script.js). So\xE1t t\xEAn h\xE0m trong script.js tr\u01B0\u1EDBc khi \u0111i t\xECm ch\u1ED7 kh\xE1c.");if(e.appEmpty&&e.jsErrors===0&&e.vueWarns===0&&e.envMissing===0)o.push("Trang tr\u1EAFng m\xE0 KH\xD4NG c\xF3 JS error/Vue warn l\xE0 d\u1EA5u hi\u1EC7u kinh \u0111i\u1EC3n c\u1EE7a statement trong m\u1ED9t `:attr` (Vue nu\u1ED1t l\u1ED7i compile template) \u2014 ch\u1EA1y module_validate, n\xF3 b\u1EAFt t\u0129nh \u0111\u01B0\u1EE3c ca n\xE0y.");if(t.some((s)=>/is not defined|Property or method/i.test(s)))o.push('[Vue warn] ki\u1EC3u "property is not defined" = bi\u1EBFn d\xF9ng trong template ch\u01B0a khai trong data[] \u2014 module_validate b\u1EAFt \u0111\u01B0\u1EE3c (v-model/watch/state), kh\xF4ng c\u1EA7n browser.');if(e.suspectTexts>0&&e.jsErrors===0)o.push('Text "undefined"/"NaN" th\u01B0\u1EDDng l\xE0 OUT/mapData tr\u1ECF sai kho\xE1 ch\u1EE9 kh\xF4ng ph\u1EA3i l\u1ED7i hi\u1EC3n th\u1ECB \u2014 module_simulate (engine vm) ch\u1EC9 ra ch\u1ED7 g\xE1n, ch\u1EA1y nhanh h\u01A1n nhi\u1EC1u.');return o}async function Ch(){for(let e of["playwright","playwright-core"])for(let t of[void 0,process.cwd()])try{return await(t?import(Bun.resolveSync(e,t)):import(e))}catch{}throw Error(`rendering needs Playwright (optional, not installed). Install once:
178
188
  bun add -g playwright
179
189
  bunx playwright install chromium
180
- (module simulate without --render works without Playwright.)`)}var df=60000,ys=new Map;function $s(){return K.join(fn(),"_cache","cdn")}function vs(e){return rl("sha1").update(e).digest("hex")}function al(e){return rl("sha256").update(e).digest("hex")}async function pf(e){let t=vs(e),n=$s();try{let o=JSON.parse(await at.readFile(K.join(n,`${t}.meta.json`),"utf-8"));return{body:await at.readFile(K.join(n,`${t}.bin`)),contentType:o.contentType??cl(e),meta:o}}catch{return}}async function mf(e,t,n,o){try{let s=vs(e),r=$s();await at.mkdir(r,{recursive:!0}),await at.writeFile(K.join(r,`${s}.bin`),t);let i={url:e,contentType:n,cachedAt:new Date().toISOString(),etag:o.etag,lastModified:o.lastModified,sha256:al(t)};await at.writeFile(K.join(r,`${s}.meta.json`),JSON.stringify(i),"utf-8")}catch{}}async function ff(e,t){try{let n=vs(e);await at.writeFile(K.join($s(),`${n}.meta.json`),JSON.stringify({...t,cachedAt:new Date().toISOString()}),"utf-8")}catch{}}function bs(e){if(!e)return"kh\xF4ng r\xF5 tu\u1ED5i";let t=Date.now()-Date.parse(e);if(!Number.isFinite(t))return"kh\xF4ng r\xF5 tu\u1ED5i";let n=Math.floor(t/86400000);if(n>=1)return`c\u0169 ${n} ng\xE0y`;let o=Math.floor(t/3600000);return o>=1?`c\u0169 ${o} gi\u1EDD`:"c\u0169 d\u01B0\u1EDBi 1 gi\u1EDD"}async function hf(e,t){let n=t.refresh?void 0:await pf(e);if(t.offline){if(!n)throw Error("offline: ch\u01B0a c\xF3 b\u1EA3n cache n\xE0o cho URL n\xE0y");return{body:n.body,contentType:n.contentType,origin:"stale",note:`offline \u2014 d\xF9ng cache ${bs(n.meta.cachedAt)}`}}if(n&&!t.refresh){let r=ys.get(e);if(r!==void 0&&Date.now()-r<df)return{body:n.body,contentType:n.contentType,origin:"memo"}}let o={},s=Boolean(n?.meta.etag||n?.meta.lastModified);if(n&&s&&!t.refresh){if(n.meta.etag)o["If-None-Match"]=n.meta.etag;if(n.meta.lastModified)o["If-Modified-Since"]=n.meta.lastModified}try{let r=await fetch(e,{headers:o,signal:AbortSignal.timeout(20000)});if(r.status===304&&n)return ys.set(e,Date.now()),await ff(e,n.meta),{body:n.body,contentType:n.contentType,origin:"revalidated"};if(!r.ok)throw Error(`HTTP ${r.status}`);let i=Buffer.from(await r.arrayBuffer()),a=r.headers.get("content-type")??cl(e),c=r.headers.get("etag")??void 0,l=r.headers.get("last-modified")??void 0;await mf(e,i,a,{etag:c,lastModified:l}),ys.set(e,Date.now());let u=Boolean(n?.meta.sha256&&n.meta.sha256!==al(i));return{body:i,contentType:a,origin:n?"updated":"fetched",changed:u,note:u?`n\u1ED9i dung \u0110\u1ED4I so v\u1EDBi b\u1EA3n cache ${bs(n?.meta.cachedAt)}`:void 0}}catch(r){if(n)return{body:n.body,contentType:n.contentType,origin:"stale",note:`kh\xF4ng revalidate \u0111\u01B0\u1EE3c (${r.message}) \u2014 d\xF9ng cache ${bs(n.meta.cachedAt)}`};throw r}}function cl(e){switch(K.extname(new URL(e).pathname).toLowerCase()){case".js":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";default:return"application/octet-stream"}}function wn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function bt(e,t){return e.replace(new RegExp(`</(?=${t})`,"gi"),"<\\/")}function Fo(e,t){return JSON.stringify(e,null,t).replace(/</g,"\\u003c")}function gf(e,t){if(!e)return;let n=e.trim();if(!n)return;if(/^https?:\/\//i.test(n))return n;if(n.startsWith("//"))return`https:${n}`;if(n.startsWith("/"))return`https://${t}${n}`;return}function tl(e){if(typeof e!=="string")return[];let t=e.trim();if(!t)return[];if(t.startsWith("["))try{let n=JSON.parse(t);if(Array.isArray(n))return n.filter((o)=>typeof o==="string").map((o)=>o.trim()).filter(Boolean)}catch{}return[t]}function wf(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function yf(e,t,n){let o=[],s=wf(e.url),r=tl(e.mDomain),i=tl(t?.pDomain),a=s.host;if(!a)a=r[0]??i[0]??n,o.push("_moduleInfo.json kh\xF4ng c\xF3 url k\xE8m host \u2014 origin l\u1EA5y t\u1EEB "+`${r.length?"mDomain":i.length?"pDomain":"domain asset m\u1EB7c \u0111\u1ECBnh"}: ${a}. `+"Ch\u1EA1y `module_get` \u0111\u1EC3 c\xF3 url th\u1EADt n\u1EBFu render sai domain.");let c=[...new Set([...s.host?[s.host]:[],...r,...i])];if(c.length>1)o.push(`Project/module ph\u1EE5c v\u1EE5 tr\xEAn nhi\u1EC1u domain (${c.join(", ")}) \u2014 l\u1EA7n render n\xE0y ch\u1EA1y \u1EDF "${a}". `+"v_Set.domainSetting c\u1EE7a domain kh\xE1c KH\xD4NG \u0111\u01B0\u1EE3c \xE1p d\u1EE5ng; mu\u1ED1n th\u1EED domain kh\xE1c th\xEC render module c\xF3 url thu\u1ED9c domain \u0111\xF3.");let l=s.path&&s.path!=="/"?s.path:`/${e.ModuleID}`;return{host:a,basePath:l,notes:o}}function nl(e,t){if(!e)return null;let o=new RegExp(`var\\s+\\$${t}\\s*=\\s*\\{`).exec(e);if(!o)return null;let s=e.indexOf("{",o.index),r=0,i=null;for(let a=s;a<e.length;a+=1){let c=e[a];if(i){if(c==="\\"){a+=1;continue}if(c===i)i=null;continue}if(c==='"'||c==="'"){i=c;continue}if(c==="{")r+=1;else if(c==="}"){if(r-=1,r===0)try{return JSON.parse(e.slice(s,a+1))}catch{return null}}}return null}function bf(e,t){let n={},o={},s=(i,a)=>{if(!j(i))return;for(let[c,l]of Object.entries(i))n[c]=l,o[c]=a};s(e.groupSetting,"$projectGroupSetting"),s(e.projectData,"$projectData"),s(e.moduleSet,"module.json set");let r=n.domainSetting;if(j(r))s(r[t],`domainSetting[${t}]`),delete n.domainSetting,delete o.domainSetting;return{settings:n,origins:o}}async function ol(e){let t=await Me(e),n=[],o=[];for(let s of t){let r=await F(K.join(e,s));if(!r.trim())continue;let{compiled:i,warning:a}=Xc(s.replace(/\.vue$/,""),r);if(n.push(i),a)o.push(a)}return{components:n,warnings:o}}async function kf(e){let t=await Me(e),n=[];for(let o of t){let s=o.replace(/\.vue$/i,""),r=await F(K.join(e,o));if(!r.trim())continue;try{let{template:i,script:a,style:c}=wt(r);n.push({name:s,template:i,script:a,css:c})}catch(i){n.push({name:s,parseError:i.message})}}return n}async function $f(e,t,n){let o=[],s=[],r=[],i={project:[],module:[]},a=[{scope:"project",items:await U(K.join(t,"imports","_imports.json"),[]),baseDir:t,fixHint:"project_sync"},{scope:"module",items:await U(K.join(e,"imports","_imports.json"),[]),baseDir:e,fixHint:"module_get"}];for(let{scope:c,items:l,baseDir:u,fixHint:d}of a){if(!Array.isArray(l))continue;for(let p of l){if(!p||!p.name)continue;let m=`[${c}] ${p.name}`,w=p.type===".css"?"css":p.type===".js"?"js":void 0;if(!w){s.push(`import ${m}: type "${p.type??"(tr\u1ED1ng)"}" kh\xF4ng ph\u1EA3i .js/.css \u2014 kh\xF4ng n\u1EA1p v\xE0o trang render`);continue}if(typeof p.sort!=="number")i[c].push(p.name);o.push({label:m,scope:c,kind:w,sort:typeof p.sort==="number"?p.sort:Zc[c],name:p.name,fileID:p.fileID,contentType:p.contentType,filePath:p.filePath,baseDir:u,domain:n,fixHint:d})}}for(let c of["project","module"]){let l=i[c];if(l.length===0)continue;r.push(`${l.length} import c\u1EA5p ${c} kh\xF4ng c\xF3 FileSort trong _imports.json (${l.join(", ")}) \u2192 t\u1EA1m x\u1EBFp cu\u1ED1i `+`(${Zc[c]}). Th\u1EE9 t\u1EF1 n\u1EA1p c\xF3 th\u1EC3 kh\xE1c production; ch\u1EA1y \`${c==="project"?"project_sync":"module_get"}\` \u0111\u1EC3 l\u1EA5y FileSort th\u1EADt.`)}return{entries:o,problems:s,notes:r}}async function vf(e){let t=[],n=[],o=[],s=[],r=[],i=new Set;for(let a of Kc(e)){let c=a.fileID?`id:${a.fileID}`:`name:${a.scope}:${a.name}`;if(i.has(c))continue;if(a.filePath&&a.baseDir){let p=await F(K.join(a.baseDir,a.filePath));if(p.trim()){i.add(c);let m=wn(a.label);if(a.kind==="css")t.push(`<style data-fui-import="${m}">
190
+ (module simulate without --render works without Playwright.)`)}var Rh=60000,ks=new Map;function $s(){return K.join(hn(),"_cache","cdn")}function js(e){return dl("sha1").update(e).digest("hex")}function hl(e){return dl("sha256").update(e).digest("hex")}async function Ph(e){let t=js(e),n=$s();try{let o=JSON.parse(await ct.readFile(K.join(n,`${t}.meta.json`),"utf-8"));return{body:await ct.readFile(K.join(n,`${t}.bin`)),contentType:o.contentType??fl(e),meta:o}}catch{return}}async function Th(e,t,n,o){try{let s=js(e),r=$s();await ct.mkdir(r,{recursive:!0}),await ct.writeFile(K.join(r,`${s}.bin`),t);let i={url:e,contentType:n,cachedAt:new Date().toISOString(),etag:o.etag,lastModified:o.lastModified,sha256:hl(t)};await ct.writeFile(K.join(r,`${s}.meta.json`),JSON.stringify(i),"utf-8")}catch{}}async function Eh(e,t){try{let n=js(e);await ct.writeFile(K.join($s(),`${n}.meta.json`),JSON.stringify({...t,cachedAt:new Date().toISOString()}),"utf-8")}catch{}}function vs(e){if(!e)return"kh\xF4ng r\xF5 tu\u1ED5i";let t=Date.now()-Date.parse(e);if(!Number.isFinite(t))return"kh\xF4ng r\xF5 tu\u1ED5i";let n=Math.floor(t/86400000);if(n>=1)return`c\u0169 ${n} ng\xE0y`;let o=Math.floor(t/3600000);return o>=1?`c\u0169 ${o} gi\u1EDD`:"c\u0169 d\u01B0\u1EDBi 1 gi\u1EDD"}async function Nh(e,t){let n=t.refresh?void 0:await Ph(e);if(t.offline){if(!n)throw Error("offline: ch\u01B0a c\xF3 b\u1EA3n cache n\xE0o cho URL n\xE0y");return{body:n.body,contentType:n.contentType,origin:"stale",note:`offline \u2014 d\xF9ng cache ${vs(n.meta.cachedAt)}`}}if(n&&!t.refresh){let r=ks.get(e);if(r!==void 0&&Date.now()-r<Rh)return{body:n.body,contentType:n.contentType,origin:"memo"}}let o={},s=Boolean(n?.meta.etag||n?.meta.lastModified);if(n&&s&&!t.refresh){if(n.meta.etag)o["If-None-Match"]=n.meta.etag;if(n.meta.lastModified)o["If-Modified-Since"]=n.meta.lastModified}try{let r=await fetch(e,{headers:o,signal:AbortSignal.timeout(20000)});if(r.status===304&&n)return ks.set(e,Date.now()),await Eh(e,n.meta),{body:n.body,contentType:n.contentType,origin:"revalidated"};if(!r.ok)throw Error(`HTTP ${r.status}`);let i=Buffer.from(await r.arrayBuffer()),a=r.headers.get("content-type")??fl(e),c=r.headers.get("etag")??void 0,l=r.headers.get("last-modified")??void 0;await Th(e,i,a,{etag:c,lastModified:l}),ks.set(e,Date.now());let u=Boolean(n?.meta.sha256&&n.meta.sha256!==hl(i));return{body:i,contentType:a,origin:n?"updated":"fetched",changed:u,note:u?`n\u1ED9i dung \u0110\u1ED4I so v\u1EDBi b\u1EA3n cache ${vs(n?.meta.cachedAt)}`:void 0}}catch(r){if(n)return{body:n.body,contentType:n.contentType,origin:"stale",note:`kh\xF4ng revalidate \u0111\u01B0\u1EE3c (${r.message}) \u2014 d\xF9ng cache ${vs(n.meta.cachedAt)}`};throw r}}function fl(e){switch(K.extname(new URL(e).pathname).toLowerCase()){case".js":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".woff":return"font/woff";case".woff2":return"font/woff2";case".ttf":return"font/ttf";default:return"application/octet-stream"}}function wn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function bt(e,t){return e.replace(new RegExp(`</(?=${t})`,"gi"),"<\\/")}function Lo(e,t){return JSON.stringify(e,null,t).replace(/</g,"\\u003c")}function Ah(e,t){if(!e)return;let n=e.trim();if(!n)return;if(/^https?:\/\//i.test(n))return n;if(n.startsWith("//"))return`https:${n}`;if(n.startsWith("/"))return`https://${t}${n}`;return}function cl(e){if(typeof e!=="string")return[];let t=e.trim();if(!t)return[];if(t.startsWith("["))try{let n=JSON.parse(t);if(Array.isArray(n))return n.filter((o)=>typeof o==="string").map((o)=>o.trim()).filter(Boolean)}catch{}return[t]}function _h(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let s=new URL(n);return{host:s.host,path:s.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function Dh(e,t,n){let o=[],s=_h(e.url),r=cl(e.mDomain),i=cl(t?.pDomain),a=s.host;if(!a)a=r[0]??i[0]??n,o.push("_moduleInfo.json kh\xF4ng c\xF3 url k\xE8m host \u2014 origin l\u1EA5y t\u1EEB "+`${r.length?"mDomain":i.length?"pDomain":"domain asset m\u1EB7c \u0111\u1ECBnh"}: ${a}. `+"Ch\u1EA1y `module_get` \u0111\u1EC3 c\xF3 url th\u1EADt n\u1EBFu render sai domain.");let c=[...new Set([...s.host?[s.host]:[],...r,...i])];if(c.length>1)o.push(`Project/module ph\u1EE5c v\u1EE5 tr\xEAn nhi\u1EC1u domain (${c.join(", ")}) \u2014 l\u1EA7n render n\xE0y ch\u1EA1y \u1EDF "${a}". `+"v_Set.domainSetting c\u1EE7a domain kh\xE1c KH\xD4NG \u0111\u01B0\u1EE3c \xE1p d\u1EE5ng; mu\u1ED1n th\u1EED domain kh\xE1c th\xEC render module c\xF3 url thu\u1ED9c domain \u0111\xF3.");let l=s.path&&s.path!=="/"?s.path:`/${e.ModuleID}`;return{host:a,basePath:l,notes:o}}function ll(e,t){if(!e)return null;let o=new RegExp(`var\\s+\\$${t}\\s*=\\s*\\{`).exec(e);if(!o)return null;let s=e.indexOf("{",o.index),r=0,i=null;for(let a=s;a<e.length;a+=1){let c=e[a];if(i){if(c==="\\"){a+=1;continue}if(c===i)i=null;continue}if(c==='"'||c==="'"){i=c;continue}if(c==="{")r+=1;else if(c==="}"){if(r-=1,r===0)try{return JSON.parse(e.slice(s,a+1))}catch{return null}}}return null}function xh(e,t){let n={},o={},s=(i,a)=>{if(!j(i))return;for(let[c,l]of Object.entries(i))n[c]=l,o[c]=a};s(e.groupSetting,"$projectGroupSetting"),s(e.projectData,"$projectData"),s(e.moduleSet,"module.json set");let r=n.domainSetting;if(j(r))s(r[t],`domainSetting[${t}]`),delete n.domainSetting,delete o.domainSetting;return{settings:n,origins:o}}async function ul(e){let t=await Me(e),n=[],o=[];for(let s of t){let r=await F(K.join(e,s));if(!r.trim())continue;let{compiled:i,warning:a}=ol(s.replace(/\.vue$/,""),r);if(n.push(i),a)o.push(a)}return{components:n,warnings:o}}async function Oh(e){let t=await Me(e),n=[];for(let o of t){let s=o.replace(/\.vue$/i,""),r=await F(K.join(e,o));if(!r.trim())continue;try{let{template:i,script:a,style:c}=yt(r);n.push({name:s,template:i,script:a,css:c})}catch(i){n.push({name:s,parseError:i.message})}}return n}async function Mh(e,t,n){let o=[],s=[],r=[],i={project:[],module:[]},a=[{scope:"project",items:await U(K.join(t,"imports","_imports.json"),[]),baseDir:t,fixHint:"project_sync"},{scope:"module",items:await U(K.join(e,"imports","_imports.json"),[]),baseDir:e,fixHint:"module_get"}];for(let{scope:c,items:l,baseDir:u,fixHint:d}of a){if(!Array.isArray(l))continue;for(let p of l){if(!p||!p.name)continue;let m=`[${c}] ${p.name}`,w=p.type===".css"?"css":p.type===".js"?"js":void 0;if(!w){s.push(`import ${m}: type "${p.type??"(tr\u1ED1ng)"}" kh\xF4ng ph\u1EA3i .js/.css \u2014 kh\xF4ng n\u1EA1p v\xE0o trang render`);continue}if(typeof p.sort!=="number")i[c].push(p.name);o.push({label:m,scope:c,kind:w,sort:typeof p.sort==="number"?p.sort:il[c],name:p.name,fileID:p.fileID,contentType:p.contentType,filePath:p.filePath,baseDir:u,domain:n,fixHint:d})}}for(let c of["project","module"]){let l=i[c];if(l.length===0)continue;r.push(`${l.length} import c\u1EA5p ${c} kh\xF4ng c\xF3 FileSort trong _imports.json (${l.join(", ")}) \u2192 t\u1EA1m x\u1EBFp cu\u1ED1i `+`(${il[c]}). Th\u1EE9 t\u1EF1 n\u1EA1p c\xF3 th\u1EC3 kh\xE1c production; ch\u1EA1y \`${c==="project"?"project_sync":"module_get"}\` \u0111\u1EC3 l\u1EA5y FileSort th\u1EADt.`)}return{entries:o,problems:s,notes:r}}async function Fh(e){let t=[],n=[],o=[],s=[],r=[],i=new Set;for(let a of el(e)){let c=a.fileID?`id:${a.fileID}`:`name:${a.scope}:${a.name}`;if(i.has(c))continue;if(a.filePath&&a.baseDir){let p=await F(K.join(a.baseDir,a.filePath));if(p.trim()){i.add(c);let m=wn(a.label);if(a.kind==="css")t.push(`<style data-fui-import="${m}">
181
191
  ${bt(p,"style")}
182
192
  </style>`);else n.push(`<script data-fui-import="${m}">
183
193
  ${bt(p,"script")}
184
- </script>`);s.push(`${a.sort}. ${a.label} \u2014 inline ${p.length} bytes`);continue}if(!a.fileID){o.push(`import ${a.label}: file n\u1ED9i b\u1ED9 "${a.filePath}" thi\u1EBFu/r\u1ED7ng v\xE0 kh\xF4ng c\xF3 fileID \u0111\u1EC3 l\u1EA5y b\u1EA3n server \u2192 KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c. `+`Ch\u1EA1y \`${a.fixHint}\`. L\u1ED7i JS/hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3 c\u1EE7a vi\u1EC7c n\xE0y.`);continue}r.push(`${a.label}: ch\u01B0a c\xF3 b\u1EA3n local \u2192 d\xF9ng b\u1EA3n tr\xEAn server. Ch\u1EA1y \`${a.fixHint}\` n\u1EBFu c\u1EA7n s\u1EEDa file n\xE0y.`)}let u=!a.filePath&&(!a.contentType||a.contentType==="link"||a.contentType==="comp-link")?a.name:a.fileID?`//${a.domain}/_aset/fp/auth/moduleImportFile/${a.fileID}${a.kind==="css"?".css":".js"}`:void 0,d=gf(u,a.domain);if(!d){o.push(`import ${a.label}: kh\xF4ng c\xF3 file n\u1ED9i b\u1ED9 v\xE0 t\xEAn kh\xF4ng ph\u1EA3i URL/\u0111\u01B0\u1EDDng d\u1EABn tuy\u1EC7t \u0111\u1ED1i \u2192 kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c ngu\u1ED3n \u0111\u1EC3 n\u1EA1p. `+`N\u1EBFu \u0111\xE2y l\xE0 file t\u1EF1 t\u1EA1o, ch\u1EA1y \`${a.fixHint}\`.`);continue}if(i.has(`url:${d}`))continue;if(i.add(c),i.add(`url:${d}`),a.kind==="css")t.push(`<link href="${wn(d)}" rel="stylesheet" type="text/css" />`);else n.push(`<script src="${wn(d)}" type="text/javascript"></script>`);s.push(`${a.sort}. ${a.label} \u2192 ${d}`)}return{styles:t,scripts:n,problems:o,loaded:s,serverFallbackNotes:r}}function Sf(e,t,n){return`(() => {
185
- const previewVueData = ${Fo(n,2)};
194
+ </script>`);s.push(`${a.sort}. ${a.label} \u2014 inline ${p.length} bytes`);continue}if(!a.fileID){o.push(`import ${a.label}: file n\u1ED9i b\u1ED9 "${a.filePath}" thi\u1EBFu/r\u1ED7ng v\xE0 kh\xF4ng c\xF3 fileID \u0111\u1EC3 l\u1EA5y b\u1EA3n server \u2192 KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c. `+`Ch\u1EA1y \`${a.fixHint}\`. L\u1ED7i JS/hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi c\xF3 th\u1EC3 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3 c\u1EE7a vi\u1EC7c n\xE0y.`);continue}r.push(`${a.label}: ch\u01B0a c\xF3 b\u1EA3n local \u2192 d\xF9ng b\u1EA3n tr\xEAn server. Ch\u1EA1y \`${a.fixHint}\` n\u1EBFu c\u1EA7n s\u1EEDa file n\xE0y.`)}let u=!a.filePath&&(!a.contentType||a.contentType==="link"||a.contentType==="comp-link")?a.name:a.fileID?`//${a.domain}/_aset/fp/auth/moduleImportFile/${a.fileID}${a.kind==="css"?".css":".js"}`:void 0,d=Ah(u,a.domain);if(!d){o.push(`import ${a.label}: kh\xF4ng c\xF3 file n\u1ED9i b\u1ED9 v\xE0 t\xEAn kh\xF4ng ph\u1EA3i URL/\u0111\u01B0\u1EDDng d\u1EABn tuy\u1EC7t \u0111\u1ED1i \u2192 kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c ngu\u1ED3n \u0111\u1EC3 n\u1EA1p. `+`N\u1EBFu \u0111\xE2y l\xE0 file t\u1EF1 t\u1EA1o, ch\u1EA1y \`${a.fixHint}\`.`);continue}if(i.has(`url:${d}`))continue;if(i.add(c),i.add(`url:${d}`),a.kind==="css")t.push(`<link href="${wn(d)}" rel="stylesheet" type="text/css" />`);else n.push(`<script src="${wn(d)}" type="text/javascript"></script>`);s.push(`${a.sort}. ${a.label} \u2192 ${d}`)}return{styles:t,scripts:n,problems:o,loaded:s,serverFallbackNotes:r}}function Lh(e,t,n){return`(() => {
195
+ const previewVueData = ${Lo(n,2)};
186
196
  const basePath = ${JSON.stringify(t)};
187
197
  const moduleId = ${JSON.stringify(e.ModuleID)};
188
198
 
@@ -221,20 +231,20 @@ ${bt(p,"script")}
221
231
  if (window.vueData && Object.keys(window.vueData).length) {
222
232
  syncRouteState(window.vueData);
223
233
  }
224
- })();`}async function jf(e,t){let n=[],o=await U(K.join(e,"_moduleInfo.json"),{ProjectID:"render-project",ModuleID:K.basename(e)}),s=hn(o.ProjectID),r=await U(K.join(s,"_projectInfo.json"),null),i=await U(K.join(s,"project.json"),null),a=(o.Framework??"V3").toUpperCase()==="V2"?"V2":"V3",c=Mo.replace(/^https:\/\//,""),l=yf(o,r,c),u=l.host,d=`https://${u}`;n.push(...l.notes);let p=await F(K.join(e,"module.json")),m={ModuleName:o.ModuleName??o.ModuleID,data:[],watch:{},controls:[],set:{}};if(p.trim())try{m=JSON.parse(p)}catch(ne){throw Error(`module.json c\xF3 l\u1ED7i JSON syntax: ${ne.message} \u2014 s\u1EEDa tr\u01B0\u1EDBc khi renderUI.`)}else n.push("module.json r\u1ED7ng \u2014 render shell v\u1EDBi module tr\u1ED1ng");let w=await F(K.join(e,"header.html")),y=await F(K.join(e,"body.html")),v=await F(K.join(e,"script.js")),T=await F(K.join(e,"style.css")),I=o.HTMLOnly===!0,g=I?{deps:[],source:"(b\u1ECF qua \u2014 HTMLOnly)",warning:""}:await Vc(a,Mo);if(g.warning)n.push(g.warning);let b=g.deps.map((ne)=>Gc(ne,Mo)).filter((ne)=>Boolean(ne)),R=await $f(e,s,u),C=await vf([...b,...R.entries]);C.problems.unshift(...R.problems),n.push(...R.notes);let S=I?`Framework ${a}: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)`:`Framework ${a}: danh m\u1EE5c t\u1EEB ${g.source}`;n.push(...C.serverFallbackNotes);let N=await ol(K.join(s,"components"));n.push(...N.warnings);let D=I?{js:"",css:"",jsUrl:"",notes:[],problems:[]}:await zc(o.ProjectID,u),A=Boolean(D.js)&&!t.forceLocalProjectBundle;if(I)n.push("HTMLOnly=true \u2192 render KH\xD4NG nh\xFAng framework "+a+", KH\xD4NG nh\xFAng projectdata/StyleCSS c\u1EA5p project "+"v\xE0 KH\xD4NG d\u1EF1ng #fastProjectAPP \u2014 gi\u1ED1ng h\u1EC7t trang production. Th\u01B0 vi\u1EC7n n\xE0o module c\u1EA7n (Vue, jQuery, Vuetify, "+"lodash\u2026) ph\u1EA3i do CH\xCDNH module khai l\xE0m import; thi\u1EBFu l\xE0 tr\u1EAFng trang th\u1EADt, kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a render.");else if(t.forceLocalProjectBundle)n.push("T\u1EA7ng project d\u1EF1ng t\u1EEB workspace LOCAL (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component) \u2014 kh\xF4ng d\xF9ng bundle server. "+"Ch\u1EA1y `project_sync` n\u1EBFu nghi file project trong workspace \u0111\xE3 c\u0169.");else if(!A)n.push(...D.problems);else{let ne=await kf(K.join(s,"components")),De=Yc(ne,D.js,D.css??""),Ht=[...De.changed.map((de)=>`${de} (\u0111\xE3 s\u1EEDa local, ch\u01B0a push)`),...De.missing.map((de)=>`${de} (ch\u01B0a c\xF3 tr\xEAn server)`),...De.changedCss.filter((de)=>!De.changed.includes(de)).map((de)=>`${de} (<style> \u0111\xE3 s\u1EEDa local, ch\u01B0a push)`)];if(Ht.length>0)throw Error(`\u26D4 Component c\u1EA5p project l\u1EC7ch v\u1EDBi b\u1EA3n tr\xEAn server: ${Ht.join(", ")}.
234
+ })();`}async function Uh(e,t){let n=[],o=await U(K.join(e,"_moduleInfo.json"),{ProjectID:"render-project",ModuleID:K.basename(e)}),s=fn(o.ProjectID),r=await U(K.join(s,"_projectInfo.json"),null),i=await U(K.join(s,"project.json"),null),a=(o.Framework??"V3").toUpperCase()==="V2"?"V2":"V3",c=Fo.replace(/^https:\/\//,""),l=Dh(o,r,c),u=l.host,d=`https://${u}`;n.push(...l.notes);let p=await F(K.join(e,"module.json")),m={ModuleName:o.ModuleName??o.ModuleID,data:[],watch:{},controls:[],set:{}};if(p.trim())try{m=JSON.parse(p)}catch(ne){throw Error(`module.json c\xF3 l\u1ED7i JSON syntax: ${ne.message} \u2014 s\u1EEDa tr\u01B0\u1EDBc khi renderUI.`)}else n.push("module.json r\u1ED7ng \u2014 render shell v\u1EDBi module tr\u1ED1ng");let w=await F(K.join(e,"header.html")),y=await F(K.join(e,"body.html")),v=await F(K.join(e,"script.js")),E=await F(K.join(e,"style.css")),I=o.HTMLOnly===!0,g=I?{deps:[],source:"(b\u1ECF qua \u2014 HTMLOnly)",warning:""}:await Zc(a,Fo);if(g.warning)n.push(g.warning);let b=g.deps.map((ne)=>tl(ne,Fo)).filter((ne)=>Boolean(ne)),R=await Mh(e,s,u),C=await Fh([...b,...R.entries]);C.problems.unshift(...R.problems),n.push(...R.notes);let S=I?`Framework ${a}: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)`:`Framework ${a}: danh m\u1EE5c t\u1EEB ${g.source}`;n.push(...C.serverFallbackNotes);let A=await ul(K.join(s,"components"));n.push(...A.warnings);let D=I?{js:"",css:"",jsUrl:"",notes:[],problems:[]}:await nl(o.ProjectID,u),N=Boolean(D.js)&&!t.forceLocalProjectBundle;if(I)n.push("HTMLOnly=true \u2192 render KH\xD4NG nh\xFAng framework "+a+", KH\xD4NG nh\xFAng projectdata/StyleCSS c\u1EA5p project "+"v\xE0 KH\xD4NG d\u1EF1ng #fastProjectAPP \u2014 gi\u1ED1ng h\u1EC7t trang production. Th\u01B0 vi\u1EC7n n\xE0o module c\u1EA7n (Vue, jQuery, Vuetify, "+"lodash\u2026) ph\u1EA3i do CH\xCDNH module khai l\xE0m import; thi\u1EBFu l\xE0 tr\u1EAFng trang th\u1EADt, kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a render.");else if(t.forceLocalProjectBundle)n.push("T\u1EA7ng project d\u1EF1ng t\u1EEB workspace LOCAL (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component) \u2014 kh\xF4ng d\xF9ng bundle server. "+"Ch\u1EA1y `project_sync` n\u1EBFu nghi file project trong workspace \u0111\xE3 c\u0169.");else if(!N)n.push(...D.problems);else{let ne=await Oh(K.join(s,"components")),De=rl(ne,D.js,D.css??""),Bt=[...De.changed.map((pe)=>`${pe} (\u0111\xE3 s\u1EEDa local, ch\u01B0a push)`),...De.missing.map((pe)=>`${pe} (ch\u01B0a c\xF3 tr\xEAn server)`),...De.changedCss.filter((pe)=>!De.changed.includes(pe)).map((pe)=>`${pe} (<style> \u0111\xE3 s\u1EEDa local, ch\u01B0a push)`)];if(Bt.length>0)throw Error(`\u26D4 Component c\u1EA5p project l\u1EC7ch v\u1EDBi b\u1EA3n tr\xEAn server: ${Bt.join(", ")}.
225
235
 
226
236
  `+"renderUI d\u1EF1ng trang b\u1EB1ng bundle project T\u1EA2I T\u1EEA SERVER (_aset/.../projectdata) n\xEAn b\u1EA3n local ch\u01B0a push "+`kh\xF4ng xu\u1EA5t hi\u1EC7n \u2014 n\u1EBFu v\u1EABn render, t\u1EA5m \u1EA3nh b\u1EA1n nh\u1EADn \u0111\u01B0\u1EE3c l\xE0 B\u1EA2N C\u0168, kh\xF4ng ph\u1EA3i b\u1EA3n v\u1EEBa s\u1EEDa.
227
237
 
228
- `+"\u2022 N\u1EBFu b\u1EA3n local l\xE0 b\u1EA3n b\u1EA1n v\u1EEBa s\u1EEDa \u2192 `component_preview` \u0111\u1EC3 ki\u1EC3m ch\u1EE9ng ri\xEAng component (ch\u1EA1y \u0111\u01B0\u1EE3c b\u1EB1ng "+"file local, kh\xF4ng c\u1EA7n push), r\u1ED3i `component_update` t\u1EEBng component, r\u1ED3i render l\u1EA1i.\n"+"\u2022 N\u1EBFu b\u1EA3n tr\xEAn server m\u1EDBi h\u01A1n (ai \u0111\xF3 s\u1EEDa tr\xEAn web IDE) \u2192 `project_sync` \u0111\u1EC3 l\u1EA5y v\u1EC1, \u0110\u1EEANG push \u0111\xE8.");if(De.unverifiable.length>0)n.push(`Kh\xF4ng \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c component c\u1EA5p project: ${De.unverifiable.join(", ")}. `+"Kh\xF4ng ch\u1EB7n render, nh\u01B0ng n\u1EBFu giao di\u1EC7n kh\xE1c mong \u0111\u1EE3i th\xEC \u0111\xE2y l\xE0 ch\u1ED7 \u0111\xE1ng nghi \u0111\u1EA7u ti\xEAn.");let Re=N.components.filter((de)=>de.css.trim()).map((de)=>de.name);if(Re.length>0&&!(D.css??"").trim())n.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 R\u1ED6NG nh\u01B0ng component local c\xF3 khai <style>: ${Re.join(", ")}. `+"Ho\u1EB7c c\xE1c component \u0111\xF3 ch\u01B0a push (`component_update`), ho\u1EB7c route StyleCSS \u0111ang h\u1ECFng \u2014 "+"d\xF9 nguy\xEAn nh\xE2n n\xE0o th\xEC style c\u1EA5p project trong l\u1EA7n render n\xE0y c\u0169ng \u0111ang thi\u1EBFu.")}let G=await ol(K.join(e,"components"));n.push(...G.warnings);let _=j(i?.data)?i.data:i??{},le={..._,ProjectName:_.ProjectName??r?.ProjectName??o.ProjectID,title:_.title??o.mTitle??o.ModuleName??o.ModuleID},Ie=nl(D.js,"projectGroupSetting"),B=nl(D.js,"projectData");if(A&&!B)n.push("Kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c `var $projectData` kh\u1ECFi bundle projectdata t\u1EA3i t\u1EEB server \u2014 apiDomain/userInfo "+"trong l\u1EA7n render n\xE0y suy ra t\u1EEB project.json local, c\xF3 th\u1EC3 l\u1EC7ch v\u1EDBi b\u1EA3n trang th\u1EADt \u0111ang ch\u1EA1y.");let ae=String(_.apiDomain??""),ge=typeof B?.apiDomain==="string"?B.apiDomain:"";if(B&&ae&&ge&&ae!==ge)n.push(`project.json local c\xF3 apiDomain "${ae}" nh\u01B0ng b\u1EA3n tr\xEAn server l\xE0 "${ge}" \u2014 `+"render d\xF9ng b\u1EA3n server (gi\u1ED1ng trang th\u1EADt). Ch\u1EA1y `project_sync` \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 file local.");let We=j(m.set)?m.set:null,Ye=bf({groupSetting:Ie,projectData:B??_,moduleSet:We},u),Ce=String(Ye.settings.apiDomain??""),$t=Ye.origins.apiDomain??"(kh\xF4ng t\u1EA7ng n\xE0o khai)",qe=(()=>{let ne=typeof Ye.settings.userInfo==="string"?Ye.settings.userInfo.trim():"";if(!ne)return"";return/^https?:\/\//i.test(ne)?ne:`${Ce}${ne}`})();if(!Ce)n.push(`Kh\xF4ng t\u1EA7ng n\xE0o ($projectGroupSetting \u2192 $projectData \u2192 module.json set \u2192 domainSetting["${u}"]) khai apiDomain cho "${o.ProjectID}" \u2014 API s\u1EBD g\u1ECDi b\u1EB1ng \u0111\u01B0\u1EDDng d\u1EABn t\u01B0\u01A1ng \u0111\u1ED1i v\xE0 apiMocks kh\u1EDBp theo URL `+"g\u1EA7n nh\u01B0 ch\u1EAFc ch\u1EAFn tr\u01B0\u1EE3t. Ch\u1EA1y `project_sync` n\u1EBFu d\u1EEF li\u1EC7u project \u0111\xE3 c\u0169.");let Qe={},vt=l.basePath,Y=t.user??{},Yn=!("SystemRight"in Y),yn=!("FunctionRight"in Y),Bt=Mt([p,JSON.stringify(_),w,y,v]),Qn={user:{UserID:"render-preview",FirstName:"Render",LastName:"Preview",Email:"render@preview.local",SystemRight:9,FunctionRight:Bt,...Y},...t.vueData??{}},Zn=yn?Wn(Bt,(await Ot(s)).functionRights):null,eo=(()=>{let ne=[];if(Yn)ne.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(yn)ne.push(Bt.length?`FunctionRight=[${Bt.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json/project)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o)");return ne.length?`user preview: ${ne.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD \u1EA9n \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`:null})(),Je=wn(o.mTitle||o.ModuleName||o.ModuleID),te=I?"":A?D.css??"":gs(N.components),Le=[T,gs(G.components)].filter((ne)=>ne.trim()).join(`
229
- `),bn=I?"":A?`<script data-fui-projectdata="${wn(D.jsUrl)}">
238
+ `+"\u2022 N\u1EBFu b\u1EA3n local l\xE0 b\u1EA3n b\u1EA1n v\u1EEBa s\u1EEDa \u2192 `component_preview` \u0111\u1EC3 ki\u1EC3m ch\u1EE9ng ri\xEAng component (ch\u1EA1y \u0111\u01B0\u1EE3c b\u1EB1ng "+"file local, kh\xF4ng c\u1EA7n push), r\u1ED3i `component_update` t\u1EEBng component, r\u1ED3i render l\u1EA1i.\n"+"\u2022 N\u1EBFu b\u1EA3n tr\xEAn server m\u1EDBi h\u01A1n (ai \u0111\xF3 s\u1EEDa tr\xEAn web IDE) \u2192 `project_sync` \u0111\u1EC3 l\u1EA5y v\u1EC1, \u0110\u1EEANG push \u0111\xE8.");if(De.unverifiable.length>0)n.push(`Kh\xF4ng \u0111\u1ED1i chi\u1EBFu \u0111\u01B0\u1EE3c component c\u1EA5p project: ${De.unverifiable.join(", ")}. `+"Kh\xF4ng ch\u1EB7n render, nh\u01B0ng n\u1EBFu giao di\u1EC7n kh\xE1c mong \u0111\u1EE3i th\xEC \u0111\xE2y l\xE0 ch\u1ED7 \u0111\xE1ng nghi \u0111\u1EA7u ti\xEAn.");let Re=A.components.filter((pe)=>pe.css.trim()).map((pe)=>pe.name);if(Re.length>0&&!(D.css??"").trim())n.push(`StyleCSS c\u1EA5p project t\u1EA3i v\u1EC1 R\u1ED6NG nh\u01B0ng component local c\xF3 khai <style>: ${Re.join(", ")}. `+"Ho\u1EB7c c\xE1c component \u0111\xF3 ch\u01B0a push (`component_update`), ho\u1EB7c route StyleCSS \u0111ang h\u1ECFng \u2014 "+"d\xF9 nguy\xEAn nh\xE2n n\xE0o th\xEC style c\u1EA5p project trong l\u1EA7n render n\xE0y c\u0169ng \u0111ang thi\u1EBFu.")}let G=await ul(K.join(e,"components"));n.push(...G.warnings);let _=j(i?.data)?i.data:i??{},ue={..._,ProjectName:_.ProjectName??r?.ProjectName??o.ProjectID,title:_.title??o.mTitle??o.ModuleName??o.ModuleID},Ie=ll(D.js,"projectGroupSetting"),H=ll(D.js,"projectData");if(N&&!H)n.push("Kh\xF4ng b\xF3c \u0111\u01B0\u1EE3c `var $projectData` kh\u1ECFi bundle projectdata t\u1EA3i t\u1EEB server \u2014 apiDomain/userInfo "+"trong l\u1EA7n render n\xE0y suy ra t\u1EEB project.json local, c\xF3 th\u1EC3 l\u1EC7ch v\u1EDBi b\u1EA3n trang th\u1EADt \u0111ang ch\u1EA1y.");let ae=String(_.apiDomain??""),ge=typeof H?.apiDomain==="string"?H.apiDomain:"";if(H&&ae&&ge&&ae!==ge)n.push(`project.json local c\xF3 apiDomain "${ae}" nh\u01B0ng b\u1EA3n tr\xEAn server l\xE0 "${ge}" \u2014 `+"render d\xF9ng b\u1EA3n server (gi\u1ED1ng trang th\u1EADt). Ch\u1EA1y `project_sync` \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 file local.");let We=j(m.set)?m.set:null,Qe=xh({groupSetting:Ie,projectData:H??_,moduleSet:We},u),Ce=String(Qe.settings.apiDomain??""),vt=Qe.origins.apiDomain??"(kh\xF4ng t\u1EA7ng n\xE0o khai)",Ve=(()=>{let ne=typeof Qe.settings.userInfo==="string"?Qe.settings.userInfo.trim():"";if(!ne)return"";return/^https?:\/\//i.test(ne)?ne:`${Ce}${ne}`})();if(!Ce)n.push(`Kh\xF4ng t\u1EA7ng n\xE0o ($projectGroupSetting \u2192 $projectData \u2192 module.json set \u2192 domainSetting["${u}"]) khai apiDomain cho "${o.ProjectID}" \u2014 API s\u1EBD g\u1ECDi b\u1EB1ng \u0111\u01B0\u1EDDng d\u1EABn t\u01B0\u01A1ng \u0111\u1ED1i v\xE0 apiMocks kh\u1EDBp theo URL `+"g\u1EA7n nh\u01B0 ch\u1EAFc ch\u1EAFn tr\u01B0\u1EE3t. Ch\u1EA1y `project_sync` n\u1EBFu d\u1EEF li\u1EC7u project \u0111\xE3 c\u0169.");let Ze={},St=l.basePath,Y=t.user??{},Yn=!("SystemRight"in Y),yn=!("FunctionRight"in Y),Ht=Mt([p,JSON.stringify(_),w,y,v]),Qn={user:{UserID:"render-preview",FirstName:"Render",LastName:"Preview",Email:"render@preview.local",SystemRight:9,FunctionRight:Ht,...Y},...t.vueData??{}},Zn=yn?qn(Ht,(await Ot(s)).functionRights):null,eo=(()=>{let ne=[];if(Yn)ne.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(yn)ne.push(Ht.length?`FunctionRight=[${Ht.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json/project)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o)");return ne.length?`user preview: ${ne.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD \u1EA9n \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`:null})(),Je=wn(o.mTitle||o.ModuleName||o.ModuleID),te=I?"":N?D.css??"":ys(A.components),Le=[E,ys(G.components)].filter((ne)=>ne.trim()).join(`
239
+ `),bn=I?"":N?`<script data-fui-projectdata="${wn(D.jsUrl)}">
230
240
  ${bt(D.js??"","script")}
231
241
  </script>`:`<script data-fui-projectdata="local">
232
- var $projectData = ${Fo(le,2)};
233
- ${bt(hs(N.components),"script")}
242
+ var $projectData = ${Lo(ue,2)};
243
+ ${bt(ws(A.components),"script")}
234
244
  </script>`,to=`<script data-fui-moduledata="local">
235
- var $moduleUI = ${Fo(m,2)};
245
+ var $moduleUI = ${Lo(m,2)};
236
246
  ${bt(v,"script")}
237
- ${bt(hs(G.components),"script")}
247
+ ${bt(ws(G.components),"script")}
238
248
  </script>`;return{html:`<!DOCTYPE html>
239
249
  <html>
240
250
  <head>
@@ -257,7 +267,7 @@ ${bt(Le,"style")}
257
267
  var Framework = ${JSON.stringify(a)};
258
268
  </script>
259
269
  <script>
260
- ${Sf(o,vt,Qn)}
270
+ ${Lh(o,St,Qn)}
261
271
  </script>
262
272
  </head>
263
273
  <body>
@@ -270,127 +280,129 @@ ${Sf(o,vt,Qn)}
270
280
  ${C.scripts.join(`
271
281
  `)}
272
282
  ${I?"":`<script>
273
- var $projectGroupSetting = ${Fo(Qe,2)};
283
+ var $projectGroupSetting = ${Lo(Ze,2)};
274
284
  </script>`}
275
285
  ${bn}
276
286
  ${to}
277
287
  </body>
278
- </html>`,moduleInfo:o,basePath:vt,origin:d,apiDomain:Ce,apiDomainOrigin:$t,userInfoUrl:qe,previewUser:Qn.user,importsLoaded:C.loaded,importProblems:C.problems,frameworkNote:S,htmlOnly:I,projectBundleNotes:I?["projectdata/StyleCSS: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)"]:A?D.notes:[...t.forceLocalProjectBundle?[]:D.notes,t.forceLocalProjectBundle?"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component \u2014 b\u1EA3n local ch\u01B0a push v\u1EABn hi\u1EC7n)":"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n server)"],rightDefaultNote:eo,undefinedRightsNote:Zn,warnings:n}}function If(e,t,n){let o=[t,n];for(let s of o){let r=Object.keys(e).find((i)=>i===s);if(r??=Object.keys(e).find((i)=>s.endsWith(i)||i.endsWith(s)),r??=Object.keys(e).find((i)=>s.includes(i)||i.includes(s)),r!==void 0)return{key:r,value:e[r]}}return}var Oo={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"*"};function Cf(e){if(!e)return null;try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return null}}async function Rf(e,t,n,o,s,r,i,a){if(!o||n!==o)return null;let c=Vn(s);if(!c)return null;let l=await Mc(t,c.alias);if(!l)return null;let u=await Fc(t,l,c.fnName);if(!u)return null;let d=`${l}:${u}`,p=r.get(d);if(p===void 0)p=await Lc(t,l,u),r.set(d,p);if(p==="safe-read"&&i.count<Lt){i.count++;try{let y;if(!c.isAuth){let{config:T}=await Oc(t,{db:l});if(T.userToken)y={...await e.request().allHeaders(),authorization:`Bearer ${T.userToken}`}}let v=await e.fetch({timeout:xo,headers:y});if(v.ok()){let T=await v.json().catch(()=>null);if(T!==null)return await ps(t,l,u,"live",T),a.live++,{payload:T,note:`g\u1ECDi tAPI th\u1EADt (SP ${u}, ${l})`}}}catch{}}let m=await Uc(t,l,u);if(m)return a.cache++,{payload:m.data,note:`_testdata cache (SP ${u}, ${l}, ngu\u1ED3n: ${m.source})`};let w=await Bc(t,l,u);if(w)return await ps(t,l,u,"generated",w),a.generated++,{payload:w,note:`t\u1EF1 sinh mock (SP ${u}, ${l}), \u0111\xE3 l\u01B0u cache`};return null}async function Lo(e,t){let{html:n,moduleInfo:o,basePath:s,origin:r,apiDomain:i,apiDomainOrigin:a,userInfoUrl:c,previewUser:l,importsLoaded:u,importProblems:d,frameworkNote:p,htmlOnly:m,projectBundleNotes:w,rightDefaultNote:y,undefinedRightsNote:v,warnings:T}=await jf(e,t),I=await uf(),g=[],b=new Set(T),R=new Set,C=[],S=[],N=[],D=[],A={revalidated:0,updated:0,fetched:0,memo:0,stale:0},G=new Set,_=new Set,le=!1,Ie=new Map,B={count:0},ae=Cf(i),ge={live:0,cache:0,generated:0},We=t.urlParams&&Object.keys(t.urlParams).length?"?"+new URLSearchParams(t.urlParams).toString():"",Ye=`${r}${s}${We}`,Ce=(()=>{try{return c?new URL(c).pathname:""}catch{return""}})(),$t;try{$t=await I.chromium.launch({headless:!0})}catch(qe){let Qe=qe.message;if(/executable doesn't exist|Executable doesn't exist|browserType.launch/i.test(Qe))throw Error(`Kh\xF4ng launch \u0111\u01B0\u1EE3c Chromium: ${Qe}
279
- \u2192 Ch\u1EA1y m\u1ED9t l\u1EA7n: npx playwright install chromium`);throw qe}try{let qe=t.device??"desktop",Qe=nf[qe],vt=t.viewport??Qe.viewport,Y=await $t.newPage({viewport:vt,isMobile:Qe.isMobile,hasTouch:Qe.hasTouch,userAgent:Qe.userAgent,deviceScaleFactor:1}),Yn=Math.min(Math.max(t.settleMs??Zm,100),5000);Y.on("pageerror",(P)=>{C.push(String(P?.message??P))}),Y.on("console",(P)=>{let W=P.type(),q=P.text();if(W==="error"){if(!/Failed to load resource/i.test(q))C.push(q)}else if(W==="warning"&&/\[Vue warn\]|\[Vuetify\]/i.test(q))S.push(q)}),await Y.route("**/*",async(P)=>{let W=P.request(),q=W.url(),fe=W.method(),x=W.resourceType();if(W.isNavigationRequest()){if(q.startsWith(r))await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:n});else b.add(`runtime \u0111i\u1EC1u h\u01B0\u1EDBng ra "${q}" \u2014 \u0111\xE3 ch\u1EB7n (th\u01B0\u1EDDng do login gate / redirect trong action)`),await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:`<html><body>[fui-render] navigation blocked: ${wn(q)}</body></html>`});return}let M;try{M=new URL(q)}catch{await P.fulfill({status:400,contentType:"text/plain",body:""});return}if(fe==="OPTIONS"){await P.fulfill({status:204,headers:Oo,body:""});return}if(M.pathname==="/favicon.ico"){await P.fulfill({status:204,body:""});return}let we=K.extname(M.pathname).toLowerCase();if(ef.has(x)||fe==="GET"&&tf.has(we)){try{let V=await hf(q,{refresh:t.refreshAssets,offline:t.offline});if(A[V.origin]++,V.origin==="stale")G.add(`${x} "${q}" \u2014 ${V.note}`);else if(V.changed)_.add(`${q} \u2014 ${V.note}`);await P.fulfill({status:200,contentType:V.contentType,body:V.body,headers:Oo})}catch(V){let be=`${x} "${q}" t\u1EA3i th\u1EA5t b\u1EA1i (${V.message})`;if(x==="script"||x==="stylesheet")R.add(be);else b.add(`${be} \u2014 tr\u1EA3 404 (offline + ch\u01B0a cache?)`);await P.fulfill({status:404,contentType:"text/plain",body:""})}return}N.push(`${fe} ${q}`);let ye=If(t.apiMocks,q,M.pathname),Z;if(ye)Z=ye.value,g.push(`API ${fe} ${M.pathname} \u2192 mock "${ye.key}"`);else if(Ce&&(q===c||M.pathname===Ce))Z=l,g.push(`API ${fe} ${M.pathname} \u2192 user preview (userInfo c\u1EE7a project, auto-mock)`);else{let V=await Rf(P,o.ProjectID,M.hostname,ae,M.pathname,Ie,B,ge);if(V)Z=V.payload,g.push(`API ${fe} ${M.pathname} \u2192 ${V.note}`);else Z={data:[]},b.add(`kh\xF4ng c\xF3 mock cho API "${q}" \u2014 tr\u1EA3 {data: []}`),g.push(`API ${fe} ${M.pathname} \u2192 {data:[]} (kh\xF4ng c\xF3 mock)`)}if(j(Z)&&Z.__error!==void 0){let V=j(Z.__error)?Z.__error:{};await P.fulfill({status:Number(V.status??500),contentType:"application/json; charset=utf-8",headers:Oo,body:JSON.stringify(V.responseJSON??{Message:"Simulated error"})});return}await P.fulfill({status:200,contentType:"application/json; charset=utf-8",headers:Oo,body:JSON.stringify(Z)})});let yn=Y;if(typeof yn.routeWebSocket==="function")await yn.routeWebSocket("**/*",(P)=>{b.add("module m\u1EDF WebSocket t\u1EDBi backend th\u1EADt \u2014 \u0111\xE3 ch\u1EB7n (renderUI ch\u1EC9 cho ph\xE9p g\u1ECDi th\u1EADt qua tAPI SP \u0111\xE3 x\xE1c nh\u1EADn an to\xE0n, kh\xF4ng qua WebSocket)"),P.close()});g.push("\u2500\u2500 INIT \u2500\u2500");let Bt=Date.now();if(await Y.goto(Ye,{waitUntil:"networkidle",timeout:Qm}),!m)try{await Y.waitForFunction("!!window.vm || !!document.querySelector('#mainContainer, #fastProjectAPP .v-application')",void 0,{timeout:15000})}catch{b.add("Vue ch\u01B0a mount sau 15s \u2014 trang ch\u01B0a init xong (xem JS error / asset h\u1ECFng / API treo b\xEAn d\u01B0\u1EDBi)")}await Y.waitForTimeout(Yn);let Qn=[A.revalidated?`${A.revalidated} revalidate 304`:"",A.updated?`${A.updated} C\u1EACP NH\u1EACT`:"",A.fetched?`${A.fetched} t\u1EA3i m\u1EDBi`:"",A.memo?`${A.memo} memo`:"",A.stale?`${A.stale} STALE`:""].filter(Boolean).join(" + ")||"0",Zn=[ge.live?`${ge.live} g\u1ECDi th\u1EADt`:"",ge.cache?`${ge.cache} cache`:"",ge.generated?`${ge.generated} t\u1EF1 sinh`:""].filter(Boolean).join(" + ");g.push(`trang load xong sau ${((Date.now()-Bt)/1000).toFixed(1)}s \xB7 asset: ${Qn} \xB7 ${N.length} API call${Zn?` (API kh\xF4ng-mock: ${Zn})`:""}`);let eo=C.length,Je=K.join(e,"_preview");await at.mkdir(Je,{recursive:!0});for(let P=0;P<t.scenario.length;P++){let W=t.scenario[P],q=P+1,fe=C.length;try{if(j(W.set))g.push("",`\u2500\u2500 STEP ${q}: set ${Object.keys(W.set).join(", ")} \u2500\u2500`),await Y.evaluate((x)=>{let M=window,we=(Z,V,be)=>{if(M.Vue&&typeof M.Vue.set==="function")M.Vue.set(Z,V,be);else Z[V]=be},ye=(Z,V,be)=>{let xe=String(V).match(/[^.[\]']+/g)||[V],J=Z;for(let Q=0;Q<xe.length-1;Q++){if(J[xe[Q]]==null)we(J,xe[Q],/^\d+$/.test(xe[Q+1])?[]:{});J=J[xe[Q]]}we(J,xe[xe.length-1],be)};for(let[Z,V]of x)ye(M.vueData,Z,V)},Object.entries(W.set));else if(typeof W.click==="string"){g.push("",`\u2500\u2500 STEP ${q}: click "${W.click}" \u2500\u2500`);let x=W.click,M=typeof W.nth==="number"?W.nth-1:0,we=Y.locator(".v-btn, button, [role='button'], .v-list-item, .v-tab, a",{hasText:x}),ye=await we.count();if(ye===0)we=Y.locator(`text=${x}`),ye=await we.count();if(ye===0)b.add(`STEP ${q}: kh\xF4ng t\xECm th\u1EA5y ph\u1EA7n t\u1EED c\xF3 text "${x}" tr\xEAn DOM`),le=!0;else{if(ye>1)g.push(`(kh\u1EDBp ${ye} ph\u1EA7n t\u1EED \u2014 d\xF9ng #${M+1})`);await we.nth(Math.min(M,ye-1)).click({timeout:ws}),await Y.waitForTimeout(350);let Z=Y.locator(".jconfirm .jconfirm-buttons button"),V=await Z.count();if(V>0){let be=W.cancelConfirm===!0,xe=!1;for(let J=0;J<V;J++){let Q=(await Z.nth(J).innerText()).trim().toLowerCase(),oe=/h\u1EE7y|hu\u1EF7|cancel|kh\u00F4ng|no|\u0111\u00F3ng/.test(Q);if(be===oe){g.push(`CONFIRM \u2192 b\u1EA5m "${Q}"`),await Z.nth(J).click({timeout:ws}),xe=!0;break}}if(!xe)g.push(`CONFIRM \u2192 b\u1EA5m n\xFAt ${be?"cu\u1ED1i":"\u0111\u1EA7u"} (kh\xF4ng nh\u1EADn di\u1EC7n \u0111\u01B0\u1EE3c label)`),await Z.nth(be?V-1:0).click({timeout:ws})}}}else if(typeof W.call==="string")g.push("",`\u2500\u2500 STEP ${q}: call ${W.call} \u2500\u2500`),await Y.evaluate((x)=>{let M=window;if(typeof M.CALL!=="function")throw Error("global CALL kh\xF4ng t\u1ED3n t\u1EA1i trong runtime n\xE0y");let we=M.vueData&&M.vueData[x]!==void 0?M.vueData[x]:x;M.CALL(we)},W.call);else if(typeof W.exe==="string")g.push("",`\u2500\u2500 STEP ${q}: exe \u2500\u2500`),await Y.evaluate((x)=>{Function(x)()},W.exe);else if(typeof W.assert==="string"){let x=typeof W.label==="string"?W.label:W.assert,M=!1;try{M=await Y.evaluate((we)=>{try{return!!Function("with(window.vueData||{}){return ("+we+")}")()}catch{return!1}},W.assert)}catch{M=!1}D.push({label:x,ok:M}),g.push("",`\u2500\u2500 STEP ${q}: assert ${M?"\u2714":"\u2718"} ${x} \u2500\u2500`);continue}else{b.add(`event ${q} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);continue}await Y.waitForTimeout(Yn);for(let x=fe;x<C.length;x++)g.push(`\u2718 pageerror: ${C[x]}`);if(t.screenshotSteps){let x=K.join(Je,`render-step-${q}.png`);await Y.screenshot({path:x}),g.push(`screenshot: ${x}`)}}catch(x){le=!0,g.push(`\u2718 STEP ${q} l\u1ED7i: ${x.message.split(`
280
- `)[0]}`)}}let te=await Y.evaluate(()=>{let P=document.querySelector("#fastProjectAPP"),W=P?P.children.length:Array.from(document.body.children).filter((J)=>!/^(SCRIPT|STYLE|LINK|TEMPLATE|NOSCRIPT)$/.test(J.tagName)).length,q=window.innerWidth,fe=document.documentElement.scrollWidth>q+4,x=[],M=(J)=>J&&typeof J.className==="string"?J.className.trim().split(/\s+/).slice(0,2).join("."):"";if(fe)for(let J of document.body.querySelectorAll("*")){let Q=J.getBoundingClientRect();if(Q.width>0&&Q.right>q+4&&x.length<5){let oe=M(J);x.push(J.tagName.toLowerCase()+(oe?"."+oe:"")+" (right="+Math.round(Q.right)+"px)")}}let we=document.body.innerText||"",ye=[];for(let J of["undefined","NaN","[object Object]"])if(we.includes(J))ye.push(J);let Z=[];for(let J of document.querySelectorAll("img"))if(J.complete&&J.naturalWidth===0&&J.getBoundingClientRect().width>0&&Z.length<5)Z.push((J.getAttribute("src")||"(no src)").slice(0,120));let V=[];for(let J of document.querySelectorAll("canvas")){let Q=J.getBoundingClientRect();if((Q.width===0||Q.height===0)&&V.length<5){let oe=J.parentElement,re=M(oe);V.push("canvas trong "+(oe?oe.tagName.toLowerCase():"?")+(re?"."+re:"")+" ("+Math.round(Q.width)+"\xD7"+Math.round(Q.height)+")")}}let be=[];{let J=(oe)=>{let re="";for(let Ve of oe.childNodes)if(Ve.nodeType===Node.TEXT_NODE)re+=Ve.textContent||"";return re.trim()},Q=[];for(let oe of document.body.querySelectorAll("*")){if(Q.length>=600)break;let re=J(oe);if(!re)continue;let Ve=oe.getBoundingClientRect();if(Ve.width<=0||Ve.height<=0)continue;Q.push({el:oe,rect:Ve,text:re.slice(0,24)})}Q.sort((oe,re)=>oe.rect.top-re.rect.top||oe.rect.left-re.rect.left);e:for(let oe=0;oe<Q.length;oe++){let re=Q[oe];for(let Ve=oe+1;Ve<Q.length;Ve++){let Ke=Q[Ve];if(Ke.rect.top>re.rect.bottom+4)break;if(re.el.contains(Ke.el)||Ke.el.contains(re.el))continue;let fu=re.rect.top+re.rect.height/2,hu=Ke.rect.top+Ke.rect.height/2;if(Math.abs(fu-hu)>Math.min(re.rect.height,Ke.rect.height)*0.6)continue;let Vo=re.rect.left<=Ke.rect.left?Ke.rect.left-re.rect.right:re.rect.left-Ke.rect.right;if(Vo<2){let gu=Vo<0?"ch\u1ED3ng l\u1EA5n":`d\xEDnh s\xE1t (gap ${Math.round(Vo)}px)`;if(be.push(`${gu}: "${re.text}" \u2194 "${Ke.text}"`),be.length>=6)break e}}}}let xe=!!document.querySelector(".v-dialog--active, .v-overlay--active");return{appChildCount:W,overflowX:fe,overflowers:x,suspectTexts:ye,brokenImages:Z,deadCanvas:V,textCollisions:be,dialogOpen:xe}}),Le=[];if(te.appChildCount===0)Le.push((m?"\u2718 <body> kh\xF4ng render \u0111\u01B0\u1EE3c ph\u1EA7n t\u1EED n\xE0o (HTMLOnly \u2014 body.html L\xC0 c\u1EA3 trang)":"\u2718 #fastProjectAPP r\u1ED7ng \u2014 controls kh\xF4ng render")+(d.length>0||R.size>0?" (C\xD3 file to\xE0n c\u1EE5c n\u1EA1p thi\u1EBFu \u2014 xem m\u1EE5c M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE tr\u01B0\u1EDBc khi nghi module.json)":" (ki\u1EC3m tra pageerror / module.json)"));if(te.overflowX)Le.push(`\u26A0 trang tr\xE0n ngang viewport ${vt.width}px \u2014 ph\u1EA7n t\u1EED tr\xE0n: ${te.overflowers.join(", ")||"(kh\xF4ng x\xE1c \u0111\u1ECBnh)"}`);if(te.suspectTexts.length>0)Le.push(`\u26A0 text nghi v\u1EA5n hi\u1EC3n th\u1ECB tr\xEAn trang: ${te.suspectTexts.map((P)=>`"${P}"`).join(", ")} (binding thi\u1EBFu data?)`);if(te.brokenImages.length>0)Le.push(`\u26A0 \u1EA3nh v\u1EE1 (load fail nh\u01B0ng \u0111ang chi\u1EBFm ch\u1ED7): ${te.brokenImages.join(", ")}`);if(te.deadCanvas.length>0)Le.push(`\u26A0 canvas chart 0-size \u2014 chart init khi container ch\u01B0a c\xF3 k\xEDch th\u01B0\u1EDBc: ${te.deadCanvas.join(", ")}`);if(te.textCollisions.length>0)Le.push(`\u26A0 text d\xEDnh/ch\u1ED3ng l\u1EA5n (th\u1EA9m m\u1EF9 \u2014 PH\u1EA2I s\u1EEDa, kh\xF4ng \u0111\u01B0\u1EE3c b\u1ECF qua): ${te.textCollisions.join("; ")}`);let bn=K.join(Je,"render.png"),to=await Y.screenshot({path:bn}),kn,ne="image/png";if(to.length<=Qc)kn=to.toString("base64");else try{let P=await Y.screenshot({type:"jpeg",quality:60});if(P.length<=Qc)kn=P.toString("base64"),ne="image/jpeg"}catch{}try{await Y.screenshot({path:K.join(Je,"render-full.png"),fullPage:!0})}catch{}let De;if(te.dialogOpen)try{De=K.join(Je,"render-dialog.jpg"),await Y.locator(".v-dialog--active, .v-overlay--active").first().screenshot({path:De,type:"jpeg",quality:60,timeout:3000})}catch{De=void 0}let Ht=[];try{Ht=await Y.evaluate(()=>{let P=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","p_path","p_render","p_module_id","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]),W=[],q=window.vueData||{};for(let fe of Object.keys(q)){if(P.has(fe)||W.length>=40)continue;let x=q[fe];if(x&&typeof x==="object"&&!Array.isArray(x)&&(x.API!==void 0||x.CALL!==void 0||x.EXE!==void 0||x.FUN!==void 0||x.IF!==void 0))continue;let M;try{M=JSON.stringify(x)}catch{M=String(x)}if(M===void 0)M="undefined";if(M.length>120)M=M.slice(0,120)+"\u2026";W.push(fe+" = "+M)}return W})}catch{}let Re={ts:new Date().toISOString(),jsErrors:C.length,initErrors:eo,vueWarns:S.length,envMissing:d.length+R.size,appEmpty:te.appChildCount===0,overflowX:te.overflowX,suspectTexts:te.suspectTexts.length,brokenImages:te.brokenImages.length,deadCanvas:te.deadCanvas.length,textCollisions:te.textCollisions.length,assertFail:D.filter((P)=>!P.ok).length},de=await rf(Je,qe),pu=de!==null&&ks(Re)&&il(de,Re).length===0;Re.failStreak=!ks(Re)?0:pu?(de?.failStreak??1)+1:1,await at.writeFile(sl(Je,qe),JSON.stringify(Re,null,1),"utf8").catch(()=>{return});let Ds=[...new Set(C)],xs=[...S],Os=lf(Re,xs,Ds),L=[];if(L.push(t.reportLabel?`RENDER (browser th\u1EADt): ${t.reportLabel}`:`RENDER (browser th\u1EADt): ${e}`),L.push(...cf(Re,Os,Ds,xs)),de)L.push(sf(de,Re));if((Re.failStreak??0)>=of)L.push(...af(Re.failStreak,Os));L.push(""),L.push(`Framework ${o.Framework??"V3"}${m?" \xB7 HTMLOnly=true (KH\xD4NG nh\xFAng framework/projectdata \u2014 nh\u01B0 production)":""} \xB7 viewport ${vt.width}\xD7${vt.height}${qe!=="desktop"?` (${qe})`:""} \xB7 CDN ${Mo}`),L.push(`Origin trang: ${r} \xB7 apiDomain: ${i||"(tr\u1ED1ng)"} [${a}]${c?` \xB7 userInfo auto-mock: ${c}`:""}`),L.push(p);for(let P of w)L.push(` \u2713 ${P}`);L.push(`Import (framework \u222A project \u222A module, sort theo FileSort/FileName): ${u.length} n\u1EA1p \u0111\u01B0\u1EE3c${d.length?`, ${d.length} KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c`:""}`);for(let P of u)L.push(` \u2713 ${P}`);if(y)L.push(`\u2139 ${y}`);if(v)L.push(v);L.push(""),L.push(...g),L.push(""),L.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");for(let P of D)L.push(`${P.ok?"\u2714":"\u2718"} assert: ${P.label}`);if(d.length>0||R.size>0){L.push("","\u2500\u2500 \u26D4 M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE (\u0111\u1ECDc TR\u01AF\u1EDAC khi s\u1EEDa module) \u2500\u2500","C\xE1c file d\u01B0\u1EDBi \u0111\xE2y kh\xF4ng n\u1EA1p \u0111\u01B0\u1EE3c v\xE0o trang test. H\xE0m/bi\u1EBFn/style to\xE0n c\u1EE5c do ch\xFAng","\u0111\u1ECBnh ngh\u0129a s\u1EBD kh\xF4ng t\u1ED3n t\u1EA1i \u2192 l\u1ED7i JS v\xE0 l\u1ED7i hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi C\xD3 TH\u1EC2 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3,","kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a module. Kh\u1EAFc ph\u1EE5c ph\u1EA7n n\xE0y tr\u01B0\u1EDBc r\u1ED3i render l\u1EA1i.");for(let P of d)L.push(`\u26D4 ${P}`);for(let P of R)L.push(`\u26D4 ${P}`)}if(G.size>0){L.push("","\u2500\u2500 \u26D4 ASSET C\u0168, KH\xD4NG X\xC1C MINH \u0110\u01AF\u1EE2C (\u0111\u1ECDc TR\u01AF\u1EDAC khi k\u1EBFt lu\u1EADn v\u1EC1 platform) \u2500\u2500","Kh\xF4ng h\u1ECFi \u0111\u01B0\u1EE3c server xem c\xE1c file d\u01B0\u1EDBi \u0111\xE2y c\xF2n \u0111\xFAng kh\xF4ng, n\xEAn trang test \u0111ang ch\u1EA1y",'b\u1EB1ng b\u1EA3n tr\xEAn \u0111\u0129a. M\u1ECDi k\u1EBFt lu\u1EADn ki\u1EC3u "th\u01B0 vi\u1EC7n X c\xF3 bug" r\xFAt ra t\u1EEB l\u01B0\u1EE3t render n\xE0y',"\u0111\u1EC1u KH\xD4NG d\xF9ng \u0111\u01B0\u1EE3c \u2014 b\u1EA3n th\u1EADt tr\xEAn CDN c\xF3 th\u1EC3 \u0111\xE3 kh\xE1c. N\u1ED1i m\u1EA1ng r\u1ED3i render l\u1EA1i,","ho\u1EB7c \xE9p t\u1EA3i m\u1EDBi b\u1EB1ng refreshAssets: true.");for(let P of G)L.push(`\u26D4 ${P}`)}if(_.size>0){L.push("","\u2500\u2500 \u2139\uFE0F ASSET PLATFORM \u0110\xC3 \u0110\u1ED4I so v\u1EDBi l\u01B0\u1EE3t render tr\u01B0\u1EDBc \u2500\u2500","H\xE0nh vi \u0111\u1ED5i m\xE0 module.json kh\xF4ng \u0111\u1EE5ng t\u1EDBi c\xF3 th\u1EC3 \u0111\u1EBFn t\u1EEB \u0111\xE2y, kh\xF4ng ph\u1EA3i t\u1EEB module.");for(let P of _)L.push(`\u2139\uFE0F ${P}`)}if(C.length>0){L.push("","\u2500\u2500 JS/Vue errors \u2500\u2500");for(let P of[...new Set(C)].slice(0,20))L.push(`\u2718 ${P.split(`
288
+ </html>`,moduleInfo:o,basePath:St,origin:d,apiDomain:Ce,apiDomainOrigin:vt,userInfoUrl:Ve,previewUser:Qn.user,importsLoaded:C.loaded,importProblems:C.problems,frameworkNote:S,htmlOnly:I,projectBundleNotes:I?["projectdata/StyleCSS: KH\xD4NG nh\xFAng (HTMLOnly=true \u2014 server c\u0169ng kh\xF4ng nh\xFAng)"]:N?D.notes:[...t.forceLocalProjectBundle?[]:D.notes,t.forceLocalProjectBundle?"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (ch\u1EBF \u0111\u1ED9 preview c\u1EA5p component \u2014 b\u1EA3n local ch\u01B0a push v\u1EABn hi\u1EC7n)":"projectdata/StyleCSS: d\u1EF1ng t\u1EEB workspace local (kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n server)"],rightDefaultNote:eo,undefinedRightsNote:Zn,warnings:n}}function Hh(e,t,n){let o=[t,n];for(let s of o){let r=Object.keys(e).find((i)=>i===s);if(r??=Object.keys(e).find((i)=>s.endsWith(i)||i.endsWith(s)),r??=Object.keys(e).find((i)=>s.includes(i)||i.includes(s)),r!==void 0)return{key:r,value:e[r]}}return}var Mo={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"*"};function Bh(e){if(!e)return null;try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return null}}async function qh(e,t,n,o,s,r,i,a){if(!o||n!==o)return null;let c=Jn(s);if(!c)return null;let l=await Wc(t,c.alias);if(!l)return null;let u=await Vc(t,l,c.fnName);if(!u)return null;let d=`${l}:${u}`,p=r.get(d);if(p===void 0)p=await Jc(t,l,u),r.set(d,p);if(p==="safe-read"&&i.count<Lt){i.count++;try{let y;if(!c.isAuth){let{config:E}=await qc(t,{db:l});if(E.userToken)y={...await e.request().allHeaders(),authorization:`Bearer ${E.userToken}`}}let v=await e.fetch({timeout:Oo,headers:y});if(v.ok()){let E=await v.json().catch(()=>null);if(E!==null)return await hs(t,l,u,"live",E),a.live++,{payload:E,note:`g\u1ECDi tAPI th\u1EADt (SP ${u}, ${l})`}}}catch{}}let m=await Kc(t,l,u);if(m)return a.cache++,{payload:m.data,note:`_testdata cache (SP ${u}, ${l}, ngu\u1ED3n: ${m.source})`};let w=await Gc(t,l,u);if(w)return await hs(t,l,u,"generated",w),a.generated++,{payload:w,note:`t\u1EF1 sinh mock (SP ${u}, ${l}), \u0111\xE3 l\u01B0u cache`};return null}async function Uo(e,t){let{html:n,moduleInfo:o,basePath:s,origin:r,apiDomain:i,apiDomainOrigin:a,userInfoUrl:c,previewUser:l,importsLoaded:u,importProblems:d,frameworkNote:p,htmlOnly:m,projectBundleNotes:w,rightDefaultNote:y,undefinedRightsNote:v,warnings:E}=await Uh(e,t),I=await Ch(),g=[],b=new Set(E),R=new Set,C=[],S=[],A=[],D=[],N={revalidated:0,updated:0,fetched:0,memo:0,stale:0},G=new Set,_=new Set,ue=!1,Ie=new Map,H={count:0},ae=Bh(i),ge={live:0,cache:0,generated:0},We=t.urlParams&&Object.keys(t.urlParams).length?"?"+new URLSearchParams(t.urlParams).toString():"",Qe=`${r}${s}${We}`,Ce=(()=>{try{return c?new URL(c).pathname:""}catch{return""}})(),vt;try{vt=await I.chromium.launch({headless:!0})}catch(Ve){let Ze=Ve.message;if(/executable doesn't exist|Executable doesn't exist|browserType.launch/i.test(Ze))throw Error(`Kh\xF4ng launch \u0111\u01B0\u1EE3c Chromium: ${Ze}
289
+ \u2192 Ch\u1EA1y m\u1ED9t l\u1EA7n: npx playwright install chromium`);throw Ve}try{let Ve=t.device??"desktop",Ze=bh[Ve],St=t.viewport??Ze.viewport,Y=await vt.newPage({viewport:St,isMobile:Ze.isMobile,hasTouch:Ze.hasTouch,userAgent:Ze.userAgent,deviceScaleFactor:1}),Yn=Math.min(Math.max(t.settleMs??gh,100),5000);Y.on("pageerror",(P)=>{C.push(String(P?.message??P))}),Y.on("console",(P)=>{let q=P.type(),W=P.text();if(q==="error"){if(!/Failed to load resource/i.test(W))C.push(W)}else if(q==="warning"&&/\[Vue warn\]|\[Vuetify\]/i.test(W))S.push(W)}),await Y.route("**/*",async(P)=>{let q=P.request(),W=q.url(),he=q.method(),x=q.resourceType();if(q.isNavigationRequest()){if(W.startsWith(r))await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:n});else b.add(`runtime \u0111i\u1EC1u h\u01B0\u1EDBng ra "${W}" \u2014 \u0111\xE3 ch\u1EB7n (th\u01B0\u1EDDng do login gate / redirect trong action)`),await P.fulfill({status:200,contentType:"text/html; charset=utf-8",body:`<html><body>[fui-render] navigation blocked: ${wn(W)}</body></html>`});return}let M;try{M=new URL(W)}catch{await P.fulfill({status:400,contentType:"text/plain",body:""});return}if(he==="OPTIONS"){await P.fulfill({status:204,headers:Mo,body:""});return}if(M.pathname==="/favicon.ico"){await P.fulfill({status:204,body:""});return}let we=K.extname(M.pathname).toLowerCase();if(wh.has(x)||he==="GET"&&yh.has(we)){try{let J=await Nh(W,{refresh:t.refreshAssets,offline:t.offline});if(N[J.origin]++,J.origin==="stale")G.add(`${x} "${W}" \u2014 ${J.note}`);else if(J.changed)_.add(`${W} \u2014 ${J.note}`);await P.fulfill({status:200,contentType:J.contentType,body:J.body,headers:Mo})}catch(J){let be=`${x} "${W}" t\u1EA3i th\u1EA5t b\u1EA1i (${J.message})`;if(x==="script"||x==="stylesheet")R.add(be);else b.add(`${be} \u2014 tr\u1EA3 404 (offline + ch\u01B0a cache?)`);await P.fulfill({status:404,contentType:"text/plain",body:""})}return}A.push(`${he} ${W}`);let ye=Hh(t.apiMocks,W,M.pathname),Z;if(ye)Z=ye.value,g.push(`API ${he} ${M.pathname} \u2192 mock "${ye.key}"`);else if(Ce&&(W===c||M.pathname===Ce))Z=l,g.push(`API ${he} ${M.pathname} \u2192 user preview (userInfo c\u1EE7a project, auto-mock)`);else{let J=await qh(P,o.ProjectID,M.hostname,ae,M.pathname,Ie,H,ge);if(J)Z=J.payload,g.push(`API ${he} ${M.pathname} \u2192 ${J.note}`);else Z={data:[]},b.add(`kh\xF4ng c\xF3 mock cho API "${W}" \u2014 tr\u1EA3 {data: []}`),g.push(`API ${he} ${M.pathname} \u2192 {data:[]} (kh\xF4ng c\xF3 mock)`)}if(j(Z)&&Z.__error!==void 0){let J=j(Z.__error)?Z.__error:{};await P.fulfill({status:Number(J.status??500),contentType:"application/json; charset=utf-8",headers:Mo,body:JSON.stringify(J.responseJSON??{Message:"Simulated error"})});return}await P.fulfill({status:200,contentType:"application/json; charset=utf-8",headers:Mo,body:JSON.stringify(Z)})});let yn=Y;if(typeof yn.routeWebSocket==="function")await yn.routeWebSocket("**/*",(P)=>{b.add("module m\u1EDF WebSocket t\u1EDBi backend th\u1EADt \u2014 \u0111\xE3 ch\u1EB7n (renderUI ch\u1EC9 cho ph\xE9p g\u1ECDi th\u1EADt qua tAPI SP \u0111\xE3 x\xE1c nh\u1EADn an to\xE0n, kh\xF4ng qua WebSocket)"),P.close()});g.push("\u2500\u2500 INIT \u2500\u2500");let Ht=Date.now();if(await Y.goto(Qe,{waitUntil:"networkidle",timeout:fh}),!m)try{await Y.waitForFunction("!!window.vm || !!document.querySelector('#mainContainer, #fastProjectAPP .v-application')",void 0,{timeout:15000})}catch{b.add("Vue ch\u01B0a mount sau 15s \u2014 trang ch\u01B0a init xong (xem JS error / asset h\u1ECFng / API treo b\xEAn d\u01B0\u1EDBi)")}await Y.waitForTimeout(Yn);let Qn=[N.revalidated?`${N.revalidated} revalidate 304`:"",N.updated?`${N.updated} C\u1EACP NH\u1EACT`:"",N.fetched?`${N.fetched} t\u1EA3i m\u1EDBi`:"",N.memo?`${N.memo} memo`:"",N.stale?`${N.stale} STALE`:""].filter(Boolean).join(" + ")||"0",Zn=[ge.live?`${ge.live} g\u1ECDi th\u1EADt`:"",ge.cache?`${ge.cache} cache`:"",ge.generated?`${ge.generated} t\u1EF1 sinh`:""].filter(Boolean).join(" + ");g.push(`trang load xong sau ${((Date.now()-Ht)/1000).toFixed(1)}s \xB7 asset: ${Qn} \xB7 ${A.length} API call${Zn?` (API kh\xF4ng-mock: ${Zn})`:""}`);let eo=C.length,Je=K.join(e,"_preview");await ct.mkdir(Je,{recursive:!0});for(let P=0;P<t.scenario.length;P++){let q=t.scenario[P],W=P+1,he=C.length;try{if(j(q.set))g.push("",`\u2500\u2500 STEP ${W}: set ${Object.keys(q.set).join(", ")} \u2500\u2500`),await Y.evaluate((x)=>{let M=window,we=(Z,J,be)=>{if(M.Vue&&typeof M.Vue.set==="function")M.Vue.set(Z,J,be);else Z[J]=be},ye=(Z,J,be)=>{let xe=String(J).match(/[^.[\]']+/g)||[J],V=Z;for(let Q=0;Q<xe.length-1;Q++){if(V[xe[Q]]==null)we(V,xe[Q],/^\d+$/.test(xe[Q+1])?[]:{});V=V[xe[Q]]}we(V,xe[xe.length-1],be)};for(let[Z,J]of x)ye(M.vueData,Z,J)},Object.entries(q.set));else if(typeof q.click==="string"){g.push("",`\u2500\u2500 STEP ${W}: click "${q.click}" \u2500\u2500`);let x=q.click,M=typeof q.nth==="number"?q.nth-1:0,we=Y.locator(".v-btn, button, [role='button'], .v-list-item, .v-tab, a",{hasText:x}),ye=await we.count();if(ye===0)we=Y.locator(`text=${x}`),ye=await we.count();if(ye===0)b.add(`STEP ${W}: kh\xF4ng t\xECm th\u1EA5y ph\u1EA7n t\u1EED c\xF3 text "${x}" tr\xEAn DOM`),ue=!0;else{if(ye>1)g.push(`(kh\u1EDBp ${ye} ph\u1EA7n t\u1EED \u2014 d\xF9ng #${M+1})`);await we.nth(Math.min(M,ye-1)).click({timeout:bs}),await Y.waitForTimeout(350);let Z=Y.locator(".jconfirm .jconfirm-buttons button"),J=await Z.count();if(J>0){let be=q.cancelConfirm===!0,xe=!1;for(let V=0;V<J;V++){let Q=(await Z.nth(V).innerText()).trim().toLowerCase(),oe=/h\u1EE7y|hu\u1EF7|cancel|kh\u00F4ng|no|\u0111\u00F3ng/.test(Q);if(be===oe){g.push(`CONFIRM \u2192 b\u1EA5m "${Q}"`),await Z.nth(V).click({timeout:bs}),xe=!0;break}}if(!xe)g.push(`CONFIRM \u2192 b\u1EA5m n\xFAt ${be?"cu\u1ED1i":"\u0111\u1EA7u"} (kh\xF4ng nh\u1EADn di\u1EC7n \u0111\u01B0\u1EE3c label)`),await Z.nth(be?J-1:0).click({timeout:bs})}}}else if(typeof q.call==="string")g.push("",`\u2500\u2500 STEP ${W}: call ${q.call} \u2500\u2500`),await Y.evaluate((x)=>{let M=window;if(typeof M.CALL!=="function")throw Error("global CALL kh\xF4ng t\u1ED3n t\u1EA1i trong runtime n\xE0y");let we=M.vueData&&M.vueData[x]!==void 0?M.vueData[x]:x;M.CALL(we)},q.call);else if(typeof q.exe==="string")g.push("",`\u2500\u2500 STEP ${W}: exe \u2500\u2500`),await Y.evaluate((x)=>{Function(x)()},q.exe);else if(typeof q.assert==="string"){let x=typeof q.label==="string"?q.label:q.assert,M=!1;try{M=await Y.evaluate((we)=>{try{return!!Function("with(window.vueData||{}){return ("+we+")}")()}catch{return!1}},q.assert)}catch{M=!1}D.push({label:x,ok:M}),g.push("",`\u2500\u2500 STEP ${W}: assert ${M?"\u2714":"\u2718"} ${x} \u2500\u2500`);continue}else{b.add(`event ${W} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);continue}await Y.waitForTimeout(Yn);for(let x=he;x<C.length;x++)g.push(`\u2718 pageerror: ${C[x]}`);if(t.screenshotSteps){let x=K.join(Je,`render-step-${W}.png`);await Y.screenshot({path:x}),g.push(`screenshot: ${x}`)}}catch(x){ue=!0,g.push(`\u2718 STEP ${W} l\u1ED7i: ${x.message.split(`
290
+ `)[0]}`)}}let te=await Y.evaluate(()=>{let P=document.querySelector("#fastProjectAPP"),q=P?P.children.length:Array.from(document.body.children).filter((V)=>!/^(SCRIPT|STYLE|LINK|TEMPLATE|NOSCRIPT)$/.test(V.tagName)).length,W=window.innerWidth,he=document.documentElement.scrollWidth>W+4,x=[],M=(V)=>V&&typeof V.className==="string"?V.className.trim().split(/\s+/).slice(0,2).join("."):"";if(he)for(let V of document.body.querySelectorAll("*")){let Q=V.getBoundingClientRect();if(Q.width>0&&Q.right>W+4&&x.length<5){let oe=M(V);x.push(V.tagName.toLowerCase()+(oe?"."+oe:"")+" (right="+Math.round(Q.right)+"px)")}}let we=document.body.innerText||"",ye=[];for(let V of["undefined","NaN","[object Object]"])if(we.includes(V))ye.push(V);let Z=[];for(let V of document.querySelectorAll("img"))if(V.complete&&V.naturalWidth===0&&V.getBoundingClientRect().width>0&&Z.length<5)Z.push((V.getAttribute("src")||"(no src)").slice(0,120));let J=[];for(let V of document.querySelectorAll("canvas")){let Q=V.getBoundingClientRect();if((Q.width===0||Q.height===0)&&J.length<5){let oe=V.parentElement,re=M(oe);J.push("canvas trong "+(oe?oe.tagName.toLowerCase():"?")+(re?"."+re:"")+" ("+Math.round(Q.width)+"\xD7"+Math.round(Q.height)+")")}}let be=[];{let V=(oe)=>{let re="";for(let Ke of oe.childNodes)if(Ke.nodeType===Node.TEXT_NODE)re+=Ke.textContent||"";return re.trim()},Q=[];for(let oe of document.body.querySelectorAll("*")){if(Q.length>=600)break;let re=V(oe);if(!re)continue;let Ke=oe.getBoundingClientRect();if(Ke.width<=0||Ke.height<=0)continue;Q.push({el:oe,rect:Ke,text:re.slice(0,24)})}Q.sort((oe,re)=>oe.rect.top-re.rect.top||oe.rect.left-re.rect.left);e:for(let oe=0;oe<Q.length;oe++){let re=Q[oe];for(let Ke=oe+1;Ke<Q.length;Ke++){let Ge=Q[Ke];if(Ge.rect.top>re.rect.bottom+4)break;if(re.el.contains(Ge.el)||Ge.el.contains(re.el))continue;let Iu=re.rect.top+re.rect.height/2,Cu=Ge.rect.top+Ge.rect.height/2;if(Math.abs(Iu-Cu)>Math.min(re.rect.height,Ge.rect.height)*0.6)continue;let Ko=re.rect.left<=Ge.rect.left?Ge.rect.left-re.rect.right:re.rect.left-Ge.rect.right;if(Ko<2){let Ru=Ko<0?"ch\u1ED3ng l\u1EA5n":`d\xEDnh s\xE1t (gap ${Math.round(Ko)}px)`;if(be.push(`${Ru}: "${re.text}" \u2194 "${Ge.text}"`),be.length>=6)break e}}}}let xe=!!document.querySelector(".v-dialog--active, .v-overlay--active");return{appChildCount:q,overflowX:he,overflowers:x,suspectTexts:ye,brokenImages:Z,deadCanvas:J,textCollisions:be,dialogOpen:xe}}),Le=[];if(te.appChildCount===0)Le.push((m?"\u2718 <body> kh\xF4ng render \u0111\u01B0\u1EE3c ph\u1EA7n t\u1EED n\xE0o (HTMLOnly \u2014 body.html L\xC0 c\u1EA3 trang)":"\u2718 #fastProjectAPP r\u1ED7ng \u2014 controls kh\xF4ng render")+(d.length>0||R.size>0?" (C\xD3 file to\xE0n c\u1EE5c n\u1EA1p thi\u1EBFu \u2014 xem m\u1EE5c M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE tr\u01B0\u1EDBc khi nghi module.json)":" (ki\u1EC3m tra pageerror / module.json)"));if(te.overflowX)Le.push(`\u26A0 trang tr\xE0n ngang viewport ${St.width}px \u2014 ph\u1EA7n t\u1EED tr\xE0n: ${te.overflowers.join(", ")||"(kh\xF4ng x\xE1c \u0111\u1ECBnh)"}`);if(te.suspectTexts.length>0)Le.push(`\u26A0 text nghi v\u1EA5n hi\u1EC3n th\u1ECB tr\xEAn trang: ${te.suspectTexts.map((P)=>`"${P}"`).join(", ")} (binding thi\u1EBFu data?)`);if(te.brokenImages.length>0)Le.push(`\u26A0 \u1EA3nh v\u1EE1 (load fail nh\u01B0ng \u0111ang chi\u1EBFm ch\u1ED7): ${te.brokenImages.join(", ")}`);if(te.deadCanvas.length>0)Le.push(`\u26A0 canvas chart 0-size \u2014 chart init khi container ch\u01B0a c\xF3 k\xEDch th\u01B0\u1EDBc: ${te.deadCanvas.join(", ")}`);if(te.textCollisions.length>0)Le.push(`\u26A0 text d\xEDnh/ch\u1ED3ng l\u1EA5n (th\u1EA9m m\u1EF9 \u2014 PH\u1EA2I s\u1EEDa, kh\xF4ng \u0111\u01B0\u1EE3c b\u1ECF qua): ${te.textCollisions.join("; ")}`);let bn=K.join(Je,"render.png"),to=await Y.screenshot({path:bn}),kn,ne="image/png";if(to.length<=sl)kn=to.toString("base64");else try{let P=await Y.screenshot({type:"jpeg",quality:60});if(P.length<=sl)kn=P.toString("base64"),ne="image/jpeg"}catch{}try{await Y.screenshot({path:K.join(Je,"render-full.png"),fullPage:!0})}catch{}let De;if(te.dialogOpen)try{De=K.join(Je,"render-dialog.jpg"),await Y.locator(".v-dialog--active, .v-overlay--active").first().screenshot({path:De,type:"jpeg",quality:60,timeout:3000})}catch{De=void 0}let Bt=[];try{Bt=await Y.evaluate(()=>{let P=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","p_path","p_render","p_module_id","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]),q=[],W=window.vueData||{};for(let he of Object.keys(W)){if(P.has(he)||q.length>=40)continue;let x=W[he];if(x&&typeof x==="object"&&!Array.isArray(x)&&(x.API!==void 0||x.CALL!==void 0||x.EXE!==void 0||x.FUN!==void 0||x.IF!==void 0))continue;let M;try{M=JSON.stringify(x)}catch{M=String(x)}if(M===void 0)M="undefined";if(M.length>120)M=M.slice(0,120)+"\u2026";q.push(he+" = "+M)}return q})}catch{}let Re={ts:new Date().toISOString(),jsErrors:C.length,initErrors:eo,vueWarns:S.length,envMissing:d.length+R.size,appEmpty:te.appChildCount===0,overflowX:te.overflowX,suspectTexts:te.suspectTexts.length,brokenImages:te.brokenImages.length,deadCanvas:te.deadCanvas.length,textCollisions:te.textCollisions.length,assertFail:D.filter((P)=>!P.ok).length},pe=await vh(Je,Ve),$u=pe!==null&&Ss(Re)&&ml(pe,Re).length===0;Re.failStreak=!Ss(Re)?0:$u?(pe?.failStreak??1)+1:1,await ct.writeFile(pl(Je,Ve),JSON.stringify(Re,null,1),"utf8").catch(()=>{return});let Os=[...new Set(C)],Ms=[...S],Fs=Ih(Re,Ms,Os),L=[];if(L.push(t.reportLabel?`RENDER (browser th\u1EADt): ${t.reportLabel}`:`RENDER (browser th\u1EADt): ${e}`),L.push(...jh(Re,Fs,Os,Ms)),pe)L.push(Sh(pe,Re));if((Re.failStreak??0)>=kh)L.push(...$h(Re.failStreak,Fs));L.push(""),L.push(`Framework ${o.Framework??"V3"}${m?" \xB7 HTMLOnly=true (KH\xD4NG nh\xFAng framework/projectdata \u2014 nh\u01B0 production)":""} \xB7 viewport ${St.width}\xD7${St.height}${Ve!=="desktop"?` (${Ve})`:""} \xB7 CDN ${Fo}`),L.push(`Origin trang: ${r} \xB7 apiDomain: ${i||"(tr\u1ED1ng)"} [${a}]${c?` \xB7 userInfo auto-mock: ${c}`:""}`),L.push(p);for(let P of w)L.push(` \u2713 ${P}`);L.push(`Import (framework \u222A project \u222A module, sort theo FileSort/FileName): ${u.length} n\u1EA1p \u0111\u01B0\u1EE3c${d.length?`, ${d.length} KH\xD4NG n\u1EA1p \u0111\u01B0\u1EE3c`:""}`);for(let P of u)L.push(` \u2713 ${P}`);if(y)L.push(`\u2139 ${y}`);if(v)L.push(v);L.push(""),L.push(...g),L.push(""),L.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");for(let P of D)L.push(`${P.ok?"\u2714":"\u2718"} assert: ${P.label}`);if(d.length>0||R.size>0){L.push("","\u2500\u2500 \u26D4 M\xD4I TR\u01AF\u1EDCNG THI\u1EBEU FILE (\u0111\u1ECDc TR\u01AF\u1EDAC khi s\u1EEDa module) \u2500\u2500","C\xE1c file d\u01B0\u1EDBi \u0111\xE2y kh\xF4ng n\u1EA1p \u0111\u01B0\u1EE3c v\xE0o trang test. H\xE0m/bi\u1EBFn/style to\xE0n c\u1EE5c do ch\xFAng","\u0111\u1ECBnh ngh\u0129a s\u1EBD kh\xF4ng t\u1ED3n t\u1EA1i \u2192 l\u1ED7i JS v\xE0 l\u1ED7i hi\u1EC3n th\u1ECB b\xEAn d\u01B0\u1EDBi C\xD3 TH\u1EC2 ch\u1EC9 l\xE0 h\u1EC7 qu\u1EA3,","kh\xF4ng ph\u1EA3i l\u1ED7i c\u1EE7a module. Kh\u1EAFc ph\u1EE5c ph\u1EA7n n\xE0y tr\u01B0\u1EDBc r\u1ED3i render l\u1EA1i.");for(let P of d)L.push(`\u26D4 ${P}`);for(let P of R)L.push(`\u26D4 ${P}`)}if(G.size>0){L.push("","\u2500\u2500 \u26D4 ASSET C\u0168, KH\xD4NG X\xC1C MINH \u0110\u01AF\u1EE2C (\u0111\u1ECDc TR\u01AF\u1EDAC khi k\u1EBFt lu\u1EADn v\u1EC1 platform) \u2500\u2500","Kh\xF4ng h\u1ECFi \u0111\u01B0\u1EE3c server xem c\xE1c file d\u01B0\u1EDBi \u0111\xE2y c\xF2n \u0111\xFAng kh\xF4ng, n\xEAn trang test \u0111ang ch\u1EA1y",'b\u1EB1ng b\u1EA3n tr\xEAn \u0111\u0129a. M\u1ECDi k\u1EBFt lu\u1EADn ki\u1EC3u "th\u01B0 vi\u1EC7n X c\xF3 bug" r\xFAt ra t\u1EEB l\u01B0\u1EE3t render n\xE0y',"\u0111\u1EC1u KH\xD4NG d\xF9ng \u0111\u01B0\u1EE3c \u2014 b\u1EA3n th\u1EADt tr\xEAn CDN c\xF3 th\u1EC3 \u0111\xE3 kh\xE1c. N\u1ED1i m\u1EA1ng r\u1ED3i render l\u1EA1i,","ho\u1EB7c \xE9p t\u1EA3i m\u1EDBi b\u1EB1ng refreshAssets: true.");for(let P of G)L.push(`\u26D4 ${P}`)}if(_.size>0){L.push("","\u2500\u2500 \u2139\uFE0F ASSET PLATFORM \u0110\xC3 \u0110\u1ED4I so v\u1EDBi l\u01B0\u1EE3t render tr\u01B0\u1EDBc \u2500\u2500","H\xE0nh vi \u0111\u1ED5i m\xE0 module.json kh\xF4ng \u0111\u1EE5ng t\u1EDBi c\xF3 th\u1EC3 \u0111\u1EBFn t\u1EEB \u0111\xE2y, kh\xF4ng ph\u1EA3i t\u1EEB module.");for(let P of _)L.push(`\u2139\uFE0F ${P}`)}if(C.length>0){L.push("","\u2500\u2500 JS/Vue errors \u2500\u2500");for(let P of[...new Set(C)].slice(0,20))L.push(`\u2718 ${P.split(`
281
291
  `)[0].slice(0,300)}`)}if(S.length>0){L.push("","\u2500\u2500 [Vue warn] \u2500\u2500");for(let P of[...new Set(S)].slice(0,15))L.push(`\u26A0 ${P.split(`
282
- `)[0].slice(0,300)}`)}if(Le.length>0)L.push("","\u2500\u2500 DOM AUDIT \u2500\u2500",...Le);if(b.size>0){L.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let P of b)L.push(`\u26A0 ${P}`)}if(Ht.length>0)L.push("","\u2500\u2500 STATE CU\u1ED0I (vueData) \u2500\u2500",...Ht);if(L.push(""),L.push(`Screenshot: ${bn}${kn?" (\u0111\xEDnh k\xE8m b\xEAn d\u01B0\u1EDBi)":" (qu\xE1 l\u1EDBn \u0111\u1EC3 inline \u2014 d\xF9ng Read tool \u0111\u1EC3 xem)"}`),L.push(`Full-page: ${K.join(Je,"render-full.png")}`),De)L.push(`Dialog \u0111ang m\u1EDF \u2014 ch\u1EE5p ri\xEAng: ${De}`);L.push(""),L.push(`Th\u1ED1ng k\xEA: ${N.length} API call (t\u1EA5t c\u1EA3 mock) \xB7 ${C.length} JS error (${eo} l\xFAc init) \xB7 ${S.length} Vue warn \xB7 ${D.filter((P)=>P.ok).length}/${D.length} assert \u0111\u1EA1t${te.dialogOpen?" \xB7 dialog \u0111ang m\u1EDF":""}`);let mu=D.some((P)=>!P.ok)||le||C.length>0||te.appChildCount===0||d.length>0||R.size>0||G.size>0&&!t.offline;return{report:L.join(`
283
- `),hasFailures:mu,screenshotPath:bn,inlineScreenshotBase64:kn,inlineScreenshotMime:ne}}finally{await $t.close().catch(()=>{return})}}var Pf="_componentPreview",ll=8;function Ef(e){return e.normalize("NFD").replace(/[\u0300-\u036F]/g,"").replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase()||"case"}function Tf(e){let t={},n={},o=[];for(let[s,r]of Object.entries(e)){if(s.startsWith("@")||s.startsWith("#")){o.push(`prop "${s}": FUI kh\xF4ng nh\u1EADn c\xFA ph\xE1p @/# trong JSON \u2014 \u0111\u1ED5i th\xE0nh "v-on:"/"v-slot:". \u0110\xE3 b\u1ECF qua.`);continue}if(s.startsWith(":")||s.startsWith("v-")){t[s]=r;continue}if(typeof r==="string"){t[s]=r;continue}if(typeof r==="boolean"||typeof r==="number"){t[`:${s}`]=JSON.stringify(r);continue}let i=`cp_${s.replace(/[^a-zA-Z0-9_]/g,"_")}`;n[i]=r,t[`:${s}`]=i}return{attr:t,hoisted:n,notes:o}}function Af(e,t,n){let{attr:o,hoisted:s,notes:r}=Tf(n.props??{}),i={...n.vueData??{},...s},a={w:"12",el:e,attr:o};if(n.slot!==void 0)a.innerHTML=n.slot;return{moduleJson:{ModuleName:`preview: ${e} \u2014 ${t}`,data:Object.keys(i).length>0?[i]:[],watch:{},controls:[{prop:"fluid pa-0",rows:[{prop:"",cols:[a]}]}],set:{}},notes:r}}async function ul(e,t){try{await ze.access(e)}catch{return!1}return await ze.cp(e,t,{recursive:!0}),!0}async function Nf(e,t){let n=hn(e),o=[];if(t){let i=await U(ue.join(ds(e,t),"_moduleInfo.json"),null);o.push(i?.Framework)}let s=await U(ue.join(n,"_projectInfo.json"),null);if(o.push(s?.Framework),!o.some(Boolean))try{let i=await ze.readdir(ue.join(n,"modules"),{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let c=await U(ue.join(n,"modules",a.name,"_moduleInfo.json"),null);if(c?.Framework){o.push(c.Framework);break}}}catch{}let r=o.find(Boolean);return String(r??"V3").toUpperCase()==="V2"?"V2":"V3"}async function dl(e){let t=await Dc(),n=e.projectId||t?.projectId;if(!n)throw Error("Kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c projectId. Truy\u1EC1n projectId ho\u1EB7c ch\u1EA1y session_set tr\u01B0\u1EDBc.");let o=e.moduleId||(!e.projectId?t?.moduleId:void 0),s=hn(n),r=o?ds(n,o):s,i=ue.join(r,"components"),a=o?`module (${o})`:"project (global)",c=ue.join(i,`${e.componentName}.vue`);if(!(await F(c)).trim()){let I=await Me(i);throw Error(`Kh\xF4ng t\xECm th\u1EA5y ${e.componentName}.vue trong ${i}.`+(I.length?` C\xF3 s\u1EB5n: ${I.join(", ")}.`:" Th\u01B0 m\u1EE5c components r\u1ED7ng.")+(o?"":" N\u1EBFu \u0111\xE2y l\xE0 component c\u1EA5p module, truy\u1EC1n th\xEAm moduleId."))}let u=e.cases&&e.cases.length>0?e.cases:[{name:"default"}],d=e.devices&&e.devices.length>0?e.devices:["desktop","mobile"];if(u.length*d.length>ll)throw Error(`${u.length} case \xD7 ${d.length} device = ${u.length*d.length} l\u1EA7n render, v\u01B0\u1EE3t gi\u1EDBi h\u1EA1n ${ll}. `+"M\u1ED7i l\u1EA7n render l\xE0 m\u1ED9t phi\xEAn browser th\u1EADt \u2014 chia nh\u1ECF th\xE0nh nhi\u1EC1u l\u1EA7n g\u1ECDi.");let p=await Nf(n,o),m=ue.join(fn(),Pf,tt(n,"projectId"),tt(e.componentName,"componentName")),w=ue.join(m,"_preview");await ze.mkdir(w,{recursive:!0});let y=[],v=new Set;for(let[I,g]of u.entries()){let b=g.name?.trim()||`case-${I+1}`,R=Ef(b),C=ue.join(m,R);await ze.rm(C,{recursive:!0,force:!0}),await ze.mkdir(C,{recursive:!0}),await ze.writeFile(ue.join(C,"_moduleInfo.json"),JSON.stringify({ProjectID:n,ModuleID:`_cpreview_${e.componentName}`,ModuleName:`preview ${e.componentName}`,Framework:p},null,2),"utf-8");let{moduleJson:S,notes:N}=Af(e.componentName,b,g);for(let D of N)v.add(`[${b}] ${D}`);if(await ze.writeFile(ue.join(C,"module.json"),JSON.stringify(S,null,2),"utf-8"),await ul(i,ue.join(C,"components")),o)await ul(ue.join(r,"imports"),ue.join(C,"imports"));for(let D of d){let A=`${e.componentName} \xB7 ${b} \xB7 ${D}`,G=await Lo(C,{scenario:g.scenario??[],apiMocks:g.apiMocks??{},vueData:void 0,user:e.user,device:D,viewport:e.viewport,settleMs:e.settleMs,screenshotSteps:e.screenshotSteps,forceLocalProjectBundle:!0,reportLabel:A}),_=G.screenshotPath;if(G.screenshotPath){let le=ue.join(w,`${R}-${D}.png`);try{await ze.rename(G.screenshotPath,le),_=le;let Ie=ue.join(ue.dirname(G.screenshotPath),"render-full.png");await ze.rename(Ie,ue.join(w,`${R}-${D}-full.png`)).catch(()=>{})}catch{}}y.push({caseName:b,device:D,report:G.report,hasFailures:G.hasFailures,screenshotPath:_,inlineScreenshotBase64:G.inlineScreenshotBase64,inlineScreenshotMime:G.inlineScreenshotMime})}}let T=[`PREVIEW COMPONENT: ${e.componentName}`,`Scope: ${a} \xB7 Project: ${n} \xB7 Framework ${p}`,`Ngu\u1ED3n: ${c}`,`Cases: ${u.map((I)=>I.name?.trim()||"(kh\xF4ng t\xEAn)").join(", ")} \xD7 Devices: ${d.join(", ")} = ${y.length} l\u1EA7n render`,`\u1EA2nh: ${w}`,"","Kh\xF4ng gian ri\xEAng: ch\u1EC9 component n\xE0y + CSS c\u1EE7a ch\xEDnh n\xF3 + theme/import c\u1EA5p project.","style.css c\u1EE7a module KH\xD4NG n\u1EA1p \u2014 component ph\u1EA3i t\u1EF1 \u0111\u1EE9ng \u0111\u01B0\u1EE3c. T\u1EA7ng project d\u1EF1ng t\u1EEB workspace","local n\xEAn b\u1EA3n ch\u01B0a push v\u1EABn hi\u1EC7n; ch\u1EA1y `project_sync` n\u1EBFu nghi file local \u0111\xE3 c\u0169."];if(v.size>0){T.push("","\u26A0 Ghi ch\xFA d\u1EF1ng harness:");for(let I of v)T.push(` \u2022 ${I}`)}return{header:T.join(`
284
- `),runs:y,hasFailures:y.some((I)=>I.hasFailures)}}import Ss from"vm";var pl=500,ml=15,fl=500;function Xe(e){if(typeof e==="string")return`"${e.length>40?e.slice(0,40)+"\u2026":e}"`;return Jn(e)}function js(e){return e.replace(/^`+|`+$/g,"")}function hl(e){let t=e.split(/[\\/]+/).filter(Boolean),n=t.lastIndexOf("modules");return n>0?t[n-1]:null}async function Uo(e,t,n){let{loadAllSchemaProcNames:o,getVerifiedSpNames:s}=e;if(!t||Object.keys(t).length===0)return null;let r=hl(n);if(!r)return null;let i=[];try{i=await o(r)}catch{return null}if(i.length===0)return null;let a=await s(),c=_c(Object.keys(t),i,a);if(c.length===0)return null;return["\uD83D\uDEA8 C\u1EA4U TR\xDAC MOCK CH\u01AFA \u0110\u01AF\u1EE2C KI\u1EC2M CH\u1EE8NG \u2014 nghi ng\u1EDD t\u1EF1 suy di\u1EC5n thay v\xEC \u0111\u1ECDc t\u1EEB SP th\u1EADt:",...c.map((l)=>` - apiMocks["${l.mockKey}"] kh\u1EDBp t\xEAn SP C\xD3 TH\u1EACT "${l.spName}" trong schema local, nh\u01B0ng SP \u0111\xF3 ch\u01B0a t\u1EEBng \u0111\u01B0\u1EE3c db_sp_verify \u0111\u1ECDc th\xE2n trong phi\xEAn n\xE0y.`),` \u2192 Ch\u1EA1y db_sp_verify({ name: "${c[0].spName}" }) \u0111\u1EC3 \u0111\u1ECDc th\xE2n SP th\u1EADt r\u1ED3i COPY NGUY\xCAN V\u0102N snippet apiMocks n\xF3 sinh s\u1EB5n \u2014 kh\xF4ng t\u1EF1 \u0111o\xE1n t\xEAn c\u1ED9t/shape cho SP \u0111\xE3 t\u1ED3n t\u1EA1i, k\u1EBFt qu\u1EA3 m\xF4 ph\u1ECFng s\u1EBD l\u1EC7ch v\u1EDBi th\u1EF1c t\u1EBF.`].join(`
285
- `)}function _f(e){let t=[],n=new Set;return Fe(e,(o,s,r)=>{if(r)return;let i=o.API;if(typeof i!=="string"||!i||i.includes("`")||i.includes("{{"))return;let a=o.IN;if(a!==void 0&&!(j(a)&&Object.keys(a).length===0))return;if(n.has(i))return;n.add(i),t.push({url:i,method:typeof o.METHOD==="string"?o.METHOD:"POST"})}),t}async function gl(e,t,n,o){let{getConnectionAlias:s,resolveApiSpName:r,writeTestData:i,classifySpSafety:a,callLiveApiSp:c,readTestData:l,generateMockForLocalSp:u}=e,d={...o},p=[],m=hl(n);if(!m)return{mocks:d,trace:p};let w=_f(t);if(w.length===0)return{mocks:d,trace:p};let y=0;for(let{url:v,method:T}of w){let I=Vn(v);if(!I)continue;let g=await s(m,I.alias);if(!g)continue;let b=await r(m,g,I.fnName);if(!b)continue;let R=Object.keys(o).find((A)=>A===v);if(R??=Object.keys(o).find((A)=>v.endsWith(A)||A.endsWith(v)),R??=Object.keys(o).find((A)=>v.includes(A)||A.includes(v)),R!==void 0){await i(m,g,b,"user",o[R]);continue}let C=await a(m,g,b),S=!1;if(C==="safe-read"&&y<Lt){y++;let A=await c(m,g,I.isAuth,I.fnName,T);if(A.ok)d[v]=A.data,p.push(`live ${v} \u2192 SP ${b} (${g}) \u2014 g\u1ECDi tAPI th\u1EADt th\xE0nh c\xF4ng`),await i(m,g,b,"live",A.data),S=!0;else p.push(`\u26A0 live ${v} \u2192 SP ${b} (${g}) g\u1ECDi th\u1EADt th\u1EA5t b\u1EA1i (${A.error??`HTTP ${A.status}`}) \u2014 r\u01A1i xu\u1ED1ng cache/t\u1EF1 sinh`)}else if(C==="unsafe-write")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 l\u1EC7nh ghi \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="opaque")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 \u0111o\u1EA1n \u0111\u1ECDc t\u0129nh kh\xF4ng nh\xECn xuy\xEAn qua \u0111\u01B0\u1EE3c (SQL \u0111\u1ED9ng / g\u1ECDi SP kh\xE1c) \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="unknown")p.push(`skip ${v} \u2192 SP ${b} (${g}) ch\u01B0a c\xF3 th\xE2n SP local \u0111\u1EC3 ph\xE2n lo\u1EA1i an to\xE0n \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(y>=Lt)p.push(`skip ${v} \u2192 SP ${b} (${g}) \u0111\xE3 \u0111\u1EA1t tr\u1EA7n ${Lt} l\u01B0\u1EE3t g\u1ECDi th\u1EADt/l\u01B0\u1EE3t simulate \u2014 d\xF9ng cache/t\u1EF1 sinh`);if(S)continue;let N=await l(m,g,b);if(N){d[v]=N.data,p.push(`cache ${v} \u2192 SP ${b} (${g}) \u2014 d\xF9ng _testdata cache (ngu\u1ED3n: ${N.source}, l\u01B0u l\xFAc ${N.savedAt})`);continue}let D=await u(m,g,b);if(D)d[v]=D,p.push(`gen ${v} \u2192 SP ${b} (${g}) \u2014 t\u1EF1 sinh mock t\u1EEB c\u1EA5u tr\xFAc SELECT, \u0111\xE3 l\u01B0u cache`),await i(m,g,b,"generated",D)}return{mocks:d,trace:p}}class Is{vueData;moduleJson;opts;sandbox;ctx;trace=[];warnings=new Set;depth=0;apiCallCount=0;toastCount=0;watchTriggerCount=0;assertResults=[];dialogs=[];watchSnapshots=new Map;stubUsed=new Set;scannedFunctionRightCodes;systemRightDefaulted;functionRightDefaulted;constructor(e,t){this.moduleJson=e,this.opts=t,this.systemRightDefaulted=!("SystemRight"in(t.user??{})),this.functionRightDefaulted=!("FunctionRight"in(t.user??{})),this.scannedFunctionRightCodes=Mt([JSON.stringify(e)]),this.vueData={p_domain:"sim.local",p_routers:[],p_params:"",p_urlReferrer:"",p_router:"/sim/module",v_Loading:!1,v_LoadingOverlay:[],v_containerOn:!0,user:{UserID:null,UserName:null,LastName:null,FirstName:null,Sex:null,Phone:null,Email:null,DepartmentID:null,GroupID:null,SystemRight:9,FunctionRight:this.scannedFunctionRightCodes,LastLogin:null,...t.user??{}},v_Set:{title:null,apiDomain:"",login:!1,menu:[],userInfo:""}},this.sandbox=this.buildSandbox(),this.ctx=Ss.createContext(this.sandbox)}log(e){if(this.trace.length===pl)this.trace.push("\u2026 (trace b\u1ECB c\u1EAFt \u2014 qu\xE1 d\xE0i)");if(this.trace.length>pl)return;this.trace.push(" ".repeat(Math.min(this.depth,6))+e)}warn(e){this.warnings.add(e),this.log(`\u26A0 ${e}`)}rightTest(e){if(!j(e))return!0;let t=this.vueData.user??{};for(let[n,o]of Object.entries(e)){let s=Array.isArray(o)?o:[o],r=t[n],i=Array.isArray(r)?r:[r];if(!s.some((a)=>i.includes(a)))return!1}return!0}buildSandbox(){let e=this,t=(r,i)=>(...a)=>(e.stubUsed.add(r),e.log(`FUN ${r}(${a.map((c)=>Xe(c)).join(", ")}) \u2014 stub, kh\xF4ng m\xF4 ph\u1ECFng UI th\u1EADt`),i),n=(r)=>{e.stubUsed.add("moment");let i=r!==void 0&&r!==null?new Date(r):new Date,a=(u)=>String(u).padStart(2,"0"),c={},l=(u)=>{let d=`${i.getFullYear()}-${a(i.getMonth()+1)}-${a(i.getDate())}`;if(!u)return`${d}T${a(i.getHours())}:${a(i.getMinutes())}:${a(i.getSeconds())}`;return u.replace(/YYYY/g,String(i.getFullYear())).replace(/MM/g,a(i.getMonth()+1)).replace(/DD/g,a(i.getDate())).replace(/HH/g,a(i.getHours())).replace(/mm/g,a(i.getMinutes())).replace(/ss/g,a(i.getSeconds()))};return Object.assign(c,{format:l,add:()=>c,subtract:()=>c,startOf:()=>c,endOf:()=>c,valueOf:()=>i.getTime(),toDate:()=>i,toString:()=>l(),year:()=>i.getFullYear(),month:()=>i.getMonth(),date:()=>i.getDate(),daysInMonth:()=>new Date(i.getFullYear(),i.getMonth()+1,0).getDate(),locale:()=>c,isValid:()=>!isNaN(i.getTime())}),c},o={forEach:(r,i)=>{if(Array.isArray(r))r.forEach((a,c)=>i(a,c));else if(j(r))Object.entries(r).forEach(([a,c])=>i(c,a));return r},map:(r,i)=>{let a=Array.isArray(r)?r:j(r)?Object.values(r):[];if(typeof i==="string")return a.map((c)=>c?.[i]);return a.map(i)},filter:(r,i)=>Array.isArray(r)?r.filter(i):[],find:(r,i)=>Array.isArray(r)?r.find(i):void 0,findIndex:(r,i)=>Array.isArray(r)?r.findIndex(i):-1,includes:(r,i)=>Array.isArray(r)||typeof r==="string"?r.includes(i):!1,cloneDeep:(r)=>r===void 0?void 0:JSON.parse(JSON.stringify(r)),isArray:Array.isArray,isString:(r)=>typeof r==="string",isNumber:(r)=>typeof r==="number",isPlainObject:j,isEmpty:(r)=>r==null||typeof r==="string"&&r===""||Array.isArray(r)&&r.length===0||j(r)&&Object.keys(r).length===0,isUndefined:(r)=>r===void 0,keys:(r)=>j(r)?Object.keys(r):[],values:(r)=>j(r)?Object.values(r):[],uniq:(r)=>[...new Set(r)],uniqBy:(r,i)=>{let a=new Set;return(r??[]).filter((c)=>{let l=c?.[i];if(a.has(l))return!1;return a.add(l),!0})},sumBy:(r,i)=>(r??[]).reduce((a,c)=>a+Number(typeof i==="string"?c?.[i]??0:i(c)),0),maxBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])>Number(a?.[i])?c:a,void 0),minBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])<Number(a?.[i])?c:a,void 0),orderBy:(r,i,a)=>{let c=Array.isArray(i)?i:[i],l=Array.isArray(a)?a:[a??"asc"];return[...r??[]].sort((u,d)=>{for(let p=0;p<c.length;p++){let m=u?.[c[p]],w=d?.[c[p]];if(m===w)continue;let y=m>w?1:-1;return(l[p]??"asc")==="desc"?-y:y}return 0})},sortBy:(r,i)=>o.orderBy(r,i),groupBy:(r,i)=>{let a={};return(r??[]).forEach((c)=>{let l=String(c?.[i]);(a[l]??=[]).push(c)}),a},without:(r,...i)=>(r??[]).filter((a)=>!i.includes(a)),intersection:(r,i)=>(r??[]).filter((a)=>(i??[]).includes(a)),castArray:(r)=>Array.isArray(r)?r:[r],trim:(r,i)=>{if(typeof r!=="string")return r;if(!i)return r.trim();let a=i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return r.replace(new RegExp(`^[${a}]+|[${a}]+$`,"g"),"")},replace:(r,i,a)=>String(r).replace(i,a),split:(r,i,a)=>String(r).split(i,a),startsWith:(r,i)=>String(r).startsWith(i),endsWith:(r,i)=>String(r).endsWith(i),concat:(...r)=>[].concat(...r),take:(r,i)=>(r??[]).slice(0,i),last:(r)=>(r??[])[(r??[]).length-1],head:(r)=>(r??[])[0],first:(r)=>(r??[])[0],round:(r,i=0)=>Math.round(r*10**i)/10**i,assignIn:Object.assign,assign:Object.assign,at:(r,i)=>[e.getPath(r,i)],property:(r)=>(i)=>e.getPath(i,r)},s=new Proxy(o,{get(r,i){if(i in r)return r[i];return(...a)=>{e.warn(`_.${i} ch\u01B0a h\u1ED7 tr\u1EE3 trong simulator \u2014 tr\u1EA3 undefined`);return}}});return{vueData:this.vueData,console:{log:(...r)=>e.log(`console.log: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`),error:(...r)=>e.log(`console.error: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`),warn:(...r)=>e.log(`console.warn: ${r.map((i)=>typeof i==="string"?i:Xe(i)).join(" ")}`)},JSON,Math,Date,Array,Object,String,Number,Boolean,RegExp,parseInt,parseFloat,isNaN,isFinite,encodeURIComponent,decodeURIComponent,encodeURI,decodeURI,moment:n,_:s,CALL:(r,i)=>e.runAction(r,i),groupBy:(r,i,a)=>{let c=Array.isArray(a)?a:[a],l={};return(r??[]).forEach((u)=>{let d=String(u?.[i]);(l[d]??=[]).push(u)}),Object.entries(l).map(([u,d])=>{let p={[i]:u,count:d.length};for(let m of c)if(m)p[m]=d.reduce((w,y)=>w+Number(y?.[m]??0),0);return p})},buildHeader:(r,i)=>{let a=[];if(i)(Array.isArray(r)?r:[]).forEach((c)=>a.push({text:c[i.text],value:c[i.value],divider:!0}));else{let c=Array.isArray(r)?r[0]:j(r)&&Array.isArray(r.data)?r.data[0]:void 0;if(j(c))for(let[l,u]of Object.entries(c))a.push(typeof u==="number"?{el:"t-num",text:l,value:l,divider:!0}:{text:l,value:l,divider:!0})}return a},findInArray:(r)=>(r?.src??[]).find((i)=>i?.[r.key]===r.value),openWindow:(r)=>{e.stubUsed.add("openWindow"),e.log(`FUN openWindow id=${String(r?.id??"?")} url=${String(r?.url??"?")} \u2014 stub (sub-window kh\xF4ng m\xF4 ph\u1ECFng)`)},showMessage:(r)=>{if(e.log(`MESSBOX/showMessage "${String(r?.title??"")}"${r?.message?` \u2014 ${String(r.message)}`:""}`),r?.onclose)e.log("(gi\u1EA3 l\u1EADp user \u0111\xF3ng dialog \u2192 ch\u1EA1y onclose)"),e.runAction(r.onclose)},confirm:(r)=>{if(e.log(`CONFIRM "${String(r?.title??"")}" \u2192 OK (m\u1EB7c \u0111\u1ECBnh simulator)`),typeof r?.action==="function")r.action()},ajaxCALL:t("ajaxCALL"),jsonToExcel:t("jsonToExcel"),printPDF:t("printPDF"),copyToClipboard:t("copyToClipboard"),redirect:t("redirect"),reload:t("reload"),logout:t("logout"),webSocket_Send:t("webSocket_Send"),webSocketJoinGroup:t("webSocketJoinGroup"),appCommand:t("appCommand"),pushRouter:t("pushRouter"),capacityText:(r)=>`${r}`,generateID:()=>`sim${Math.floor(Math.random()*1e6)}`,rightTest:(r)=>e.rightTest(r),fillData:t("fillData"),chartDataBuild:t("chartDataBuild"),fixURL:(r)=>r,alert:t("alert")}}evalExpr(e,t){return this.sandbox.__scope__=t,this.sandbox.__ret__=void 0,Ss.runInContext(`with(__scope__) { __ret__ = ${e} }`,this.ctx,{timeout:fl}),this.sandbox.__ret__}evalJs(e,t){this.sandbox.__scope__=t,Ss.runInContext(`with(__scope__) { ${e} }`,this.ctx,{timeout:fl})}bindDataToString(e,t){if(typeof e!=="string")return e;return e.replace(/{{([\s\S]+?)}}/g,(n,o)=>{let s=this.evalExpr(o,t);return s===void 0||s===null?"":String(s)})}getPath(e,t){let n=String(t).match(/[^.[\]]+/g)??[],o=e;for(let s of n){if(o==null)return;o=o[s]}return o}getVueData(e,t){if(typeof e==="string"){if(e==="")return"";let n=e.replace("vueData.","").trim();if(n[0]==="`")return js(this.bindDataToString(n,t));let o=n;if(/^{{.*}}$/gm.test(o))o=o.replace(/^{{|}}$/gm,"");try{return this.evalExpr(o,t)}catch{if(/({{.*}}|[ ])/gm.test(n))try{return js(this.bindDataToString(n,t))}catch{return}return}}else if(j(e)&&e.ARRAY&&e.COL){let n=this.getPath(t,String(e.ARRAY));return Array.isArray(n)?n.map((o)=>this.getPath(o,String(e.COL))):[]}return e}setValue(e,t,n,o,s){if(Array.isArray(t)){if(!j(e)&&!Array.isArray(e))return;let r=e,i=s??0,a=t[i];if(typeof a==="string"&&a[a.length-1]==="]")if(a=a.replaceAll("]",""),/^[0-9]*$/gm.test(a))a=parseInt(a,10);else if(a[0]==="'")a=a.replaceAll("'","");else a=this.getVueData(a,o??this.vueData);if(t.length-1===i){r[a]=n;return}if(typeof r[a]>"u"){let c=t[i+1];r[a]=c[c.length-1]==="]"?[]:{}}this.setValue(r[a],t,n,o,i+1)}else if(typeof t==="string")if(t[0]==="`"||t[0]==="'")this.setValue(e,[t.replaceAll("'","").replaceAll("`","")],n,o,0);else this.setValue(e,t.match(/[^.[]+/g)??[t],n,o,0)}mapData(e,t,n,o){if(typeof e==="string"){this.setValue(t,e,n);return}if(!j(e))return;for(let[s,r]of Object.entries(e)){if(o)n={...this.vueData,...o};let i;try{i=this.bindDataToString(s,this.vueData)}catch{i=s}if(typeof r==="string"&&r[0]==="#")this.warn(`\u0111\u1ECDc cross-window "${r}" \u2014 kh\xF4ng m\xF4 ph\u1ECFng \u0111\u01B0\u1EE3c, g\xE1n undefined`),this.setValue(t,i,void 0);else if(i[0]==="#")this.log(`\u2192 ghi cross-window ${i} = ${Xe(this.getVueData(r,n))} (kh\xF4ng m\xF4 ph\u1ECFng)`);else this.setValue(t,i,this.getVueData(r,n),n)}}defProp(e){if(typeof e==="string"){if(typeof this.vueData[e]>"u")this.vueData[e]=[]}else if(j(e)){for(let t of Object.keys(e))if(typeof this.vueData[t]>"u")this.vueData[t]=void 0}}runAction(e,t){if(!e)return;let n={...this.vueData,...t??{}};if(typeof e==="string"){let s=this.getVueData(e,n);if(!s){this.warn(`Action "${e}" undefined \u2014 CALL tr\u1ECF t\u1EDBi action kh\xF4ng t\u1ED3n t\u1EA1i?`);return}e=s}if(!e)return;let o=Array.isArray(e)?e:[e];for(let s of o)if(j(s)&&s.CONFIRM){let r;try{r=this.bindDataToString(String(s.CONFIRM),n)}catch{r=String(s.CONFIRM)}let i=t?.__cancelConfirm===!0;if(this.log(`CONFIRM "${r}" \u2192 ${i?"H\u1EE6Y":"OK"}`),i){if(s.CANCEL)this.vueActionSafe(s.CANCEL,t)}else{let a=JSON.parse(JSON.stringify(s));delete a.CONFIRM,delete a.CANCEL,this.vueActionSafe(a,t)}}else this.vueActionSafe(s,t)}vueActionSafe(e,t){this.depth++;try{this.vueAction(e,t)}catch(n){this.warn(`L\u1ED7i khi ch\u1EA1y action: ${n.message} \u2014 action: ${_e(e)}`)}finally{this.depth--}}vueAction(e,t){if(!e)return;if(typeof e==="string"){this.log(`EXE ${e}`),this.evalJs(e,{...t??{},...this.vueData});return}if(!j(e))return;let n=JSON.parse(JSON.stringify(e)),o={...this.vueData,...t??{}};if(n.IN!==void 0)if(typeof n.IN==="string")this.mapData({IN:n.IN},n,o);else{let r={};this.mapData(n.IN,r,o),n.IN=r}if(n.API!==void 0){try{n.API=this.bindDataToString(String(n.API),o)}catch{}if(n.HEADER!==void 0)if(typeof n.HEADER==="string")this.mapData({HEADER:n.HEADER},n,o);else this.mapData(n.HEADER,n.HEADER,o);this.defProp(n.OUT),this.callAPI(n,t);return}if(n.CALL!==void 0){this.log(`CALL ${String(n.CALL)}`),this.runAction(n.CALL,t);return}if(n.EXE!==void 0){this.log(`EXE ${String(n.EXE).replace(/\s+/g," ").slice(0,100)}`),this.evalJs(String(n.EXE),{...t??{},...this.vueData});return}if(n.FUN!==void 0){let r;try{r=this.bindDataToString(String(n.FUN),o)}catch{r=String(n.FUN)}let i=this.sandbox[r];if(typeof i==="function"){let a=i(n.IN);if(n.OUT!==void 0)this.mapData(n.OUT,this.vueData,a);if(n.CALLBACK!==void 0)this.runAction(n.CALLBACK,t)}else this.warn(`FUN "${r}" kh\xF4ng t\u1ED3n t\u1EA1i (simulator ch\u1EC9 c\xF3 c\xE1c h\xE0m FUI chu\u1EA9n)`);return}if(n.IF!==void 0){let r=!1;try{r=String(this.evalExpr(String(n.IF),{...t??{},...this.vueData}))==="true"}catch(a){this.warn(`IF "${String(n.IF)}" l\u1ED7i eval: ${a.message} \u2192 coi nh\u01B0 false`)}this.log(`IF(${String(n.IF).slice(0,60)}) \u2192 ${r?"THEN":"ELSE"}`);let i=r?n.THEN:n.ELSE;if(typeof i==="string")this.log(`EXE ${i.slice(0,80)}`),this.evalJs(i,{...t??{},...this.vueData});else if(i!==void 0)this.runAction(i,t);return}if(n.MESS!==void 0){let r;try{r=this.bindDataToString(String(n.MESS),o)}catch{r=String(n.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${r}"`);return}if(n.MESSBOX!==void 0){let r;try{r=this.bindDataToString(String(n.MESSBOX),o)}catch{r=String(n.MESSBOX)}this.log(`MESSBOX "${r}"`);return}let s=[];for(let r of Object.keys(n))s.push(r);if(this.mapData(n,this.vueData,this.vueData,t),s.length>0)this.log(`SET ${s.map((r)=>`${r} = ${Xe(r[0]==="#"?"(cross-window)":this.getPath(this.vueData,r))}`).join(" \xB7 ")}`)}callAPI(e,t){let n=String(e.API),o=e.METHOD?String(e.METHOD):"POST";this.apiCallCount++,this.log(`API ${o} ${n} IN=${e.IN!==void 0?JSON.stringify(e.IN)?.slice(0,200):"{}"}`);let s=this.opts.apiMocks,r=Object.keys(s).find((a)=>a===n);r??=Object.keys(s).find((a)=>n.endsWith(a)||a.endsWith(n)),r??=Object.keys(s).find((a)=>n.includes(a)||a.includes(n));let i;if(r!==void 0)i=JSON.parse(JSON.stringify(s[r])),this.log(`(mock kh\u1EDBp "${r}")`);else i={data:[]},this.warn(`kh\xF4ng c\xF3 mock cho API "${n}" \u2014 tr\u1EA3 m\u1EB7c \u0111\u1ECBnh {data: []}`);if(j(i)&&i.__error!==void 0){let a=j(i.__error)?i.__error:{},c={status:a.status??500,responseJSON:a.responseJSON??{Message:"Simulated error"}};if(this.log(`\u2190 HTTP ${String(c.status)} (mock l\u1ED7i)`),e.ERROROUT!==void 0)this.mapData(e.ERROROUT,this.vueData,c);if(e.ERRORCALLBACK!==void 0)this.runAction(e.ERRORCALLBACK,t);if(e.ERROROUT!==void 0)return;let l=c.responseJSON;this.warn(`API "${n}" l\u1ED7i kh\xF4ng \u0111\u01B0\u1EE3c b\u1EAFt (kh\xF4ng c\xF3 ERROROUT) \u2192 hi\u1EC7n ERROR DIALOG m\u1EB7c \u0111\u1ECBnh: ${typeof l==="string"?l:j(l)?String(l.Message??""):""}`);return}if(e.OUT!==void 0){let a=i;if(typeof e.OUT==="string"&&j(i)&&Object.keys(i).length===1&&i.data)a=i.data;this.mapData(e.OUT,this.vueData,a);let c=typeof e.OUT==="string"?`${e.OUT} = ${Xe(this.vueData[e.OUT])}`:Object.keys(e.OUT).map((l)=>`${l} = ${Xe(this.getPath(this.vueData,l))}`).join(" \xB7 ");this.log(`\u2190 OUT ${c}`)}else this.log("\u2190 (kh\xF4ng c\xF3 OUT)");if(e.MESS!==void 0){let a;try{a=this.bindDataToString(String(e.MESS),this.vueData)}catch{a=String(e.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${a}"`)}if(e.CALLBACK!==void 0)this.runAction(e.CALLBACK,t)}initWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{};for(let o of Object.keys(e)){if(o==="deep-watch")continue;this.setValue(this.vueData,o,this.getVueData(o,this.vueData))}let t=(o,s)=>{let r=this.getPath(this.vueData,o);this.watchSnapshots.set(`${s?"D:":"S:"}${o}`,{deep:s,snap:s?JSON.stringify(r):r})},n=e["deep-watch"];if(j(n))for(let o of Object.keys(n))t(o,!0);for(let o of Object.keys(e)){if(o==="deep-watch")continue;t(o,!1)}}checkWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{},t=j(e["deep-watch"])?e["deep-watch"]:{};for(let n=0;n<ml;n++){let o=[];for(let[s,r]of this.watchSnapshots){let i=s.slice(2),a=this.getPath(this.vueData,i);if(r.deep){let c=JSON.stringify(a);if(c!==r.snap)o.push({key:i,handler:t[i],oldVal:r.snap}),r.snap=c}else if(!Object.is(a,r.snap))o.push({key:i,handler:e[i],oldVal:r.snap}),r.snap=a}if(o.length===0)return;for(let s of o){this.watchTriggerCount++,this.log(`WATCH ${s.key} thay \u0111\u1ED5i \u2192 ${_e(s.handler)}`);let r={};this.setValue(r,s.key,s.oldVal),this.depth++;try{this.runAction(s.handler,{v_old:r})}finally{this.depth--}}}this.warn(`watch cascade v\u01B0\u1EE3t ${ml} v\xF2ng \u2014 nguy c\u01A1 V\xD2NG L\u1EB6P V\xD4 H\u1EA0N gi\u1EEFa c\xE1c watch/action`)}initDialogs(){(Array.isArray(this.moduleJson.controls)?this.moduleJson.controls:[]).forEach((t,n)=>{if(!j(t)||t.el!=="hidden-container")return;let o=j(t.attr)?t.attr:{},s=t["v-model"]??o["v-model"];if(typeof s==="string"){let r=s.replace("vueData.","").split(/[.[]/)[0];this.dialogs.push({vModel:r,where:`container ${n+1}`,open:Boolean(this.getPath(this.vueData,r))})}})}checkDialogs(){for(let e of this.dialogs){let t=Boolean(this.getPath(this.vueData,e.vModel));if(t!==e.open)e.open=t,this.log(`DIALOG ${e.vModel} (${e.where}): ${t?"M\u1EDE \uD83D\uDD32":"\u0110\xD3NG \u25AA"}`)}}declareControlVars(){let e=/^[a-zA-Z0-9$_\-.[\]]*$/,t=(n)=>{if(Array.isArray(n)){n.forEach(t);return}if(!j(n))return;let o=j(n.attr)?n.attr:{};for(let[s,r]of Object.entries(o)){if(typeof r!=="string")continue;if(s==="v-model"){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:void 0},this.vueData,{})}else if(s[0]===":"&&e.test(r)){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:s===":items"?[]:void 0},this.vueData,{})}}if(Array.isArray(n.innerHTML))n.innerHTML.forEach(t);if(Array.isArray(n.rows))n.rows.forEach(t);if(Array.isArray(n.cols))n.cols.forEach(t)};t(this.moduleJson.controls)}collectClickables(){let e=[],t=(n,o)=>{if(Array.isArray(n)){n.forEach((a)=>t(a,o));return}if(!j(n))return;let s=j(n.attr)?n.attr:{},r=s["v-on:click"]??s[":action"]??s.action;if(r!==void 0){let a=[];for(let c of["label",":label","innerText","text"]){let l=s[c]??n[c];if(typeof l==="string"&&l)a.push(js(l))}if(typeof n.innerHTML==="string"&&n.innerHTML)a.push(n.innerHTML.replace(/<[^>]*>/g,"").trim());e.push({labels:a,handler:r,el:typeof n.el==="string"?n.el:"?",tableItemsExpr:o})}let i=o;if(n.el==="f-table"){let a=s[":items"];if(typeof a==="string")i=a}if(Array.isArray(n.innerHTML))n.innerHTML.forEach((a)=>t(a,i));if(Array.isArray(n.rows))n.rows.forEach((a)=>t(a,i));if(Array.isArray(n.cols))n.cols.forEach((a)=>t(a,i))};return t(this.moduleJson.controls),e}simulate(e){if(this.log("\u2500\u2500 INIT \u2500\u2500"),j(this.moduleJson.set))Object.assign(this.vueData.v_Set,this.moduleJson.set);if(this.opts.urlParams)for(let[n,o]of Object.entries(this.opts.urlParams))this.vueData[n]=o;try{this.runAction(this.moduleJson.data)}catch(n){this.warn(`L\u1ED7i khi init data[]: ${n.message}`)}this.declareControlVars(),this.initWatches(),this.initDialogs(),this.checkDialogs();let t=this.collectClickables();return e.forEach((n,o)=>{let s=o+1;if(j(n.set)){this.log(""),this.log(`\u2500\u2500 STEP ${s}: set ${Object.entries(n.set).map(([r,i])=>`${r}=${Xe(i)}`).join(", ")} \u2500\u2500`);for(let[r,i]of Object.entries(n.set))this.setValue(this.vueData,r,i)}else if(typeof n.click==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: click "${n.click}" \u2500\u2500`);let r=n.click.toLowerCase(),i=t.filter((d)=>d.labels.some((p)=>p.toLowerCase()===r)),a=i.length>0?i:t.filter((d)=>d.labels.some((p)=>p.toLowerCase().includes(r)));if(a.length===0){this.warn(`kh\xF4ng t\xECm th\u1EA5y control c\xF3 label "${n.click}" k\xE8m v-on:click/:action`);return}let c=typeof n.nth==="number"?n.nth-1:0;if(a.length>1)this.log(`(kh\u1EDBp ${a.length} control \u2014 d\xF9ng #${c+1})`);let l=a[Math.min(c,a.length-1)],u;if(l.tableItemsExpr!==void 0||n.item!==void 0||n.index!==void 0){let d=n.item,p=typeof n.index==="number"?n.index:0;if(d===void 0&&l.tableItemsExpr){let m=this.getVueData(l.tableItemsExpr,this.vueData);if(Array.isArray(m)&&m.length>0)d=m[Math.min(p,m.length-1)],this.log(`(control trong f-table \u2014 d\xF9ng row ${p} c\u1EE7a ${l.tableItemsExpr})`);else d={},this.warn(`control n\u1EB1m trong f-table nh\u01B0ng "${l.tableItemsExpr??""}" r\u1ED7ng \u2014 item = {} (truy\u1EC1n "item" trong event \u0111\u1EC3 ch\u1EC9 \u0111\u1ECBnh)`)}u={$row:d,item:d,index:p}}if(n.cancelConfirm===!0)u={...u??{},__cancelConfirm:!0};this.runAction(l.handler,u)}else if(typeof n.call==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: call ${n.call} \u2500\u2500`);let r;if(n.item!==void 0||n.index!==void 0)r={$row:n.item,item:n.item,index:typeof n.index==="number"?n.index:0};if(n.cancelConfirm===!0)r={...r??{},__cancelConfirm:!0};this.runAction(n.call,r)}else if(typeof n.exe==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: exe \u2500\u2500`),this.log(`EXE ${n.exe.slice(0,100)}`);try{this.evalJs(n.exe,{...this.vueData})}catch(r){this.warn(`exe l\u1ED7i: ${r.message}`)}}else if(typeof n.assert==="string"){let r=typeof n.label==="string"?n.label:n.assert,i=!1;try{i=Boolean(this.evalExpr(n.assert,{...this.vueData}))}catch(a){this.warn(`assert "${n.assert}" l\u1ED7i eval: ${a.message}`)}this.assertResults.push({label:r,ok:i}),this.log(""),this.log(`\u2500\u2500 STEP ${s}: assert ${i?"\u2714":"\u2718"} ${r} \u2500\u2500`);return}else{this.warn(`event ${s} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);return}this.checkWatches(),this.checkDialogs()}),{report:this.buildReport(),hasFailures:this.assertResults.some((n)=>!n.ok)}}buildRightDefaultNote(){let e=[];if(this.systemRightDefaulted)e.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(this.functionRightDefaulted)e.push(this.scannedFunctionRightCodes.length?`FunctionRight=[${this.scannedFunctionRightCodes.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o trong module.json)");if(e.length===0)return null;return`user preview: ${e.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD tr\u1EA3 false \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`}buildReport(){let e=[...this.trace,""];e.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");let t=this.buildRightDefaultNote();if(t)e.push(`\u2139 ${t}`);if(this.functionRightDefaulted){let s=Wn(this.scannedFunctionRightCodes,this.opts.definedFunctionRights??null);if(s)e.push(s)}for(let s of this.assertResults)e.push(`${s.ok?"\u2714":"\u2718"} assert: ${s.label}`);if(this.warnings.size>0){e.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let s of this.warnings)e.push(`\u26A0 ${s}`)}if(this.stubUsed.size>0)e.push("",`(stub \u0111\xE3 d\xF9ng \u2014 k\u1EBFt qu\u1EA3 c\xE1c h\xE0m n\xE0y KH\xD4NG ph\u1EA3n \xE1nh runtime th\u1EADt: ${[...this.stubUsed].join(", ")})`);e.push("","\u2500\u2500 STATE CU\u1ED0I \u2500\u2500");let n=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]);for(let[s,r]of Object.entries(this.vueData)){if(n.has(s))continue;if(j(r)&&(r.API!==void 0||r.CALL!==void 0||r.EXE!==void 0||r.FUN!==void 0||r.IF!==void 0))continue;e.push(`${s} = ${Xe(r)}`)}let o=this.dialogs.filter((s)=>s.open).map((s)=>s.vModel);return e.push("",`Th\u1ED1ng k\xEA: ${this.apiCallCount} API call \xB7 ${this.toastCount} toast \xB7 ${this.watchTriggerCount} watch trigger \xB7 ${this.assertResults.filter((s)=>s.ok).length}/${this.assertResults.length} assert \u0111\u1EA1t${o.length>0?` \xB7 dialog \u0111ang m\u1EDF: ${o.join(", ")}`:""}`),e.join(`
286
- `)}}function wl(e){let t=new nt(e.paths);return us({store:t,session:e.session,api:e.api,activeTarget:async()=>{let n=e.targets.cwdTarget();if(n)return{projectId:n.projectId,moduleId:n.moduleId??"",moduleDir:n.moduleDir??""};return e.session.getTarget()}}),t}async function yl(e,t,n){let o=wl(e),s=await Ft(t),r=await Uo(mn(o,e.session),n.apiMocks,t),i=`${s?`${s}
292
+ `)[0].slice(0,300)}`)}if(Le.length>0)L.push("","\u2500\u2500 DOM AUDIT \u2500\u2500",...Le);if(b.size>0){L.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let P of b)L.push(`\u26A0 ${P}`)}if(Bt.length>0)L.push("","\u2500\u2500 STATE CU\u1ED0I (vueData) \u2500\u2500",...Bt);if(L.push(""),L.push(`Screenshot: ${bn}${kn?" (\u0111\xEDnh k\xE8m b\xEAn d\u01B0\u1EDBi)":" (qu\xE1 l\u1EDBn \u0111\u1EC3 inline \u2014 d\xF9ng Read tool \u0111\u1EC3 xem)"}`),L.push(`Full-page: ${K.join(Je,"render-full.png")}`),De)L.push(`Dialog \u0111ang m\u1EDF \u2014 ch\u1EE5p ri\xEAng: ${De}`);L.push(""),L.push(`Th\u1ED1ng k\xEA: ${A.length} API call (t\u1EA5t c\u1EA3 mock) \xB7 ${C.length} JS error (${eo} l\xFAc init) \xB7 ${S.length} Vue warn \xB7 ${D.filter((P)=>P.ok).length}/${D.length} assert \u0111\u1EA1t${te.dialogOpen?" \xB7 dialog \u0111ang m\u1EDF":""}`);let ju=D.some((P)=>!P.ok)||ue||C.length>0||te.appChildCount===0||d.length>0||R.size>0||G.size>0&&!t.offline;return{report:L.join(`
293
+ `),hasFailures:ju,screenshotPath:bn,inlineScreenshotBase64:kn,inlineScreenshotMime:ne}}finally{await vt.close().catch(()=>{return})}}var Wh="_componentPreview",gl=8;function Vh(e){return e.normalize("NFD").replace(/[\u0300-\u036F]/g,"").replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase()||"case"}function Jh(e){let t={},n={},o=[];for(let[s,r]of Object.entries(e)){if(s.startsWith("@")||s.startsWith("#")){o.push(`prop "${s}": FUI kh\xF4ng nh\u1EADn c\xFA ph\xE1p @/# trong JSON \u2014 \u0111\u1ED5i th\xE0nh "v-on:"/"v-slot:". \u0110\xE3 b\u1ECF qua.`);continue}if(s.startsWith(":")||s.startsWith("v-")){t[s]=r;continue}if(typeof r==="string"){t[s]=r;continue}if(typeof r==="boolean"||typeof r==="number"){t[`:${s}`]=JSON.stringify(r);continue}let i=`cp_${s.replace(/[^a-zA-Z0-9_]/g,"_")}`;n[i]=r,t[`:${s}`]=i}return{attr:t,hoisted:n,notes:o}}function Kh(e,t,n){let{attr:o,hoisted:s,notes:r}=Jh(n.props??{}),i={...n.vueData??{},...s},a={w:"12",el:e,attr:o};if(n.slot!==void 0)a.innerHTML=n.slot;return{moduleJson:{ModuleName:`preview: ${e} \u2014 ${t}`,data:Object.keys(i).length>0?[i]:[],watch:{},controls:[{prop:"fluid pa-0",rows:[{prop:"",cols:[a]}]}],set:{}},notes:r}}async function wl(e,t){try{await Xe.access(e)}catch{return!1}return await Xe.cp(e,t,{recursive:!0}),!0}async function Gh(e,t){let n=fn(e),o=[];if(t){let i=await U(de.join(ms(e,t),"_moduleInfo.json"),null);o.push(i?.Framework)}let s=await U(de.join(n,"_projectInfo.json"),null);if(o.push(s?.Framework),!o.some(Boolean))try{let i=await Xe.readdir(de.join(n,"modules"),{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let c=await U(de.join(n,"modules",a.name,"_moduleInfo.json"),null);if(c?.Framework){o.push(c.Framework);break}}}catch{}let r=o.find(Boolean);return String(r??"V3").toUpperCase()==="V2"?"V2":"V3"}async function yl(e){let t=await Hc(),n=e.projectId||t?.projectId;if(!n)throw Error("Kh\xF4ng x\xE1c \u0111\u1ECBnh \u0111\u01B0\u1EE3c projectId. Truy\u1EC1n projectId ho\u1EB7c ch\u1EA1y session_set tr\u01B0\u1EDBc.");let o=e.moduleId||(!e.projectId?t?.moduleId:void 0),s=fn(n),r=o?ms(n,o):s,i=de.join(r,"components"),a=o?`module (${o})`:"project (global)",c=de.join(i,`${e.componentName}.vue`);if(!(await F(c)).trim()){let I=await Me(i);throw Error(`Kh\xF4ng t\xECm th\u1EA5y ${e.componentName}.vue trong ${i}.`+(I.length?` C\xF3 s\u1EB5n: ${I.join(", ")}.`:" Th\u01B0 m\u1EE5c components r\u1ED7ng.")+(o?"":" N\u1EBFu \u0111\xE2y l\xE0 component c\u1EA5p module, truy\u1EC1n th\xEAm moduleId."))}let u=e.cases&&e.cases.length>0?e.cases:[{name:"default"}],d=e.devices&&e.devices.length>0?e.devices:["desktop","mobile"];if(u.length*d.length>gl)throw Error(`${u.length} case \xD7 ${d.length} device = ${u.length*d.length} l\u1EA7n render, v\u01B0\u1EE3t gi\u1EDBi h\u1EA1n ${gl}. `+"M\u1ED7i l\u1EA7n render l\xE0 m\u1ED9t phi\xEAn browser th\u1EADt \u2014 chia nh\u1ECF th\xE0nh nhi\u1EC1u l\u1EA7n g\u1ECDi.");let p=await Gh(n,o),m=de.join(hn(),Wh,nt(n,"projectId"),nt(e.componentName,"componentName")),w=de.join(m,"_preview");await Xe.mkdir(w,{recursive:!0});let y=[],v=new Set;for(let[I,g]of u.entries()){let b=g.name?.trim()||`case-${I+1}`,R=Vh(b),C=de.join(m,R);await Xe.rm(C,{recursive:!0,force:!0}),await Xe.mkdir(C,{recursive:!0}),await Xe.writeFile(de.join(C,"_moduleInfo.json"),JSON.stringify({ProjectID:n,ModuleID:`_cpreview_${e.componentName}`,ModuleName:`preview ${e.componentName}`,Framework:p},null,2),"utf-8");let{moduleJson:S,notes:A}=Kh(e.componentName,b,g);for(let D of A)v.add(`[${b}] ${D}`);if(await Xe.writeFile(de.join(C,"module.json"),JSON.stringify(S,null,2),"utf-8"),await wl(i,de.join(C,"components")),o)await wl(de.join(r,"imports"),de.join(C,"imports"));for(let D of d){let N=`${e.componentName} \xB7 ${b} \xB7 ${D}`,G=await Uo(C,{scenario:g.scenario??[],apiMocks:g.apiMocks??{},vueData:void 0,user:e.user,device:D,viewport:e.viewport,settleMs:e.settleMs,screenshotSteps:e.screenshotSteps,forceLocalProjectBundle:!0,reportLabel:N}),_=G.screenshotPath;if(G.screenshotPath){let ue=de.join(w,`${R}-${D}.png`);try{await Xe.rename(G.screenshotPath,ue),_=ue;let Ie=de.join(de.dirname(G.screenshotPath),"render-full.png");await Xe.rename(Ie,de.join(w,`${R}-${D}-full.png`)).catch(()=>{})}catch{}}y.push({caseName:b,device:D,report:G.report,hasFailures:G.hasFailures,screenshotPath:_,inlineScreenshotBase64:G.inlineScreenshotBase64,inlineScreenshotMime:G.inlineScreenshotMime})}}let E=[`PREVIEW COMPONENT: ${e.componentName}`,`Scope: ${a} \xB7 Project: ${n} \xB7 Framework ${p}`,`Ngu\u1ED3n: ${c}`,`Cases: ${u.map((I)=>I.name?.trim()||"(kh\xF4ng t\xEAn)").join(", ")} \xD7 Devices: ${d.join(", ")} = ${y.length} l\u1EA7n render`,`\u1EA2nh: ${w}`,"","Kh\xF4ng gian ri\xEAng: ch\u1EC9 component n\xE0y + CSS c\u1EE7a ch\xEDnh n\xF3 + theme/import c\u1EA5p project.","style.css c\u1EE7a module KH\xD4NG n\u1EA1p \u2014 component ph\u1EA3i t\u1EF1 \u0111\u1EE9ng \u0111\u01B0\u1EE3c. T\u1EA7ng project d\u1EF1ng t\u1EEB workspace","local n\xEAn b\u1EA3n ch\u01B0a push v\u1EABn hi\u1EC7n; ch\u1EA1y `project_sync` n\u1EBFu nghi file local \u0111\xE3 c\u0169."];if(v.size>0){E.push("","\u26A0 Ghi ch\xFA d\u1EF1ng harness:");for(let I of v)E.push(` \u2022 ${I}`)}return{header:E.join(`
294
+ `),runs:y,hasFailures:y.some((I)=>I.hasFailures)}}import Is from"vm";var bl=500,kl=15,vl=500;function Ye(e){if(typeof e==="string")return`"${e.length>40?e.slice(0,40)+"\u2026":e}"`;return Vn(e)}function Cs(e){return e.replace(/^`+|`+$/g,"")}function Sl(e){let t=e.split(/[\\/]+/).filter(Boolean),n=t.lastIndexOf("modules");return n>0?t[n-1]:null}async function Ho(e,t,n){let{loadAllSchemaProcNames:o,getVerifiedSpNames:s}=e;if(!t||Object.keys(t).length===0)return null;let r=Sl(n);if(!r)return null;let i=[];try{i=await o(r)}catch{return null}if(i.length===0)return null;let a=await s(),c=Uc(Object.keys(t),i,a);if(c.length===0)return null;return["\uD83D\uDEA8 C\u1EA4U TR\xDAC MOCK CH\u01AFA \u0110\u01AF\u1EE2C KI\u1EC2M CH\u1EE8NG \u2014 nghi ng\u1EDD t\u1EF1 suy di\u1EC5n thay v\xEC \u0111\u1ECDc t\u1EEB SP th\u1EADt:",...c.map((l)=>` - apiMocks["${l.mockKey}"] kh\u1EDBp t\xEAn SP C\xD3 TH\u1EACT "${l.spName}" trong schema local, nh\u01B0ng SP \u0111\xF3 ch\u01B0a t\u1EEBng \u0111\u01B0\u1EE3c db_sp_verify \u0111\u1ECDc th\xE2n trong phi\xEAn n\xE0y.`),` \u2192 Ch\u1EA1y db_sp_verify({ name: "${c[0].spName}" }) \u0111\u1EC3 \u0111\u1ECDc th\xE2n SP th\u1EADt r\u1ED3i COPY NGUY\xCAN V\u0102N snippet apiMocks n\xF3 sinh s\u1EB5n \u2014 kh\xF4ng t\u1EF1 \u0111o\xE1n t\xEAn c\u1ED9t/shape cho SP \u0111\xE3 t\u1ED3n t\u1EA1i, k\u1EBFt qu\u1EA3 m\xF4 ph\u1ECFng s\u1EBD l\u1EC7ch v\u1EDBi th\u1EF1c t\u1EBF.`].join(`
295
+ `)}function zh(e){let t=[],n=new Set;return Fe(e,(o,s,r)=>{if(r)return;let i=o.API;if(typeof i!=="string"||!i||i.includes("`")||i.includes("{{"))return;let a=o.IN;if(a!==void 0&&!(j(a)&&Object.keys(a).length===0))return;if(n.has(i))return;n.add(i),t.push({url:i,method:typeof o.METHOD==="string"?o.METHOD:"POST"})}),t}async function $l(e,t,n,o){let{getConnectionAlias:s,resolveApiSpName:r,writeTestData:i,classifySpSafety:a,callLiveApiSp:c,readTestData:l,generateMockForLocalSp:u}=e,d={...o},p=[],m=Sl(n);if(!m)return{mocks:d,trace:p};let w=zh(t);if(w.length===0)return{mocks:d,trace:p};let y=0;for(let{url:v,method:E}of w){let I=Jn(v);if(!I)continue;let g=await s(m,I.alias);if(!g)continue;let b=await r(m,g,I.fnName);if(!b)continue;let R=Object.keys(o).find((N)=>N===v);if(R??=Object.keys(o).find((N)=>v.endsWith(N)||N.endsWith(v)),R??=Object.keys(o).find((N)=>v.includes(N)||N.includes(v)),R!==void 0){await i(m,g,b,"user",o[R]);continue}let C=await a(m,g,b),S=!1;if(C==="safe-read"&&y<Lt){y++;let N=await c(m,g,I.isAuth,I.fnName,E);if(N.ok)d[v]=N.data,p.push(`live ${v} \u2192 SP ${b} (${g}) \u2014 g\u1ECDi tAPI th\u1EADt th\xE0nh c\xF4ng`),await i(m,g,b,"live",N.data),S=!0;else p.push(`\u26A0 live ${v} \u2192 SP ${b} (${g}) g\u1ECDi th\u1EADt th\u1EA5t b\u1EA1i (${N.error??`HTTP ${N.status}`}) \u2014 r\u01A1i xu\u1ED1ng cache/t\u1EF1 sinh`)}else if(C==="unsafe-write")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 l\u1EC7nh ghi \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="opaque")p.push(`skip ${v} \u2192 SP ${b} (${g}) c\xF3 \u0111o\u1EA1n \u0111\u1ECDc t\u0129nh kh\xF4ng nh\xECn xuy\xEAn qua \u0111\u01B0\u1EE3c (SQL \u0111\u1ED9ng / g\u1ECDi SP kh\xE1c) \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(C==="unknown")p.push(`skip ${v} \u2192 SP ${b} (${g}) ch\u01B0a c\xF3 th\xE2n SP local \u0111\u1EC3 ph\xE2n lo\u1EA1i an to\xE0n \u2014 KH\xD4NG g\u1ECDi th\u1EADt, d\xF9ng cache/t\u1EF1 sinh`);else if(y>=Lt)p.push(`skip ${v} \u2192 SP ${b} (${g}) \u0111\xE3 \u0111\u1EA1t tr\u1EA7n ${Lt} l\u01B0\u1EE3t g\u1ECDi th\u1EADt/l\u01B0\u1EE3t simulate \u2014 d\xF9ng cache/t\u1EF1 sinh`);if(S)continue;let A=await l(m,g,b);if(A){d[v]=A.data,p.push(`cache ${v} \u2192 SP ${b} (${g}) \u2014 d\xF9ng _testdata cache (ngu\u1ED3n: ${A.source}, l\u01B0u l\xFAc ${A.savedAt})`);continue}let D=await u(m,g,b);if(D)d[v]=D,p.push(`gen ${v} \u2192 SP ${b} (${g}) \u2014 t\u1EF1 sinh mock t\u1EEB c\u1EA5u tr\xFAc SELECT, \u0111\xE3 l\u01B0u cache`),await i(m,g,b,"generated",D)}return{mocks:d,trace:p}}class Rs{vueData;moduleJson;opts;sandbox;ctx;trace=[];warnings=new Set;depth=0;apiCallCount=0;toastCount=0;watchTriggerCount=0;assertResults=[];dialogs=[];watchSnapshots=new Map;stubUsed=new Set;scannedFunctionRightCodes;systemRightDefaulted;functionRightDefaulted;constructor(e,t){this.moduleJson=e,this.opts=t,this.systemRightDefaulted=!("SystemRight"in(t.user??{})),this.functionRightDefaulted=!("FunctionRight"in(t.user??{})),this.scannedFunctionRightCodes=Mt([JSON.stringify(e)]),this.vueData={p_domain:"sim.local",p_routers:[],p_params:"",p_urlReferrer:"",p_router:"/sim/module",v_Loading:!1,v_LoadingOverlay:[],v_containerOn:!0,user:{UserID:null,UserName:null,LastName:null,FirstName:null,Sex:null,Phone:null,Email:null,DepartmentID:null,GroupID:null,SystemRight:9,FunctionRight:this.scannedFunctionRightCodes,LastLogin:null,...t.user??{}},v_Set:{title:null,apiDomain:"",login:!1,menu:[],userInfo:""}},this.sandbox=this.buildSandbox(),this.ctx=Is.createContext(this.sandbox)}log(e){if(this.trace.length===bl)this.trace.push("\u2026 (trace b\u1ECB c\u1EAFt \u2014 qu\xE1 d\xE0i)");if(this.trace.length>bl)return;this.trace.push(" ".repeat(Math.min(this.depth,6))+e)}warn(e){this.warnings.add(e),this.log(`\u26A0 ${e}`)}rightTest(e){if(!j(e))return!0;let t=this.vueData.user??{};for(let[n,o]of Object.entries(e)){let s=Array.isArray(o)?o:[o],r=t[n],i=Array.isArray(r)?r:[r];if(!s.some((a)=>i.includes(a)))return!1}return!0}buildSandbox(){let e=this,t=(r,i)=>(...a)=>(e.stubUsed.add(r),e.log(`FUN ${r}(${a.map((c)=>Ye(c)).join(", ")}) \u2014 stub, kh\xF4ng m\xF4 ph\u1ECFng UI th\u1EADt`),i),n=(r)=>{e.stubUsed.add("moment");let i=r!==void 0&&r!==null?new Date(r):new Date,a=(u)=>String(u).padStart(2,"0"),c={},l=(u)=>{let d=`${i.getFullYear()}-${a(i.getMonth()+1)}-${a(i.getDate())}`;if(!u)return`${d}T${a(i.getHours())}:${a(i.getMinutes())}:${a(i.getSeconds())}`;return u.replace(/YYYY/g,String(i.getFullYear())).replace(/MM/g,a(i.getMonth()+1)).replace(/DD/g,a(i.getDate())).replace(/HH/g,a(i.getHours())).replace(/mm/g,a(i.getMinutes())).replace(/ss/g,a(i.getSeconds()))};return Object.assign(c,{format:l,add:()=>c,subtract:()=>c,startOf:()=>c,endOf:()=>c,valueOf:()=>i.getTime(),toDate:()=>i,toString:()=>l(),year:()=>i.getFullYear(),month:()=>i.getMonth(),date:()=>i.getDate(),daysInMonth:()=>new Date(i.getFullYear(),i.getMonth()+1,0).getDate(),locale:()=>c,isValid:()=>!isNaN(i.getTime())}),c},o={forEach:(r,i)=>{if(Array.isArray(r))r.forEach((a,c)=>i(a,c));else if(j(r))Object.entries(r).forEach(([a,c])=>i(c,a));return r},map:(r,i)=>{let a=Array.isArray(r)?r:j(r)?Object.values(r):[];if(typeof i==="string")return a.map((c)=>c?.[i]);return a.map(i)},filter:(r,i)=>Array.isArray(r)?r.filter(i):[],find:(r,i)=>Array.isArray(r)?r.find(i):void 0,findIndex:(r,i)=>Array.isArray(r)?r.findIndex(i):-1,includes:(r,i)=>Array.isArray(r)||typeof r==="string"?r.includes(i):!1,cloneDeep:(r)=>r===void 0?void 0:JSON.parse(JSON.stringify(r)),isArray:Array.isArray,isString:(r)=>typeof r==="string",isNumber:(r)=>typeof r==="number",isPlainObject:j,isEmpty:(r)=>r==null||typeof r==="string"&&r===""||Array.isArray(r)&&r.length===0||j(r)&&Object.keys(r).length===0,isUndefined:(r)=>r===void 0,keys:(r)=>j(r)?Object.keys(r):[],values:(r)=>j(r)?Object.values(r):[],uniq:(r)=>[...new Set(r)],uniqBy:(r,i)=>{let a=new Set;return(r??[]).filter((c)=>{let l=c?.[i];if(a.has(l))return!1;return a.add(l),!0})},sumBy:(r,i)=>(r??[]).reduce((a,c)=>a+Number(typeof i==="string"?c?.[i]??0:i(c)),0),maxBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])>Number(a?.[i])?c:a,void 0),minBy:(r,i)=>(r??[]).reduce((a,c)=>a===void 0||Number(c?.[i])<Number(a?.[i])?c:a,void 0),orderBy:(r,i,a)=>{let c=Array.isArray(i)?i:[i],l=Array.isArray(a)?a:[a??"asc"];return[...r??[]].sort((u,d)=>{for(let p=0;p<c.length;p++){let m=u?.[c[p]],w=d?.[c[p]];if(m===w)continue;let y=m>w?1:-1;return(l[p]??"asc")==="desc"?-y:y}return 0})},sortBy:(r,i)=>o.orderBy(r,i),groupBy:(r,i)=>{let a={};return(r??[]).forEach((c)=>{let l=String(c?.[i]);(a[l]??=[]).push(c)}),a},without:(r,...i)=>(r??[]).filter((a)=>!i.includes(a)),intersection:(r,i)=>(r??[]).filter((a)=>(i??[]).includes(a)),castArray:(r)=>Array.isArray(r)?r:[r],trim:(r,i)=>{if(typeof r!=="string")return r;if(!i)return r.trim();let a=i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return r.replace(new RegExp(`^[${a}]+|[${a}]+$`,"g"),"")},replace:(r,i,a)=>String(r).replace(i,a),split:(r,i,a)=>String(r).split(i,a),startsWith:(r,i)=>String(r).startsWith(i),endsWith:(r,i)=>String(r).endsWith(i),concat:(...r)=>[].concat(...r),take:(r,i)=>(r??[]).slice(0,i),last:(r)=>(r??[])[(r??[]).length-1],head:(r)=>(r??[])[0],first:(r)=>(r??[])[0],round:(r,i=0)=>Math.round(r*10**i)/10**i,assignIn:Object.assign,assign:Object.assign,at:(r,i)=>[e.getPath(r,i)],property:(r)=>(i)=>e.getPath(i,r)},s=new Proxy(o,{get(r,i){if(i in r)return r[i];return(...a)=>{e.warn(`_.${i} ch\u01B0a h\u1ED7 tr\u1EE3 trong simulator \u2014 tr\u1EA3 undefined`);return}}});return{vueData:this.vueData,console:{log:(...r)=>e.log(`console.log: ${r.map((i)=>typeof i==="string"?i:Ye(i)).join(" ")}`),error:(...r)=>e.log(`console.error: ${r.map((i)=>typeof i==="string"?i:Ye(i)).join(" ")}`),warn:(...r)=>e.log(`console.warn: ${r.map((i)=>typeof i==="string"?i:Ye(i)).join(" ")}`)},JSON,Math,Date,Array,Object,String,Number,Boolean,RegExp,parseInt,parseFloat,isNaN,isFinite,encodeURIComponent,decodeURIComponent,encodeURI,decodeURI,moment:n,_:s,CALL:(r,i)=>e.runAction(r,i),groupBy:(r,i,a)=>{let c=Array.isArray(a)?a:[a],l={};return(r??[]).forEach((u)=>{let d=String(u?.[i]);(l[d]??=[]).push(u)}),Object.entries(l).map(([u,d])=>{let p={[i]:u,count:d.length};for(let m of c)if(m)p[m]=d.reduce((w,y)=>w+Number(y?.[m]??0),0);return p})},buildHeader:(r,i)=>{let a=[];if(i)(Array.isArray(r)?r:[]).forEach((c)=>a.push({text:c[i.text],value:c[i.value],divider:!0}));else{let c=Array.isArray(r)?r[0]:j(r)&&Array.isArray(r.data)?r.data[0]:void 0;if(j(c))for(let[l,u]of Object.entries(c))a.push(typeof u==="number"?{el:"t-num",text:l,value:l,divider:!0}:{text:l,value:l,divider:!0})}return a},findInArray:(r)=>(r?.src??[]).find((i)=>i?.[r.key]===r.value),openWindow:(r)=>{e.stubUsed.add("openWindow"),e.log(`FUN openWindow id=${String(r?.id??"?")} url=${String(r?.url??"?")} \u2014 stub (sub-window kh\xF4ng m\xF4 ph\u1ECFng)`)},showMessage:(r)=>{if(e.log(`MESSBOX/showMessage "${String(r?.title??"")}"${r?.message?` \u2014 ${String(r.message)}`:""}`),r?.onclose)e.log("(gi\u1EA3 l\u1EADp user \u0111\xF3ng dialog \u2192 ch\u1EA1y onclose)"),e.runAction(r.onclose)},confirm:(r)=>{if(e.log(`CONFIRM "${String(r?.title??"")}" \u2192 OK (m\u1EB7c \u0111\u1ECBnh simulator)`),typeof r?.action==="function")r.action()},ajaxCALL:t("ajaxCALL"),jsonToExcel:t("jsonToExcel"),printPDF:t("printPDF"),copyToClipboard:t("copyToClipboard"),redirect:t("redirect"),reload:t("reload"),logout:t("logout"),webSocket_Send:t("webSocket_Send"),webSocketJoinGroup:t("webSocketJoinGroup"),appCommand:t("appCommand"),pushRouter:t("pushRouter"),capacityText:(r)=>`${r}`,generateID:()=>`sim${Math.floor(Math.random()*1e6)}`,rightTest:(r)=>e.rightTest(r),fillData:t("fillData"),chartDataBuild:t("chartDataBuild"),fixURL:(r)=>r,alert:t("alert")}}evalExpr(e,t){return this.sandbox.__scope__=t,this.sandbox.__ret__=void 0,Is.runInContext(`with(__scope__) { __ret__ = ${e} }`,this.ctx,{timeout:vl}),this.sandbox.__ret__}evalJs(e,t){this.sandbox.__scope__=t,Is.runInContext(`with(__scope__) { ${e} }`,this.ctx,{timeout:vl})}bindDataToString(e,t){if(typeof e!=="string")return e;return e.replace(/{{([\s\S]+?)}}/g,(n,o)=>{let s=this.evalExpr(o,t);return s===void 0||s===null?"":String(s)})}getPath(e,t){let n=String(t).match(/[^.[\]]+/g)??[],o=e;for(let s of n){if(o==null)return;o=o[s]}return o}getVueData(e,t){if(typeof e==="string"){if(e==="")return"";let n=e.replace("vueData.","").trim();if(n[0]==="`")return Cs(this.bindDataToString(n,t));let o=n;if(/^{{.*}}$/gm.test(o))o=o.replace(/^{{|}}$/gm,"");try{return this.evalExpr(o,t)}catch{if(/({{.*}}|[ ])/gm.test(n))try{return Cs(this.bindDataToString(n,t))}catch{return}return}}else if(j(e)&&e.ARRAY&&e.COL){let n=this.getPath(t,String(e.ARRAY));return Array.isArray(n)?n.map((o)=>this.getPath(o,String(e.COL))):[]}return e}setValue(e,t,n,o,s){if(Array.isArray(t)){if(!j(e)&&!Array.isArray(e))return;let r=e,i=s??0,a=t[i];if(typeof a==="string"&&a[a.length-1]==="]")if(a=a.replaceAll("]",""),/^[0-9]*$/gm.test(a))a=parseInt(a,10);else if(a[0]==="'")a=a.replaceAll("'","");else a=this.getVueData(a,o??this.vueData);if(t.length-1===i){r[a]=n;return}if(typeof r[a]>"u"){let c=t[i+1];r[a]=c[c.length-1]==="]"?[]:{}}this.setValue(r[a],t,n,o,i+1)}else if(typeof t==="string")if(t[0]==="`"||t[0]==="'")this.setValue(e,[t.replaceAll("'","").replaceAll("`","")],n,o,0);else this.setValue(e,t.match(/[^.[]+/g)??[t],n,o,0)}mapData(e,t,n,o){if(typeof e==="string"){this.setValue(t,e,n);return}if(!j(e))return;for(let[s,r]of Object.entries(e)){if(o)n={...this.vueData,...o};let i;try{i=this.bindDataToString(s,this.vueData)}catch{i=s}if(typeof r==="string"&&r[0]==="#")this.warn(`\u0111\u1ECDc cross-window "${r}" \u2014 kh\xF4ng m\xF4 ph\u1ECFng \u0111\u01B0\u1EE3c, g\xE1n undefined`),this.setValue(t,i,void 0);else if(i[0]==="#")this.log(`\u2192 ghi cross-window ${i} = ${Ye(this.getVueData(r,n))} (kh\xF4ng m\xF4 ph\u1ECFng)`);else this.setValue(t,i,this.getVueData(r,n),n)}}defProp(e){if(typeof e==="string"){if(typeof this.vueData[e]>"u")this.vueData[e]=[]}else if(j(e)){for(let t of Object.keys(e))if(typeof this.vueData[t]>"u")this.vueData[t]=void 0}}runAction(e,t){if(!e)return;let n={...this.vueData,...t??{}};if(typeof e==="string"){let s=this.getVueData(e,n);if(!s){this.warn(`Action "${e}" undefined \u2014 CALL tr\u1ECF t\u1EDBi action kh\xF4ng t\u1ED3n t\u1EA1i?`);return}e=s}if(!e)return;let o=Array.isArray(e)?e:[e];for(let s of o)if(j(s)&&s.CONFIRM){let r;try{r=this.bindDataToString(String(s.CONFIRM),n)}catch{r=String(s.CONFIRM)}let i=t?.__cancelConfirm===!0;if(this.log(`CONFIRM "${r}" \u2192 ${i?"H\u1EE6Y":"OK"}`),i){if(s.CANCEL)this.vueActionSafe(s.CANCEL,t)}else{let a=JSON.parse(JSON.stringify(s));delete a.CONFIRM,delete a.CANCEL,this.vueActionSafe(a,t)}}else this.vueActionSafe(s,t)}vueActionSafe(e,t){this.depth++;try{this.vueAction(e,t)}catch(n){this.warn(`L\u1ED7i khi ch\u1EA1y action: ${n.message} \u2014 action: ${_e(e)}`)}finally{this.depth--}}vueAction(e,t){if(!e)return;if(typeof e==="string"){this.log(`EXE ${e}`),this.evalJs(e,{...t??{},...this.vueData});return}if(!j(e))return;let n=JSON.parse(JSON.stringify(e)),o={...this.vueData,...t??{}};if(n.IN!==void 0)if(typeof n.IN==="string")this.mapData({IN:n.IN},n,o);else{let r={};this.mapData(n.IN,r,o),n.IN=r}if(n.API!==void 0){try{n.API=this.bindDataToString(String(n.API),o)}catch{}if(n.HEADER!==void 0)if(typeof n.HEADER==="string")this.mapData({HEADER:n.HEADER},n,o);else this.mapData(n.HEADER,n.HEADER,o);this.defProp(n.OUT),this.callAPI(n,t);return}if(n.CALL!==void 0){this.log(`CALL ${String(n.CALL)}`),this.runAction(n.CALL,t);return}if(n.EXE!==void 0){this.log(`EXE ${String(n.EXE).replace(/\s+/g," ").slice(0,100)}`),this.evalJs(String(n.EXE),{...t??{},...this.vueData});return}if(n.FUN!==void 0){let r;try{r=this.bindDataToString(String(n.FUN),o)}catch{r=String(n.FUN)}let i=this.sandbox[r];if(typeof i==="function"){let a=i(n.IN);if(n.OUT!==void 0)this.mapData(n.OUT,this.vueData,a);if(n.CALLBACK!==void 0)this.runAction(n.CALLBACK,t)}else this.warn(`FUN "${r}" kh\xF4ng t\u1ED3n t\u1EA1i (simulator ch\u1EC9 c\xF3 c\xE1c h\xE0m FUI chu\u1EA9n)`);return}if(n.IF!==void 0){let r=!1;try{r=String(this.evalExpr(String(n.IF),{...t??{},...this.vueData}))==="true"}catch(a){this.warn(`IF "${String(n.IF)}" l\u1ED7i eval: ${a.message} \u2192 coi nh\u01B0 false`)}this.log(`IF(${String(n.IF).slice(0,60)}) \u2192 ${r?"THEN":"ELSE"}`);let i=r?n.THEN:n.ELSE;if(typeof i==="string")this.log(`EXE ${i.slice(0,80)}`),this.evalJs(i,{...t??{},...this.vueData});else if(i!==void 0)this.runAction(i,t);return}if(n.MESS!==void 0){let r;try{r=this.bindDataToString(String(n.MESS),o)}catch{r=String(n.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${r}"`);return}if(n.MESSBOX!==void 0){let r;try{r=this.bindDataToString(String(n.MESSBOX),o)}catch{r=String(n.MESSBOX)}this.log(`MESSBOX "${r}"`);return}let s=[];for(let r of Object.keys(n))s.push(r);if(this.mapData(n,this.vueData,this.vueData,t),s.length>0)this.log(`SET ${s.map((r)=>`${r} = ${Ye(r[0]==="#"?"(cross-window)":this.getPath(this.vueData,r))}`).join(" \xB7 ")}`)}callAPI(e,t){let n=String(e.API),o=e.METHOD?String(e.METHOD):"POST";this.apiCallCount++,this.log(`API ${o} ${n} IN=${e.IN!==void 0?JSON.stringify(e.IN)?.slice(0,200):"{}"}`);let s=this.opts.apiMocks,r=Object.keys(s).find((a)=>a===n);r??=Object.keys(s).find((a)=>n.endsWith(a)||a.endsWith(n)),r??=Object.keys(s).find((a)=>n.includes(a)||a.includes(n));let i;if(r!==void 0)i=JSON.parse(JSON.stringify(s[r])),this.log(`(mock kh\u1EDBp "${r}")`);else i={data:[]},this.warn(`kh\xF4ng c\xF3 mock cho API "${n}" \u2014 tr\u1EA3 m\u1EB7c \u0111\u1ECBnh {data: []}`);if(j(i)&&i.__error!==void 0){let a=j(i.__error)?i.__error:{},c={status:a.status??500,responseJSON:a.responseJSON??{Message:"Simulated error"}};if(this.log(`\u2190 HTTP ${String(c.status)} (mock l\u1ED7i)`),e.ERROROUT!==void 0)this.mapData(e.ERROROUT,this.vueData,c);if(e.ERRORCALLBACK!==void 0)this.runAction(e.ERRORCALLBACK,t);if(e.ERROROUT!==void 0)return;let l=c.responseJSON;this.warn(`API "${n}" l\u1ED7i kh\xF4ng \u0111\u01B0\u1EE3c b\u1EAFt (kh\xF4ng c\xF3 ERROROUT) \u2192 hi\u1EC7n ERROR DIALOG m\u1EB7c \u0111\u1ECBnh: ${typeof l==="string"?l:j(l)?String(l.Message??""):""}`);return}if(e.OUT!==void 0){let a=i;if(typeof e.OUT==="string"&&j(i)&&Object.keys(i).length===1&&i.data)a=i.data;this.mapData(e.OUT,this.vueData,a);let c=typeof e.OUT==="string"?`${e.OUT} = ${Ye(this.vueData[e.OUT])}`:Object.keys(e.OUT).map((l)=>`${l} = ${Ye(this.getPath(this.vueData,l))}`).join(" \xB7 ");this.log(`\u2190 OUT ${c}`)}else this.log("\u2190 (kh\xF4ng c\xF3 OUT)");if(e.MESS!==void 0){let a;try{a=this.bindDataToString(String(e.MESS),this.vueData)}catch{a=String(e.MESS)}this.toastCount++,this.log(`MESS \uD83D\uDD14 "${a}"`)}if(e.CALLBACK!==void 0)this.runAction(e.CALLBACK,t)}initWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{};for(let o of Object.keys(e)){if(o==="deep-watch")continue;this.setValue(this.vueData,o,this.getVueData(o,this.vueData))}let t=(o,s)=>{let r=this.getPath(this.vueData,o);this.watchSnapshots.set(`${s?"D:":"S:"}${o}`,{deep:s,snap:s?JSON.stringify(r):r})},n=e["deep-watch"];if(j(n))for(let o of Object.keys(n))t(o,!0);for(let o of Object.keys(e)){if(o==="deep-watch")continue;t(o,!1)}}checkWatches(){let e=j(this.moduleJson.watch)?this.moduleJson.watch:{},t=j(e["deep-watch"])?e["deep-watch"]:{};for(let n=0;n<kl;n++){let o=[];for(let[s,r]of this.watchSnapshots){let i=s.slice(2),a=this.getPath(this.vueData,i);if(r.deep){let c=JSON.stringify(a);if(c!==r.snap)o.push({key:i,handler:t[i],oldVal:r.snap}),r.snap=c}else if(!Object.is(a,r.snap))o.push({key:i,handler:e[i],oldVal:r.snap}),r.snap=a}if(o.length===0)return;for(let s of o){this.watchTriggerCount++,this.log(`WATCH ${s.key} thay \u0111\u1ED5i \u2192 ${_e(s.handler)}`);let r={};this.setValue(r,s.key,s.oldVal),this.depth++;try{this.runAction(s.handler,{v_old:r})}finally{this.depth--}}}this.warn(`watch cascade v\u01B0\u1EE3t ${kl} v\xF2ng \u2014 nguy c\u01A1 V\xD2NG L\u1EB6P V\xD4 H\u1EA0N gi\u1EEFa c\xE1c watch/action`)}initDialogs(){(Array.isArray(this.moduleJson.controls)?this.moduleJson.controls:[]).forEach((t,n)=>{if(!j(t)||t.el!=="hidden-container")return;let o=j(t.attr)?t.attr:{},s=t["v-model"]??o["v-model"];if(typeof s==="string"){let r=s.replace("vueData.","").split(/[.[]/)[0];this.dialogs.push({vModel:r,where:`container ${n+1}`,open:Boolean(this.getPath(this.vueData,r))})}})}checkDialogs(){for(let e of this.dialogs){let t=Boolean(this.getPath(this.vueData,e.vModel));if(t!==e.open)e.open=t,this.log(`DIALOG ${e.vModel} (${e.where}): ${t?"M\u1EDE \uD83D\uDD32":"\u0110\xD3NG \u25AA"}`)}}declareControlVars(){let e=/^[a-zA-Z0-9$_\-.[\]]*$/,t=(n)=>{if(Array.isArray(n)){n.forEach(t);return}if(!j(n))return;let o=j(n.attr)?n.attr:{};for(let[s,r]of Object.entries(o)){if(typeof r!=="string")continue;if(s==="v-model"){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:void 0},this.vueData,{})}else if(s[0]===":"&&e.test(r)){if(!r.includes("vueData.")&&this.getVueData(r,this.vueData)===void 0)this.mapData({[r]:s===":items"?[]:void 0},this.vueData,{})}}if(Array.isArray(n.innerHTML))n.innerHTML.forEach(t);if(Array.isArray(n.rows))n.rows.forEach(t);if(Array.isArray(n.cols))n.cols.forEach(t)};t(this.moduleJson.controls)}collectClickables(){let e=[],t=(n,o)=>{if(Array.isArray(n)){n.forEach((a)=>t(a,o));return}if(!j(n))return;let s=j(n.attr)?n.attr:{},r=s["v-on:click"]??s[":action"]??s.action;if(r!==void 0){let a=[];for(let c of["label",":label","innerText","text"]){let l=s[c]??n[c];if(typeof l==="string"&&l)a.push(Cs(l))}if(typeof n.innerHTML==="string"&&n.innerHTML)a.push(n.innerHTML.replace(/<[^>]*>/g,"").trim());e.push({labels:a,handler:r,el:typeof n.el==="string"?n.el:"?",tableItemsExpr:o})}let i=o;if(n.el==="f-table"){let a=s[":items"];if(typeof a==="string")i=a}if(Array.isArray(n.innerHTML))n.innerHTML.forEach((a)=>t(a,i));if(Array.isArray(n.rows))n.rows.forEach((a)=>t(a,i));if(Array.isArray(n.cols))n.cols.forEach((a)=>t(a,i))};return t(this.moduleJson.controls),e}simulate(e){if(this.log("\u2500\u2500 INIT \u2500\u2500"),j(this.moduleJson.set))Object.assign(this.vueData.v_Set,this.moduleJson.set);if(this.opts.urlParams)for(let[n,o]of Object.entries(this.opts.urlParams))this.vueData[n]=o;try{this.runAction(this.moduleJson.data)}catch(n){this.warn(`L\u1ED7i khi init data[]: ${n.message}`)}this.declareControlVars(),this.initWatches(),this.initDialogs(),this.checkDialogs();let t=this.collectClickables();return e.forEach((n,o)=>{let s=o+1;if(j(n.set)){this.log(""),this.log(`\u2500\u2500 STEP ${s}: set ${Object.entries(n.set).map(([r,i])=>`${r}=${Ye(i)}`).join(", ")} \u2500\u2500`);for(let[r,i]of Object.entries(n.set))this.setValue(this.vueData,r,i)}else if(typeof n.click==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: click "${n.click}" \u2500\u2500`);let r=n.click.toLowerCase(),i=t.filter((d)=>d.labels.some((p)=>p.toLowerCase()===r)),a=i.length>0?i:t.filter((d)=>d.labels.some((p)=>p.toLowerCase().includes(r)));if(a.length===0){this.warn(`kh\xF4ng t\xECm th\u1EA5y control c\xF3 label "${n.click}" k\xE8m v-on:click/:action`);return}let c=typeof n.nth==="number"?n.nth-1:0;if(a.length>1)this.log(`(kh\u1EDBp ${a.length} control \u2014 d\xF9ng #${c+1})`);let l=a[Math.min(c,a.length-1)],u;if(l.tableItemsExpr!==void 0||n.item!==void 0||n.index!==void 0){let d=n.item,p=typeof n.index==="number"?n.index:0;if(d===void 0&&l.tableItemsExpr){let m=this.getVueData(l.tableItemsExpr,this.vueData);if(Array.isArray(m)&&m.length>0)d=m[Math.min(p,m.length-1)],this.log(`(control trong f-table \u2014 d\xF9ng row ${p} c\u1EE7a ${l.tableItemsExpr})`);else d={},this.warn(`control n\u1EB1m trong f-table nh\u01B0ng "${l.tableItemsExpr??""}" r\u1ED7ng \u2014 item = {} (truy\u1EC1n "item" trong event \u0111\u1EC3 ch\u1EC9 \u0111\u1ECBnh)`)}u={$row:d,item:d,index:p}}if(n.cancelConfirm===!0)u={...u??{},__cancelConfirm:!0};this.runAction(l.handler,u)}else if(typeof n.call==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: call ${n.call} \u2500\u2500`);let r;if(n.item!==void 0||n.index!==void 0)r={$row:n.item,item:n.item,index:typeof n.index==="number"?n.index:0};if(n.cancelConfirm===!0)r={...r??{},__cancelConfirm:!0};this.runAction(n.call,r)}else if(typeof n.exe==="string"){this.log(""),this.log(`\u2500\u2500 STEP ${s}: exe \u2500\u2500`),this.log(`EXE ${n.exe.slice(0,100)}`);try{this.evalJs(n.exe,{...this.vueData})}catch(r){this.warn(`exe l\u1ED7i: ${r.message}`)}}else if(typeof n.assert==="string"){let r=typeof n.label==="string"?n.label:n.assert,i=!1;try{i=Boolean(this.evalExpr(n.assert,{...this.vueData}))}catch(a){this.warn(`assert "${n.assert}" l\u1ED7i eval: ${a.message}`)}this.assertResults.push({label:r,ok:i}),this.log(""),this.log(`\u2500\u2500 STEP ${s}: assert ${i?"\u2714":"\u2718"} ${r} \u2500\u2500`);return}else{this.warn(`event ${s} kh\xF4ng h\u1EE3p l\u1EC7 \u2014 c\u1EA7n m\u1ED9t trong: set / click / call / exe / assert`);return}this.checkWatches(),this.checkDialogs()}),{report:this.buildReport(),hasFailures:this.assertResults.some((n)=>!n.ok)}}buildRightDefaultNote(){let e=[];if(this.systemRightDefaulted)e.push("SystemRight=9 (m\u1EB7c \u0111\u1ECBnh, quy\u1EC1n cao nh\u1EA5t)");if(this.functionRightDefaulted)e.push(this.scannedFunctionRightCodes.length?`FunctionRight=[${this.scannedFunctionRightCodes.join(", ")}] (m\u1EB7c \u0111\u1ECBnh \u2014 qu\xE9t \u0111\u01B0\u1EE3c t\u1EEB module.json)`:"FunctionRight=[] (m\u1EB7c \u0111\u1ECBnh \u2014 kh\xF4ng qu\xE9t \u0111\u01B0\u1EE3c m\xE3 n\xE0o trong module.json)");if(e.length===0)return null;return`user preview: ${e.join(" \xB7 ")}. rightTest()/v-if theo FunctionRight kh\xE1c m\xE3 tr\xEAn s\u1EBD tr\u1EA3 false \u2014 truy\u1EC1n tham s\u1ED1 \`user\` \u0111\u1EC3 test vai tr\xF2 h\u1EA1n ch\u1EBF h\u01A1n.`}buildReport(){let e=[...this.trace,""];e.push("\u2550\u2550\u2550 K\u1EBET QU\u1EA2 \u2550\u2550\u2550");let t=this.buildRightDefaultNote();if(t)e.push(`\u2139 ${t}`);if(this.functionRightDefaulted){let s=qn(this.scannedFunctionRightCodes,this.opts.definedFunctionRights??null);if(s)e.push(s)}for(let s of this.assertResults)e.push(`${s.ok?"\u2714":"\u2718"} assert: ${s.label}`);if(this.warnings.size>0){e.push("","\u2500\u2500 C\u1EA3nh b\xE1o \u2500\u2500");for(let s of this.warnings)e.push(`\u26A0 ${s}`)}if(this.stubUsed.size>0)e.push("",`(stub \u0111\xE3 d\xF9ng \u2014 k\u1EBFt qu\u1EA3 c\xE1c h\xE0m n\xE0y KH\xD4NG ph\u1EA3n \xE1nh runtime th\u1EADt: ${[...this.stubUsed].join(", ")})`);e.push("","\u2500\u2500 STATE CU\u1ED0I \u2500\u2500");let n=new Set(["p_domain","p_routers","p_params","p_urlReferrer","p_router","v_Loading","v_LoadingOverlay","v_containerOn","user","v_Set"]);for(let[s,r]of Object.entries(this.vueData)){if(n.has(s))continue;if(j(r)&&(r.API!==void 0||r.CALL!==void 0||r.EXE!==void 0||r.FUN!==void 0||r.IF!==void 0))continue;e.push(`${s} = ${Ye(r)}`)}let o=this.dialogs.filter((s)=>s.open).map((s)=>s.vModel);return e.push("",`Th\u1ED1ng k\xEA: ${this.apiCallCount} API call \xB7 ${this.toastCount} toast \xB7 ${this.watchTriggerCount} watch trigger \xB7 ${this.assertResults.filter((s)=>s.ok).length}/${this.assertResults.length} assert \u0111\u1EA1t${o.length>0?` \xB7 dialog \u0111ang m\u1EDF: ${o.join(", ")}`:""}`),e.join(`
296
+ `)}}function jl(e){let t=new ot(e.paths);return ps({store:t,session:e.session,api:e.api,activeTarget:async()=>{let n=e.targets.cwdTarget();if(n)return{projectId:n.projectId,moduleId:n.moduleId??"",moduleDir:n.moduleDir??""};return e.session.getTarget()}}),t}async function Il(e){try{return await e()}catch(t){if(t instanceof Error)t.message=Be(t.message);throw t}}async function Cl(e,t,n){let o=jl(e),s=await Ft(t),r=await Ho(mn(o,e.session),n.apiMocks,t),i=`${s?`${s}
287
297
 
288
298
  `:""}${r?`${r}
289
299
 
290
- `:""}`,a=await Lo(t,n);return{text:`${i}Render UI: ${t}
300
+ `:""}`,a=await Il(()=>Uo(t,n));return{text:Be(`${i}Render UI: ${t}
291
301
 
292
302
  ${a.report}
293
- `,hasFailures:a.hasFailures,screenshots:a.screenshotPath?[a.screenshotPath]:[]}}async function bl(e,t){wl(e);let n=await dl(t),o=n.runs.map((r)=>`${r.hasFailures?"\u2718":"\u2714"} ${r.caseName} \xB7 ${r.device}${r.screenshotPath?` \u2192 ${Df(r.screenshotPath)}`:""}`).join(`
303
+ `),hasFailures:a.hasFailures,screenshots:a.screenshotPath?[a.screenshotPath]:[]}}async function Rl(e,t){jl(e);let n=await Il(()=>yl(t)),o=n.runs.map((r)=>`${r.hasFailures?"\u2718":"\u2714"} ${r.caseName} \xB7 ${r.device}${r.screenshotPath?` \u2192 ${Xh(r.screenshotPath)}`:""}`).join(`
294
304
  `),s=[`${n.header}
295
305
 
296
- \u2500\u2500 T\u1ED4NG H\u1EE2P \u2500\u2500
306
+ \u2500\u2500 SUMMARY \u2500\u2500
297
307
  ${o}`];for(let r of n.runs)if(s.push(`
298
308
  \u2550\u2550 ${r.caseName} \xB7 ${r.device} \u2550\u2550
299
- ${r.report}`),r.screenshotPath)s.push(`screenshot: ${r.screenshotPath}`);return{text:`${s.join(`
309
+ ${r.report}`),r.screenshotPath)s.push(`screenshot: ${r.screenshotPath}`);return{text:Be(`${s.join(`
300
310
  `)}
301
- `,hasFailures:n.hasFailures,screenshots:n.runs.flatMap((r)=>r.screenshotPath?[r.screenshotPath]:[])}}import{dirname as kl,join as xf}from"path";async function $l(e,t,n){let o=mn(new nt(e.paths),e.session),s=await Ft(t),r=await Uo(o,n.apiMocks,t),i=`${s?`${s}
311
+ `),hasFailures:n.hasFailures,screenshots:n.runs.flatMap((r)=>r.screenshotPath?[r.screenshotPath]:[])}}import{dirname as Pl,join as Yh}from"path";async function Tl(e,t,n){let o=mn(new ot(e.paths),e.session),s=await Ft(t),r=await Ho(o,n.apiMocks,t),i=`${s?`${s}
302
312
 
303
313
  `:""}${r?`${r}
304
314
 
305
- `:""}`,a=await F(xf(t,"module.json"));if(!a.trim())throw new f(`module.json is empty or missing in ${t}`);let c;try{c=JSON.parse(a)}catch(v){throw new f(`module.json has a JSON syntax error: ${v.message}; fix it, simulate needs valid JSON`)}if(!j(c))throw new f("module.json root is not an object");let l=await Ot(kl(kl(t))),{mocks:u,trace:d}=await gl(o,c,t,n.apiMocks??{}),p=new Is(c,{apiMocks:u,user:n.user,urlParams:n.urlParams,definedFunctionRights:l.functionRights}),{report:m,hasFailures:w}=p.simulate(n.scenario??[]),y=d.length>0?`
315
+ `:""}`,a=await F(Yh(t,"module.json"));if(!a.trim())throw new h(`module.json is empty or missing in ${t}`);let c;try{c=JSON.parse(a)}catch(v){throw new h(`module.json has a JSON syntax error: ${v.message}; fix it, simulate needs valid JSON`)}if(!j(c))throw new h("module.json root is not an object");let l=await Ot(Pl(Pl(t))),{mocks:u,trace:d}=await $l(o,c,t,n.apiMocks??{}),p=new Rs(c,{apiMocks:u,user:n.user,urlParams:n.urlParams,definedFunctionRights:l.functionRights}),{report:m,hasFailures:w}=p.simulate(n.scenario??[]),y=d.length>0?`
306
316
 
307
- \u2500\u2500 Live/Cache data (SP ch\u01B0a c\xF3 mock, \u0111\xE3 t\u1EF1 resolve) \u2500\u2500
317
+ \u2500\u2500 Live/Cache data (SPs without mocks, auto-resolved) \u2500\u2500
308
318
  ${d.join(`
309
- `)}`:"";return{text:`${i}Simulate: ${t}
319
+ `)}`:"";return{text:Be(`${i}Simulate: ${t}
310
320
 
311
321
  ${m}${y}
312
- `,hasFailures:w}}var ct={module:{type:"string",short:"m"}},jl={data:{type:"string"},"data-file":{type:"string"}};function kt(e){if(k(`${e.notes.join(`
322
+ `),hasFailures:w}}var lt={module:{type:"string",short:"m"}},_l={data:{type:"string"},"data-file":{type:"string"}};function kt(e){if(k(`${e.notes.join(`
313
323
  `)}
314
- `),e.data)k(e.data)}async function Il(e){return e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module"))}async function Kn(e){return e.targets.resolveModuleDir(h(e.values,"module-dir"),h(e.values,"project"),h(e.values,"module"))}var Of={name:"module validate",mcpName:"module_validate",usage:"fui module validate [-p P -m M | --module-dir D]",summary:"Static semantic check of module.json (no network); fix every error before publishing",options:{...ct,"module-dir":{type:"string"}},async run(e){let t=await Kn(e),n=await vc(t);for(let o of n.notes)E(`# ${o}`);if(k(n.data??""),n.errorCount>0)throw new lt(`module.json has ${n.errorCount} error(s)`,[...n.notes,`Validate: ${t}`,"",n.data??""].join(`
315
- `))}},Mf={name:"module outline",mcpName:"module_outline",usage:"fui module outline [-p P -m M | --module-dir D]",summary:"Text layout tree of module.json: grid widths, bindings, actions, dialogs, unused state (no network)",options:{...ct,"module-dir":{type:"string"}},async run(e){let t=await Ac(await Kn(e));for(let n of t.notes)E(`# ${n}`);k(t.data??"")}},Cl={device:{type:"string"},viewport:{type:"string"},"settle-ms":{type:"string"},"screenshot-steps":{type:"boolean"}},vl=["desktop","tablet","mobile"];function Rl(e){let n=(typeof e.device==="string"?e.device:void 0)?.split(",").map((r)=>r.trim());for(let r of n??[])if(!vl.includes(r))throw new f(`--device takes ${vl.join(", ")}`);let o;if(typeof e.viewport==="string"){let r=/^(\d+)x(\d+)$/i.exec(e.viewport);if(!r)throw new f("--viewport takes WIDTHxHEIGHT, e.g. 1366x768");o={width:Number(r[1]),height:Number(r[2])}}let s;if(typeof e["settle-ms"]==="string"){if(s=Number(e["settle-ms"]),!Number.isFinite(s)||s<0)throw new f("--settle-ms takes a number of milliseconds")}return{devices:n,viewport:o,settleMs:s,screenshotSteps:e["screenshot-steps"]===!0?!0:void 0}}var Ff={name:"module simulate",mcpName:"module_simulate",usage:`fui module simulate [-p P -m M | --module-dir D] [--scenario '[{"click":"Save"}]'] [--mocks '{"Fn":{"data":[]}}']
324
+ `),e.data)k(e.data)}async function Dl(e){return e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module"))}async function Kn(e){return e.targets.resolveModuleDir(f(e.values,"module-dir"),f(e.values,"project"),f(e.values,"module"))}var Zh={name:"module validate",mcpName:"module_validate",usage:"fui module validate [-p P -m M | --module-dir D]",summary:"Static semantic check of module.json (no network); fix every error before publishing",options:{...lt,"module-dir":{type:"string"}},async run(e){let t=await Kn(e),n=await jc(t);for(let o of n.notes)T(`# ${o}`);if(k(n.data??""),n.errorCount>0)throw new ut(`module.json has ${n.errorCount} error(s)`,[...n.notes,`Validate: ${t}`,"",n.data??""].join(`
325
+ `))}},ef={name:"module outline",mcpName:"module_outline",usage:"fui module outline [-p P -m M | --module-dir D]",summary:"Text layout tree of module.json: grid widths, bindings, actions, dialogs, unused state (no network)",options:{...lt,"module-dir":{type:"string"}},async run(e){let t=await _c(await Kn(e));for(let n of t.notes)T(`# ${n}`);k(t.data??"")}},xl={device:{type:"string"},viewport:{type:"string"},"settle-ms":{type:"string"},"screenshot-steps":{type:"boolean"}},El=["desktop","tablet","mobile"];function Ol(e){let n=(typeof e.device==="string"?e.device:void 0)?.split(",").map((r)=>r.trim());for(let r of n??[])if(!El.includes(r))throw new h(`--device takes ${El.join(", ")}`);let o;if(typeof e.viewport==="string"){let r=/^(\d+)x(\d+)$/i.exec(e.viewport);if(!r)throw new h("--viewport takes WIDTHxHEIGHT, e.g. 1366x768");o={width:Number(r[1]),height:Number(r[2])}}let s;if(typeof e["settle-ms"]==="string"){if(s=Number(e["settle-ms"]),!Number.isFinite(s)||s<0)throw new h("--settle-ms takes a number of milliseconds")}return{devices:n,viewport:o,settleMs:s,screenshotSteps:e["screenshot-steps"]===!0?!0:void 0}}var Ml=[["scenario","array"],["mocks","object"],["user","object"],["url-params","object"],["vue-data","object"]],Fl=[...Ml.map(([e])=>[e,`${e}-file`]),["render"],["device"],["viewport"],["settle-ms"],["screenshot-steps"],["offline"],["brief"]];function tf(e,t){let n={};for(let o of Fl.flat()){let s=e[o];if(typeof s!=="string"&&typeof s!=="boolean")continue;n[o]=s==="-"?JSON.stringify(t[o]):typeof s==="string"&&o.endsWith("-file")?Qh(s):s}return n}function Nl(e,t){if(!t){k(e);return}let n=Dc(e);T(`# brief: ${n.kept} of ${n.total} lines (failures, warnings, asserts, stats, screenshots); full report: rerun without --brief`),k(n.text)}var nf={name:"module simulate",mcpName:"module_simulate",usage:`fui module simulate [-p P -m M | --module-dir D] [--scenario '[{"click":"Save"}]'] [--mocks '{"Fn":{"data":[]}}']
316
326
  [--user '{"SystemRight":3}'] [--url-params '{"id":"1"}'] (each also as --<name>-file path)
317
327
  real browser: --render [--device desktop|tablet|mobile] [--viewport 1366x768] [--vue-data {...}] [--settle-ms N]
318
- [--screenshot-steps] [--refresh-assets | --offline] (needs Playwright)`,summary:"Run module.json in the simulated FUI runtime (or --render: a real browser); fails when an assert or the page fails",options:{...ct,"module-dir":{type:"string"},...ns("scenario","mocks","user","url-params","vue-data"),...Cl,render:{type:"boolean"},"refresh-assets":{type:"boolean"},offline:{type:"boolean"}},async run(e){let{values:t}=e,n=await Kn(e),o={scenario:await yt(t,"scenario","array"),apiMocks:await yt(t,"mocks","object"),user:await yt(t,"user","object"),urlParams:await yt(t,"url-params","object")};if(t.render!==!0){let i=["vue-data","vue-data-file","device","viewport","settle-ms","screenshot-steps"];for(let c of[...i,"refresh-assets","offline"])if(t[c]!==void 0)throw new f(`--${c} needs --render`);let a=await $l(O(e),n,o);if(k(a.text),a.hasFailures)throw new lt("simulate: an assert failed",a.text);return}let s=Rl(t);if((s.devices?.length??0)>1)throw new f("module render takes one --device; component preview takes several");let r=await yl(O(e),n,{...o,scenario:o.scenario??[],apiMocks:o.apiMocks??{},vueData:await yt(t,"vue-data","object"),device:s.devices?.[0],viewport:s.viewport,settleMs:s.settleMs,screenshotSteps:s.screenshotSteps,refreshAssets:t["refresh-assets"]===!0,offline:t.offline===!0});k(r.text);for(let i of r.screenshots)if(!r.text.includes(i))k(`screenshot: ${i}
319
- `);if(r.hasFailures)throw new lt("render: the page has failures",r.text)}},Lf={name:"component preview",mcpName:"component_preview",usage:`fui component preview <uc-name> [-p P] [-m M] [--cases '[{"name":"empty","props":{"items":[]}}]'] [--device desktop,mobile]
320
- [--viewport 1366x420] [--user {...}] [--settle-ms N] [--screenshot-steps] (needs Playwright)`,summary:"Render one local .vue alone in a real browser per case x device: JS errors, Vue warns, DOM audit, screenshots",options:{...ct,...ns("cases","user"),...Cl},async run(e){let{values:t}=e,n=e.positionals[0];if(!n)throw new f("missing <uc-name> (file name without .vue)");let o=Rl(t),s=await bl(O(e),{componentName:n,projectId:h(t,"project"),moduleId:h(t,"module"),cases:await yt(t,"cases","array"),devices:o.devices,viewport:o.viewport,user:await yt(t,"user","object"),settleMs:o.settleMs,screenshotSteps:o.screenshotSteps});if(k(s.text),s.hasFailures)throw new lt("component preview: a run has failures",s.text)}},Uf={name:"module preview",mcpName:"module_preview",usage:"fui module preview [-p P -m M | --module-dir D]",summary:"Diff local vs server, run the validator and issue an approval token for publish html",options:{...ct,"module-dir":{type:"string"}},async run(e){kt(await Sc(O(e),await Kn(e)))}},Bf={name:"module publish html",mcpName:"module_publish_html",usage:"fui module publish html --approval <token> [-p P -m M | --module-dir D]",summary:"Publish the whole staged module (needs the token from fui module preview, after user approval)",options:{...ct,"module-dir":{type:"string"},approval:{type:"string"}},async run(e){kt(await jc(O(e),await Kn(e),h(e.values,"approval")))}},Hf={name:"module publish json",mcpName:"module_publish_json",usage:"fui module publish json [-p P -m M]",summary:"Push local module.json (skips when unchanged since the last publish/sync)",options:ct,async run(e){kt(await To(O(e),await Il(e)))}};function Sl(e){let t=e==="script"?"script.js":"style.css";return{name:`module publish ${e}`,mcpName:`module_publish_${e}`,usage:`fui module publish ${e} [-p P -m M]`,summary:`Push local ${t} (an empty file clears it on the server; a missing file is refused)`,options:ct,async run(n){kt(await Ao(O(n),await Il(n),t))}}}function Bo(e,t,n){return{name:`component ${e}`,mcpName:`component_${e}`,usage:`fui component ${e}${e==="list"?"":" <name>"} [-p P [-m M]]`,summary:n,options:ct,async run(o){let s=o.positionals[0];if(e!=="list"&&!s)throw new f("missing <name>");kt(await t(o,s))}}}async function Ho(e){return an(O(e),h(e.values,"project"),h(e.values,"module"))}var Wf=[Bo("list",async(e)=>Cc(O(e),await Ho(e)),"Refresh _components.json from the server (archives .vue files the server dropped)"),Bo("new",async(e,t)=>No(O(e),await Ho(e),t),"Create a component on the server from local components/<name>.vue"),Bo("update",async(e,t)=>_o(O(e),await Ho(e),t),"Overwrite an existing component from local components/<name>.vue (skips when unchanged)"),Bo("delete",async(e,t)=>Rc(O(e),await Ho(e),t),"Delete a component on the server (ask the user first); archives the local .vue")],qf={name:"project new",mcpName:"project_new",usage:"fui project new (--data '<json>' | --data - | --data-file f)",summary:"Create a project (needs SystemRight>=3 on fp; server reads ProjectID, ProjectName, GroupName, Framework, pDomain, Router)",options:jl,async run(e){kt(await Pc(O(e),await ts(e.values)))}},Jf={name:"project update",mcpName:"project_update",usage:"fui project update (--data '<json with id>' | --data - | --data-file f)",summary:"Edit project metadata; projectData.id is required",options:jl,async run(e){kt(await Ec(O(e),await ts(e.values)))}},Vf={name:"project publish-config",mcpName:"project_publish_config",usage:"fui project publish-config [-p project]",summary:"Push local <project>/project.json (menu, routes, settings); skips when unchanged",async run(e){let{projectId:t}=await e.targets.resolveProject(h(e.values,"project"));kt(await Do(O(e),t))}},Pl=[Of,Mf,Ff,Uf,Bf,Hf,Sl("script"),Sl("css"),...Wf,Lf,qf,Jf,Vf];import{rename as Kf}from"fs/promises";import{join as El}from"path";async function Al(e,t,n){let o=await ce(e);if(!t)return{rows:await o.api.listImportFiles(void 0,n),notes:["no project given: read-only query, local workspace not synced"]};let s=await o.syncImportsLocal(t,n);return{rows:s.list,notes:[`_imports.json synced for ${n?`module ${n}`:`project ${t}`} (${s.count} file(s), metadata only; content: fui import get <fileId>)`,...s.removed.length>0?[`removed orphaned local files: ${s.removed.join(", ")}`]:[]]}}async function Nl(e,t){return(await e.api()).listFrameworkImportFiles(t)}async function _l(e,t){let o=(await(await e.api()).getFileContent(t))?.data?.FileContent??"",s=await Gr(e.paths,t,o);return{notes:s.length>0?s.map((r)=>`written: ${r}`):["no local _imports.json declares this fileID yet; content printed only. materialize it: fui import list -p <project> [-m <module>]"],data:o}}async function Dl(e,t,n){let o=await(await e.api()).uploadFileContent(t,n),s=await Gr(e.paths,t,n);return{notes:[`ok uploaded content of import ${t}`,...s.map((r)=>`local copy synced: ${r}`)],data:`${JSON.stringify(o)}
321
- `}}async function xl(e,t){let n=await ce(e),{effectiveSort:o,note:s}=zr(t.fileSort,t.moduleId?"module":"project"),r=await n.api.insertImportFile({...t.extra,ProjectID:t.projectId,ModuleID:t.moduleId,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:o}),i=["ok import added",...s?[s]:[]];if(t.projectId)try{let{count:a}=await n.syncImportsLocal(t.projectId,t.moduleId);i.push(`_imports.json synced for ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`} (${a} file(s))`)}catch{i.push("warning: import added but the _imports.json sync failed")}return{notes:i,data:`${JSON.stringify(r)}
322
- `}}async function Ol(e,t){let n=await ce(e),o=await Kr(e.paths,t.fileId),s=t.fileSort,r="";if(t.fileSort!==void 0&&o.length>0){let u=zr(t.fileSort,o.some((d)=>d.moduleId)?"module":"project");s=u.effectiveSort,r=u.note}let i=await n.api.updateImportFile(t.fileId,{...t.extra,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:s}),a=en(t.contentType)?`imports/${Mn(t.fileName,t.fileId,t.fileType)}`:void 0,c=[],l=new Set;for(let u of o){let d=u.entry.filePath;if(d&&a&&a!==d)try{await Kf(El(u.baseDir,d),El(u.baseDir,a)),c.push(`local file moved: ${d} -> ${a}`)}catch{}let p=`${u.projectId}::${u.moduleId??""}`;if(l.has(p))continue;l.add(p);let m=u.moduleId?`module ${u.moduleId}`:`project ${u.projectId}`;try{let{removed:w}=await n.syncImportsLocal(u.projectId,u.moduleId);c.push(`${m}: _imports.json synced${w.length>0?`, removed orphaned ${w.join(", ")}`:""}`)}catch{c.push(`${m}: sync failed`)}}return{notes:["ok import updated",...r?[r]:[],...c],data:`${JSON.stringify(i)}
323
- `}}async function Ml(e,t){let n=await ce(e),o=await Kr(e.paths,t),s=await n.api.deleteImportFile(t),r=[],i=new Set;for(let a of o){let c=`${a.projectId}::${a.moduleId??""}`;if(i.has(c))continue;i.add(c);let l=a.moduleId?`module ${a.moduleId}`:`project ${a.projectId}`;try{let{removed:u}=await n.syncImportsLocal(a.projectId,a.moduleId);r.push(`${l}: _imports.json synced${u.length>0?`, removed ${u.join(", ")}`:""}`)}catch{r.push(`${l}: sync failed`)}}return{notes:[`ok import ${t} deleted`,...r],data:`${JSON.stringify(s)}
324
- `}}var Gf={1:"View only",2:"Develop",3:"Admin"};function Gn(e){let t=typeof e==="number"?e:Number(e);if(!Number.isFinite(t))return String(e??"");let n=Gf[t];return n?`${t} (${n})`:String(t)}function Tl(e,t){let n=t.trim().toLowerCase();return e.find((o)=>String(o.UserID??"").trim().toLowerCase()===n)}function Wo(e,t){return e.map((n)=>({userId:String(n.UserID??"?"),username:n.UserName||"\u2014",fullName:n.FullName||"\u2014",...t?{right:Gn(n.UserRight)}:{}}))}async function Fl(e,t,n){return(await e.api()).searchUsers(t,n??"%")}async function Ll(e,t){return(await e.api()).listProjectUserRights(t)}async function Ul(e,t,n){let o=await e.api(),s=`project ${t.projectId}`,r=t.userId.trim();if(!r)throw new f("empty userId; find the real UserID: fui user search <text>");let i=Tl(await o.listProjectUserRights(t.projectId),r);if(n==="new"&&i)throw new f(`user "${r}" already has right ${Gn(i.UserRight)} on ${s}; change it: fui user set ${r} --right N`);if(n!=="new"&&!i)throw new f(`user "${r}" has no right on ${s}; ${n==="update"?`grant one: fui user add ${r} --right N`:"nothing to revoke; see: fui user list"}`);let a=n==="delete"?0:t.userRight;await o.updateProjectUserRight(t.projectId,a,JSON.stringify([r]));let c=await o.listProjectUserRights(t.projectId),l=Tl(c,r),u=n==="delete"?`ok revoked the right of "${r}" on ${s}`:`ok set right ${Gn(a)} for "${r}" on ${s}`,d=n==="delete"?l?`warning: post-check: the user is still listed with right ${Gn(l.UserRight)}; the server did not remove it`:"post-check: user no longer listed":l?`post-check: ${r} has ${Gn(l.UserRight)}`:"warning: post-check: user not found in the list after the write; check: fui user list";return{notes:[u,d,`current rights on ${s} (${c.length}):`],rows:c}}var Hl={module:{type:"string",short:"m"}},Wl={type:{type:"string"},"content-type":{type:"string"},name:{type:"string"},sort:{type:"string"},set:{type:"string",multiple:!0}};function qo(e){if(k(`${e.notes.join(`
328
+ [--screenshot-steps] [--refresh-assets | --offline] (needs Playwright)
329
+ --last: reuse this module's last input (files are re-read); flags given now replace their saved value
330
+ --brief: print only failures, warnings, asserts, stats and screenshot paths`,summary:"Run module.json in the simulated FUI runtime (or --render: a real browser); fails when an assert or the page fails",options:{...lt,"module-dir":{type:"string"},...rs("scenario","mocks","user","url-params","vue-data"),...xl,render:{type:"boolean"},"refresh-assets":{type:"boolean"},offline:{type:"boolean"},last:{type:"boolean"},brief:{type:"boolean"}},async run(e){let t=await Kn(e),n=e.values;if(n.last===!0){let a=await Oc(t,"module simulate");if(!a)throw new h("no saved input for this module yet; run fui module simulate with flags first");let c=Fc(n,a,Fl);n=c.merged,T(`# reused last input: ${c.reused.join(", ")||"none (every input given now)"}`)}let o={};for(let[a,c]of Ml)o[a]=await Co(n,a,c);await Mc(t,"module simulate",tf(n,o));let s={scenario:o.scenario,apiMocks:o.mocks,user:o.user,urlParams:o["url-params"]};if(n.render!==!0){let a=["vue-data","vue-data-file","device","viewport","settle-ms","screenshot-steps"];for(let l of[...a,"refresh-assets","offline"])if(n[l]!==void 0)throw new h(`--${l} needs --render`);let c=await Tl(O(e),t,s);if(Nl(c.text,n.brief===!0),c.hasFailures)throw new ut("simulate: an assert failed",c.text);return}let r=Ol(n);if((r.devices?.length??0)>1)throw new h("module render takes one --device; component preview takes several");let i=await Cl(O(e),t,{...s,scenario:s.scenario??[],apiMocks:s.apiMocks??{},vueData:o["vue-data"],device:r.devices?.[0],viewport:r.viewport,settleMs:r.settleMs,screenshotSteps:r.screenshotSteps,refreshAssets:n["refresh-assets"]===!0,offline:n.offline===!0});Nl(i.text,n.brief===!0);for(let a of i.screenshots)if(!i.text.includes(a))k(`screenshot: ${a}
331
+ `);if(i.hasFailures)throw new ut("render: the page has failures",i.text)}},of={name:"component preview",mcpName:"component_preview",usage:`fui component preview <uc-name> [-p P] [-m M] [--cases '[{"name":"empty","props":{"items":[]}}]'] [--device desktop,mobile]
332
+ [--viewport 1366x420] [--user {...}] [--settle-ms N] [--screenshot-steps] (needs Playwright)`,summary:"Render one local .vue alone in a real browser per case x device: JS errors, Vue warns, DOM audit, screenshots",options:{...lt,...rs("cases","user"),...xl},async run(e){let{values:t}=e,n=e.positionals[0];if(!n)throw new h("missing <uc-name> (file name without .vue)");let o=Ol(t),s=await Rl(O(e),{componentName:n,projectId:f(t,"project"),moduleId:f(t,"module"),cases:await Co(t,"cases","array"),devices:o.devices,viewport:o.viewport,user:await Co(t,"user","object"),settleMs:o.settleMs,screenshotSteps:o.screenshotSteps});if(k(s.text),s.hasFailures)throw new ut("component preview: a run has failures",s.text)}},rf={name:"module preview",mcpName:"module_preview",usage:"fui module preview [-p P -m M | --module-dir D]",summary:"Diff local vs server, run the validator and issue an approval token for publish html",options:{...lt,"module-dir":{type:"string"}},async run(e){kt(await Ic(O(e),await Kn(e)))}},sf={name:"module publish html",mcpName:"module_publish_html",usage:"fui module publish html --approval <token> [-p P -m M | --module-dir D]",summary:"Publish the whole staged module (needs the token from fui module preview, after user approval)",options:{...lt,"module-dir":{type:"string"},approval:{type:"string"}},async run(e){kt(await Cc(O(e),await Kn(e),f(e.values,"approval")))}},af={name:"module publish json",mcpName:"module_publish_json",usage:"fui module publish json [-p P -m M]",summary:"Push local module.json (skips when unchanged since the last publish/sync)",options:lt,async run(e){kt(await No(O(e),await Dl(e)))}};function Al(e){let t=e==="script"?"script.js":"style.css";return{name:`module publish ${e}`,mcpName:`module_publish_${e}`,usage:`fui module publish ${e} [-p P -m M]`,summary:`Push local ${t} (an empty file clears it on the server; a missing file is refused)`,options:lt,async run(n){kt(await Ao(O(n),await Dl(n),t))}}}function Bo(e,t,n){return{name:`component ${e}`,mcpName:`component_${e}`,usage:`fui component ${e}${e==="list"?"":" <name>"} [-p P [-m M]]`,summary:n,options:lt,async run(o){let s=o.positionals[0];if(e!=="list"&&!s)throw new h("missing <name>");kt(await t(o,s))}}}async function qo(e){return an(O(e),f(e.values,"project"),f(e.values,"module"))}var cf=[Bo("list",async(e)=>Pc(O(e),await qo(e)),"Refresh _components.json from the server (archives .vue files the server dropped)"),Bo("new",async(e,t)=>_o(O(e),await qo(e),t),"Create a component on the server from local components/<name>.vue"),Bo("update",async(e,t)=>Do(O(e),await qo(e),t),"Overwrite an existing component from local components/<name>.vue (skips when unchanged)"),Bo("delete",async(e,t)=>Tc(O(e),await qo(e),t),"Delete a component on the server (ask the user first); archives the local .vue")],lf={name:"project new",mcpName:"project_new",usage:"fui project new (--data '<json>' | --data - | --data-file f)",summary:"Create a project (needs SystemRight>=3 on fp; server reads ProjectID, ProjectName, GroupName, Framework, pDomain, Router)",options:_l,async run(e){kt(await Ec(O(e),await os(e.values)))}},uf={name:"project update",mcpName:"project_update",usage:"fui project update (--data '<json with id>' | --data - | --data-file f)",summary:"Edit project metadata; projectData.id is required",options:_l,async run(e){kt(await Nc(O(e),await os(e.values)))}},df={name:"project publish-config",mcpName:"project_publish_config",usage:"fui project publish-config [-p project]",summary:"Push local <project>/project.json (menu, routes, settings); skips when unchanged",async run(e){let{projectId:t}=await e.targets.resolveProject(f(e.values,"project"));kt(await xo(O(e),t))}},Ll=[Zh,ef,nf,rf,sf,af,Al("script"),Al("css"),...cf,of,lf,uf,df];import{rename as pf}from"fs/promises";import{join as Ul}from"path";async function Bl(e,t,n){let o=await ce(e);if(!t)return{rows:await o.api.listImportFiles(void 0,n),notes:["no project given: read-only query, local workspace not synced"]};let s=await o.syncImportsLocal(t,n);return{rows:s.list,notes:[`_imports.json synced for ${n?`module ${n}`:`project ${t}`} (${s.count} file(s), metadata only; content: fui import get <fileId>)`,...s.removed.length>0?[`removed orphaned local files: ${s.removed.join(", ")}`]:[]]}}async function ql(e,t){return(await e.api()).listFrameworkImportFiles(t)}async function Wl(e,t){let o=(await(await e.api()).getFileContent(t))?.data?.FileContent??"",s=await Xr(e.paths,t,o);return{notes:s.length>0?s.map((r)=>`written: ${r}`):["no local _imports.json declares this fileID yet; content printed only. materialize it: fui import list -p <project> [-m <module>]"],data:o}}async function Vl(e,t,n){let o=await(await e.api()).uploadFileContent(t,n),s=await Xr(e.paths,t,n);return{notes:[`ok uploaded content of import ${t}`,...s.map((r)=>`local copy synced: ${r}`)],data:`${JSON.stringify(o)}
333
+ `}}async function Jl(e,t){let n=await ce(e),{effectiveSort:o,note:s}=Yr(t.fileSort,t.moduleId?"module":"project"),r=await n.api.insertImportFile({...t.extra,ProjectID:t.projectId,ModuleID:t.moduleId,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:o}),i=["ok import added",...s?[s]:[]];if(t.projectId)try{let{count:a}=await n.syncImportsLocal(t.projectId,t.moduleId);i.push(`_imports.json synced for ${t.moduleId?`module ${t.moduleId}`:`project ${t.projectId}`} (${a} file(s))`)}catch{i.push("warning: import added but the _imports.json sync failed")}return{notes:i,data:`${JSON.stringify(r)}
334
+ `}}async function Kl(e,t){let n=await ce(e),o=await zr(e.paths,t.fileId),s=t.fileSort,r="";if(t.fileSort!==void 0&&o.length>0){let u=Yr(t.fileSort,o.some((d)=>d.moduleId)?"module":"project");s=u.effectiveSort,r=u.note}let i=await n.api.updateImportFile(t.fileId,{...t.extra,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:s}),a=en(t.contentType)?`imports/${Mn(t.fileName,t.fileId,t.fileType)}`:void 0,c=[],l=new Set;for(let u of o){let d=u.entry.filePath;if(d&&a&&a!==d)try{await pf(Ul(u.baseDir,d),Ul(u.baseDir,a)),c.push(`local file moved: ${d} -> ${a}`)}catch{}let p=`${u.projectId}::${u.moduleId??""}`;if(l.has(p))continue;l.add(p);let m=u.moduleId?`module ${u.moduleId}`:`project ${u.projectId}`;try{let{removed:w}=await n.syncImportsLocal(u.projectId,u.moduleId);c.push(`${m}: _imports.json synced${w.length>0?`, removed orphaned ${w.join(", ")}`:""}`)}catch{c.push(`${m}: sync failed`)}}return{notes:["ok import updated",...r?[r]:[],...c],data:`${JSON.stringify(i)}
335
+ `}}async function Gl(e,t){let n=await ce(e),o=await zr(e.paths,t),s=await n.api.deleteImportFile(t),r=[],i=new Set;for(let a of o){let c=`${a.projectId}::${a.moduleId??""}`;if(i.has(c))continue;i.add(c);let l=a.moduleId?`module ${a.moduleId}`:`project ${a.projectId}`;try{let{removed:u}=await n.syncImportsLocal(a.projectId,a.moduleId);r.push(`${l}: _imports.json synced${u.length>0?`, removed ${u.join(", ")}`:""}`)}catch{r.push(`${l}: sync failed`)}}return{notes:[`ok import ${t} deleted`,...r],data:`${JSON.stringify(s)}
336
+ `}}var mf={1:"View only",2:"Develop",3:"Admin"};function Gn(e){let t=typeof e==="number"?e:Number(e);if(!Number.isFinite(t))return String(e??"");let n=mf[t];return n?`${t} (${n})`:String(t)}function Hl(e,t){let n=t.trim().toLowerCase();return e.find((o)=>String(o.UserID??"").trim().toLowerCase()===n)}function Wo(e,t){return e.map((n)=>({userId:String(n.UserID??"?"),username:n.UserName||"\u2014",fullName:n.FullName||"\u2014",...t?{right:Gn(n.UserRight)}:{}}))}async function zl(e,t,n){return(await e.api()).searchUsers(t,n??"%")}async function Xl(e,t){return(await e.api()).listProjectUserRights(t)}async function Yl(e,t,n){let o=await e.api(),s=`project ${t.projectId}`,r=t.userId.trim();if(!r)throw new h("empty userId; find the real UserID: fui user search <text>");let i=Hl(await o.listProjectUserRights(t.projectId),r);if(n==="new"&&i)throw new h(`user "${r}" already has right ${Gn(i.UserRight)} on ${s}; change it: fui user set ${r} --right N`);if(n!=="new"&&!i)throw new h(`user "${r}" has no right on ${s}; ${n==="update"?`grant one: fui user add ${r} --right N`:"nothing to revoke; see: fui user list"}`);let a=n==="delete"?0:t.userRight;await o.updateProjectUserRight(t.projectId,a,JSON.stringify([r]));let c=await o.listProjectUserRights(t.projectId),l=Hl(c,r),u=n==="delete"?`ok revoked the right of "${r}" on ${s}`:`ok set right ${Gn(a)} for "${r}" on ${s}`,d=n==="delete"?l?`warning: post-check: the user is still listed with right ${Gn(l.UserRight)}; the server did not remove it`:"post-check: user no longer listed":l?`post-check: ${r} has ${Gn(l.UserRight)}`:"warning: post-check: user not found in the list after the write; check: fui user list";return{notes:[u,d,`current rights on ${s} (${c.length}):`],rows:c}}var Zl={module:{type:"string",short:"m"}},eu={type:{type:"string"},"content-type":{type:"string"},name:{type:"string"},sort:{type:"string"},set:{type:"string",multiple:!0}};function Vo(e){if(k(`${e.notes.join(`
325
337
  `)}
326
- `),e.data)k(e.data)}function ql(e){if(e===void 0)return;let t=Number(e);if(!Number.isFinite(t))throw new f("--sort must be a number");return t}function Jo(e){let t=e.positionals[0];if(!t)throw new f("missing <fileId>; list them: fui import list -p <project> [-m <module>]");return t}function Bl(e){return e.map((t)=>({fileId:String(t.fileID??""),name:String(t.FileName??""),type:String(t.FileType??""),contentType:String(t.ContentType??""),scope:String(t.Scope??""),sort:String(t.FileSort??"")}))}var zf={name:"import list",mcpName:"file_import_list",usage:"fui import list [-p P [-m M]] | fui import list --framework V2|V3",summary:"List import files (with -p it also syncs local _imports.json); --framework lists framework imports",options:{...Hl,framework:{type:"string"}},async run(e){let t=O(e),n=h(e.values,"framework");if(n){k(X(Bl(await Nl(t,n.toUpperCase())),e.render));return}let{rows:o,notes:s}=await Al(t,h(e.values,"project"),h(e.values,"module"));for(let r of s)E(`# ${r}`);k(X(Bl(o),e.render))}},Xf={name:"import get",mcpName:"file_import_get",usage:"fui import get <fileId> [-o file]",summary:"Print an inline import's source and write it to every local imports/ location declaring it",options:{out:{type:"string",short:"o"}},async run(e){let t=await _l(O(e),Jo(e));for(let o of t.notes)E(`# ${o}`);let n=h(e.values,"out");if(n)await Bun.write(n,t.data??""),k(`${n}
327
- `);else k(t.data??"")}},Yf={name:"import upload",mcpName:"file_import_upload_content",usage:"fui import upload <fileId> (-f file | -)",summary:"Overwrite an existing inline import's source (text/javascript or text/css) and sync local copies",options:{file:{type:"string",short:"f"}},async run(e){let t=Jo(e),n=h(e.values,"file"),o=e.positionals[1];if(n===void 0===(o!=="-"))throw new f("pass the content with -f <file> or - (stdin)");let s=n?await Bun.file(n).text():await ro();qo(await Dl(O(e),t,s))}},Qf={name:"import new",mcpName:"file_import_new",usage:"fui import new -p P [-m M] --type js|css|... --content-type link|comp-link|text/javascript|text/css --name <name|url> [--sort N]",summary:"Add an import (CDN link or inline code); sort is raised to 20 (project) / 50 (module) when lower",options:{...Hl,...Wl},async run(e){let{values:t}=e,n=h(t,"type"),o=h(t,"content-type"),s=h(t,"name");if(!n||!o||!s)throw new f("need --type, --content-type and --name");qo(await xl(O(e),{projectId:h(t,"project"),moduleId:h(t,"module"),fileType:n,contentType:o,fileName:s,fileSort:ql(h(t,"sort")),extra:cn(t.set)}))}},Zf={name:"import update",mcpName:"file_import_update",usage:"fui import update <fileId> --type T --content-type C --name N [--sort N]",summary:"Edit import metadata (not its source); renames the local content file when the name changes",options:Wl,async run(e){let{values:t}=e,n=h(t,"type"),o=h(t,"content-type"),s=h(t,"name");if(!n||!o||!s)throw new f("need --type, --content-type and --name (the server replaces all three)");qo(await Ol(O(e),{fileId:Jo(e),fileType:n,contentType:o,fileName:s,fileSort:ql(h(t,"sort")),extra:cn(t.set)}))}},eh={name:"import delete",mcpName:"file_import_delete",usage:"fui import delete <fileId>",summary:"Delete an import on the server (ask the user first) and clean local _imports.json copies",async run(e){qo(await Ml(O(e),Jo(e)))}};async function Jl(e){return(await e.targets.resolveProject(h(e.values,"project"))).projectId}function th(e){if(e!=="1"&&e!=="2"&&e!=="3")throw new f("--right must be 1 (View only), 2 (Develop) or 3 (Admin)");return Number(e)}var nh={name:"user search",mcpName:"project_user_search",usage:"fui user search <text> [--id-like pattern]",summary:"Find a developer account; the only way to get the exact UserID for user add/set/remove",options:{"id-like":{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <text>");let n=await Fl(O(e),t,h(e.values,"id-like"));if(n.length===0){E(`# no user matches "${t}"; the user may not exist, or --id-like filtered it out (default % = no filter)`);return}E("# grant: fui user add <UserID> --right 3 (1=View only, 2=Develop, 3=Admin)"),k(X(Wo(n,!1),e.render))}},oh={name:"user list",mcpName:"project_user_list",usage:"fui user list [-p project]",summary:"Who holds an IDE right on the project (project-wide; not the acc SystemRight)",async run(e){let t=await Jl(e),n=await Ll(O(e),t);if(n.length===0){E(`# nobody has a right on project ${t}; find a user: fui user search <text>`);return}k(X(Wo(n,!0),e.render))}};function Cs(e,t,n){return{name:`user ${e}`,mcpName:`project_user_${t}`,usage:`fui user ${e} <UserID>${t==="delete"?"":" --right 1|2|3"} [-p project]`,summary:n,options:t==="delete"?{}:{right:{type:"string"}},async run(o){let s=o.positionals[0];if(s===void 0)throw new f("missing <UserID>; find it: fui user search <text>");let r=await Ul(O(o),{projectId:await Jl(o),userId:s,userRight:t==="delete"?void 0:th(h(o.values,"right"))},t);k(`${r.notes.join(`
338
+ `),e.data)k(e.data)}function tu(e){if(e===void 0)return;let t=Number(e);if(!Number.isFinite(t))throw new h("--sort must be a number");return t}function Jo(e){let t=e.positionals[0];if(!t)throw new h("missing <fileId>; list them: fui import list -p <project> [-m <module>]");return t}function Ql(e){return e.map((t)=>({fileId:String(t.fileID??""),name:String(t.FileName??""),type:String(t.FileType??""),contentType:String(t.ContentType??""),scope:String(t.Scope??""),sort:String(t.FileSort??"")}))}var hf={name:"import list",mcpName:"file_import_list",usage:"fui import list [-p P [-m M]] | fui import list --framework V2|V3",summary:"List import files (with -p it also syncs local _imports.json); --framework lists framework imports",options:{...Zl,framework:{type:"string"}},async run(e){let t=O(e),n=f(e.values,"framework");if(n){k(X(Ql(await ql(t,n.toUpperCase())),e.render));return}let{rows:o,notes:s}=await Bl(t,f(e.values,"project"),f(e.values,"module"));for(let r of s)T(`# ${r}`);k(X(Ql(o),e.render))}},ff={name:"import get",mcpName:"file_import_get",usage:"fui import get <fileId> [-o file]",summary:"Print an inline import's source and write it to every local imports/ location declaring it",options:{out:{type:"string",short:"o"}},async run(e){let t=await Wl(O(e),Jo(e));for(let o of t.notes)T(`# ${o}`);let n=f(e.values,"out");if(n)await Bun.write(n,t.data??""),k(`${n}
339
+ `);else k(t.data??"")}},gf={name:"import upload",mcpName:"file_import_upload_content",usage:"fui import upload <fileId> (-f file | -)",summary:"Overwrite an existing inline import's source (text/javascript or text/css) and sync local copies",options:{file:{type:"string",short:"f"}},async run(e){let t=Jo(e),n=f(e.values,"file"),o=e.positionals[1];if(n===void 0===(o!=="-"))throw new h("pass the content with -f <file> or - (stdin)");let s=n?await Bun.file(n).text():await ro();Vo(await Vl(O(e),t,s))}},wf={name:"import new",mcpName:"file_import_new",usage:"fui import new -p P [-m M] --type js|css|... --content-type link|comp-link|text/javascript|text/css --name <name|url> [--sort N]",summary:"Add an import (CDN link or inline code); sort is raised to 20 (project) / 50 (module) when lower",options:{...Zl,...eu},async run(e){let{values:t}=e,n=f(t,"type"),o=f(t,"content-type"),s=f(t,"name");if(!n||!o||!s)throw new h("need --type, --content-type and --name");Vo(await Jl(O(e),{projectId:f(t,"project"),moduleId:f(t,"module"),fileType:n,contentType:o,fileName:s,fileSort:tu(f(t,"sort")),extra:cn(t.set)}))}},yf={name:"import update",mcpName:"file_import_update",usage:"fui import update <fileId> --type T --content-type C --name N [--sort N]",summary:"Edit import metadata (not its source); renames the local content file when the name changes",options:eu,async run(e){let{values:t}=e,n=f(t,"type"),o=f(t,"content-type"),s=f(t,"name");if(!n||!o||!s)throw new h("need --type, --content-type and --name (the server replaces all three)");Vo(await Kl(O(e),{fileId:Jo(e),fileType:n,contentType:o,fileName:s,fileSort:tu(f(t,"sort")),extra:cn(t.set)}))}},bf={name:"import delete",mcpName:"file_import_delete",usage:"fui import delete <fileId>",summary:"Delete an import on the server (ask the user first) and clean local _imports.json copies",async run(e){Vo(await Gl(O(e),Jo(e)))}};async function nu(e){return(await e.targets.resolveProject(f(e.values,"project"))).projectId}function kf(e){if(e!=="1"&&e!=="2"&&e!=="3")throw new h("--right must be 1 (View only), 2 (Develop) or 3 (Admin)");return Number(e)}var vf={name:"user search",mcpName:"project_user_search",usage:"fui user search <text> [--id-like pattern]",summary:"Find a developer account; the only way to get the exact UserID for user add/set/remove",options:{"id-like":{type:"string"}},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <text>");let n=await zl(O(e),t,f(e.values,"id-like"));if(n.length===0){T(`# no user matches "${t}"; the user may not exist, or --id-like filtered it out (default % = no filter)`);return}T("# grant: fui user add <UserID> --right 3 (1=View only, 2=Develop, 3=Admin)"),k(X(Wo(n,!1),e.render))}},Sf={name:"user list",mcpName:"project_user_list",usage:"fui user list [-p project]",summary:"Who holds an IDE right on the project (project-wide; not the acc SystemRight)",async run(e){let t=await nu(e),n=await Xl(O(e),t);if(n.length===0){T(`# nobody has a right on project ${t}; find a user: fui user search <text>`);return}k(X(Wo(n,!0),e.render))}};function Ps(e,t,n){return{name:`user ${e}`,mcpName:`project_user_${t}`,usage:`fui user ${e} <UserID>${t==="delete"?"":" --right 1|2|3"} [-p project]`,summary:n,options:t==="delete"?{}:{right:{type:"string"}},async run(o){let s=o.positionals[0];if(s===void 0)throw new h("missing <UserID>; find it: fui user search <text>");let r=await Yl(O(o),{projectId:await nu(o),userId:s,userRight:t==="delete"?void 0:kf(f(o.values,"right"))},t);k(`${r.notes.join(`
328
340
  `)}
329
- `),k(X(Wo(r.rows,!0),o.render))}}}var Vl=[zf,Xf,Yf,Qf,Zf,eh,nh,oh,Cs("add","new","Grant a right to a user who has none (exact UserID from fui user search)"),Cs("set","update","Change the right level of a user who already has one"),Cs("remove","delete","Revoke a user's right (ask the user first)")];import{rm as Yl}from"fs/promises";import{appendFile as rh,mkdir as sh,rename as ih,stat as ah}from"fs/promises";import{dirname as ch,join as lh}from"path";var uh=1048576,Kl=["\u274C","\u26D4","\u2718","\u26A0\uFE0F"];function zn(e){return lh(e,"_logs","tool-errors.jsonl")}function dh(e,t){let n=e.split(`
330
- `).map((i)=>i.trim()).filter(Boolean),o=[],s=new Set,r=0;for(let i of Kl)for(let a of n){if(!a.startsWith(i)||s.has(a))continue;let c=a.length>200?`${a.slice(0,200)}\u2026`:a;if(r+c.length+1>t)return o;s.add(a),o.push(c),r+=c.length+1}return o}function Rs(e,t,n){if(e.length<=t+n)return e;let o=e.slice(t,e.length-n),s=dh(o,Math.round((t+n)/2)),r=s.length>0?[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1 \u2014 gi\u1EEF l\u1EA1i ${s.length} d\xF2ng l\xFD do h\u1ECFng]\u2026`,...s]:[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1]\u2026`];return`${e.slice(0,t)}
341
+ `),k(X(Wo(r.rows,!0),o.render))}}}var ou=[hf,ff,gf,wf,yf,bf,vf,Sf,Ps("add","new","Grant a right to a user who has none (exact UserID from fui user search)"),Ps("set","update","Change the right level of a user who already has one"),Ps("remove","delete","Revoke a user's right (ask the user first)")];import{rm as cu}from"fs/promises";import{appendFile as $f,mkdir as jf,rename as If,stat as Cf}from"fs/promises";import{dirname as Rf,join as Pf}from"path";var Tf=1048576,ru=["\u274C","\u26D4","\u2718","\u26A0\uFE0F"];function zn(e){return Pf(e,"_logs","tool-errors.jsonl")}function Ef(e,t){let n=e.split(`
342
+ `).map((i)=>i.trim()).filter(Boolean),o=[],s=new Set,r=0;for(let i of ru)for(let a of n){if(!a.startsWith(i)||s.has(a))continue;let c=a.length>200?`${a.slice(0,200)}\u2026`:a;if(r+c.length+1>t)return o;s.add(a),o.push(c),r+=c.length+1}return o}function Ts(e,t,n){if(e.length<=t+n)return e;let o=e.slice(t,e.length-n),s=Ef(o,Math.round((t+n)/2)),r=s.length>0?[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1 \u2014 gi\u1EEF l\u1EA1i ${s.length} d\xF2ng l\xFD do h\u1ECFng]\u2026`,...s]:[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1]\u2026`];return`${e.slice(0,t)}
331
343
  ${r.join(`
332
344
  `)}
333
- ${e.slice(-n)}`}async function Gl(e,t,n,o){try{let s=zn(e);await sh(ch(s),{recursive:!0});try{if((await ah(s)).size>uh)await ih(s,s.replace(/\.jsonl$/,".1.jsonl")).catch(()=>{return})}catch{}let r={ts:new Date().toISOString(),pid:process.pid,tool:t,message:Rs(n,400,600),...o&&o!=="error"?{kind:o}:{}};await rh(s,`${JSON.stringify(r)}
334
- `,"utf-8")}catch{}}async function zl(e){let t="";try{t=await H(zn(e))}catch{}let n=[];for(let o of t.split(`
335
- `)){if(!o.trim())continue;try{n.push(JSON.parse(o))}catch{}}return n}function ph(e){let t=e.split(`
336
- `).map((o)=>o.trim()).filter(Boolean),n=Kl.map((o)=>t.find((s)=>s.startsWith(o))).find(Boolean);if(!n)return"(b\u1EA3n ghi kh\xF4ng ch\u1EE9a d\xF2ng l\xFD do h\u1ECFng \u2014 xem v\xED d\u1EE5 b\xEAn d\u01B0\u1EDBi)";return n.replace(/^[\u274C\u26D4\u2718\u26A0\uFE0F]+\uFE0F?\s*/,"").replace(/'[^'\s]*'|"[^"\s]*"|`[^`\s]*`/g,"'\u2026'").replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f-]{12,}\b/gi,"\u2026").replace(/[A-Za-z]:[\\/][^\s,)]+|\/[^\s,)]*\/[^\s,)]+/g,"\u2026").replace(/\b\d+\b/g,"N").replace(/\s+/g," ").trim().slice(0,140)}function Xl(e){return{errors:e.filter((t)=>t.kind!=="guard"),guards:e.filter((t)=>t.kind==="guard")}}function Ps(e){let t=new Map;for(let n of e){let o=ph(n.message),s=`${n.tool} ${o}`,r=t.get(s);if(r)r.count+=1,r.lastTs=n.ts,r.sample=n.message;else t.set(s,{tool:n.tool,signature:o,count:1,firstTs:n.ts,lastTs:n.ts,sample:n.message})}return[...t.values()].sort((n,o)=>o.count-n.count||o.lastTs.localeCompare(n.lastTs))}function Ql(e,t,n,o,s){if(e===void 0)return s;let r=Number(e);if(!Number.isInteger(r)||r<n||r>o)throw new f(`${t} must be an integer ${n}-${o}`);return r}var mh={name:"log read",mcpName:"error_log_read",usage:"fui log read [--tool mcp_tool_name] [--limit N] [--summary] [--sample-chars N]",summary:"Shared tool-error log (Fui MCP + fui) grouped by tool and error signature, most frequent first",options:{tool:{type:"string"},limit:{type:"string"},summary:{type:"boolean"},"sample-chars":{type:"string"}},async run({paths:e,values:t}){let n=h(t,"tool"),o=Ql(h(t,"limit"),"--limit",0,Number.MAX_SAFE_INTEGER,15),s=Ql(h(t,"sample-chars"),"--sample-chars",200,1e5,900),r=zn(e.root),i=await zl(e.root);if(n)i=i.filter((m)=>m.tool===n);let{errors:a,guards:c}=Xl(i);if(a.length===0&&c.length===0){k(`no errors logged${n?` for ${n}`:""}
345
+ ${e.slice(-n)}`}async function su(e,t,n,o){try{let s=zn(e);await jf(Rf(s),{recursive:!0});try{if((await Cf(s)).size>Tf)await If(s,s.replace(/\.jsonl$/,".1.jsonl")).catch(()=>{return})}catch{}let r={ts:new Date().toISOString(),pid:process.pid,tool:t,message:Ts(n,400,600),...o&&o!=="error"?{kind:o}:{}};await $f(s,`${JSON.stringify(r)}
346
+ `,"utf-8")}catch{}}async function iu(e){let t="";try{t=await B(zn(e))}catch{}let n=[];for(let o of t.split(`
347
+ `)){if(!o.trim())continue;try{n.push(JSON.parse(o))}catch{}}return n}function Nf(e){let t=e.split(`
348
+ `).map((o)=>o.trim()).filter(Boolean),n=ru.map((o)=>t.find((s)=>s.startsWith(o))).find(Boolean);if(!n)return"(b\u1EA3n ghi kh\xF4ng ch\u1EE9a d\xF2ng l\xFD do h\u1ECFng \u2014 xem v\xED d\u1EE5 b\xEAn d\u01B0\u1EDBi)";return n.replace(/^[\u274C\u26D4\u2718\u26A0\uFE0F]+\uFE0F?\s*/,"").replace(/'[^'\s]*'|"[^"\s]*"|`[^`\s]*`/g,"'\u2026'").replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f-]{12,}\b/gi,"\u2026").replace(/[A-Za-z]:[\\/][^\s,)]+|\/[^\s,)]*\/[^\s,)]+/g,"\u2026").replace(/\b\d+\b/g,"N").replace(/\s+/g," ").trim().slice(0,140)}function au(e){return{errors:e.filter((t)=>t.kind!=="guard"),guards:e.filter((t)=>t.kind==="guard")}}function Es(e){let t=new Map;for(let n of e){let o=Nf(n.message),s=`${n.tool} ${o}`,r=t.get(s);if(r)r.count+=1,r.lastTs=n.ts,r.sample=n.message;else t.set(s,{tool:n.tool,signature:o,count:1,firstTs:n.ts,lastTs:n.ts,sample:n.message})}return[...t.values()].sort((n,o)=>o.count-n.count||o.lastTs.localeCompare(n.lastTs))}function lu(e,t,n,o,s){if(e===void 0)return s;let r=Number(e);if(!Number.isInteger(r)||r<n||r>o)throw new h(`${t} must be an integer ${n}-${o}`);return r}var Af={name:"log read",mcpName:"error_log_read",usage:"fui log read [--tool mcp_tool_name] [--limit N] [--summary] [--sample-chars N]",summary:"Shared tool-error log (Fui MCP + fui) grouped by tool and error signature, most frequent first",options:{tool:{type:"string"},limit:{type:"string"},summary:{type:"boolean"},"sample-chars":{type:"string"}},async run({paths:e,values:t}){let n=f(t,"tool"),o=lu(f(t,"limit"),"--limit",0,Number.MAX_SAFE_INTEGER,15),s=lu(f(t,"sample-chars"),"--sample-chars",200,1e5,900),r=zn(e.root),i=await iu(e.root);if(n)i=i.filter((m)=>m.tool===n);let{errors:a,guards:c}=au(i);if(a.length===0&&c.length===0){k(`no errors logged${n?` for ${n}`:""}
337
349
  log file: ${r}
338
- `);return}let l=new Map;for(let m of a)l.set(m.tool,(l.get(m.tool)??0)+1);let u=Ps(a),d=o===0?u:u.slice(0,o),p=[`tool error log: ${a.length} failures, ${u.length} groups${n?` (tool ${n})`:""}`,`file: ${r}`,"","failures per tool (most first: where the skill is weakest):",...[...l.entries()].sort((m,w)=>w[1]-m[1]).map(([m,w])=>` ${String(w).padStart(4)}x ${m}`),"",`recurring groups (${d.length}/${u.length}, most first):`];if(d.forEach((m,w)=>{if(p.push("",`[${w+1}] ${m.count}x ${m.tool} first ${m.firstTs} last ${m.lastTs}`,` ${m.signature}`),t.summary!==!0){p.push(" -- latest occurrence --");for(let y of Rs(m.sample,Math.round(s*0.4),Math.round(s*0.6)).split(`
339
- `))p.push(` ${y}`)}}),u.length>d.length)p.push("",`${u.length-d.length} more groups; raise --limit (0 = all)`);if(c.length>0){p.push("",`guards that blocked by design: ${c.length} (not skill gaps; not counted above):`);for(let m of Ps(c).slice(0,5))p.push(` ${String(m.count).padStart(4)}x ${m.tool} ${m.signature}`)}k(`${p.join(`
350
+ `);return}let l=new Map;for(let m of a)l.set(m.tool,(l.get(m.tool)??0)+1);let u=Es(a),d=o===0?u:u.slice(0,o),p=[`tool error log: ${a.length} failures, ${u.length} groups${n?` (tool ${n})`:""}`,`file: ${r}`,"","failures per tool (most first: where the skill is weakest):",...[...l.entries()].sort((m,w)=>w[1]-m[1]).map(([m,w])=>` ${String(w).padStart(4)}x ${m}`),"",`recurring groups (${d.length}/${u.length}, most first):`];if(d.forEach((m,w)=>{if(p.push("",`[${w+1}] ${m.count}x ${m.tool} first ${m.firstTs} last ${m.lastTs}`,` ${m.signature}`),t.summary!==!0){p.push(" -- latest occurrence --");for(let y of Ts(m.sample,Math.round(s*0.4),Math.round(s*0.6)).split(`
351
+ `))p.push(` ${y}`)}}),u.length>d.length)p.push("",`${u.length-d.length} more groups; raise --limit (0 = all)`);if(c.length>0){p.push("",`guards that blocked by design: ${c.length} (not skill gaps; not counted above):`);for(let m of Es(c).slice(0,5))p.push(` ${String(m.count).padStart(4)}x ${m.tool} ${m.signature}`)}k(`${p.join(`
340
352
  `)}
341
- `)}},fh={name:"log clear",mcpName:"error_log_clear",usage:"fui log clear",summary:"Delete the shared tool-error log (after its patterns were folded back into the skill)",async run({paths:e}){let t=zn(e.root);await Yl(t,{force:!0}),await Yl(t.replace(/\.jsonl$/,".1.jsonl"),{force:!0}),k(`cleared ${t}
342
- `)}},Zl=[mh,fh];import{join as hh}from"path";var gh={module:{type:"string",short:"m"}},Es=["load","ui","html","script","css","components","imports"],Ts=["info","config","modules","components","imports"];async function eu(e,t){let n=typeof t==="string"?t:`${JSON.stringify(t,null,e.json?0:2)}
343
- `,o=h(e.values,"out");if(o)await Bun.write(o,n),k(`${o}
344
- `);else k(n)}var wh={name:"remote module",usage:`fui remote module [-p P -m M] --part ${Es.join("|")} [-o file]`,summary:"Print one part of a module as the server has it (script/css/html raw; others JSON); no local writes",options:{...gh,part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=h(e.values,"part");if(!t||!Es.includes(t))throw new f(`--part must be one of: ${Es.join(", ")}`);let{projectId:n,moduleId:o}=await e.targets.resolveProjectModule(h(e.values,"project"),h(e.values,"module")),s=await O(e).api(),r=await{load:()=>s.getModuleContent(n,o),ui:async()=>(await s.getModuleUI(n,o)).data,html:async()=>String((await s.getModuleHtml(n,o)).data?.mBody??""),script:()=>s.getModuleScript(n,o),css:()=>s.getModuleCSS(n,o),components:()=>s.getModuleComponents(n,o),imports:()=>s.getModuleDependencies(n,o)}[t]();await eu(e,r)}},yh={name:"remote project",usage:`fui remote project [-p P] --part ${Ts.join("|")} [-o file]`,summary:"Print one part of a project as the server has it; no local writes",options:{part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=h(e.values,"part");if(!t||!Ts.includes(t))throw new f(`--part must be one of: ${Ts.join(", ")}`);let{projectId:n}=await e.targets.resolveProject(h(e.values,"project")),o=await O(e).api(),s=await{info:async()=>(await o.listProjects()).find((r)=>r.ProjectID===n)??null,config:()=>o.getProjectJson(n),modules:()=>o.listModules(n),components:()=>o.getModuleComponents(n),imports:()=>o.listImportFiles(n)}[t]();await eu(e,s)}};function tu(e,t){let n=ko(e.paths,t);if(!n)throw new f(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var bh={name:"remote diff",usage:"fui remote diff <path...>",summary:"Compare local module files with the server copy (module preview logic, per file); no writes",async run(e){if(e.positionals.length===0)throw new f("missing <path>");let t=e.positionals.map((r)=>tu(e,r)),n=new Bn(e.paths,await O(e).api()),o=new Map,s=[];for(let r of t){if(!r.moduleId){s.push(`${r.rel} unsupported only module files can be diffed (use fui remote project --part config)`);continue}let i=`${r.projectId}/${r.moduleId}`,a=o.get(i);if(!a){let d=(await n.resolveModuleInfoFromCache(r.projectId,r.moduleId)).found;if(!d)throw new f(`module ${i} unknown; run fui module list -p ${r.projectId}`);let p=await n.fetchModuleBundle(d);a=tn(d,p.content,p.components,p.script,p.css,p.dependencies),o.set(i,a)}let c=await F(r.abs,"\x00missing"),l=a[r.rel],u;if(c==="\x00missing")u=l===void 0?"absent":"only-server";else if(l===void 0)u="only-local";else if(r.rel.endsWith(".json")){let d=!1;try{d=gt(JSON.parse(c))===gt(JSON.parse(l))}catch{}u=d?"same":"differs"}else u=ft(c)===ft(l)?"same":"differs";s.push(`${r.rel} ${u} ${hh(r.projectId,"modules",r.moduleId)}`)}k(`${s.join(`
353
+ `)}},_f={name:"log clear",mcpName:"error_log_clear",usage:"fui log clear",summary:"Delete the shared tool-error log (after its patterns were folded back into the skill)",async run({paths:e}){let t=zn(e.root);await cu(t,{force:!0}),await cu(t.replace(/\.jsonl$/,".1.jsonl"),{force:!0}),k(`cleared ${t}
354
+ `)}},uu=[Af,_f];import{join as Df}from"path";var xf={module:{type:"string",short:"m"}},Ns=["load","ui","html","script","css","components","imports"],As=["info","config","modules","components","imports"];async function du(e,t){let n=typeof t==="string"?t:`${JSON.stringify(t,null,e.json?0:2)}
355
+ `,o=f(e.values,"out");if(o)await Bun.write(o,n),k(`${o}
356
+ `);else k(n)}var Of={name:"remote module",usage:`fui remote module [-p P -m M] --part ${Ns.join("|")} [-o file]`,summary:"Print one part of a module as the server has it (script/css/html raw; others JSON); no local writes",options:{...xf,part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=f(e.values,"part");if(!t||!Ns.includes(t))throw new h(`--part must be one of: ${Ns.join(", ")}`);let{projectId:n,moduleId:o}=await e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module")),s=await O(e).api(),r=await{load:()=>s.getModuleContent(n,o),ui:async()=>(await s.getModuleUI(n,o)).data,html:async()=>String((await s.getModuleHtml(n,o)).data?.mBody??""),script:()=>s.getModuleScript(n,o),css:()=>s.getModuleCSS(n,o),components:()=>s.getModuleComponents(n,o),imports:()=>s.getModuleDependencies(n,o)}[t]();await du(e,r)}},Mf={name:"remote project",usage:`fui remote project [-p P] --part ${As.join("|")} [-o file]`,summary:"Print one part of a project as the server has it; no local writes",options:{part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=f(e.values,"part");if(!t||!As.includes(t))throw new h(`--part must be one of: ${As.join(", ")}`);let{projectId:n}=await e.targets.resolveProject(f(e.values,"project")),o=await O(e).api(),s=await{info:async()=>(await o.listProjects()).find((r)=>r.ProjectID===n)??null,config:()=>o.getProjectJson(n),modules:()=>o.listModules(n),components:()=>o.getModuleComponents(n),imports:()=>o.listImportFiles(n)}[t]();await du(e,s)}};function pu(e,t){let n=ko(e.paths,t);if(!n)throw new h(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Ff={name:"remote diff",usage:"fui remote diff <path...>",summary:"Compare local module files with the server copy (module preview logic, per file); no writes",async run(e){if(e.positionals.length===0)throw new h("missing <path>");let t=e.positionals.map((r)=>pu(e,r)),n=new Hn(e.paths,await O(e).api()),o=new Map,s=[];for(let r of t){if(!r.moduleId){s.push(`${r.rel} unsupported only module files can be diffed (use fui remote project --part config)`);continue}let i=`${r.projectId}/${r.moduleId}`,a=o.get(i);if(!a){let d=(await n.resolveModuleInfoFromCache(r.projectId,r.moduleId)).found;if(!d)throw new h(`module ${i} unknown; run fui module list -p ${r.projectId}`);let p=await n.fetchModuleBundle(d);a=tn(d,p.content,p.components,p.script,p.css,p.dependencies),o.set(i,a)}let c=await F(r.abs,"\x00missing"),l=a[r.rel],u;if(c==="\x00missing")u=l===void 0?"absent":"only-server";else if(l===void 0)u="only-local";else if(r.rel.endsWith(".json")){let d=!1;try{d=wt(JSON.parse(c))===wt(JSON.parse(l))}catch{}u=d?"same":"differs"}else u=ft(c)===ft(l)?"same":"differs";s.push(`${r.rel} ${u} ${Df(r.projectId,"modules",r.moduleId)}`)}k(`${s.join(`
345
357
  `)}
346
- `)}},kh={name:"file push",usage:"fui file push <path...> [--new]",summary:"Push local files through their guarded publisher (module.json, script.js, style.css, components/*.vue, project.json)",options:{new:{type:"boolean"}},async run(e){if(e.positionals.length===0)throw new f("missing <path>");let t=O(e),n=0;for(let o of e.positionals){let s=tu(e,o),r;if(!s.moduleId&&s.rel==="project.json")r=await Do(t,s.projectId);else if(s.rel.startsWith("components/")&&s.rel.endsWith(".vue")&&!s.rel.slice(11).includes("/")){let i=await an(t,s.projectId,s.moduleId),a=s.rel.slice(11,-4);r=e.values.new===!0?await No(t,i,a):await _o(t,i,a)}else if(s.moduleId&&(s.rel==="module.json"||s.rel==="script.js"||s.rel==="style.css")){let i={projectId:s.projectId,moduleId:s.moduleId,moduleDir:s.baseDir};r=s.rel==="module.json"?await To(t,i):await Ao(t,i,s.rel)}else if(s.rel==="header.html"||s.rel==="body.html"){E(`# ${o}: header/body publish only as a whole module: fui module preview, then fui module publish html --approval <token>`),n=Math.max(n,2);continue}else{E(`# ${o}: no publisher for this file`),n=Math.max(n,2);continue}k(`${r.notes.join(`
358
+ `)}},Lf={name:"file push",usage:"fui file push <path...> [--new]",summary:"Push local files through their guarded publisher (module.json, script.js, style.css, components/*.vue, project.json)",options:{new:{type:"boolean"}},async run(e){if(e.positionals.length===0)throw new h("missing <path>");let t=O(e),n=0;for(let o of e.positionals){let s=pu(e,o),r;if(!s.moduleId&&s.rel==="project.json")r=await xo(t,s.projectId);else if(s.rel.startsWith("components/")&&s.rel.endsWith(".vue")&&!s.rel.slice(11).includes("/")){let i=await an(t,s.projectId,s.moduleId),a=s.rel.slice(11,-4);r=e.values.new===!0?await _o(t,i,a):await Do(t,i,a)}else if(s.moduleId&&(s.rel==="module.json"||s.rel==="script.js"||s.rel==="style.css")){let i={projectId:s.projectId,moduleId:s.moduleId,moduleDir:s.baseDir};r=s.rel==="module.json"?await No(t,i):await Ao(t,i,s.rel)}else if(s.rel==="header.html"||s.rel==="body.html"){T(`# ${o}: header/body publish only as a whole module: fui module preview, then fui module publish html --approval <token>`),n=Math.max(n,2);continue}else{T(`# ${o}: no publisher for this file`),n=Math.max(n,2);continue}k(`${r.notes.join(`
347
359
  `)}
348
- `)}return n}},nu=[wh,yh,bh,kh];function $h(e){if(e===void 0)return qi;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new f("--limit must be an integer >= 0 (0 = no limit)");return t}var vh={name:"query",mcpName:"db_sql_execute",usage:'fui query ("<sql>" | -f file | -) [--db ref] [--limit 200] [--confirm-write] [--json]',summary:"Run SQL that returns rows (TSV); writes need --confirm-write",options:{file:{type:"string",short:"f"},limit:{type:"string"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,render:o}=e,s=await vn(n[0],h(t,"file")),r=$h(h(t,"limit")),{rows:i,connection:a}=await ta(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)E(`# ${a.note}`);let c=ii(i,r);k(X(c.rows,o)),E(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},Sh={name:"exec",mcpName:"db_sql_execute_nonquery",usage:'fui exec ("<sql>" | -f file | -) [--db ref] [--confirm-write]',summary:"Run DDL/DML that returns no rows; several connections need --db",options:{file:{type:"string",short:"f"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,json:o}=e,s=await vn(n[0],h(t,"file")),{result:r,connection:i,notes:a}=await na(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)E(`# ${i.note}`);let l=Xt(r);k(o?nr({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
349
- `);for(let u of a)E(`# ${u}`)}},ou=[vh,Sh];function As(e){if(e===void 0)return;let t=pr.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new f(`--type must be one of: ${pr.join(", ")}`);return t}var jh={name:"schema",mcpName:"db_schema_read",usage:'fui schema [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search text]',summary:"Print compact DDL (one line per object) from the local cache; --search matches the whole line",options:{type:{type:"string"},search:{type:"string"}},async run(e){let t=await zi(await z(e),{db:h(e.values,"db"),objectType:As(h(e.values,"type")),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);k(t.data??"")}},Ih={name:"schema pull",mcpName:"db_schema_get",usage:"fui schema pull [--db ref] [--prune-local-only]",summary:"Refresh schema.json and local .sql bodies; archive files of objects dropped on the server",options:{"prune-local-only":{type:"boolean"}},async run(e){let t=await Gi(await z(e),h(e.values,"db"),e.values["prune-local-only"]===!0);k(`${t.notes.join(`
360
+ `)}return n}},mu=[Of,Mf,Ff,Lf];function Uf(e){if(e===void 0)return Ji;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new h("--limit must be an integer >= 0 (0 = no limit)");return t}var Hf={name:"query",mcpName:"db_sql_execute",usage:'fui query ("<sql>" | -f file | -) [--db ref] [--limit 200] [--confirm-write] [--json]',summary:"Run SQL that returns rows (TSV); writes need --confirm-write",options:{file:{type:"string",short:"f"},limit:{type:"string"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,render:o}=e,s=await Sn(n[0],f(t,"file")),r=Uf(f(t,"limit")),{rows:i,connection:a}=await oa(await z(e),s,{db:f(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)T(`# ${a.note}`);let c=ci(i,r);k(X(c.rows,o)),T(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},Bf={name:"exec",mcpName:"db_sql_execute_nonquery",usage:'fui exec ("<sql>" | -f file | -) [--db ref] [--confirm-write]',summary:"Run DDL/DML that returns no rows; several connections need --db",options:{file:{type:"string",short:"f"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,json:o}=e,s=await Sn(n[0],f(t,"file")),{result:r,connection:i,notes:a}=await ra(await z(e),s,{db:f(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)T(`# ${i.note}`);let l=Xt(r);k(o?rr({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
361
+ `);for(let u of a)T(`# ${u}`)}},hu=[Hf,Bf];function _s(e){if(e===void 0)return;let t=hr.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new h(`--type must be one of: ${hr.join(", ")}`);return t}var qf={name:"schema",mcpName:"db_schema_read",usage:'fui schema [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search text]',summary:"Print compact DDL (one line per object) from the local cache; --search matches the whole line",options:{type:{type:"string"},search:{type:"string"}},async run(e){let t=await Yi(await z(e),{db:f(e.values,"db"),objectType:_s(f(e.values,"type")),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);k(t.data??"")}},Wf={name:"schema pull",mcpName:"db_schema_get",usage:"fui schema pull [--db ref] [--prune-local-only]",summary:"Refresh schema.json and local .sql bodies; archive files of objects dropped on the server",options:{"prune-local-only":{type:"boolean"}},async run(e){let t=await Xi(await z(e),f(e.values,"db"),e.values["prune-local-only"]===!0);k(`${t.notes.join(`
350
362
  `)}
351
- `)}},ru=[jh,Ih];import{cp as Rh,mkdir as Ph,rm as Eh}from"fs/promises";import{homedir as Th}from"os";import{join as Xn}from"path";var Ns="---\nname: fui\ndescription: Use the `fui` CLI for FUI (low-code platform) work \u2014 projects, modules (get, validate, preview, publish), components, imports, project users, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), workspace files, publish ledger and history. Prefer it over Fui MCP tools whenever `fui` covers the tool (see the map below); same guards, a fraction of the tokens.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for Fui MCP tools. Run it with your shell tool. It uses the same workspace, connection files, publish ledger, `_history` and error log as Fui MCP, so you can mix the two during the transition.\n\nDomain rules (module.json, controls, tAPI conventions) live in the `fui-skill` skill. Read its `references/` as needed, then run `fui skill loaded`; strict mode (`FUI_MCP_STRICT=1`) blocks deploys until you do.\n\n## Output contract\n\n- stdout is data only: TSV with a header when piped, an aligned table in a terminal, `--json` for one-line JSON, `--no-header` to drop the header.\n- stderr carries `# ...` notes and `error:` / `guard:` lines.\n- Exit codes:\n - `0`: ok, including \"skipped: unchanged\".\n - `1`: runtime error (network or SQL).\n - `2`: usage error. Read the message; it lists the valid choices.\n - `3`: blocked by a guard, or waiting for `--confirm-write`.\n\n## Targets\n\n- **Project and module**, first match wins:\n 1. `-p <project> -m <module>`. Passing only one of them is an error.\n 2. The current directory, if it is inside `<workspace>/<project>/modules/<module>/`.\n 3. The session target.\n- **Session:** one per `FUI_SESSION` (default `default`). Parallel agents should each set their own `FUI_SESSION`.\n- **Connection:**\n - `--db <alias>` or `--db <project>/<alias>`.\n - With no `--db`: the only connection, else one inferred from the object name (`sp` commands), else the default.\n - Writes never use the default when a project has several connections.\n- `fui file where` prints what resolved and why.\n- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.\n- A global install updates itself in the background, at most once an hour. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.\n\n## Fui MCP tool \u2192 fui command\n\nImplemented (same guards, same files, same requests as Fui MCP 1.4.0):\n\n| MCP tool | fui |\n| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| session_set / session_read / session_clear / session_resolve | `use <target>` / `use` / `use --clear` / `resolve <target>` |\n| project_list / project_sync | `project list` / `project sync` |\n| project_new / project_update / project_publish_config | `project new --data {\u2026}` / `project update --data {id:\u2026}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set \u2026` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] \u2026` / `module delete -p P -m M` |\n| module_get / module_get_ui / module_get_html | `module get [<target>]` / `module get-ui` / `module get-html` |\n| module_validate / module_preview | `module validate` / `module preview` |\n| module_outline / module_simulate | `module outline` / `module simulate [--scenario [...]] [--mocks {...}] [--user {...}] [--url-params {...}]` |\n| module_simulate renderUI / component_preview | `module simulate --render [--device mobile] [--vue-data {...}]` / `component preview <uc-name> [--cases [...]] [--device desktop,mobile]` |\n| error_log_read / error_log_clear | `log read [--tool T] [--limit N\\|0] [--summary]` / `log clear` |\n| module_publish_html / json / script / css | `module publish html --approval <token>` / `module publish json` / `\u2026 script` / `\u2026 css` |\n| component_list / get / new / update / delete | `component list` / `component get <name>` / `component new <name>` / `component update <name>` / `component delete <name>` |\n| file_import_list / get / new / update / upload_content / delete | `import list` / `import get <id>` / `import new \u2026` / `import update <id> \u2026` / `import upload <id> -f file` / `import delete <id>` |\n| db_config_read / db_connect | `db list` / `db add <alias> --token - --api-domain host` |\n| db_connect_by_name / db_alias_list / db_alias_new | `db add-by-name <alias>` / `db alias list [--search s]` / `db alias new <alias> \u2026` |\n| db_user_token_set / db_token_rebuild | `db user-token [--from p/alias]` / `db token-rebuild --uid U --pwd-env VAR` |\n| right_system_* / right_function_* | `right system list\\|new <0-9>\\|update <0-9>\\|delete <0-9>` / `right function list\\|new --code N\\|update <id>\\|delete <id>` |\n| db_schema_get / db_schema_read | `schema pull [--prune-local-only]` / `schema [--type T] [--search text]` |\n| db_sp_list / db_sp_get / db_sp_save | `sp list` / `sp get <name> [--print]` / `sp save <name> -f file` |\n| db_sp_deploy / db_sp_help | `sp deploy <name>` / `sp help <name\\|Fn> [--full]` |\n| db_sp_verify / db_sp_delete / db_sp_rename | `sp verify <name\\|Fn> [--params {\u2026}] [--check-grant]` / `sp delete <name> --type Procedure\\|Function` / `sp rename <old> <new>` |\n| db_sql_execute / db_sql_execute_nonquery | `query \"<sql>\"` / `exec \"<sql>\"` (+ `--confirm-write`) |\n| skill_get / skill_reference_get / skill_sync | `skill get` / `skill ref <path...>` / `skill sync` |\n| design_list / design_read | `design list` / `design read <brand>` |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n\n## Modules\n\n1. `fui module get /project/module` (also a live URL or display name).\n - It archives local files that differ, deletes `.vue` files the server no longer has, and makes the module the session target.\n2. Edit the files in `<workspace>/<project>/modules/<module>/`. Running `fui` from inside that folder targets it.\n3. Check, cheapest first:\n - `fui module validate`: fix every error (exit 1 while any remain); it only reads local files.\n - `fui module outline`: the layout tree (grid widths, bindings, actions, dialogs) to confirm the structure is what you meant.\n - `fui module simulate --scenario '[{\"click\":\"Save\"},{\"assert\":\"vueData.list.length > 0\"}]'`: runs the module in a simulated runtime; exit 1 when an assert fails. `--mocks` wins over live data. An unmocked static API may call the real tAPI when its local `.sql` body reads as read-only (max 8 calls, cached in `_testdata/`). For an existing SP take mocks from `fui sp verify`, never invent them.\n - `fui component preview <uc-name> --cases '[{\"name\":\"empty\",\"props\":{\"items\":[]}}]'`: one local `.vue` alone in a real browser; do it before wiring a component in and before publishing a project-scope one.\n - `fui module simulate --render [--device mobile]`: the real page in headless Chromium, last and only for problems that need pixels. Project-level assets come from the server, so publish project files first. It prints screenshot paths: open and read them, then fix what you see. Needs Playwright once: `bun add -g playwright && bunx playwright install chromium`.\n4. Publish:\n - **Whole module, including header/body:** `fui module preview` \u2192 show the changes to the user \u2192 after approval `fui module publish html --approval <token>`.\n - The token breaks if any payload file changes after the preview.\n - **One file:** `fui module publish json|script|css`, `fui component update <name>`, or `fui file push <path...>`, which routes each path to the right publisher.\n - Unchanged files are skipped via the publish ledger.\n5. `fui file status` lists what is still unpublished.\n\n`fui remote module --part ui|html|script|css|load|components|imports` and `fui remote project --part config|modules|\u2026` show the server copy without touching the workspace. `fui remote diff <path...>` compares local files with the server.\n\nAsk the user before `module delete`, `component delete`, `import delete` and `user remove`.\n\n## Data\n\n```bash\nfui query \"SELECT TOP 20 * FROM tblStudent WHERE ClassID = 5\" --db lhu-docs/docs\nfui query -f report.sql --limit 0 # default cap 200 rows; 0 = all\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` first. Every row you read costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --search studentid # compact DDL lines; --search matches the whole line (columns, params)\nfui schema --type API --search Student # Table View API \"API File\" Procedure Function\nfui schema pull # refresh schema.json + every local .sql body\nfui sp list --search Student # columns: db name kind count local\n```\n\n- With no cache, `schema` fails and tells you to run `fui schema pull`. It never pulls on its own.\n- A `# warning: ... last synced N days ago` note means pull before trusting the names.\n- `schema pull` archives and deletes local `.sql` files whose objects were dropped on the server.\n- It only lists drafts that exist in neither schema. `--prune-local-only` archives those too, but only once the user confirms they are stale.\n\n## Stored procedures (MSSQL)\n\n1. `fui sp get <name>` saves the live body to `_db/<alias>/<name>.sql`. A differing local copy is archived first.\n - For a new SP: `fui sp path <name> --db <ref>`, or `fui sp save <name> -f file.sql --db <ref>`.\n2. Edit the file. `CREATE` versus `ALTER` does not matter.\n3. `fui sp deploy <name>`:\n - saves the live body as a rollback copy\n - fixes the verb\n - grants `EXECUTE` to `[public]` on new procedures and checks it\n - clears the tAPI cache\n4. If the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"`.\n\nBefore writing `apiMocks` for an SP that exists, run `fui sp verify <name>`. It reads the real body (never calls the endpoint) and prints the params, the response shape, the error contract, read/write and a mock snippet. `sp delete` and `sp rename` change the server: ask the user first.\n\n## Writes need the user\n\n- Writes to real data exit `3` until you add `--confirm-write`. That covers `INSERT`, `UPDATE`, `DELETE`, `MERGE` and `TRUNCATE` in `query` or `exec`, and `ALTER TABLE ... ALTER COLUMN`.\n- Add `--confirm-write` **only after the user approved that exact write in chat.**\n- Always blocked, with no flag to unlock them. Show the SQL to the user to run by hand:\n - `DROP TABLE`\n - `ALTER TABLE ... DROP COLUMN` or `SWITCH`\n - `DELETE`/`UPDATE` without `WHERE` in `exec`\n- Never write or call a live API SP \"to test\" it. Read its body.\n\n## Workspace files (local, no network)\n\n```bash\nfui file where # workspace, project, module, dirs, and why\nfui file status [--all] # files changed since the last publish/sync, with the command to push each\nfui file history <path> # archived versions in _history, newest first\nfui file restore <path> [--at YYYYMMDD_HHMMSS] # the current file is archived first\nfui file ledger <path...> [--mark|--forget]\n```\n\n## Compose with shell\n\nFilter and loop in the shell. Piped output costs no tokens. Every table also takes `--filter text`, `--where col=value` and `--fields a,b` (applied after the request, so nothing else changes).\n\n```bash\nfui sp list --no-header --type API | cut -f2 # API SP names\nfui sp list --no-header | awk -F'\\t' '$5==\"yes\"{print $2}' # SPs with a local copy\nfui schema --type Procedure | grep -i tblStudent | cut -d' ' -f3 # procedures mentioning a word\nfui sp list --no-header --search Student | cut -f2 | xargs -n1 fui sp get\nfui query \"SELECT TOP 5 ID, Name FROM tblX\" --json | jq -r '.[].Name'\n```\n\n- Check exit codes. An exit `3` inside a loop means ask the user; do not retry.\n- Never put `--confirm-write` in a loop or `xargs`.\n\n## Connections and secrets\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn --user-token-env UT\nfui db add local -p demo --conn-env PG_URL # postgres:// mysql:// sqlite:// connect directly\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # never shows the password\n```\n\nSecrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo or paste token values.\n\n- Never `cat`, `jq` or otherwise print `_db/_connections.json`, `_db/config.json` or MCP config files. Use `fui db list`, `fui token decode` or `fui config`; they never print secrets.\n- No token at hand? `fui db user-token` lists fingerprints of the tokens already in the workspace; copy one with `--from <project/alias>`. The value never appears.\n- Know the alias but not the connection string? `fui db alias list`, then `fui db add-by-name <alias>`: it borrows the SQL login of a connection on the same apiDomain.\n- `right *` and `db alias new` write to the shared acc system. Ask the user first, especially for `delete`.\n- `fui db list` shows a pre-1.4 `_db/config.json` as `engine=legacy`. Migrate it with `fui db import-legacy -p <project>`.\n\n## FUI conventions\n\n- tAPI routes:\n - `spAPI_<Fn>` \u2192 `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` \u2192 `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` \u2192 file endpoints\n- tAPI parameters must not have default values (`@x int = 0`). Deploy blocks them.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. `sp deploy` does it; raw `exec` only reports it.\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";async function su(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Ah={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Ns)}},Nh={name:"skill get",mcpName:"skill_get",usage:"fui skill get",summary:"Print the FUI domain skill (SKILL.md + Tier-A refs with an active target, else the summary); marks it loaded",async run(e){let t=await Ja(await su(e));await e.session.markSkillLoaded(),E(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${t.text}
352
- `)}},_h={name:"skill ref",mcpName:"skill_reference_get",usage:"fui skill ref <path> [path...] e.g. references/db-workflow.md",summary:"Print skill reference files; marks the skill loaded",async run(e){if(e.positionals.length===0)throw new f("missing <path>; see references/INDEX.md");await e.session.markSkillLoaded();let t=await su(e),n=t?Xn(t,"_skills",Dt):await sn();E(`# skill dir: ${n}`),k(`${(await Va(n,e.positionals)).trimStart()}
353
- `)}},Dh={name:"skill loaded",usage:"fui skill loaded",summary:"Record that the skill was read (after reading installed skill files yourself); satisfies strict-mode gates",async run({session:e}){await e.markSkillLoaded(),k(`skill marked loaded for this session
354
- `)}},xh={name:"skill sync",mcpName:"skill_sync",usage:"fui skill sync",summary:"Re-copy the bundled fui-skill into <workspace>/_skills/",async run({paths:e}){let t=await Io(e.root);k(`${t.skillDir} (${t.source})
355
- `)}},Oh={name:"skill install",usage:"fui skill install [--dir <skills-root>]",summary:"Install the fui and fui-skill Claude skills into ~/.claude/skills (or --dir)",options:{dir:{type:"string"}},async run({values:e}){let t=h(e,"dir")??Xn(Th(),".claude","skills"),n=Xn(t,"fui","SKILL.md");await Bun.write(n,Ns);let o=Xn(t,Dt);await Eh(o,{recursive:!0,force:!0}),await Ph(t,{recursive:!0}),await Rh(await sn(),o,{recursive:!0}),k(`${n}
363
+ `)}},fu=[qf,Wf];import{cp as Jf,mkdir as Kf,rm as Gf}from"fs/promises";import{homedir as zf}from"os";import{join as Xn}from"path";var Ds="---\nname: fui\ndescription: Use the `fui` CLI for FUI (low-code platform) work \u2014 projects, modules (get, validate, preview, publish), components, imports, project users, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), workspace files, publish ledger and history. Prefer it over Fui MCP tools whenever `fui` covers the tool (see the map below); same guards, a fraction of the tokens.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for Fui MCP tools. Run it with your shell tool. It uses the same workspace, connection files, publish ledger, `_history` and error log as Fui MCP, so you can mix the two during the transition.\n\nDomain rules (module.json, controls, tAPI conventions) live in the `fui-skill` skill. Read its `references/` as needed, then run `fui skill loaded`; strict mode (`FUI_MCP_STRICT=1`) blocks deploys until you do.\n\n## Output contract\n\n- stdout is data only: TSV with a header when piped, an aligned table in a terminal, `--json` for one-line JSON, `--no-header` to drop the header.\n- stderr carries `# ...` notes and `error:` / `guard:` lines.\n- Exit codes:\n - `0`: ok, including \"skipped: unchanged\".\n - `1`: runtime error (network or SQL).\n - `2`: usage error. Read the message; it lists the valid choices.\n - `3`: blocked by a guard, or waiting for `--confirm-write`.\n\n## Targets\n\n- **Project and module**, first match wins:\n 1. `-p <project> -m <module>`. Passing only one of them is an error.\n 2. The current directory, if it is inside `<workspace>/<project>/modules/<module>/`.\n 3. The session target.\n- **Session:** one per `FUI_SESSION` (default `default`). Parallel agents should each set their own `FUI_SESSION`.\n- **Connection:**\n - `--db <alias>` or `--db <project>/<alias>`.\n - With no `--db`: the only connection, else one inferred from the object name (`sp` commands), else the default.\n - Writes never use the default when a project has several connections.\n- `fui file where` prints what resolved and why.\n- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.\n- A global install updates itself in the background, at most once an hour. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.\n\n## Fui MCP tool \u2192 fui command\n\nImplemented (same guards, same files, same requests as Fui MCP 1.4.0):\n\n| MCP tool | fui |\n| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| session_set / session_read / session_clear / session_resolve | `use <target>` / `use` / `use --clear` / `resolve <target>` |\n| project_list / project_sync | `project list` / `project sync` |\n| project_new / project_update / project_publish_config | `project new --data {\u2026}` / `project update --data {id:\u2026}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set \u2026` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] \u2026` / `module delete -p P -m M` |\n| module_get / module_get_ui / module_get_html | `module get [<target>]` / `module get-ui` / `module get-html` |\n| module_validate / module_preview | `module validate` / `module preview` |\n| module_outline / module_simulate | `module outline` / `module simulate [--scenario [...]] [--mocks {...}] [--user {...}] [--url-params {...}]` |\n| module_simulate renderUI / component_preview | `module simulate --render [--device mobile] [--vue-data {...}]` / `component preview <uc-name> [--cases [...]] [--device desktop,mobile]` |\n| error_log_read / error_log_clear | `log read [--tool T] [--limit N\\|0] [--summary]` / `log clear` |\n| module_publish_html / json / script / css | `module publish html --approval <token>` / `module publish json` / `\u2026 script` / `\u2026 css` |\n| component_list / get / new / update / delete | `component list` / `component get <name>` / `component new <name>` / `component update <name>` / `component delete <name>` |\n| file_import_list / get / new / update / upload_content / delete | `import list` / `import get <id>` / `import new \u2026` / `import update <id> \u2026` / `import upload <id> -f file` / `import delete <id>` |\n| db_config_read / db_connect | `db list` / `db add <alias> --token - --api-domain host` |\n| db_connect_by_name / db_alias_list / db_alias_new | `db add-by-name <alias>` / `db alias list [--search s]` / `db alias new <alias> \u2026` |\n| db_user_token_set / db_token_rebuild | `db user-token [--from p/alias]` / `db token-rebuild --uid U --pwd-env VAR` |\n| right_system_* / right_function_* | `right system list\\|new <0-9>\\|update <0-9>\\|delete <0-9>` / `right function list\\|new --code N\\|update <id>\\|delete <id>` |\n| db_schema_get / db_schema_read | `schema pull [--prune-local-only]` / `schema [--type T] [--search text]` |\n| db_sp_list / db_sp_get / db_sp_save | `sp list` / `sp get <name> [--print]` / `sp save <name> -f file` |\n| db_sp_deploy / db_sp_help | `sp deploy <name>` / `sp help <name\\|Fn> [--full]` |\n| db_sp_verify / db_sp_delete / db_sp_rename | `sp verify <name\\|Fn> [--params {\u2026}] [--check-grant]` / `sp delete <name> --type Procedure\\|Function` / `sp rename <old> <new>` |\n| db_sql_execute / db_sql_execute_nonquery | `query \"<sql>\"` / `exec \"<sql>\"` (+ `--confirm-write`) |\n| skill_get / skill_reference_get / skill_sync | `skill get` / `skill ref <path...>` / `skill sync` |\n| design_list / design_read | `design list` / `design read <brand>` |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n\n## Modules\n\n1. `fui module get /project/module` (also a live URL or display name).\n - It archives local files that differ, deletes `.vue` files the server no longer has, and makes the module the session target.\n2. Edit the files in `<workspace>/<project>/modules/<module>/`. Running `fui` from inside that folder targets it.\n3. Check, cheapest first:\n - `fui module validate`: fix every error (exit 1 while any remain); it only reads local files.\n - `fui module outline`: the layout tree (grid widths, bindings, actions, dialogs) to confirm the structure is what you meant.\n - `fui module simulate --scenario '[{\"click\":\"Save\"},{\"assert\":\"vueData.list.length > 0\"}]'`: runs the module in a simulated runtime; exit 1 when an assert fails. `--mocks` wins over live data. An unmocked static API may call the real tAPI when its local `.sql` body reads as read-only (max 8 calls, cached in `_testdata/`). For an existing SP take mocks from `fui sp verify`, never invent them. Keep scenario and mocks in files (`--scenario-file`, `--mocks-file`); `fui module simulate --last` reruns the module's last input (render mode, viewport, files) and re-reads those files. When the scenario changes a lot, pass it inline with `--last --scenario '[...]'`: one command, one step. Add `--brief` (remembered by `--last`) to print only failures, warnings, asserts, stats and screenshot paths.\n - `fui component preview <uc-name> --cases '[{\"name\":\"empty\",\"props\":{\"items\":[]}}]'`: one local `.vue` alone in a real browser; do it before wiring a component in and before publishing a project-scope one.\n - `fui module simulate --render [--device mobile]`: the real page in headless Chromium, last and only for problems that need pixels. Project-level assets come from the server, so publish project files first. It prints screenshot paths: open and read them, then fix what you see. Needs Playwright once: `bun add -g playwright && bunx playwright install chromium`.\n4. Publish:\n - **Whole module, including header/body:** `fui module preview` \u2192 show the changes to the user \u2192 after approval `fui module publish html --approval <token>`.\n - The token breaks if any payload file changes after the preview.\n - **One file:** `fui module publish json|script|css`, `fui component update <name>`, or `fui file push <path...>`, which routes each path to the right publisher.\n - Unchanged files are skipped via the publish ledger.\n5. `fui file status` lists what is still unpublished.\n\n`fui remote module --part ui|html|script|css|load|components|imports` and `fui remote project --part config|modules|\u2026` show the server copy without touching the workspace. `fui remote diff <path...>` compares local files with the server.\n\nAsk the user before `module delete`, `component delete`, `import delete` and `user remove`.\n\n## Data\n\n```bash\nfui query \"SELECT TOP 20 * FROM tblStudent WHERE ClassID = 5\" --db lhu-docs/docs\nfui query -f report.sql --limit 0 # default cap 200 rows; 0 = all\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` first. Every row you read costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --search studentid # compact DDL lines; --search matches the whole line (columns, params)\nfui schema --type API --search Student # Table View API \"API File\" Procedure Function\nfui schema pull # refresh schema.json + every local .sql body\nfui sp list --search Student # columns: db name kind count local\n```\n\n- With no cache, `schema` fails and tells you to run `fui schema pull`. It never pulls on its own.\n- A `# warning: ... last synced N days ago` note means pull before trusting the names.\n- `schema pull` archives and deletes local `.sql` files whose objects were dropped on the server.\n- It only lists drafts that exist in neither schema. `--prune-local-only` archives those too, but only once the user confirms they are stale.\n\n## Stored procedures (MSSQL)\n\n1. `fui sp get <name>` saves the live body to `_db/<alias>/<name>.sql`. A differing local copy is archived first.\n - For a new SP: `fui sp path <name> --db <ref>`, or `fui sp save <name> -f file.sql --db <ref>`.\n2. Edit the file. `CREATE` versus `ALTER` does not matter.\n3. `fui sp deploy <name>`:\n - saves the live body as a rollback copy\n - fixes the verb\n - grants `EXECUTE` to `[public]` on new procedures and checks it\n - clears the tAPI cache\n4. If the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"`.\n\nBefore writing `apiMocks` for an SP that exists, run `fui sp verify <name>`. It reads the real body (never calls the endpoint) and prints the params, the response shape, the error contract, read/write and a mock snippet. `sp delete` and `sp rename` change the server: ask the user first.\n\n## Writes need the user\n\n- Writes to real data exit `3` until you add `--confirm-write`. That covers `INSERT`, `UPDATE`, `DELETE`, `MERGE` and `TRUNCATE` in `query` or `exec`, and `ALTER TABLE ... ALTER COLUMN`.\n- Add `--confirm-write` **only after the user approved that exact write in chat.**\n- Always blocked, with no flag to unlock them. Show the SQL to the user to run by hand:\n - `DROP TABLE`\n - `ALTER TABLE ... DROP COLUMN` or `SWITCH`\n - `DELETE`/`UPDATE` without `WHERE` in `exec`\n- Never write or call a live API SP \"to test\" it. Read its body.\n\n## Workspace files (local, no network)\n\n```bash\nfui file where # workspace, project, module, dirs, and why\nfui file status [--all] # files changed since the last publish/sync, with the command to push each\nfui file history <path> # archived versions in _history, newest first\nfui file restore <path> [--at YYYYMMDD_HHMMSS] # the current file is archived first\nfui file ledger <path...> [--mark|--forget]\n```\n\n## Compose with shell\n\nFilter and loop in the shell. Piped output costs no tokens. Every table also takes `--filter text`, `--where col=value` and `--fields a,b` (applied after the request, so nothing else changes).\n\n```bash\nfui sp list --no-header --type API | cut -f2 # API SP names\nfui sp list --no-header | awk -F'\\t' '$5==\"yes\"{print $2}' # SPs with a local copy\nfui schema --type Procedure | grep -i tblStudent | cut -d' ' -f3 # procedures mentioning a word\nfui sp list --no-header --search Student | cut -f2 | xargs -n1 fui sp get\nfui query \"SELECT TOP 5 ID, Name FROM tblX\" --json | jq -r '.[].Name'\n```\n\n- Check exit codes. An exit `3` inside a loop means ask the user; do not retry.\n- Never put `--confirm-write` in a loop or `xargs`.\n\n## Connections and secrets\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn --user-token-env UT\nfui db add local -p demo --conn-env PG_URL # postgres:// mysql:// sqlite:// connect directly\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # never shows the password\n```\n\nSecrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo or paste token values.\n\n- Never `cat`, `jq` or otherwise print `_db/_connections.json`, `_db/config.json` or MCP config files. Use `fui db list`, `fui token decode` or `fui config`; they never print secrets.\n- No token at hand? `fui db user-token` lists fingerprints of the tokens already in the workspace; copy one with `--from <project/alias>`. The value never appears.\n- Know the alias but not the connection string? `fui db alias list`, then `fui db add-by-name <alias>`: it borrows the SQL login of a connection on the same apiDomain.\n- `right *` and `db alias new` write to the shared acc system. Ask the user first, especially for `delete`.\n- `fui db list` shows a pre-1.4 `_db/config.json` as `engine=legacy`. Migrate it with `fui db import-legacy -p <project>`.\n\n## FUI conventions\n\n- tAPI routes:\n - `spAPI_<Fn>` \u2192 `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` \u2192 `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` \u2192 file endpoints\n- tAPI parameters must not have default values (`@x int = 0`). Deploy blocks them.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. `sp deploy` does it; raw `exec` only reports it.\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";async function gu(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Xf={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Ds)}},Yf={name:"skill get",mcpName:"skill_get",usage:"fui skill get",summary:"Print the FUI domain skill (SKILL.md + Tier-A refs with an active target, else the summary); marks it loaded",async run(e){let t=await Ka(await gu(e));await e.session.markSkillLoaded(),T(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${t.text}
364
+ `)}},Qf={name:"skill ref",mcpName:"skill_reference_get",usage:"fui skill ref <path> [path...] e.g. references/db-workflow.md",summary:"Print skill reference files; marks the skill loaded",async run(e){if(e.positionals.length===0)throw new h("missing <path>; see references/INDEX.md");await e.session.markSkillLoaded();let t=await gu(e),n=t?Xn(t,"_skills",Dt):await sn();T(`# skill dir: ${n}`),k(`${(await Ga(n,e.positionals)).trimStart()}
365
+ `)}},Zf={name:"skill loaded",usage:"fui skill loaded",summary:"Record that the skill was read (after reading installed skill files yourself); satisfies strict-mode gates",async run({session:e}){await e.markSkillLoaded(),k(`skill marked loaded for this session
366
+ `)}},eg={name:"skill sync",mcpName:"skill_sync",usage:"fui skill sync",summary:"Re-copy the bundled fui-skill into <workspace>/_skills/",async run({paths:e}){let t=await Io(e.root);k(`${t.skillDir} (${t.source})
367
+ `)}},tg={name:"skill install",usage:"fui skill install [--dir <skills-root>]",summary:"Install the fui and fui-skill Claude skills into ~/.claude/skills (or --dir)",options:{dir:{type:"string"}},async run({values:e}){let t=f(e,"dir")??Xn(zf(),".claude","skills"),n=Xn(t,"fui","SKILL.md");await Bun.write(n,Ds);let o=Xn(t,Dt);await Gf(o,{recursive:!0,force:!0}),await Kf(t,{recursive:!0}),await Jf(await sn(),o,{recursive:!0}),k(`${n}
356
368
  ${o}
357
- `)}},Mh={name:"design list",mcpName:"design_list",usage:"fui design list",summary:"List bundled design systems (brands) for fui design read",async run({render:e}){let t=await Zr();E(`# ${t.length} brands`),k(e.json?`${JSON.stringify(t)}
369
+ `)}},ng={name:"design list",mcpName:"design_list",usage:"fui design list",summary:"List bundled design systems (brands) for fui design read",async run({render:e}){let t=await ts();T(`# ${t.length} brands`),k(e.json?`${JSON.stringify(t)}
358
370
  `:`${t.join(`
359
371
  `)}
360
- `)}},Fh={name:"design read",mcpName:"design_read",usage:"fui design read <brand>",summary:"Print a brand's DESIGN.md",async run({positionals:e}){let t=e[0];if(!t)throw new f("missing <brand>; run: fui design list");let n=await Zr();if(!n.includes(t))throw new f(`unknown brand "${t}"; available: ${n.join(", ")}`);let o=Xn(await sn(),"design-md",t,"DESIGN.md");E(`# source: ${o}`),k(await H(o))}},iu=[Ah,Nh,_h,Dh,xh,Oh,Mh,Fh];function Ut(e){let t=e[0];if(!t)throw new f("missing <name>");return t}var Lh={name:"sp list",mcpName:"db_sp_list",usage:'fui sp list [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search name]',summary:"List objects from the schema cache (every connection unless --db); count = params or columns",options:{type:{type:"string"},search:{type:"string"}},async run(e){let{rows:t,notes:n}=await Xi(await z(e),{db:h(e.values,"db"),objectType:As(h(e.values,"type")),search:h(e.values,"search")});for(let r of n)E(`# ${r}`);let o=new Map,s=[];for(let r of t){let i=o.get(r.db);if(!i){let[a,c]=r.db.split("/");i=new Set((await e.store.localSqlNames(a,c)).map((l)=>l.toLowerCase())),o.set(r.db,i)}s.push({...r,local:i.has(r.name.toLowerCase())?"yes":"no"})}k(X(s,e.render))}},Uh={name:"sp get",mcpName:"db_sp_get",usage:"fui sp get <name> [--db ref] [--print | -o file]",summary:"Fetch a definition into _db/<alias>/<name>.sql (a differing local copy is archived first)",options:{print:{type:"boolean"},out:{type:"string",short:"o"}},async run(e){let t=await Yi(await z(e),Ut(e.positionals),h(e.values,"db"));for(let o of t.notes)E(`# ${o}`);let n=h(e.values,"out");if(n)await Bun.write(n,t.body),k(`${n}
372
+ `)}},og={name:"design read",mcpName:"design_read",usage:"fui design read <brand>",summary:"Print a brand's DESIGN.md",async run({positionals:e}){let t=e[0];if(!t)throw new h("missing <brand>; run: fui design list");let n=await ts();if(!n.includes(t))throw new h(`unknown brand "${t}"; available: ${n.join(", ")}`);let o=Xn(await sn(),"design-md",t,"DESIGN.md");T(`# source: ${o}`),k(await B(o))}},wu=[Xf,Yf,Qf,Zf,eg,tg,ng,og];function Ut(e){let t=e[0];if(!t)throw new h("missing <name>");return t}var rg={name:"sp list",mcpName:"db_sp_list",usage:'fui sp list [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search name]',summary:"List objects from the schema cache (every connection unless --db); count = params or columns",options:{type:{type:"string"},search:{type:"string"}},async run(e){let{rows:t,notes:n}=await Qi(await z(e),{db:f(e.values,"db"),objectType:_s(f(e.values,"type")),search:f(e.values,"search")});for(let r of n)T(`# ${r}`);let o=new Map,s=[];for(let r of t){let i=o.get(r.db);if(!i){let[a,c]=r.db.split("/");i=new Set((await e.store.localSqlNames(a,c)).map((l)=>l.toLowerCase())),o.set(r.db,i)}s.push({...r,local:i.has(r.name.toLowerCase())?"yes":"no"})}k(X(s,e.render))}},sg={name:"sp get",mcpName:"db_sp_get",usage:"fui sp get <name> [--db ref] [--print | -o file]",summary:"Fetch a definition into _db/<alias>/<name>.sql (a differing local copy is archived first)",options:{print:{type:"boolean"},out:{type:"string",short:"o"}},async run(e){let t=await Zi(await z(e),Ut(e.positionals),f(e.values,"db"));for(let o of t.notes)T(`# ${o}`);let n=f(e.values,"out");if(n)await Bun.write(n,t.body),k(`${n}
361
373
  `);else k(e.values.print===!0?t.body:`${t.path}
362
- `)}},Bh={name:"sp save",mcpName:"db_sp_save",usage:'fui sp save <name> (--sql "<text>" | -f file | -) [--db ref]',summary:"Write a script to _db/<alias>/<name>.sql locally; nothing reaches the database",options:{sql:{type:"string"},file:{type:"string",short:"f"}},async run(e){let t=Ut(e.positionals),n=h(e.values,"sql"),o=e.positionals[1];if(n!==void 0&&o!==void 0)throw new f("pass --sql or - / -f, not both");let s=await vn(n??o,h(e.values,"file")),r=await Qi(await z(e),t,s,h(e.values,"db"));for(let i of r.notes)E(`# ${i}`);k(`${r.path}
374
+ `)}},ig={name:"sp save",mcpName:"db_sp_save",usage:'fui sp save <name> (--sql "<text>" | -f file | -) [--db ref]',summary:"Write a script to _db/<alias>/<name>.sql locally; nothing reaches the database",options:{sql:{type:"string"},file:{type:"string",short:"f"}},async run(e){let t=Ut(e.positionals),n=f(e.values,"sql"),o=e.positionals[1];if(n!==void 0&&o!==void 0)throw new h("pass --sql or - / -f, not both");let s=await Sn(n??o,f(e.values,"file")),r=await ea(await z(e),t,s,f(e.values,"db"));for(let i of r.notes)T(`# ${i}`);k(`${r.path}
363
375
  next: fui sp deploy ${t} --db ${r.alias}
364
- `)}},Hh={name:"sp path",usage:"fui sp path <name> [--db ref]",summary:"Print the local .sql path to edit (file may not exist yet)",async run(e){let t=pt(Ut(e.positionals)),n=await z(e),o=await e.store.resolve({db:h(e.values,"db"),project:n.project,contextProject:n.contextProject,objectName:t});k(`${e.store.paths.spFile(o.projectId,o.alias,t)}
365
- `)}},Wh={name:"sp deploy",mcpName:"db_sp_deploy",usage:"fui sp deploy <name> [--db ref]",summary:"Deploy the local .sql: rollback copy, CREATE/ALTER fix, grant, tAPI cache clear",async run(e){let t=await Zi(await z(e),Ut(e.positionals),h(e.values,"db"));k(`${t.notes.join(`
376
+ `)}},ag={name:"sp path",usage:"fui sp path <name> [--db ref]",summary:"Print the local .sql path to edit (file may not exist yet)",async run(e){let t=mt(Ut(e.positionals)),n=await z(e),o=await e.store.resolve({db:f(e.values,"db"),project:n.project,contextProject:n.contextProject,objectName:t});k(`${e.store.paths.spFile(o.projectId,o.alias,t)}
377
+ `)}},cg={name:"sp deploy",mcpName:"db_sp_deploy",usage:"fui sp deploy <name> [--db ref]",summary:"Deploy the local .sql: rollback copy, CREATE/ALTER fix, grant, tAPI cache clear",async run(e){let t=await ta(await z(e),Ut(e.positionals),f(e.values,"db"));k(`${t.notes.join(`
366
378
  `)}
367
- `)}},qh={name:"sp help",mcpName:"db_sp_help",usage:"fui sp help <name|Fn> [--db ref] [--full]",summary:"Call tAPI /help: shows params and clears the tAPI cache (never fails)",options:{full:{type:"boolean"}},async run(e){let t=await ea(await z(e),Ut(e.positionals),h(e.values,"db"),e.values.full===!0?0:Ji);for(let n of t.notes)E(`# ${n}`);k(t.data??"")}};function Jh(e){if(e===void 0)return;let t;try{t=JSON.parse(e)}catch(n){throw new f(`--params is not valid JSON: ${n.message}`)}if(!t||typeof t!=="object"||Array.isArray(t))throw new f("--params must be a JSON object");return t}var Vh={name:"sp verify",mcpName:"db_sp_verify",usage:`fui sp verify <name|Fn> [--db ref] [--params '{"ClassID":"C01"}'] [--url-param v]... [--check-grant]`,summary:"Static contract check of an API SP from its real body (params, response shape, errors, read/write, mock); never calls it",options:{params:{type:"string"},"url-param":{type:"string",multiple:!0},"check-grant":{type:"boolean"}},async run(e){let t=e.values["url-param"],n=await oa(await z(e),{name:Ut(e.positionals),db:h(e.values,"db"),params:Jh(h(e.values,"params")),urlParams:Array.isArray(t)?t.map(String):void 0,checkGrant:e.values["check-grant"]===!0});k(`${n.notes.join(`
379
+ `)}},lg={name:"sp help",mcpName:"db_sp_help",usage:"fui sp help <name|Fn> [--db ref] [--full]",summary:"Call tAPI /help: shows params and clears the tAPI cache (never fails)",options:{full:{type:"boolean"}},async run(e){let t=await na(await z(e),Ut(e.positionals),f(e.values,"db"),e.values.full===!0?0:Ki);for(let n of t.notes)T(`# ${n}`);k(t.data??"")}};function ug(e){if(e===void 0)return;let t;try{t=JSON.parse(e)}catch(n){throw new h(`--params is not valid JSON: ${n.message}`)}if(!t||typeof t!=="object"||Array.isArray(t))throw new h("--params must be a JSON object");return t}var dg={name:"sp verify",mcpName:"db_sp_verify",usage:`fui sp verify <name|Fn> [--db ref] [--params '{"ClassID":"C01"}'] [--url-param v]... [--check-grant]`,summary:"Static contract check of an API SP from its real body (params, response shape, errors, read/write, mock); never calls it",options:{params:{type:"string"},"url-param":{type:"string",multiple:!0},"check-grant":{type:"boolean"}},async run(e){let t=e.values["url-param"],n=await sa(await z(e),{name:Ut(e.positionals),db:f(e.values,"db"),params:ug(f(e.values,"params")),urlParams:Array.isArray(t)?t.map(String):void 0,checkGrant:e.values["check-grant"]===!0});k(`${n.notes.join(`
368
380
  `)}
369
- `)}},Kh={name:"sp delete",mcpName:"db_sp_delete",usage:"fui sp delete <name> --type Procedure|Function [--db ref]",summary:"DROP a procedure or function on the server (ask the user first); archives the local .sql",options:{type:{type:"string"}},async run(e){let t=h(e.values,"type");if(t!=="Procedure"&&t!=="Function")throw new f("--type must be Procedure or Function");let n=await ra(await z(e),Ut(e.positionals),t,h(e.values,"db"));k(`${n.notes.join(`
381
+ `)}},pg={name:"sp delete",mcpName:"db_sp_delete",usage:"fui sp delete <name> --type Procedure|Function [--db ref]",summary:"DROP a procedure or function on the server (ask the user first); archives the local .sql",options:{type:{type:"string"}},async run(e){let t=f(e.values,"type");if(t!=="Procedure"&&t!=="Function")throw new h("--type must be Procedure or Function");let n=await ia(await z(e),Ut(e.positionals),t,f(e.values,"db"));k(`${n.notes.join(`
370
382
  `)}
371
- `)}},Gh={name:"sp rename",mcpName:"db_sp_rename",usage:"fui sp rename <old> <new> [--db ref]",summary:"sp_rename a procedure, function or table on the server; renames the local .sql to match",async run(e){let[t,n]=e.positionals;if(!t||!n)throw new f("usage: fui sp rename <old> <new>");let o=await sa(await z(e),t,n,h(e.values,"db"));k(`${o.notes.join(`
383
+ `)}},mg={name:"sp rename",mcpName:"db_sp_rename",usage:"fui sp rename <old> <new> [--db ref]",summary:"sp_rename a procedure, function or table on the server; renames the local .sql to match",async run(e){let[t,n]=e.positionals;if(!t||!n)throw new h("usage: fui sp rename <old> <new>");let o=await aa(await z(e),t,n,f(e.values,"db"));k(`${o.notes.join(`
372
384
  `)}
373
- `)}},au=[Lh,Uh,Bh,Hh,Wh,qh,Vh,Kh,Gh];var zh={name:"token decode",usage:"fui token decode [--db ref]",summary:"Show engine, server, database and user of a stored token (password never shown)",async run({store:e,values:t,render:n}){let o=await e.resolve({db:h(t,"db"),project:h(t,"project")}),s={db:`${o.projectId}/${o.alias}`,...io(o.dbToken)};k(X([s],n))}},Xh={name:"token encode",usage:"fui token encode --server S --database D --uid U --pwd-env VAR",summary:"Print a Fui MSSQL dbToken (Base64) built like Fui MCP",options:{server:{type:"string"},database:{type:"string"},uid:{type:"string"},"pwd-env":{type:"string"}},async run({values:e}){let t=h(e,"server"),n=h(e,"database"),o=h(e,"uid"),s=await Pe(void 0,h(e,"pwd-env"),"--pwd");if(!t||!n||!o||!s)throw new f("need --server, --database, --uid and --pwd-env");k(`${Jt(t,n,o,s)}
374
- `)}},cu=[zh,Xh];var Yh={name:"update",usage:"fui update [--check]",summary:"Update fui to the latest npm release via bun add -g (--check only reports)",options:{check:{type:"boolean"},background:{type:"boolean"}},async run({values:e}){if(e.background===!0){await Mr().catch(()=>{return});return}if(e.check===!0){let n=await xr();k(Or(n)?`update available: ${je} -> ${n}; run: fui update
385
+ `)}},yu=[rg,sg,ig,ag,cg,lg,dg,pg,mg];var hg={name:"token decode",usage:"fui token decode [--db ref]",summary:"Show engine, server, database and user of a stored token (password never shown)",async run({store:e,values:t,render:n}){let o=await e.resolve({db:f(t,"db"),project:f(t,"project")}),s={db:`${o.projectId}/${o.alias}`,...io(o.dbToken)};k(X([s],n))}},fg={name:"token encode",usage:"fui token encode --server S --database D --uid U --pwd-env VAR",summary:"Print a Fui MSSQL dbToken (Base64) built like Fui MCP",options:{server:{type:"string"},database:{type:"string"},uid:{type:"string"},"pwd-env":{type:"string"}},async run({values:e}){let t=f(e,"server"),n=f(e,"database"),o=f(e,"uid"),s=await Pe(void 0,f(e,"pwd-env"),"--pwd");if(!t||!n||!o||!s)throw new h("need --server, --database, --uid and --pwd-env");k(`${Vt(t,n,o,s)}
386
+ `)}},bu=[hg,fg];var gg={name:"update",usage:"fui update [--check]",summary:"Update fui to the latest npm release via bun add -g (--check only reports)",options:{check:{type:"boolean"},background:{type:"boolean"}},async run({values:e}){if(e.background===!0){await Lr().catch(()=>{return});return}if(e.check===!0){let n=await Mr();k(Fr(n)?`update available: ${je} -> ${n}; run: fui update
375
387
  `:`fui ${je} is current (latest ${n})
376
- `);return}let t=await Mr();if(t.action==="current")k(`fui ${je} is current
388
+ `);return}let t=await Lr();if(t.action==="current")k(`fui ${je} is current
377
389
  `);else if(t.action==="installed")k(`updated ${je} -> ${t.latest}; refresh installed skills: fui skill install
378
- `);else if(t.action==="manual")E(`# ${Bun.main} is not a bun global install (bun link checkout or compiled binary); it cannot replace itself`),k(`update available: ${je} -> ${t.latest}; install: ${t.detail}
379
- `);else return E(t.detail??""),k(`update to ${t.latest} failed; retry: bun add -g ${On}@${t.latest}
380
- `),Oe.Runtime}},lu=[Yh];var _s=[...Ca,...ba,...cu,...ou,...ru,...au,...cc,...Pl,...Vl,...nu,...Pa,...iu,...Zl,...Ia,...lu],du=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json --no-header -h/--help -v/--version
390
+ `);else if(t.action==="manual")T(`# ${Bun.main} is not a bun global install (bun link checkout or compiled binary); it cannot replace itself`),k(`update available: ${je} -> ${t.latest}; install: ${t.detail}
391
+ `);else return T(t.detail??""),k(`update to ${t.latest} failed; retry: bun add -g ${On}@${t.latest}
392
+ `),Oe.Runtime}},ku=[gg];var xs=[...Pa,...va,...bu,...hu,...fu,...yu,...uc,...Ll,...ou,...mu,...Ea,...wu,...uu,...Ra,...ku],Su=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json --no-header -h/--help -v/--version
381
393
  lists: --filter text --where col=value (repeatable) --fields a,b,c (client-side; the request is unchanged)
382
- exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`;function uu(){let e=Math.max(..._s.map((n)=>n.name.length)),t=_s.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`);return`fui ${je}: FUI platform CLI
394
+ exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`;function vu(){let e=Math.max(...xs.map((n)=>n.name.length)),t=xs.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`);return`fui ${je}: FUI platform CLI
383
395
 
384
396
  commands:
385
397
  ${t.join(`
386
398
  `)}
387
399
 
388
- ${du}
400
+ ${Su}
389
401
  run "fui skill" for the agent guide
390
- `}function Zh(e){return`${e.usage}
402
+ `}function yg(e){return`${e.usage}
391
403
  ${e.summary}
392
- ${du}
393
- `}var eg=3;function tg(e){let t=[];for(let n of e.slice(0,eg)){if(n.startsWith("-"))break;t.push(n)}for(let n=t.length;n>0;n--){let o=t.slice(0,n).join(" "),s=_s.find((r)=>r.name===o);if(s)return{command:s,rest:e.slice(n)}}return null}async function ng(e,t,n,o){if(n instanceof $e&&n.severity==="soft")return;let s=n instanceof $e?n.logKind:"error",r=n instanceof lt?n.report:`\u274C ${o}`;await Gl(e,t.mcpName??`fui ${t.name}`,r,s)}async function og(e){let t=tg(e);if(!t){let r=e[0];if(!r||r==="-h"||r==="--help"||r==="help")return k(uu()),Oe.Ok;if(r==="-v"||r==="--version")return k(`${je}
394
- `),Oe.Ok;return E(`unknown command: ${e.slice(0,2).join(" ")}
395
- `),E(uu()),Oe.Usage}let{command:n,rest:o}=t,s;try{let r;try{r=Qh({args:o,options:{...ni,...n.options},allowPositionals:!0,strict:!0})}catch(i){throw new f(`${i.message}
396
- usage: ${n.usage}`)}if(r.values.help===!0)return k(Zh(n)),Oe.Ok;return s=await oi(r.values,r.positionals),await n.run(s)??Oe.Ok}catch(r){let i=Ms(r),a=i===Oe.Guard?"guard":"error",c=r.message;if(E(`${a}: ${c}`),s)await ng(s.paths.root,n,r,c);return i}}if(import.meta.main){let e=await Sa().catch(()=>null);if(e)E(e);process.exitCode=await og(Bun.argv.slice(2))}export{og as main};
404
+ ${Su}
405
+ `}var bg=3;function kg(e){let t=[];for(let n of e.slice(0,bg)){if(n.startsWith("-"))break;t.push(n)}for(let n=t.length;n>0;n--){let o=t.slice(0,n).join(" "),s=xs.find((r)=>r.name===o);if(s)return{command:s,rest:e.slice(n)}}return null}async function vg(e,t,n,o){if(n instanceof ve&&n.severity==="soft")return;let s=n instanceof ve?n.logKind:"error",r=n instanceof ut?n.report:`\u274C ${o}`;await su(e,t.mcpName??`fui ${t.name}`,r,s)}async function Sg(e){let t=kg(e);if(!t){let r=e[0];if(!r||r==="-h"||r==="--help"||r==="help")return k(vu()),Oe.Ok;if(r==="-v"||r==="--version")return k(`${je}
406
+ `),Oe.Ok;return T(`unknown command: ${e.slice(0,2).join(" ")}
407
+ `),T(vu()),Oe.Usage}let{command:n,rest:o}=t,s;try{let r;try{r=wg({args:o,options:{...ri,...n.options},allowPositionals:!0,strict:!0})}catch(i){throw new h(`${i.message}
408
+ usage: ${n.usage}`)}if(r.values.help===!0)return k(yg(n)),Oe.Ok;return s=await si(r.values,r.positionals),await n.run(s)??Oe.Ok}catch(r){let i=Ls(r),a=i===Oe.Guard?"guard":"error",c=r.message;if(T(`${a}: ${c}`),s)await vg(s.paths.root,n,r,c);return i}}if(import.meta.main){let e=await Ia().catch(()=>null);if(e)T(e);process.exitCode=await Sg(Bun.argv.slice(2))}export{Sg as main};