@fui-org/fui-cli 1.0.4 → 1.1.0

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,23 @@
1
- #!/usr/bin/env bun
2
- // @bun
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(`
1
+ #!/usr/bin/env node
2
+ var R=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(o){n=[o]}if(n)throw n[0];return t};import{createHash as xp}from"node:crypto";import{statSync as Op}from"node:fs";import{mkdir as Mp,readdir as Fp,readFile as Lp,stat as ia,writeFile as Up}from"node:fs/promises";import{delimiter as Hp,dirname as qp,join as Vr}from"node:path";function rt(){return Wt?Wt.main:process.argv[1]??""}async function st(e){return aa.decode(await Lp(e))}async function Jt(e){return JSON.parse(await st(e))}async function Ke(e){try{return(await ia(e)).isFile()}catch{return!1}}async function Wr(e){try{return(await ia(e)).isDirectory()}catch{return!1}}async function O(e,t){await Mp(qp(e),{recursive:!0}),await Up(e,t)}async function*fn(e,t){if(!await Wr(t))return;if(Wt){yield*new Wt.Glob(e).scan({cwd:t,onlyFiles:!0});return}yield*Uo(t,"",e.split("/"))}function Vp(e){let t="";for(let n of e)if(n==="*")t+=".*";else if(n==="?")t+=".";else t+=/[.+^${}()|[\]\\]/.test(n)?`\\${n}`:n;return new RegExp(`^${t}$`)}async function*Uo(e,t,n){let[o,...s]=n;if(o===void 0)return;let r;try{r=await Fp(Vr(e,t),{withFileTypes:!0})}catch{return}if(o==="**"){yield*Uo(e,t,s);for(let a of r)if(a.isDirectory())yield*Uo(e,t?`${t}/${a.name}`:a.name,n);return}let i=Vp(o);for(let a of r){if(!i.test(a.name))continue;let c=t?`${t}/${a.name}`:a.name;if(s.length===0){if(a.isFile())yield c}else if(a.isDirectory())yield*Uo(e,c,s)}}function Ho(e,t=process.env.PATH??""){let n=process.platform==="win32"?[`${e}.exe`,`${e}.cmd`]:[e];for(let o of t.split(Hp))for(let s of n)try{if(o&&Op(Vr(o,s)).isFile())return Vr(o,s)}catch{}return null}function gn(e,t){return xp(e).update(t).digest("hex")}async function Zn(){let e=[];for await(let t of process.stdin)e.push(typeof t==="string"?Buffer.from(t):t);return aa.decode(Buffer.concat(e))}function Br(e){if(Wt)return Wt.stringWidth(e);let t=0;for(let n of e.replace(Wp,"")){if(/\p{Mn}|\p{Me}|\p{Cf}/u.test(n))continue;let o=n.codePointAt(0),s=o>=4352&&o<=4447||o>=11904&&o<=42191||o>=44032&&o<=55203||o>=63744&&o<=64255||o>=65072&&o<=65103||o>=65280&&o<=65376||o>=65504&&o<=65510||o>=127744&&o<=129791||o>=131072&&o<=262141;t+=s?2:1}return t}function ca(e,t){let n=(r)=>{let[i="",a]=r.replace(/^v/,"").split("-",2);return{parts:i.split(".").map((c)=>Number.parseInt(c,10)||0),pre:a}},o=n(e),s=n(t);for(let r=0;r<3;r++){let i=(o.parts[r]??0)-(s.parts[r]??0);if(i!==0)return Math.sign(i)}if(o.pre===s.pre)return 0;if(o.pre===void 0)return 1;if(s.pre===void 0)return-1;return o.pre<s.pre?-1:1}var Wt,Bt,aa,Wp;var te=R(()=>{Wt=globalThis.Bun,Bt=Wt!==void 0;aa=new TextDecoder("utf-8");Wp=new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`,"g")});function la(e){if(e instanceof m)return Ve.Usage;if(e instanceof Pe)return Ve.Guard;return Ve.Runtime}var Ve,m,Pe,Ct;var q=R(()=>{Ve={Ok:0,Runtime:1,Usage:2,Guard:3};m=class m extends Error{name="UsageError"};Pe=class Pe extends Error{severity;logKind;name="GuardError";constructor(e,t="hard",n="guard"){super(e);this.severity=t;this.logKind=n}};Ct=class Ct extends Error{report;name="ReportedFailure";constructor(e,t){super(e);this.report=t}}});import{chmod as Bp}from"node:fs/promises";import{homedir as Jp}from"node:os";import{join as Kt,resolve as Kp}from"node:path";function eo(e){return e.HOME||Jp()}function wn(e,t=process.env,n=process.cwd()){let o=e.trim();if(o==="~")return eo(t);if(o.startsWith("~/"))return Kt(eo(t),o.slice(2));return Kp(n,o)}function ft(e=process.env){let t=e.XDG_CONFIG_HOME||Kt(eo(e),".config");return Kt(t,"fui","config.json")}async function Rt(e=process.env){let t=ft(e);if(!await Ke(t))return{};try{let n=await Jt(t);if(n&&typeof n==="object"&&!Array.isArray(n))return n}catch{}throw new m(`${t} is not a JSON object; fix or delete it`)}async function Jr(e,t=process.env){let n=ft(t);return await O(n,`${JSON.stringify(e,null,2)}
3
+ `),await Bp(n,384),n}function Gp(e){return e.CLAUDE_CONFIG_DIR?Kt(e.CLAUDE_CONFIG_DIR,".claude.json"):Kt(eo(e),".claude.json")}function zp(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function ua(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()&&zp(n,o))return s}return}async function pa(e,t=process.env,n=process.cwd()){let o=Gp(t);if(!await Ke(o))return null;let s;try{s=await Jt(o)}catch{return null}let r=ua(s.projects?.[n]?.mcpServers,e)??ua(s.mcpServers,e);return r?{value:r,file:o}:null}async function Xp(e=process.env,t=process.cwd()){let n=await pa("FUI_MCP_WORKDIR",e,t);return n?{path:wn(n.value,e,t),file:n.file}:null}async function ma(e,t=process.env,n=process.cwd()){if(e)return{path:wn(e,t,n),source:"flag",detail:"--workdir"};for(let r of["FUI_WORKDIR","FUI_MCP_WORKDIR"]){let i=t[r];if(i)return{path:wn(i,t,n),source:"env",detail:r}}let o=await Rt(t);if(typeof o.workdir==="string"&&o.workdir)return{path:wn(o.workdir,t,n),source:"config",detail:ft(t)};let s=await Xp(t,n);if(s)return{path:s.path,source:"mcp",detail:s.file};return{path:Kt(eo(t),Kr),source:"default",detail:"built-in"}}async function da(e,t,n,o){let s=n[e];if(s)return{value:s,source:"env",detail:e};let i=(await Rt(n))[t];if(typeof i==="string"&&i)return{value:i,source:"config",detail:ft(n)};let a=await pa(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 Vo(e=process.env,t=process.cwd()){let[n,o]=await Promise.all([da("FUI_API_BASE","api-base",e,t),da("FUI_API_TOKEN","api-token",e,t)]);return{base:n,token:o}}var qo,Kr;var yn=R(()=>{q();te();qo=["workdir","api-base","api-token","auto-update"];Kr=Kt(".fui-mcp","workspaces")});function ha(e){return e?.code==="EPIPE"}function k(e){try{process.stdout.write(e)}catch(t){if(ha(t))process.exit(0);throw t}}function P(e){process.stderr.write(e.endsWith(`
5
4
  `)?e:`${e}
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:
5
+ `)}async function Wo(){return Zn()}async function to(e,t){if(t&&e)throw new m("pass SQL as an argument or with -f, not both");let n;if(t){if(!await Ke(t))throw new m(`file not found: ${t}`);n=await st(t)}else if(e==="-")n=await Wo();else if(e!==void 0)n=e;else throw new m('missing SQL: pass "<sql>", -f <file>, or - for stdin');if(!n.trim())throw new m("SQL is empty");return n}async function xe(e,t,n){if(e!==void 0&&t!==void 0)throw new m(`pass ${n} or ${n}-env, not both`);if(t!==void 0){let s=process.env[t];if(!s)throw new m(`env var ${t} is empty or unset`);return s.trim()}if(e===void 0)return;if(e!=="-")throw new m(`${n} takes "-" (stdin) only; use ${n}-env VAR for env vars`);let o=(await Wo()).trim();if(!o)throw new m(`${n}: stdin is empty`);return o}var me=R(()=>{q();te();process.stdout.on("error",(e)=>{if(ha(e))process.exit(0);throw e})});function wa(e){return e.length<=ga?e:`${e.slice(0,ga)}…`}async function Yp(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` — ${wa(s.trim())}`}}catch{}return` — ${wa(t)}`}function gt(e){return e.data||[]}class ya{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(fa)})}catch(o){let s=o?.name;if(s==="TimeoutError"||s==="AbortError")throw Error(`FUI API timeout: ${e} did not answer within ${fa/1000}s`);throw Error(`FUI API network error: ${e} — ${o?.message??String(o)}`)}if(!n.ok)throw Error(`FUI API error: ${n.status} ${n.statusText}${await Yp(n)}`);return await n.json()}async listProjects(){return gt(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 gt(await this.request("/fp/FUI_Dev_UserRightList",{PID:e,MID:""}))}async searchUsers(e,t){return gt(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 gt(await this.request("/fp/FUI_Runtime_ModuleVersionSelect",{domain:e,url1_ID:t,url2_ID:n}))}async listModules(e){return gt(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)P(`# 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 gt(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 gt(await this.request("/fp/FUI_Dev_File/select",{ProjectID:e,ModuleID:t}))}async listFrameworkImportFiles(e){return gt(await this.request("/fp/FUI_Dev_File/select",{frameWork:e}))}async listImportFiles(e,t){return gt(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 Bo(){let{base:e,token:t}=await Vo();if(!e.value||!t.value){let n=[!e.value&&"FUI_API_BASE",!t.value&&"FUI_API_TOKEN"].filter(Boolean).join(", ");throw new m(`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 ya(e.value,t.value)}var fa=60000,ga=400;var Gr=R(()=>{q();me();yn()});import{readFile as Qp}from"node:fs/promises";async function B(e){return Zp.decode(await Qp(e))}async function he(e){try{return await B(e)}catch{return null}}function no(e){return e?.code}var Zp;var be=R(()=>{Zp=new TextDecoder("utf-8",{ignoreBOM:!0})});import{mkdir as em,rename as tm}from"node:fs/promises";import{homedir as nm}from"node:os";import{join as zr}from"node:path";function rm(e=process.env){return e.FUI_SESSION_DIR||e.FUI_MCP_SESSION_DIR||zr(e.HOME||nm(),".fui-mcp","session")}function Xr(e=process.env){let t=e.FUI_SESSION||"default";if(!om.test(t))throw new m(`invalid FUI_SESSION "${t}": letters, digits, "_" or "-"`);return zr(rm(e),`cli-${t}.json`)}class oo{file;constructor(e=Xr()){this.file=e}async read(){let e=await he(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 em(zr(this.file,".."),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;await O(t,JSON.stringify(e,null,2)),await tm(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 bn(e=process.env){return["1","true","yes"].includes((e.FUI_STRICT||e.FUI_MCP_STRICT||"").trim().toLowerCase())}var om;var Gt=R(()=>{q();be();te();om=/^[A-Za-z0-9_-]+$/});import{readdir as sm}from"node:fs/promises";import{isAbsolute as im,join as Qr,relative as am,sep as cm}from"node:path";function wt(e,t){if(!e||!lm.test(e)||/^\.+$/.test(e))throw new m(`invalid ${t} ${JSON.stringify(e)}: letters, digits, ".", "_", "-" and spaces only (used as a workspace folder name)`);return e}function um(e,t){let n=am(e,t);if(!n||n.startsWith("..")||im(n))return null;let[o,s,r]=n.split(cm);if(!o||o.startsWith("_")||o.startsWith("."))return null;if(s==="modules"&&r)return{projectId:o,moduleId:r,moduleDir:Qr(e,o,"modules",r)};return{projectId:o}}async function ba(e,t){try{return(await sm(e,{withFileTypes:!0})).filter((o)=>o.isDirectory()&&!(t&&(o.name.startsWith("_")||o.name.startsWith(".")))).map((o)=>o.name).sort()}catch{return[]}}function ka(e){return e.length>Yr?[...e.slice(0,Yr),` ... and ${e.length-Yr} more`]:e}class Zr{paths;session;cwd;constructor(e,t,n=process.cwd()){this.paths=e;this.session=t;this.cwd=n}cwdTarget(){return um(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:wt(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 m(await this.noTargetMenu("project"))}async resolveProjectModule(e,t){if(e&&t)return wt(e,"projectId"),wt(t,"moduleId"),{projectId:e,moduleId:t,moduleDir:Qr(this.paths.root,e,"modules",t),source:"explicit"};if(e||t)throw new m("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 m(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 ba(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}
6
+ workspace ${this.paths.root} has no projects; run: fui project list`;if(e==="module"&&t.length===1){let o=await ba(Qr(this.paths.root,t[0],"modules"),!1);if(o.length>0)return[n,...ka(o.map((s)=>` fui use ${t[0]}/${s}`))].join(`
7
+ `)}return[n,...ka(t.map((o)=>` -p ${o}`))].join(`
8
+ `)}}var lm,Yr=20;var es=R(()=>{q();lm=/^[A-Za-z0-9._ -]+$/});function ts(e){return Buffer.from(e,"utf8").toString("base64")}function ro(e){try{let t=e.replace(/\s+/g,"");if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t)||t.length%4===1)throw Error("not base64");return new TextDecoder("utf-8",{fatal:!0}).decode(Buffer.from(t,"base64"))}catch{throw new m("token is not valid Base64 UTF-8")}}function kn(e,t,n,o){return ts(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function va(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 dm(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=va(t);if(n.has("server")||n.has("data source"))return"mssql";throw new m("unsupported connection: expected postgres://, mysql://, mariadb://, sqlite:/file: URL or MSSQL Server=...; string")}function so(e){let t=ro(e);return{engine:dm(t),plain:t}}function Jo(e){let{engine:t,plain:n}=so(e);if(t==="mssql"){let s=va(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)}}var Tt=R(()=>{q()});import{mkdir as pm,readdir as mm,writeFile as hm}from"node:fs/promises";import{basename as Sa,dirname as ja,extname as Ia,isAbsolute as fm,join as ns,relative as Ca,sep as gm}from"node:path";function Ko(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 wm(e,t,n={}){let o=Ca(e.root,t),r=o!==""&&!o.startsWith("..")&&!fm(o)?o:ns("_external",Sa(t)),i=Ia(r),a=Sa(r,i),c=n.stamp??Ko(),l=n.suffix?`_${n.suffix}`:"";return ns(e.historyRoot(),ja(r),`${a}_${c}${l}${i}`)}async function ym(e,t){let n=Ia(e),o=e.slice(0,e.length-n.length);for(let r=1;r<=$a;r++){let i=r===1?e:`${o}_${r}${n}`;try{return await hm(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(a){if(no(a)!=="EEXIST")throw a}}let s=`${o}_${$a}${n}`;return await O(s,t),s}async function Se(e,t,n={}){let o=n.content;if(o===void 0)try{o=await B(t)}catch(r){let i=no(r);if(i==="ENOENT"||i==="ENOTDIR")return null;throw r}let s=wm(e,t,n);return await pm(ja(s),{recursive:!0}),ym(s,o)}function zt(e,t){let n=Ca(e.root,t);return(n.startsWith("..")?t:n).split(gm).join("/")}async function Go(e,t,n={}){let o=n.stamp??Ko(),s=new Set(n.skipDirNames??["_preview"]),r=[];async function i(a){let c;try{c=await mm(a,{withFileTypes:!0})}catch{return}for(let l of c){let u=ns(a,l.name);if(l.isDirectory()){if(!s.has(l.name))await i(u)}else if(l.isFile()){let d=await Se(e,u,{suffix:n.suffix,stamp:o});if(d)r.push(d)}}}return await i(t),r}var $a=50;var Pt=R(()=>{be();te()});import{join as bm}from"node:path";function vn(e){if(!km.test(e))throw new m(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function os(e){if(!vm.test(e)||e.startsWith("_")||e.startsWith("."))throw new m(`invalid project id "${e}"`);return e}function Ra(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 m(`invalid --db "${e}": use <alias> or <project>/<alias>`)}function ke(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class yt{paths;constructor(e){this.paths=e}async load(e){let t=this.paths.connectionsFile(e);if(!await Ke(t))return{connections:{}};let n=await B(t);if(!n.trim())return{connections:{}};let o;try{o=JSON.parse(n)}catch(r){throw Error(`${t} is not valid JSON (${r.message}); fix it by hand before writing`)}if(!o||typeof o!=="object"||Array.isArray(o))throw Error(`${t} must be an object { default, connections }`);let s=o;if(s.connections===void 0)return{...s,connections:{}};if(!s.connections||typeof s.connections!=="object"||Array.isArray(s.connections))throw Error(`${t}: "connections" must be an object of alias -> config`);return s}async write(e,t){let n=this.paths.connectionsFile(e);await Se(this.paths,n),await O(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let s=await this.load(e),r=ke(s,t)??vn(t);if(s.connections[r]=n,o||!s.default||!ke(s,s.default))s.default=r;return await this.write(e,s),r}async setDefault(e,t){let n=await this.load(e),o=ke(n,t);if(!o)throw new m(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 he(bm(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 fn("*/_db/config.json",this.paths.root)){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=[];try{for await(let t of fn("*/_db/_connections.json",this.paths.root)){let n=t.split(/[\\/]/)[0];if(!n.startsWith("_"))e.push(n)}}catch(t){if(t.code!=="ENOENT")throw t}return e.sort()}async resolveProject(e){let t=e.db?Ra(e.db):void 0,n=t?.project??e.project??process.env.FUI_PROJECT??e.contextProject;if(n)return os(n);let o=await this.listProjects();if(t){let r=[];for(let i of o)if(ke(await this.load(i),t.alias))r.push(i);if(r.length===1)return r[0];if(r.length===0)throw new m(`no project has connection "${t.alias}"; run: fui db list`);throw new m(`connection "${t.alias}" exists in several projects; use one of:
10
9
  `+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 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>:
10
+ `))}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 m("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new m(`several projects have connections; pass -p <project> or --db <project>/<alias>:
12
11
  `+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 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(`
12
+ `))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new m(`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 m(`connection ${t}/${s} has no dbToken; re-add it`);let{engine:a}=so(i.dbToken);if(a==="mssql"&&!i.apiDomain)throw new m(`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}=Ra(n.db),i=ke(t,r);if(!i)throw new m(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 m(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 m(this.menu(e,t,`project "${e}" has ${o.length} connections; this command needs --db`));let s=t.default?ke(t,t.default):null;if(s)return{alias:s,note:`using default connection "${s}" (${o.length} connections)`};throw new m(this.menu(e,t,`project "${e}" has several connections and no default`))}async knownObjectNames(e,t){let n=new Set,o=this.paths.schemaFile(e,t);if(await Ke(o))try{let s=await Jt(o);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 fn("*.sql",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(`
13
+ `)}}var km,vm;var it=R(()=>{q();Tt();be();Pt();te();km=/^[A-Za-z0-9_-]+$/,vm=/^[A-Za-z0-9_.-]+$/});import{join as Xt}from"node:path";class io{root;constructor(e){this.root=e}projectDir(e){return Xt(this.root,e)}dbDir(e){return Xt(this.projectDir(e),"_db")}connectionsFile(e){return Xt(this.dbDir(e),"_connections.json")}connDir(e,t){return Xt(this.dbDir(e),t)}schemaFile(e,t){return Xt(this.connDir(e,t),"schema.json")}spFile(e,t,n){return Xt(this.connDir(e,t),`${n}.sql`)}historyRoot(){return Xt(this.root,"_history")}}var rs=()=>{};async function Pa(e,t){let n=await ma(f(e,"workdir")),o=new io(n.path),s=e.json===!0,r={json:s,header:e["no-header"]!==!0,filter:f(e,"filter"),where:Sm(e.where),fields:f(e,"fields")?.split(",").map((c)=>c.trim()).filter(Boolean)},i=new oo,a=new Zr(o,i);return{paths:o,session:i,targets:a,workdir:n,store:new yt(o),values:e,positionals:t,json:s,render:r}}function Sm(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 m(`--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 Q(e){return{store:e.store,session:e.session,project:f(e.values,"project"),contextProject:await e.targets.contextProjectId()}}function F(e){let t;return{paths:e.paths,session:e.session,targets:e.targets,api:()=>t??=Bo()}}var Ta;var Re=R(()=>{yn();Gr();q();Gt();es();it();rs();Ta={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"}}});function $m(e){if(e===null||e===void 0)return"NULL";if(e instanceof Date)return e.toISOString();if(e instanceof Uint8Array)return`0x${Buffer.from(e).toString("hex")}`;return(typeof e==="object"?JSON.stringify(e):String(e)).replace(/\\/g,"\\\\").replace(/\t/g,"\\t").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function _a(e,t){let n=[...new Set(e.flatMap((s)=>Object.keys(s)))],o=e.map((s)=>n.map((r)=>$m(s[r])));return t?[n,...o]:o}function jm(e,t=!0){if(e.length===0)return"";return`${_a(e,t).map((n)=>n.join("\t")).join(`
15
14
  `)}
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(`
15
+ `}function Im(e,t=!0){if(e.length===0)return"";let n=_a(e,t),o=n[0].map((r,i)=>Math.max(...n.map((a)=>Br(a[i]))));return`${n.map((r)=>r.map((i,a)=>a===r.length-1?i:i+" ".repeat(o[a]-Br(i))).join(" ")).join(`
17
16
  `)}
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=`
17
+ `}function ss(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
18
+ `}function Ea(e){return e===null||e===void 0?"":typeof e==="object"?JSON.stringify(e):String(e)}function Cm(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 m(`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)=>Ea(l).toLowerCase().includes(a)))}for(let[a,c]of n??[]){let l=r(a);if(l)i=i.filter((u)=>Ea(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 Z(e,t){let{json:n,header:o,isTty:s=process.stdout.isTTY===!0}=t,r=Cm(e,t);if(n)return ss(r);return s?Im(r,o):jm(r,o)}function Aa(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}var at=R(()=>{q();te()});function Et(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function Oe(e){let t=Et(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function Rm(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 Na(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}: ${Rm(r)||s.statusText}`);try{return JSON.parse(r)}catch{throw Error(`tAPI returned non-JSON: ${r.slice(0,400)}`)}}class kt{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await Na(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return Na(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function is(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()}}import{readdir as Tm}from"node:fs/promises";import{join as Oa}from"node:path";async function ao(e,t,n,o){if(!t)throw new m('no userToken: run fui db user-token first (a developer token with access to the permission database, alias "acc")');let s=await fetch(`${Oe(e)}/acc/${n}`,{method:"POST",headers:{"Content-Type":"application/json",authorization:`Bearer ${t}`},body:JSON.stringify(o),signal:AbortSignal.timeout(Pm)}),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 Ma(e){try{return(await Tm(e,{withFileTypes:!0})).filter((t)=>t.isDirectory()&&!t.name.startsWith("_")).map((t)=>t.name)}catch{return[]}}async function as(e){let t=[];for(let n of await Ma(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?Et(r.apiDomain):""})}return t}function cs(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 Da(e){return e.map((t)=>`${t.projectId}/${t.alias}`).join(", ")}function Fa(e){return e.map((t)=>`${t.projectId}/${t.alias} (${t.apiDomain||"?"})`).join(", ")}function La(e){return gn("sha256",e).slice(-8)}function Ua(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 m(`${n} must be "<project>/<alias>" or "<alias>" (a connection of this project); got "${t}"`)}async function Ha(e,t,n){let{projectId:o,alias:s}=Ua(t,n,"--from"),r=await e.load(o),i=ke(r,s);if(!i)throw new m(`no connection "${s}" in project "${o}"; see: fui db list -p ${o}`);let a=r.connections[i].userToken;if(!a)throw new m(`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 Em(e,t){let n=await he(Oa(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 Et(i)}async function ls(e,t,n){if(n){let o=ke(await e.load(t),n);if(o)return e.resolve({project:t,db:o})}return e.resolve({project:t})}async function zo(e,t,n){let o=n.apiDomain?Et(n.apiDomain):void 0;if(o&&n.userToken)return{apiDomain:o,userToken:n.userToken,note:null};try{let{alias:i,config:a}=await ls(e,t,n.apiName),c=o??(a.apiDomain?Et(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 Em(e.paths,t);if(!s)throw new m(`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=cs((await as(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 ${Da(i.sources)} (same apiDomain ${s})`}}if(r.length===0)throw new m(`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 m([`${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)=>` ${Da(i.sources)}`),"pass --user-token-env VAR with the token of the source you mean"].join(`
19
+ `))}async function co(e){return(await ao(e.apiDomain,e.userToken,"SM_Modules_ManagerSelect",{})).data??[]}function us(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 xa(e){let t;try{t=ro(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 qa(e,t,n,o){let s=async(a,c)=>{let l=await e.load(a),u=ke(l,c);if(!u)throw new m(`no connection "${c}" in project "${a}"; see: fui db list -p ${a}`);let d=xa(l.connections[u].dbToken??"");if(!d)throw new m(`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}=Ua(t,n,"--borrow-from");return s(a,c)}let r=[];for(let a of await Ma(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=xa(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 m(`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 m([`${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(`
20
+ `))}async function Va(e,t){let n,o;try{let i=ke(await e.load(t),"acc");if(!i)throw Error("no acc alias");let a=await e.resolve({project:t,db:i});n=Oe(a.config.apiDomain),o=a.dbToken}catch{throw new m(`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 kt(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 ds(e,t,n,o){try{let s=Oa(e.dbDir(t),"rights.json"),r={};try{r=JSON.parse(await he(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 O(s,JSON.stringify(r,null,2))}catch{}}var Pm=20000;var Wa=R(()=>{q();be();it();Tt();te()});class ps{engine;url;sql;nodeSqlite;constructor(e,t){this.engine=e;this.url=t}async client(){if(!Bt)throw Error(`${this.engine} connections need Bun (https://bun.sh); fui runs on Bun automatically once it is installed`);return this.sql??=new(await import("bun")).SQL(this.url),this.sql}async sqlite(){if(Bt||this.engine!=="sqlite")return null;let{DatabaseSync:e}=await import("node:sqlite");return this.nodeSqlite??=new e(this.url.replace(/^(?:sqlite|file):(?:\/\/)?/i,"")),this.nodeSqlite}async query(e){let t=await this.sqlite();if(t)return t.prepare(e).all().map((o)=>({...o}));return[...await(await this.client()).unsafe(e)]}async exec(e){let t=await this.sqlite();if(t){let o=()=>Number(t.prepare("SELECT total_changes() AS n").all()[0]?.n??0),s=o();return t.exec(e),{count:o()-s}}return{count:(await(await this.client()).unsafe(e)).count??null}}async close(){this.nodeSqlite?.close(),await this.sql?.close()}}var Ba=R(()=>{te()});function ms(e){if(e.engine==="mssql")return new kt(Oe(e.config.apiDomain),e.dbToken);return new ps(e.engine,ro(e.dbToken))}async function Ee(e,t){let n=ms(e);try{return await t(n)}finally{await n.close()}}var hs=R(()=>{Ba();Tt()});function za(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=_m.has(s)?t/2:t;return r>0?`${e}(${r})`:e}function Am(e){let t=[String(e.ColumnName),za(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 fs(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function Xa(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(Am(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=za(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 Yt(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 Ya(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}function ws(e){if(/^CREATE TABLE/i.test(e))return"Table";if(/^CREATE VIEW/i.test(e))return"View";return null}function ys(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+)/i);return t?fs(t[2],t[1].toUpperCase()==="FUNCTION"):null}function Qa(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 bs(e){let t=e.match(/^CREATE (TABLE|VIEW) (\S+) \((.*)\);?$/s),n=t?Qa(t[3]):null;if(t&&n.every((s)=>Nm.test(s.trim())))return{name:t[2],objectType:t[1]==="VIEW"?"View":"Table",count:n.length};let o=Ya(e);if(!o)throw Error(`Cannot parse table DDL: ${e.slice(0,120)}`);return{name:o,objectType:ws(e)??"Table",count:0,opaque:!0}}function Xo(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+) \((.*)\);?$/s),n=t?Qa(t[3]):null;if(t&&n.every((r)=>Ja.test(r.trim())))return{name:t[2],objectType:fs(t[2],t[1]==="FUNCTION"),count:n.length,parameters:n.map((r)=>Ja.exec(r.trim())[1])};let o=Ya(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:ys(e)??fs(o,s),count:0,parameters:[],opaque:!0}}var Ka,Ga,_m,gs,Nm,Ja;var lo=R(()=>{Ka=`
22
21
  SELECT
23
22
  o.type AS ObjectTypeCode, o.name AS ObjectName, c.name AS ColumnName, t.name AS DataType,
24
23
  c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity,
@@ -42,7 +41,7 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('U','V') AND s.name NOT IN ('sys','INFO
42
41
  AND o.name NOT LIKE 'sys%' AND o.name NOT LIKE 'dt[_]%'
43
42
  AND o.name NOT LIKE 'queue[_]%' AND o.name NOT LIKE 'filestream[_]%' AND o.name NOT LIKE 'cdc.%'
44
43
  ORDER BY ObjectTypeCode, ObjectName
45
- `.trim(),Si=`
44
+ `.trim(),Ga=`
46
45
  SELECT
47
46
  o.type AS ObjectTypeCode, o.name AS ObjectName,
48
47
  p.name AS ParameterName, pt.name AS ParameterType,
@@ -57,76 +56,62 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('P','FN','TF','IF') AND s.name NOT IN (
57
56
  AND o.name NOT IN ('fn_diagramobjects','sp_alterdiagram','sp_creatediagram','sp_dropdiagram',
58
57
  'sp_helpdiagramdefinition','sp_helpdiagrams','sp_renamediagram','sp_upgraddiagrams')
59
58
  ORDER BY ObjectTypeCode, ObjectName
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,`
59
+ `.trim(),_m=new Set(["nvarchar","nchar","ntext"]);gs=["Table","View","API","API File","Procedure","Function"];Nm=/^(\S+)\s+(\S+)\s+(NULL|NOT NULL)(\s+PRIMARY KEY)?(\s+IDENTITY)?(?:\s+REFERENCES\s+(\S+)\((\S+)\))?$/,Ja=/^@(\S+)\s+(\S+)$/});import{mkdir as Dm,readdir as xm}from"node:fs/promises";import{join as Sn,sep as Om}from"node:path";function Te(e){return e.split(Om).join("/")}function Qt(e){return`sha1:${gn("sha1",e.replace(/\r\n/g,`
60
+ `))}`}async function Yo(e){try{let t=JSON.parse(await B(Sn(e,$n)));return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async function Za(e,t){await Dm(e,{recursive:!0}),await O(Sn(e,$n),JSON.stringify(t,null,2))}async function Me(e,t){if(t.length===0)return;let n=await Yo(e),o=!1;for(let s of t){let r=Te(s);try{n[r]=Qt(await B(Sn(e,r))),o=!0}catch{if(r in n)delete n[r],o=!0}}if(o)await Za(e,n)}async function ec(e,t){if(t.length===0)return;let n=await Yo(e),o=!1;for(let s of t){let r=Te(s);if(r in n)delete n[r],o=!0}if(o)await Za(e,n)}async function Fm(e){try{return(await xm(Sn(e,"components"),{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.toLowerCase().endsWith(".vue")).map((n)=>Te(Sn("components",n.name)))}catch{return[]}}async function uo(e){let t=await Yo(e),n=[];for(let o of[...Mm,...await Fm(e)]){let s;try{s=await B(Sn(e,o))}catch{continue}let r=t[o];if(r===void 0)n.push({relPath:o,reason:"new"});else if(r!==Qt(s))n.push({relPath:o,reason:"changed"})}return n}async function jn(e,t,n){let o=(await Yo(e))[Te(t)];return o!==void 0&&Qt(n)===o}function Qo(e){if(e.startsWith("components/"))return"fui component update";return Lm[e]??"fui module publish json"}function ks(e){if(e.length===0)return null;return`unpublished local files: ${e.map((n)=>`${n.relPath} (${Qo(n.relPath)})`).join(", ")}`}var $n="_publishState.json",Mm,Lm;var _t=R(()=>{be();te();Mm=["module.json","header.html","body.html","script.js","style.css","project.json"];Lm={"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"}});import{mkdir as Um,stat as Hm}from"node:fs/promises";import{basename as qm,dirname as Vm,join as vs,resolve as tc,sep as Wm}from"node:path";function nc(e){return Jm.has(qm(e))}function Km(e,t){return e.replace(/\r\n/g,`
62
61
  `)===t.replace(/\r\n/g,`
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}
62
+ `)}function Gm(e,t,n){let o=tc(e),s=tc(t);if(s!==o&&!s.startsWith(o+Wm))throw Error(`server file path escapes the target folder: ${JSON.stringify(n)} -> ${s} (must be inside ${o}); nothing written`)}async function Fe(e,t,n){let o=[],s=[],r=[],i=Ko(),a=[];for(let c of Object.keys(n)){let l=Te(c);Gm(t,vs(t,l),l)}for(let[c,l]of Object.entries(n)){let u=Te(c),d=vs(t,u),p=await he(d);if(p!==null&&Km(p,l))s.push(u);else if(p!==null&&!nc(u)){let h=await Se(e,d,{stamp:i});if(h)r.push({relPath:u,historyPath:zt(e,h)})}if(await Um(Vm(d),{recursive:!0}),await O(d,l),o.push(d),!nc(u))a.push(u)}if(a.length>0)await Me(t,a).catch(()=>{return});return{written:o,unchanged:s,archived:r}}function Ss(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(", ")}function $s(e){if(e.length===0)return[];let t=e.slice(0,zm).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 Xm(e,t){try{return(await Hm(vs(e,t))).mtime}catch{return null}}async function Zt(e,t,n,o,s=Date.now()){let r=await Xm(e,o);if(!r)return null;let i=s-r.getTime();if(i<Bm)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 Bm=172800000,Jm,zm=8;var In=R(()=>{be();Pt();_t();te();Jm=new Set(["_moduleInfo.json","_projectInfo.json","_modules.json","_components.json","_imports.json",$n])});import{mkdir as Ym,readdir as Qm,unlink as Zm}from"node:fs/promises";import{dirname as eh,join as th}from"node:path";async function oh(e){try{return JSON.parse(await B(e))}catch{return null}}async function js(e,t,n){return oh(e.schemaFile(t,n))}async function Is(e,t,n){let o=await js(e,t,n);if(!o)return null;if(!Yt(o))throw new Zo(`_db/${n}/schema.json of project "${t}" is not in the current compact format`);try{return{fetchedAt:o.fetchedAt,tables:o.tables.map(bs),procedures:o.procedures.map(Xo)}}catch(s){throw new Zo(`_db/${n}/schema.json of project "${t}": ${s.message}`)}}async function rc(e){try{return(await Qm(e)).filter((t)=>t.toLowerCase().endsWith(".sql"))}catch{return null}}async function rh(e,t){try{let n=await B(t);return await Se(e,t,{content:n,suffix:"deleted"}),await Zm(t),!0}catch{return!1}}async function sh(e,t,n,o,s,r){let i=[],a=[],c=await rc(e.connDir(t,n));if(!c)return{removed:i,localOnly:a};let l=(p)=>new Set([...p].map((h)=>h.toLowerCase())),u=l(o),d=l(s);for(let p of c){let h=p.slice(0,-4).toLowerCase();if(d.has(h))continue;if(u.has(h)||r){if(await rh(e,th(e.connDir(t,n),p)))i.push(p)}else a.push(p)}return{removed:i,localOnly:a}}async function ih(e,t,n,o){let s=await rc(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(nh.test(p))r.push(p);else i.push(d)}let a=new Map;for(let d=0;d<r.length;d+=oc){let p=r.slice(d,d+oc).map((w)=>`N'${w}'`).join(","),h=await t.query(`SELECT o.name AS name, ISNULL(OBJECT_DEFINITION(o.object_id), '') AS sqlText
63
+ FROM sys.objects o WHERE o.name IN (${p})`);for(let w of h){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 Fe(e,e.dbDir(n),c),skipped:i,missing:l}}async function er(e,t,n,o,s=!1){let r=null;try{r=await Is(e,n,o)}catch{}let i=new Set((r?.procedures??[]).map((w)=>w.name)),[a,c]=await Promise.all([t.query(Ka),t.query(Ga)]),l=Xa(a,c),u=e.schemaFile(n,o);await Ym(eh(u),{recursive:!0}),await O(u,JSON.stringify(l,null,2));let d=new Set(l.procedures.map((w)=>Xo(w).name)),p=await sh(e,n,o,i,d,s),h=await ih(e,t,n,o);return{schemaFile:u,schema:l,refresh:h.sync,skipped:h.skipped,missingOnServer:h.missing,removed:p.removed,localOnly:p.localOnly}}async function tr(e,t,n,o){try{return await Is(e,t,n)}catch(s){if(!(s instanceof Zo))throw s;return await o(),Is(e,t,n)}}async function sc(e,t,n,o){let s=await js(e,t,n);if(!s)return null;if(Yt(s))return s;await o();let r=await js(e,t,n);if(!Yt(r))throw Error("refetched the schema but it is still not in the compact format");return r}function ic(e){let t={Table:0,View:0,API:0,"API File":0,Procedure:0,Function:0};for(let n of e.tables)t[bs(n).objectType]+=1;for(let n of e.procedures)t[Xo(n).objectType]+=1;return t}var nh,oc=50,Zo;var ac=R(()=>{be();lo();Pt();In();te();nh=/^[A-Za-z0-9_]+$/;Zo=class Zo extends Error{name="SchemaFormatError"}});function lc(e){return`<redacted ${e.length} chars>`}function uc(e){if(cc.some((t)=>t.test(e)))return!0;if(e.length<40||!/^[A-Za-z0-9+/=]+$/.test(e))return!1;try{return cc.some((t)=>t.test(atob(e)))}catch{return!1}}function Cn(e){if(Array.isArray(e))return e.map((t)=>Cn(t));if(e&&typeof e==="object")return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n==="string"&&(ah.test(t)||uc(n))?lc(n):Cn(n)]));if(typeof e==="string"&&uc(e))return lc(e);return e}var ah,cc;var Cs=R(()=>{ah=/token|passw|pwd|secret|api[-_]?key|credential|authorization/i,cc=[/\b(?:PWD|Password)\s*=\s*[^;]+/i,/^Bearer\s+\S+/i,/:\/\/[^/\s:@]+:[^@\s]+@/]});function Ge(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]!==`
64
+ `)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 en(e){return e.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ").replace(/N?'(?:[^']|'')*'/gi,"''")}function Rs(e){return Ge(e,{strings:!1})}function Ts(e){let t=Ge(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 po(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 tn(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 Ps(e){let t=Ge(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 dc(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(Ge(e))}function pc(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...Ge(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 Es(e){let t=Ge(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=po(a(t.slice(o,r))),l=po(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 ch(e){return`N'${e.replace(/'/g,"''")}'`}function mo(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function mc(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(${ch(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var _s=()=>{};function dh(e){let t=Rs(e),n=null;for(let o of Ts(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=lh.exec(s)?.[1]?.toUpperCase();if(!i||!uh.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 hc(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 ph(e){let t=Rs(e);for(let{start:n,end:o}of Ts(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)&&!hc(s,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:r};if(/\bUPDATE\b/i.test(s)&&/\bSET\b/i.test(s)&&!hc(s,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:r}}return null}function fc(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")}function Rn(e){let t=new Set;return fc(en(e),t),fc(Ge(e),t),mh.filter((n)=>t.has(n))}function As(e,t,n){let o=dh(e);if(o&&(o.severity==="block"||!t))return o;if(n==="nonquery"){let r=ph(e);if(r)return r}let s=Rn(e);if(s.length>0&&!t)return{severity:"confirm",reason:`writes real data (${s.join(", ")})`,statement:e.trim()};return null}var lh,uh,mh;var Ns=R(()=>{lh=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,uh=new Set(["ADD","ALTER","DROP"]);mh=["INSERT","UPDATE","DELETE","MERGE","TRUNCATE"]});function hh(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)}…`:t}function Ds(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.slice(y).match(/^[A-Za-z]+/)?.[0]?.toUpperCase();if(_&&["INSERT","UPDATE","DELETE","MERGE","SELECT"].includes(_)){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 h=/(^|,)\s*\*\s*($|,)/.test(p)||p==="*",w=null;if(!h&&p)w=po(p,",").map(hh).filter(Boolean);t.push({columns:w,hasStar:h,isInto:l})}return t}function gc(e){return/RAISERROR\s*\([^)]*\)[^;]*;?\s*(\r?\n)?\s*RETURN\b/i.test(e)}function xs(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 wc(e){let t=Ds(en(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]=xs(o);return{data:[n,{...n}]}}function fh(e){let t=en(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 yc(e){if(e===null)return"unknown";if(Rn(e).length>0)return"unsafe-write";if(fh(e).length>0)return"opaque";return"safe-read"}var Os=R(()=>{Ns()});import{mkdir as gh,rename as wh,unlink as yh}from"node:fs/promises";import{dirname as bh}from"node:path";function At(e){if(!kh.test(e))throw new m(`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 vt(e,t){if(e.engine!=="mssql")throw new m(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}function vc(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 Pe(`${e.reason}
66
65
  ${n}
67
66
  ---
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(`
67
+ ${t}`,e.severity==="block"?"hard":"soft")}function Sc(e,t){let n=ic(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 ho(e,t,n){vn(n.alias);let{engine:o}=so(n.dbToken);if(o==="mssql"&&!n.apiDomain)throw new m("MSSQL connections need --api-domain <host>");let s=n.apiDomain?Et(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 Ee(r,(w)=>w.query("SELECT 1 AS ok"));let i=await e.store.load(t),a=ke(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),h=[`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 Ee(w,(v)=>er(e.store.paths,v,t,d));h.push(...Sc(d,y))}return{notes:h}}async function $c(e,t,n){let o=await e.store.resolve(_e(e,{db:t}));vt(o,"schema pull");let s=await Ee(o,(r)=>er(e.store.paths,r,o.projectId,o.alias,n));return{notes:[...o.note?[o.note]:[],`ok ${o.projectId}/${o.alias} schema refreshed`,...Sc(o.alias,s)]}}function nr(e,t){return()=>Ee(t,(n)=>er(e.store.paths,n,t.projectId,t.alias))}async function jc(e,t){let n=await e.store.resolve(_e(e,{db:t.db}));vt(n,"schema");let{projectId:o,alias:s}=n,r=await sc(e.store.paths,o,s,nr(e,n));if(!r)throw new m(`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||ws(y)===c)&&a(y)):[],p=u?r.procedures.filter((y)=>(!c||ys(y)===c)&&a(y)):[],h=await Zt(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]:[],...h?[h]:[],`${o}/${s} fetchedAt=${r.fetchedAt} tables/views=${d.length} procs/functions=${p.length}`],data:w.length>0?`${w.join(`
69
68
  `)}
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
- `+m.map((w)=>` - ${w}`).join(`
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.
69
+ `:""}}async function Ic(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 m(`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 h=await e.store.resolve({project:n,db:u});d=await tr(e.store.paths,n,u,nr(e,h))}catch(h){l.push(`${u}: schema unreadable: ${h.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 h of d.tables)if((!a||h.objectType===a)&&i(h.name))c.push({db:p,name:h.name,kind:h.objectType,count:h.count})}if(!a||a!=="Table"&&a!=="View"){for(let h of d.procedures)if((!a||h.objectType===a)&&i(h.name))c.push({db:p,name:h.name,kind:h.objectType,count:h.count})}}return{rows:c,notes:l}}async function Cc(e,t,n){At(t);let o=await e.store.resolve(_e(e,{db:n,objectName:t}));vt(o,"sp get");let s=await Ee(o,(l)=>l.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Ms(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 Fe(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 Rc(e,t,n,o){At(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 gh(bh(r),{recursive:!0}),await O(r,n),{path:r,alias:s.alias,notes:s.note?[s.note]:[]}}function vh(e){return/(CREATE\s+PROCEDURE|CREATE\s+PROC|CREATE\s+FUNCTION)/im.test(e)}async function Fs(e,t){try{let n=await e.query(mc(t)),o=Number(n[0]?.granted??0);return Number.isFinite(o)?o>0:null}catch{return null}}async function Sh(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 $h(e,t){if(!bn()||await e.wasSkillLoaded())return;throw new Pe(`[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 Tc(e,t,n){await $h(e.session,"sp deploy"),At(t);let o=await e.store.resolve(_e(e,{db:n,objectName:t}));vt(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 m(`file not found: ${a}; run fui sp save or fui sp get first`)}if(!c.trim())throw new m(`file is empty: ${a}`);let l=Ps(c)??t,u=tn(l);if(u){let h=Es(c);if(h.length>0)throw new Pe(`tAPI does not support parameter default values; remove "= ..." and save before deploying:
70
+ `+h.map((w)=>` - ${w}`).join(`
71
+ `),"hard","error")}let d=`${s}/${r}`,p=[];return await Ee(o,async(h)=>{let w=null,y="";try{let b=await h.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Ms(l)}')), '') AS def`);y=String(b[0]?.def??"").trim(),w=y.length>0}catch{}let v="";if(y)try{v=`rollback copy: ${await Se(i,a,{content:y})}`}catch(b){throw Error(`could not save the rollback copy (${b.message}); not deploying`)}let _=pc(c,w);if(await h.exec(_.sql),o.note)p.push(o.note);if(p.push(`ok deployed ${t} to ${d}`,`file: ${a}`),_.changed)p.push(`ran as ${_.to} because "${l}" ${_.to==="ALTER"?"exists":"does not exist"}; file unchanged`);if(v)p.push(v);let I=w===null?vh(c):!w,g=dc(c);if(I&&!g){let b=mo(l);try{await h.exec(b),p.push(`GRANT EXECUTE ON [dbo].[${l}] TO [public]: applied`)}catch(T){p.push(`deploy OK but grant FAILED (${T.message}); do not redeploy, run: fui exec "${b}" --db ${d}`)}}if(!g){let b=await Fs(h,l);if(b===!1)p.push(`[public] lacks EXECUTE on "${l}"; tAPI calls will fail. run: fui exec "${mo(l)}" --db ${d}`);else if(b===!0&&!I)p.push(`[public] has EXECUTE on "${l}"`)}if(u)try{let b=await is(Oe(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 Sh(e.session),...p]}}async function Pc(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}));vt(r,"sp help");let i=null;try{i=await tr(e.store.paths,r.projectId,r.alias,nr(e,r))}catch{}let a=t,c=i?.procedures.find((h)=>s.some((w)=>w.toLowerCase()===h.name.toLowerCase()));if(c)a=c.name;let l=tn(a);if(!l)throw new m(`"${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 is(Oe(r.config.apiDomain),r.alias,l.isAuth,l.fnName,r.config.userToken)}catch(h){return u.push(`/help for "${a}" failed (${h.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 h=JSON.stringify(Cn(JSON.parse(d.text)));p=o>0&&h.length>o?`${h.slice(0,o)}…(truncated)`:h}catch{p=o>0?d.text.slice(0,o):d.text}return{notes:u,data:`${p}
72
+ `}}async function Ec(e,t,n){let o=As(t,n.confirmWrite,"query");if(o)throw vc(o);let s=await e.store.resolve(_e(e,{db:n.db,write:!n.db&&Rn(t).length>0}));return{rows:await Ee(s,(i)=>i.query(t)),connection:s}}function jh(e){if(!/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\b/i.test(Ge(e)))return null;return Ps(e)}async function _c(e,t,n){let o=As(t,n.confirmWrite,"nonquery");if(o)throw vc(o);let s=await e.store.resolve(_e(e,{db:n.db,write:!n.db})),r=ms(s),i=[];try{let a=await r.exec(t),c=s.engine==="mssql"?jh(t):null;if(c){let l=`${s.projectId}/${s.alias}`,u=await Fs(r,c);if(u===!1)i.push(`[public] lacks EXECUTE on "${c}"; tAPI calls will fail. run: fui exec "${mo(c)}" --db ${l} (fui sp save + sp deploy grants automatically)`);else if(u===!0)i.push(`[public] has EXECUTE on "${c}"`);if(tn(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 Ac(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}));vt(s,"sp verify");let{projectId:r,alias:i,config:a}=s,c=e.store.paths,l=null;try{l=await tr(c,r,i,nr(e,s))}catch{}let u=n,d=l?.procedures.find((W)=>o.some((de)=>de.toLowerCase()===W.name.toLowerCase()));if(d)u=d.name;let p=tn(u);if(!p)throw new m(`"${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:h,isAuth:w,fnName:y}=p,v=/^spAPIFILE_UPLOAD_/i.test(h),_=/^spAPIFILE_/i.test(h)&&!v,I=await Ih(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 Zt(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)",T=v?"API File Upload":_?"API File Get":"API",C=_||v?"GET/POST":"POST",S=[...s.note?[s.note]:[],...g?[g]:[],`verify ${u} (from ${b})`,`kind: ${T}${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 W=[];for(let $e of d.parameters??[]){let Ze=$e.replace(/^@/,"");if(/^sys_/i.test(Ze))continue;if(/^url\d+_/i.test(Ze)){N++;continue}W.push(Ze)}if(S.push(`route params (@url*_): ${N}`,`body params: ${W.length?W.join(", "):"(none)"}`),t.params){let $e=new Set(Object.keys(t.params).map((Ne)=>Ne.toLowerCase())),Ze=W.filter((Ne)=>!$e.has(Ne.toLowerCase())),mt=Object.keys(t.params).filter((Ne)=>!/^@?sys_/i.test(Ne)&&!W.some((qt)=>qt.toLowerCase()===Ne.replace(/^@/,"").toLowerCase()));if(Ze.length)S.push(` warning: missing from --params: ${Ze.join(", ")}`);if(mt.length)S.push(` warning: not declared by the SP: ${mt.join(", ")}`)}let de=(t.urlParams??[]).length;if(t.urlParams&&de!==N)S.push(` warning: the SP declares ${N} route params, --url-param gave ${de}`)}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 W=await Ee(s,(de)=>Fs(de,u));S.push(W===!0?`[public] has EXECUTE on "${u}"`:W===!1?`[public] LACKS EXECUTE on "${u}"; the endpoint fails with a permission error. fix: fui exec "${mo(u)}" --db ${r}/${i}`:"warning: could not read sys.database_permissions; EXECUTE grant unknown")}let x=Es(I.sql);if(x.length)S.push(`warning: tAPI does not support default values; sp deploy will block: ${x.join("; ")}`);S.push("","## response shape (static, best effort)");let A=Ds(en(I.sql)),Y=A.filter((W)=>!W.isInto),D=A.length-Y.length;if(D>0)S.push(`(${D} SELECT ... INTO #temp/@table not returned to the client; not counted)`);if(Y.length===0)S.push("warning: no result-returning SELECT; the SP may only write (check any OUT binding in module.json)");else if(Y.length===1){let W=Y[0];if(S.push("1 SELECT: data is an array (one resultset)"),W.hasStar)S.push("warning: SELECT *: columns unknown from the body; check fui schema --search <table>");else if(W.columns)S.push(`columns: ${W.columns.join(", ")}`)}else S.push(`${Y.length} result SELECTs: data may be array-of-arrays (data[0], data[1], ...)`),Y.forEach((W,de)=>{S.push(` data[${de}]: ${W.hasStar?"SELECT * (columns unknown)":W.columns?W.columns.join(", "):"(unknown)"}`)});S.push(gc(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 ge=Rn(I.sql);if(ge.length===0)S.push("read API: no INSERT/UPDATE+SET/DELETE/MERGE in the body");else S.push(`WRITE API: ${ge.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 Ae=Y[0];if(Ae&&!Ae.hasStar&&Ae.columns?.length){let W={};for(let de of Ae.columns)W[de]=xs(de);S.push(JSON.stringify({apiMocks:{[y]:{data:[W,W]}}},null,2))}else S.push("(not generated: SELECT * or unknown columns; write apiMocks from fui schema --search <table>)");return{notes:S}}async function Ih(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 Ee(t,(r)=>r.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${Ms(n)}')),'') AS sqlText`)),s=String(o[0]?.sqlText??"");if(s.trim())return{sql:s,source:"live"}}catch{}return null}async function Nc(e,t,n,o){At(t);let s=await e.store.resolve(_e(e,{db:o,objectName:t}));vt(s,"sp delete");let{projectId:r,alias:i}=s,a=n==="Function"?`DROP FUNCTION [dbo].[${t}]`:`DROP PROCEDURE [dbo].[${t}]`;await Ee(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 Se(e.store.paths,l,{content:u,suffix:"deleted"});await yh(l),c.push(`local file archived: ${d?zt(e.store.paths,d):""} (removed from _db/${i}/)`)}catch{}return{notes:c}}async function Dc(e,t,n,o){At(t),At(n);let s=await e.store.resolve(_e(e,{db:o,objectName:t}));vt(s,"sp rename");let{projectId:r,alias:i}=s;await Ee(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 wh(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 xc(e,t){let n=await e.store.resolve(_e(e,{db:t.db,write:!t.db}));vt(n,"db token-rebuild");let{projectId:o,alias:s,config:r}=n;if(!r.sqlServer||!r.database)throw new m(`connection ${o}/${s} has no sqlServer/database saved; re-add it: fui db add ${s} -p ${o} --sql-server <host> --database <name> ...`);let i=kn(r.sqlServer,r.database,t.uid,t.password);return await new kt(Oe(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)}`]}}var kh,bc=200,kc=2000,Ms=(e)=>e.replace(/'/g,"''");var nn=R(()=>{q();hs();ac();Tt();be();Cs();lo();Gt();_s();Os();Ns();it();Pt();In();te();kh=/^[A-Za-z0-9_]+$/});async function fo(e){return e.store.resolveProject({project:e.project,contextProject:e.contextProject})}async function Oc(e,t){let n=await fo(e),o=e.store.paths.connectionsFile(n);if(t.tokenSource){let{userToken:r,source:i}=await Ha(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 as(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=cs(r);return{notes:[`${i.length} distinct userToken(s) across ${r.length} connection(s); pick one (same apiDomain preferred):`,...i.map((a)=>`fingerprint ${La(a.token)} sources: ${Fa(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 Mc(e,t){let n=await fo(e),o=await zo(e.store,n,t),s=await co(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:us(i),notes:a,total:s.length}}async function Fc(e,t){vn(t.apiName);let n=await fo(e),o=await zo(e.store,n,{...t}),s=(await co(o)).find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s)throw new m(`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 Va(e.store,n);await ao(o.apiDomain,o.userToken,"SM_Modules_Update",{ModuleID:r,ModuleName:t.moduleName,APIName:t.apiName,Enable:1,UpdateConnection:0}),await ao(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 co(o).catch(()=>null))?.find((l)=>String(l.APIName??"").toLowerCase()===t.apiName.toLowerCase()),c=a?us([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 Lc(e,t){let n=await fo(e);vn(t.apiName);let o=await zo(e.store,n,t),s=(await co(o)).filter((u)=>String(u.APIName??"").toLowerCase()===t.apiName.toLowerCase());if(s.length===0)throw new m(`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 m(`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 m(`the acc record of "${t.apiName}" has no SQLServer/DatabaseName; use fui db add with your own token`);let{creds:i,source:a}=await qa(e.store,n,t.borrowFrom,o.apiDomain),c=kn(r.SQLServer,r.DatabaseName,i.uid,i.password);try{await new kt(Oe(o.apiDomain),c).query("SELECT 1 AS ok")}catch(u){throw Error(`test connection to ${r.SQLServer}/${r.DatabaseName} failed; nothing saved.
74
73
  ${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 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(`
74
+ 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 ho(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 Uc(e,t){let n=await fo(e),o=await ls(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>ao(s,r,i,a),target:{ModuleID:0,APIName:t??o.alias}}}async function Hc(e,t,n){let{projectId:o,call:s,target:r}=await Uc(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await ds(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function qc(e,t,n){let{projectId:o,call:s,target:r}=await Uc(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 m(`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 ds(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 Vc=R(()=>{q();Wa();Tt();it();nn()});async function Hs(e){return{apiDomain:f(e.values,"api-domain"),userToken:await xe(f(e.values,"user-token"),f(e.values,"user-token-env"),"--user-token")}}function go(e){k(`${e.join(`
76
75
  `)}
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.4";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(`
76
+ `)}function Jc(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function Kc(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"}))}function Ls(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new m(`${t} must be ${o}`);return s}function Wc(e,t,n){if(e!==void 0&&e.length>t)throw new m(`${n} is at most ${t} characters`);return e}function Bc(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:Gc,async run(t){let{apiName:n,rows:o}=await Hc(await Q(t),e,f(t.values,"api-name"));if(P(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){P(`# none defined; create one: fui right ${e} new ...`);return}k(Z(e==="system"?Jc(o):Kc(o),t.render))}}}function Tn(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:{...Gc,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:Ls(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:Wc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:Ls(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:Ls(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:Wc(f(r,"name"),100,"--name")}}let c=await qc(await Q(s),a,f(r,"api-name"));go(c.notes),k(Z(e==="system"?Jc(c.rows):Kc(c.rows),s.render))}}}var Us,Ch,Rh,Th,Ph,Eh,Gc,zc;var Xc=R(()=>{Re();q();at();me();Vc();nn();Us={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};Ch={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 xe(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new m("pass --from or --token/--token-env, not both");let o=await Oc(await Q(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});go(o.notes)}},Rh={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 m("missing --uid");let n=await xe(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new m("missing --pwd - or --pwd-env VAR");go((await xc(await Q(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},Th={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"},...Us},async run(e){let t=await Mc(await Q(e),{...await Hs(e),search:f(e.values,"search")});for(let n of t.notes)P(`# ${n}`);if(t.rows.length>0)k(Z(t.rows,e.render))}},Ph={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"},...Us},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 m("need <apiName>, --module-name, --sql-server, --database and --username");let a=await xe(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new m("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 m("--module-id must be an integer");let u=await Fc(await Q(e),{...await Hs(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});go(u.notes)}},Eh={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"},...Us},async run(e){let t=e.positionals[0];if(!t)throw new m("missing <apiName>; see: fui db alias list");let n=await Lc(await Q(e),{...await Hs(e),apiName:t,borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});go(n.notes)}};Gc={"api-name":{type:"string"}};zc=[Ch,Rh,Th,Ph,Eh,Bc("system"),Tn("system","new"),Tn("system","update"),Tn("system","delete"),Bc("function"),Tn("function","new"),Tn("function","update"),Tn("function","delete")]});var fe="1.1.0";var Yc=()=>{};import{execFile as Ah,spawn as Nh}from"node:child_process";import{realpathSync as Dh}from"node:fs";import{join as Qc,sep as Nt}from"node:path";function Zc(e=process.env){return Qc(ft(e),"..","update.json")}async function wo(e=process.env){try{let t=await Jt(Zc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ct(e,t=process.env){await O(Zc(t),`${JSON.stringify(e,null,2)}
77
+ `)}function sr(e=rt()){let t;try{t=Dh(e)}catch{return null}for(let[n,o,s]of Fh){let r=`${n}${Mh}`,i=t.indexOf(r);if(i>=0){let a=t.slice(0,s?i+n.length:i);return{dir:t.slice(0,i+r.length-1),manager:o,prefix:a}}}return null}function ir(e=rt()){return sr(e)?.dir??null}function rr(e,t){return e==="bun"?`bun add -g ${or}@${t}`:`npm install -g ${or}@${t}`}function qs(e,t,n=process.env){return new Promise((o)=>{Ah(e,t,{env:n,shell:process.platform==="win32",maxBuffer:16777216,windowsHide:!0},(s,r,i)=>o({ok:!s,output:`${r}${i}`.trim()}))})}async function Vs(e=process.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await Rt(e))["auto-update"]!=="off"}catch{return!0}}async function Ws(e=process.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${or.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(Oh),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 Bs(e,t=fe){return ca(e,t)===1}async function Lh(e,t){let n=`${or}@${t}`;if(e.manager==="npm")return qs("npm",["install","-g","--prefix",e.prefix,n]);return qs(Bt?process.execPath:"bun",["add","-g",n],{...process.env,BUN_INSTALL:e.prefix})}async function Js(e=process.env){let t=await wo(e),n=new Date().toISOString(),o;try{o=await Ws(e)}catch(i){throw await ct({...t,checkedAt:n,error:i.message},e),i}if(!Bs(o))return await ct({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};let s=sr();if(!s)return await ct({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`${rr("bun",o)} (or ${rr("npm",o)})`};if(!Ho("node",e.PATH)){let i=`fui ${o} starts through Node.js; install Node.js 22+ first, then run: fui update`;return await ct({...t,checkedAt:n,latest:o,error:i},e),{latest:o,action:"failed",detail:i}}let r=await Lh(s,o);if(!r.ok)return await ct({...t,checkedAt:n,latest:o,error:r.output.slice(-500)},e),{latest:o,action:"failed",detail:r.output};return await ct({checkedAt:n,latest:o,updatedFrom:fe,updatedTo:o,noticeShown:!1,skillsVersion:t.skillsVersion,skillsAgents:t.skillsAgents},e),await Uh(e),{latest:o,action:"installed"}}async function Uh(e){let t=ir();if(!t)return;await qs(process.execPath,[Qc(t,"dist","fui.js"),"skill","install"],{...e,FUI_AUTO_SKILLS:"off",FUI_AUTO_UPDATE:"off"})}async function el(e=process.env){if(!ir()||!await Vs(e))return null;let t=await wo(e),n=null;if(t.updatedTo===fe&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${fe}`,t.noticeShown=!0,await ct(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<xh)return n;return await ct({...t,checkedAt:new Date().toISOString()},e),Nh(process.execPath,[rt(),"update","--background"],{stdio:"ignore",env:{...e,FUI_AUTO_UPDATE:"off"},detached:!0,windowsHide:!0}).unref(),n}var or="@fui-org/fui-cli",xh=3600000,Oh=5000,Mh,Fh;var yo=R(()=>{Yc();yn();te();Mh=`${Nt}node_modules${Nt}@fui-org${Nt}fui-cli${Nt}`,Fh=[[`${Nt}install${Nt}global`,"bun",!1],[`${Nt}lib`,"npm",!1],[`${Nt}npm`,"npm",!0]]});import{mkdir as Hh}from"node:fs/promises";function tl(e){let t=e[0];if(!t)throw new m(`missing <key>; keys: ${qo.join(", ")}`);if(!qo.includes(t))throw new m(`unknown key "${t}"; keys: ${qo.join(", ")}`);return t}async function qh(){let e=await Vs(),t=process.env.FUI_AUTO_UPDATE?.trim(),n=(await Rt())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?ft():"built-in (global bun installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var Vh,Wh,Bh,nl;var ol=R(()=>{q();me();yn();at();yo();Vh={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 Vo();P(`# config file: ${ft()}`),k(Z([{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 qh()],t))}},Wh={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=tl(e),n=e[1];if(!n)throw new m(`missing <value> for ${t}`);let o;if(t==="workdir")o=wn(n),await Hh(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new m("auto-update takes on or off");o=n}else if(t==="api-token")o=await xe(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await Rt();s[t]=o;let r=await Jr(s),i={workdir:process.env.FUI_MCP_WORKDIR&&!process.env.FUI_WORKDIR?"FUI_MCP_WORKDIR":"FUI_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(process.env[i])P(`# note: ${i} is set in this shell and overrides the config file`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
78
+ `)}},Bh={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=tl(e),n=await Rt();delete n[t];let o=await Jr(n);k(`unset ${t} in ${o}
79
+ `)}},nl=[Vh,Wh,Bh]});var Jh,Kh,Gh,zh,Xh,rl;var sl=R(()=>{Re();q();at();me();hs();Tt();it();nn();Jh={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?[os(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&&!ke(a,l))r.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),P(`# ${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",h=d.database??"";try{if(d.dbToken){let w=Jo(d.dbToken);p=w.engine,h||=w.database}}catch{}r.push({db:`${i}/${u}`,engine:p,default:a.default===u?"*":"",apiDomain:d.apiDomain??"",database:h,userToken:d.userToken?"yes":"no"})}}if(r.length===0){P("no connections; add one: fui db add <alias> -p <project> --token -");return}k(Z(r,n))}},Kh={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
80
+ [--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 m("missing <alias>");let r=(await o.resolveProject(f(t,"project"))).projectId,i=await xe(f(t,"token"),f(t,"token-env"),"--token"),a=await xe(f(t,"conn"),f(t,"conn-env"),"--conn");if(i===void 0===(a===void 0))throw new m("pass exactly one of --token/--token-env or --conn/--conn-env");let c=await ho(await Q(e),r,{alias:s,dbToken:i??ts(a),apiDomain:f(t,"api-domain"),sqlServer:f(t,"sql-server"),database:f(t,"database"),userToken:await xe(void 0,f(t,"user-token-env"),"--user-token"),makeDefault:t.default===!0,test:t["no-test"]!==!0});k(`${c.notes.join(`
82
81
  `)}
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(`
82
+ `)}},Gh={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 m("missing <project>/<alias>");let n=await Q(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}
83
+ `)}},zh={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run(e){let t=await Q(e),n=await e.store.resolve({db:f(e.values,"db"),project:t.project,contextProject:t.contextProject}),o=performance.now();await Ee(n,(r)=>r.query("SELECT 1 AS ok"));let s=Math.round(performance.now()-o);k(`ok ${n.projectId}/${n.alias} ${n.engine} ${s}ms
84
+ `)}},Xh={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 m(`project "${s}" has no legacy _db/config.json with a dbToken`);let i=f(t,"alias")??r.apiName;if(!i)throw new m("legacy config has no apiName; pass --alias <name> (the tAPI alias in module.json API paths)");let a=await ho(await Q(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
85
  `)}
87
86
  legacy _db/config.json left unchanged
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
- ${await F(se(e,o))}`);let n=se(e,"components");for(let o of(await Me(n)).slice().sort())t.push(`components/${o}
93
- ${await F(se(n,o))}`);return Rt(t.join(`
87
+ `)}},rl=[Jh,Kh,Xh,Gh,zh]});import{readdir as Yh}from"node:fs/promises";import{basename as Qh,dirname as Zh,extname as ef,isAbsolute as tf,join as cr,relative as ar,resolve as nf,sep as of}from"node:path";function lr(e,t,n=process.cwd()){let o=nf(n,t),s=ar(e.root,o);if(!s||s.startsWith("..")||tf(s))return null;let r=s.split(of),i=r[0];if(i.startsWith("_")||r.length<2)return null;if(r[1]==="modules"&&r.length>=4){let c=r[2],l=cr(e.root,i,"modules",c);return{abs:o,baseDir:l,rel:Te(ar(l,o)),projectId:i,moduleId:c}}let a=cr(e.root,i);return{abs:o,baseDir:a,rel:Te(ar(a,o)),projectId:i}}async function Ks(e,t){let n=ar(e.root,t),o=ef(n),s=Qh(n,o),r=cr(e.historyRoot(),Zh(n)),i=new RegExp(`^${il(s)}_(\\d{8}_\\d{6})((?:_[A-Za-z0-9-]+)*)${il(o)}$`),a;try{a=await Yh(r)}catch{return[]}return a.map((c)=>{let l=i.exec(c);return l?{stamp:l[1],suffix:l[2].replace(/^_/,""),path:cr(r,c)}:null}).filter((c)=>c!==null).sort((c,l)=>l.stamp.localeCompare(c.stamp)||l.path.localeCompare(c.path))}var il=(e)=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");var Gs=R(()=>{_t()});import{readdir as rf}from"node:fs/promises";import{join as zs}from"node:path";function Xs(e,t){let n=lr(e.paths,t);if(!n)throw new m(`${t} is not inside a project of workspace ${e.paths.root}`);return n}async function af(e){try{return(await rf(zs(e,"modules"),{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>zs(e,"modules",n.name))}catch{return[]}}var sf,cf,lf,uf,df,al;var cl=R(()=>{Re();q();at();me();be();Pt();Gs();_t();te();sf={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(Z(a,i))}};cf={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 af(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 uo(a))i.push({dir:a,path:c.relPath,reason:c.reason,push:Qo(c.relPath)});P(`# ${i.length} unpublished file(s)`),k(Z(i,s))}},lf={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 m("missing <path>");let n=Xs(e,t),o=await Ks(e.paths,n.abs);P(`# ${o.length} version(s) of ${n.rel}`),k(Z(o.map((s)=>({...s,path:zt(e.paths,s.path)})),e.render))}},uf={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 m("missing <path>");let n=Xs(e,t),o=await Ks(e.paths,n.abs),s=f(e.values,"at"),r=s?o.find((c)=>c.stamp===s):o[0];if(!r)throw new m(s?`no version ${s} of ${n.rel}; run: fui file history ${t}`:`no archived versions of ${n.rel}`);let i=await he(n.abs),a=i===null?null:await Se(e.paths,n.abs,{content:i});if(await O(n.abs,await B(r.path)),a)P(`# previous content archived: ${zt(e.paths,a)}`);k(`restored ${n.rel} from ${r.stamp}; push it with: ${Qo(n.rel)}
88
+ `)}},df={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 m("missing <path>");if(n.mark===!0&&n.forget===!0)throw new m("pass --mark or --forget, not both");let s=t.map((a)=>Xs(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 Me(a,l);if(n.forget===!0)await ec(a,l)}let i=[];for(let[a,c]of r){let l={};try{l=JSON.parse(await B(zs(a,$n)))}catch{}for(let u of c){let d=await he(u.abs),p=l[u.rel]??"",h=d===null?"missing":!p?"new":p===Qt(d)?"published":"changed";i.push({path:u.rel,state:h,ledger:p,dir:a})}}k(Z(i,o))}},al=[sf,cf,lf,uf,df]});function Pn(e){return e==="text/css"||e==="text/javascript"}function pf(e){return(e||"").trim().replace(/[<>:"/\\|?*\x00-\x1F]/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"import-file"}function bo(e,t,n){let o=n.startsWith(".")?n:`.${n}`;return`${pf(e)}-${t}${o}`}function Dt(e){return e.map((t)=>({name:t.FileName,type:t.FileType,contentType:t.ContentType,fileID:t.fileID,scope:t.Scope,sort:t.FileSort??null,...Pn(t.ContentType)?{filePath:`imports/${bo(t.FileName,t.fileID,t.FileType)}`}:{}}))}function En(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(Dt(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 Ys(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(Dt(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 ll(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(`
89
+ `)}import{mkdir as _n,readdir as on,rm as mf,stat as hf,unlink as Qs}from"node:fs/promises";import{basename as ul,dirname as ur,join as le}from"node:path";function rn(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function U(e,t=""){try{return await B(e)}catch{return t}}async function Zs(e,t){try{return await B(e)}catch(n){if(no(n)==="ENOENT")throw new m(`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 V(e,t){try{return JSON.parse(await B(e))}catch{return t}}async function Ot(e){try{return(await hf(e)).isDirectory()}catch{return!1}}async function We(e){try{return(await on(e,{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.endsWith(".vue")).map((n)=>n.name)}catch{return[]}}async function ko(e,t){let n=[];for(let[o,s]of Object.entries(t)){let r=le(e,o);await _n(ur(r),{recursive:!0}),await O(r,s),n.push(r)}return n}async function dl(e,t,n){let o=[],s;try{s=await on(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=le(t,r.name);await Se(e,i,{suffix:"deleted"}),await Qs(i),o.push(i)}return o}function pl(e){return new Set(Object.keys(e).filter((t)=>t.startsWith("components/")&&t.endsWith(".vue")).map((t)=>ul(t)))}async function ei(e,t,n){return await _n(t,{recursive:!0}),await dl(e,le(t,"components"),pl(n)),Fe(e,t,n)}async function ml(e,t,n,o,s){await _n(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(le(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 dl(e,le(t,"components"),pl(r)),c=await Fe(e,t,r);return{moduleDir:t,removedFiles:a,warnings:i,sync:c}}async function hl(e,t,n){let o=await V(le(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=le(t,"components",`${r.comName}.vue`);try{await Se(e,i,{suffix:"deleted"}),await Qs(i),s.push(`${r.comName}.vue`)}catch{}}return s}async function vo(e,t,n){let o=le(t,"imports"),s=[],r;try{r=await on(o,{withFileTypes:!0})}catch{return s}let i=new Set([...n].map((a)=>ul(a)));for(let a of r){if(!a.isFile()||a.name==="_imports.json"||i.has(a.name))continue;let c=le(o,a.name);try{await Se(e,c,{suffix:"deleted"}),await Qs(c),s.push(`imports/${a.name}`)}catch{}}return s}async function fl(e,t,n){let o=le(t,"modules"),s=[],r;try{r=await on(o,{withFileTypes:!0})}catch{return s}for(let i of r){if(!i.isDirectory()||n.has(i.name))continue;let a=le(o,i.name);await Go(e,a,{suffix:"deleted"}),await mf(a,{recursive:!0,force:!0}),s.push(i.name)}return s}function ti(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 ni(e,t){return le(e.projectDir(t),"modules","_modules.json")}async function gl(e,t){let n=await V(ni(e,t),[]);return Array.isArray(n)?n.filter(ti):[]}async function So(e,t,n){let o=e.projectDir(t);await ko(o,{"modules/_modules.json":JSON.stringify(n,null,2)});for(let s of n){let r=le(o,"modules",s.ModuleID,"_moduleInfo.json"),i=await V(r,null);if(!i)continue;await O(r,JSON.stringify({...i,...s},null,2))}return n.length}async function dr(e,t,n,o){let s=ni(e,t),r=await V(s,[]),i=Array.isArray(r)?r.filter(ti):[],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 _n(ur(s),{recursive:!0}),await O(s,JSON.stringify(i,null,2))}async function wl(e,t,n){let o=ni(e,t),s=await V(o,[]),r=Array.isArray(s)?s.filter(ti):[],i=r.findIndex((a)=>a.ModuleID===n);if(i<0)return!1;return r.splice(i,1),await _n(ur(o),{recursive:!0}),await O(o,JSON.stringify(r,null,2)),!0}async function yl(e){try{return(await on(e.root,{withFileTypes:!0})).filter((n)=>n.isDirectory()&&!n.name.startsWith("_")).map((n)=>n.name)}catch{return[]}}async function bl(e){let t=[],n;try{n=await on(e.root,{withFileTypes:!0})}catch{return t}for(let o of n){if(!o.isDirectory()||o.name.startsWith("_"))continue;let s=le(e.root,o.name);t.push({baseDir:s,projectId:o.name});try{for(let r of await on(le(s,"modules"),{withFileTypes:!0}))if(r.isDirectory())t.push({baseDir:le(s,"modules",r.name),projectId:o.name,moduleId:r.name})}catch{}}return t}async function oi(e,t){let n=[];for(let o of await bl(e)){let s=await V(le(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 ri(e,t,n){let o=[];for(let{baseDir:s}of await bl(e)){let r=await V(le(s,"imports/_imports.json"),[]),i=Array.isArray(r)?r.find((c)=>c?.fileID===t&&c?.filePath):void 0;if(!i?.filePath)continue;let a=le(s,i.filePath);try{await _n(ur(a),{recursive:!0}),await O(a,n),o.push(a)}catch{}}return o}function si(e,t){let n=t==="module"?gf:ff;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 Mt(e){return JSON.stringify(e??{},null,2)}function xt(e){return e.replace(/\r\n/g,`
90
+ `).trim()}async function pr(e){let t=[];for(let o of wf)t.push(`${o}
91
+ ${await U(le(e,o))}`);let n=le(e,"components");for(let o of(await We(n)).slice().sort())t.push(`components/${o}
92
+ ${await U(le(n,o))}`);return Qt(t.join(`
94
93
  \x00
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
-
97
- Use these rules whenever editing a FUI module:
98
-
99
- - Always work in the staged local module structure: _info.json, module.json, script.js, header.html, body.html, dependencies.json, components/_components.json, components/*.vue.
100
- - In module.json, keep the metadata-driven structure: data, watch, controls, set.
101
- - controls must start with the mandatory grid wrapper: container > rows > cols.
102
- - Inside attr, use JSON-safe directives like v-on:click, never @click.
103
- - In action IN blocks, force literal strings when needed by using backtick or quoted-string patterns.
104
- - Reference reactive state with vueData. prefixes.
105
- - Custom Vue components should use the uc- prefix.
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
- - 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
- - 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
- `,lm="https://github.com/fui-org/fui-skill";function um(){return`${Ja}
94
+ `))}function kl(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 vl(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(xt(a)!==xt(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 ii(e){if(!rn(e))throw new m("project.json must be a JSON object at the top level");if(rn(e.data))return{config:e.data,wasEnveloped:!0};return{config:e,wasEnveloped:!1}}function mr(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}`]}var ff=20,gf=50,wf;var lt=R(()=>{q();be();Pt();_t();In();te();wf=["module.json","header.html","body.html","script.js","style.css"]});function Ft(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=bf(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=kf(e.substring(a.index+a[0].length,r).trim()),u=yf(e,o,r);return{template:c,script:l,style:u}}function yf(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 bf(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 kf(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()}import{join as ue}from"node:path";function An(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Sl(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 vf(e){let t=e.trim();if(/^(https?:)?\/\//i.test(t)){let{host:o,path:s}=Sl(t);return o?{host:o,path:s}:null}let n=t.split("/")[0];if(n&&n.includes(".")){let{host:o,path:s}=Sl(`//${t}`);return o?{host:o,path:s}:null}return null}function $l(e){let n=e.trim().replace(/^https?:\/\/[^/]+/i,"").split(/[?#]/,1)[0].replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);if(n.length<2)throw new m(`target "${e}" must look like /projectId/moduleId`);return{projectHint:n[0],moduleHint:n.slice(1).join("/")}}function Sf(e){let t=[e.ModuleID,e.ModuleName,e.mDomain,e.url].filter((n)=>Boolean(n));return[...new Set(t.flatMap((n)=>[n.toLowerCase(),An(n),n.replace(/^\/+|\/+$/g,"").toLowerCase()]))]}function ai(e,t){let n=t.trim().toLowerCase().replace(/^\/+|\/+$/g,""),o=An(t);return e.filter((s)=>{let r=Sf(s);return r.includes(n)||r.includes(o)})}function Nn(e,t,n){if(n?.refreshed)return new m(`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 m(`module "${t}" is not in the local cache (${e}/modules/_modules.json).${o} run: fui module list -p ${e}`)}class $o{paths;api;constructor(e,t){this.paths=e;this.api=t}baseDir(e,t){return t?ue(this.paths.projectDir(e),"modules",t):this.paths.projectDir(e)}async lookupModuleInfo(e,t){let n=t(await gl(this.paths,e));if(n)return{found:n,refreshed:!1};let o;try{o=await this.api.listModules(e),await So(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 Ot(t))return null;let n=(await this.api.listProjects()).find((o)=>o.ProjectID===e);if(!n)return null;return await ko(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=An(e),o=(await yl(this.paths)).filter((a)=>a.toLowerCase()===t||An(a)===n);if(o.length>1)throw new m(`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)=>An(a.ProjectName)===n||a.ProjectName.trim().toLowerCase()===t);if(r.length===1)return r[0].ProjectID;if(r.length>1)throw new m(`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)||An(a.ProjectName).includes(n)).slice(0,10);if(i.length>0)throw new m(`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 m(`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 Nn(s.ProjectID,s.ModuleID,r);return{projectId:s.ProjectID,moduleId:s.ModuleID,moduleInfo:r.found,matchedBy:"runtime_lookup"}}async resolveProjectAndModuleHint(e){let t=vf(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=$l(e);return{projectId:await this.resolveProjectIdFromHint(s.projectHint),moduleHint:s.moduleHint,matchedBy:"target_path"}}catch(s){throw new m(`cannot resolve target "${e}": as URL/domain: ${o.message}; as /projectId/moduleId: ${s.message}`)}}let n=$l(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 Nn(t,n,r);return{projectId:t,moduleId:n,moduleInfo:r.found,matchedBy:"explicit_ids"}}if(!e)throw new m("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=ai(r,o.moduleHint);if(i.length>1)throw new m(`target "${e}" matched several modules in project ${o.projectId}: ${i.map((a)=>a.ModuleID).join(", ")}`);return i[0]??null});if(!s.found)throw Nn(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(!Pn(o.ContentType))continue;try{let r=(await this.api.getFileContent(o.fileID))?.data?.FileContent??"";t[`imports/${bo(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 V(ue(e,"imports/_imports.json"),[]);for(let n of t){if(!n.fileID||!n.filePath||!Pn(n.contentType??""))continue;let o=await U(ue(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 hl(this.paths,s,new Set(o.map((i)=>i.comName)));return await ko(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=Dt(o),i=new Set(r.map((c)=>c.filePath).filter((c)=>!!c)),a=await vo(this.paths,s,i);return await ko(s,{"imports/_imports.json":JSON.stringify(r,null,2)}),{list:n,count:o.length,removed:a}}async publishStagedModuleDir(e){let t=await V(ue(e,"_moduleInfo.json"),null);if(!t)throw new m(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await U(ue(e,"module.json")),o;try{o=JSON.parse(n)}catch(g){throw new m(`module.json has invalid JSON syntax: ${g.message}; fix it before publishing`)}let s=await U(ue(e,"header.html")),r=await U(ue(e,"body.html")),i=await U(ue(e,"script.js")),a=await U(ue(e,"style.css")),{pageHeader:c,pageBody:l,mTitle:u}=kl(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 h=ue(e,"components"),w=await V(ue(h,"_components.json"),[]),y=new Map(w.map((g)=>[g.comName,g.comID])),v=await We(h),_=new Set(v.map((g)=>g.replace(/\.vue$/i,"")));for(let g of v){let b=g.replace(/\.vue$/i,""),{template:T,script:C,style:S}=Ft(await U(ue(h,g)));await this.api.updateComponent({ProjectID:d,ModuleID:p,ComID:y.get(b),ComName:b,ComTemplate:T,ComScript:C,ComCSS:S})}for(let g of w)if(!_.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(Te(ue("components",g)));return await Me(e,I).catch(()=>{return}),t}async previewStagedModuleDir(e){let t=await V(ue(e,"_moduleInfo.json"),null);if(!t)throw new m(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await this.fetchModuleBundle(t),o=En(n.moduleInfo,n.content,n.components,n.script,n.css,n.dependencies),s;try{s=JSON.parse(await U(ue(e,"module.json")))}catch(p){throw new m(`module.json has invalid JSON syntax: ${p.message}; fix it before previewing`)}let r=await U(ue(e,"header.html")),i=await U(ue(e,"body.html")),a=await U(ue(e,"script.js")),c=await U(ue(e,"style.css")),l=await V(ue(e,"imports/_imports.json"),[]),u={};for(let p of await We(ue(e,"components")))u[p.replace(/\.vue$/i,"")]=await U(ue(e,"components",p));let d=[];if(Mt(s)!==Mt(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 h={"header.html":r,"body.html":i,"script.js":a,"style.css":c}[p];if(xt(h)!==xt(o[p]||""))d.push(`${p} will be updated`)}if(Mt(l)!==Mt(JSON.parse(o["imports/_imports.json"]||"[]")))d.push("imports/_imports.json differs locally (metadata only, not sent)");return d.push(...vl(n.components,u)),{moduleInfo:t,changes:d,remoteSummary:mr(n.content)}}}var ci=R(()=>{q();_t();lt()});import{cp as $f,mkdir as li,readdir as jf,rm as If}from"node:fs/promises";import{dirname as jl,isAbsolute as Cf,join as Le,relative as Rf,sep as Tf}from"node:path";import{fileURLToPath as Pf}from"node:url";function Af(){return`${Il}
110
95
 
111
96
  ## Source
112
97
 
113
- - Bundled with fui-mcp from ${lm}
98
+ - Bundled with fui-mcp from ${_f}
114
99
 
115
100
  ## Notes
116
101
 
117
102
  - This fallback file exists so every editor can read a project-local skill snapshot after checkout.
118
103
  - 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 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()}
104
+ - 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 Nf(){let e=jl(Pf(import.meta.url));for(let t of[Le(e,"..","..","..","skills"),Le(e,"..","skills")])if(await Ke(Le(t,Lt,"SKILL.md")))return t;return Le(e,"..","..","..","skills")}async function sn(){return Le(await Nf(),Lt)}function Df(e){return Le(e,"_skills",Lt,"SKILL.md")}async function hr(e){let t=Le(e,"_skills"),n=Le(t,Lt);await li(e,{recursive:!0});try{let o=await sn();if(!await Ke(Le(o,"SKILL.md")))throw Error(`bundled skill missing at ${o}`);return await If(t,{recursive:!0,force:!0}),await li(t,{recursive:!0}),await $f(o,n,{recursive:!0,filter:(s)=>!s.split(Tf).includes(".git")}),{workspaceRoot:e,skillDir:n,source:"bundled-copy"}}catch(o){let s=`Could not copy bundled skills/ into ${n}: ${o.message}`;return await li(n,{recursive:!0}),await O(Le(n,"SKILL.md"),`${Af()}
120
105
 
121
106
  ## Sync Warning
122
107
 
123
108
  ${s}
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(`
109
+ `),{workspaceRoot:e,skillDir:n,source:"bundled"}}}async function Cl(e){if(e){let t=Df(e),n=await he(t);if(n&&n.trim()){let o=[n];for(let s of Ef){let r=await he(Le(jl(t),s));if(r?.trim())o.push(`
125
110
 
126
111
  ---
127
112
  <!-- ${s} -->
128
113
 
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`
114
+ ${r}`)}return{text:o.join(""),source:t}}}return{text:Il.trim(),source:"bundled-summary"}}async function Rl(e,t){return(await Promise.all(t.map(async(o)=>{let s=Le(e,o),r=Rf(e,s);if(r.startsWith("..")||Cf(r))return`
130
115
 
131
116
  ---
132
117
  <!-- ${o} -->
@@ -141,58 +126,72 @@ ${await B(s)}`}catch{return`
141
126
  ---
142
127
  <!-- ${o} -->
143
128
 
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}
129
+ not found at ${s}`}}))).join("")}async function ui(){let e=Le(await sn(),"design-md");return(await jf(e,{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>n.name).sort()}var Lt="fui-skill",Ef,Il=`# FUI Skill Summary
130
+
131
+ Use these rules whenever editing a FUI module:
132
+
133
+ - Always work in the staged local module structure: _info.json, module.json, script.js, header.html, body.html, dependencies.json, components/_components.json, components/*.vue.
134
+ - In module.json, keep the metadata-driven structure: data, watch, controls, set.
135
+ - controls must start with the mandatory grid wrapper: container > rows > cols.
136
+ - Inside attr, use JSON-safe directives like v-on:click, never @click.
137
+ - In action IN blocks, force literal strings when needed by using backtick or quoted-string patterns.
138
+ - Reference reactive state with vueData. prefixes.
139
+ - Custom Vue components should use the uc- prefix.
140
+ - Run module_preview first, then ask the user if they want to push. Only call module_publish_html (with the returned approvalToken) — or module_publish_json / module_publish_script / component_update for their respective files — after explicit approval.
141
+ - 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.
142
+ - Never call a live API/SP endpoint to verify it — 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.
143
+ `,_f="https://github.com/fui-org/fui-skill";var fr=R(()=>{be();te();Ef=["references/controls-patterns.md","references/module-structure.md","references/INDEX.md"]});import{rename as xf}from"node:fs/promises";import{basename as Of,dirname as di,join as ze}from"node:path";async function pe(e){return new $o(e.paths,await e.api())}function Tl(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 an(e,t){let n=await e.wasSkillLoaded();if(!n&&bn())throw new Pe(`[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 Pl(e,t){let n=await(await pe(e)).resolveModuleTarget(t);return{notes:[],data:`project ${n.projectId}
145
144
  module ${n.moduleId}
146
145
  matchedBy ${n.matchedBy}
147
146
  moduleDir ${e.paths.projectDir(n.projectId)}/modules/${n.moduleId}
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(`
147
+ `}}async function El(e,t){let{projectId:n,moduleId:o}=t,s=t.moduleDir,r="module_dir";if(!s){if(!t.target&&!(n&&o))throw new m("give a target (/project/module or URL), -p and -m, or --module-dir");let a=await(await pe(e)).resolveModuleTarget(t.target,n,o);n=a.projectId,o=a.moduleId,s=ze(e.paths.projectDir(n),"modules",o),r=a.matchedBy}else{let a=await V(ze(s,"_moduleInfo.json"),null);if(!a)throw new m(`missing _moduleInfo.json in ${s}`);n=a.ProjectID,o=a.ModuleID}let i=t.moduleDir?di(di(di(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 _l(e){return(await e.api()).listProjects()}async function Al(e,t){let n=await pe(e),o=e.paths.projectDir(t),s=await n.fetchProjectBundle(t),r=Ys(s.projectInfo,s.projectJson,s.modules,s.projectComponents,s.projectImports),{"modules/_modules.json":i,...a}=r,c=new Set(Dt(s.projectImports).map((y)=>y.filePath).filter((y)=>!!y)),l=await vo(e.paths,o,c),u=await n.downloadImportContents(s.projectImports),d=await ei(e.paths,o,{...a,...u.files}),p=await fl(e.paths,o,new Set(s.modules.map((y)=>y.ModuleID)));await So(e.paths,t,s.modules);let h=!1,w=await e.session.getTarget();if(w?.projectId===t&&w.moduleId&&p.includes(w.moduleId))await e.session.clearTarget(),h=!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: ${Ss(d)}`,...l.length>0?[`removed orphaned import files: ${l.join(", ")}`]:[],...p.length>0?[`removed orphaned module dirs (archived): ${p.join(", ")}`]:[],...h?["session target cleared: it pointed to a removed module; run fui use / fui module get again"]:[],...$s(d.archived),...Tl(u.failures)]}}async function Nl(e,t){let n=await(await e.api()).listModules(t),o;try{await So(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 Dl(e,t){let n=await pe(e),o=(await n.resolveModuleInfoFromCache(t.projectId,t.moduleId)).found;if(o)throw new m(`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 dr(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 xl(e,t){let n=await pe(e),{projectId:o,moduleId:s,newModuleId:r}=t,i=ze(e.paths.projectDir(o),"modules",s),a=await V(ze(i,"_moduleInfo.json"),null),c=a?null:await n.resolveModuleInfoFromCache(o,s),l=a??c?.found;if(!l)throw Nn(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,h={...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,h)).data??[]).find((I)=>I.ModuleID===h.ModuleID),v={...l,Framework:h.Framework,mDomain:h.mDomain,ModuleID:h.ModuleID,ModuleName:h.ModuleName,HTMLOnly:h.HTMLOnly};if(r&&r!==s)v.id=`${o}/${r}`,v.url=`/${o}/${r}`;if(y)v={...v,...y};await dr(e.paths,o,v,s);let _=["modules/_modules.json updated"];if(a){let I=i;if(r&&r!==s){let b=ze(e.paths.projectDir(o),"modules",r);if(await Ot(b))u.push(`warning: cannot rename the local folder: ${b} exists; _moduleInfo.json updated in place`);else await xf(i,b),I=b,_.push(`local folder renamed: modules/${s} -> modules/${r}`)}await O(ze(I,"_moduleInfo.json"),JSON.stringify({...a,...v},null,2)),_.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}),_.push("session target updated")}return{notes:[`ok updated ${v.id} url ${v.url} name ${v.ModuleName} framework ${v.Framework} htmlOnly ${v.HTMLOnly}`,..._,...u]}}async function Ol(e,t,n){await(await e.api()).deleteModule(t,n);let o=[`ok deleted module ${n} from project ${t}`];try{o.push(await wl(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=ze(e.paths.projectDir(t),"modules",n);if(await Ot(s)){await Go(e.paths,s,{suffix:"deleted"});let{rm:i}=await import("node: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 Ml(e,t){let n=await pe(e),o,s,r,i;if(t.projectId&&t.moduleId)o=t.projectId,s=t.moduleId,r="explicit_ids";else if(t.target){let D=await n.resolveProjectAndModuleHint(t.target);o=D.projectId,s=D.moduleInfo?.ModuleID??D.moduleHint,r=D.matchedBy,i=D.moduleInfo}else{let D=await e.targets.resolveProjectModule();o=D.projectId,s=D.moduleId,r=D.source==="cwd"?"cwd":"active_session"}let a=e.paths.projectDir(o),c=await V(ze(a,"_projectInfo.json"),null)!==null,l=[];if(!c){let D=await n.fetchProjectBundle(o);await ei(e.paths,a,Ys(D.projectInfo,D.projectJson,D.modules,D.projectComponents,D.projectImports));let ge=await n.downloadImportContents(D.projectImports);if(l.push(...ge.failures),Object.keys(ge.files).length>0)await Fe(e.paths,a,ge.files)}let u=i??null;if(!u){let D=await n.lookupModuleInfo(o,(ge)=>{let Ae=ge.find((de)=>de.ModuleID===s);if(Ae||r!=="target_path")return Ae??null;let W=ai(ge,s);if(W.length>1)throw new m(`target "${t.target}" matched several modules in project ${o}: ${W.map((de)=>de.ModuleID).join(", ")}`);return W[0]??null});if(u=D.found,!u)throw Nn(o,s,D)}let{content:d,components:p,script:h,css:w,dependencies:y}=await n.fetchModuleBundle(u),v=En(u,d,p,h,w,y),_=ze(e.paths.projectDir(u.ProjectID),"modules",u.ModuleID),I=new Set(Dt(y.filter((D)=>D.Scope!=="project")).map((D)=>D.filePath).filter((D)=>!!D)),g=await vo(e.paths,_,I),[b,T]=await Promise.all([n.downloadImportContents(y.filter((D)=>D.Scope==="module")),n.downloadImportContents(c?y.filter((D)=>D.Scope==="project"):[])]);l.push(...b.failures,...T.failures);let C=await ml(e.paths,_,u,d,{...v,...b.files}),S=Object.keys(T.files).length>0?await Fe(e.paths,a,T.files):null;await dr(e.paths,o,{...u,Framework:d.Framework,HTMLOnly:d.HTMLOnly});let N=await hr(e.paths.root);await e.session.setTarget({projectId:u.ProjectID,moduleId:u.ModuleID,moduleDir:C.moduleDir,workspaceRoot:e.paths.root,target:t.target});let x=[...C.removedFiles.map((D)=>Of(D)),...g],A=[...C.sync.archived,...S?.archived??[]],Y=c?await Zt(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: ${Ss(C.sync)}`,...x.length>0?[`removed orphaned local files: ${x.join(", ")}`]:[],...$s(A),c?"project data already local (not fetched); refresh: fui project sync":"project data bootstrapped (project.json, components, imports, modules/_modules.json)",...Y?[Y]:[],`skills: ${N.skillDir}${N.source==="bundled"?" (fallback summary; copy failed)":""}`,...C.warnings.map((D)=>`warning: ${D}`),...Tl(l),...mr(d),"now active for fui module preview / publish / validate"],data:`${ll(u.ModuleID,{"_moduleInfo.json":"{}",...v})}
148
+ `}}async function Fl(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=ze(e.paths.projectDir(t),"modules",n),c=[];if(o==="ui"&&i?.moduleUI!==void 0&&i.moduleUI!==null&&await Ot(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 Fe(e.paths,a,{"module.json":l}),c.push(`module.json written to ${a}`)}if(o==="html"&&typeof i?.mBody==="string"&&await Ot(a))await Fe(e.paths,a,{"body.html":i.mBody}),c.push(`body.html written to ${a}`);return{notes:c,data:`${JSON.stringify(r)}
149
+ `}}async function Dn(e,t,n){let o=t,s=n;if(!o){let a=e.targets.cwdTarget()??await e.session.getTarget();if(!a?.projectId)throw new m("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?ze(e.paths.projectDir(o),"modules",s):e.paths.projectDir(o);return{projectId:o,moduleId:s,baseDir:r}}async function Ll(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 m(`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 Ot(t.baseDir))await Fe(e.paths,t.baseDir,{[`components/${n}.vue`]:s.comCode}),i.push(`${n}.vue written to ${t.baseDir}/components/`);return{notes:i,data:s.comCode}}var xn=R(()=>{q();lt();ci();fr();Gt();Pt();_t();In();te()});function On(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 m(`--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 pi(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 m("pass exactly one of --data '<json>' | --data - | --data-file <path>");let o=n!==void 0?await st(n):t==="-"?await Zn():t,s;try{s=JSON.parse(o)}catch(r){throw new m(`--data is not valid JSON: ${r.message}`)}if(!s||typeof s!=="object"||Array.isArray(s))throw new m("--data must be a JSON object");return s}async function gr(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 m(`pass --${t} or --${t}-file, not both`);let r=s!==void 0?await st(s):o==="-"?await Zn():o,i;try{i=JSON.parse(r)}catch(c){throw new m(`--${t} is not valid JSON: ${c.message}`)}if(!(n==="array"?Array.isArray(i):!!i&&typeof i==="object"&&!Array.isArray(i)))throw new m(`--${t} must be a JSON ${n}`);return i}function mi(...e){return Object.fromEntries(e.flatMap((t)=>[[t,{type:"string"}],[`${t}-file`,{type:"string"}]]))}var wr=R(()=>{q();te()});function yr(e){for(let t of e.notes)P(`# ${t}`);if(e.data)k(e.data)}function jo(e){if(k(`${e.notes.join(`
151
150
  `)}
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(`
151
+ `),e.data)k(e.data)}async function hi(e){return(await e.targets.resolveProject(f(e.values,"project"))).projectId}async function Hl(e){return e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module"))}function Mf(e,t){if(e===void 0)return;if(e==="true")return!0;if(e==="false")return!1;throw new m(`${t} takes true or false`)}function ql(e){if(e===void 0)return;let t=e.toUpperCase();if(t!=="V2"&&t!=="V3")throw new m("--framework must be V2 or V3");return t}function Ul(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:Mn,async run(t){let{projectId:n,moduleId:o}=await Hl(t);yr(await Fl(F(t),n,o,e))}}}var Mn,Ff,Lf,Uf,Hf,qf,Vf,Wf,Bf,Jf,Kf,Vl;var Wl=R(()=>{Re();q();at();me();xn();wr();Mn={module:{type:"string",short:"m"}};Ff={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:{...Mn,"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
152
+ `);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){P("# no session target; set one: fui use <project>/<module>");return}k(Z([{...l,session:o.file}],s));return}if(i&&!a||!i&&a)throw new m("pass both -p and -m");jo(await El(F(e),{target:r,projectId:i,moduleId:a,moduleDir:c}))}},Lf={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 m("missing <target>");yr(await Pl(F(e),t))}},Uf={name:"project list",mcpName:"project_list",usage:"fui project list",summary:"List projects you can access",async run(e){let t=(await _l(F(e))).map((n)=>({group:n.GroupName||"Khac",id:n.ProjectID,name:n.ProjectName,framework:n.Framework??"",domain:n.pDomain??""}));k(Z(t,e.render))}},Hf={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){jo(await Al(F(e),await hi(e)))}},qf={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 Nl(F(e),await hi(e));for(let o of n)P(`# ${o}`);k(Z(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))}},Vf={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 m("need <moduleId> and --name");jo(await Dl(F(e),{projectId:await hi(e),moduleId:t,moduleName:n,framework:ql(f(e.values,"framework")),htmlOnly:e.values["html-only"]===!0?!0:void 0,mDomain:f(e.values,"domain"),extra:On(e.values.set)}))}},Wf={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:{...Mn,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 Hl(e);jo(await xl(F(e),{projectId:t,moduleId:n,moduleName:f(e.values,"name"),framework:ql(f(e.values,"framework")),htmlOnly:Mf(f(e.values,"html-only"),"--html-only"),mDomain:f(e.values,"domain"),newModuleId:f(e.values,"new-id"),extra:On(e.values.set)}))}},Bf={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:Mn,async run(e){let t=f(e.values,"project"),n=f(e.values,"module");if(!t||!n)throw new m("module delete needs explicit -p <project> -m <module>");jo(await Ol(F(e),t,n))}},Jf={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:Mn,async run(e){let t=f(e.values,"project"),n=f(e.values,"module");if(t&&!n||!t&&n)throw new m("pass both -p and -m, or a target");yr(await Ml(F(e),{target:e.positionals[0],projectId:t,moduleId:n}))}};Kf={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:Mn,async run(e){let t=e.positionals[0];if(!t)throw new m("missing <name>");let n=F(e),o=await Dn(n,f(e.values,"project"),f(e.values,"module"));yr(await Ll(n,o,t))}},Vl=[Ff,Lf,Uf,Hf,qf,Vf,Wf,Bf,Jf,Ul("ui"),Ul("html"),Kf]});import{join as Bl}from"node:path";async function cn(e,t){let n={functionRights:null,systemRights:null},o;try{o=JSON.parse(await B(Bl(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 V(Bl(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")}}var br=R(()=>{be();lt()});function ln(e){return Kl("FunctionRight",e)}function Jl(e){return Kl("SystemRight",e)}function Io(e,t){if(e.length===0)return null;if(t===null)return`Chưa có cache _db/rights.json — KHÔNG kiểm được ${e.length} mã FunctionRight (${e.join(", ")}) `+"có thật hay không. Preview cấp sẵn đúng những mã này nên control vẫn hiện ở ĐÂY; user thật chỉ thấy nếu mã đã "+"được định nghĩa. Chạy right_function_list (một lần cho mỗi project) rồi render lại.";let n=e.filter((o)=>!t.has(o));if(n.length===0)return null;return`\uD83D\uDEA8 FunctionRight CHƯA ĐƯỢC ĐỊNH NGHĨA trên hệ thống acc: ${n.join(", ")} — preview cấp sẵn nên control vẫn `+"hiện ở ĐÂY, nhưng chạy thật thì rightTest() luôn trả false → control/menu biến mất mà không có lỗi JS nào. "+'Tạo mã: right_function_new({ functionCode: …, functionName: "…" }); hoặc right_function_list nếu cache cũ.'}function Kl(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()}import St from"node:path";function j(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function wi(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)=>Fn.has(n)));if(!j(e))return!1;return Object.keys(e).some((t)=>Fn.has(t))}function ut(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 Co(e){return!!e&&!e.includes(" ")&&!e.includes("{{")&&!e.includes("(")&&!e.startsWith("`")&&!e.startsWith("#")&&!e.startsWith("!")&&/^[A-Za-z_$]/.test(e.replace(/^vueData\./,""))}function Xf(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 Yf(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("")}function Zf(e,t,n,o){if(typeof t!=="string")return;if(!(e.startsWith(":")&&!e.startsWith("::")||e.startsWith("v-bind:")))return;let r=Yf(t),i=[];for(let{re:a,what:c}of Qf)if(a.test(r))i.push(c);if(r.replace(/;\s*$/,"").includes(";"))i.push('dấu ";" nối nhiều câu lệnh');if(i.length===0)return;o.push(`attr "${e}" (tại ${n}) chứa ${i.join(" + ")} — giá trị binding đi thẳng vào template compiler của Vue, `+"chỉ nhận MỘT BIỂU THỨC thuần (object/array literal, ternary, chuỗi .map()/.filter() arrow, truy cập thuộc tính). "+"Statement ở đây làm compile template hỏng → TRANG TRẮNG mà không có JS error hay [Vue warn] nào. "+'Cách sửa: đưa logic vào một hàm trong script.js (function buildX(){ … vueData.x = …; }), gọi qua "EXE": "buildX()" '+`trong CALLBACK của action nạp dữ liệu, rồi "${e}" chỉ trỏ tới biến vueData đó (vd "${e}": "x"). `+"Chi tiết: references/advanced-techniques.md.")}function Be(e,t,n="$",o=!1){if(Array.isArray(e))e.forEach((s,r)=>Be(s,t,`${n}[${r}]`,o));else if(j(e)){t(e,n,o);for(let[s,r]of Object.entries(e))Be(r,t,`${n}.${s}`,o||s.startsWith("#"))}}function sg(e,t,n){let o=JSON.stringify(e),s=ln([o]),r=Jl([o]).filter((l)=>!Xl.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ác quyền bằng ${i} mã literal (${[...s,...r].join(", ")}) nhưng chưa có cache _db/rights.json `+"để đối chiếu — KHÔNG kiểm được mã nào có thật. Mã không tồn tại làm rightTest() luôn trả false: "+"control/menu biến mất im lặng, không lỗi JS. Chạy right_function_list + right_system_list (một lần cho mỗi project) rồi validate lại."),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ưa được định nghĩa cho project (_db/rights.json có ${l.size} mã`+`${l.size?`: ${Array.from(l).slice(0,15).join(", ")}${l.size>15?", …":""}`:""}). `+"rightTest() sẽ luôn trả false → control/menu gác bằng mã này biến mất mà không báo lỗi. "+`Tạo mã: right_function_new({ functionCode: ${/^\d+$/.test(d)?d:`"${d}"`}, functionName: "…" }); `+"hoặc chạy right_function_list nếu mã đã có trên server mà cache cũ.")}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ưa được định nghĩa cho project (_db/rights.json có: `+`${l.size?Array.from(l).sort().join(", "):"(chưa mức nào)"}${Xl.size?" — mức 9 luôn hợp lệ":""}). `+"Không ai được cấp mức này thì control gác bằng nó biến mất im lặng. "+`Định nghĩa: right_system_new({ sysRight: ${d}, note: "…" }); hoặc chạy right_system_list nếu cache cũ.`)}return i}function ig(e,t){let n=(a)=>t.push(`[design] ${a} ${eg}`),o=(a)=>typeof a==="string"?a:"";Be(e,(a,c)=>{let l=a.API;if(typeof l==="string"&&og.test(l)&&a.CONFIRM===void 0)n(`Action gọi API "${l}" (tại ${c}) có vẻ là thao tác XÓA nhưng thiếu CONFIRM — mọi action xóa phải hỏi xác nhận.`)});let s=new Set;Be(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(!ng.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út/mục xóa (tại ${c}) đang màu "${p}" — nút xóa dùng màu "warning" (cam), không dùng error/đỏ.`)});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),h=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 h)))n(`v-toolbar (tại ${c}) có color/dark — toolbar tiêu đề phải "dense flat" cùng màu nền với thân dialog, không tô màu/dark riêng.`);if(d==="v-dialog"||d==="f-dialog"){let I=h.width??h[":width"],g=typeof I==="number"?I:typeof I==="string"&&/^\d+$/.test(I.trim())?parseInt(I,10):NaN;if(!Number.isNaN(g)&&!tg.has(g))n(`Dialog (tại ${c}) width=${g} — bộ width chuẩn: 400 / 550 / 600 / 650 / 800.`)}if(w&&d==="v-card"&&["elevation",":elevation","outlined",":outlined"].some((I)=>(I in h)))n(`v-card trong dialog (tại ${c}) có elevation/outlined — bỏ đi: v-dialog đã tự có bóng đổ, thêm nữa là bóng chồng bóng. v-card chỉ cần class "pa-0" (triệt đệm để toolbar tiêu đề sát mép).`);let v=o(h.class),_=v.includes("flex-md-grow-0")&&v.includes("ml-2");if(_)n(`Control (tại ${c}) mang class "flex-md-grow-0 ml-2" — dấu hiệu pattern label-div rời đã loại bỏ; form dùng floating label ("label" trong attr của field).`);if(w&&!_&&rg.has(d)&&!("label"in h)&&!(":label"in h)&&!("placeholder"in h))n(`${d} trong dialog (tại ${c}) thiếu "label" — form dialog dùng floating label.`);if(d==="v-snackbar"||d==="v-alert")n(`${d} (tại ${c}) — phản hồi/thông báo dùng hệ thống có sẵn của FUI: MESS (toast) / MESSBOX (hộp thoại) trong action, CONFIRM để xác nhận — không tự dựng ${d} trong controls.`);if(d==="f-table"||d==="f-sheet"){let I=h[":height"]??h.height;if(I===void 0)n(`${d} (tại ${c}) không có ":height" — bảng sẽ tự giãn theo số dòng và làm cả TRANG cuộn. Bảng data chính nên đặt \`":height": -16\` (fill tới đáy màn hình, chừa 16px — component tự đo vị trí thật, không cần tính tay); trong dialog dùng số dương (px cố định, vd 360). Bảng ngắn/lookup chủ ý tự giãn thì bỏ qua.`);else if(typeof I==="string"&&/\$\s*\(\s*window\s*\)\s*\.\s*height\s*\(\s*\)/.test(I))n(`${d} (tại ${c}) tính ":height" thủ công theo viewport (\`${I}\`) — không cần nữa: ${d} tự đo vị trí thật của bảng sau layout và tự cập nhật khi resize. Thay bằng \`":height": -16\` (số âm = khoảng chừa ở đáy).`)}if(y&&(d==="f-button"||d==="v-btn")){let I=o(h.label)||(typeof a.innerHTML==="string"?a.innerHTML:"");if(/^\s*(đóng|dong|close)\s*$/i.test(I))n(`Nút "${I.trim()}" trong v-card-actions (tại ${c}) — dialog chuẩn chỉ dùng icon close trên toolbar, không thêm nút Đóng ở 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))}function Zl(e){return typeof e==="string"?e:j(e)?[e.class,e[":class"]].filter((t)=>typeof t==="string").join(" "):""}function cg(e){return e.w===void 0||e.w===null||e.w===""||e.w===0}function lg(e,t){Be(e.controls,(n,o)=>{if(!Array.isArray(n.cols))return;ug(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<=ag)return;let l=`${o}.cols[${r}]${typeof i.el==="string"?` (el: ${i.el})`:""}`;if(c<fi){t.push(`${l} có "w": ${c} — vùng chết 13–24: V2 hiểu là ${c}px, còn V3 sinh class "v-col-${c}" mà Vuetify 3 không định nghĩa (chỉ có .v-col-1 … .v-col-12) nên cột KHÔNG có bề rộng nào, rơi về chia đều. Dùng 1–12 (chia cột) hoặc >= ${fi} (px, kèm \`"col": { "class": "shrink" }\`).`);return}let u=i.col;if(Ql.test(Zl(u)))return;t.push(j(u)&&"shrink"in u?`${l} có "w": ${c} (px) kèm \`"col": { "shrink": ... }\` — dạng thuộc tính trần là prop của v-flex (Vuetify 2); Vuetify 3 không có prop đó nên nó trơ và cột vẫn giãn hết chỗ trống. Viết \`"col": { "class": "shrink" }\` để đúng ở cả hai framework.`:`${l} có "w": ${c} — số >= ${fi} nghĩa là bề rộng cố định px, nhưng cột grid mặc định flex-grow:1 nên flex-basis ${c}px KHÔNG có tác dụng: cột vẫn giãn hết chỗ trống (đo thật: w=300 ra 687px ở V2, 738px ở V3). Thêm \`"col": { "class": "shrink" }\`.`)})},"$.controls")}function ug(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=Ql.test(Zl(c));if(i.el==="v-spacer"){if(l)n.push(`${a} — \`v-spacer\` mà có \`shrink\` thì co lại còn 0px (đo thật: 24px ở V3, 0px ở V2), không đẩy được nhóm sau sang phải nữa và cả hàng dồn về trái. Bỏ \`shrink\` khỏi riêng cột này.`);return}if(!cg(i)||l)return;n.push(j(c)&&"shrink"in c?`${a} nằm cùng hàng với \`v-spacer\`, \`w\` rỗng và chỉ có \`"col": { "shrink": ... }\` — dạng thuộc tính trần là prop của v-flex (Vuetify 2); Vuetify 3 không có prop đó nên cột vẫn giãn và chiếm một phần đều. Viết \`"col": "shrink"\` (hoặc \`{ "class": "shrink" }\`).`:`${a} nằm cùng hàng với \`v-spacer\` nhưng thiếu \`shrink\` — \`w\` rỗng KHÔNG có nghĩa "tự co theo nội dung": runtime bọc phần bề rộng trong \`if (c.w)\` nên cột này không có class grid lẫn flex-basis, còn trơ \`flex-grow: 1\` và được CHIA PHẦN TRỐNG y như \`v-spacer\`. Hệ quả: control bị kéo dãn, hở khoảng giữa các nút, nhóm cuối hàng không sát mép phải. Chuẩn cho hàng nhiều control: MỌI cột \`"col": "shrink"\`, trừ đúng \`v-spacer\`.`)})}function pg(e,t,n){for(let[o,s]of Object.entries(e)){if(o==="set")continue;Be(s,(r,i)=>{let a=Object.keys(r),c=Yl.filter((h)=>a.includes(h));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((h)=>!u.has(h));if(d.length>0){t.push(`${i} gộp ${c.length} lệnh trong MỘT object \`{}\` (${c.join(", ")}) — chỉ \`${l}\` chạy, còn ${d.map((h)=>`\`${h}\``).join(", ")} bị bỏ IM LẶNG (không JS error, không [Vue warn]). \`vueAction\` dò key lệnh theo thứ tự cố định API → CALL → EXE → FUN → IF → MESS → MESSBOX và \`return\` ngay ở nhánh đầu tiên khớp, nên thứ tự viết không có tác dụng. Nhiều lệnh có thứ tự trước sau thì phải là MẢNG, mỗi lệnh một object: \`[{ "${c[0]}": … }, { "${c[1]}": … }]\`.`);return}let p=a.filter((h)=>!u.has(h)&&!dg.has(h)&&!Yl.includes(h));if(p.length>0)n.push(`${i} có lệnh \`${l}\` nằm chung object với key gán dữ liệu ${p.map((h)=>`\`${h}\``).join(", ")} — fragment gán chạy ở nhánh CUỐI của \`vueAction\`, nhánh đó chỉ tới lượt khi KHÔNG key lệnh nào khớp, nên các key này không bao giờ được gán. Tách thành mảng: \`[{ ${p.map((h)=>`"${h}": …`).join(", ")} }, { "${l}": … }]\`.`)},`$.${o}`)}}function mg(e,t){let n=[],o=[];if(t.schemaWarning)o.push(t.schemaWarning);if(!j(e))return{errors:["module.json gốc phải là một object với các khối data/watch/controls/set."],warnings:o,stats:gi()};let s=e,r=new Set(["data","watch","controls","set"]);for(let g of Object.keys(s))if(!r.has(g))o.push(`Khối cấp 1 không chuẩn: "${g}" — module.json chỉ có data/watch/controls/set (xem module-json-anatomy.md).`);if(s.data!==void 0&&!Array.isArray(s.data))n.push(`"data" phải là MẢNG (array), hiện là ${typeof s.data}.`);if(s.watch!==void 0&&!j(s.watch))n.push(`"watch" phải là OBJECT, hiện là ${Array.isArray(s.watch)?"array":typeof s.watch}.`);if(s.controls!==void 0&&!Array.isArray(s.controls))n.push(`"controls" phải là MẢNG container, hiện là ${typeof s.controls}.`);if(s.set!==void 0&&!j(s.set))n.push(`"set" phải là OBJECT, hiện là ${Array.isArray(s.set)?"array":typeof s.set}.`);let i=new Set(zl),a=new Set,c=Array.isArray(s.data)?s.data:[];for(let g of c){if(!j(g)){o.push(`data[] chứa phần tử không phải object: ${JSON.stringify(g)?.slice(0,60)}`);continue}for(let[b,T]of Object.entries(g)){if(Fn.has(b))continue;if(b.startsWith("#"))continue;if(wi(T))a.add(b);else i.add(ut(b))}}let l=0;Be(s,(g,b,T)=>{if(T)return;for(let C of["OUT","ERROROUT"]){let S=g[C];if(typeof S==="string"){if(Co(S))i.add(ut(S));else if(S.trim())l++}else if(j(S))for(let N of Object.keys(S))i.add(ut(N))}});let u=new Map,d=new Map;Be(s,(g,b,T)=>{let C=g.CALL;if(typeof C==="string"){if(Co(C))(T?d:u).set(ut(C),b);else if(C.trim()&&!T)l++}let S=g.API;if(typeof S==="string"&&S.trim()&&!S.startsWith("/")&&!Co(S))l++;for(let[N,x]of Object.entries(g)){if(typeof x!=="string")continue;let A=T||N.startsWith("#")?d:u;for(let Y of x.matchAll(/\bCALL\(\s*([A-Za-z_$][\w$.]*)\s*\)/g))A.set(ut(Y[1]),b)}});for(let[g,b]of u)if(!a.has(g))n.push(`CALL "${g}" (tại ${b}) không khớp named action nào trong data[]. Named actions hiện có: ${[...a].join(", ")||"(không có)"}.`);let p=0,h=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ải là container có "rows" (grid bắt buộc: container > rows > cols).`);continue}for(let T=0;T<b.rows.length;T++){let C=b.rows[T];if(!j(C)||!Array.isArray(C.cols)){n.push(`controls[${g}].rows[${T}] phải có "cols" (mảng Control Object).`);continue}for(let S=0;S<C.cols.length;S++){let N=C.cols[S];if(h++,!j(N))continue;if(N.w===void 0&&N.el!==void 0)o.push(`controls[${g}].rows[${T}].cols[${S}] (el: ${String(N.el)}) thiếu "w" — bắt buộc cho item trong cols.`)}}}let y=new Set;if(Be(s,(g,b)=>{if("children"in g&&(("el"in g)||("rows"in g)||("cols"in g)))n.push(`"children" không được hỗ trợ (tại ${b}) — dùng "innerHTML" (mảng Control Object) để lồng.`);let T=g.el;if(typeof T==="string"&&T){if(y.add(T),(T.startsWith("f-")||T.startsWith("fp-"))&&!Gf.has(T)&&!t.localComponents.has(T))o.push(`Component "${T}" (tại ${b}) không có trong registry FUI (component-quickref.md) và không thấy trong components local — kiểm tra chính tả hoặc chạy component_list.`);else if(T.startsWith("t-")&&!Gl.has(T))o.push(`Cell component "${T}" (tại ${b}) không có trong danh sách t-* hợp lệ: ${[...Gl].join(", ")}.`);else if(T.startsWith("uc-")&&!t.localComponents.has(T))o.push(`Custom component "${T}" (tại ${b}) chưa thấy file .vue/local metadata — tạo component trước hoặc chạy 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ại ${b}) — module.json KHÔNG hỗ trợ shorthand "@"; dùng "v-on:${S.slice(1)}".`);else if(S.startsWith("#"))n.push(`attr "${S}" (tại ${b}) — module.json KHÔNG hỗ trợ slot shorthand "#"; dùng "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ại ${b}) — module thực tế gần như không dùng style ngoài width (controls-styling-vocabulary.md). Cân nhắc bỏ.`)}else if((S.startsWith(":")||S.startsWith("v-bind:"))&&typeof N==="string"&&!Xf(N))l++;else if(S==="v-model"&&typeof N==="string"){if(Co(N)){let x=ut(N);if(!i.has(x)&&!a.has(x))o.push(`v-model "${N}" (tại ${b}) — biến gốc "${x}" chưa khai báo trong data[]. Khai báo state trước để reactive hoạt động đúng.`)}else if(N.trim())l++}Zf(S,N,b,n)}}),j(s.watch)){let g=(b,T)=>{for(let C of Object.keys(b)){if(C==="deep-watch"){if(j(b[C]))g(b[C],`${T}.deep-watch`);continue}let S=ut(C);if(!i.has(S))o.push(`watch "${C}" (${T}) — biến "${S}" chưa khai báo trong data[], watcher sẽ không bao giờ chạy.`)}};g(s.watch,"watch")}for(let g of y){let b=zf[g];if(!b)continue;let T=b.filter((C)=>!t.importsText.includes(C.toLowerCase()));if(T.length>0)n.push(`"${g}" cần import: ${T.join(", ")} — chưa thấy trong _imports.json (module lẫn project). Dùng file_import_list/file_import_new (xem component-quickref.md).`)}let v=0,_=new Set;if(t.schemaProcs){let g=new Set;for(let b of t.schemaProcs.values())for(let T of b)g.add(T);if(Be(s,(b,T)=>{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){_.add(A[0]);return}S=A[1]==="auth"?A[2]??null:A[1]}else if(Co(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ại ${T}) — không tìm thấy spAPI_${S} trong schema local. Deploy SP trước hoặc chạy db_schema_get (bỏ qua nếu API thuộc backend khác).`)}),_.size>0){let b=[..._].sort();o.push(`${b.length} alias được gọi nhưng chưa có kết nối local — KHÔNG đối chiếu được tên SP: ${b.join(", ")}. `+`Kết nối từng cái bằng db_connect({ apiName: "${b[0]}", apiDomain: "…", dbToken: "…" }) để bước kiểm này chạy (bỏ qua nếu alias thuộc backend của nhóm khác).`)}}let I=sg(s,t,o);return ig(s,o),lg(s,o),pg(s,n,o),{errors:n,warnings:o,stats:{stateKeys:i.size-zl.size,namedActions:a.size,containers:p,controls:h,callRefs:u.size,apiRefs:v,crossWindowCalls:d.size,dynamicRefs:l,rightRefs:I}}}function gi(){return{stateKeys:0,namedActions:0,containers:0,controls:0,callRefs:0,apiRefs:0,crossWindowCalls:0,dynamicRefs:0,rightRefs:0}}function hg(e){return e.match(/^CREATE (?:PROCEDURE|FUNCTION)\s+(\S+)\s*\(/i)?.[1]??null}async function fg(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 We(c))n.add(u.replace(/\.vue$/i,""));let l=await V(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 U(St.join(c,"imports","_imports.json")));let s=await V(St.join(t,"_db","_connections.json"),null),r=new Map,i=[];for(let c of Object.keys(s?.connections??{})){let l=await V(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(hg).filter((d)=>d!==null)))}let a=await cn(t);return{localComponents:n,importsText:o.join(`
153
+ `).toLowerCase(),schemaProcs:r.size>0?r:null,schemaWarning:i.length>0?`_db/${i.join("/schema.json, _db/")}/schema.json sai định dạng (bản cache cũ) — bỏ qua bước đối chiếu tên API với stored procedure ở ${i.length>1?"các kết nối này":"kết nối này"}. Chạy db_schema_get để lấy bản mới.`:null,definedFunctionRights:a.functionRights,definedSystemRights:a.systemRights}}async function kr(e){let t=await U(St.join(e,"module.json"));if(!t.trim())return{errors:[`module.json rỗng hoặc không tồn tại trong ${e}.`],warnings:[],stats:gi()};let n;try{n=JSON.parse(t)}catch(s){return{errors:[`module.json có lỗi JSON syntax: ${s.message}`],warnings:[],stats:gi()}}let o=await fg(e);return mg(n,o)}function vr(e){let t=[];if(e.errors.length===0&&e.warnings.length===0)t.push("✅ module.json hợp lệ — không phát hiện lỗi ngữ nghĩa.");else{if(e.errors.length>0)t.push(`❌ ${e.errors.length} lỗi (sẽ hỏng khi chạy):`),t.push(...e.errors.map((o)=>` - ${o}`));if(e.warnings.length>0){if(t.length)t.push("");t.push(`⚠️ ${e.warnings.length} cảnh báo (nên xem lại):`),t.push(...e.warnings.map((o)=>` - ${o}`))}}let n=e.stats;if(t.push(""),n.dynamicRefs>0)t.push(`ℹ️ ${n.dynamicRefs} biểu thức động (\`:attr\` phức tạp, CALL/API/v-model/OUT dạng \`{{…}}\`/gọi hàm/template string) KHÔNG kiểm tĩnh được — biến bên trong chúng không được đối chiếu với data[]. "Hợp lệ" ở đây KHÔNG có nghĩa là chạy đúng: dùng module_simulate để phủ phần này.`);return t.push(`Thống kê: ${n.stateKeys} state · ${n.namedActions} named action · ${n.containers} container / ${n.controls} control · ${n.callRefs} CALL ref${n.apiRefs?` · ${n.apiRefs} API ref đối chiếu schema`:""}${n.crossWindowCalls?` · ${n.crossWindowCalls} CALL xuyên cửa sổ (thuộc module khác, không kiểm được từ đây)`:""}${n.rightRefs?` · ${n.rightRefs} mã quyền đối chiếu _db/rights.json`:""}`),t.join(`
154
+ `)}var Gf,Gl,zf,Fn,zl,Qf,eg="(chuẩn mặc định fui/DESIGN.md — chỉ giữ nguyên nếu user yêu cầu rõ kiểu khác)",tg,ng,og,rg,Xl,ag=12,fi=25,Ql,Yl,dg;var Ln=R(()=>{lt();br();Gf=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"]),Gl=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"]),zf={"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"]},Fn=new Set(["API","CALL","IF","FUN","EXE","MESS","MESSBOX","ARRAY","CONFIRM","IN","OUT","THEN","ELSE","CALLBACK","ERROROUT","ERRORCALLBACK","CANCEL","METHOD","HEADER","COL"]),zl=new Set(["item","index","v_old","user","vueData","p_router","p_routers","v_Set","moment"]);Qf=[{re:/\bfunction\s*\(/,what:'khai báo "function(...)"'},{re:/\bfunction\s+[A-Za-z_$]/,what:'khai báo "function ten(...)"'},{re:/\breturn\b/,what:'lệnh "return"'},{re:/\bvar\s+[A-Za-z_$]/,what:'khai báo "var"'},{re:/\blet\s+[A-Za-z_$]/,what:'khai báo "let"'},{re:/\bconst\s+[A-Za-z_$]/,what:'khai báo "const"'}];tg=new Set([400,550,600,650,800]),ng=/(xóa|xoá|\bdelete\b|\bremove\b)/i,og=/(delete|remove|xoa)/i,rg=new Set(["v-text-field","v-textarea","v-select","v-combobox","v-autocomplete","f-date","f-time","f-search"]),Xl=new Set(["9"]);Ql=/\b(shrink|flex-grow-0)\b/;Yl=["API","CALL","EXE","FUN","IF","MESS","MESSBOX"],dg=new Set(["IN","OUT","HEADER","METHOD","CALLBACK","ERROROUT","ERRORCALLBACK","THEN","ELSE","CONFIRM","CANCEL","ARRAY","COL","MAP"])});function re(e,t){let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t)+"…":n}function Ro(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?"{}":`{${re(t.join(","),40)}}`}if(typeof e==="string")return`"${re(e,30)}"`;return String(e)}function Ue(e,t=0){if(t>3)return"…";if(typeof e==="string")return re(e,70);if(Array.isArray(e))return e.map((o)=>Ue(o,t+1)).join(" ; ");if(!j(e))return re(JSON.stringify(e)??"",60);let n=[];if(e.CONFIRM!==void 0)n.push(`CONFIRM "${re(String(e.CONFIRM),40)}"`);if(e.IF!==void 0)n.push(`IF(${re(String(e.IF),50)})`);if(e.API!==void 0)n.push(`API ${re(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)?re(Object.keys(e.IN).join(","),50):re(String(e.IN),40)}}`);if(e.OUT!==void 0)n.push(`OUT→${j(e.OUT)?Object.keys(e.OUT).join(","):String(e.OUT)}`);if(e.ARRAY!==void 0)n.push(`ARRAY ${re(String(e.ARRAY),30)}${e.COL!==void 0?` COL ${re(String(e.COL),30)}`:""}`);if(e.CALL!==void 0)n.push(`CALL ${re(String(e.CALL),40)}`);if(e.FUN!==void 0)n.push(`FUN ${re(String(e.FUN),40)}`);if(e.EXE!==void 0)n.push(`EXE ${re(String(e.EXE),70)}`);if(e.MESS!==void 0)n.push(`MESS "${re(String(e.MESS),40)}"`);if(e.MESSBOX!==void 0)n.push(`MESSBOX "${re(String(e.MESSBOX),40)}"`);if(e.THEN!==void 0)n.push(`THEN[${Ue(e.THEN,t+1)}]`);if(e.ELSE!==void 0)n.push(`ELSE[${Ue(e.ELSE,t+1)}]`);if(e.CALLBACK!==void 0)n.push(`CALLBACK[${Ue(e.CALLBACK,t+1)}]`);if(e.ERROROUT!==void 0)n.push(`ERROROUT→${String(e.ERROROUT)}`);if(e.ERRORCALLBACK!==void 0)n.push(`ERRORCALLBACK[${Ue(e.ERRORCALLBACK,t+1)}]`);if(e.CANCEL!==void 0)n.push(`CANCEL[${Ue(e.CANCEL,t+1)}]`);return n.join(" ")||re(JSON.stringify(e)??"",60)}function wg(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 gg){let i=s[r];if(i===void 0)continue;t.push(`${r}=${typeof i==="string"?re(i,45):Ro(i)}`)}for(let[r,i]of Object.entries(s)){if(!r.startsWith("v-on:"))continue;let a=r.slice(5);t.push(`${a}→${typeof i==="string"?re(i,60):Ue(i)}`)}for(let r of["innerText","html","text"])if(typeof e[r]==="string"&&e[r])t.push(`"${re(String(e[r]),35)}"`);return t.join(" ")}function eu(e,t,n,o){let s=o?"└─":"├─",r=t+(o?" ":"│ ");if(!j(e)){n.push(`${t}${s} ${re(JSON.stringify(e)??String(e),60)}`);return}n.push(`${t}${s} ${wg(e)}`);let i=e.innerHTML;if(Array.isArray(i))i.forEach((a,c)=>eu(a,r,n,c===i.length-1));if(Array.isArray(e.rows))tu(e.rows,r,n)}function tu(e,t,n){e.forEach((o,s)=>{if(!j(o)||!Array.isArray(o.cols)){n.push(`${t}row ${s+1}: ❌ thiếu "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=` (Σw=${u}${u>12?" ⚠ vượt 12":""})`}let l=[];for(let u of["v-if","v-show","prop"])if(typeof o[u]==="string")l.push(`${u}=${re(String(o[u]),40)}`);n.push(`${t}row ${s+1}${l.length?` [${l.join(" ")}]`:""}${c}`),r.forEach((u,d)=>eu(u,t+" ",n,d===r.length-1))})}function nu(e){if(!j(e))return"❌ module.json gốc không phải 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((h)=>Fn.has(h))){r.push(d);continue}for(let[h,w]of Object.entries(d)){if(Fn.has(h)){r.push({[h]:w});continue}if(h.startsWith("#"))continue;if(wi(w))s.set(h,w);else o.set(ut(h),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("═══ LAYOUT (controls) ═══");let l=Array.isArray(t.controls)?t.controls:[];if(l.length===0)n.push("(không có controls)");let u=[];if(l.forEach((d,p)=>{if(!j(d)){n.push(`[container ${p+1}] ❌ không phải object`);return}let h=typeof d.el==="string"?d.el:"",w=h==="hidden-container",y=j(d.attr)?d.attr:{},v=[];if(h)v.push(`el=${h}`);if(typeof d.prop==="string")v.push(`prop="${re(d.prop,40)}"`);for(let _ of["v-if","v-show","v-model"]){let I=d[_]??y[_];if(typeof I==="string")v.push(`${_}=${re(I,40)}`)}if(n.push(`[container ${p+1}${w?" — DIALOG":""}] ${v.join(" ")||"(mặc định)"}`),w){let _=d["v-model"]??y["v-model"];if(typeof _==="string")u.push({vModel:ut(_),where:`container ${p+1}`});else u.push({vModel:"",where:`container ${p+1}`})}if(Array.isArray(d.rows))tu(d.rows," ",n);else n.push(' ❌ thiếu "rows"')}),n.push("","═══ STATE (data) ═══"),o.size===0)n.push("(không có state)");for(let[d,p]of o){let h=c(d);n.push(`${d} = ${Ro(p)}${h?"":" ⚠ không thấy dùng ở controls/watch/actions"}`)}if(n.push("","═══ ACTIONS ═══"),s.size===0&&r.length===0)n.push("(không có action)");for(let[d,p]of s){let h=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(h);n.push(`${d}: ${Ue(p)}${w?"":" ⚠ không thấy nơi gọi (CALL/watch/v-on)"}`)}if(r.forEach((d,p)=>{n.push(`(auto-startup ${p+1}): ${Ue(d)}`)}),j(t.watch)&&Object.keys(t.watch).length>0){n.push("","═══ WATCH ═══");let d=(p,h)=>{for(let[w,y]of Object.entries(p)){if(w==="deep-watch"&&j(y)){d(y,"deep ");continue}n.push(`${h}${w} → ${Ue(y)}`)}};d(t.watch,"")}if(u.length>0){n.push("","═══ DIALOGS ═══");let d=JSON.stringify(t)??"";for(let p of u){if(!p.vModel){n.push(`${p.where}: ⚠ hidden-container không có v-model — không điều khiển mở/đóng được`);continue}let h=p.vModel.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),w=new RegExp(`\\b${h}\\b\\s*=\\s*true`).test(d);n.push(`${p.where}: v-model=${p.vModel}${w?` — có lệnh mở (${p.vModel} = true)`:` ⚠ không thấy lệnh mở "${p.vModel} = true" ở đâu`}`)}}if(j(t.set)&&Object.keys(t.set).length>0){n.push("","═══ SET (override) ═══");for(let[d,p]of Object.entries(t.set))n.push(`${d} = ${typeof p==="string"?re(p,60):Ro(p)}`)}return n.push("",`Thống kê: ${o.size} state · ${s.size} named action · ${r.length} auto-startup · ${l.length} container · ${u.length} dialog`),n.join(`
155
+ `)}var gg;var yi=R(()=>{Ln();gg=["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 Xe(e){let t=e;for(let[n,o]of yg)t=t.replace(n,o);for(let[n,o]of vg)t=t.replaceAll(n,o);return t.replace(/`(project_sync|module_get|file_import_list)`/g,(n,o)=>`\`${bg[o]}\``)}var yg,bg,kg,vg;var Sr=R(()=>{yg=[[/cũ (\d+) ngày/g,"$1 days old"],[/cũ (\d+) giờ/g,"$1 h old"],[/\(control trong f-table — dùng row (\d+) của /g,"(control in f-table; using row $1 of "],[/(✘ STEP \d+) lỗi: /g,"$1 error: "]],bg={project_sync:"fui project sync",module_get:"fui module get",file_import_list:"fui import list"},kg={'khai báo "function(...)"':'"function(...)" declaration','khai báo "function ten(...)"':'"function name(...)" declaration','lệnh "return"':'"return" statement','khai báo "var"':'"var" declaration','khai báo "let"':'"let" declaration','khai báo "const"':'"const" declaration','dấu ";" nối nhiều câu lệnh':'";" joining statements','" (tại ':'" (at ',") chứa ":") contains "," — giá trị binding đi thẳng vào template compiler của Vue, ":" — Vue compiles a binding as ONE expression ","chỉ nhận MỘT BIỂU THỨC thuần (object/array literal, ternary, chuỗi .map()/.filter() arrow, truy cập thuộc tính). ":"(literal, ternary, .map()/.filter() arrow, property access). ","Statement ở đây làm compile template hỏng → TRANG TRẮNG mà không có JS error hay [Vue warn] nào. ":"A statement breaks the template: BLANK PAGE with no JS error or [Vue warn]. ",'Cách sửa: đưa logic vào một hàm trong script.js (function buildX(){ … vueData.x = …; }), gọi qua "EXE": "buildX()" ':'Fix: move the logic into a script.js function (function buildX(){ … vueData.x = …; }), call it with "EXE": "buildX()" ','trong CALLBACK của action nạp dữ liệu, rồi "':'in the load action CALLBACK, and point "','" chỉ trỏ tới biến vueData đó (vd "':'" at that vueData var (e.g. "',"Chi tiết: references/advanced-techniques.md.":"See: fui skill ref references/advanced-techniques.md.","(chuẩn mặc định fui/DESIGN.md — chỉ giữ nguyên nếu user yêu cầu rõ kiểu khác)":"(fui/DESIGN.md default; keep only if the user asked for another style)","Module gác quyền bằng ":"Module gates on "," mã literal (":" literal right codes (",") nhưng chưa có cache _db/rights.json ":") but _db/rights.json is not cached, ","để đối chiếu — KHÔNG kiểm được mã nào có thật. Mã không tồn tại làm rightTest() luôn trả false: ":"so none can be checked. An unknown code makes rightTest() always false: ","control/menu biến mất im lặng, không lỗi JS. Chạy right_function_list + right_system_list (một lần cho mỗi project) rồi validate lại.":"the control/menu silently disappears. Run fui right function list and fui right system list (once per project), then validate again.",'" chưa được định nghĩa cho project (_db/rights.json có ':'" is not defined for the project (_db/rights.json has '," mã: ":" codes: "," mã). ":" codes). ","rightTest() sẽ luôn trả false → control/menu gác bằng mã này biến mất mà không báo lỗi. ":"rightTest() is always false, so controls gated by it silently disappear. ","Tạo mã: right_function_new({ functionCode: ":"Create: fui right function new --code ",', functionName: "…" }); hoặc chạy right_function_list nếu mã đã có trên server mà cache cũ.':' --name "…", or fui right function list if the cache is stale.',"hoặc chạy right_function_list nếu mã đã có trên server mà cache cũ.":"or fui right function list if the cache is stale."," chưa được định nghĩa cho project (_db/rights.json có: ":" is not defined for the project (_db/rights.json has: ","(chưa mức nào)":"(none)"," — mức 9 luôn hợp lệ":"; 9 is always valid","Không ai được cấp mức này thì control gác bằng nó biến mất im lặng. ":"Nobody holds this level, so gated controls silently disappear. ","Định nghĩa: right_system_new({ sysRight: ":"Define: fui right system new ",', note: "…" }); hoặc chạy right_system_list nếu cache cũ.':' --note "…", or fui right system list if the cache is stale.','Action gọi API "':'Action calling API "',") có vẻ là thao tác XÓA nhưng thiếu CONFIRM — mọi action xóa phải hỏi xác nhận.":") looks like a DELETE without CONFIRM; every delete action must confirm.","Nút/mục xóa (tại ":"Delete button/item (at ",') đang màu "':') is color "','" — nút xóa dùng màu "warning" (cam), không dùng error/đỏ.':'"; delete buttons use "warning" (orange), not error/red.',"v-toolbar (tại ":"v-toolbar (at ",') có color/dark — toolbar tiêu đề phải "dense flat" cùng màu nền với thân dialog, không tô màu/dark riêng.':') has color/dark; a title toolbar must be "dense flat" on the dialog background, no own color/dark.',"Dialog (tại ":"Dialog (at "," — bộ width chuẩn: 400 / 550 / 600 / 650 / 800.":"; standard widths: 400 / 550 / 600 / 650 / 800.","v-card trong dialog (tại ":"v-card in dialog (at ",') có elevation/outlined — bỏ đi: v-dialog đã tự có bóng đổ, thêm nữa là bóng chồng bóng. v-card chỉ cần class "pa-0" (triệt đệm để toolbar tiêu đề sát mép).':') has elevation/outlined; remove it (v-dialog already casts a shadow). v-card needs only class "pa-0".',"Control (tại ":"Control (at ",') mang class "flex-md-grow-0 ml-2" — dấu hiệu pattern label-div rời đã loại bỏ; form dùng floating label ("label" trong attr của 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ại ":" in dialog (at ",') thiếu "label" — form dialog dùng floating label.':') lacks "label"; dialog forms use floating labels.'," (tại ":" (at ",") — phản hồi/thông báo dùng hệ thống có sẵn của FUI: MESS (toast) / MESSBOX (hộp thoại) trong action, CONFIRM để xác nhận — không tự dựng ":"); use FUI feedback instead: MESS (toast) / MESSBOX (dialog) in actions, CONFIRM to confirm; do not build "," trong controls.":" in controls.",') không có ":height" — bảng sẽ tự giãn theo số dòng và làm cả TRANG cuộn. Bảng data chính nên đặt `":height": -16` (fill tới đáy màn hình, chừa 16px — component tự đo vị trí thật, không cần tính tay); trong dialog dùng số dương (px cố định, vd 360). Bảng ngắn/lookup chủ ý tự giãn thì bỏ 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ính ":height" thủ công theo viewport (`':') computes ":height" from the viewport (`',"`) — không cần nữa: ":"`); not needed: ",' tự đo vị trí thật của bảng sau layout và tự cập nhật khi resize. Thay bằng `":height": -16` (số âm = khoảng chừa ở đáy).':' measures itself after layout and on resize. Use `":height": -16` (negative = bottom gap).','Nút "':'Button "','" trong v-card-actions (tại ':'" in v-card-actions (at ',") — dialog chuẩn chỉ dùng icon close trên toolbar, không thêm nút Đóng ở actions.":"); standard dialogs close via the toolbar icon only, no Close button in actions.",' có "w": ':' has "w": '," — vùng chết 13–24: V2 hiểu là ":" — dead zone 13–24: V2 reads ",'px, còn V3 sinh class "v-col-':'px, V3 emits class "v-col-','" mà Vuetify 3 không định nghĩa (chỉ có .v-col-1 … .v-col-12) nên cột KHÔNG có bề rộng nào, rơi về chia đều. Dùng 1–12 (chia cột) hoặc >= ':'", which Vuetify 3 lacks (only .v-col-1 … .v-col-12), so the column gets NO width. Use 1–12 (grid) or >= ',' (px, kèm `"col": { "class": "shrink" }`).':' (px, with `"col": { "class": "shrink" }`).',' (px) kèm `"col": { "shrink": ... }` — dạng thuộc tính trần là prop của v-flex (Vuetify 2); Vuetify 3 không có prop đó nên nó trơ và cột vẫn giãn hết chỗ trống. Viết `"col": { "class": "shrink" }` để đúng ở cả 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.'," — số >= ":" — >= "," nghĩa là bề rộng cố định px, nhưng cột grid mặc định flex-grow:1 nên flex-basis ":" means fixed px, but grid columns default to flex-grow:1, so flex-basis ",'px KHÔNG có tác dụng: cột vẫn giãn hết chỗ trống (đo thật: w=300 ra 687px ở V2, 738px ở V3). Thêm `"col": { "class": "shrink" }`.':'px has NO effect and the column still grows (measured: w=300 gives 687px V2, 738px V3). Add `"col": { "class": "shrink" }`.'," — `v-spacer` mà có `shrink` thì co lại còn 0px (đo thật: 24px ở V3, 0px ở V2), không đẩy được nhóm sau sang phải nữa và cả hàng dồn về trái. Bỏ `shrink` khỏi riêng cột này.":" — `v-spacer` with `shrink` collapses to 0px and no longer pushes the next group right. Remove `shrink` from this column.",' nằm cùng hàng với `v-spacer`, `w` rỗng và chỉ có `"col": { "shrink": ... }` — dạng thuộc tính trần là prop của v-flex (Vuetify 2); Vuetify 3 không có prop đó nên cột vẫn giãn và chiếm một phần đều. Viết `"col": "shrink"` (hoặc `{ "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ằm cùng hàng với `v-spacer` nhưng thiếu `shrink` — `w` rỗng KHÔNG có nghĩa "tự co theo nội dung": runtime bọc phần bề rộng trong `if (c.w)` nên cột này không có class grid lẫn flex-basis, còn trơ `flex-grow: 1` và được CHIA PHẦN TRỐNG y như `v-spacer`. Hệ quả: control bị kéo dãn, hở khoảng giữa các nút, nhóm cuối hàng không sát mép phải. Chuẩn cho hàng nhiều control: MỌI cột `"col": "shrink"`, trừ đúng `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ộp ":" puts "," lệnh trong MỘT object `{}` (":" commands in ONE object `{}` (",") — chỉ `":"); only `","` chạy, còn ":"` runs, ",' bị bỏ IM LẶNG (không JS error, không [Vue warn]). `vueAction` dò key lệnh theo thứ tự cố định API → CALL → EXE → FUN → IF → MESS → MESSBOX và `return` ngay ở nhánh đầu tiên khớp, nên thứ tự viết không có tác dụng. Nhiều lệnh có thứ tự trước sau thì phải là MẢNG, mỗi lệnh một object: `[{ "':' are SILENTLY dropped: `vueAction` checks API → CALL → EXE → FUN → IF → MESS → MESSBOX and runs only the first match. Ordered commands need an ARRAY, one per object: `[{ "'," có lệnh `":" has command `","` nằm chung object với key gán dữ liệu ":"` in the same object as data keys "," — fragment gán chạy ở nhánh CUỐI của `vueAction`, nhánh đó chỉ tới lượt khi KHÔNG key lệnh nào khớp, nên các key này không bao giờ được gán. Tách thành mảng: `[{ ":" — `vueAction` assigns only when NO command key matches, so these keys are never set. Split into an array: `[{ ","module.json gốc phải là một object với các khối data/watch/controls/set.":"module.json root must be an object with data/watch/controls/set.",'Khối cấp 1 không chuẩn: "':'Unknown top-level key: "','" — module.json chỉ có data/watch/controls/set (xem module-json-anatomy.md).':'"; module.json has only data/watch/controls/set (see module-json-anatomy.md).','"data" phải là MẢNG (array), hiện là ':'"data" must be an array, got ','"watch" phải là OBJECT, hiện là ':'"watch" must be an object, got ','"controls" phải là MẢNG container, hiện là ':'"controls" must be an array of containers, got ','"set" phải là OBJECT, hiện là ':'"set" must be an object, got ',"data[] chứa phần tử không phải object: ":"data[] has a non-object item: ",") không khớp named action nào trong data[]. Named actions hiện có: ":") matches no named action in data[]. Named actions: ","(không có)":"(none)",'] phải là container có "rows" (grid bắt buộc: container > rows > cols).':'] must be a container with "rows" (container > rows > cols).','] phải có "cols" (mảng Control Object).':'] needs "cols" (array of Control Objects).',') thiếu "w" — bắt buộc cho item trong cols.':') lacks "w" (required in cols).','"children" không được hỗ trợ (tại ':'"children" is not supported (at ',') — dùng "innerHTML" (mảng Control Object) để lồng.':'); nest with "innerHTML" (array of Control Objects).',") không có trong registry FUI (component-quickref.md) và không thấy trong components local — kiểm tra chính tả hoặc chạy component_list.":") is neither in the FUI registry (component-quickref.md) nor a local component; check spelling or run fui component list.",") không có trong danh sách t-* hợp lệ: ":") is not a valid t-* cell: ",") chưa thấy file .vue/local metadata — tạo component trước hoặc chạy component_list.":") has no .vue/local metadata; create it first or run fui component list.",') — module.json KHÔNG hỗ trợ shorthand "@"; dùng "v-on:':'); module.json has no "@" shorthand; use "v-on:',') — module.json KHÔNG hỗ trợ slot shorthand "#"; dùng "v-slot:':'); module.json has no "#" slot shorthand; use "v-slot:',") — module thực tế gần như không dùng style ngoài width (controls-styling-vocabulary.md). Cân nhắc bỏ.":"); real modules rarely style beyond width (controls-styling-vocabulary.md). Consider removing it.",') — biến gốc "':'); root var "','" chưa khai báo trong data[]. Khai báo state trước để reactive hoạt động đúng.':'" is not declared in data[]; declare it so reactivity works.',') — biến "':') — var "','" chưa khai báo trong data[], watcher sẽ không bao giờ chạy.':'" is not declared in data[]; the watcher never runs.','" cần import: ':'" needs import: '," — chưa thấy trong _imports.json (module lẫn project). Dùng 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).",") — không tìm thấy spAPI_":"); no spAPI_"," trong schema local. Deploy SP trước hoặc chạy db_schema_get (bỏ qua nếu API thuộc backend khác).":" in the local schema. Deploy the SP or run fui schema pull (ignore if another backend)."," alias được gọi nhưng chưa có kết nối local — KHÔNG đối chiếu được tên SP: ":" called alias(es) have no local connection, so SP names are NOT checked: ",'Kết nối từng cái bằng db_connect({ apiName: "':"Connect each: fui db add ",'", apiDomain: "…", dbToken: "…" }) để bước kiểm này chạy (bỏ qua nếu alias thuộc backend của nhóm khác).':" -p <project> --token - --api-domain … (ignore if another team's backend).","/schema.json sai định dạng (bản cache cũ) — bỏ qua bước đối chiếu tên API với stored procedure ở ":"/schema.json has an old format; API names were not checked against SPs for ","các kết nối này":"these connections","kết nối này":"this connection",". Chạy db_schema_get để lấy bản mới.":". Run fui schema pull.","module.json rỗng hoặc không tồn tại trong ":"module.json is empty or missing in ","module.json có lỗi JSON syntax: ":"module.json JSON syntax error: ","✅ module.json hợp lệ — không phát hiện lỗi ngữ nghĩa.":"✅ module.json valid: no semantic errors."," lỗi (sẽ hỏng khi chạy):":" error(s) (break at runtime):"," cảnh báo (nên xem lại):":" warning(s) (review):",' biểu thức động (`:attr` phức tạp, CALL/API/v-model/OUT dạng `{{…}}`/gọi hàm/template string) KHÔNG kiểm tĩnh được — biến bên trong chúng không được đối chiếu với data[]. "Hợp lệ" ở đây KHÔNG có nghĩa là chạy đúng: dùng module_simulate để phủ phần này.':' dynamic expression(s) (complex `:attr`, `{{…}}` CALL/API/v-model/OUT, calls, template strings) are NOT checked statically. "Valid" does NOT mean it runs: use fui module simulate.',"Thống kê: ":"Stats: "," API ref đối chiếu schema":" API ref checked against schema"," CALL xuyên cửa sổ (thuộc module khác, không kiểm được từ đây)":" cross-window CALL (other module, not checked)"," mã quyền đối chiếu _db/rights.json":" right code checked against _db/rights.json","Chưa có cache _db/rights.json — KHÔNG kiểm được ":"_db/rights.json not cached: cannot check whether "," mã FunctionRight (":" FunctionRight code(s) (","có thật hay không. Preview cấp sẵn đúng những mã này nên control vẫn hiện ở ĐÂY; user thật chỉ thấy nếu mã đã ":"exist. The preview grants them, so controls show HERE; real users see them only if the codes are ","được định nghĩa. Chạy right_function_list (một lần cho mỗi project) rồi render lại.":"defined. Run fui right function list (once per project), then render again.","\uD83D\uDEA8 FunctionRight CHƯA ĐƯỢC ĐỊNH NGHĨA trên hệ thống acc: ":"\uD83D\uDEA8 FunctionRight NOT DEFINED in acc: "," — preview cấp sẵn nên control vẫn ":" — the preview grants them, so controls ","hiện ở ĐÂY, nhưng chạy thật thì rightTest() luôn trả false → control/menu biến mất mà không có lỗi JS nào. ":"show HERE, but live rightTest() is false and they silently disappear. ",'Tạo mã: right_function_new({ functionCode: …, functionName: "…" }); hoặc right_function_list nếu cache cũ.':'Create: fui right function new --code … --name "…", or fui right function list if the cache is stale.',"\uD83D\uDEA8 CẤU TRÚC MOCK CHƯA ĐƯỢC KIỂM CHỨNG — nghi ngờ tự suy diễn thay vì đọc từ SP thật:":"\uD83D\uDEA8 UNVERIFIED MOCK SHAPE: likely guessed instead of read from the real SP:",'"] khớp tên SP CÓ THẬT "':'"] matches REAL SP "','" trong schema local, nhưng SP đó chưa từng được db_sp_verify đọc thân trong phiên này.':'", whose body fui sp verify has not read in this session.',' → Chạy db_sp_verify({ name: "':" → Run fui sp verify ",'" }) để đọc thân SP thật rồi COPY NGUYÊN VĂN snippet apiMocks nó sinh sẵn — không tự đoán tên cột/shape cho SP đã tồn tại, kết quả mô phỏng sẽ lệch với thực tế.':" and copy its apiMocks snippet VERBATIM; do not guess columns/shape of an existing SP.",") — gọi tAPI thật thành công":"): live tAPI call OK",") gọi thật thất bại (":") live call failed (",") — rơi xuống cache/tự sinh":"); fell back to cache/generated",") có lệnh ghi — KHÔNG gọi thật, dùng cache/tự sinh":") writes: NOT called live, cache/generated used",") có đoạn đọc tĩnh không nhìn xuyên qua được (SQL động / gọi SP khác) — KHÔNG gọi thật, dùng cache/tự sinh":") has opaque reads (dynamic SQL / nested SP): NOT called live, cache/generated used",") chưa có thân SP local để phân loại an toàn — KHÔNG gọi thật, dùng cache/tự sinh":") has no local body to classify: NOT called live, cache/generated used",") đã đạt trần ":") hit the cap of "," lượt gọi thật/lượt simulate — dùng cache/tự sinh":" live calls per run; cache/generated used",") — dùng _testdata cache (nguồn: ":"): _testdata cache (source: ",", lưu lúc ":", saved ",") — tự sinh mock từ cấu trúc SELECT, đã lưu cache":"): mock generated from the SELECT shape, cached","… (trace bị cắt — quá dài)":"… (trace truncated)",") — stub, không mô phỏng UI thật":"): stub, UI not simulated"," chưa hỗ trợ trong simulator — trả undefined":" unsupported in the simulator: undefined"," — stub (sub-window không mô phỏng)":": stub (sub-window not simulated)","(giả lập user đóng dialog → chạy onclose)":"(simulated dialog close → onclose)",'" → OK (mặc định simulator)':'" → OK (simulator default)','" → HỦY':'" → CANCEL','đọc cross-window "':'cross-window read "','" — không mô phỏng được, gán undefined':'": not simulated, undefined'," (không mô phỏng)":" (not simulated)",'" undefined — CALL trỏ tới action không tồn tại?':'" undefined: CALL to a missing action?',"Lỗi khi chạy action: ":"Action error: ",'" không tồn tại (simulator chỉ có các hàm FUI chuẩn)':'" does not exist (simulator has only standard FUI functions)','" lỗi eval: ':'" eval error: '," → coi như false":" → treated as false",'(mock khớp "':'(mock matched "','không có mock cho API "':'no mock for API "','" — trả mặc định {data: []}':'": default {data: []}'," (mock lỗi)":" (error mock)",'" lỗi không được bắt (không có ERROROUT) → hiện ERROR DIALOG mặc định: ':'" error not handled (no ERROROUT) → default ERROR DIALOG: ',"← (không có OUT)":"← (no OUT)"," thay đổi → ":" changed → ","watch cascade vượt ":"watch cascade over "," vòng — nguy cơ VÒNG LẶP VÔ HẠN giữa các watch/action":" rounds: possible INFINITE LOOP between watch/actions","MỞ \uD83D\uDD32":"OPEN \uD83D\uDD32","ĐÓNG ▪":"CLOSED ▪","Lỗi khi init data[]: ":"data[] init error: ",'không tìm thấy control có label "':'no control labeled "','" kèm v-on:click/:action':'" with v-on:click/:action',"(khớp ":"(matched "," control — dùng #":" controls; using #",'control nằm trong f-table nhưng "':'control is in an f-table but "','" rỗng — item = {} (truyền "item" trong event để chỉ định)':'" is empty: item = {} (pass "item" in the event)',"exe lỗi: ":"exe error: "," không hợp lệ — cần một trong: set / click / call / exe / assert":" invalid: needs one of set / click / call / exe / assert","SystemRight=9 (mặc định, quyền cao nhất)":"SystemRight=9 (default, highest)","] (mặc định — quét được từ module.json)":"] (default: scanned from module.json)","FunctionRight=[] (mặc định — không quét được mã nào trong module.json)":"FunctionRight=[] (default: no codes found in module.json)",". rightTest()/v-if theo FunctionRight khác mã trên sẽ trả false — truyền tham số `user` để test vai trò hạn chế hơn.":". rightTest()/v-if on other FunctionRight codes is false; pass --user to test a narrower role.","═══ KẾT QUẢ ═══":"═══ RESULT ═══","── Cảnh báo ──":"── Warnings ──","(stub đã dùng — kết quả các hàm này KHÔNG phản ánh runtime thật: ":"(stubs used; their results do NOT reflect the real runtime: ","── STATE CUỐI ──":"── FINAL STATE ──"," assert đạt":" assert passed"," · dialog đang mở: ":" · open dialogs: ","\n\n── Live/Cache data (SP chưa có mock, đã tự resolve) ──\n":`
156
+
157
+ ── Live/Cache data (SPs without mocks, auto-resolved) ──
158
+ `,"Phản hồi không phải JSON":"Response is not JSON",': ❌ thiếu "cols"':': ❌ missing "cols"'," ⚠ vượt 12":" ⚠ over 12","❌ module.json gốc không phải object.":"❌ module.json root is not an object.","(không có controls)":"(no controls)","] ❌ không phải object":"] ❌ not an object","(mặc định)":"(default)",' ❌ thiếu "rows"':' ❌ missing "rows"',"(không có state)":"(no state)"," ⚠ không thấy dùng ở controls/watch/actions":" ⚠ unused in controls/watch/actions","(không có action)":"(no actions)"," ⚠ không thấy nơi gọi (CALL/watch/v-on)":" ⚠ never called (CALL/watch/v-on)",": ⚠ hidden-container không có v-model — không điều khiển mở/đóng được":": ⚠ hidden-container without v-model: cannot open/close"," — có lệnh mở (":" — opened by (",' ⚠ không thấy lệnh mở "':' ⚠ no opener "',' = true" ở đâu':' = true" found'," file, tải lúc ":" files, fetched ","API trả danh mục rỗng":"API returned an empty list","cache CŨ (":"STALE cache (","Không gọi được API danh mục framework ":"Framework list API failed for ",") — dùng cache cũ trên đĩa.":"); using the stale disk cache.","danh sách dự phòng hardcode (":"hardcoded fallback list (",") và chưa có cache — ":") and no cache: ","dùng danh sách hardcode trong renderAssets.ts. Danh sách này có thể đã lệch với server.":"using the hardcoded list, which may differ from the server.","projectdata.js tải về từ ":"projectdata.js from ",' không chứa "$projectData =" (':' lacks "$projectData =" (',"không dùng, quay về dựng $projectData + component project từ workspace local.":"ignored; building $projectData + project components from the local workspace.","Không tải được ":"Cannot fetch "," — quay về dựng từ workspace local.":"; building from the local workspace.","StyleCSS cấp project tải về từ ":"Project StyleCSS from "," là HTML chứ không phải CSS — bỏ qua.":" is HTML, not CSS; skipped.","StyleCSS project rỗng (":"Project StyleCSS empty (",") — project chưa có component nào khai style.":"): no project component declares a style."," — CSS component cấp project sẽ dựng từ local.":"; project component CSS comes from local.",'.vue" không có khuôn `export default { … }` — server biên dịch bằng cách ':'.vue" lacks `export default { … }`; the server ',"nối thẳng phần <script> vào `{template:`…`, …}` nên bản trên server có thể lỗi cú pháp. ":"splices <script> into `{template:`…`, …}`, so the server build may have a syntax error. ","renderUI đã bọc an toàn để chạy tiếp, nhưng nên sửa .vue về đúng khuôn.":"The render wraps it to continue; fix the .vue shape."," (bundle server không theo khuôn ComponentBuild)":" (server bundle not in ComponentBuild shape)"," (chỉ đối chiếu được template, không bóc được thân script)":" (template checked only; script body not extractable)",'": FUI không nhận cú pháp @/# trong JSON — đổi thành "v-on:"/"v-slot:". Đã bỏ qua.':'": FUI does not accept @/# in JSON; use "v-on:"/"v-slot:". Skipped.',"Không xác định được projectId. Truyền projectId hoặc chạy session_set trước.":"No project: pass -p or run fui use first.","Không tìm thấy ":"Not found: ",".vue trong ":".vue in "," Có sẵn: ":" Available: "," Thư mục components rỗng.":" The components folder is empty."," Nếu đây là component cấp module, truyền thêm moduleId.":" For a module-level component, also pass -m."," lần render, vượt giới hạn ":" renders, over the limit of ","Mỗi lần render là một phiên browser thật — chia nhỏ thành nhiều lần gọi.":"Each render is a real browser session; split into several calls.","Nguồn: ":"Source: ","(không tên)":"(unnamed)"," lần render":" renders","Ảnh: ":"Screenshots: ","Không gian riêng: chỉ component này + CSS của chính nó + theme/import cấp project.":"Isolated: this component + its own CSS + project theme/imports.","style.css của module KHÔNG nạp — component phải tự đứng được. Tầng project dựng từ workspace":"Module style.css is NOT loaded; the component must stand alone. The project layer comes from the workspace,","local nên bản chưa push vẫn hiện; chạy `project_sync` nếu nghi file local đã cũ.":"so unpushed edits show; run `fui project sync` if local files look stale.","⚠ Ghi chú dựng harness:":"⚠ Harness notes:","file môi trường thiếu":"missing env files","text nghi vấn":"suspect text","ảnh vỡ":"broken images","canvas chết":"dead canvas","text dính/chồng lấn":"text collisions","assert hỏng":"failed asserts","app render RỖNG (trước có nội dung) ↑":"app EMPTY (had content before) ↑","app đã có nội dung ↓":"app has content now ↓","tràn ngang (trước không) ↑":"horizontal overflow (none before) ↑","hết tràn ngang ↓":"no horizontal overflow ↓","Δ so với lượt trước (":"Δ vs previous run (","): KHÔNG chỉ số nào đổi — thay đổi vừa rồi không chạm tới lỗi đang có. Đổi hướng chẩn đoán, đừng render lại cùng một cách.":"): NO metric changed; the last edit missed the failure. Change the diagnosis, do not render again the same way.","): không đổi — cả hai lượt đều sạch.":"): unchanged; both runs clean.","\uD83D\uDED1 ĐANG GIẬM CHÂN — ":"\uD83D\uDED1 STUCK: "," lượt render hỏng liên tiếp mà KHÔNG chỉ số nào đổi.":" failed renders in a row with NO metric change."," Render lại lần nữa theo cùng hướng sẽ cho đúng kết quả này. Chọn MỘT trong ba:":" Rendering again the same way gives the same result. Pick ONE:"," 1. Chạy tầng rẻ hơn được nêu ở dòng ↪ trên — nó chỉ thẳng vào chỗ hỏng.":" 1. Run the cheaper check on the ↪ line above; it points at the fault."," 1. Lùi về tầng tĩnh: module_validate (lỗi quy tắc) → module_outline (cấu trúc) → module_simulate không renderUI (action/watch).":" 1. Step back: fui module validate (rules) → fui module outline (structure) → fui module simulate without --render (actions/watch)."," 2. Đọc lại screenshot + khối lỗi ở cuối report bằng mắt, đừng chỉ đọc con số.":" 2. Look at the screenshot and the error block at the end, not only the numbers."," 3. HỎI NGƯỜI DÙNG. Ba lượt không nhúc nhích thường nghĩa là giả định ban đầu sai, không phải sửa chưa đủ.":" 3. ASK THE USER. Three stuck runs usually mean a wrong assumption, not an incomplete fix.","═══ CHẨN ĐOÁN ═══":"═══ DIAGNOSIS ═══","[MÔI TRƯỜNG] ":"[ENV] "," file import/asset KHÔNG nạp được → sửa phần này TRƯỚC (project_sync / module_get / file_import_*). Lỗi ở hai nhóm dưới có thể chỉ là hệ quả.":" import/asset file(s) NOT loaded; fix this FIRST (fui project sync / fui module get / fui import). Errors below may only be consequences.","[MÔI TRƯỜNG] ✓ mọi import/asset nạp đủ.":"[ENV] ✓ all imports/assets loaded."," lúc init)":" at init)"," assert hỏng":" failed asserts"," → lỗi nằm trong module.json / script.js / component.":" → fault is in module.json / script.js / component.","[MODULE] ✓ không JS error, không Vue warn.":"[MODULE] ✓ no JS error, no Vue warn.","app render RỖNG":"app EMPTY","tràn ngang viewport":"horizontal viewport overflow"," text nghi vấn (undefined/NaN)":" suspect text (undefined/NaN)"," ảnh vỡ":" broken images"," chỗ text dính/chồng lấn":" text collisions","[HIỂN THỊ] ":"[VIEW] "," → xem screenshot; đây là nhóm CHỈ renderUI thấy được.":" → check the screenshot; only --render sees these.","[HIỂN THỊ] ✓ DOM audit sạch.":"[VIEW] ✓ DOM audit clean.","`… is not defined` = một định danh trần không có trên window. Ca thường gặp nhất: `EXE`/`FUN` gọi hàm chưa viết trong script.js — hoặc viết rồi mà quên `module_publish_script` (module_publish_json KHÔNG đẩy script.js). Soát tên hàm trong script.js trước khi đi tìm chỗ khác.":"`… 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ắng mà KHÔNG có JS error/Vue warn là dấu hiệu kinh điển của statement trong một `:attr` (Vue nuốt lỗi compile template) — chạy module_validate, nó bắt tĩnh được ca này.":"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ểu "property is not defined" = biến dùng trong template chưa khai trong data[] — module_validate bắt được (v-model/watch/state), không cần 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ường là OUT/mapData trỏ sai khoá chứ không phải lỗi hiển thị — module_simulate (engine vm) chỉ ra chỗ gán, chạy nhanh hơn nhiều.':'Text "undefined"/"NaN" usually means OUT/mapData uses a wrong key; fui module simulate (no --render) shows the assignment, much faster.',"không rõ tuổi":"unknown age","cũ dưới 1 giờ":"under 1 h old","offline: chưa có bản cache nào cho URL này":"offline: no cached copy of this URL","offline — dùng cache ":"offline: cache ","nội dung ĐỔI so với bản cache ":"content CHANGED vs cache ","không revalidate được (":"revalidate failed (",") — dùng cache ":"); cache ","_moduleInfo.json không có url kèm host — origin lấy từ ":"_moduleInfo.json has no url with a host; origin from ","domain asset mặc định":"default asset domain","Chạy `module_get` để có url thật nếu render sai domain.":"Run `fui module get` for the real url if the domain is wrong.","Project/module phục vụ trên nhiều domain (":"Project/module served on several domains (",') — lần render này chạy ở "':'); this render uses "',"v_Set.domainSetting của domain khác KHÔNG được áp dụng; muốn thử domain khác thì render module có url thuộc domain đó.":"v_Set.domainSetting of other domains is NOT applied; to test another domain, render a module whose url is on it.","(trống)":"(empty)",'" không phải .js/.css — không nạp vào trang render':'" is not .js/.css; not loaded'," import cấp ":" import(s), scope "," không có FileSort trong _imports.json (":", lack FileSort in _imports.json (",") → tạm xếp cuối ":") → sorted last ","). Thứ tự nạp có thể khác production; chạy `":"). Load order may differ from production; run `","` để lấy FileSort thật.":"` for the real FileSort.",': file nội bộ "':': inline file "','" thiếu/rỗng và không có fileID để lấy bản server → KHÔNG nạp được. ':'" missing/empty and no fileID to fetch it → NOT loaded. ',"Chạy `":"Run `","`. Lỗi JS/hiển thị bên dưới có thể chỉ là hệ quả của việc này.":"`. JS/display errors below may only be consequences.",": chưa có bản local → dùng bản trên server. Chạy `":": no local copy → server copy used. Run `","` nếu cần sửa file này.":"` to edit it.",": không có file nội bộ và tên không phải URL/đường dẫn tuyệt đối → không xác định được nguồn để nạp. ":": no inline file and the name is not a URL/absolute path → nothing to load. ","Nếu đây là file tự tạo, chạy `":"If you created it, run `"," — sửa trước khi renderUI.":"; fix it before --render.","module.json rỗng — render shell với module trống":"module.json empty: rendering an empty shell","(bỏ qua — HTMLOnly)":"(skipped: HTMLOnly)",": KHÔNG nhúng (HTMLOnly=true — server cũng không nhúng)":": NOT embedded (HTMLOnly=true, same as the server)",": danh mục từ ":": list from ","HTMLOnly=true → render KHÔNG nhúng framework ":"HTMLOnly=true → no framework ",", KHÔNG nhúng projectdata/StyleCSS cấp project ":", no project projectdata/StyleCSS ","và KHÔNG dựng #fastProjectAPP — giống hệt trang production. Thư viện nào module cần (Vue, jQuery, Vuetify, ":"and no #fastProjectAPP, exactly like production. Libraries the module needs (Vue, jQuery, Vuetify, ","lodash…) phải do CHÍNH module khai làm import; thiếu là trắng trang thật, không phải lỗi của render.":"lodash…) must be module imports; a missing one blanks the real page too.","Tầng project dựng từ workspace LOCAL (chế độ preview cấp component) — không dùng bundle server. ":"Project layer built from the LOCAL workspace (component preview), not the server bundle. ","Chạy `project_sync` nếu nghi file project trong workspace đã cũ.":"Run `fui project sync` if workspace project files look stale."," (đã sửa local, chưa push)":" (edited locally, not pushed)"," (chưa có trên server)":" (not on the server)"," (<style> đã sửa local, chưa push)":" (<style> edited locally, not pushed)","⛔ Component cấp project lệch với bản trên server: ":"⛔ Project components differ from the server: ","renderUI dựng trang bằng bundle project TẢI TỪ SERVER (_aset/.../projectdata) nên bản local chưa push ":"The render uses the project bundle FROM THE SERVER (_aset/.../projectdata), so unpushed local edits ","không xuất hiện — nếu vẫn render, tấm ảnh bạn nhận được là BẢN CŨ, không phải bản vừa sửa.\n\n":`do not show; a render now shows the OLD version.
159
+
160
+ `,"• Nếu bản local là bản bạn vừa sửa → `component_preview` để kiểm chứng riêng component (chạy được bằng ":"• Local is your edit → `fui component preview` checks the component alone (uses ","file local, không cần push), rồi `component_update` từng component, rồi render lại.\n":"local files), then `fui component update` each one, then render again.\n","• Nếu bản trên server mới hơn (ai đó sửa trên web IDE) → `project_sync` để lấy về, ĐỪNG push đè.":"• Server is newer (edited in the web IDE) → `fui project sync`; do NOT overwrite it.","Không đối chiếu được component cấp project: ":"Cannot compare project components: ","Không chặn render, nhưng nếu giao diện khác mong đợi thì đây là chỗ đáng nghi đầu tiên.":"Render continues; if the UI looks wrong, suspect this first.","StyleCSS cấp project tải về RỖNG nhưng component local có khai <style>: ":"Project StyleCSS is EMPTY but local components declare <style>: ","Hoặc các component đó chưa push (`component_update`), hoặc route StyleCSS đang hỏng — ":"Either they are not pushed (`fui component update`) or the StyleCSS route is broken; ","dù nguyên nhân nào thì style cấp project trong lần render này cũng đang thiếu.":"either way project styles are missing in this render.","Không bóc được `var $projectData` khỏi bundle projectdata tải từ server — apiDomain/userInfo ":"Cannot extract `var $projectData` from the server projectdata bundle; apiDomain/userInfo ","trong lần render này suy ra từ project.json local, có thể lệch với bản trang thật đang chạy.":"come from local project.json and may differ from the live page.",'project.json local có apiDomain "':'Local project.json apiDomain "','" nhưng bản trên server là "':'" but the server has "',"render dùng bản server (giống trang thật). Chạy `project_sync` để đồng bộ file local.":"render uses the server value (like the live page). Run `fui project sync`.","(không tầng nào khai)":"(not set anywhere)",'Không tầng nào ($projectGroupSetting → $projectData → module.json set → domainSetting["':'No layer ($projectGroupSetting → $projectData → module.json set → domainSetting["','"]) khai apiDomain cho "':'"]) sets apiDomain for "','" — API sẽ gọi bằng đường dẫn tương đối và apiMocks khớp theo URL ':'"; APIs use relative paths, so URL-matched mocks ',"gần như chắc chắn trượt. Chạy `project_sync` nếu dữ liệu project đã cũ.":"almost surely miss. Run `fui project sync` if project data is stale.","] (mặc định — quét được từ module.json/project)":"] (default: scanned from module.json/project)","FunctionRight=[] (mặc định — không quét được mã nào)":"FunctionRight=[] (default: no codes found)",". rightTest()/v-if theo FunctionRight khác mã trên sẽ ẩn — truyền tham số `user` để test vai trò hạn chế hơn.":". rightTest()/v-if on other FunctionRight codes hides; pass --user to test a narrower role.","projectdata/StyleCSS: KHÔNG nhúng (HTMLOnly=true — server cũng không nhúng)":"projectdata/StyleCSS: NOT embedded (HTMLOnly=true, same as the server)","projectdata/StyleCSS: dựng từ workspace local (chế độ preview cấp component — bản local chưa push vẫn hiện)":"projectdata/StyleCSS: from the local workspace (component preview; unpushed edits show)","projectdata/StyleCSS: dựng từ workspace local (không tải được bản server)":"projectdata/StyleCSS: from the local workspace (server copy unavailable)","gọi tAPI thật (SP ":"live tAPI call (SP ",", nguồn: ":", source: ","tự sinh mock (SP ":"generated mock (SP ","), đã lưu cache":"), cached","Không launch được Chromium: ":"Cannot launch Chromium: ","\n→ Chạy một lần: npx playwright install chromium":`
161
+ → Run once: bunx playwright install chromium`,'runtime điều hướng ra "':'runtime navigated to "','" — đã chặn (thường do login gate / redirect trong action)':'"; blocked (usually a login gate / redirect in an action)','" tải thất bại (':'" failed to load ('," — trả 404 (offline + chưa cache?)":"; answered 404 (offline, not cached?)"," → user preview (userInfo của project, auto-mock)":" → preview user (project userInfo, auto-mock)",'" — trả {data: []}':'": {data: []}'," → {data:[]} (không có mock)":" → {data:[]} (no mock)","module mở WebSocket tới backend thật — đã chặn (renderUI chỉ cho phép gọi thật qua tAPI SP đã xác nhận an toàn, không qua WebSocket)":"module opened a WebSocket to the real backend; blocked (only verified-safe tAPI SPs run live)","Vue chưa mount sau 15s — trang chưa init xong (xem JS error / asset hỏng / API treo bên dưới)":"Vue not mounted after 15s; page init unfinished (see JS errors / broken assets / hung APIs below)"," CẬP NHẬT":" UPDATED"," tải mới":" fetched"," gọi thật":" live"," tự sinh":" generated"," (API không-mock: ":" (unmocked API: ","trang load xong sau ":"page loaded in ",': không tìm thấy phần tử có text "':': no element with text "','" trên DOM':'" in the DOM'," phần tử — dùng #":" elements; using #",'CONFIRM → bấm "':'CONFIRM → click "',"CONFIRM → bấm nút cuối (không nhận diện được label)":"CONFIRM → click the last button (label not recognized)","CONFIRM → bấm nút đầu (không nhận diện được label)":"CONFIRM → click the first button (label not recognized)","global CALL không tồn tại trong runtime này":"global CALL missing in this runtime","chồng lấn":"overlap","dính sát (gap ":"touching (gap ","canvas trong ":"canvas in ","✘ <body> không render được phần tử nào (HTMLOnly — body.html LÀ cả trang)":"✘ <body> rendered nothing (HTMLOnly: body.html IS the page)","✘ #fastProjectAPP rỗng — controls không render":"✘ #fastProjectAPP empty: controls not rendered"," (CÓ file toàn cục nạp thiếu — xem mục MÔI TRƯỜNG THIẾU FILE trước khi nghi module.json)":" (global files FAILED to load; see MISSING ENV FILES before suspecting module.json)"," (kiểm tra pageerror / module.json)":" (check pageerror / module.json)","⚠ trang tràn ngang viewport ":"⚠ page overflows the viewport ","px — phần tử tràn: ":"px; overflowing: ","(không xác định)":"(unknown)","⚠ text nghi vấn hiển thị trên trang: ":"⚠ suspect text on page: "," (binding thiếu data?)":" (binding missing data?)","⚠ ảnh vỡ (load fail nhưng đang chiếm chỗ): ":"⚠ broken images (failed but taking space): ","⚠ canvas chart 0-size — chart init khi container chưa có kích thước: ":"⚠ 0-size chart canvas (init before the container had a size): ","⚠ text dính/chồng lấn (thẩm mỹ — PHẢI sửa, không được bỏ qua): ":"⚠ text collisions (visual; MUST fix): ","RENDER (browser thật): ":"RENDER (real browser): "," · HTMLOnly=true (KHÔNG nhúng framework/projectdata — như production)":" · HTMLOnly=true (no framework/projectdata, like production)","Origin trang: ":"Page origin: ","Import (framework ∪ project ∪ module, sort theo FileSort/FileName): ":"Imports (framework ∪ project ∪ module, by FileSort/FileName): "," nạp được":" loaded"," KHÔNG nạp được":" NOT loaded","── ⛔ MÔI TRƯỜNG THIẾU FILE (đọc TRƯỚC khi sửa module) ──":"── ⛔ MISSING ENV FILES (read BEFORE fixing the module) ──","Các file dưới đây không nạp được vào trang test. Hàm/biến/style toàn cục do chúng":"These files did not load. Their global functions/vars/styles","định nghĩa sẽ không tồn tại → lỗi JS và lỗi hiển thị bên dưới CÓ THỂ chỉ là hệ quả,":"are missing, so JS and display errors below MAY only be consequences,","không phải lỗi của module. Khắc phục phần này trước rồi render lại.":"not module faults. Fix this first, then render again.","── ⛔ ASSET CŨ, KHÔNG XÁC MINH ĐƯỢC (đọc TRƯỚC khi kết luận về platform) ──":"── ⛔ STALE, UNVERIFIED ASSETS (read BEFORE blaming the platform) ──","Không hỏi được server xem các file dưới đây còn đúng không, nên trang test đang chạy":"The server could not confirm these files, so the page ran",'bằng bản trên đĩa. Mọi kết luận kiểu "thư viện X có bug" rút ra từ lượt render này':'on disk copies. Any "library X has a bug" conclusion from this render',"đều KHÔNG dùng được — bản thật trên CDN có thể đã khác. Nối mạng rồi render lại,":"is NOT valid; the CDN copy may differ. Go online and render again,","hoặc ép tải mới bằng refreshAssets: true.":"or force a fetch with --refresh-assets.","── ℹ️ ASSET PLATFORM ĐÃ ĐỔI so với lượt render trước ──":"── ℹ️ PLATFORM ASSETS CHANGED since the previous render ──","Hành vi đổi mà module.json không đụng tới có thể đến từ đây, không phải từ module.":"Behavior changes not caused by module.json may come from here.","── STATE CUỐI (vueData) ──":"── FINAL STATE (vueData) ──"," (đính kèm bên dưới)":" (attached below)"," (quá lớn để inline — dùng Read tool để xem)":" (too large to inline; open it with the Read tool)","Dialog đang mở — chụp riêng: ":"Open dialog screenshot: "," API call (tất cả mock) · ":" API call (all mocked) · "," lúc init) · ":" at init) · "," · dialog đang mở":" · open dialog"},vg=Object.entries(kg).sort(([e],[t])=>t.length-e.length)});import{join as $t}from"node:path";async function Hn(e){let t=ks(await uo(e));return t?[t]:[]}async function ou(e,t){let n=await kr(e);if(n.errors.length===0&&n.warnings.length===0)return[];let o=Xe(vr(n));if(n.errors.length>0&&bn())throw new Pe(`[FUI_MCP_STRICT] ${t} blocked: module.json has semantic errors; fix them and retry
162
+
163
+ ${o}`,"hard","error");return["--- module validate ---",o]}async function un(e){let t=await V($t(e,"_moduleInfo.json"),null),n=t?.ProjectID&&t?.ModuleID?`fui module get -p ${t.ProjectID} -m ${t.ModuleID}`:"fui module get";return Zt(e,"local module",n,"_moduleInfo.json")}async function ru(e){let t=await un(e),n=await kr(e);return{notes:[...t?[t]:[],...await Hn(e)],data:`${Xe(vr(n))}
164
+ `,errorCount:n.errors.length}}async function su(e,t){let o=await(await pe(e)).previewStagedModuleDir(t),s=await e.session.createApproval({projectId:o.moduleInfo.ProjectID,moduleId:o.moduleInfo.ModuleID,moduleDir:t,contentHash:await pr(t)}),r=await un(t),i=await kr(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 ---",Xe(vr(i)),`approval token: ${s.token}`,"ask the user; only after explicit approval run: fui module publish html --approval <token>"]}}async function iu(e,t,n){let o=await an(e.session,"module publish html"),s=(c)=>new Pe(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 pr(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 ou(t,"module publish html"),a=await(await pe(e)).publishStagedModuleDir(t);return await e.session.clearApproval(),{notes:[...o,`ok published ${a.ProjectID}/${a.ModuleID} (${t})`,...i,...await Hn(t)]}}async function jr(e,t){let n=await an(e.session,"module publish json"),o=await U($t(t.moduleDir,"module.json"));if(!o.trim())throw new m("local module.json is empty or missing");try{JSON.parse(o)}catch(r){throw new m(`module.json has invalid JSON syntax: ${r.message}`)}if(await jn(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 ou(t.moduleDir,"module publish json");return await(await e.api()).updateModuleUI(t.projectId,t.moduleId,o),await Me(t.moduleDir,["module.json"]).catch(()=>{return}),{notes:[...n,`ok published module.json to ${t.projectId}/${t.moduleId}`,...s,...await Hn(t.moduleDir)]}}async function Ir(e,t,n){let o=`module publish ${n==="script.js"?"script":"css"}`,s=await an(e.session,o),r=await Zs($t(t.moduleDir,n),n);if(await jn(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 Me(t.moduleDir,[n]).catch(()=>{return}),{notes:[...s,`ok published ${n} to ${t.projectId}/${t.moduleId}`,...await Hn(t.moduleDir)]}}function Un(e){return e.moduleId?`module (${e.moduleId})`:"project (global, loaded in all modules)"}async function au(e,t){let n=await U($t(e.baseDir,"components",`${t}.vue`));if(!n.trim())throw new m(`${t}.vue not found in ${e.baseDir}/components/${e.moduleId?"":"; for module scope pass -m <module>"}`);return n}async function $r(e){return V($t(e,"components","_components.json"),[])}async function cu(e,t){let{count:n,removed:o}=await(await pe(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 Cr(e,t,n){let o=await an(e.session,"component new"),s=await au(t,n),r=await pe(e);await r.syncComponentsLocal(t.projectId,t.moduleId);let i=(await $r(t.baseDir)).find((h)=>h.comName===n);if(i)throw new m(`component "${n}" already exists in ${Un(t)} (ComID ${i.comID}); overwrite it: fui component update ${n}, or pick another name`);let{template:a,script:c,style:l}=Ft(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 $r(t.baseDir)).find((h)=>h.comName===n);return await Me(t.baseDir,[Te($t("components",`${n}.vue`))]).catch(()=>{return}),{notes:[...o,`ok created component ${n} in ${Un(t)} (ComID ${p?.comID??"not visible yet; run fui component list"})`,`${u} component(s) synced${d.length>0?`; removed orphaned: ${d.join(", ")}`:""}`,...await Hn(t.baseDir)]}}async function Rr(e,t,n){let o=await an(e.session,"component update"),s=await au(t,n),r=(await $r(t.baseDir)).find((h)=>h.comName===n)?.comID;if(r===void 0)throw new m(`component "${n}" is not in _components.json of ${Un(t)}, so there is no ComID to overwrite. new component: fui component new ${n}; stale cache: fui component list, then retry`);let i=Te($t("components",`${n}.vue`));if(await jn(t.baseDir,i,s))return{notes:[...o,`skip ${n}.vue unchanged since the last publish/sync`]};let{template:a,script:c,style:l}=Ft(s),u=await pe(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 Me(t.baseDir,[i]).catch(()=>{return}),{notes:[...o,`ok updated component ${n} in ${Un(t)} (ComID ${r})`,`${d} component(s) synced${p.length>0?`; removed orphaned: ${p.join(", ")}`:""}`,...await Hn(t.baseDir)]}}async function lu(e,t,n){let o=(await $r(t.baseDir)).find((a)=>a.comName===n);if(!o)throw new m(`component "${n}" not in the local _components.json of ${Un(t)}; run fui component list first`);let s=await pe(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 ${Un(t)}; ${r} remain`,...i.length>0?[`local files removed (archived): ${i.join(", ")}`]:[]]}}async function uu(e,t){let{id:n,...o}=t,s=await pe(e),r=await s.api.editProject(o),i=await s.projectInfoRefreshNote(pu(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)}
165
+ `}}async function du(e,t){let n=t.id;if(n===void 0||n===null||n==="")throw new m("projectData.id is required to update (set it to the ProjectID); to create a project use fui project new");let o=await pe(e),s=await o.api.editProject(t),r=await o.projectInfoRefreshNote(pu(t,s));return{notes:["ok project updated",...r?[r]:[]],data:`${JSON.stringify(s)}
166
+ `}}function pu(e,t){let n=rn(e)?e.id??e.ProjectID:void 0;if(typeof n==="string"&&n.trim())return n;let o=rn(t)?t.data:void 0,s=Array.isArray(o)?o[0]:o,r=rn(s)?s.ProjectID??s.id:void 0;return typeof r==="string"&&r.trim()?r:null}async function Tr(e,t){let n=await an(e.session,"project publish-config"),o=e.paths.projectDir(t),s=await U($t(o,"project.json"));if(!s.trim())throw new m(`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 m(`${t}/project.json has invalid JSON: ${l.message}; nothing published`)}let{config:i,wasEnveloped:a}=ii(r);if(await jn(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 Me(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)}
167
+ `}}async function Sg(e,t){let n=await U($t(e,"module.json"));if(!n.trim())throw new m(`module.json is empty or missing in ${e}`);try{return JSON.parse(n)}catch(o){throw new m(`module.json has a JSON syntax error: ${o.message}; fix it, ${t} needs valid JSON`)}}async function mu(e){let t=await Sg(e,"outline"),n=await un(e);return{notes:n?[n]:[],data:`Outline: ${e}
168
+
169
+ ${Xe(nu(t))}
170
+ `}}var To=R(()=>{q();lt();yi();Gt();Sr();Ln();_t();In();xn()});function hu(e){let t=e.split(`
171
+ `).filter((o)=>o.trim()),n=t.filter((o)=>$g.some((s)=>o.includes(s))||jg.some((s)=>o.startsWith(s)));return{text:`${n.join(`
173
172
  `)}
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:`;(() => {
173
+ `,kept:n.length,total:t.length}}var $g,jg;var fu=R(()=>{$g=["✘","✔","❌","⚠","❗","\uD83D\uDEA8","Stats:","Screenshot:","screenshot:","Full-page:"],jg=["Simulate:","Render UI:","live ","skip ","cache ","gen "]});import{join as Ig}from"node:path";function gu(e,t){let n=Xr(),o=gn("sha1",`${t}
174
+ ${e}`).slice(0,16);return Ig(n.replace(/\.json$/,""),"last-input",`${o}.json`)}async function wu(e,t){let n=await he(gu(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 yu(e,t,n){await O(gu(e,t),JSON.stringify({moduleDir:e,savedAt:new Date().toISOString(),flags:n},null,2))}function bu(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}}var ku=R(()=>{be();Gt();te()});import{join as Cg}from"node:path";function Rg(e){let t=e.split(/[?#]/)[0],n=t.split("/").filter(Boolean);return(n.length?n[n.length-1]:t).toLowerCase()}function Su(e,t,n){let o=new Map;for(let r of t){o.set(r.toLowerCase(),r);let i=tn(r);if(i)o.set(i.fnName.toLowerCase(),r)}let s=[];for(let r of e){let i=o.get(Rg(r));if(i&&!n.has(i.toLowerCase()))s.push({mockKey:r,spName:i})}return s}function Po(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 bi(e){try{return JSON.parse(await B(e))}catch{return null}}function vu(e){if(!Yt(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 qn(e,t){let n=e.paths,o=(r,i,a)=>Cg(n.projectDir(r),"_testdata",i,`${a}.json`),s=async(r,i,a)=>{let c=await he(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 vu(await bi(n.schemaFile(r,a))))i.add(c);return[...i]},getVerifiedSpNames:()=>t.getVerifiedSpNames(),async getConnectionAlias(r,i){return ke(await e.load(r),i)},async resolveApiSpName(r,i,a){let c=await bi(n.schemaFile(r,i));if(!Yt(c))return null;let l=vu(c),u=[a,`spAPI_${a}`,`spAPI_AUTH_${a}`,`spAPIFILE_${a}`,`spAPIFILE_UPLOAD_${a}`];for(let d of u){let p=l.find((h)=>h.toLowerCase()===d.toLowerCase());if(p)return p}return null},async classifySpSafety(r,i,a){return yc(await s(r,i,a))},async callLiveApiSp(r,i,a,c,l="GET",u,d=Pr){try{let{config:p}=await e.resolve({project:r,db:i}),h=`${Oe(p.apiDomain)}/${i}/${a?"auth/":""}${c}`,w={"content-type":"application/json"};if(!a&&p.userToken)w.authorization=`Bearer ${p.userToken}`;let y=await fetch(h,{method:l,headers:w,body:u!==void 0?JSON.stringify(u):void 0,signal:AbortSignal.timeout(d)}),v=await y.text(),_;try{_=JSON.parse(v)}catch{return{ok:!1,status:y.status,error:"Phản hồi không phải JSON"}}if(!y.ok)return{ok:!1,status:y.status,data:_,error:`HTTP ${y.status}`};return{ok:!0,status:y.status,data:_}}catch(p){return{ok:!1,error:p instanceof Error?p.message:String(p)}}},async readTestData(r,i,a){return await bi(o(r,i,a))},async writeTestData(r,i,a,c,l){try{let u={source:c,savedAt:new Date().toISOString(),data:l};await O(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:wc(c)}}}var dn=8,Pr=5000;var Vn=R(()=>{be();Os();_s();it();lo();te()});import{homedir as Tg}from"node:os";import{join as vi}from"node:path";function Si(e){ki={...e,live:qn(e.store,e.session)}}function Ye(){if(!ki){let e=process.env.FUI_WORKDIR||process.env.FUI_MCP_WORKDIR||vi(Tg(),Kr),t=new oo;Si({store:new yt(new io(e)),session:t,api:Bo,activeTarget:()=>t.getTarget()})}return ki}async function ju(e){return await(await Ye().api()).listFrameworkImportFiles(e)}async function Iu(e,t){let n=await Ye().store.resolve({project:e,db:t.db});return{alias:n.alias,config:{...n.config,apiName:n.alias},note:n.note}}var ki=null,Wn=()=>Ye().store.paths.root,Bn=(e)=>vi(Ye().store.paths.root,wt(e,"projectId")),$i=(e,t)=>vi(Bn(e),"modules",wt(t,"moduleId")),$u=()=>Ye().activeTarget(),Cu=(...e)=>Ye().live.getConnectionAlias(...e),Ru=(...e)=>Ye().live.resolveApiSpName(...e),Tu=(...e)=>Ye().live.classifySpSafety(...e),Pu=(...e)=>Ye().live.readTestData(...e),ji=(...e)=>Ye().live.writeTestData(...e),Eu=(...e)=>Ye().live.generateMockForLocalSp(...e);var Eo=R(()=>{Gr();yn();rs();Vn();Gt();es();it();Vn();lt();br()});import{promises as Ii}from"node:fs";import Du from"node:path";function Eg(e){return Du.join(Wn(),"_cache","framework",`${e}.json`)}function _g(e,t){let n=_u[e]??_u.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 xu(e,t){let n=Eg(e),o;try{o=JSON.parse(await Ii.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<Pg)return{deps:o.data,source:`cache (${o.data.length} file, tải lúc ${o.fetchedAt})`};try{let r=await ju(e);if(r.length===0)throw Error("API trả danh mục rỗng");try{await Ii.mkdir(Du.dirname(n),{recursive:!0}),await Ii.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Ũ (${o.data.length} file, tải lúc ${o.fetchedAt})`,warning:`Không gọi được API danh mục framework ${e} (${r.message}) — dùng cache cũ trên đĩa.`};let i=_g(e,t);return{deps:i,source:`danh sách dự phòng hardcode (${i.length} file)`,warning:`Không gọi được API danh mục framework ${e} (${r.message}) và chưa có cache — `+"dùng danh sách hardcode trong renderAssets.ts. Danh sách này có thể đã lệch với server."}}}function Ag(e){if(e===".css")return"css";if(e===".js")return"js";return}function Ou(e){return[...e].sort((t,n)=>t.sort-n.sort||(t.name<n.name?-1:t.name>n.name?1:0))}function Mu(e,t){let n=Ag(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"}}async function Nu(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 Fu(e,t){let n=t.replace(/^https?:\/\//,"").replace(/\/+$/,""),o=`https://${n}/_aset/fp/auth/projectdata/${e}@${n.replace(/\//g,"")}/${Au}.js`,s=`https://${n}/_aset/fp/auth/StyleCSS/${e}/${Au}.css`,r=[],i=[],[a,c]=await Promise.all([Nu(o),Nu(s)]),l;if(a.ok)if(/\$projectData\s*=/.test(a.text))l=a.text,r.push(`projectdata.js ← ${o} (${a.text.length} bytes)`);else i.push(`projectdata.js tải về từ ${o} không chứa "$projectData =" (${a.text.length} bytes) — `+"không dùng, quay về dựng $projectData + component project từ workspace local.");else i.push(`Không tải được ${o}: ${a.reason} — quay về dựng từ workspace local.`);let u;if(c.ok)if(/<html|<!DOCTYPE/i.test(c.text.slice(0,200)))i.push(`StyleCSS cấp project tải về từ ${s} là HTML chứ không phải CSS — bỏ qua.`);else if(c.text.trim())u=c.text,r.push(`StyleCSS project ← ${s} (${c.text.length} bytes)`);else r.push(`StyleCSS project rỗng (${s}) — project chưa có component nào khai style.`);else i.push(`Không tải được ${s}: ${c.reason} — CSS component cấp project sẽ dựng từ local.`);return{js:l,css:u,jsUrl:o,cssUrl:s,notes:r,problems:i}}function Ci(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 Ng(e){return e.replace(/[\r\n\t]+/g," ").replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function Dg(e){let t=/export\s+default\s*\{([\s\S]*)\}\s*;?\s*$/.exec(e.trim());return t?t[1].trim():void 0}function Lu(e,t){let n=Ci(t,"template")[0]??"",o=Ci(t,"script")[0]??"",s=Ci(t,"style").map((c)=>c.trim()).filter(Boolean).join(`
175
+ `);if(!n&&!o&&t.includes("Vue.component("))return{compiled:{name:e,script:t,css:s}};let r=Dg(o),i=Ng(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:`;(() => {
177
176
  const component = (() => {
178
177
  ${a}
179
178
  })() || {};
180
179
  component.template = \`${i}\`;
181
180
  Vue.component(${JSON.stringify(e)}, component);
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(`;
181
+ })();`,css:s},warning:`component "${e}.vue" không có khuôn \`export default { … }\` — server biên dịch bằng cách `+"nối thẳng phần <script> vào `{template:`…`, …}` nên bản trên server có thể lỗi cú pháp. "+"renderUI đã bọc an toàn để chạy tiếp, nhưng nên sửa .vue về đúng khuôn."}}function Jn(e){return e.replace(/\s+/g," ").trim()}function xg(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 Og(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=xg(e.slice(a,l)),d=r+1<o.length?o[r+1].matchStart:e.length,p,h=e.slice(l+1,d).trim();if(h.startsWith(",")){let w=h.replace(/[;\s]+$/,"");if(w.endsWith("})"))p=w.slice(1,w.length-2).trim()}t.set(i,{template:u,body:p})}return t}function Uu(e,t,n){let o={missing:[],changed:[],changedCss:[],unverifiable:[]},s=Og(t),r=Jn(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ông theo khuôn ComponentBuild)`);continue}if(Jn(i.template??"")!==Jn(c.template)){o.changed.push(i.name);continue}if(c.body===void 0)o.unverifiable.push(`${i.name} (chỉ đối chiếu được template, không bóc được thân script)`);else if(Jn(i.script??"")!==Jn(c.body)){o.changed.push(i.name);continue}let l=Jn(i.css??"");if(l&&!r.includes(l))o.changedCss.push(i.name)}return o}function Ri(e){if(e.length===0)return"";return`${e.map((t)=>t.script).join(`;
183
182
  `)};
184
- `}function ys(e){let t=e.map((n)=>n.css).filter((n)=>n.trim());if(t.length===0)return"";return`${t.join(`
183
+ `}function Ti(e){let t=e.map((n)=>n.css).filter((n)=>n.trim());if(t.length===0)return"";return`${t.join(`
185
184
  `)}
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:
185
+ `}var Pg=86400000,_u,Au="render";var Hu=R(()=>{Eo();_u={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"]}}});import{promises as jt}from"node:fs";import{createHash as Gu}from"node:crypto";import{createRequire as Mg}from"node:module";import z from"node:path";import{pathToFileURL as Fg}from"node:url";function zu(e,t){return z.join(e,`last-vitals-${t}.json`)}async function Bg(e,t){try{let n=await jt.readFile(zu(e,t),"utf8"),o=JSON.parse(n);return typeof o?.jsErrors==="number"?o:null}catch{return null}}function Xu(e,t){let n=[],o=(s,r,i)=>{if(r!==i)n.push(`${s} ${r}→${i}${i<r?" ↓":" ↑"}`)};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ôi trường thiếu",e.envMissing,t.envMissing),o("text nghi vấn",e.suspectTexts,t.suspectTexts),o("ảnh vỡ",e.brokenImages,t.brokenImages),o("canvas chết",e.deadCanvas,t.deadCanvas),o("text dính/chồng lấn",e.textCollisions,t.textCollisions),o("assert hỏng",e.assertFail,t.assertFail),e.appEmpty!==t.appEmpty)n.push(t.appEmpty?"app render RỖNG (trước có nội dung) ↑":"app đã có nội dung ↓");if(e.overflowX!==t.overflowX)n.push(t.overflowX?"tràn ngang (trước không) ↑":"hết tràn ngang ↓");return n}function Jg(e,t){let n=Xu(e,t),o=e.ts.replace("T"," ").slice(0,19);if(n.length>0)return`Δ so với lượt trước (${o}): ${n.join(" · ")}`;return Ai(t)?`Δ so với lượt trước (${o}): KHÔNG chỉ số nào đổi — thay đổi vừa rồi không chạm tới lỗi đang có. Đổi hướng chẩn đoán, đừng render lại cùng một cách.`:`Δ so với lượt trước (${o}): không đổi — cả hai lượt đều sạch.`}function Wu(e,t){let n=new Set;for(let o of e){let s=String(o).split(`
186
+ `)[0].trim();if(s)n.add(s.length>200?s.slice(0,200)+"…":s);if(n.size>=t)break}return[...n]}function Kg(e,t){return["",`\uD83D\uDED1 ĐANG GIẬM CHÂN — ${e} lượt render hỏng liên tiếp mà KHÔNG chỉ số nào đổi.`," Render lại lần nữa theo cùng hướng sẽ cho đúng kết quả này. Chọn MỘT trong ba:",t.length>0?" 1. Chạy tầng rẻ hơn được nêu ở dòng ↪ trên — nó chỉ thẳng vào chỗ hỏng.":" 1. Lùi về tầng tĩnh: module_validate (lỗi quy tắc) → module_outline (cấu trúc) → module_simulate không renderUI (action/watch)."," 2. Đọc lại screenshot + khối lỗi ở cuối report bằng mắt, đừng chỉ đọc con số."," 3. HỎI NGƯỜI DÙNG. Ba lượt không nhúc nhích thường nghĩa là giả định ban đầu sai, không phải sửa chưa đủ."]}function Ai(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 Gg(e,t,n,o){let s=["═══ CHẨN ĐOÁN ═══"];s.push(e.envMissing>0?`[MÔI TRƯỜNG] ${e.envMissing} file import/asset KHÔNG nạp được → sửa phần này TRƯỚC (project_sync / module_get / file_import_*). Lỗi ở hai nhóm dưới có thể chỉ là hệ quả.`:"[MÔI TRƯỜNG] ✓ mọi import/asset nạp đủ.");let r=[];if(e.jsErrors>0)r.push(`${e.jsErrors} JS error${e.initErrors?` (${e.initErrors} lúc init)`:""}`);if(e.vueWarns>0)r.push(`${e.vueWarns} [Vue warn]`);if(e.assertFail>0)r.push(`${e.assertFail} assert hỏng`);s.push(r.length>0?`[MODULE] ${r.join(" · ")} → lỗi nằm trong module.json / script.js / component.`:"[MODULE] ✓ không JS error, không Vue warn.");for(let a of Wu(n,3))s.push(` ✘ ${a}`);for(let a of Wu(o,2))s.push(` ⚠ ${a}`);let i=[];if(e.appEmpty)i.push("app render RỖNG");if(e.overflowX)i.push("tràn ngang viewport");if(e.suspectTexts>0)i.push(`${e.suspectTexts} text nghi vấn (undefined/NaN)`);if(e.brokenImages>0)i.push(`${e.brokenImages} ảnh vỡ`);if(e.deadCanvas>0)i.push(`${e.deadCanvas} canvas chart 0-size`);if(e.textCollisions>0)i.push(`${e.textCollisions} chỗ text dính/chồng lấn`);s.push(i.length>0?`[HIỂN THỊ] ${i.join(" · ")} → xem screenshot; đây là nhóm CHỈ renderUI thấy được.`:"[HIỂN THỊ] ✓ DOM audit sạch.");for(let a of t)s.push(` ↪ ${a}`);return s}function zg(e,t,n){let o=[];if(n.some((s)=>/\bis not defined\b/.test(s)))o.push("`… is not defined` = một định danh trần không có trên window. Ca thường gặp nhất: `EXE`/`FUN` gọi hàm chưa viết trong script.js — hoặc viết rồi mà quên `module_publish_script` (module_publish_json KHÔNG đẩy script.js). Soát tên hàm trong script.js trước khi đi tìm chỗ khác.");if(e.appEmpty&&e.jsErrors===0&&e.vueWarns===0&&e.envMissing===0)o.push("Trang trắng mà KHÔNG có JS error/Vue warn là dấu hiệu kinh điển của statement trong một `:attr` (Vue nuốt lỗi compile template) — chạy module_validate, nó bắt tĩnh được ca này.");if(t.some((s)=>/is not defined|Property or method/i.test(s)))o.push('[Vue warn] kiểu "property is not defined" = biến dùng trong template chưa khai trong data[] — module_validate bắt được (v-model/watch/state), không cần browser.');if(e.suspectTexts>0&&e.jsErrors===0)o.push('Text "undefined"/"NaN" thường là OUT/mapData trỏ sai khoá chứ không phải lỗi hiển thị — module_simulate (engine vm) chỉ ra chỗ gán, chạy nhanh hơn nhiều.');return o}async function Xg(){for(let e of["playwright","playwright-core"])for(let t of[void 0,process.cwd()])try{return await(t?import(Fg(Mg(z.join(t,"noop.js")).resolve(e)).href):import(e))}catch{}throw Error(`rendering needs Playwright (optional, not installed). Install once:
188
187
  bun add -g playwright
189
188
  bunx playwright install chromium
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}">
191
- ${bt(p,"style")}
192
- </style>`);else n.push(`<script data-fui-import="${m}">
193
- ${bt(p,"script")}
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)};
189
+ (module simulate without --render works without Playwright.)`)}function Ni(){return z.join(Wn(),"_cache","cdn")}function Di(e){return Gu("sha1").update(e).digest("hex")}function Yu(e){return Gu("sha256").update(e).digest("hex")}async function Qg(e){let t=Di(e),n=Ni();try{let o=JSON.parse(await jt.readFile(z.join(n,`${t}.meta.json`),"utf-8"));return{body:await jt.readFile(z.join(n,`${t}.bin`)),contentType:o.contentType??Qu(e),meta:o}}catch{return}}async function Zg(e,t,n,o){try{let s=Di(e),r=Ni();await jt.mkdir(r,{recursive:!0}),await jt.writeFile(z.join(r,`${s}.bin`),t);let i={url:e,contentType:n,cachedAt:new Date().toISOString(),etag:o.etag,lastModified:o.lastModified,sha256:Yu(t)};await jt.writeFile(z.join(r,`${s}.meta.json`),JSON.stringify(i),"utf-8")}catch{}}async function ew(e,t){try{let n=Di(e);await jt.writeFile(z.join(Ni(),`${n}.meta.json`),JSON.stringify({...t,cachedAt:new Date().toISOString()}),"utf-8")}catch{}}function _i(e){if(!e)return"không rõ tuổi";let t=Date.now()-Date.parse(e);if(!Number.isFinite(t))return"không rõ tuổi";let n=Math.floor(t/86400000);if(n>=1)return`cũ ${n} ngày`;let o=Math.floor(t/3600000);return o>=1?`cũ ${o} giờ`:"cũ dưới 1 giờ"}async function tw(e,t){let n=t.refresh?void 0:await Qg(e);if(t.offline){if(!n)throw Error("offline: chưa có bản cache nào cho URL này");return{body:n.body,contentType:n.contentType,origin:"stale",note:`offline — dùng cache ${_i(n.meta.cachedAt)}`}}if(n&&!t.refresh){let r=Ei.get(e);if(r!==void 0&&Date.now()-r<Yg)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 Ei.set(e,Date.now()),await ew(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")??Qu(e),c=r.headers.get("etag")??void 0,l=r.headers.get("last-modified")??void 0;await Zg(e,i,a,{etag:c,lastModified:l}),Ei.set(e,Date.now());let u=Boolean(n?.meta.sha256&&n.meta.sha256!==Yu(i));return{body:i,contentType:a,origin:n?"updated":"fetched",changed:u,note:u?`nội dung ĐỔI so với bản cache ${_i(n?.meta.cachedAt)}`:void 0}}catch(r){if(n)return{body:n.body,contentType:n.contentType,origin:"stale",note:`không revalidate được (${r.message}) — dùng cache ${_i(n.meta.cachedAt)}`};throw r}}function Qu(e){switch(z.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 Kn(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Ut(e,t){return e.replace(new RegExp(`</(?=${t})`,"gi"),"<\\/")}function Ar(e,t){return JSON.stringify(e,null,t).replace(/</g,"\\u003c")}function nw(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 Bu(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 ow(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 rw(e,t,n){let o=[],s=ow(e.url),r=Bu(e.mDomain),i=Bu(t?.pDomain),a=s.host;if(!a)a=r[0]??i[0]??n,o.push("_moduleInfo.json không có url kèm host — origin lấy từ "+`${r.length?"mDomain":i.length?"pDomain":"domain asset mặc định"}: ${a}. `+"Chạy `module_get` để có url thật nếu render sai domain.");let c=[...new Set([...s.host?[s.host]:[],...r,...i])];if(c.length>1)o.push(`Project/module phục vụ trên nhiều domain (${c.join(", ")}) — lần render này chạy ở "${a}". `+"v_Set.domainSetting của domain khác KHÔNG được áp dụng; muốn thử domain khác thì render module có url thuộc domain đó.");let l=s.path&&s.path!=="/"?s.path:`/${e.ModuleID}`;return{host:a,basePath:l,notes:o}}function Ju(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 sw(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 Ku(e){let t=await We(e),n=[],o=[];for(let s of t){let r=await U(z.join(e,s));if(!r.trim())continue;let{compiled:i,warning:a}=Lu(s.replace(/\.vue$/,""),r);if(n.push(i),a)o.push(a)}return{components:n,warnings:o}}async function iw(e){let t=await We(e),n=[];for(let o of t){let s=o.replace(/\.vue$/i,""),r=await U(z.join(e,o));if(!r.trim())continue;try{let{template:i,script:a,style:c}=Ft(r);n.push({name:s,template:i,script:a,css:c})}catch(i){n.push({name:s,parseError:i.message})}}return n}async function aw(e,t,n){let o=[],s=[],r=[],i={project:[],module:[]},a=[{scope:"project",items:await V(z.join(t,"imports","_imports.json"),[]),baseDir:t,fixHint:"project_sync"},{scope:"module",items:await V(z.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 h=`[${c}] ${p.name}`,w=p.type===".css"?"css":p.type===".js"?"js":void 0;if(!w){s.push(`import ${h}: type "${p.type??"(trống)"}" không phải .js/.css — không nạp vào trang render`);continue}if(typeof p.sort!=="number")i[c].push(p.name);o.push({label:h,scope:c,kind:w,sort:typeof p.sort==="number"?p.sort:Vu[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ấp ${c} không có FileSort trong _imports.json (${l.join(", ")}) → tạm xếp cuối `+`(${Vu[c]}). Thứ tự nạp có thể khác production; chạy \`${c==="project"?"project_sync":"module_get"}\` để lấy FileSort thật.`)}return{entries:o,problems:s,notes:r}}async function cw(e){let t=[],n=[],o=[],s=[],r=[],i=new Set;for(let a of Ou(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 U(z.join(a.baseDir,a.filePath));if(p.trim()){i.add(c);let h=Kn(a.label);if(a.kind==="css")t.push(`<style data-fui-import="${h}">
190
+ ${Ut(p,"style")}
191
+ </style>`);else n.push(`<script data-fui-import="${h}">
192
+ ${Ut(p,"script")}
193
+ </script>`);s.push(`${a.sort}. ${a.label} — inline ${p.length} bytes`);continue}if(!a.fileID){o.push(`import ${a.label}: file nội bộ "${a.filePath}" thiếu/rỗng và không có fileID để lấy bản server → KHÔNG nạp được. `+`Chạy \`${a.fixHint}\`. Lỗi JS/hiển thị bên dưới có thể chỉ là hệ quả của việc này.`);continue}r.push(`${a.label}: chưa có bản local → dùng bản trên server. Chạy \`${a.fixHint}\` nếu cần sửa file này.`)}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=nw(u,a.domain);if(!d){o.push(`import ${a.label}: không có file nội bộ và tên không phải URL/đường dẫn tuyệt đối → không xác định được nguồn để nạp. `+`Nếu đây là file tự tạo, chạy \`${a.fixHint}\`.`);continue}if(i.has(`url:${d}`))continue;if(i.add(c),i.add(`url:${d}`),a.kind==="css")t.push(`<link href="${Kn(d)}" rel="stylesheet" type="text/css" />`);else n.push(`<script src="${Kn(d)}" type="text/javascript"></script>`);s.push(`${a.sort}. ${a.label} → ${d}`)}return{styles:t,scripts:n,problems:o,loaded:s,serverFallbackNotes:r}}function lw(e,t,n){return`(() => {
194
+ const previewVueData = ${Ar(n,2)};
196
195
  const basePath = ${JSON.stringify(t)};
197
196
  const moduleId = ${JSON.stringify(e.ModuleID)};
198
197
 
@@ -231,34 +230,34 @@ ${bt(p,"script")}
231
230
  if (window.vueData && Object.keys(window.vueData).length) {
232
231
  syncRouteState(window.vueData);
233
232
  }
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(", ")}.
233
+ })();`}async function uw(e,t){let n=[],o=await V(z.join(e,"_moduleInfo.json"),{ProjectID:"render-project",ModuleID:z.basename(e)}),s=Bn(o.ProjectID),r=await V(z.join(s,"_projectInfo.json"),null),i=await V(z.join(s,"project.json"),null),a=(o.Framework??"V3").toUpperCase()==="V2"?"V2":"V3",c=_r.replace(/^https:\/\//,""),l=rw(o,r,c),u=l.host,d=`https://${u}`;n.push(...l.notes);let p=await U(z.join(e,"module.json")),h={ModuleName:o.ModuleName??o.ModuleID,data:[],watch:{},controls:[],set:{}};if(p.trim())try{h=JSON.parse(p)}catch(ie){throw Error(`module.json có lỗi JSON syntax: ${ie.message} — sửa trước khi renderUI.`)}else n.push("module.json rỗng — render shell với module trống");let w=await U(z.join(e,"header.html")),y=await U(z.join(e,"body.html")),v=await U(z.join(e,"script.js")),_=await U(z.join(e,"style.css")),I=o.HTMLOnly===!0,g=I?{deps:[],source:"(bỏ qua — HTMLOnly)",warning:""}:await xu(a,_r);if(g.warning)n.push(g.warning);let b=g.deps.map((ie)=>Mu(ie,_r)).filter((ie)=>Boolean(ie)),T=await aw(e,s,u),C=await cw([...b,...T.entries]);C.problems.unshift(...T.problems),n.push(...T.notes);let S=I?`Framework ${a}: KHÔNG nhúng (HTMLOnly=true — server cũng không nhúng)`:`Framework ${a}: danh mục từ ${g.source}`;n.push(...C.serverFallbackNotes);let N=await Ku(z.join(s,"components"));n.push(...N.warnings);let x=I?{js:"",css:"",jsUrl:"",notes:[],problems:[]}:await Fu(o.ProjectID,u),A=Boolean(x.js)&&!t.forceLocalProjectBundle;if(I)n.push("HTMLOnly=true → render KHÔNG nhúng framework "+a+", KHÔNG nhúng projectdata/StyleCSS cấp project "+"và KHÔNG dựng #fastProjectAPP — giống hệt trang production. Thư viện nào module cần (Vue, jQuery, Vuetify, "+"lodash…) phải do CHÍNH module khai làm import; thiếu là trắng trang thật, không phải lỗi của render.");else if(t.forceLocalProjectBundle)n.push("Tầng project dựng từ workspace LOCAL (chế độ preview cấp component) — không dùng bundle server. "+"Chạy `project_sync` nếu nghi file project trong workspace đã cũ.");else if(!A)n.push(...x.problems);else{let ie=await iw(z.join(s,"components")),He=Uu(ie,x.js,x.css??""),hn=[...He.changed.map((ye)=>`${ye} (đã sửa local, chưa push)`),...He.missing.map((ye)=>`${ye} (chưa có trên server)`),...He.changedCss.filter((ye)=>!He.changed.includes(ye)).map((ye)=>`${ye} (<style> đã sửa local, chưa push)`)];if(hn.length>0)throw Error(`⛔ Component cấp project lệch với bản trên server: ${hn.join(", ")}.
235
234
 
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.
235
+ `+"renderUI dựng trang bằng bundle project TẢI TỪ SERVER (_aset/.../projectdata) nên bản local chưa push "+`không xuất hiện — nếu vẫn render, tấm ảnh bạn nhận được là BẢN CŨ, không phải bản vừa sửa.
237
236
 
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)}">
240
- ${bt(D.js??"","script")}
237
+ `+"• Nếu bản local là bản bạn vừa sửa → `component_preview` để kiểm chứng riêng component (chạy được bằng "+"file local, không cần push), rồi `component_update` từng component, rồi render lại.\n"+"• Nếu bản trên server mới hơn (ai đó sửa trên web IDE) → `project_sync` để lấy về, ĐỪNG push đè.");if(He.unverifiable.length>0)n.push(`Không đối chiếu được component cấp project: ${He.unverifiable.join(", ")}. `+"Không chặn render, nhưng nếu giao diện khác mong đợi thì đây là chỗ đáng nghi đầu tiên.");let De=N.components.filter((ye)=>ye.css.trim()).map((ye)=>ye.name);if(De.length>0&&!(x.css??"").trim())n.push(`StyleCSS cấp project tải về RỖNG nhưng component local có khai <style>: ${De.join(", ")}. `+"Hoặc các component đó chưa push (`component_update`), hoặc route StyleCSS đang hỏng — "+"dù nguyên nhân nào thì style cấp project trong lần render này cũng đang thiếu.")}let Y=await Ku(z.join(e,"components"));n.push(...Y.warnings);let D=j(i?.data)?i.data:i??{},ge={...D,ProjectName:D.ProjectName??r?.ProjectName??o.ProjectID,title:D.title??o.mTitle??o.ModuleName??o.ModuleID},Ae=Ju(x.js,"projectGroupSetting"),W=Ju(x.js,"projectData");if(A&&!W)n.push("Không bóc được `var $projectData` khỏi bundle projectdata tải từ server — apiDomain/userInfo "+"trong lần render này suy ra từ project.json local, có thể lệch với bản trang thật đang chạy.");let de=String(D.apiDomain??""),$e=typeof W?.apiDomain==="string"?W.apiDomain:"";if(W&&de&&$e&&de!==$e)n.push(`project.json local có apiDomain "${de}" nhưng bản trên server là "${$e}" — `+"render dùng bản server (giống trang thật). Chạy `project_sync` để đồng bộ file local.");let Ze=j(h.set)?h.set:null,mt=sw({groupSetting:Ae,projectData:W??D,moduleSet:Ze},u),Ne=String(mt.settings.apiDomain??""),qt=mt.origins.apiDomain??"(không tầng nào khai)",et=(()=>{let ie=typeof mt.settings.userInfo==="string"?mt.settings.userInfo.trim():"";if(!ie)return"";return/^https?:\/\//i.test(ie)?ie:`${Ne}${ie}`})();if(!Ne)n.push(`Không tầng nào ($projectGroupSetting → $projectData → module.json set → domainSetting["${u}"]) khai apiDomain cho "${o.ProjectID}" — API sẽ gọi bằng đường dẫn tương đối và apiMocks khớp theo URL `+"gần như chắc chắn trượt. Chạy `project_sync` nếu dữ liệu project đã cũ.");let ht={},Vt=l.basePath,ee=t.user??{},xo=!("SystemRight"in ee),Xn=!("FunctionRight"in ee),mn=ln([p,JSON.stringify(D),w,y,v]),Oo={user:{UserID:"render-preview",FirstName:"Render",LastName:"Preview",Email:"render@preview.local",SystemRight:9,FunctionRight:mn,...ee},...t.vueData??{}},Mo=Xn?Io(mn,(await cn(s)).functionRights):null,Fo=(()=>{let ie=[];if(xo)ie.push("SystemRight=9 (mặc định, quyền cao nhất)");if(Xn)ie.push(mn.length?`FunctionRight=[${mn.join(", ")}] (mặc định — quét được từ module.json/project)`:"FunctionRight=[] (mặc định — không quét được mã nào)");return ie.length?`user preview: ${ie.join(" · ")}. rightTest()/v-if theo FunctionRight khác mã trên sẽ ẩn — truyền tham số \`user\` để test vai trò hạn chế hơn.`:null})(),tt=Kn(o.mTitle||o.ModuleName||o.ModuleID),se=I?"":A?x.css??"":Ti(N.components),Je=[_,Ti(Y.components)].filter((ie)=>ie.trim()).join(`
238
+ `),Yn=I?"":A?`<script data-fui-projectdata="${Kn(x.jsUrl)}">
239
+ ${Ut(x.js??"","script")}
241
240
  </script>`:`<script data-fui-projectdata="local">
242
- var $projectData = ${Lo(ue,2)};
243
- ${bt(ws(A.components),"script")}
244
- </script>`,to=`<script data-fui-moduledata="local">
245
- var $moduleUI = ${Lo(m,2)};
246
- ${bt(v,"script")}
247
- ${bt(ws(G.components),"script")}
241
+ var $projectData = ${Ar(ge,2)};
242
+ ${Ut(Ri(N.components),"script")}
243
+ </script>`,Lo=`<script data-fui-moduledata="local">
244
+ var $moduleUI = ${Ar(h,2)};
245
+ ${Ut(v,"script")}
246
+ ${Ut(Ri(Y.components),"script")}
248
247
  </script>`;return{html:`<!DOCTYPE html>
249
248
  <html>
250
249
  <head>
251
250
  <meta charset="UTF-8" />
252
251
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
253
- <title>${Je}</title>
252
+ <title>${tt}</title>
254
253
  ${C.styles.join(`
255
254
  `)}
256
255
  ${w}
257
- ${te.trim()?`<style data-fui-project-css>
258
- ${bt(te,"style")}
256
+ ${se.trim()?`<style data-fui-project-css>
257
+ ${Ut(se,"style")}
259
258
  </style>`:""}
260
- ${Le.trim()?`<style data-fui-module-css>
261
- ${bt(Le,"style")}
259
+ ${Je.trim()?`<style data-fui-module-css>
260
+ ${Ut(Je,"style")}
262
261
  </style>`:""}
263
262
  <script>
264
263
  var urlReferrer = '';
@@ -267,7 +266,7 @@ ${bt(Le,"style")}
267
266
  var Framework = ${JSON.stringify(a)};
268
267
  </script>
269
268
  <script>
270
- ${Lh(o,St,Qn)}
269
+ ${lw(o,Vt,Oo)}
271
270
  </script>
272
271
  </head>
273
272
  <body>
@@ -280,129 +279,142 @@ ${Lh(o,St,Qn)}
280
279
  ${C.scripts.join(`
281
280
  `)}
282
281
  ${I?"":`<script>
283
- var $projectGroupSetting = ${Lo(Ze,2)};
282
+ var $projectGroupSetting = ${Ar(ht,2)};
284
283
  </script>`}
285
- ${bn}
286
- ${to}
284
+ ${Yn}
285
+ ${Lo}
287
286
  </body>
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(`
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(`
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
+ </html>`,moduleInfo:o,basePath:Vt,origin:d,apiDomain:Ne,apiDomainOrigin:qt,userInfoUrl:et,previewUser:Oo.user,importsLoaded:C.loaded,importProblems:C.problems,frameworkNote:S,htmlOnly:I,projectBundleNotes:I?["projectdata/StyleCSS: KHÔNG nhúng (HTMLOnly=true — server cũng không nhúng)"]:A?x.notes:[...t.forceLocalProjectBundle?[]:x.notes,t.forceLocalProjectBundle?"projectdata/StyleCSS: dựng từ workspace local (chế độ preview cấp component — bản local chưa push vẫn hiện)":"projectdata/StyleCSS: dựng từ workspace local (không tải được bản server)"],rightDefaultNote:Fo,undefinedRightsNote:Mo,warnings:n}}function dw(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}function pw(e){if(!e)return null;try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return null}}async function mw(e,t,n,o,s,r,i,a){if(!o||n!==o)return null;let c=Po(s);if(!c)return null;let l=await Cu(t,c.alias);if(!l)return null;let u=await Ru(t,l,c.fnName);if(!u)return null;let d=`${l}:${u}`,p=r.get(d);if(p===void 0)p=await Tu(t,l,u),r.set(d,p);if(p==="safe-read"&&i.count<dn){i.count++;try{let y;if(!c.isAuth){let{config:_}=await Iu(t,{db:l});if(_.userToken)y={...await e.request().allHeaders(),authorization:`Bearer ${_.userToken}`}}let v=await e.fetch({timeout:Pr,headers:y});if(v.ok()){let _=await v.json().catch(()=>null);if(_!==null)return await ji(t,l,u,"live",_),a.live++,{payload:_,note:`gọi tAPI thật (SP ${u}, ${l})`}}}catch{}}let h=await Pu(t,l,u);if(h)return a.cache++,{payload:h.data,note:`_testdata cache (SP ${u}, ${l}, nguồn: ${h.source})`};let w=await Eu(t,l,u);if(w)return await ji(t,l,u,"generated",w),a.generated++,{payload:w,note:`tự sinh mock (SP ${u}, ${l}), đã lưu cache`};return null}async function Nr(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:h,projectBundleNotes:w,rightDefaultNote:y,undefinedRightsNote:v,warnings:_}=await uw(e,t),I=await Xg(),g=[],b=new Set(_),T=new Set,C=[],S=[],N=[],x=[],A={revalidated:0,updated:0,fetched:0,memo:0,stale:0},Y=new Set,D=new Set,ge=!1,Ae=new Map,W={count:0},de=pw(i),$e={live:0,cache:0,generated:0},Ze=t.urlParams&&Object.keys(t.urlParams).length?"?"+new URLSearchParams(t.urlParams).toString():"",mt=`${r}${s}${Ze}`,Ne=(()=>{try{return c?new URL(c).pathname:""}catch{return""}})(),qt;try{qt=await I.chromium.launch({headless:!0})}catch(et){let ht=et.message;if(/executable doesn't exist|Executable doesn't exist|browserType.launch/i.test(ht))throw Error(`Không launch được Chromium: ${ht}
288
+ → Chạy một lần: npx playwright install chromium`);throw et}try{let et=t.device??"desktop",ht=Vg[et],Vt=t.viewport??ht.viewport,ee=await qt.newPage({viewport:Vt,isMobile:ht.isMobile,hasTouch:ht.hasTouch,userAgent:ht.userAgent,deviceScaleFactor:1}),xo=Math.min(Math.max(t.settleMs??Ug,100),5000);ee.on("pageerror",(E)=>{C.push(String(E?.message??E))}),ee.on("console",(E)=>{let J=E.type(),K=E.text();if(J==="error"){if(!/Failed to load resource/i.test(K))C.push(K)}else if(J==="warning"&&/\[Vue warn\]|\[Vuetify\]/i.test(K))S.push(K)}),await ee.route("**/*",async(E)=>{let J=E.request(),K=J.url(),ve=J.method(),M=J.resourceType();if(J.isNavigationRequest()){if(K.startsWith(r))await E.fulfill({status:200,contentType:"text/html; charset=utf-8",body:n});else b.add(`runtime điều hướng ra "${K}" — đã chặn (thường do login gate / redirect trong action)`),await E.fulfill({status:200,contentType:"text/html; charset=utf-8",body:`<html><body>[fui-render] navigation blocked: ${Kn(K)}</body></html>`});return}let L;try{L=new URL(K)}catch{await E.fulfill({status:400,contentType:"text/plain",body:""});return}if(ve==="OPTIONS"){await E.fulfill({status:204,headers:Er,body:""});return}if(L.pathname==="/favicon.ico"){await E.fulfill({status:204,body:""});return}let je=z.extname(L.pathname).toLowerCase();if(Hg.has(M)||ve==="GET"&&qg.has(je)){try{let X=await tw(K,{refresh:t.refreshAssets,offline:t.offline});if(A[X.origin]++,X.origin==="stale")Y.add(`${M} "${K}" — ${X.note}`);else if(X.changed)D.add(`${K} — ${X.note}`);await E.fulfill({status:200,contentType:X.contentType,body:X.body,headers:Er})}catch(X){let Ce=`${M} "${K}" tải thất bại (${X.message})`;if(M==="script"||M==="stylesheet")T.add(Ce);else b.add(`${Ce} — trả 404 (offline + chưa cache?)`);await E.fulfill({status:404,contentType:"text/plain",body:""})}return}N.push(`${ve} ${K}`);let Ie=dw(t.apiMocks,K,L.pathname),oe;if(Ie)oe=Ie.value,g.push(`API ${ve} ${L.pathname} → mock "${Ie.key}"`);else if(Ne&&(K===c||L.pathname===Ne))oe=l,g.push(`API ${ve} ${L.pathname} → user preview (userInfo của project, auto-mock)`);else{let X=await mw(E,o.ProjectID,L.hostname,de,L.pathname,Ae,W,$e);if(X)oe=X.payload,g.push(`API ${ve} ${L.pathname} → ${X.note}`);else oe={data:[]},b.add(`không có mock cho API "${K}" — trả {data: []}`),g.push(`API ${ve} ${L.pathname} → {data:[]} (không có mock)`)}if(j(oe)&&oe.__error!==void 0){let X=j(oe.__error)?oe.__error:{};await E.fulfill({status:Number(X.status??500),contentType:"application/json; charset=utf-8",headers:Er,body:JSON.stringify(X.responseJSON??{Message:"Simulated error"})});return}await E.fulfill({status:200,contentType:"application/json; charset=utf-8",headers:Er,body:JSON.stringify(oe)})});let Xn=ee;if(typeof Xn.routeWebSocket==="function")await Xn.routeWebSocket("**/*",(E)=>{b.add("module mở WebSocket tới backend thật — đã chặn (renderUI chỉ cho phép gọi thật qua tAPI SP đã xác nhận an toàn, không qua WebSocket)"),E.close()});g.push("── INIT ──");let mn=Date.now();if(await ee.goto(mt,{waitUntil:"networkidle",timeout:Lg}),!h)try{await ee.waitForFunction("!!window.vm || !!document.querySelector('#mainContainer, #fastProjectAPP .v-application')",void 0,{timeout:15000})}catch{b.add("Vue chưa mount sau 15s — trang chưa init xong (xem JS error / asset hỏng / API treo bên dưới)")}await ee.waitForTimeout(xo);let Oo=[A.revalidated?`${A.revalidated} revalidate 304`:"",A.updated?`${A.updated} CẬP NHẬT`:"",A.fetched?`${A.fetched} tải mới`:"",A.memo?`${A.memo} memo`:"",A.stale?`${A.stale} STALE`:""].filter(Boolean).join(" + ")||"0",Mo=[$e.live?`${$e.live} gọi thật`:"",$e.cache?`${$e.cache} cache`:"",$e.generated?`${$e.generated} tự sinh`:""].filter(Boolean).join(" + ");g.push(`trang load xong sau ${((Date.now()-mn)/1000).toFixed(1)}s · asset: ${Oo} · ${N.length} API call${Mo?` (API không-mock: ${Mo})`:""}`);let Fo=C.length,tt=z.join(e,"_preview");await jt.mkdir(tt,{recursive:!0});for(let E=0;E<t.scenario.length;E++){let J=t.scenario[E],K=E+1,ve=C.length;try{if(j(J.set))g.push("",`── STEP ${K}: set ${Object.keys(J.set).join(", ")} ──`),await ee.evaluate((M)=>{let L=window,je=(oe,X,Ce)=>{if(L.Vue&&typeof L.Vue.set==="function")L.Vue.set(oe,X,Ce);else oe[X]=Ce},Ie=(oe,X,Ce)=>{let qe=String(X).match(/[^.[\]']+/g)||[X],G=oe;for(let ne=0;ne<qe.length-1;ne++){if(G[qe[ne]]==null)je(G,qe[ne],/^\d+$/.test(qe[ne+1])?[]:{});G=G[qe[ne]]}je(G,qe[qe.length-1],Ce)};for(let[oe,X]of M)Ie(L.vueData,oe,X)},Object.entries(J.set));else if(typeof J.click==="string"){g.push("",`── STEP ${K}: click "${J.click}" ──`);let M=J.click,L=typeof J.nth==="number"?J.nth-1:0,je=ee.locator(".v-btn, button, [role='button'], .v-list-item, .v-tab, a",{hasText:M}),Ie=await je.count();if(Ie===0)je=ee.locator(`text=${M}`),Ie=await je.count();if(Ie===0)b.add(`STEP ${K}: không tìm thấy phần tử có text "${M}" trên DOM`),ge=!0;else{if(Ie>1)g.push(`(khớp ${Ie} phần tử — dùng #${L+1})`);await je.nth(Math.min(L,Ie-1)).click({timeout:Pi}),await ee.waitForTimeout(350);let oe=ee.locator(".jconfirm .jconfirm-buttons button"),X=await oe.count();if(X>0){let Ce=J.cancelConfirm===!0,qe=!1;for(let G=0;G<X;G++){let ne=(await oe.nth(G).innerText()).trim().toLowerCase(),ae=/hủy|huỷ|cancel|không|no|đóng/.test(ne);if(Ce===ae){g.push(`CONFIRM → bấm "${ne}"`),await oe.nth(G).click({timeout:Pi}),qe=!0;break}}if(!qe)g.push(`CONFIRM → bấm nút ${Ce?"cuối":"đầu"} (không nhận diện được label)`),await oe.nth(Ce?X-1:0).click({timeout:Pi})}}}else if(typeof J.call==="string")g.push("",`── STEP ${K}: call ${J.call} ──`),await ee.evaluate((M)=>{let L=window;if(typeof L.CALL!=="function")throw Error("global CALL không tồn tại trong runtime này");let je=L.vueData&&L.vueData[M]!==void 0?L.vueData[M]:M;L.CALL(je)},J.call);else if(typeof J.exe==="string")g.push("",`── STEP ${K}: exe ──`),await ee.evaluate((M)=>{Function(M)()},J.exe);else if(typeof J.assert==="string"){let M=typeof J.label==="string"?J.label:J.assert,L=!1;try{L=await ee.evaluate((je)=>{try{return!!Function("with(window.vueData||{}){return ("+je+")}")()}catch{return!1}},J.assert)}catch{L=!1}x.push({label:M,ok:L}),g.push("",`── STEP ${K}: assert ${L?"✔":"✘"} ${M} ──`);continue}else{b.add(`event ${K} không hợp lệ — cần một trong: set / click / call / exe / assert`);continue}await ee.waitForTimeout(xo);for(let M=ve;M<C.length;M++)g.push(`✘ pageerror: ${C[M]}`);if(t.screenshotSteps){let M=z.join(tt,`render-step-${K}.png`);await ee.screenshot({path:M}),g.push(`screenshot: ${M}`)}}catch(M){ge=!0,g.push(`✘ STEP ${K} lỗi: ${M.message.split(`
289
+ `)[0]}`)}}let se=await ee.evaluate(()=>{let E=document.querySelector("#fastProjectAPP"),J=E?E.children.length:Array.from(document.body.children).filter((G)=>!/^(SCRIPT|STYLE|LINK|TEMPLATE|NOSCRIPT)$/.test(G.tagName)).length,K=window.innerWidth,ve=document.documentElement.scrollWidth>K+4,M=[],L=(G)=>G&&typeof G.className==="string"?G.className.trim().split(/\s+/).slice(0,2).join("."):"";if(ve)for(let G of document.body.querySelectorAll("*")){let ne=G.getBoundingClientRect();if(ne.width>0&&ne.right>K+4&&M.length<5){let ae=L(G);M.push(G.tagName.toLowerCase()+(ae?"."+ae:"")+" (right="+Math.round(ne.right)+"px)")}}let je=document.body.innerText||"",Ie=[];for(let G of["undefined","NaN","[object Object]"])if(je.includes(G))Ie.push(G);let oe=[];for(let G of document.querySelectorAll("img"))if(G.complete&&G.naturalWidth===0&&G.getBoundingClientRect().width>0&&oe.length<5)oe.push((G.getAttribute("src")||"(no src)").slice(0,120));let X=[];for(let G of document.querySelectorAll("canvas")){let ne=G.getBoundingClientRect();if((ne.width===0||ne.height===0)&&X.length<5){let ae=G.parentElement,ce=L(ae);X.push("canvas trong "+(ae?ae.tagName.toLowerCase():"?")+(ce?"."+ce:"")+" ("+Math.round(ne.width)+"×"+Math.round(ne.height)+")")}}let Ce=[];{let G=(ae)=>{let ce="";for(let nt of ae.childNodes)if(nt.nodeType===Node.TEXT_NODE)ce+=nt.textContent||"";return ce.trim()},ne=[];for(let ae of document.body.querySelectorAll("*")){if(ne.length>=600)break;let ce=G(ae);if(!ce)continue;let nt=ae.getBoundingClientRect();if(nt.width<=0||nt.height<=0)continue;ne.push({el:ae,rect:nt,text:ce.slice(0,24)})}ne.sort((ae,ce)=>ae.rect.top-ce.rect.top||ae.rect.left-ce.rect.left);e:for(let ae=0;ae<ne.length;ae++){let ce=ne[ae];for(let nt=ae+1;nt<ne.length;nt++){let ot=ne[nt];if(ot.rect.top>ce.rect.bottom+4)break;if(ce.el.contains(ot.el)||ot.el.contains(ce.el))continue;let Ap=ce.rect.top+ce.rect.height/2,Np=ot.rect.top+ot.rect.height/2;if(Math.abs(Ap-Np)>Math.min(ce.rect.height,ot.rect.height)*0.6)continue;let qr=ce.rect.left<=ot.rect.left?ot.rect.left-ce.rect.right:ce.rect.left-ot.rect.right;if(qr<2){let Dp=qr<0?"chồng lấn":`dính sát (gap ${Math.round(qr)}px)`;if(Ce.push(`${Dp}: "${ce.text}" ↔ "${ot.text}"`),Ce.length>=6)break e}}}}let qe=!!document.querySelector(".v-dialog--active, .v-overlay--active");return{appChildCount:J,overflowX:ve,overflowers:M,suspectTexts:Ie,brokenImages:oe,deadCanvas:X,textCollisions:Ce,dialogOpen:qe}}),Je=[];if(se.appChildCount===0)Je.push((h?"✘ <body> không render được phần tử nào (HTMLOnly — body.html LÀ cả trang)":"✘ #fastProjectAPP rỗng — controls không render")+(d.length>0||T.size>0?" (CÓ file toàn cục nạp thiếu — xem mục MÔI TRƯỜNG THIẾU FILE trước khi nghi module.json)":" (kiểm tra pageerror / module.json)"));if(se.overflowX)Je.push(`⚠ trang tràn ngang viewport ${Vt.width}px — phần tử tràn: ${se.overflowers.join(", ")||"(không xác định)"}`);if(se.suspectTexts.length>0)Je.push(`⚠ text nghi vấn hiển thị trên trang: ${se.suspectTexts.map((E)=>`"${E}"`).join(", ")} (binding thiếu data?)`);if(se.brokenImages.length>0)Je.push(`⚠ ảnh vỡ (load fail nhưng đang chiếm chỗ): ${se.brokenImages.join(", ")}`);if(se.deadCanvas.length>0)Je.push(`⚠ canvas chart 0-size — chart init khi container chưa có kích thước: ${se.deadCanvas.join(", ")}`);if(se.textCollisions.length>0)Je.push(`⚠ text dính/chồng lấn (thẩm mỹ — PHẢI sửa, không được bỏ qua): ${se.textCollisions.join("; ")}`);let Yn=z.join(tt,"render.png"),Lo=await ee.screenshot({path:Yn}),Qn,ie="image/png";if(Lo.length<=qu)Qn=Lo.toString("base64");else try{let E=await ee.screenshot({type:"jpeg",quality:60});if(E.length<=qu)Qn=E.toString("base64"),ie="image/jpeg"}catch{}try{await ee.screenshot({path:z.join(tt,"render-full.png"),fullPage:!0})}catch{}let He;if(se.dialogOpen)try{He=z.join(tt,"render-dialog.jpg"),await ee.locator(".v-dialog--active, .v-overlay--active").first().screenshot({path:He,type:"jpeg",quality:60,timeout:3000})}catch{He=void 0}let hn=[];try{hn=await ee.evaluate(()=>{let E=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"]),J=[],K=window.vueData||{};for(let ve of Object.keys(K)){if(E.has(ve)||J.length>=40)continue;let M=K[ve];if(M&&typeof M==="object"&&!Array.isArray(M)&&(M.API!==void 0||M.CALL!==void 0||M.EXE!==void 0||M.FUN!==void 0||M.IF!==void 0))continue;let L;try{L=JSON.stringify(M)}catch{L=String(M)}if(L===void 0)L="undefined";if(L.length>120)L=L.slice(0,120)+"…";J.push(ve+" = "+L)}return J})}catch{}let De={ts:new Date().toISOString(),jsErrors:C.length,initErrors:Fo,vueWarns:S.length,envMissing:d.length+T.size,appEmpty:se.appChildCount===0,overflowX:se.overflowX,suspectTexts:se.suspectTexts.length,brokenImages:se.brokenImages.length,deadCanvas:se.deadCanvas.length,textCollisions:se.textCollisions.length,assertFail:x.filter((E)=>!E.ok).length},ye=await Bg(tt,et),Ep=ye!==null&&Ai(De)&&Xu(ye,De).length===0;De.failStreak=!Ai(De)?0:Ep?(ye?.failStreak??1)+1:1,await jt.writeFile(zu(tt,et),JSON.stringify(De,null,1),"utf8").catch(()=>{return});let oa=[...new Set(C)],ra=[...S],sa=zg(De,ra,oa),H=[];if(H.push(t.reportLabel?`RENDER (browser thật): ${t.reportLabel}`:`RENDER (browser thật): ${e}`),H.push(...Gg(De,sa,oa,ra)),ye)H.push(Jg(ye,De));if((De.failStreak??0)>=Wg)H.push(...Kg(De.failStreak,sa));H.push(""),H.push(`Framework ${o.Framework??"V3"}${h?" · HTMLOnly=true (KHÔNG nhúng framework/projectdata — như production)":""} · viewport ${Vt.width}×${Vt.height}${et!=="desktop"?` (${et})`:""} · CDN ${_r}`),H.push(`Origin trang: ${r} · apiDomain: ${i||"(trống)"} [${a}]${c?` · userInfo auto-mock: ${c}`:""}`),H.push(p);for(let E of w)H.push(` ✓ ${E}`);H.push(`Import (framework ∪ project ∪ module, sort theo FileSort/FileName): ${u.length} nạp được${d.length?`, ${d.length} KHÔNG nạp được`:""}`);for(let E of u)H.push(` ✓ ${E}`);if(y)H.push(`ℹ ${y}`);if(v)H.push(v);H.push(""),H.push(...g),H.push(""),H.push("═══ KẾT QUẢ ═══");for(let E of x)H.push(`${E.ok?"✔":"✘"} assert: ${E.label}`);if(d.length>0||T.size>0){H.push("","── ⛔ MÔI TRƯỜNG THIẾU FILE (đọc TRƯỚC khi sửa module) ──","Các file dưới đây không nạp được vào trang test. Hàm/biến/style toàn cục do chúng","định nghĩa sẽ không tồn tại → lỗi JS và lỗi hiển thị bên dưới CÓ THỂ chỉ là hệ quả,","không phải lỗi của module. Khắc phục phần này trước rồi render lại.");for(let E of d)H.push(`⛔ ${E}`);for(let E of T)H.push(`⛔ ${E}`)}if(Y.size>0){H.push("","── ⛔ ASSET CŨ, KHÔNG XÁC MINH ĐƯỢC (đọc TRƯỚC khi kết luận về platform) ──","Không hỏi được server xem các file dưới đây còn đúng không, nên trang test đang chạy",'bằng bản trên đĩa. Mọi kết luận kiểu "thư viện X có bug" rút ra từ lượt render này',"đều KHÔNG dùng được — bản thật trên CDN có thể đã khác. Nối mạng rồi render lại,","hoặc ép tải mới bằng refreshAssets: true.");for(let E of Y)H.push(`⛔ ${E}`)}if(D.size>0){H.push("","── ℹ️ ASSET PLATFORM ĐÃ ĐỔI so với lượt render trước ──","Hành vi đổi mà module.json không đụng tới có thể đến từ đây, không phải từ module.");for(let E of D)H.push(`ℹ️ ${E}`)}if(C.length>0){H.push("","── JS/Vue errors ──");for(let E of[...new Set(C)].slice(0,20))H.push(`✘ ${E.split(`
290
+ `)[0].slice(0,300)}`)}if(S.length>0){H.push("","── [Vue warn] ──");for(let E of[...new Set(S)].slice(0,15))H.push(`⚠ ${E.split(`
291
+ `)[0].slice(0,300)}`)}if(Je.length>0)H.push("","── DOM AUDIT ──",...Je);if(b.size>0){H.push("","── Cảnh báo ──");for(let E of b)H.push(`⚠ ${E}`)}if(hn.length>0)H.push("","── STATE CUỐI (vueData) ──",...hn);if(H.push(""),H.push(`Screenshot: ${Yn}${Qn?" (đính kèm bên dưới)":" (quá lớn để inline — dùng Read tool để xem)"}`),H.push(`Full-page: ${z.join(tt,"render-full.png")}`),He)H.push(`Dialog đang mở — chụp riêng: ${He}`);H.push(""),H.push(`Thống kê: ${N.length} API call (tất cả mock) · ${C.length} JS error (${Fo} lúc init) · ${S.length} Vue warn · ${x.filter((E)=>E.ok).length}/${x.length} assert đạt${se.dialogOpen?" · dialog đang mở":""}`);let _p=x.some((E)=>!E.ok)||ge||C.length>0||se.appChildCount===0||d.length>0||T.size>0||Y.size>0&&!t.offline;return{report:H.join(`
292
+ `),hasFailures:_p,screenshotPath:Yn,inlineScreenshotBase64:Qn,inlineScreenshotMime:ie}}finally{await qt.close().catch(()=>{return})}}var _r="https://fui.vn",Lg=45000,Ug=700,Pi=4000,qu=1200000,Hg,qg,Vu,Vg,Wg=3,Yg=60000,Ei,Er;var xi=R(()=>{Eo();Ln();Hu();Hg=new Set(["script","stylesheet","image","font","media","manifest","texttrack"]),qg=new Set([".js",".css",".map",".woff",".woff2",".ttf",".otf",".eot",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp"]),Vu={project:90000,module:95000},Vg={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"}};Ei=new Map;Er={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"*"}});import{promises as dt}from"node:fs";import we from"node:path";function fw(e){return e.normalize("NFD").replace(/[̀-ͯ]/g,"").replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase()||"case"}function gw(e){let t={},n={},o=[];for(let[s,r]of Object.entries(e)){if(s.startsWith("@")||s.startsWith("#")){o.push(`prop "${s}": FUI không nhận cú pháp @/# trong JSON — đổi thành "v-on:"/"v-slot:". Đã bỏ 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 ww(e,t,n){let{attr:o,hoisted:s,notes:r}=gw(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} — ${t}`,data:Object.keys(i).length>0?[i]:[],watch:{},controls:[{prop:"fluid pa-0",rows:[{prop:"",cols:[a]}]}],set:{}},notes:r}}async function ed(e,t){try{await dt.access(e)}catch{return!1}return await dt.cp(e,t,{recursive:!0}),!0}async function yw(e,t){let n=Bn(e),o=[];if(t){let i=await V(we.join($i(e,t),"_moduleInfo.json"),null);o.push(i?.Framework)}let s=await V(we.join(n,"_projectInfo.json"),null);if(o.push(s?.Framework),!o.some(Boolean))try{let i=await dt.readdir(we.join(n,"modules"),{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let c=await V(we.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 td(e){let t=await $u(),n=e.projectId||t?.projectId;if(!n)throw Error("Không xác định được projectId. Truyền projectId hoặc chạy session_set trước.");let o=e.moduleId||(!e.projectId?t?.moduleId:void 0),s=Bn(n),r=o?$i(n,o):s,i=we.join(r,"components"),a=o?`module (${o})`:"project (global)",c=we.join(i,`${e.componentName}.vue`);if(!(await U(c)).trim()){let I=await We(i);throw Error(`Không tìm thấy ${e.componentName}.vue trong ${i}.`+(I.length?` Có sẵn: ${I.join(", ")}.`:" Thư mục components rỗng.")+(o?"":" Nếu đây là component cấp module, truyền thêm 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>Zu)throw Error(`${u.length} case × ${d.length} device = ${u.length*d.length} lần render, vượt giới hạn ${Zu}. `+"Mỗi lần render là một phiên browser thật — chia nhỏ thành nhiều lần gọi.");let p=await yw(n,o),h=we.join(Wn(),hw,wt(n,"projectId"),wt(e.componentName,"componentName")),w=we.join(h,"_preview");await dt.mkdir(w,{recursive:!0});let y=[],v=new Set;for(let[I,g]of u.entries()){let b=g.name?.trim()||`case-${I+1}`,T=fw(b),C=we.join(h,T);await dt.rm(C,{recursive:!0,force:!0}),await dt.mkdir(C,{recursive:!0}),await dt.writeFile(we.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}=ww(e.componentName,b,g);for(let x of N)v.add(`[${b}] ${x}`);if(await dt.writeFile(we.join(C,"module.json"),JSON.stringify(S,null,2),"utf-8"),await ed(i,we.join(C,"components")),o)await ed(we.join(r,"imports"),we.join(C,"imports"));for(let x of d){let A=`${e.componentName} · ${b} · ${x}`,Y=await Nr(C,{scenario:g.scenario??[],apiMocks:g.apiMocks??{},vueData:void 0,user:e.user,device:x,viewport:e.viewport,settleMs:e.settleMs,screenshotSteps:e.screenshotSteps,forceLocalProjectBundle:!0,reportLabel:A}),D=Y.screenshotPath;if(Y.screenshotPath){let ge=we.join(w,`${T}-${x}.png`);try{await dt.rename(Y.screenshotPath,ge),D=ge;let Ae=we.join(we.dirname(Y.screenshotPath),"render-full.png");await dt.rename(Ae,we.join(w,`${T}-${x}-full.png`)).catch(()=>{})}catch{}}y.push({caseName:b,device:x,report:Y.report,hasFailures:Y.hasFailures,screenshotPath:D,inlineScreenshotBase64:Y.inlineScreenshotBase64,inlineScreenshotMime:Y.inlineScreenshotMime})}}let _=[`PREVIEW COMPONENT: ${e.componentName}`,`Scope: ${a} · Project: ${n} · Framework ${p}`,`Nguồn: ${c}`,`Cases: ${u.map((I)=>I.name?.trim()||"(không tên)").join(", ")} × Devices: ${d.join(", ")} = ${y.length} lần render`,`Ảnh: ${w}`,"","Không gian riêng: chỉ component này + CSS của chính nó + theme/import cấp project.","style.css của module KHÔNG nạp — component phải tự đứng được. Tầng project dựng từ workspace","local nên bản chưa push vẫn hiện; chạy `project_sync` nếu nghi file local đã cũ."];if(v.size>0){_.push("","⚠ Ghi chú dựng harness:");for(let I of v)_.push(` • ${I}`)}return{header:_.join(`
293
+ `),runs:y,hasFailures:y.some((I)=>I.hasFailures)}}var hw="_componentPreview",Zu=8;var nd=R(()=>{Eo();xi()});import Oi from"node:vm";function pt(e){if(typeof e==="string")return`"${e.length>40?e.slice(0,40)+"…":e}"`;return Ro(e)}function Mi(e){return e.replace(/^`+|`+$/g,"")}function id(e){let t=e.split(/[\\/]+/).filter(Boolean),n=t.lastIndexOf("modules");return n>0?t[n-1]:null}async function Dr(e,t,n){let{loadAllSchemaProcNames:o,getVerifiedSpNames:s}=e;if(!t||Object.keys(t).length===0)return null;let r=id(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=Su(Object.keys(t),i,a);if(c.length===0)return null;return["\uD83D\uDEA8 CẤU TRÚC MOCK CHƯA ĐƯỢC KIỂM CHỨNG — nghi ngờ tự suy diễn thay vì đọc từ SP thật:",...c.map((l)=>` - apiMocks["${l.mockKey}"] khớp tên SP CÓ THẬT "${l.spName}" trong schema local, nhưng SP đó chưa từng được db_sp_verify đọc thân trong phiên này.`),` → Chạy db_sp_verify({ name: "${c[0].spName}" }) để đọc thân SP thật rồi COPY NGUYÊN VĂN snippet apiMocks nó sinh sẵn — không tự đoán tên cột/shape cho SP đã tồn tại, kết quả mô phỏng sẽ lệch với thực tế.`].join(`
294
+ `)}function bw(e){let t=[],n=new Set;return Be(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 ad(e,t,n,o){let{getConnectionAlias:s,resolveApiSpName:r,writeTestData:i,classifySpSafety:a,callLiveApiSp:c,readTestData:l,generateMockForLocalSp:u}=e,d={...o},p=[],h=id(n);if(!h)return{mocks:d,trace:p};let w=bw(t);if(w.length===0)return{mocks:d,trace:p};let y=0;for(let{url:v,method:_}of w){let I=Po(v);if(!I)continue;let g=await s(h,I.alias);if(!g)continue;let b=await r(h,g,I.fnName);if(!b)continue;let T=Object.keys(o).find((A)=>A===v);if(T??=Object.keys(o).find((A)=>v.endsWith(A)||A.endsWith(v)),T??=Object.keys(o).find((A)=>v.includes(A)||A.includes(v)),T!==void 0){await i(h,g,b,"user",o[T]);continue}let C=await a(h,g,b),S=!1;if(C==="safe-read"&&y<dn){y++;let A=await c(h,g,I.isAuth,I.fnName,_);if(A.ok)d[v]=A.data,p.push(`live ${v} → SP ${b} (${g}) — gọi tAPI thật thành công`),await i(h,g,b,"live",A.data),S=!0;else p.push(`⚠ live ${v} → SP ${b} (${g}) gọi thật thất bại (${A.error??`HTTP ${A.status}`}) — rơi xuống cache/tự sinh`)}else if(C==="unsafe-write")p.push(`skip ${v} → SP ${b} (${g}) có lệnh ghi — KHÔNG gọi thật, dùng cache/tự sinh`);else if(C==="opaque")p.push(`skip ${v} → SP ${b} (${g}) có đoạn đọc tĩnh không nhìn xuyên qua được (SQL động / gọi SP khác) — KHÔNG gọi thật, dùng cache/tự sinh`);else if(C==="unknown")p.push(`skip ${v} → SP ${b} (${g}) chưa có thân SP local để phân loại an toàn — KHÔNG gọi thật, dùng cache/tự sinh`);else if(y>=dn)p.push(`skip ${v} → SP ${b} (${g}) đã đạt trần ${dn} lượt gọi thật/lượt simulate — dùng cache/tự sinh`);if(S)continue;let N=await l(h,g,b);if(N){d[v]=N.data,p.push(`cache ${v} → SP ${b} (${g}) — dùng _testdata cache (nguồn: ${N.source}, lưu lúc ${N.savedAt})`);continue}let x=await u(h,g,b);if(x)d[v]=x,p.push(`gen ${v} → SP ${b} (${g}) — tự sinh mock từ cấu trúc SELECT, đã lưu cache`),await i(h,g,b,"generated",x)}return{mocks:d,trace:p}}class Fi{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=ln([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=Oi.createContext(this.sandbox)}log(e){if(this.trace.length===od)this.trace.push("… (trace bị cắt — quá dài)");if(this.trace.length>od)return;this.trace.push(" ".repeat(Math.min(this.depth,6))+e)}warn(e){this.warnings.add(e),this.log(`⚠ ${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)=>pt(c)).join(", ")}) — stub, không mô phỏng UI thật`),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 h=u?.[c[p]],w=d?.[c[p]];if(h===w)continue;let y=h>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ưa hỗ trợ trong simulator — trả undefined`);return}}});return{vueData:this.vueData,console:{log:(...r)=>e.log(`console.log: ${r.map((i)=>typeof i==="string"?i:pt(i)).join(" ")}`),error:(...r)=>e.log(`console.error: ${r.map((i)=>typeof i==="string"?i:pt(i)).join(" ")}`),warn:(...r)=>e.log(`console.warn: ${r.map((i)=>typeof i==="string"?i:pt(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 h of c)if(h)p[h]=d.reduce((w,y)=>w+Number(y?.[h]??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??"?")} — stub (sub-window không mô phỏng)`)},showMessage:(r)=>{if(e.log(`MESSBOX/showMessage "${String(r?.title??"")}"${r?.message?` — ${String(r.message)}`:""}`),r?.onclose)e.log("(giả lập user đóng dialog → chạy onclose)"),e.runAction(r.onclose)},confirm:(r)=>{if(e.log(`CONFIRM "${String(r?.title??"")}" → OK (mặc định 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,Oi.runInContext(`with(__scope__) { __ret__ = ${e} }`,this.ctx,{timeout:sd}),this.sandbox.__ret__}evalJs(e,t){this.sandbox.__scope__=t,Oi.runInContext(`with(__scope__) { ${e} }`,this.ctx,{timeout:sd})}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 Mi(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 Mi(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(`đọc cross-window "${r}" — không mô phỏng được, gán undefined`),this.setValue(t,i,void 0);else if(i[0]==="#")this.log(`→ ghi cross-window ${i} = ${pt(this.getVueData(r,n))} (không mô phỏng)`);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 — CALL trỏ tới action không tồn tại?`);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}" → ${i?"HỦY":"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ỗi khi chạy action: ${n.message} — action: ${Ue(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ông tồn tại (simulator chỉ có các hàm FUI chuẩn)`);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ỗi eval: ${a.message} → coi như false`)}this.log(`IF(${String(n.IF).slice(0,60)}) → ${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} = ${pt(r[0]==="#"?"(cross-window)":this.getPath(this.vueData,r))}`).join(" · ")}`)}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ớp "${r}")`);else i={data:[]},this.warn(`không có mock cho API "${n}" — trả mặc định {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(`← HTTP ${String(c.status)} (mock lỗi)`),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ỗi không được bắt (không có ERROROUT) → hiện ERROR DIALOG mặc định: ${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} = ${pt(this.vueData[e.OUT])}`:Object.keys(e.OUT).map((l)=>`${l} = ${pt(this.getPath(this.vueData,l))}`).join(" · ");this.log(`← OUT ${c}`)}else this.log("← (không có 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<rd;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 đổi → ${Ue(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ượt ${rd} vòng — nguy cơ VÒNG LẶP VÔ HẠN giữa các 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Ở \uD83D\uDD32":"ĐÓNG ▪"}`)}}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(Mi(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("── INIT ──"),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ỗi 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(`── STEP ${s}: set ${Object.entries(n.set).map(([r,i])=>`${r}=${pt(i)}`).join(", ")} ──`);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(`── STEP ${s}: click "${n.click}" ──`);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ông tìm thấy control có label "${n.click}" kèm v-on:click/:action`);return}let c=typeof n.nth==="number"?n.nth-1:0;if(a.length>1)this.log(`(khớp ${a.length} control — dùng #${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 h=this.getVueData(l.tableItemsExpr,this.vueData);if(Array.isArray(h)&&h.length>0)d=h[Math.min(p,h.length-1)],this.log(`(control trong f-table — dùng row ${p} của ${l.tableItemsExpr})`);else d={},this.warn(`control nằm trong f-table nhưng "${l.tableItemsExpr??""}" rỗng — item = {} (truyền "item" trong event để chỉ định)`)}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(`── STEP ${s}: call ${n.call} ──`);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(`── STEP ${s}: exe ──`),this.log(`EXE ${n.exe.slice(0,100)}`);try{this.evalJs(n.exe,{...this.vueData})}catch(r){this.warn(`exe lỗi: ${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ỗi eval: ${a.message}`)}this.assertResults.push({label:r,ok:i}),this.log(""),this.log(`── STEP ${s}: assert ${i?"✔":"✘"} ${r} ──`);return}else{this.warn(`event ${s} không hợp lệ — cần một 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ặc định, quyền cao nhất)");if(this.functionRightDefaulted)e.push(this.scannedFunctionRightCodes.length?`FunctionRight=[${this.scannedFunctionRightCodes.join(", ")}] (mặc định — quét được từ module.json)`:"FunctionRight=[] (mặc định — không quét được mã nào trong module.json)");if(e.length===0)return null;return`user preview: ${e.join(" · ")}. rightTest()/v-if theo FunctionRight khác mã trên sẽ trả false — truyền tham số \`user\` để test vai trò hạn chế hơn.`}buildReport(){let e=[...this.trace,""];e.push("═══ KẾT QUẢ ═══");let t=this.buildRightDefaultNote();if(t)e.push(`ℹ ${t}`);if(this.functionRightDefaulted){let s=Io(this.scannedFunctionRightCodes,this.opts.definedFunctionRights??null);if(s)e.push(s)}for(let s of this.assertResults)e.push(`${s.ok?"✔":"✘"} assert: ${s.label}`);if(this.warnings.size>0){e.push("","── Cảnh báo ──");for(let s of this.warnings)e.push(`⚠ ${s}`)}if(this.stubUsed.size>0)e.push("",`(stub đã dùng — kết quả các hàm này KHÔNG phản ánh runtime thật: ${[...this.stubUsed].join(", ")})`);e.push("","── STATE CUỐI ──");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} = ${pt(r)}`)}let o=this.dialogs.filter((s)=>s.open).map((s)=>s.vModel);return e.push("",`Thống kê: ${this.apiCallCount} API call · ${this.toastCount} toast · ${this.watchTriggerCount} watch trigger · ${this.assertResults.filter((s)=>s.ok).length}/${this.assertResults.length} assert đạt${o.length>0?` · dialog đang mở: ${o.join(", ")}`:""}`),e.join(`
295
+ `)}}var od=500,rd=15,sd=500;var Li=R(()=>{Ln();yi();Vn()});import{basename as kw}from"node:path";function cd(e){let t=new yt(e.paths);return Si({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 ld(e){try{return await e()}catch(t){if(t instanceof Error)t.message=Xe(t.message);throw t}}async function ud(e,t,n){let o=cd(e),s=await un(t),r=await Dr(qn(o,e.session),n.apiMocks,t),i=`${s?`${s}
297
296
 
298
297
  `:""}${r?`${r}
299
298
 
300
- `:""}`,a=await Il(()=>Uo(t,n));return{text:Be(`${i}Render UI: ${t}
299
+ `:""}`,a=await ld(()=>Nr(t,n));return{text:Xe(`${i}Render UI: ${t}
301
300
 
302
301
  ${a.report}
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(`
302
+ `),hasFailures:a.hasFailures,screenshots:a.screenshotPath?[a.screenshotPath]:[]}}async function dd(e,t){cd(e);let n=await ld(()=>td(t)),o=n.runs.map((r)=>`${r.hasFailures?"✘":"✔"} ${r.caseName} · ${r.device}${r.screenshotPath?` → ${kw(r.screenshotPath)}`:""}`).join(`
304
303
  `),s=[`${n.header}
305
304
 
306
- \u2500\u2500 SUMMARY \u2500\u2500
305
+ ── SUMMARY ──
307
306
  ${o}`];for(let r of n.runs)if(s.push(`
308
- \u2550\u2550 ${r.caseName} \xB7 ${r.device} \u2550\u2550
309
- ${r.report}`),r.screenshotPath)s.push(`screenshot: ${r.screenshotPath}`);return{text:Be(`${s.join(`
307
+ ══ ${r.caseName} · ${r.device} ══
308
+ ${r.report}`),r.screenshotPath)s.push(`screenshot: ${r.screenshotPath}`);return{text:Xe(`${s.join(`
310
309
  `)}
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}
310
+ `),hasFailures:n.hasFailures,screenshots:n.runs.flatMap((r)=>r.screenshotPath?[r.screenshotPath]:[])}}var pd=R(()=>{Vn();nd();Eo();xi();Sr();Li();it();To()});import{dirname as md,join as vw}from"node:path";async function hd(e,t,n){let o=qn(new yt(e.paths),e.session),s=await un(t),r=await Dr(o,n.apiMocks,t),i=`${s?`${s}
312
311
 
313
312
  `:""}${r?`${r}
314
313
 
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?`
314
+ `:""}`,a=await U(vw(t,"module.json"));if(!a.trim())throw new m(`module.json is empty or missing in ${t}`);let c;try{c=JSON.parse(a)}catch(v){throw new m(`module.json has a JSON syntax error: ${v.message}; fix it, simulate needs valid JSON`)}if(!j(c))throw new m("module.json root is not an object");let l=await cn(md(md(t))),{mocks:u,trace:d}=await ad(o,c,t,n.apiMocks??{}),p=new Fi(c,{apiMocks:u,user:n.user,urlParams:n.urlParams,definedFunctionRights:l.functionRights}),{report:h,hasFailures:w}=p.simulate(n.scenario??[]),y=d.length>0?`
316
315
 
317
- \u2500\u2500 Live/Cache data (SPs without mocks, auto-resolved) \u2500\u2500
316
+ ── Live/Cache data (SPs without mocks, auto-resolved) ──
318
317
  ${d.join(`
319
- `)}`:"";return{text:Be(`${i}Simulate: ${t}
318
+ `)}`:"";return{text:Xe(`${i}Simulate: ${t}
320
319
 
321
- ${m}${y}
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(`
320
+ ${h}${y}
321
+ `),hasFailures:w}}var fd=R(()=>{q();Vn();lt();Sr();Li();Ln();br();it();To()});import{resolve as Sw}from"node:path";function Ht(e){if(k(`${e.notes.join(`
323
322
  `)}
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":[]}}']
323
+ `),e.data)k(e.data)}async function kd(e){return e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module"))}async function _o(e){return e.targets.resolveModuleDir(f(e.values,"module-dir"),f(e.values,"project"),f(e.values,"module"))}function Sd(e){let n=(typeof e.device==="string"?e.device:void 0)?.split(",").map((r)=>r.trim());for(let r of n??[])if(!gd.includes(r))throw new m(`--device takes ${gd.join(", ")}`);let o;if(typeof e.viewport==="string"){let r=/^(\d+)x(\d+)$/i.exec(e.viewport);if(!r)throw new m("--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 m("--settle-ms takes a number of milliseconds")}return{devices:n,viewport:o,settleMs:s,screenshotSteps:e["screenshot-steps"]===!0?!0:void 0}}function Iw(e,t){let n={};for(let o of jd.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")?Sw(s):s}return n}function wd(e,t){if(!t){k(e);return}let n=hu(e);P(`# brief: ${n.kept} of ${n.total} lines (failures, warnings, asserts, stats, screenshots); full report: rerun without --brief`),k(n.text)}function yd(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:It,async run(n){Ht(await Ir(F(n),await kd(n),t))}}}function xr(e,t,n){return{name:`component ${e}`,mcpName:`component_${e}`,usage:`fui component ${e}${e==="list"?"":" <name>"} [-p P [-m M]]`,summary:n,options:It,async run(o){let s=o.positionals[0];if(e!=="list"&&!s)throw new m("missing <name>");Ht(await t(o,s))}}}async function Or(e){return Dn(F(e),f(e.values,"project"),f(e.values,"module"))}var It,bd,$w,jw,vd,gd,$d,jd,Cw,Rw,Tw,Pw,Ew,_w,Aw,Nw,Dw,Id;var Cd=R(()=>{Re();q();me();xn();To();fu();ku();pd();fd();wr();It={module:{type:"string",short:"m"}},bd={data:{type:"string"},"data-file":{type:"string"}};$w={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:{...It,"module-dir":{type:"string"}},async run(e){let t=await _o(e),n=await ru(t);for(let o of n.notes)P(`# ${o}`);if(k(n.data??""),n.errorCount>0)throw new Ct(`module.json has ${n.errorCount} error(s)`,[...n.notes,`Validate: ${t}`,"",n.data??""].join(`
324
+ `))}},jw={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:{...It,"module-dir":{type:"string"}},async run(e){let t=await mu(await _o(e));for(let n of t.notes)P(`# ${n}`);k(t.data??"")}},vd={device:{type:"string"},viewport:{type:"string"},"settle-ms":{type:"string"},"screenshot-steps":{type:"boolean"}},gd=["desktop","tablet","mobile"];$d=[["scenario","array"],["mocks","object"],["user","object"],["url-params","object"],["vue-data","object"]],jd=[...$d.map(([e])=>[e,`${e}-file`]),["render"],["device"],["viewport"],["settle-ms"],["screenshot-steps"],["offline"],["brief"]];Cw={name:"module simulate",mcpName:"module_simulate",usage:`fui module simulate [-p P -m M | --module-dir D] [--scenario '[{"click":"Save"}]'] [--mocks '{"Fn":{"data":[]}}']
326
325
  [--user '{"SystemRight":3}'] [--url-params '{"id":"1"}'] (each also as --<name>-file path)
327
326
  real browser: --render [--device desktop|tablet|mobile] [--viewport 1366x768] [--vue-data {...}] [--settle-ms N]
328
327
  [--screenshot-steps] [--refresh-assets | --offline] (needs Playwright)
329
328
  --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(`
329
+ --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:{...It,"module-dir":{type:"string"},...mi("scenario","mocks","user","url-params","vue-data"),...vd,render:{type:"boolean"},"refresh-assets":{type:"boolean"},offline:{type:"boolean"},last:{type:"boolean"},brief:{type:"boolean"}},async run(e){let t=await _o(e),n=e.values;if(n.last===!0){let a=await wu(t,"module simulate");if(!a)throw new m("no saved input for this module yet; run fui module simulate with flags first");let c=bu(n,a,jd);n=c.merged,P(`# reused last input: ${c.reused.join(", ")||"none (every input given now)"}`)}let o={};for(let[a,c]of $d)o[a]=await gr(n,a,c);await yu(t,"module simulate",Iw(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 m(`--${l} needs --render`);let c=await hd(F(e),t,s);if(wd(c.text,n.brief===!0),c.hasFailures)throw new Ct("simulate: an assert failed",c.text);return}let r=Sd(n);if((r.devices?.length??0)>1)throw new m("module render takes one --device; component preview takes several");let i=await ud(F(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});wd(i.text,n.brief===!0);for(let a of i.screenshots)if(!i.text.includes(a))k(`screenshot: ${a}
330
+ `);if(i.hasFailures)throw new Ct("render: the page has failures",i.text)}},Rw={name:"component preview",mcpName:"component_preview",usage:`fui component preview <uc-name> [-p P] [-m M] [--cases '[{"name":"empty","props":{"items":[]}}]'] [--device desktop,mobile]
331
+ [--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:{...It,...mi("cases","user"),...vd},async run(e){let{values:t}=e,n=e.positionals[0];if(!n)throw new m("missing <uc-name> (file name without .vue)");let o=Sd(t),s=await dd(F(e),{componentName:n,projectId:f(t,"project"),moduleId:f(t,"module"),cases:await gr(t,"cases","array"),devices:o.devices,viewport:o.viewport,user:await gr(t,"user","object"),settleMs:o.settleMs,screenshotSteps:o.screenshotSteps});if(k(s.text),s.hasFailures)throw new Ct("component preview: a run has failures",s.text)}},Tw={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:{...It,"module-dir":{type:"string"}},async run(e){Ht(await su(F(e),await _o(e)))}},Pw={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:{...It,"module-dir":{type:"string"},approval:{type:"string"}},async run(e){Ht(await iu(F(e),await _o(e),f(e.values,"approval")))}},Ew={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:It,async run(e){Ht(await jr(F(e),await kd(e)))}};_w=[xr("list",async(e)=>cu(F(e),await Or(e)),"Refresh _components.json from the server (archives .vue files the server dropped)"),xr("new",async(e,t)=>Cr(F(e),await Or(e),t),"Create a component on the server from local components/<name>.vue"),xr("update",async(e,t)=>Rr(F(e),await Or(e),t),"Overwrite an existing component from local components/<name>.vue (skips when unchanged)"),xr("delete",async(e,t)=>lu(F(e),await Or(e),t),"Delete a component on the server (ask the user first); archives the local .vue")],Aw={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:bd,async run(e){Ht(await uu(F(e),await pi(e.values)))}},Nw={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:bd,async run(e){Ht(await du(F(e),await pi(e.values)))}},Dw={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"));Ht(await Tr(F(e),t))}},Id=[$w,jw,Cw,Tw,Pw,Ew,yd("script"),yd("css"),..._w,Rw,Aw,Nw,Dw]});import{rename as xw}from"node:fs/promises";import{join as Rd}from"node:path";async function Pd(e,t,n){let o=await pe(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 Ed(e,t){return(await e.api()).listFrameworkImportFiles(t)}async function _d(e,t){let o=(await(await e.api()).getFileContent(t))?.data?.FileContent??"",s=await ri(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 Ad(e,t,n){let o=await(await e.api()).uploadFileContent(t,n),s=await ri(e.paths,t,n);return{notes:[`ok uploaded content of import ${t}`,...s.map((r)=>`local copy synced: ${r}`)],data:`${JSON.stringify(o)}
332
+ `}}async function Nd(e,t){let n=await pe(e),{effectiveSort:o,note:s}=si(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)}
333
+ `}}async function Dd(e,t){let n=await pe(e),o=await oi(e.paths,t.fileId),s=t.fileSort,r="";if(t.fileSort!==void 0&&o.length>0){let u=si(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=Pn(t.contentType)?`imports/${bo(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 xw(Rd(u.baseDir,d),Rd(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 h=u.moduleId?`module ${u.moduleId}`:`project ${u.projectId}`;try{let{removed:w}=await n.syncImportsLocal(u.projectId,u.moduleId);c.push(`${h}: _imports.json synced${w.length>0?`, removed orphaned ${w.join(", ")}`:""}`)}catch{c.push(`${h}: sync failed`)}}return{notes:["ok import updated",...r?[r]:[],...c],data:`${JSON.stringify(i)}
334
+ `}}async function xd(e,t){let n=await pe(e),o=await oi(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)}
335
+ `}}function Ao(e){let t=typeof e==="number"?e:Number(e);if(!Number.isFinite(t))return String(e??"");let n=Ow[t];return n?`${t} (${n})`:String(t)}function Td(e,t){let n=t.trim().toLowerCase();return e.find((o)=>String(o.UserID??"").trim().toLowerCase()===n)}function Mr(e,t){return e.map((n)=>({userId:String(n.UserID??"?"),username:n.UserName||"—",fullName:n.FullName||"—",...t?{right:Ao(n.UserRight)}:{}}))}async function Od(e,t,n){return(await e.api()).searchUsers(t,n??"%")}async function Md(e,t){return(await e.api()).listProjectUserRights(t)}async function Fd(e,t,n){let o=await e.api(),s=`project ${t.projectId}`,r=t.userId.trim();if(!r)throw new m("empty userId; find the real UserID: fui user search <text>");let i=Td(await o.listProjectUserRights(t.projectId),r);if(n==="new"&&i)throw new m(`user "${r}" already has right ${Ao(i.UserRight)} on ${s}; change it: fui user set ${r} --right N`);if(n!=="new"&&!i)throw new m(`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=Td(c,r),u=n==="delete"?`ok revoked the right of "${r}" on ${s}`:`ok set right ${Ao(a)} for "${r}" on ${s}`,d=n==="delete"?l?`warning: post-check: the user is still listed with right ${Ao(l.UserRight)}; the server did not remove it`:"post-check: user no longer listed":l?`post-check: ${r} has ${Ao(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 Ow;var Ld=R(()=>{q();lt();xn();Ow={1:"View only",2:"Develop",3:"Admin"}});function Fr(e){if(k(`${e.notes.join(`
336
+ `)}
337
+ `),e.data)k(e.data)}function Vd(e){if(e===void 0)return;let t=Number(e);if(!Number.isFinite(t))throw new m("--sort must be a number");return t}function Lr(e){let t=e.positionals[0];if(!t)throw new m("missing <fileId>; list them: fui import list -p <project> [-m <module>]");return t}function Ud(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??"")}))}async function Wd(e){return(await e.targets.resolveProject(f(e.values,"project"))).projectId}function Vw(e){if(e!=="1"&&e!=="2"&&e!=="3")throw new m("--right must be 1 (View only), 2 (Develop) or 3 (Admin)");return Number(e)}function Ui(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 m("missing <UserID>; find it: fui user search <text>");let r=await Fd(F(o),{projectId:await Wd(o),userId:s,userRight:t==="delete"?void 0:Vw(f(o.values,"right"))},t);k(`${r.notes.join(`
337
338
  `)}
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(`
339
+ `),k(Z(Mr(r.rows,!0),o.render))}}}var Hd,qd,Mw,Fw,Lw,Uw,Hw,qw,Ww,Bw,Bd;var Jd=R(()=>{Re();q();at();me();Ld();wr();te();Hd={module:{type:"string",short:"m"}},qd={type:{type:"string"},"content-type":{type:"string"},name:{type:"string"},sort:{type:"string"},set:{type:"string",multiple:!0}};Mw={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:{...Hd,framework:{type:"string"}},async run(e){let t=F(e),n=f(e.values,"framework");if(n){k(Z(Ud(await Ed(t,n.toUpperCase())),e.render));return}let{rows:o,notes:s}=await Pd(t,f(e.values,"project"),f(e.values,"module"));for(let r of s)P(`# ${r}`);k(Z(Ud(o),e.render))}},Fw={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 _d(F(e),Lr(e));for(let o of t.notes)P(`# ${o}`);let n=f(e.values,"out");if(n)await O(n,t.data??""),k(`${n}
340
+ `);else k(t.data??"")}},Lw={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=Lr(e),n=f(e.values,"file"),o=e.positionals[1];if(n===void 0===(o!=="-"))throw new m("pass the content with -f <file> or - (stdin)");let s=n?await st(n):await Wo();Fr(await Ad(F(e),t,s))}},Uw={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:{...Hd,...qd},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 m("need --type, --content-type and --name");Fr(await Nd(F(e),{projectId:f(t,"project"),moduleId:f(t,"module"),fileType:n,contentType:o,fileName:s,fileSort:Vd(f(t,"sort")),extra:On(t.set)}))}},Hw={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:qd,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 m("need --type, --content-type and --name (the server replaces all three)");Fr(await Dd(F(e),{fileId:Lr(e),fileType:n,contentType:o,fileName:s,fileSort:Vd(f(t,"sort")),extra:On(t.set)}))}},qw={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){Fr(await xd(F(e),Lr(e)))}};Ww={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 m("missing <text>");let n=await Od(F(e),t,f(e.values,"id-like"));if(n.length===0){P(`# no user matches "${t}"; the user may not exist, or --id-like filtered it out (default % = no filter)`);return}P("# grant: fui user add <UserID> --right 3 (1=View only, 2=Develop, 3=Admin)"),k(Z(Mr(n,!1),e.render))}},Bw={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 Wd(e),n=await Md(F(e),t);if(n.length===0){P(`# nobody has a right on project ${t}; find a user: fui user search <text>`);return}k(Z(Mr(n,!0),e.render))}};Bd=[Mw,Fw,Lw,Uw,Hw,qw,Ww,Bw,Ui("add","new","Grant a right to a user who has none (exact UserID from fui user search)"),Ui("set","update","Change the right level of a user who already has one"),Ui("remove","delete","Revoke a user's right (ask the user first)")]});import{mkdir as Jw,readFile as Kw,stat as Gw,writeFile as zw}from"node:fs/promises";import{join as Hi}from"node:path";async function Kd(e){try{return(await Gw(e)).mtimeMs}catch{return null}}var Ur,Xw=86400000,Yw,Gd;var zd=R(()=>{q();me();Ur={component:"https://fui.vn/component.js","component-table":"https://fui.vn/componentTable.js","default-function":"https://fui.vn/defaultfunction.js","fast-project":"https://fui.vn/fastproject.js","default-style":"https://fui.vn/projectdefaultstyle.css"};Yw={name:"library",usage:`fui library [${Object.keys(Ur).join("|")}] [--refresh] [--path]`,summary:"FUI framework sources from fui.vn (component, table, default functions, fast project, style), cached a day",options:{refresh:{type:"boolean"},path:{type:"boolean"}},async run({positionals:e,values:t,paths:n}){let o=Hi(n.root,"_cache","library"),s=e[0];if(!s){let c=await Promise.all(Object.entries(Ur).map(async([l,u])=>{let d=Hi(o,u.slice(u.lastIndexOf("/")+1)),p=await Kd(d);return`${l} ${u} ${p?new Date(p).toISOString():"not cached"}`}));k(`name url cached
341
+ ${c.join(`
340
342
  `)}
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)}
343
+ `);return}let r=Ur[s];if(!r)throw new m(`unknown library "${s}"; choose ${Object.keys(Ur).join(", ")}`);let i=Hi(o,r.slice(r.lastIndexOf("/")+1)),a=await Kd(i);if(t.refresh===!0||a===null||Date.now()-a>Xw){let c=await fetch(r,{signal:AbortSignal.timeout(30000)});if(!c.ok)throw Error(`${r} answered HTTP ${c.status}`);await Jw(o,{recursive:!0}),await zw(i,await c.text()),P(`# fetched ${r}`)}else P(`# cached ${new Date(a).toISOString()}; --refresh to fetch again`);k(t.path===!0?`${i}
344
+ `:await Kw(i,"utf8"))}},Gd=[Yw]});import{appendFile as Qw,mkdir as Zw,rename as ey,stat as ty}from"node:fs/promises";import{dirname as ny,join as oy}from"node:path";function No(e){return oy(e,"_logs","tool-errors.jsonl")}function sy(e,t){let n=e.split(`
345
+ `).map((i)=>i.trim()).filter(Boolean),o=[],s=new Set,r=0;for(let i of Xd)for(let a of n){if(!a.startsWith(i)||s.has(a))continue;let c=a.length>200?`${a.slice(0,200)}…`:a;if(r+c.length+1>t)return o;s.add(a),o.push(c),r+=c.length+1}return o}function qi(e,t,n){if(e.length<=t+n)return e;let o=e.slice(t,e.length-n),s=sy(o,Math.round((t+n)/2)),r=s.length>0?[`…[cắt ${o.length} ký tự — giữ lại ${s.length} dòng lý do hỏng]…`,...s]:[`…[cắt ${o.length} ký tự]…`];return`${e.slice(0,t)}
343
346
  ${r.join(`
344
347
  `)}
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}`:""}
348
+ ${e.slice(-n)}`}async function Yd(e,t,n,o){try{let s=No(e);await Zw(ny(s),{recursive:!0});try{if((await ty(s)).size>ry)await ey(s,s.replace(/\.jsonl$/,".1.jsonl")).catch(()=>{return})}catch{}let r={ts:new Date().toISOString(),pid:process.pid,tool:t,message:qi(n,400,600),...o&&o!=="error"?{kind:o}:{}};await Qw(s,`${JSON.stringify(r)}
349
+ `,"utf-8")}catch{}}async function Qd(e){let t="";try{t=await B(No(e))}catch{}let n=[];for(let o of t.split(`
350
+ `)){if(!o.trim())continue;try{n.push(JSON.parse(o))}catch{}}return n}function iy(e){let t=e.split(`
351
+ `).map((o)=>o.trim()).filter(Boolean),n=Xd.map((o)=>t.find((s)=>s.startsWith(o))).find(Boolean);if(!n)return"(bản ghi không chứa dòng lý do hỏng — xem ví dụ bên dưới)";return n.replace(/^[❌⛔✘⚠️]+️?\s*/,"").replace(/'[^'\s]*'|"[^"\s]*"|`[^`\s]*`/g,"'…'").replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f-]{12,}\b/gi,"…").replace(/[A-Za-z]:[\\/][^\s,)]+|\/[^\s,)]*\/[^\s,)]+/g,"…").replace(/\b\d+\b/g,"N").replace(/\s+/g," ").trim().slice(0,140)}function Zd(e){return{errors:e.filter((t)=>t.kind!=="guard"),guards:e.filter((t)=>t.kind==="guard")}}function Vi(e){let t=new Map;for(let n of e){let o=iy(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))}var ry=1048576,Xd;var Wi=R(()=>{be();Xd=["❌","⛔","✘","⚠️"]});import{rm as ep}from"node:fs/promises";function tp(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 m(`${t} must be an integer ${n}-${o}`);return r}var ay,cy,np;var op=R(()=>{Re();q();me();Wi();ay={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=tp(f(t,"limit"),"--limit",0,Number.MAX_SAFE_INTEGER,15),s=tp(f(t,"sample-chars"),"--sample-chars",200,1e5,900),r=No(e.root),i=await Qd(e.root);if(n)i=i.filter((h)=>h.tool===n);let{errors:a,guards:c}=Zd(i);if(a.length===0&&c.length===0){k(`no errors logged${n?` for ${n}`:""}
349
352
  log file: ${r}
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(`
353
+ `);return}let l=new Map;for(let h of a)l.set(h.tool,(l.get(h.tool)??0)+1);let u=Vi(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((h,w)=>w[1]-h[1]).map(([h,w])=>` ${String(w).padStart(4)}x ${h}`),"",`recurring groups (${d.length}/${u.length}, most first):`];if(d.forEach((h,w)=>{if(p.push("",`[${w+1}] ${h.count}x ${h.tool} first ${h.firstTs} last ${h.lastTs}`,` ${h.signature}`),t.summary!==!0){p.push(" -- latest occurrence --");for(let y of qi(h.sample,Math.round(s*0.4),Math.round(s*0.6)).split(`
354
+ `))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 h of Vi(c).slice(0,5))p.push(` ${String(h.count).padStart(4)}x ${h.tool} ${h.signature}`)}k(`${p.join(`
352
355
  `)}
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(`
356
+ `)}},cy={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=No(e.root);await ep(t,{force:!0}),await ep(t.replace(/\.jsonl$/,".1.jsonl"),{force:!0}),k(`cleared ${t}
357
+ `)}},np=[ay,cy]});import{join as ly}from"node:path";async function rp(e,t){let n=typeof t==="string"?t:`${JSON.stringify(t,null,e.json?0:2)}
358
+ `,o=f(e.values,"out");if(o)await O(o,n),k(`${o}
359
+ `);else k(n)}function sp(e,t){let n=lr(e.paths,t);if(!n)throw new m(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var uy,Bi,Ji,dy,py,my,hy,ip;var ap=R(()=>{Re();q();me();lt();ci();Gs();xn();To();te();uy={module:{type:"string",short:"m"}},Bi=["load","ui","html","script","css","components","imports"],Ji=["info","config","modules","components","imports"];dy={name:"remote module",usage:`fui remote module [-p P -m M] --part ${Bi.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:{...uy,part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=f(e.values,"part");if(!t||!Bi.includes(t))throw new m(`--part must be one of: ${Bi.join(", ")}`);let{projectId:n,moduleId:o}=await e.targets.resolveProjectModule(f(e.values,"project"),f(e.values,"module")),s=await F(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 rp(e,r)}},py={name:"remote project",usage:`fui remote project [-p P] --part ${Ji.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||!Ji.includes(t))throw new m(`--part must be one of: ${Ji.join(", ")}`);let{projectId:n}=await e.targets.resolveProject(f(e.values,"project")),o=await F(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 rp(e,s)}};my={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 m("missing <path>");let t=e.positionals.map((r)=>sp(e,r)),n=new $o(e.paths,await F(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 m(`module ${i} unknown; run fui module list -p ${r.projectId}`);let p=await n.fetchModuleBundle(d);a=En(d,p.content,p.components,p.script,p.css,p.dependencies),o.set(i,a)}let c=await U(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=Mt(JSON.parse(c))===Mt(JSON.parse(l))}catch{}u=d?"same":"differs"}else u=xt(c)===xt(l)?"same":"differs";s.push(`${r.rel} ${u} ${ly(r.projectId,"modules",r.moduleId)}`)}k(`${s.join(`
357
360
  `)}
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(`
361
+ `)}},hy={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 m("missing <path>");let t=F(e),n=0;for(let o of e.positionals){let s=sp(e,o),r;if(!s.moduleId&&s.rel==="project.json")r=await Tr(t,s.projectId);else if(s.rel.startsWith("components/")&&s.rel.endsWith(".vue")&&!s.rel.slice(11).includes("/")){let i=await Dn(t,s.projectId,s.moduleId),a=s.rel.slice(11,-4);r=e.values.new===!0?await Cr(t,i,a):await Rr(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 jr(t,i):await Ir(t,i,s.rel)}else if(s.rel==="header.html"||s.rel==="body.html"){P(`# ${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{P(`# ${o}: no publisher for this file`),n=Math.max(n,2);continue}k(`${r.notes.join(`
359
362
  `)}
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(`
363
+ `)}return n}},ip=[dy,py,my,hy]});function fy(e){if(e===void 0)return bc;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new m("--limit must be an integer >= 0 (0 = no limit)");return t}var gy,wy,cp;var lp=R(()=>{Re();q();at();me();Cs();nn();gy={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 to(n[0],f(t,"file")),r=fy(f(t,"limit")),{rows:i,connection:a}=await Ec(await Q(e),s,{db:f(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)P(`# ${a.note}`);let c=Aa(i,r);k(Z(c.rows,o)),P(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},wy={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 to(n[0],f(t,"file")),{result:r,connection:i,notes:a}=await _c(await Q(e),s,{db:f(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)P(`# ${i.note}`);let l=Cn(r);k(o?ss({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
364
+ `);for(let u of a)P(`# ${u}`)}},cp=[gy,wy]});function Ki(e){if(e===void 0)return;let t=gs.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new m(`--type must be one of: ${gs.join(", ")}`);return t}var yy,by,up;var Gi=R(()=>{Re();q();me();lo();nn();yy={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 jc(await Q(e),{db:f(e.values,"db"),objectType:Ki(f(e.values,"type")),search:f(e.values,"search")});for(let n of t.notes)P(`# ${n}`);k(t.data??"")}},by={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 $c(await Q(e),f(e.values,"db"),e.values["prune-local-only"]===!0);k(`${t.notes.join(`
365
+ `)}
366
+ `)}},up=[yy,by]});var Hr="---\nname: fui\ndescription: Use the `fui` CLI for all FUI (low-code platform) work — projects, modules (get, validate, simulate, preview, publish), components, imports, project users, rights, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), framework library, workspace files, publish ledger and history. Run it in the shell.\n---\n\n# fui CLI\n\n`fui` is the command-line tool for the FUI low-code platform. Run it with your shell tool.\n\n## Before you start\n\n- FUI has its own conventions (module.json action engine, tAPI SP naming, `f-*` components). They differ from standard Vue/Vuetify/REST, so never rely on training-data assumptions.\n- Before creating, editing, reviewing or deploying any module, component or stored procedure: run `fui skill get`, pick the reference files for your task from `references/INDEX.md`, and load them with `fui skill ref references/<file>.md ...`. The same files are in the `fui-skill` skill.\n- Strict mode (`FUI_STRICT=1`) blocks publishing and deploys until the skill was read this session (`fui skill get`, `fui skill ref` or `fui skill loaded`) and while module.json has validation errors.\n- First use on a machine: `fui config set api-base <url>` and `printf %s \"$TOKEN\" | fui config set api-token -` (ask the user for both). `fui config` shows what is set.\n\n## Working rules\n\n- **Build order:** UI first with fake data in `data[]` plus a visual check, then design and deploy the SP contract, then replace the fake data with real API bindings (IN/OUT/CALLBACK).\n- **Check cheapest first:** `module validate` (fix every error) → `module outline` → `module simulate` → `component preview` → `module simulate --render`. Read every screenshot you get and fix what you see in the same turn.\n- **Never call a real API/SP/DML \"to test\".** Verifying an SP means reading its body (`fui sp verify`). Data edits the user asks for still run, gated by `--confirm-write`.\n- **Create and edit are separate commands** (`new` / `update`); nothing upserts. Editing needs the record id.\n- **Publishing is per file:** edit the local file, then push it. Pushing module.json does not carry script.js along (`module publish json` / `script` / `css` / `html`, `component new` / `update`, `import upload`, `project publish-config`).\n- **Local files are a snapshot;** other people publish to the same project. `module get`, `project sync`, `schema pull` and `sp get` overwrite every file in their scope without asking. Whatever they replace is copied to `<workspace>/_history/` first (`fui file history <path>`, `fui file restore <path>`).\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: flag, env (`FUI_WORKDIR`, `FUI_API_BASE`, `FUI_API_TOKEN`), `fui config set`, then a Fui MCP entry in `~/.claude.json` if one exists.\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; the skills for Claude Code, Codex and Antigravity refresh on their own. `fui --version` prints the version.\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` → show the changes to the user → 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|…` 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## Components\n\n- A component is one `uc-<name>.vue` file (Vue 2 options API). FUI takes the name from the file name and registers every `uc-*.vue` globally, so leave out `name:` and `components: {}` in `export default`.\n- Use Vuetify components where they fit and validate props.\n- Check it alone with `fui component preview <uc-name> --cases [...]` before wiring it into a module, then `fui component new|update <name>`.\n\n## Framework library\n\n`fui library` lists the FUI framework sources on fui.vn (`component`, `component-table`, `default-function`, `fast-project`, `default-style`). `fui library default-function --path` caches one for a day and prints its path, so you can `grep` it instead of reading it whole.\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>` → `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` → `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` → 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\n## Coming from Fui MCP\n\n`fui` shares the workspace, connection files, publish ledger, `_history` and error log with Fui MCP, so the two can run side by side. Each Fui MCP 1.4.0 tool maps to a command with the same guards, files and requests:\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 {…}` / `project update --data {id:…}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set …` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] …` / `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` / `… script` / `… 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 …` / `import update <id> …` / `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> …` |\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 {…}] [--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| resources lowcode://library/v2/\\* / prompt generate-vue-component | `library <name>` / the Components section above |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n";var zi=()=>{};function Gn(e){return e.replace(Sy,(t,n)=>`fui ${pp[n]} *`).replace(vy,(t,n)=>`fui ${dp[n]}`)}function mp(e){let t=Gn(e),n=t.startsWith(`---
367
+ `)?t.indexOf(`
368
+ ---
369
+ `,4):-1;if(n<0)return`> Installed by the \`fui\` CLI: tool names below are \`fui\` commands (run them in the shell). Object arguments such as \`({ name: "X" })\` become positionals or flags; \`fui <command> --help\` shows them.
370
+
371
+ ${t}`;let o=n+5;return`${t.slice(0,o)}
372
+ > Installed by the \`fui\` CLI: tool names below are \`fui\` commands (run them in the shell). Object arguments such as \`({ name: "X" })\` become positionals or flags; \`fui <command> --help\` shows them.
373
+ ${t.slice(o)}`}var dp,pp,vy,Sy;var Xi=R(()=>{dp={session_set:"use",session_read:"use",session_clear:"use --clear",session_resolve:"resolve",project_list:"project list",project_sync:"project sync",project_new:"project new",project_update:"project update",project_publish_config:"project publish-config",project_user_search:"user search",project_user_list:"user list",project_user_new:"user add",project_user_update:"user set",project_user_delete:"user remove",module_list:"module list",module_new:"module new",module_update:"module update",module_delete:"module delete",module_get:"module get",module_get_ui:"module get-ui",module_get_html:"module get-html",module_validate:"module validate",module_preview:"module preview",module_outline:"module outline",module_simulate:"module simulate",module_publish_html:"module publish html",module_publish_json:"module publish json",module_publish_script:"module publish script",module_publish_css:"module publish css",component_list:"component list",component_get:"component get",component_new:"component new",component_update:"component update",component_delete:"component delete",component_preview:"component preview",file_import_list:"import list",file_import_get:"import get",file_import_new:"import new",file_import_update:"import update",file_import_upload_content:"import upload",file_import_delete:"import delete",error_log_read:"log read",error_log_clear:"log clear",db_config_read:"db list",db_connect:"db add",db_connect_by_name:"db add-by-name",db_alias_list:"db alias list",db_alias_new:"db alias new",db_user_token_set:"db user-token",db_token_rebuild:"db token-rebuild",right_system_list:"right system list",right_system_new:"right system new",right_system_update:"right system update",right_system_delete:"right system delete",right_function_list:"right function list",right_function_new:"right function new",right_function_update:"right function update",right_function_delete:"right function delete",db_schema_get:"schema pull",db_schema_read:"schema",db_sp_list:"sp list",db_sp_get:"sp get",db_sp_save:"sp save",db_sp_deploy:"sp deploy",db_sp_help:"sp help",db_sp_verify:"sp verify",db_sp_delete:"sp delete",db_sp_rename:"sp rename",db_sql_execute:"query",db_sql_execute_nonquery:"exec",skill_get:"skill get",skill_reference_get:"skill ref",skill_sync:"skill sync",design_list:"design list",design_read:"design read"},pp={project_user_:"user",module_publish_:"module publish",right_system_:"right system",right_function_:"right function",file_import_:"import"},vy=new RegExp(`\\b(${Object.keys(dp).sort((e,t)=>t.length-e.length).join("|")})\\b(?!\\*)`,"g"),Sy=new RegExp(`\\b(${Object.keys(pp).join("|")})\\*`,"g")});import{cp as $y,mkdir as jy,rm as Iy}from"node:fs/promises";import{homedir as Cy}from"node:os";import{join as Qe}from"node:path";function zn(e){return e.HOME||Cy()}function Ry(e,t=process.env){if(e==="claude")return Qe(t.CLAUDE_CONFIG_DIR||Qe(zn(t),".claude"),"skills");if(e==="codex")return Qe(zn(t),".agents","skills");return Qe(zn(t),".gemini","antigravity","skills")}async function Yi(e){try{return await Wr(e)}catch{return!1}}async function Qi(e=process.env){let t=[];if(await Yi(e.CLAUDE_CONFIG_DIR||Qe(zn(e),".claude")))t.push("claude");if(await Yi(e.CODEX_HOME||Qe(zn(e),".codex")))t.push("codex");if(await Yi(Qe(zn(e),".gemini","antigravity")))t.push("antigravity");return t}async function Zi(e){await O(Qe(e,"fui","SKILL.md"),Hr);let t=Qe(e,Lt);await Iy(t,{recursive:!0,force:!0}),await jy(e,{recursive:!0}),await $y(await sn(),t,{recursive:!0});for await(let n of fn("**/*.md",t)){let o=Qe(t,n),s=await st(o);await O(o,n==="SKILL.md"?mp(s):Gn(s))}return[Qe(e,"fui"),t]}async function ea(e,t=process.env){let n=[];for(let i of e)n.push(...await Zi(Ry(i,t)));let o=await wo(t),s=o.skillsVersion===fe?o.skillsAgents??[]:[],r=Do.filter((i)=>e.includes(i)||s.includes(i));return await ct({...o,skillsVersion:fe,skillsAgents:r},t),n}async function hp(e=process.env,t=rt()){if(!ir(t))return null;let n=e.FUI_AUTO_SKILLS?.trim().toLowerCase();if(n&&["0","off","false","no"].includes(n))return null;let o=await wo(e),s=o.skillsVersion===fe?o.skillsAgents??[]:[],r=(await Qi(e)).filter((i)=>!s.includes(i));if(r.length===0)return null;return await ea(r,e),`# fui ${fe} skills installed for ${r.join(", ")}`}var Do;var ta=R(()=>{zi();yo();fr();Xi();te();Do=["claude","codex","antigravity"]});import{join as fp}from"node:path";async function gp(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Ty,Py,Ey,_y,Ay,Ny,Dy,xy,wp;var yp=R(()=>{zi();Re();q();me();be();fr();Xi();ta();Ty={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Hr)}},Py={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 Cl(await gp(e));await e.session.markSkillLoaded(),P(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${Gn(t.text)}
374
+ `)}},Ey={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 m("missing <path>; see references/INDEX.md");await e.session.markSkillLoaded();let t=await gp(e),n=t?fp(t,"_skills",Lt):await sn();P(`# skill dir: ${n}`),k(`${Gn(await Rl(n,e.positionals)).trimStart()}
375
+ `)}},_y={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
376
+ `)}},Ay={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 hr(e.root);k(`${t.skillDir} (${t.source})
377
+ `)}},Ny={name:"skill install",usage:"fui skill install [--agent claude,codex,antigravity|all] [--dir <skills-root>]",summary:"Install the fui and fui-skill skills for Claude, Codex and Antigravity (default: those found); a global install does this itself",options:{agent:{type:"string"},dir:{type:"string"}},async run({values:e}){let t=f(e,"dir");if(t){k(`${(await Zi(t)).join(`
362
378
  `)}
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}
368
- ${o}
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)}
379
+ `);return}let n=f(e,"agent"),o=n==="all"?[...Do]:n?n.split(",").map((s)=>s.trim()):await Qi();if(o.length===0)throw new m("no agent found (~/.claude, ~/.codex, ~/.gemini/antigravity); pass --agent or --dir");for(let s of o)if(!Do.includes(s))throw new m(`--agent takes ${Do.join(", ")} or all`);k(`${(await ea(o)).join(`
380
+ `)}
381
+ `)}},Dy={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 ui();P(`# ${t.length} brands`),k(e.json?`${JSON.stringify(t)}
370
382
  `:`${t.join(`
371
383
  `)}
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}
384
+ `)}},xy={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 m("missing <brand>; run: fui design list");let n=await ui();if(!n.includes(t))throw new m(`unknown brand "${t}"; available: ${n.join(", ")}`);let o=fp(await sn(),"design-md",t,"DESIGN.md");P(`# source: ${o}`),k(await B(o))}},wp=[Ty,Py,Ey,_y,Ay,Ny,Dy,xy]});function pn(e){let t=e[0];if(!t)throw new m("missing <name>");return t}function qy(e){if(e===void 0)return;let t;try{t=JSON.parse(e)}catch(n){throw new m(`--params is not valid JSON: ${n.message}`)}if(!t||typeof t!=="object"||Array.isArray(t))throw new m("--params must be a JSON object");return t}var Oy,My,Fy,Ly,Uy,Hy,Vy,Wy,By,bp;var kp=R(()=>{Re();q();at();me();nn();Gi();te();Oy={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 Ic(await Q(e),{db:f(e.values,"db"),objectType:Ki(f(e.values,"type")),search:f(e.values,"search")});for(let r of n)P(`# ${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(Z(s,e.render))}},My={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 Cc(await Q(e),pn(e.positionals),f(e.values,"db"));for(let o of t.notes)P(`# ${o}`);let n=f(e.values,"out");if(n)await O(n,t.body),k(`${n}
373
385
  `);else k(e.values.print===!0?t.body:`${t.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}
386
+ `)}},Fy={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=pn(e.positionals),n=f(e.values,"sql"),o=e.positionals[1];if(n!==void 0&&o!==void 0)throw new m("pass --sql or - / -f, not both");let s=await to(n??o,f(e.values,"file")),r=await Rc(await Q(e),t,s,f(e.values,"db"));for(let i of r.notes)P(`# ${i}`);k(`${r.path}
375
387
  next: fui sp deploy ${t} --db ${r.alias}
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(`
388
+ `)}},Ly={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=At(pn(e.positionals)),n=await Q(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)}
389
+ `)}},Uy={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 Tc(await Q(e),pn(e.positionals),f(e.values,"db"));k(`${t.notes.join(`
378
390
  `)}
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(`
391
+ `)}},Hy={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 Pc(await Q(e),pn(e.positionals),f(e.values,"db"),e.values.full===!0?0:kc);for(let n of t.notes)P(`# ${n}`);k(t.data??"")}};Vy={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 Ac(await Q(e),{name:pn(e.positionals),db:f(e.values,"db"),params:qy(f(e.values,"params")),urlParams:Array.isArray(t)?t.map(String):void 0,checkGrant:e.values["check-grant"]===!0});k(`${n.notes.join(`
380
392
  `)}
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(`
393
+ `)}},Wy={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 m("--type must be Procedure or Function");let n=await Nc(await Q(e),pn(e.positionals),t,f(e.values,"db"));k(`${n.notes.join(`
382
394
  `)}
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(`
395
+ `)}},By={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 m("usage: fui sp rename <old> <new>");let o=await Dc(await Q(e),t,n,f(e.values,"db"));k(`${o.notes.join(`
384
396
  `)}
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
387
- `:`fui ${je} is current (latest ${n})
388
- `);return}let t=await Lr();if(t.action==="current")k(`fui ${je} is current
389
- `);else if(t.action==="installed")k(`updated ${je} -> ${t.latest}; refresh installed skills: fui skill install
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
393
- lists: --filter text --where col=value (repeatable) --fields a,b,c (client-side; the request is unchanged)
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
397
+ `)}},bp=[Oy,My,Fy,Ly,Uy,Hy,Vy,Wy,By]});var Jy,Ky,vp;var Sp=R(()=>{Re();q();me();Tt();at();Jy={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}`,...Jo(o.dbToken)};k(Z([s],n))}},Ky={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 xe(void 0,f(e,"pwd-env"),"--pwd");if(!t||!n||!o||!s)throw new m("need --server, --database, --uid and --pwd-env");k(`${kn(t,n,o,s)}
398
+ `)}},vp=[Jy,Ky]});var Gy,$p;var jp=R(()=>{q();me();yo();te();Gy={name:"update",usage:"fui update [--check]",summary:"Update fui to the latest npm release with the package manager that installed it (--check only reports)",options:{check:{type:"boolean"},background:{type:"boolean"}},async run({values:e}){if(e.background===!0){await Js().catch(()=>{return});return}if(e.check===!0){let n=await Ws();k(Bs(n)?`update available: ${fe} -> ${n}; run: fui update
399
+ `:`fui ${fe} is current (latest ${n})
400
+ `);return}let t=await Js();if(t.action==="current")k(`fui ${fe} is current
401
+ `);else if(t.action==="installed")k(`updated ${fe} -> ${t.latest}; skills refreshed for Claude, Codex and Antigravity (those found)
402
+ `);else if(t.action==="manual")P(`# ${rt()} is not a global bun or npm install (checkout or compiled binary); it cannot replace itself`),k(`update available: ${fe} -> ${t.latest}; install: ${t.detail}
403
+ `);else return P(t.detail??""),k(`update to ${t.latest} failed; retry: ${rr(sr()?.manager??"bun",t.latest)}
404
+ `),Ve.Runtime}},$p=[Gy]});import{parseArgs as zy}from"node:util";function Ip(){let e=Math.max(...na.map((n)=>n.name.length)),t=na.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`);return`fui ${fe}: FUI platform CLI
395
405
 
396
406
  commands:
397
407
  ${t.join(`
398
408
  `)}
399
409
 
400
- ${Su}
410
+ ${Cp}
401
411
  run "fui skill" for the agent guide
402
- `}function yg(e){return`${e.usage}
412
+ `}function Xy(e){return`${e.usage}
403
413
  ${e.summary}
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};
414
+ ${Cp}
415
+ `}function Qy(e){let t=[];for(let n of e.slice(0,Yy)){if(n.startsWith("-"))break;t.push(n)}for(let n=t.length;n>0;n--){let o=t.slice(0,n).join(" "),s=na.find((r)=>r.name===o);if(s)return{command:s,rest:e.slice(n)}}return null}async function Zy(e,t,n,o){if(n instanceof Pe&&n.severity==="soft")return;let s=n instanceof Pe?n.logKind:"error",r=n instanceof Ct?n.report:`❌ ${o}`;await Yd(e,t.mcpName??`fui ${t.name}`,r,s)}async function eb(e){let t=Qy(e);if(!t){let r=e[0];if(!r||r==="-h"||r==="--help"||r==="help")return k(Ip()),Ve.Ok;if(r==="-v"||r==="--version")return k(`${fe}
416
+ `),Ve.Ok;return P(`unknown command: ${e.slice(0,2).join(" ")}
417
+ `),P(Ip()),Ve.Usage}let{command:n,rest:o}=t,s;try{let r;try{r=zy({args:o,options:{...Ta,...n.options},allowPositionals:!0,strict:!0})}catch(i){throw new m(`${i.message}
418
+ usage: ${n.usage}`)}if(r.values.help===!0)return k(Xy(n)),Ve.Ok;return s=await Pa(r.values,r.positionals),await n.run(s)??Ve.Ok}catch(r){let i=la(r),a=i===Ve.Guard?"guard":"error",c=r.message;if(P(`${a}: ${c}`),s)await Zy(s.paths.root,n,r,c);return i}}async function Rp(e=process.argv.slice(2)){let t=await el().catch(()=>null);if(t)P(t);let n=await hp().catch(()=>null);if(n)P(n);process.exitCode=await eb(e)}var na,Cp=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json --no-header -h/--help -v/--version
419
+ lists: --filter text --where col=value (repeatable) --fields a,b,c (client-side; the request is unchanged)
420
+ exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`,Yy=3;var Tp=R(()=>{Re();q();me();Xc();ol();sl();cl();Wl();Cd();Jd();zd();op();ap();lp();Gi();yp();kp();Sp();jp();ta();yo();Wi();na=[...rl,...zc,...vp,...cp,...up,...bp,...Vl,...Id,...Bd,...ip,...al,...wp,...Gd,...np,...nl,...$p]});te();import{spawnSync as tb}from"node:child_process";var Pp=Bt||process.env.FUI_RUNTIME==="node"?null:Ho("bun");if(Pp){let e=tb(Pp,[rt(),...process.argv.slice(2)],{stdio:"inherit"});process.exit(e.status??1)}await Promise.resolve().then(() => Tp());await Rp();