@fui-org/fui-cli 0.2.0 → 0.3.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.
Files changed (212) hide show
  1. package/README.md +13 -1
  2. package/dist/fui.js +160 -43
  3. package/package.json +4 -2
  4. package/skills/fui/SKILL.md +131 -66
  5. package/skills/fui-skill/README.md +112 -0
  6. package/skills/fui-skill/SKILL.md +269 -0
  7. package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
  8. package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
  9. package/skills/fui-skill/design-md/airbnb/README.md +5 -0
  10. package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
  11. package/skills/fui-skill/design-md/airtable/README.md +5 -0
  12. package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
  13. package/skills/fui-skill/design-md/apple/README.md +5 -0
  14. package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
  15. package/skills/fui-skill/design-md/asu/README.md +107 -0
  16. package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
  17. package/skills/fui-skill/design-md/binance/README.md +5 -0
  18. package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
  19. package/skills/fui-skill/design-md/bmw/README.md +5 -0
  20. package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
  21. package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
  22. package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
  23. package/skills/fui-skill/design-md/bugatti/README.md +5 -0
  24. package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
  25. package/skills/fui-skill/design-md/cal/README.md +5 -0
  26. package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
  27. package/skills/fui-skill/design-md/claude/README.md +5 -0
  28. package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
  29. package/skills/fui-skill/design-md/clay/README.md +5 -0
  30. package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
  31. package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
  32. package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
  33. package/skills/fui-skill/design-md/cohere/README.md +5 -0
  34. package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
  35. package/skills/fui-skill/design-md/coinbase/README.md +5 -0
  36. package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
  37. package/skills/fui-skill/design-md/composio/README.md +5 -0
  38. package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
  39. package/skills/fui-skill/design-md/cursor/README.md +5 -0
  40. package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
  41. package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
  42. package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
  43. package/skills/fui-skill/design-md/expo/README.md +5 -0
  44. package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
  45. package/skills/fui-skill/design-md/ferrari/README.md +5 -0
  46. package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
  47. package/skills/fui-skill/design-md/figma/README.md +5 -0
  48. package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
  49. package/skills/fui-skill/design-md/framer/README.md +5 -0
  50. package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
  51. package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
  52. package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
  53. package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
  54. package/skills/fui-skill/design-md/ibm/README.md +5 -0
  55. package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
  56. package/skills/fui-skill/design-md/intercom/README.md +5 -0
  57. package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
  58. package/skills/fui-skill/design-md/kraken/README.md +5 -0
  59. package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
  60. package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
  61. package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
  62. package/skills/fui-skill/design-md/linear.app/README.md +5 -0
  63. package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
  64. package/skills/fui-skill/design-md/lovable/README.md +5 -0
  65. package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
  66. package/skills/fui-skill/design-md/mastercard/README.md +5 -0
  67. package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
  68. package/skills/fui-skill/design-md/meta/README.md +5 -0
  69. package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
  70. package/skills/fui-skill/design-md/minimax/README.md +5 -0
  71. package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
  72. package/skills/fui-skill/design-md/mintlify/README.md +5 -0
  73. package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
  74. package/skills/fui-skill/design-md/miro/README.md +5 -0
  75. package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
  76. package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
  77. package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
  78. package/skills/fui-skill/design-md/mongodb/README.md +5 -0
  79. package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
  80. package/skills/fui-skill/design-md/nike/README.md +5 -0
  81. package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
  82. package/skills/fui-skill/design-md/notion/README.md +5 -0
  83. package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
  84. package/skills/fui-skill/design-md/nvidia/README.md +5 -0
  85. package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
  86. package/skills/fui-skill/design-md/ollama/README.md +5 -0
  87. package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
  88. package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
  89. package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
  90. package/skills/fui-skill/design-md/pinterest/README.md +5 -0
  91. package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
  92. package/skills/fui-skill/design-md/playstation/README.md +5 -0
  93. package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
  94. package/skills/fui-skill/design-md/posthog/README.md +5 -0
  95. package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
  96. package/skills/fui-skill/design-md/raycast/README.md +5 -0
  97. package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
  98. package/skills/fui-skill/design-md/renault/README.md +5 -0
  99. package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
  100. package/skills/fui-skill/design-md/replicate/README.md +5 -0
  101. package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
  102. package/skills/fui-skill/design-md/resend/README.md +5 -0
  103. package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
  104. package/skills/fui-skill/design-md/revolut/README.md +5 -0
  105. package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
  106. package/skills/fui-skill/design-md/runwayml/README.md +5 -0
  107. package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
  108. package/skills/fui-skill/design-md/sanity/README.md +5 -0
  109. package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
  110. package/skills/fui-skill/design-md/sentry/README.md +5 -0
  111. package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
  112. package/skills/fui-skill/design-md/shopify/README.md +5 -0
  113. package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
  114. package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
  115. package/skills/fui-skill/design-md/spacex/README.md +5 -0
  116. package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
  117. package/skills/fui-skill/design-md/spotify/README.md +5 -0
  118. package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
  119. package/skills/fui-skill/design-md/starbucks/README.md +5 -0
  120. package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
  121. package/skills/fui-skill/design-md/stripe/README.md +5 -0
  122. package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
  123. package/skills/fui-skill/design-md/supabase/README.md +5 -0
  124. package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
  125. package/skills/fui-skill/design-md/superhuman/README.md +5 -0
  126. package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
  127. package/skills/fui-skill/design-md/tesla/README.md +5 -0
  128. package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
  129. package/skills/fui-skill/design-md/theverge/README.md +5 -0
  130. package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
  131. package/skills/fui-skill/design-md/together.ai/README.md +5 -0
  132. package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
  133. package/skills/fui-skill/design-md/uber/README.md +5 -0
  134. package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
  135. package/skills/fui-skill/design-md/vercel/README.md +5 -0
  136. package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
  137. package/skills/fui-skill/design-md/vodafone/README.md +5 -0
  138. package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
  139. package/skills/fui-skill/design-md/voltagent/README.md +5 -0
  140. package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
  141. package/skills/fui-skill/design-md/warp/README.md +5 -0
  142. package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
  143. package/skills/fui-skill/design-md/webflow/README.md +5 -0
  144. package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
  145. package/skills/fui-skill/design-md/wired/README.md +5 -0
  146. package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
  147. package/skills/fui-skill/design-md/wise/README.md +5 -0
  148. package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
  149. package/skills/fui-skill/design-md/x.ai/README.md +5 -0
  150. package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
  151. package/skills/fui-skill/design-md/zapier/README.md +5 -0
  152. package/skills/fui-skill/examples/component.vue +162 -0
  153. package/skills/fui-skill/examples/f-table-patterns.json +331 -0
  154. package/skills/fui-skill/examples/module-patterns.json +973 -0
  155. package/skills/fui-skill/examples/project-patterns.json +222 -0
  156. package/skills/fui-skill/metadata.json +75 -0
  157. package/skills/fui-skill/references/INDEX.md +144 -0
  158. package/skills/fui-skill/references/advanced-techniques.md +160 -0
  159. package/skills/fui-skill/references/coding-standards.md +112 -0
  160. package/skills/fui-skill/references/component-design.md +455 -0
  161. package/skills/fui-skill/references/component-quickref.md +77 -0
  162. package/skills/fui-skill/references/component-table.md +276 -0
  163. package/skills/fui-skill/references/components-dialog.md +192 -0
  164. package/skills/fui-skill/references/components-display.md +147 -0
  165. package/skills/fui-skill/references/components-echart.md +391 -0
  166. package/skills/fui-skill/references/components-input.md +359 -0
  167. package/skills/fui-skill/references/controls-patterns.md +847 -0
  168. package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
  169. package/skills/fui-skill/references/db-table-design.md +77 -0
  170. package/skills/fui-skill/references/db-workflow.md +504 -0
  171. package/skills/fui-skill/references/default-function.md +415 -0
  172. package/skills/fui-skill/references/design-modes.md +85 -0
  173. package/skills/fui-skill/references/echart-templates.md +481 -0
  174. package/skills/fui-skill/references/fastproject.md +97 -0
  175. package/skills/fui-skill/references/fsheet.md +218 -0
  176. package/skills/fui-skill/references/fullstack-workflow.md +351 -0
  177. package/skills/fui-skill/references/module-data-patterns.md +126 -0
  178. package/skills/fui-skill/references/module-json-anatomy.md +137 -0
  179. package/skills/fui-skill/references/module-structure.md +260 -0
  180. package/skills/fui-skill/references/new-session.md +108 -0
  181. package/skills/fui-skill/references/pdfmake.md +60 -0
  182. package/skills/fui-skill/references/permission-system.md +169 -0
  183. package/skills/fui-skill/references/platform-architecture.md +294 -0
  184. package/skills/fui-skill/references/project-config.md +335 -0
  185. package/skills/fui-skill/references/project-provisioning.md +383 -0
  186. package/skills/fui-skill/references/script-map.md +296 -0
  187. package/skills/fui-skill/references/sql-clr-functions.md +224 -0
  188. package/skills/fui-skill/references/system-design.md +116 -0
  189. package/skills/fui-skill/references/tapi-file-api.md +191 -0
  190. package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
  191. package/skills/fui-skill/references/tapi-reference.md +549 -0
  192. package/skills/fui-skill/references/tools-registry.md +460 -0
  193. package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
  194. package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
  195. package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
  196. package/skills/fui-skill/references/ui-patterns.md +315 -0
  197. package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
  198. package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
  199. package/skills/fui-skill/references/ui-templates.md +29 -0
  200. package/skills/fui-skill/references/verification.md +246 -0
  201. package/skills/fui-skill/references/watcher-patterns.md +196 -0
  202. package/skills/fui-skill/references/websocket-realtime.md +254 -0
  203. package/skills/fui-skill/scripts/component-3.0.js +2549 -0
  204. package/skills/fui-skill/scripts/component.js +3142 -0
  205. package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
  206. package/skills/fui-skill/scripts/componentTable.js +769 -0
  207. package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
  208. package/skills/fui-skill/scripts/defaultfunction.js +966 -0
  209. package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
  210. package/skills/fui-skill/scripts/fastproject.js +828 -0
  211. package/skills/fui-skill/scripts/fechart.js +890 -0
  212. package/skills/fui-skill/scripts/fsheet.js +1330 -0
package/dist/fui.js CHANGED
@@ -1,30 +1,24 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{parseArgs as hn}from"util";import{homedir as ut}from"os";import{join as A,resolve as lt}from"path";var C={Ok:0,Runtime:1,Usage:2,Guard:3};class a extends Error{name="UsageError"}class P extends Error{name="GuardError"}function we(e){if(e instanceof a)return C.Usage;if(e instanceof P)return C.Guard;return C.Runtime}var q=["workdir"];function L(e){return e.HOME||ut()}function _(e,t=Bun.env,n=process.cwd()){let o=e.trim();if(o==="~")return L(t);if(o.startsWith("~/"))return A(L(t),o.slice(2));return lt(n,o)}function j(e=Bun.env){let t=e.XDG_CONFIG_HOME||A(L(e),".config");return A(t,"fui","config.json")}async function B(e=Bun.env){let t=j(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 a(`${t} is not a JSON object; fix or delete it`)}async function X(e,t=Bun.env){let n=j(t);return await Bun.write(n,`${JSON.stringify(e,null,2)}
4
- `),n}function dt(e){return e.CLAUDE_CONFIG_DIR?A(e.CLAUDE_CONFIG_DIR,".claude.json"):A(L(e),".claude.json")}function pt(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function ye(e){if(!e||typeof e!=="object")return;for(let[t,n]of Object.entries(e)){let o=n?.env?.FUI_MCP_WORKDIR;if(typeof o==="string"&&o.trim()&&pt(t,n))return o}return}async function mt(e=Bun.env,t=process.cwd()){let n=dt(e),o=Bun.file(n);if(!await o.exists())return null;let r;try{r=await o.json()}catch{return null}let s=ye(r.projects?.[t]?.mcpServers)??ye(r.mcpServers);return s?{path:_(s,e,t),file:n}:null}var ft=A(".fui-mcp","workspaces");async function be(e,t=Bun.env,n=process.cwd()){if(e)return{path:_(e,t,n),source:"flag",detail:"--workdir"};if(t.FUI_MCP_WORKDIR)return{path:_(t.FUI_MCP_WORKDIR,t,n),source:"env",detail:"FUI_MCP_WORKDIR"};let o=await B(t);if(typeof o.workdir==="string"&&o.workdir)return{path:_(o.workdir,t,n),source:"config",detail:j(t)};let r=await mt(t,n);if(r)return{path:r.path,source:"mcp",detail:r.file};return{path:A(L(t),ft),source:"default",detail:"built-in"}}function z(e){return new TextEncoder().encode(e).toBase64()}function Z(e){try{return new TextDecoder("utf-8",{fatal:!0}).decode(Uint8Array.fromBase64(e.trim()))}catch{throw new a("token is not valid Base64 UTF-8")}}function Ee(e,t,n,o){return z(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function Te(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 gt(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=Te(t);if(n.has("server")||n.has("data source"))return"mssql";throw new a("unsupported connection: expected postgres://, mysql://, mariadb://, sqlite:/file: URL or MSSQL Server=...; string")}function v(e){let t=Z(e);return{engine:gt(t),plain:t}}function W(e){let{engine:t,plain:n}=v(e);if(t==="mssql"){let r=Te(n);return{engine:t,server:r.get("server")??r.get("data source")??"",database:r.get("database")??r.get("initial catalog")??"",user:r.get("uid")??r.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{mkdir as ht,writeFile as wt}from"fs/promises";import{basename as Ce,dirname as Ae,extname as Re,isAbsolute as yt,join as Se,relative as bt}from"path";var ke=50;function Et(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 Tt(e,t,n){let o=bt(e.root,t),s=o!==""&&!o.startsWith("..")&&!yt(o)?o:Se("_external",Ce(t)),i=Re(s),c=Ce(s,i),l=n?`_${n}`:"";return Se(e.historyRoot(),Ae(s),`${c}_${Et()}${l}${i}`)}async function Ct(e,t){let n=Re(e),o=e.slice(0,e.length-n.length);for(let s=1;s<=ke;s++){let i=s===1?e:`${o}_${s}${n}`;try{return await wt(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(c){if(c.code!=="EEXIST")throw c}}let r=`${o}_${ke}${n}`;return await Bun.write(r,t),r}async function R(e,t,n={}){let o=n.content;if(o===void 0){let s=Bun.file(t);if(!await s.exists())return null;o=await s.text()}let r=Tt(e,t,n.suffix);return await ht(Ae(r),{recursive:!0}),Ct(r,o)}var St=/^[A-Za-z0-9_-]+$/,kt=/^[A-Za-z0-9_.-]+$/;function At(e){if(!St.test(e))throw new a(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function H(e){if(!kt.test(e)||e.startsWith("_")||e.startsWith("."))throw new a(`invalid project id "${e}"`);return e}function xe(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 a(`invalid --db "${e}": use <alias> or <project>/<alias>`)}function O(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class ee{paths;constructor(e){this.paths=e}async load(e){let t=this.paths.connectionsFile(e),n=Bun.file(t);if(!await n.exists())return{connections:{}};let o=await n.text();if(!o.trim())return{connections:{}};let r;try{r=JSON.parse(o)}catch(i){throw Error(`${t} is not valid JSON (${i.message}); fix it by hand before writing`)}if(!r||typeof r!=="object"||Array.isArray(r))throw Error(`${t} must be an object { default, connections }`);let s=r;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 R(this.paths,n),await Bun.write(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let r=await this.load(e),s=O(r,t)??At(t);if(r.connections[s]=n,o||!r.default||!O(r,r.default))r.default=s;return await this.write(e,r),s}async setDefault(e,t){let n=await this.load(e),o=O(n,t);if(!o)throw new a(this.menu(e,n,`no connection "${t}" in project "${e}"`));return n.default=o,await this.write(e,n),o}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?xe(e.db):void 0,n=t?.project??e.project??Bun.env.FUI_PROJECT;if(n)return H(n);let o=await this.listProjects();if(t){let s=[];for(let i of o)if(O(await this.load(i),t.alias))s.push(i);if(s.length===1)return s[0];if(s.length===0)throw new a(`no project has connection "${t.alias}"; run: fui db list`);throw new a(`connection "${t.alias}" exists in several projects; use one of:
3
+ import{parseArgs as wu}from"util";import{chmod as ci}from"fs/promises";import{homedir as li}from"os";import{join as me,resolve as ui}from"path";var ne={Ok:0,Runtime:1,Usage:2,Guard:3};class m extends Error{name="UsageError"}class q extends Error{severity;logKind;name="GuardError";constructor(e,t="hard",n="guard"){super(e);this.severity=t;this.logKind=n}}function mo(e){if(e instanceof m)return ne.Usage;if(e instanceof q)return ne.Guard;return ne.Runtime}var ut=["workdir","api-base","api-token"];function Be(e){return e.HOME||li()}function Ie(e,t=Bun.env,n=process.cwd()){let o=e.trim();if(o==="~")return Be(t);if(o.startsWith("~/"))return me(Be(t),o.slice(2));return ui(n,o)}function ke(e=Bun.env){let t=e.XDG_CONFIG_HOME||me(Be(e),".config");return me(t,"fui","config.json")}async function He(e=Bun.env){let t=ke(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 m(`${t} is not a JSON object; fix or delete it`)}async function Gt(e,t=Bun.env){let n=ke(t);return await Bun.write(n,`${JSON.stringify(e,null,2)}
4
+ `),await ci(n,384),n}function di(e){return e.CLAUDE_CONFIG_DIR?me(e.CLAUDE_CONFIG_DIR,".claude.json"):me(Be(e),".claude.json")}function mi(e,t){return[t.command,...Array.isArray(t.args)?t.args:[]].join(" ").includes("fui-mcp")||e.toLowerCase().includes("fui")}function po(e,t){if(!e||typeof e!=="object")return;for(let[n,o]of Object.entries(e)){let r=o?.env?.[t];if(typeof r==="string"&&r.trim()&&mi(n,o))return r}return}async function ho(e,t=Bun.env,n=process.cwd()){let o=di(t),r=Bun.file(o);if(!await r.exists())return null;let s;try{s=await r.json()}catch{return null}let i=po(s.projects?.[n]?.mcpServers,e)??po(s.mcpServers,e);return i?{value:i,file:o}:null}async function pi(e=Bun.env,t=process.cwd()){let n=await ho("FUI_MCP_WORKDIR",e,t);return n?{path:Ie(n.value,e,t),file:n.file}:null}var fi=me(".fui-mcp","workspaces");async function go(e,t=Bun.env,n=process.cwd()){if(e)return{path:Ie(e,t,n),source:"flag",detail:"--workdir"};if(t.FUI_MCP_WORKDIR)return{path:Ie(t.FUI_MCP_WORKDIR,t,n),source:"env",detail:"FUI_MCP_WORKDIR"};let o=await He(t);if(typeof o.workdir==="string"&&o.workdir)return{path:Ie(o.workdir,t,n),source:"config",detail:ke(t)};let r=await pi(t,n);if(r)return{path:r.path,source:"mcp",detail:r.file};return{path:me(Be(t),fi),source:"default",detail:"built-in"}}async function fo(e,t,n,o){let r=n[e];if(r)return{value:r,source:"env",detail:e};let i=(await He(n))[t];if(typeof i==="string"&&i)return{value:i,source:"config",detail:ke(n)};let a=await ho(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 dt(e=Bun.env,t=process.cwd()){let[n,o]=await Promise.all([fo("FUI_API_BASE","api-base",e,t),fo("FUI_API_TOKEN","api-token",e,t)]);return{base:n,token:o}}function wo(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(wo(e))process.exit(0);throw e});function w(e){try{process.stdout.write(e)}catch(t){if(wo(t))process.exit(0);throw t}}function b(e){process.stderr.write(e.endsWith(`
5
+ `)?e:`${e}
6
+ `)}async function mt(){return Bun.stdin.text()}async function qe(e,t){if(t&&e)throw new m("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 m(`file not found: ${t}`);n=await o.text()}else if(e==="-")n=await mt();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 se(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 r=Bun.env[t];if(!r)throw new m(`env var ${t} is empty or unset`);return r.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 mt()).trim();if(!o)throw new m(`${n}: stdin is empty`);return o}var yo=60000,bo=400;function jo(e){return e.length<=bo?e:`${e.slice(0,bo)}\u2026`}async function hi(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 r=n?.[o];if(typeof r==="string"&&r.trim())return` \u2014 ${jo(r.trim())}`}}catch{}return` \u2014 ${jo(t)}`}function oe(e){return e.data||[]}class Io{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(yo)})}catch(o){let r=o?.name;if(r==="TimeoutError"||r==="AbortError")throw Error(`FUI API timeout: ${e} did not answer within ${yo/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 hi(n)}`);return await n.json()}async listProjects(){return oe(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 oe(await this.request("/fp/FUI_Dev_UserRightList",{PID:e,MID:""}))}async searchUsers(e,t){return oe(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 oe(await this.request("/fp/FUI_Runtime_ModuleVersionSelect",{domain:e,url1_ID:t,url2_ID:n}))}async listModules(e){return oe(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)b(`# 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 oe(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 oe(await this.request("/fp/FUI_Dev_File/select",{ProjectID:e,ModuleID:t}))}async listFrameworkImportFiles(e){return oe(await this.request("/fp/FUI_Dev_File/select",{frameWork:e}))}async listImportFiles(e,t){return oe(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 ko(){let{base:e,token:t}=await dt();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 Io(e.value,t.value)}import{mkdir as wi,rename as yi}from"fs/promises";import{homedir as bi}from"os";import{join as zt}from"path";var gi=new TextDecoder("utf-8",{ignoreBOM:!0});async function T(e){return gi.decode(await Bun.file(e).bytes())}async function G(e){try{return await T(e)}catch{return null}}function Je(e){return e?.code}var ji=/^[A-Za-z0-9_-]+$/;function Ii(e=Bun.env){return e.FUI_MCP_SESSION_DIR||zt(e.HOME||bi(),".fui-mcp","session")}function ki(e=Bun.env){let t=e.FUI_SESSION||"default";if(!ji.test(t))throw new m(`invalid FUI_SESSION "${t}": letters, digits, "_" or "-"`);return zt(Ii(e),`cli-${t}.json`)}class Yt{file;constructor(e=ki()){this.file=e}async read(){let e=await G(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 wi(zt(this.file,".."),{recursive:!0});let t=`${this.file}.${process.pid}.tmp`;await Bun.write(t,JSON.stringify(e,null,2)),await yi(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 $e(e=Bun.env){return["1","true","yes"].includes((e.FUI_MCP_STRICT??"").trim().toLowerCase())}import{readdir as $i}from"fs/promises";import{isAbsolute as vi,join as Zt,relative as Si,sep as Pi}from"path";var Ci=/^[A-Za-z0-9._ -]+$/,Xt=20;function Qt(e,t){if(!e||!Ci.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 _i(e,t){let n=Si(e,t);if(!n||n.startsWith("..")||vi(n))return null;let[o,r,s]=n.split(Pi);if(!o||o.startsWith("_")||o.startsWith("."))return null;if(r==="modules"&&s)return{projectId:o,moduleId:s,moduleDir:Zt(e,o,"modules",s)};return{projectId:o}}async function $o(e,t){try{return(await $i(e,{withFileTypes:!0})).filter((o)=>o.isDirectory()&&!(t&&(o.name.startsWith("_")||o.name.startsWith(".")))).map((o)=>o.name).sort()}catch{return[]}}function vo(e){return e.length>Xt?[...e.slice(0,Xt),` ... and ${e.length-Xt} more`]:e}class en{paths;session;cwd;constructor(e,t,n=process.cwd()){this.paths=e;this.session=t;this.cwd=n}cwdTarget(){return _i(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:Qt(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 Qt(e,"projectId"),Qt(t,"moduleId"),{projectId:e,moduleId:t,moduleDir:Zt(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 $o(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 $o(Zt(this.paths.root,t[0],"modules"),!1);if(o.length>0)return[n,...vo(o.map((r)=>` fui use ${t[0]}/${r}`))].join(`
8
+ `)}return[n,...vo(t.map((o)=>` -p ${o}`))].join(`
9
+ `)}}function tn(e){return new TextEncoder().encode(e).toBase64()}function nn(e){try{return new TextDecoder("utf-8",{fatal:!0}).decode(Uint8Array.fromBase64(e.trim()))}catch{throw new m("token is not valid Base64 UTF-8")}}function So(e,t,n,o){return tn(`Server=${e}; Database=${t}; UID=${n}; PWD=${o}; Encrypt=True; TrustServerCertificate=True;`)}function Po(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 Ri(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=Po(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 Ve(e){let t=nn(e);return{engine:Ri(t),plain:t}}function pt(e){let{engine:t,plain:n}=Ve(e);if(t==="mssql"){let r=Po(n);return{engine:t,server:r.get("server")??r.get("data source")??"",database:r.get("database")??r.get("initial catalog")??"",user:r.get("uid")??r.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 Oi}from"path";import{mkdir as Ei,readdir as Ti,writeFile as Ni}from"fs/promises";import{basename as Co,dirname as Ro,extname as Eo,isAbsolute as Ai,join as on,relative as To,sep as Di}from"path";var _o=50;function ft(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 xi(e,t,n={}){let o=To(e.root,t),s=o!==""&&!o.startsWith("..")&&!Ai(o)?o:on("_external",Co(t)),i=Eo(s),a=Co(s,i),c=n.stamp??ft(),l=n.suffix?`_${n.suffix}`:"";return on(e.historyRoot(),Ro(s),`${a}_${c}${l}${i}`)}async function Mi(e,t){let n=Eo(e),o=e.slice(0,e.length-n.length);for(let s=1;s<=_o;s++){let i=s===1?e:`${o}_${s}${n}`;try{return await Ni(i,t,{encoding:"utf-8",flag:"wx"}),i}catch(a){if(Je(a)!=="EEXIST")throw a}}let r=`${o}_${_o}${n}`;return await Bun.write(r,t),r}async function B(e,t,n={}){let o=n.content;if(o===void 0)try{o=await T(t)}catch(s){let i=Je(s);if(i==="ENOENT"||i==="ENOTDIR")return null;throw s}let r=xi(e,t,n);return await Ei(Ro(r),{recursive:!0}),Mi(r,o)}function Ke(e,t){let n=To(e.root,t);return(n.startsWith("..")?t:n).split(Di).join("/")}async function ht(e,t,n={}){let o=n.stamp??ft(),r=new Set(n.skipDirNames??["_preview"]),s=[];async function i(a){let c;try{c=await Ti(a,{withFileTypes:!0})}catch{return}for(let l of c){let d=on(a,l.name);if(l.isDirectory()){if(!r.has(l.name))await i(d)}else if(l.isFile()){let u=await B(e,d,{suffix:n.suffix,stamp:o});if(u)s.push(u)}}}return await i(t),s}var Fi=/^[A-Za-z0-9_-]+$/,Li=/^[A-Za-z0-9_.-]+$/;function rn(e){if(!Fi.test(e))throw new m(`invalid alias "${e}": use letters, digits, "_" or "-"`);return e}function sn(e){if(!Li.test(e)||e.startsWith("_")||e.startsWith("."))throw new m(`invalid project id "${e}"`);return e}function No(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 Q(e,t){let n=t.toLowerCase();return Object.keys(e.connections).find((o)=>o.toLowerCase()===n)??null}class an{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 T(t);if(!o.trim())return{connections:{}};let r;try{r=JSON.parse(o)}catch(i){throw Error(`${t} is not valid JSON (${i.message}); fix it by hand before writing`)}if(!r||typeof r!=="object"||Array.isArray(r))throw Error(`${t} must be an object { default, connections }`);let s=r;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 B(this.paths,n),await Bun.write(n,JSON.stringify(t,null,2))}async save(e,t,n,o){let r=await this.load(e),s=Q(r,t)??rn(t);if(r.connections[s]=n,o||!r.default||!Q(r,r.default))r.default=s;return await this.write(e,r),s}async setDefault(e,t){let n=await this.load(e),o=Q(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 G(Oi(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?No(e.db):void 0,n=t?.project??e.project??Bun.env.FUI_PROJECT??e.contextProject;if(n)return sn(n);let o=await this.listProjects();if(t){let s=[];for(let i of o)if(Q(await this.load(i),t.alias))s.push(i);if(s.length===1)return s[0];if(s.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:
5
10
  `+s.map((i)=>` --db ${i}/${t.alias}`).join(`
6
- `))}let r=[];for(let s of o)if(Object.keys((await this.load(s)).connections).length>0)r.push(s);if(r.length===1)return r[0];if(r.length===0)throw new a("no DB connections in workspace; run: fui db add <alias> -p <project> --token -");throw new a(`several projects have connections; pass -p <project> or --db <project>/<alias>:
11
+ `))}let r=[];for(let s of o)if(Object.keys((await this.load(s)).connections).length>0)r.push(s);if(r.length===1)return r[0];if(r.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>:
7
12
  `+r.map((s)=>` -p ${s}`).join(`
8
- `))}async resolve(e){let t=await this.resolveProject(e),n=await this.load(t);if(Object.keys(n.connections).length===0)throw new a(`project "${t}" has no DB connections; run: fui db add <alias> -p ${t}`);let r=await this.pickAlias(t,n,e),s=n.connections[r];if(!s.dbToken)throw new a(`connection ${t}/${r} has no dbToken; re-add it`);let{engine:i}=v(s.dbToken);if(i==="mssql"&&!s.apiDomain)throw new a(`connection ${t}/${r} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:r,config:s,engine:i,dbToken:s.dbToken}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:s}=xe(n.db),i=O(t,s);if(!i)throw new a(this.menu(e,t,`no connection "${s}" in project "${e}"`));return i}if(o.length===1)return o[0];if(n.objectName){let s=n.objectName.toLowerCase(),i=[];for(let c of o)if((await this.knownObjectNames(e,c)).has(s))i.push(c);if(i.length===1)return i[0];throw new a(this.menu(e,t,i.length===0?`cannot infer connection for "${n.objectName}"`:`"${n.objectName}" exists in several connections`))}if(n.write)throw new a(this.menu(e,t,`project "${e}" has several connections; writes need --db`));let r=t.default?O(t,t.default):null;if(r)return r;throw new a(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 r=await o.json();for(let s of[r.tables,r.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof s==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(s):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let r of await this.localSqlNames(e,t))n.add(r.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((r)=>` --db ${e}/${r}${t.default===r?" (default)":""}`);return[n,...o].join(`
9
- `)}}import{join as x}from"path";class te{root;constructor(e){this.root=e}projectDir(e){return x(this.root,e)}dbDir(e){return x(this.projectDir(e),"_db")}connectionsFile(e){return x(this.dbDir(e),"_connections.json")}connDir(e,t){return x(this.dbDir(e),t)}schemaFile(e,t){return x(this.connDir(e,t),"schema.json")}spFile(e,t,n){return x(this.connDir(e,t),`${n}.sql`)}historyRoot(){return x(this.root,"_history")}}var Ne={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},help:{type:"boolean",short:"h"}};async function Pe(e,t){let n=await be(u(e,"workdir")),o=new te(n.path);return{paths:o,workdir:n,store:new ee(o),values:e,positionals:t,json:e.json===!0}}function u(e,t){let n=e[t];return typeof n==="string"?n:void 0}function _e(e){return e?.code==="EPIPE"}process.stdout.on("error",(e)=>{if(_e(e))process.exit(0);throw e});function f(e){try{process.stdout.write(e)}catch(t){if(_e(t))process.exit(0);throw t}}function h(e){process.stderr.write(e.endsWith(`
10
- `)?e:`${e}
11
- `)}async function Oe(){return Bun.stdin.text()}async function ne(e,t){if(t&&e)throw new a("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 a(`file not found: ${t}`);n=await o.text()}else if(e==="-")n=await Oe();else if(e!==void 0)n=e;else throw new a('missing SQL: pass "<sql>", -f <file>, or - for stdin');if(!n.trim())throw new a("SQL is empty");return n}async function I(e,t,n){if(e!==void 0&&t!==void 0)throw new a(`pass ${n} or ${n}-env, not both`);if(t!==void 0){let r=Bun.env[t];if(!r)throw new a(`env var ${t} is empty or unset`);return r.trim()}if(e===void 0)return;if(e!=="-")throw new a(`${n} takes "-" (stdin) only; use ${n}-env VAR for env vars`);let o=(await Oe()).trim();if(!o)throw new a(`${n}: stdin is empty`);return o}import{mkdir as Pt}from"fs/promises";function Rt(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 $e(e){let t=[...new Set(e.flatMap((n)=>Object.keys(n)))];return[t,...e.map((n)=>t.map((o)=>Rt(n[o])))]}function xt(e){if(e.length===0)return"";return`${$e(e).map((t)=>t.join("\t")).join(`
12
- `)}
13
- `}function Nt(e){if(e.length===0)return"";let t=$e(e),n=t[0].map((r,s)=>Math.max(...t.map((i)=>Bun.stringWidth(i[s]))));return`${t.map((r)=>r.map((s,i)=>i===r.length-1?s:s+" ".repeat(n[i]-Bun.stringWidth(s))).join(" ")).join(`
14
- `)}
15
- `}function re(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
16
- `}function T(e,t,n=process.stdout.isTTY===!0){if(t)return re(e);return n?Nt(e):xt(e)}function Ie(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}function Le(e){let t=e[0];if(!t)throw new a(`missing <key>; keys: ${q.join(", ")}`);if(!q.includes(t))throw new a(`unknown key "${t}"; keys: ${q.join(", ")}`);return t}var _t={name:"config",usage:"fui config",summary:"Show settings with their effective value and source",async run({workdir:e,json:t}){h(`# config file: ${j()}`),f(T([{key:"workdir",value:e.path,source:e.source,from:e.detail}],t))}},Ot={name:"config set",usage:"fui config set workdir <path>",summary:"Save a setting to the fui config file",async run({positionals:e}){let t=Le(e),n=e[1];if(!n)throw new a(`missing <value> for ${t}`);let o=_(n);await Pt(o,{recursive:!0});let r=await B();r[t]=o;let s=await X(r);if(Bun.env.FUI_MCP_WORKDIR)h("# note: FUI_MCP_WORKDIR is set in this shell and overrides the config file");f(`set ${t}=${o} in ${s}
17
- `)}},$t={name:"config unset",usage:"fui config unset workdir",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=Le(e),n=await B();delete n[t];let o=await X(n);f(`unset ${t} in ${o}
18
- `)}},je=[_t,Ot,$t];var{SQL:It}=globalThis.Bun;class oe{engine;sql;constructor(e,t){this.engine=e;this.sql=new It(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 se(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function D(e){let t=se(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function Lt(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 ve(e,t,n,o){let r;try{r=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 s=await r.text();if(!r.ok)throw Error(`tAPI HTTP ${r.status}: ${Lt(s)||r.statusText}`);try{return JSON.parse(s)}catch{throw Error(`tAPI returned non-JSON: ${s.slice(0,400)}`)}}class ie{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await ve(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return ve(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function ae(e,t,n,o,r){let s=`${e}/help/${t}/${n?"auth/":""}${o}`,i={};if(!n&&r)i.authorization=`Bearer ${r}`;let c=await fetch(s,{headers:i,signal:AbortSignal.timeout(15000)});return{url:s,status:c.status,ok:c.ok,text:await c.text()}}function jt(e){if(e.engine==="mssql")return new ie(D(e.config.apiDomain),e.dbToken);return new oe(e.engine,Z(e.dbToken))}async function E(e,t){let n=jt(e);try{return await t(n)}finally{await n.close()}}var vt={name:"db list",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,json:n}){let o=u(t,"project"),r=o?[H(o)]:await e.listProjects(),s=[];for(let i of r){let c=await e.load(i);for(let[l,d]of Object.entries(c.connections)){let g="invalid";try{if(d.dbToken)g=v(d.dbToken).engine}catch{}s.push({db:`${i}/${l}`,engine:g,default:c.default===l?"*":"",apiDomain:d.apiDomain??"",database:d.database??"",userToken:d.userToken?"yes":"no"})}}if(s.length===0){h("no connections; add one: fui db add <alias> -p <project> --token -");return}f(T(s,n))}},Dt={name:"db add",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
19
- [--api-domain host] [--user-token-env VAR] [--default] [--no-test]`,summary:"Add or replace a connection; tests it with SELECT 1",options:{token:{type:"string"},"token-env":{type:"string"},conn:{type:"string"},"conn-env":{type:"string"},"api-domain":{type:"string"},"user-token-env":{type:"string"},default:{type:"boolean"},"no-test":{type:"boolean"}},async run({store:e,values:t,positionals:n}){let o=n[0];if(!o)throw new a("missing <alias>");let r=u(t,"project");if(!r)throw new a("missing -p <project>");let s=H(r),i=await I(u(t,"token"),u(t,"token-env"),"--token"),c=await I(u(t,"conn"),u(t,"conn-env"),"--conn");if(i===void 0===(c===void 0))throw new a("pass exactly one of --token/--token-env or --conn/--conn-env");let l=i??z(c),d=W(l),g=(await e.load(s)).connections,p=Object.entries(g).find(([k])=>k.toLowerCase()===o.toLowerCase())?.[1],m=u(t,"api-domain")??p?.apiDomain;if(d.engine==="mssql"&&!m)throw new a("MSSQL connections need --api-domain <host>");let w={...p,dbToken:l};if(m)w.apiDomain=se(m);w.sqlServer=d.server,w.database=d.database;let y=await I(void 0,u(t,"user-token-env"),"--user-token");if(y)w.userToken=y;if(t["no-test"]!==!0){let k=Bun.nanoseconds();await E({projectId:s,alias:o,config:w,engine:d.engine,dbToken:l},(M)=>M.query("SELECT 1 AS ok")),h(`test ok ${Math.round((Bun.nanoseconds()-k)/1e6)}ms`)}let N=await e.save(s,o,w,t.default===!0);f(`saved ${s}/${N} ${d.engine} ${d.database}
20
- `)}},Ft={name:"db default",usage:"fui db default <project>/<alias>",summary:"Set the project default connection",async run({store:e,values:t,positionals:n}){let o=n[0]??u(t,"db");if(!o)throw new a("missing <project>/<alias>");let r=await e.resolveProject({db:o,project:u(t,"project")}),s=o.split("/").pop(),i=await e.setDefault(r,s);f(`default ${r}/${i}
21
- `)}},Ut={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run({store:e,values:t}){let n=await e.resolve({db:u(t,"db"),project:u(t,"project")}),o=Bun.nanoseconds();await E(n,(s)=>s.query("SELECT 1 AS ok"));let r=Math.round((Bun.nanoseconds()-o)/1e6);f(`ok ${n.projectId}/${n.alias} ${n.engine} ${r}ms
22
- `)}},De=[vt,Dt,Ft,Ut];function S(e,t={}){let n=t.strings!==!1,o=e.split(""),r=0;while(r<e.length)if(e[r]==="-"&&e[r+1]==="-")while(r<e.length&&e[r]!==`
23
- `)o[r++]=" ";else if(e[r]==="/"&&e[r+1]==="*"){let s=e.indexOf("*/",r+2),i=s===-1?e.length:s+2;while(r<i)o[r++]=" "}else if(e[r]==="'"){if(n)o[r]=" ";r++;while(r<e.length){if(e[r]==="'"){if(n)o[r]=" ";if(r++,e[r]==="'"){if(n)o[r]=" ";r++;continue}break}if(n)o[r]=" ";r++}}else r++;return o.join("")}function ce(e){return S(e,{strings:!1})}function ue(e){let t=S(e),n=[],o=0;for(let r of t.matchAll(/;|^[ \t]*GO[ \t]*$/gim))n.push({start:o,end:r.index}),o=r.index+r[0].length;return n.push({start:o,end:e.length}),n.filter((r)=>r.end>r.start)}function le(e,t=","){let n=[],o=0,r="";for(let s of e){if(s==="(")o++;else if(s===")")o--;if(s===t&&o===0)n.push(r),r="";else r+=s}if(r.trim())n.push(r);return n}var Mt=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,qt=new Set(["ADD","ALTER","DROP"]);function Bt(e){let t=ce(e),n=null;for(let o of ue(e)){let r=t.slice(o.start,o.end),s=e.slice(o.start,o.end).trim();if(/\bDROP\s+TABLE\b/i.test(r))return{severity:"block",reason:"DROP TABLE",statement:s};if(!/\bALTER\s+TABLE\b/i.test(r))continue;if(/\bDROP\s+COLUMN\b/i.test(r))return{severity:"block",reason:"ALTER TABLE ... DROP COLUMN",statement:s};if(/\bSWITCH\b/i.test(r))return{severity:"block",reason:"ALTER TABLE ... SWITCH",statement:s};let i=Mt.exec(r)?.[1]?.toUpperCase();if(!i||!qt.has(i))return{severity:"block",reason:`ALTER TABLE ... ${i??"(unknown action)"}`,statement:s};if(!n&&/\bALTER\s+COLUMN\b/i.test(r))n={severity:"confirm",reason:"ALTER TABLE ... ALTER COLUMN (narrowing numeric/date precision loses data silently)",statement:s}}return n}function Fe(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 Wt(e){let t=ce(e);for(let{start:n,end:o}of ue(e)){let r=t.slice(n,o);if(/\bWHERE\b/i.test(r))continue;let s=e.slice(n,o).trim();if(/\bDELETE\b/i.test(r)&&!Fe(r,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:s};if(/\bUPDATE\b/i.test(r)&&/\bSET\b/i.test(r)&&!Fe(r,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:s}}return null}function de(e){let t=S(e),n=[];if(/\bINSERT\b/i.test(t))n.push("INSERT");if(/\bUPDATE\b/i.test(t)&&/\bSET\b/i.test(t))n.push("UPDATE");if(/\bDELETE\b/i.test(t))n.push("DELETE");if(/\bMERGE\b/i.test(t))n.push("MERGE");if(/\bTRUNCATE\b/i.test(t))n.push("TRUNCATE");return n}function pe(e,t){let n=Bt(e);if(n?.severity==="block")return n;let o=Wt(e);if(o)return o;if(n&&!t)return n;let r=de(e);if(r.length>0&&!t)return{severity:"confirm",reason:`writes real data (${r.join(", ")})`,statement:e.trim()};return null}var Ht=200,Ue=400;function Me(e){let t=e.statement.length>Ue?`${e.statement.slice(0,Ue)}...`: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 P(`${e.reason}
24
- ${n}
25
- ---
26
- ${t}`)}function Gt(e){if(e===void 0)return Ht;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new a("--limit must be an integer >= 0 (0 = no limit)");return t}var Kt={name:"query",usage:'fui query ("<sql>" | -f file | -) [--db ref] [--limit 200] [--confirm-write] [--json]',summary:"Run SQL that returns rows; prints TSV",options:{file:{type:"string",short:"f"},limit:{type:"string"},"confirm-write":{type:"boolean"}},async run({store:e,values:t,positionals:n,json:o}){let r=await ne(n[0],u(t,"file")),s=Gt(u(t,"limit")),i=t["confirm-write"]===!0,c=pe(r,i);if(c)throw Me(c);let l=await e.resolve({db:u(t,"db"),project:u(t,"project"),write:de(r).length>0}),d=await E(l,(p)=>p.query(r)),g=Ie(d,s);f(T(g.rows,o)),h(g.truncated?`# rows: ${g.rows.length} of ${g.total} (truncated; use --limit or TOP/WHERE)`:`# rows: ${g.total}`)}},Jt={name:"exec",usage:'fui exec ("<sql>" | -f file | -) [--db ref] [--confirm-write]',summary:"Run DDL/DML that returns no rows",options:{file:{type:"string",short:"f"},"confirm-write":{type:"boolean"}},async run({store:e,values:t,positionals:n,json:o}){let r=await ne(n[0],u(t,"file")),s=pe(r,t["confirm-write"]===!0);if(s)throw Me(s);let i=await e.resolve({db:u(t,"db"),project:u(t,"project"),write:!0}),c=await E(i,(d)=>d.exec(r)),l=`${i.projectId}/${i.alias}`;f(o?re({db:l,result:c}):`ok ${l} ${JSON.stringify(c)}
27
- `)}},qe=[Kt,Jt];var Be=`
13
+ `))}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:r,note:s}=await this.pickAlias(t,n,e),i=n.connections[r];if(!i.dbToken)throw new m(`connection ${t}/${r} has no dbToken; re-add it`);let{engine:a}=Ve(i.dbToken);if(a==="mssql"&&!i.apiDomain)throw new m(`connection ${t}/${r} is MSSQL and needs apiDomain; re-add with --api-domain`);return{projectId:t,alias:r,config:i,engine:a,dbToken:i.dbToken,note:s}}async pickAlias(e,t,n){let o=Object.keys(t.connections);if(n.db){let{alias:s}=No(n.db),i=Q(t,s);if(!i)throw new m(this.menu(e,t,`no connection "${s}" in project "${e}"`));return{alias:i,note:null}}if(o.length===1)return{alias:o[0],note:null};if(n.objectName){let s=Array.isArray(n.objectName)?n.objectName:[n.objectName],i=s[0],a=s.map((l)=>l.toLowerCase()),c=[];for(let l of o){let d=await this.knownObjectNames(e,l);if(a.some((u)=>d.has(u)))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 r=t.default?Q(t,t.default):null;if(r)return{alias:r,note:`using default connection "${r}" (${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=Bun.file(this.paths.schemaFile(e,t));if(await o.exists())try{let r=await o.json();for(let s of[r.tables,r.procedures].flatMap((i)=>Array.isArray(i)?i:[])){let i=typeof s==="string"?/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(s):null;if(i)n.add(i[1].toLowerCase())}}catch{}for(let r of await this.localSqlNames(e,t))n.add(r.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((r)=>` --db ${e}/${r}${t.default===r?" (default)":""}`);return[n,...o].join(`
14
+ `)}}import{join as pe}from"path";class cn{root;constructor(e){this.root=e}projectDir(e){return pe(this.root,e)}dbDir(e){return pe(this.projectDir(e),"_db")}connectionsFile(e){return pe(this.dbDir(e),"_connections.json")}connDir(e,t){return pe(this.dbDir(e),t)}schemaFile(e,t){return pe(this.connDir(e,t),"schema.json")}spFile(e,t,n){return pe(this.connDir(e,t),`${n}.sql`)}historyRoot(){return pe(this.root,"_history")}}var Ao={project:{type:"string",short:"p"},db:{type:"string"},workdir:{type:"string"},json:{type:"boolean"},"no-header":{type:"boolean"},help:{type:"boolean",short:"h"}};async function Do(e,t){let n=await go(p(e,"workdir")),o=new cn(n.path),r=e.json===!0,s={json:r,header:e["no-header"]!==!0},i=new Yt,a=new en(o,i);return{paths:o,session:i,targets:a,workdir:n,store:new an(o),values:e,positionals:t,json:r,render:s}}function p(e,t){let n=e[t];return typeof n==="string"?n:void 0}async function W(e){return{store:e.store,session:e.session,project:p(e.values,"project"),contextProject:await e.targets.contextProjectId()}}function S(e){let t;return{paths:e.paths,session:e.session,targets:e.targets,api:()=>t??=ko()}}import{mkdir as Hi}from"fs/promises";function Ui(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 xo(e,t){let n=[...new Set(e.flatMap((r)=>Object.keys(r)))],o=e.map((r)=>n.map((s)=>Ui(r[s])));return t?[n,...o]:o}function Wi(e,t=!0){if(e.length===0)return"";return`${xo(e,t).map((n)=>n.join("\t")).join(`
15
+ `)}
16
+ `}function Bi(e,t=!0){if(e.length===0)return"";let n=xo(e,t),o=n[0].map((s,i)=>Math.max(...n.map((a)=>Bun.stringWidth(a[i]))));return`${n.map((s)=>s.map((i,a)=>a===s.length-1?i:i+" ".repeat(o[a]-Bun.stringWidth(i))).join(" ")).join(`
17
+ `)}
18
+ `}function ln(e){return`${JSON.stringify(e,(t,n)=>typeof n==="bigint"?n.toString():n)}
19
+ `}function D(e,{json:t,header:n,isTty:o=process.stdout.isTTY===!0}){if(t)return ln(e);return o?Bi(e,n):Wi(e,n)}function Mo(e,t){let n=t>0&&e.length>t;return{rows:n?e.slice(0,t):e,total:e.length,truncated:n}}function Oo(e){let t=e[0];if(!t)throw new m(`missing <key>; keys: ${ut.join(", ")}`);if(!ut.includes(t))throw new m(`unknown key "${t}"; keys: ${ut.join(", ")}`);return t}var qi={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 dt();b(`# config file: ${ke()}`),w(D([{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}],t))}},Ji={name:"config set",usage:"fui config set workdir <path> | api-base <url> | api-token -",summary:"Save a setting to the fui config file (api-token only from stdin)",async run({positionals:e}){let t=Oo(e),n=e[1];if(!n)throw new m(`missing <value> for ${t}`);let o;if(t==="workdir")o=Ie(n),await Hi(o,{recursive:!0});else if(t==="api-token")o=await se(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let r=await He();r[t]=o;let s=await Gt(r),i={workdir:"FUI_MCP_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN"}[t];if(Bun.env[i])b(`# note: ${i} is set in this shell and overrides the config file`);w(`set ${t}${t==="api-token"?"":`=${o}`} in ${s}
20
+ `)}},Vi={name:"config unset",usage:"fui config unset <workdir|api-base|api-token>",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=Oo(e),n=await He();delete n[t];let o=await Gt(n);w(`unset ${t} in ${o}
21
+ `)}},Fo=[qi,Ji,Vi];var{SQL:Ki}=globalThis.Bun;class un{engine;sql;constructor(e,t){this.engine=e;this.sql=new Ki(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 dn(e){return e.trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"").toLowerCase()}function Ge(e){let t=dn(e);return`${/^(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(t)?"http":"https"}://${t}`}function Gi(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 Lo(e,t,n,o){let r;try{r=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 s=await r.text();if(!r.ok)throw Error(`tAPI HTTP ${r.status}: ${Gi(s)||r.statusText}`);try{return JSON.parse(s)}catch{throw Error(`tAPI returned non-JSON: ${s.slice(0,400)}`)}}class mn{origin;dbToken;engine="mssql";constructor(e,t){this.origin=e;this.dbToken=t}async query(e){return(await Lo(this.origin,"/help/_sql_execute",this.dbToken,e)).data??[]}exec(e){return Lo(this.origin,"/help/_sql_execute_NonQuery",this.dbToken,e)}async close(){}}async function pn(e,t,n,o,r){let s=`${e}/help/${t}/${n?"auth/":""}${o}`,i={};if(!n&&r)i.authorization=`Bearer ${r}`;let a=await fetch(s,{headers:i,signal:AbortSignal.timeout(15000)});return{url:s,status:a.status,ok:a.ok,text:await a.text()}}function fn(e){if(e.engine==="mssql")return new mn(Ge(e.config.apiDomain),e.dbToken);return new un(e.engine,nn(e.dbToken))}async function Z(e,t){let n=fn(e);try{return await t(n)}finally{await n.close()}}import{mkdir as Na}from"fs/promises";import{dirname as Aa}from"path";import{mkdir as ga,readdir as wa,unlink as ya}from"fs/promises";import{dirname as ba,join as ja}from"path";var Uo=`
28
22
  SELECT
29
23
  o.type AS ObjectTypeCode, o.name AS ObjectName, c.name AS ColumnName, t.name AS DataType,
30
24
  c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity,
@@ -48,7 +42,7 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('U','V') AND s.name NOT IN ('sys','INFO
48
42
  AND o.name NOT LIKE 'sys%' AND o.name NOT LIKE 'dt[_]%'
49
43
  AND o.name NOT LIKE 'queue[_]%' AND o.name NOT LIKE 'filestream[_]%' AND o.name NOT LIKE 'cdc.%'
50
44
  ORDER BY ObjectTypeCode, ObjectName
51
- `.trim(),We=`
45
+ `.trim(),Wo=`
52
46
  SELECT
53
47
  o.type AS ObjectTypeCode, o.name AS ObjectName,
54
48
  p.name AS ParameterName, pt.name AS ParameterType,
@@ -63,31 +57,154 @@ WHERE o.is_ms_shipped = 0 AND o.type IN ('P','FN','TF','IF') AND s.name NOT IN (
63
57
  AND o.name NOT IN ('fn_diagramobjects','sp_alterdiagram','sp_creatediagram','sp_dropdiagram',
64
58
  'sp_helpdiagramdefinition','sp_helpdiagrams','sp_renamediagram','sp_upgraddiagrams')
65
59
  ORDER BY ObjectTypeCode, ObjectName
66
- `.trim(),Qt=new Set(["nvarchar","nchar","ntext"]);function He(e,t,n,o){let r=e.toLowerCase();if(t===-1)return`${e}(max)`;if(r==="decimal"||r==="numeric")return o!==void 0?`${e}(${n},${o})`:`${e}(${n})`;let s=Qt.has(r)?t/2:t;return s>0?`${e}(${s})`:e}function Vt(e){let t=[String(e.ColumnName),He(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 Yt(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function Ge(e,t){let n=new Map;for(let r of e){let s=String(r.ObjectName??"");if(!s)continue;let i=n.get(s);if(!i)i={keyword:String(r.ObjectTypeCode??"").trim()==="V"?"VIEW":"TABLE",columns:[]},n.set(s,i);if(r.ColumnName)i.columns.push(Vt(r))}let o=new Map;for(let r of t){let s=String(r.ObjectName??""),i=String(r.ObjectTypeCode??"").trim();if(!s||!["P","FN","TF","IF"].includes(i))continue;let c=o.get(s);if(!c)c={keyword:i==="P"?"PROCEDURE":"FUNCTION",params:[]},o.set(s,c);if(r.ParameterName){let l=He(String(r.ParameterType??""),Number(r.ParameterMaxLength??0),Number(r.ParameterPrecision??0));c.params.push(`@${String(r.ParameterName).replace(/^@/,"")} ${l}`)}}return{fetchedAt:new Date().toISOString(),tables:[...n].map(([r,s])=>`CREATE ${s.keyword} ${r} (${s.columns.join(",")});`),procedures:[...o].map(([r,s])=>`CREATE ${s.keyword} ${r} (${s.params.join(",")});`)}}function Ke(e){if(!e||typeof e!=="object")return!1;let t=e,n=(o)=>Array.isArray(o)&&o.every((r)=>typeof r==="string");return n(t.tables)&&n(t.procedures)}function G(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}function K(e){let t=/^CREATE\s+(TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e);if(!t)return"Unknown";let n=t[1].toUpperCase();if(n==="TABLE")return"Table";if(n==="VIEW")return"View";return Yt(t[2],n==="FUNCTION")}function me(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 Je(e){let t=S(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 Qe(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(S(e))}function Ve(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...S(e).matchAll(/\b(CREATE|ALTER)(\s+)(?:PROCEDURE|PROC|FUNCTION)\b/gi)];if(o.length!==1)return n;let r=o[0],s=r[1].toUpperCase(),i=t?"ALTER":"CREATE";if(s===i)return n;return{sql:e.slice(0,r.index)+i+e.slice(r.index+r[1].length),changed:!0,from:s,to:i}}function Ye(e){let t=S(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,r=n[0].endsWith("(")?1:0,s=t.length,i=/\bAS\b/gi;i.lastIndex=o;for(let p=i.exec(t);p;p=i.exec(t)){let m=t.slice(o,p.index);if((m.match(/\(/g)??[]).length+r===(m.match(/\)/g)??[]).length){s=p.index;break}}let c=(p)=>p.replace(/^\(/,"").replace(/\)\s*$/,""),l=le(c(t.slice(o,s))),d=le(c(e.slice(o,s))),g=[];return l.forEach((p,m)=>{if(/^\s*@\w+[\s\S]*=/.test(p))g.push((d[m]??p).trim())}),g}function F(e){return`N'${e.replace(/'/g,"''")}'`}function J(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function Xe(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(${F(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var Xt=/^[A-Za-z0-9_]+$/,ze=50;function U(e,t){if(e.engine!=="mssql")throw new a(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}async function zt(e,t){let n=new Map;for(let o=0;o<t.length;o+=ze){let r=t.slice(o,o+ze),s=await e.query(`SELECT o.name AS name, ISNULL(OBJECT_DEFINITION(o.object_id), '') AS sqlText FROM sys.objects o WHERE o.name IN (${r.map(F).join(",")})`);for(let i of s){let c=String(i.sqlText??"");if(i.name&&c.trim())n.set(String(i.name).toLowerCase(),c)}}return n}async function Ze(e,t){U(t,"schema pull");let{projectId:n,alias:o}=t;return E(t,async(r)=>{let[s,i]=await Promise.all([r.query(Be),r.query(We)]),c=Ge(s,i);await Bun.write(e.paths.schemaFile(n,o),JSON.stringify(c,null,2));let l=(await e.localSqlNames(n,o)).filter((m)=>Xt.test(m)),d=await zt(r,l),g=[],p=[];for(let m of l){let w=d.get(m.toLowerCase());if(w===void 0){p.push(`${m}.sql`);continue}let y=e.paths.spFile(n,o,m);if(await Bun.file(y).text()===w)continue;await R(e.paths,y),await Bun.write(y,w),g.push(`${m}.sql`)}return{schema:c,refreshed:g,localOnly:p}})}async function fe(e,t){let n=Bun.file(e.paths.schemaFile(t.projectId,t.alias));if(await n.exists()){try{let o=await n.json();if(Ke(o))return o}catch{}h("# schema cache unreadable; pulling")}else h("# no schema cache; pulling");return(await Ze(e,t)).schema}function Zt(e){let t=new Map;for(let n of[...e.tables,...e.procedures]){let o=K(n);t.set(o,(t.get(o)??0)+1)}return[...t].map(([n,o])=>`${n}=${o}`).join(" ")}var en={name:"schema",usage:"fui schema [--db ref] [--filter text] [--tables | --procs]",summary:"Print compact DDL (one line per object) from the local cache",options:{filter:{type:"string"},tables:{type:"boolean"},procs:{type:"boolean"}},async run({store:e,values:t}){let n=await e.resolve({db:u(t,"db"),project:u(t,"project")});U(n,"schema");let o=await fe(e,n),r=u(t,"filter")?.toLowerCase(),s=t.tables===!0,c=[...t.procs===!0?[]:o.tables,...s?[]:o.procedures].filter((l)=>!r||(G(l)??"").toLowerCase().includes(r));if(h(`# ${n.projectId}/${n.alias} fetchedAt=${o.fetchedAt} objects=${c.length}`),c.length>0)f(`${c.join(`
60
+ `.trim(),zi=new Set(["nvarchar","nchar","ntext"]);function Bo(e,t,n,o){let r=e.toLowerCase();if(t===-1)return`${e}(max)`;if(r==="decimal"||r==="numeric")return o!==void 0?`${e}(${n},${o})`:`${e}(${n})`;let s=zi.has(r)?t/2:t;return s>0?`${e}(${s})`:e}function Yi(e){let t=[String(e.ColumnName),Bo(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 hn(e,t){if(t)return"Function";if(/^spAPIFILE_/i.test(e))return"API File";if(/^spAPI_/i.test(e))return"API";return"Procedure"}function Ho(e,t){let n=new Map;for(let r of e){let s=String(r.ObjectName??"");if(!s)continue;let i=n.get(s);if(!i)i={keyword:String(r.ObjectTypeCode??"").trim()==="V"?"VIEW":"TABLE",columns:[]},n.set(s,i);if(r.ColumnName)i.columns.push(Yi(r))}let o=new Map;for(let r of t){let s=String(r.ObjectName??""),i=String(r.ObjectTypeCode??"").trim();if(!s||!["P","FN","TF","IF"].includes(i))continue;let a=o.get(s);if(!a)a={keyword:i==="P"?"PROCEDURE":"FUNCTION",params:[]},o.set(s,a);if(r.ParameterName){let c=Bo(String(r.ParameterType??""),Number(r.ParameterMaxLength??0),Number(r.ParameterPrecision??0));a.params.push(`@${String(r.ParameterName).replace(/^@/,"")} ${c}`)}}return{fetchedAt:new Date().toISOString(),tables:[...n].map(([r,s])=>`CREATE ${s.keyword} ${r} (${s.columns.join(",")});`),procedures:[...o].map(([r,s])=>`CREATE ${s.keyword} ${r} (${s.params.join(",")});`)}}function gt(e){if(!e||typeof e!=="object")return!1;let t=e,n=(o)=>Array.isArray(o)&&o.every((r)=>typeof r==="string");return n(t.tables)&&n(t.procedures)}function qo(e){return/^CREATE\s+(?:TABLE|VIEW|PROCEDURE|FUNCTION)\s+(\S+)/i.exec(e)?.[1]??null}var gn=["Table","View","API","API File","Procedure","Function"];function wn(e){if(/^CREATE TABLE/i.test(e))return"Table";if(/^CREATE VIEW/i.test(e))return"View";return null}function yn(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+)/i);return t?hn(t[2],t[1].toUpperCase()==="FUNCTION"):null}var Xi=/^(\S+)\s+(\S+)\s+(NULL|NOT NULL)(\s+PRIMARY KEY)?(\s+IDENTITY)?(?:\s+REFERENCES\s+(\S+)\((\S+)\))?$/,Qi=/^@(\S+)\s+(\S+)$/;function Jo(e){let t=[],n=0,o="";for(let r of e){if(r==="(")n++;else if(r===")")n--;if(r===","&&n===0)t.push(o),o="";else o+=r}if(o.trim())t.push(o);return t}function bn(e){let t=e.match(/^CREATE (TABLE|VIEW) (\S+) \((.*)\);?$/s),n=t?Jo(t[3]):null;if(t&&n.every((r)=>Xi.test(r.trim())))return{name:t[2],objectType:t[1]==="VIEW"?"View":"Table",count:n.length};let o=qo(e);if(!o)throw Error(`Cannot parse table DDL: ${e.slice(0,120)}`);return{name:o,objectType:wn(e)??"Table",count:0,opaque:!0}}function wt(e){let t=e.match(/^CREATE (PROCEDURE|FUNCTION) (\S+) \((.*)\);?$/s),n=t?Jo(t[3]):null;if(t&&n.every((s)=>Qi.test(s.trim())))return{name:t[2],objectType:hn(t[2],t[1]==="FUNCTION"),count:n.length};let o=qo(e);if(!o)throw Error(`Cannot parse proc DDL: ${e.slice(0,120)}`);let r=/^CREATE\s+FUNCTION/i.test(e);return{name:o,objectType:yn(e)??hn(o,r),count:0,opaque:!0}}import{mkdir as sa,stat as ia}from"fs/promises";import{basename as aa,dirname as ca,join as In,resolve as Go,sep as la}from"path";import{mkdir as Zi,readdir as ea}from"fs/promises";import{join as ve,sep as ta}from"path";var Se="_publishState.json";function H(e){return e.split(ta).join("/")}function fe(e){return`sha1:${new Bun.CryptoHasher("sha1").update(e.replace(/\r\n/g,`
61
+ `)).digest("hex")}`}async function yt(e){try{let t=JSON.parse(await T(ve(e,Se)));return t&&typeof t==="object"&&!Array.isArray(t)?t:{}}catch{return{}}}async function Vo(e,t){await Zi(e,{recursive:!0}),await Bun.write(ve(e,Se),JSON.stringify(t,null,2))}async function J(e,t){if(t.length===0)return;let n=await yt(e),o=!1;for(let r of t){let s=H(r);try{n[s]=fe(await T(ve(e,s))),o=!0}catch{if(s in n)delete n[s],o=!0}}if(o)await Vo(e,n)}async function Ko(e,t){if(t.length===0)return;let n=await yt(e),o=!1;for(let r of t){let s=H(r);if(s in n)delete n[s],o=!0}if(o)await Vo(e,n)}var na=["module.json","header.html","body.html","script.js","style.css","project.json"];async function oa(e){try{return(await ea(ve(e,"components"),{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.toLowerCase().endsWith(".vue")).map((n)=>H(ve("components",n.name)))}catch{return[]}}async function ze(e){let t=await yt(e),n=[];for(let o of[...na,...await oa(e)]){let r;try{r=await T(ve(e,o))}catch{continue}let s=t[o];if(s===void 0)n.push({relPath:o,reason:"new"});else if(s!==fe(r))n.push({relPath:o,reason:"changed"})}return n}async function Pe(e,t,n){let o=(await yt(e))[H(t)];return o!==void 0&&fe(n)===o}var ra={"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 bt(e){if(e.startsWith("components/"))return"fui component update";return ra[e]??"fui module publish json"}function jn(e){if(e.length===0)return null;return`unpublished local files: ${e.map((n)=>`${n.relPath} (${bt(n.relPath)})`).join(", ")}`}var ua=172800000,da=new Set(["_moduleInfo.json","_projectInfo.json","_modules.json","_components.json","_imports.json",Se]);function zo(e){return da.has(aa(e))}function ma(e,t){return e.replace(/\r\n/g,`
62
+ `)===t.replace(/\r\n/g,`
63
+ `)}function pa(e,t,n){let o=Go(e),r=Go(t);if(r!==o&&!r.startsWith(o+la))throw Error(`server file path escapes the target folder: ${JSON.stringify(n)} -> ${r} (must be inside ${o}); nothing written`)}async function V(e,t,n){let o=[],r=[],s=[],i=ft(),a=[];for(let c of Object.keys(n)){let l=H(c);pa(t,In(t,l),l)}for(let[c,l]of Object.entries(n)){let d=H(c),u=In(t,d),f=await G(u);if(f!==null&&ma(f,l))r.push(d);else if(f!==null&&!zo(d)){let h=await B(e,u,{stamp:i});if(h)s.push({relPath:d,historyPath:Ke(e,h)})}if(await sa(ca(u),{recursive:!0}),await Bun.write(u,l),o.push(u),!zo(d))a.push(d)}if(a.length>0)await J(t,a).catch(()=>{return});return{written:o,unchanged:r,archived:s}}function kn(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 fa=8;function $n(e){if(e.length===0)return[];let t=e.slice(0,fa).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 ha(e,t){try{return(await ia(In(e,t))).mtime}catch{return null}}async function Ce(e,t,n,o,r=Date.now()){let s=await ha(e,o);if(!s)return null;let i=r-s.getTime();if(i<ua)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 Ia=/^[A-Za-z0-9_]+$/,Yo=50;class jt extends Error{name="SchemaFormatError"}async function ka(e){try{return JSON.parse(await T(e))}catch{return null}}async function vn(e,t,n){return ka(e.schemaFile(t,n))}async function Sn(e,t,n){let o=await vn(e,t,n);if(!o)return null;if(!gt(o))throw new jt(`_db/${n}/schema.json of project "${t}" is not in the current compact format`);try{return{fetchedAt:o.fetchedAt,tables:o.tables.map(bn),procedures:o.procedures.map(wt)}}catch(r){throw new jt(`_db/${n}/schema.json of project "${t}": ${r.message}`)}}async function Xo(e){try{return(await wa(e)).filter((t)=>t.toLowerCase().endsWith(".sql"))}catch{return null}}async function $a(e,t){try{let n=await T(t);return await B(e,t,{content:n,suffix:"deleted"}),await ya(t),!0}catch{return!1}}async function va(e,t,n,o,r,s){let i=[],a=[],c=await Xo(e.connDir(t,n));if(!c)return{removed:i,localOnly:a};let l=(f)=>new Set([...f].map((h)=>h.toLowerCase())),d=l(o),u=l(r);for(let f of c){let h=f.slice(0,-4).toLowerCase();if(u.has(h))continue;if(d.has(h)||s){if(await $a(e,ja(e.connDir(t,n),f)))i.push(f)}else a.push(f)}return{removed:i,localOnly:a}}async function Sa(e,t,n,o){let r=await Xo(e.connDir(n,o));if(!r||r.length===0)return{sync:null,skipped:[],missing:[]};let s=[],i=[];for(let u of r){let f=u.slice(0,-4);if(Ia.test(f))s.push(f);else i.push(u)}let a=new Map;for(let u=0;u<s.length;u+=Yo){let f=s.slice(u,u+Yo).map((j)=>`N'${j}'`).join(","),h=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 (${f})`);for(let j of h){let P=String(j.name??""),_=String(j.sqlText??"");if(P&&_.trim())a.set(P.toLowerCase(),_)}}let c={},l=[],d=new Set(i);for(let u of r){if(d.has(u))continue;let f=a.get(u.slice(0,-4).toLowerCase());if(f===void 0)l.push(u);else c[`${o}/${u}`]=f}if(Object.keys(c).length===0)return{sync:null,skipped:i,missing:l};return{sync:await V(e,e.dbDir(n),c),skipped:i,missing:l}}async function It(e,t,n,o,r=!1){let s=null;try{s=await Sn(e,n,o)}catch{}let i=new Set((s?.procedures??[]).map((j)=>j.name)),[a,c]=await Promise.all([t.query(Uo),t.query(Wo)]),l=Ho(a,c),d=e.schemaFile(n,o);await ga(ba(d),{recursive:!0}),await Bun.write(d,JSON.stringify(l,null,2));let u=new Set(l.procedures.map((j)=>wt(j).name)),f=await va(e,n,o,i,u,r),h=await Sa(e,t,n,o);return{schemaFile:d,schema:l,refresh:h.sync,skipped:h.skipped,missingOnServer:h.missing,removed:f.removed,localOnly:f.localOnly}}async function Pn(e,t,n,o){try{return await Sn(e,t,n)}catch(r){if(!(r instanceof jt))throw r;return await o(),Sn(e,t,n)}}async function Qo(e,t,n,o){let r=await vn(e,t,n);if(!r)return null;if(gt(r))return r;await o();let s=await vn(e,t,n);if(!gt(s))throw Error("refetched the schema but it is still not in the compact format");return s}function Zo(e){let t={Table:0,View:0,API:0,"API File":0,Procedure:0,Function:0};for(let n of e.tables)t[bn(n).objectType]+=1;for(let n of e.procedures)t[wt(n).objectType]+=1;return t}var Pa=/token|passw|pwd|secret|api[-_]?key|credential|authorization/i,er=[/\b(?:PWD|Password)\s*=\s*[^;]+/i,/^Bearer\s+\S+/i,/:\/\/[^/\s:@]+:[^@\s]+@/];function tr(e){return`<redacted ${e.length} chars>`}function nr(e){if(er.some((t)=>t.test(e)))return!0;if(e.length<40||!/^[A-Za-z0-9+/=]+$/.test(e))return!1;try{return er.some((t)=>t.test(atob(e)))}catch{return!1}}function _e(e){if(Array.isArray(e))return e.map((t)=>_e(t));if(e&&typeof e==="object")return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n==="string"&&(Pa.test(t)||nr(n))?tr(n):_e(n)]));if(typeof e==="string"&&nr(e))return tr(e);return e}function Y(e,t={}){let n=t.strings!==!1,o=e.split(""),r=0;while(r<e.length)if(e[r]==="-"&&e[r+1]==="-")while(r<e.length&&e[r]!==`
65
+ `)o[r++]=" ";else if(e[r]==="/"&&e[r+1]==="*"){let s=e.indexOf("*/",r+2),i=s===-1?e.length:s+2;while(r<i)o[r++]=" "}else if(e[r]==="'"){if(n)o[r]=" ";r++;while(r<e.length){if(e[r]==="'"){if(n)o[r]=" ";if(r++,e[r]==="'"){if(n)o[r]=" ";r++;continue}break}if(n)o[r]=" ";r++}}else r++;return o.join("")}function Cn(e){return Y(e,{strings:!1})}function _n(e){let t=Y(e),n=[],o=0;for(let r of t.matchAll(/;|^[ \t]*GO[ \t]*$/gim))n.push({start:o,end:r.index}),o=r.index+r[0].length;return n.push({start:o,end:e.length}),n.filter((r)=>r.end>r.start)}function Rn(e,t=","){let n=[],o=0,r="";for(let s of e){if(s==="(")o++;else if(s===")")o--;if(s===t&&o===0)n.push(r),r="";else r+=s}if(r.trim())n.push(r);return n}function kt(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 En(e){let t=Y(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 or(e){return/\b(?:CREATE|ALTER)\s+FUNCTION\b/i.test(Y(e))}function rr(e,t){let n={sql:e,changed:!1,from:null,to:null};if(t===null)return n;let o=[...Y(e).matchAll(/\b(CREATE|ALTER)(\s+)(?:PROCEDURE|PROC|FUNCTION)\b/gi)];if(o.length!==1)return n;let r=o[0],s=r[1].toUpperCase(),i=t?"ALTER":"CREATE";if(s===i)return n;return{sql:e.slice(0,r.index)+i+e.slice(r.index+r[1].length),changed:!0,from:s,to:i}}function sr(e){let t=Y(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,r=n[0].endsWith("(")?1:0,s=t.length,i=/\bAS\b/gi;i.lastIndex=o;for(let u=i.exec(t);u;u=i.exec(t)){let f=t.slice(o,u.index);if((f.match(/\(/g)??[]).length+r===(f.match(/\)/g)??[]).length){s=u.index;break}}let a=(u)=>u.replace(/^\(/,"").replace(/\)\s*$/,""),c=Rn(a(t.slice(o,s))),l=Rn(a(e.slice(o,s))),d=[];return c.forEach((u,f)=>{if(/^\s*@\w+[\s\S]*=/.test(u))d.push((l[f]??u).trim())}),d}function Ca(e){return`N'${e.replace(/'/g,"''")}'`}function $t(e){return`GRANT EXECUTE ON [dbo].[${e}] TO [public] AS [dbo]`}function ir(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(${Ca(e)}) AND p.permission_name = 'EXECUTE' AND p.state IN ('G','W') AND u.name = 'public'`}var _a=/\bALTER\s+TABLE\s+(?:(?:\[[^\]]*\]|\w+)\s*\.\s*)*(?:\[[^\]]*\]|\w+)\s+(?:WITH\s+(?:CHECK|NOCHECK)\s+)?(\w+)/i,Ra=new Set(["ADD","ALTER","DROP"]);function Ea(e){let t=Cn(e),n=null;for(let o of _n(e)){let r=t.slice(o.start,o.end),s=e.slice(o.start,o.end).trim();if(/\bDROP\s+TABLE\b/i.test(r))return{severity:"block",reason:"DROP TABLE",statement:s};if(!/\bALTER\s+TABLE\b/i.test(r))continue;if(/\bDROP\s+COLUMN\b/i.test(r))return{severity:"block",reason:"ALTER TABLE ... DROP COLUMN",statement:s};if(/\bSWITCH\b/i.test(r))return{severity:"block",reason:"ALTER TABLE ... SWITCH",statement:s};let i=_a.exec(r)?.[1]?.toUpperCase();if(!i||!Ra.has(i))return{severity:"block",reason:`ALTER TABLE ... ${i??"(unknown action)"}`,statement:s};if(!n&&/\bALTER\s+COLUMN\b/i.test(r))n={severity:"confirm",reason:"ALTER TABLE ... ALTER COLUMN (narrowing numeric/date precision loses data silently)",statement:s}}return n}function ar(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 Ta(e){let t=Cn(e);for(let{start:n,end:o}of _n(e)){let r=t.slice(n,o);if(/\bWHERE\b/i.test(r))continue;let s=e.slice(n,o).trim();if(/\bDELETE\b/i.test(r)&&!ar(r,"DELETE"))return{severity:"block",reason:"DELETE without WHERE",statement:s};if(/\bUPDATE\b/i.test(r)&&/\bSET\b/i.test(r)&&!ar(r,"UPDATE"))return{severity:"block",reason:"UPDATE without WHERE",statement:s}}return null}function Tn(e){let t=Y(e),n=[];if(/\bINSERT\b/i.test(t))n.push("INSERT");if(/\bUPDATE\b/i.test(t)&&/\bSET\b/i.test(t))n.push("UPDATE");if(/\bDELETE\b/i.test(t))n.push("DELETE");if(/\bMERGE\b/i.test(t))n.push("MERGE");if(/\bTRUNCATE\b/i.test(t))n.push("TRUNCATE");return n}function Nn(e,t,n){let o=Ea(e);if(o&&(o.severity==="block"||!t))return o;if(n==="nonquery"){let s=Ta(e);if(s)return s}let r=Tn(e);if(r.length>0&&!t)return{severity:"confirm",reason:`writes real data (${r.join(", ")})`,statement:e.trim()};return null}var Da=/^[A-Za-z0-9_]+$/,cr=200,lr=2000;function Ye(e){if(!Da.test(e))throw new m(`invalid object name "${e}": letters, digits and "_" only (it becomes _db/<alias>/<name>.sql)`);return e}function ee(e,t){return{project:e.project,contextProject:e.contextProject,...t}}function Xe(e,t){if(e.engine!=="mssql")throw new m(`${t} supports MSSQL connections only; ${e.alias} is ${e.engine}`)}function ur(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 q(`${e.reason}
66
+ ${n}
67
+ ---
68
+ ${t}`,e.severity==="block"?"hard":"soft")}function dr(e,t){let n=Zo(t.schema),o=[`tables ${n.Table} views ${n.View} api ${n.API} api-file ${n["API File"]} procedures ${n.Procedure} functions ${n.Function}`],r=t.refresh;if(r)o.push(`sql bodies in _db/${e}/: wrote ${r.written.length}, unchanged ${r.unchanged.length}, archived ${r.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 An(e,t,n){rn(n.alias);let{engine:o}=Ve(n.dbToken);if(o==="mssql"&&!n.apiDomain)throw new m("MSSQL connections need --api-domain <host>");let r=n.apiDomain?dn(n.apiDomain):void 0,s={projectId:t,alias:n.alias,config:{apiDomain:r,dbToken:n.dbToken},engine:o,dbToken:n.dbToken,note:null};if(n.test)await Z(s,(j)=>j.query("SELECT 1 AS ok"));let i=await e.store.load(t),a=Q(i,n.alias),c=a?i.connections[a]:void 0,l=n.userToken??c?.userToken,d={...r?{apiDomain:r}:{},...n.sqlServer?{sqlServer:n.sqlServer}:{},...n.database?{database:n.database}:{},dbToken:n.dbToken,...l?{userToken:l}:{}},u=await e.store.save(t,n.alias,d,n.makeDefault),h=[`saved ${t}/${u} ${o}${o==="mssql"&&!l?" (no userToken; tAPI calls need it: fui db add ... --user-token-env VAR)":""}`];if(o==="mssql"){let j={...s,alias:u,config:d},P=await Z(j,(_)=>It(e.store.paths,_,t,u));h.push(...dr(u,P))}return{notes:h}}async function mr(e,t,n){let o=await e.store.resolve(ee(e,{db:t}));Xe(o,"schema pull");let r=await Z(o,(s)=>It(e.store.paths,s,o.projectId,o.alias,n));return{notes:[...o.note?[o.note]:[],`ok ${o.projectId}/${o.alias} schema refreshed`,...dr(o.alias,r)]}}function Dn(e,t){return()=>Z(t,(n)=>It(e.store.paths,n,t.projectId,t.alias))}async function pr(e,t){let n=await e.store.resolve(ee(e,{db:t.db}));Xe(n,"schema");let{projectId:o,alias:r}=n,s=await Qo(e.store.paths,o,r,Dn(e,n));if(!s)throw new m(`no local schema for ${o}/${r}; run: fui schema pull --db ${o}/${r}`);let i=t.search?.toLowerCase(),a=(P)=>!i||P.toLowerCase().includes(i),c=t.objectType,l=!c||c==="Table"||c==="View",d=!c||c!=="Table"&&c!=="View",u=l?s.tables.filter((P)=>(!c||wn(P)===c)&&a(P)):[],f=d?s.procedures.filter((P)=>(!c||yn(P)===c)&&a(P)):[],h=await Ce(e.store.paths.connDir(o,r),`schema of ${o}/${r}`,`fui schema pull --db ${o}/${r}`,"schema.json"),j=[...u,...f];return{notes:[...n.note?[n.note]:[],...h?[h]:[],`${o}/${r} fetchedAt=${s.fetchedAt} tables/views=${u.length} procs/functions=${f.length}`],data:j.length>0?`${j.join(`
69
+ `)}
70
+ `:""}}async function fr(e,t){let n=await e.store.resolveProject(ee(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 r=t.db?[(await e.store.resolve(ee(e,{db:t.db}))).alias]:Object.keys(o.connections),s=t.search?.toLowerCase(),i=(d)=>!s||d.toLowerCase().includes(s),a=t.objectType,c=[],l=[];for(let d of r){let u;try{let h=await e.store.resolve({project:n,db:d});u=await Pn(e.store.paths,n,d,Dn(e,h))}catch(h){l.push(`${d}: schema unreadable: ${h.message}`);continue}if(!u){l.push(`${d}: no local schema; run: fui schema pull --db ${n}/${d}`);continue}l.push(`${d} fetchedAt=${u.fetchedAt}`);let f=`${n}/${d}`;if(!a||a==="Table"||a==="View"){for(let h of u.tables)if((!a||h.objectType===a)&&i(h.name))c.push({db:f,name:h.name,kind:h.objectType,count:h.count})}if(!a||a!=="Table"&&a!=="View"){for(let h of u.procedures)if((!a||h.objectType===a)&&i(h.name))c.push({db:f,name:h.name,kind:h.objectType,count:h.count})}}return{rows:c,notes:l}}var hr=(e)=>e.replace(/'/g,"''");async function gr(e,t,n){Ye(t);let o=await e.store.resolve(ee(e,{db:n,objectName:t}));Xe(o,"sp get");let r=await Z(o,(l)=>l.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${hr(t)}')),'') AS sqlText`)),s=String(r[0]?.sqlText??"");if(!s.trim())throw Error(`object "${t}" not found or has no definition in the database`);let i=e.store.paths,a=await V(i,i.dbDir(o.projectId),{[`${o.alias}/${t}.sql`]:s}),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:s,sync:a,notes:c}}async function wr(e,t,n,o){Ye(t);let r=await e.store.resolve(ee(e,{db:o,objectName:t})),s=e.store.paths.spFile(r.projectId,r.alias,t);return await Na(Aa(s),{recursive:!0}),await Bun.write(s,n),{path:s,alias:r.alias,notes:r.note?[r.note]:[]}}function xa(e){return/(CREATE\s+PROCEDURE|CREATE\s+PROC|CREATE\s+FUNCTION)/im.test(e)}async function yr(e,t){try{let n=await e.query(ir(t)),o=Number(n[0]?.granted??0);return Number.isFinite(o)?o>0:null}catch{return null}}async function Ma(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 Oa(e,t){if(!$e()||await e.wasSkillLoaded())return;throw new q(`[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 br(e,t,n){await Oa(e.session,"sp deploy"),Ye(t);let o=await e.store.resolve(ee(e,{db:n,objectName:t}));Xe(o,"sp deploy");let{projectId:r,alias:s}=o,i=e.store.paths,a=i.spFile(r,s,t),c;try{c=await T(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=En(c)??t,d=kt(l);if(d){let h=sr(c);if(h.length>0)throw new q(`tAPI does not support parameter default values; remove "= ..." and save before deploying:
71
+ `+h.map((j)=>` - ${j}`).join(`
72
+ `),"hard","error")}let u=`${r}/${s}`,f=[];return await Z(o,async(h)=>{let j=null,P="";try{let y=await h.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(N'${hr(l)}')), '') AS def`);P=String(y[0]?.def??"").trim(),j=P.length>0}catch{}let _="";if(P)try{_=`rollback copy: ${await B(i,a,{content:P})}`}catch(y){throw Error(`could not save the rollback copy (${y.message}); not deploying`)}let F=rr(c,j);if(await h.exec(F.sql),o.note)f.push(o.note);if(f.push(`ok deployed ${t} to ${u}`,`file: ${a}`),F.changed)f.push(`ran as ${F.to} because "${l}" ${F.to==="ALTER"?"exists":"does not exist"}; file unchanged`);if(_)f.push(_);let C=j===null?xa(c):!j,g=or(c);if(C&&!g){let y=$t(l);try{await h.exec(y),f.push(`GRANT EXECUTE ON [dbo].[${l}] TO [public]: applied`)}catch(k){f.push(`deploy OK but grant FAILED (${k.message}); do not redeploy, run: fui exec "${y}" --db ${u}`)}}if(!g){let y=await yr(h,l);if(y===!1)f.push(`[public] lacks EXECUTE on "${l}"; tAPI calls will fail. run: fui exec "${$t(l)}" --db ${u}`);else if(y===!0&&!C)f.push(`[public] has EXECUTE on "${l}"`)}if(d)try{let y=await pn(Ge(o.config.apiDomain),s,d.isAuth,d.fnName,o.config.userToken);f.push(y.ok?`tAPI cache cleared (GET ${y.url} -> HTTP ${y.status})`:`tAPI /help HTTP ${y.status}; ignored, deploy done. only if the endpoint still takes old params: fui sp help ${l}`)}catch(y){f.push(`tAPI /help failed (${y.message}); ignored, deploy done`)}}),{notes:[...await Ma(e.session),...f]}}async function jr(e,t,n,o){let r=[t,`spAPI_${t}`,`spAPI_AUTH_${t}`,`spAPIFILE_${t}`,`spAPIFILE_UPLOAD_${t}`],s=await e.store.resolve(ee(e,{db:n,objectName:r}));Xe(s,"sp help");let i=null;try{i=await Pn(e.store.paths,s.projectId,s.alias,Dn(e,s))}catch{}let a=t,c=i?.procedures.find((h)=>r.some((j)=>j.toLowerCase()===h.name.toLowerCase()));if(c)a=c.name;let l=kt(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 d=s.note?[s.note]:[],u;try{u=await pn(Ge(s.config.apiDomain),s.alias,l.isAuth,l.fnName,s.config.userToken)}catch(h){return d.push(`/help for "${a}" failed (${h.message}); ignored, does not affect the deployed SP`),{notes:d}}if(d.push(`GET ${u.url} -> HTTP ${u.status}`),!u.ok)return d.push("/help unavailable for this function; ignored. it only shows params and clears the tAPI cache. do not try other URL shapes"),{notes:d};d.push("tAPI cache cleared for this function");let f;try{let h=JSON.stringify(_e(JSON.parse(u.text)));f=o>0&&h.length>o?`${h.slice(0,o)}\u2026(truncated)`:h}catch{f=o>0?u.text.slice(0,o):u.text}return{notes:d,data:`${f}
73
+ `}}async function Ir(e,t,n){let o=Nn(t,n.confirmWrite,"query");if(o)throw ur(o);let r=await e.store.resolve(ee(e,{db:n.db,write:!n.db&&Tn(t).length>0}));return{rows:await Z(r,(i)=>i.query(t)),connection:r}}function Fa(e){if(!/\b(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\b/i.test(Y(e)))return null;return En(e)}async function kr(e,t,n){let o=Nn(t,n.confirmWrite,"nonquery");if(o)throw ur(o);let r=await e.store.resolve(ee(e,{db:n.db,write:!n.db})),s=fn(r),i=[];try{let a=await s.exec(t),c=r.engine==="mssql"?Fa(t):null;if(c){let l=`${r.projectId}/${r.alias}`,d=await yr(s,c);if(d===!1)i.push(`[public] lacks EXECUTE on "${c}"; tAPI calls will fail. run: fui exec "${$t(c)}" --db ${l} (fui sp save + sp deploy grants automatically)`);else if(d===!0)i.push(`[public] has EXECUTE on "${c}"`);if(kt(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:r,notes:i}}finally{await s.close()}}var La={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=p(t,"project"),r=o?[sn(o)]:[...new Set([...await e.listProjects(),...await e.listLegacyProjects()])].sort(),s=[];for(let i of r){let a=await e.load(i),c=await e.loadLegacy(i),l=c?.apiName??"legacy";if(c&&!Q(a,l))s.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),b(`# ${i}: _db/config.json is the pre-1.4 format and unused; run: fui db import-legacy -p ${i}`);for(let[d,u]of Object.entries(a.connections)){let f="invalid",h=u.database??"";try{if(u.dbToken){let j=pt(u.dbToken);f=j.engine,h||=j.database}}catch{}s.push({db:`${i}/${d}`,engine:f,default:a.default===d?"*":"",apiDomain:u.apiDomain??"",database:h,userToken:u.userToken?"yes":"no"})}}if(s.length===0){b("no connections; add one: fui db add <alias> -p <project> --token -");return}w(D(s,n))}},Ua={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
74
+ [--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,r=n[0];if(!r)throw new m("missing <alias>");let s=(await o.resolveProject(p(t,"project"))).projectId,i=await se(p(t,"token"),p(t,"token-env"),"--token"),a=await se(p(t,"conn"),p(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 An(await W(e),s,{alias:r,dbToken:i??tn(a),apiDomain:p(t,"api-domain"),sqlServer:p(t,"sql-server"),database:p(t,"database"),userToken:await se(void 0,p(t,"user-token-env"),"--user-token"),makeDefault:t.default===!0,test:t["no-test"]!==!0});w(`${c.notes.join(`
75
+ `)}
76
+ `)}},Wa={name:"db default",usage:"fui db default <project>/<alias>",summary:"Set the project default connection",async run(e){let t=e.positionals[0]??p(e.values,"db");if(!t)throw new m("missing <project>/<alias>");let n=await W(e),o=await e.store.resolveProject({db:t,project:n.project,contextProject:n.contextProject}),r=await e.store.setDefault(o,t.split("/").pop());w(`default ${o}/${r}
77
+ `)}},Ba={name:"db test",usage:"fui db test [--db ref]",summary:"Run SELECT 1 and report latency",async run(e){let t=await W(e),n=await e.store.resolve({db:p(e.values,"db"),project:t.project,contextProject:t.contextProject}),o=Bun.nanoseconds();await Z(n,(s)=>s.query("SELECT 1 AS ok"));let r=Math.round((Bun.nanoseconds()-o)/1e6);w(`ok ${n.projectId}/${n.alias} ${n.engine} ${r}ms
78
+ `)}},Ha={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,r=(await n.resolveProject(p(t,"project"))).projectId,s=await o.loadLegacy(r);if(!s)throw new m(`project "${r}" has no legacy _db/config.json with a dbToken`);let i=p(t,"alias")??s.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 An(await W(e),r,{alias:i,dbToken:s.dbToken,apiDomain:s.apiDomain,sqlServer:s.sqlServer,database:s.database,userToken:s.userToken,makeDefault:t.default===!0,test:t["no-test"]!==!0});w(`${a.notes.join(`
79
+ `)}
80
+ legacy _db/config.json left unchanged
81
+ `)}},$r=[La,Ua,Ha,Wa,Ba];import{readdir as Xa}from"fs/promises";import{join as Mn}from"path";import{readdir as qa}from"fs/promises";import{basename as Ja,dirname as Va,extname as Ka,isAbsolute as Ga,join as St,relative as vt,resolve as za,sep as Ya}from"path";function Pt(e,t,n=process.cwd()){let o=za(n,t),r=vt(e.root,o);if(!r||r.startsWith("..")||Ga(r))return null;let s=r.split(Ya),i=s[0];if(i.startsWith("_")||s.length<2)return null;if(s[1]==="modules"&&s.length>=4){let c=s[2],l=St(e.root,i,"modules",c);return{abs:o,baseDir:l,rel:H(vt(l,o)),projectId:i,moduleId:c}}let a=St(e.root,i);return{abs:o,baseDir:a,rel:H(vt(a,o)),projectId:i}}var vr=(e)=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function xn(e,t){let n=vt(e.root,t),o=Ka(n),r=Ja(n,o),s=St(e.historyRoot(),Va(n)),i=new RegExp(`^${vr(r)}_(\\d{8}_\\d{6})((?:_[A-Za-z0-9-]+)*)${vr(o)}$`),a;try{a=await qa(s)}catch{return[]}return a.map((c)=>{let l=i.exec(c);return l?{stamp:l[1],suffix:l[2].replace(/^_/,""),path:St(s,c)}:null}).filter((c)=>c!==null).sort((c,l)=>l.stamp.localeCompare(c.stamp)||l.path.localeCompare(c.path))}function On(e,t){let n=Pt(e.paths,t);if(!n)throw new m(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Qa={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:r,values:s,render:i}=e,a=[{key:"workspace",value:t.root,source:n.source},{key:"session",value:o.file,source:"FUI_SESSION"}];try{let c=await r.resolveProjectModule(p(s,"project"),p(s,"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 r.resolveProject(p(s,"project"));a.push({key:"project",value:c.projectId,source:c.source})}catch{a.push({key:"project",value:"",source:"none"})}}w(D(a,i))}};async function Za(e){try{return(await Xa(Mn(e,"modules"),{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>Mn(e,"modules",n.name))}catch{return[]}}var ec={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:r}=e,s=[];if(t.all===!0){let{projectId:a}=await n.resolveProject(p(t,"project"));s.push(o.projectDir(a),...await Za(o.projectDir(a)))}else{let a=await n.resolveProjectModule(p(t,"project"),p(t,"module"));s.push(a.moduleDir)}let i=[];for(let a of s)for(let c of await ze(a))i.push({dir:a,path:c.relPath,reason:c.reason,push:bt(c.relPath)});b(`# ${i.length} unpublished file(s)`),w(D(i,r))}},tc={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=On(e,t),o=await xn(e.paths,n.abs);b(`# ${o.length} version(s) of ${n.rel}`),w(D(o.map((r)=>({...r,path:Ke(e.paths,r.path)})),e.render))}},nc={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=On(e,t),o=await xn(e.paths,n.abs),r=p(e.values,"at"),s=r?o.find((c)=>c.stamp===r):o[0];if(!s)throw new m(r?`no version ${r} of ${n.rel}; run: fui file history ${t}`:`no archived versions of ${n.rel}`);let i=await G(n.abs),a=i===null?null:await B(e.paths,n.abs,{content:i});if(await Bun.write(n.abs,await T(s.path)),a)b(`# previous content archived: ${Ke(e.paths,a)}`);w(`restored ${n.rel} from ${s.stamp}; push it with: ${bt(n.rel)}
82
+ `)}},oc={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 r=t.map((a)=>On(e,a)),s=Map.groupBy(r,(a)=>a.baseDir);for(let[a,c]of s){let l=c.map((d)=>d.rel);if(n.mark===!0)await J(a,l);if(n.forget===!0)await Ko(a,l)}let i=[];for(let[a,c]of s){let l={};try{l=JSON.parse(await T(Mn(a,Se)))}catch{}for(let d of c){let u=await G(d.abs),f=l[d.rel]??"",h=u===null?"missing":!f?"new":f===fe(u)?"published":"changed";i.push({path:d.rel,state:h,ledger:f,dir:a})}}w(D(i,o))}},Sr=[Qa,ec,tc,nc,oc];import{rename as Cc}from"fs/promises";import{basename as _c,dirname as Xn,join as X}from"path";function Re(e){return e==="text/css"||e==="text/javascript"}function rc(e){return(e||"").trim().replace(/[<>:"/\\|?*\x00-\x1F]/g,"-").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"import-file"}function Qe(e,t,n){let o=n.startsWith(".")?n:`.${n}`;return`${rc(e)}-${t}${o}`}function ie(e){return e.map((t)=>({name:t.FileName,type:t.FileType,contentType:t.ContentType,fileID:t.fileID,scope:t.Scope,sort:t.FileSort??null,...Re(t.ContentType)?{filePath:`imports/${Qe(t.FileName,t.fileID,t.FileType)}`}:{}}))}function Ee(e,t,n=[],o="",r="",s=[]){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"]=r??"",i["header.html"]=t.mHeader??"",i["body.html"]=t.mBody??"",i["imports/_imports.json"]=JSON.stringify(ie(s.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 Fn(e,t,n,o,r){let s={};s["_projectInfo.json"]=JSON.stringify(e,null,2),s["project.json"]=JSON.stringify(t,null,2),s["modules/_modules.json"]=JSON.stringify(n,null,2),s["imports/_imports.json"]=JSON.stringify(ie(r),null,2),s["components/_components.json"]=JSON.stringify(o.map((i)=>({comID:i.comID,comName:i.comName})),null,2);for(let i of o)s[`components/${i.comName}.vue`]=i.comCode;return s}function Pr(e,t){let n=[`${e}/`],o=new Set;for(let r of Object.keys(t).sort()){let s=r.split("/");for(let i=0;i<s.length-1;i++){let a=s.slice(0,i+1).join("/");if(!o.has(a))o.add(a),n.push(`${" ".repeat(i+1)}${s[i]}/`)}n.push(`${" ".repeat(s.length)}${s[s.length-1]}`)}return n.join(`
83
+ `)}import{mkdir as Te,readdir as he,rm as sc,stat as ic,unlink as Ln}from"fs/promises";import{basename as Cr,dirname as Ct,join as M}from"path";function ge(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}async function N(e,t=""){try{return await T(e)}catch{return t}}async function Un(e,t){try{return await T(e)}catch(n){if(Je(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 A(e,t){try{return JSON.parse(await T(e))}catch{return t}}async function ce(e){try{return(await ic(e)).isDirectory()}catch{return!1}}async function Ne(e){try{return(await he(e,{withFileTypes:!0})).filter((n)=>n.isFile()&&n.name.endsWith(".vue")).map((n)=>n.name)}catch{return[]}}async function Ze(e,t){let n=[];for(let[o,r]of Object.entries(t)){let s=M(e,o);await Te(Ct(s),{recursive:!0}),await Bun.write(s,r),n.push(s)}return n}async function _r(e,t,n){let o=[],r;try{r=await he(t,{withFileTypes:!0})}catch{return o}for(let s of r){if(!s.isFile()||!s.name.endsWith(".vue")||n.has(s.name))continue;let i=M(t,s.name);await B(e,i,{suffix:"deleted"}),await Ln(i),o.push(i)}return o}function Rr(e){return new Set(Object.keys(e).filter((t)=>t.startsWith("components/")&&t.endsWith(".vue")).map((t)=>Cr(t)))}async function Wn(e,t,n){return await Te(t,{recursive:!0}),await _r(e,M(t,"components"),Rr(n)),V(e,t,n)}async function Er(e,t,n,o,r){await Te(t,{recursive:!0});let s={"_moduleInfo.json":JSON.stringify({...n,HTMLOnly:o.HTMLOnly,Framework:o.Framework,mTitle:o.mTitle},null,2),...r},i=[];for(let l of["header.html","body.html"])if(!s[l])try{let d=await T(M(t,l));if(d.trim())i.push(`${l}: server returned empty; local content (${d.length} chars) was overwritten. check the server SP if unexpected`)}catch{}let a=await _r(e,M(t,"components"),Rr(s)),c=await V(e,t,s);return{moduleDir:t,removedFiles:a,warnings:i,sync:c}}async function Tr(e,t,n){let o=await A(M(t,"components","_components.json"),[]),r=[];if(!Array.isArray(o))return r;for(let s of o)if(s?.comName&&!n.has(s.comName)){let i=M(t,"components",`${s.comName}.vue`);try{await B(e,i,{suffix:"deleted"}),await Ln(i),r.push(`${s.comName}.vue`)}catch{}}return r}async function et(e,t,n){let o=M(t,"imports"),r=[],s;try{s=await he(o,{withFileTypes:!0})}catch{return r}let i=new Set([...n].map((a)=>Cr(a)));for(let a of s){if(!a.isFile()||a.name==="_imports.json"||i.has(a.name))continue;let c=M(o,a.name);try{await B(e,c,{suffix:"deleted"}),await Ln(c),r.push(`imports/${a.name}`)}catch{}}return r}async function Nr(e,t,n){let o=M(t,"modules"),r=[],s;try{s=await he(o,{withFileTypes:!0})}catch{return r}for(let i of s){if(!i.isDirectory()||n.has(i.name))continue;let a=M(o,i.name);await ht(e,a,{suffix:"deleted"}),await sc(a,{recursive:!0,force:!0}),r.push(i.name)}return r}function Bn(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 Hn(e,t){return M(e.projectDir(t),"modules","_modules.json")}async function Ar(e,t){let n=await A(Hn(e,t),[]);return Array.isArray(n)?n.filter(Bn):[]}async function tt(e,t,n){let o=e.projectDir(t);await Ze(o,{"modules/_modules.json":JSON.stringify(n,null,2)});for(let r of n){let s=M(o,"modules",r.ModuleID,"_moduleInfo.json"),i=await A(s,null);if(!i)continue;await Bun.write(s,JSON.stringify({...i,...r},null,2))}return n.length}async function _t(e,t,n,o){let r=Hn(e,t),s=await A(r,[]),i=Array.isArray(s)?s.filter(Bn):[],a=o??n.ModuleID,{mTitle:c,...l}=n,d=i.findIndex((u)=>u.ModuleID===a||n.id&&u.id===n.id);if(d>=0)i[d]={...i[d],...l};else i.push(l);await Te(Ct(r),{recursive:!0}),await Bun.write(r,JSON.stringify(i,null,2))}async function Dr(e,t,n){let o=Hn(e,t),r=await A(o,[]),s=Array.isArray(r)?r.filter(Bn):[],i=s.findIndex((a)=>a.ModuleID===n);if(i<0)return!1;return s.splice(i,1),await Te(Ct(o),{recursive:!0}),await Bun.write(o,JSON.stringify(s,null,2)),!0}async function xr(e){try{return(await he(e.root,{withFileTypes:!0})).filter((n)=>n.isDirectory()&&!n.name.startsWith("_")).map((n)=>n.name)}catch{return[]}}async function Mr(e){let t=[],n;try{n=await he(e.root,{withFileTypes:!0})}catch{return t}for(let o of n){if(!o.isDirectory()||o.name.startsWith("_"))continue;let r=M(e.root,o.name);t.push({baseDir:r,projectId:o.name});try{for(let s of await he(M(r,"modules"),{withFileTypes:!0}))if(s.isDirectory())t.push({baseDir:M(r,"modules",s.name),projectId:o.name,moduleId:s.name})}catch{}}return t}async function qn(e,t){let n=[];for(let o of await Mr(e)){let r=await A(M(o.baseDir,"imports/_imports.json"),[]),s=Array.isArray(r)?r.find((i)=>i?.fileID===t):void 0;if(s)n.push({...o,entry:s})}return n}async function Jn(e,t,n){let o=[];for(let{baseDir:r}of await Mr(e)){let s=await A(M(r,"imports/_imports.json"),[]),i=Array.isArray(s)?s.find((c)=>c?.fileID===t&&c?.filePath):void 0;if(!i?.filePath)continue;let a=M(r,i.filePath);try{await Te(Ct(a),{recursive:!0}),await Bun.write(a,n),o.push(a)}catch{}}return o}var ac=20,cc=50;function Vn(e,t){let n=t==="module"?cc:ac;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 le(e){return JSON.stringify(e??{},null,2)}function ae(e){return e.replace(/\r\n/g,`
84
+ `).trim()}var lc=["module.json","header.html","body.html","script.js","style.css"];async function Rt(e){let t=[];for(let o of lc)t.push(`${o}
85
+ ${await N(M(e,o))}`);let n=M(e,"components");for(let o of(await Ne(n)).slice().sort())t.push(`components/${o}
86
+ ${await N(M(n,o))}`);return fe(t.join(`
87
+ \x00
88
+ `))}function Or(e,t,n){let o=e.trim(),r=t.trim(),s=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)s=a[1].replace(/\s+/g," ").trim(),o=o.replace(/<title[^>]*>[\s\S]*?<\/title>\s*/i,"").trim();let c=r.match(/^<body[^>]*>([\s\S]*)<\/body>\s*$/i);if(c)r=c[1].trim();return{pageHeader:o,pageBody:r,mTitle:s}}function Fr(e,t){let n=[],o=new Map(e.map((s)=>[s.comName,s.comCode])),r=new Set(Object.keys(t));for(let[s,i]of Object.entries(t)){let a=o.get(s);if(a===void 0)n.push(`added component ${s}`);else if(ae(a)!==ae(i))n.push(`updated component ${s}`)}for(let s of e)if(!r.has(s.comName))n.push(`deleted component ${s.comName}`);return n}function Kn(e){if(!ge(e))throw new m("project.json must be a JSON object at the top level");if(ge(e.data))return{config:e.data,wasEnveloped:!0};return{config:e,wasEnveloped:!1}}function Et(e){let t=0,n=0,o=new Set;try{let r=e.moduleUI?JSON.parse(e.moduleUI):{};t=(r.controls||[]).length,n=(r.data||[]).length;for(let s of r.controls||[])for(let i of s.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 O}from"path";function nt(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(),r=dc(e,o,n);if(r===-1)throw Error("no </template> closing the template block");let s=o.lastIndexOf("</script");if(s===-1)throw Error("no <script> in the source");let i=/<script(\s[^>]*)?>/gi;i.lastIndex=r;let a=i.exec(e);if(!a||a.index>=s)throw Error("no <script> in the source");let c=e.substring(n,r).trim(),l=mc(e.substring(a.index+a[0].length,s).trim()),d=uc(e,o,s);return{template:c,script:l,style:d}}function uc(e,t,n){let o=/<style(\s[^>]*)?>/i.exec(e.slice(n));if(!o)return"";let r=n+o.index+o[0].length,s=t.lastIndexOf("</style");if(s===-1||s<r)return"";return e.substring(r,s).trim()}function dc(e,t,n){let o=/<!--|<\/?([a-zA-Z][a-zA-Z0-9-]*)/g;o.lastIndex=n;let r=1,s=e.length;for(let i=o.exec(e);i!==null;i=o.exec(e)){if(i[0]==="<!--"){let u=e.indexOf("-->",o.lastIndex);if(u===-1)return-1;o.lastIndex=u+3;continue}let a=e.charAt(i.index+1)==="/",c=i[1].toLowerCase(),l=o.lastIndex,d=!1;while(l<s){let u=e.charAt(l);if(u==='"'||u==="'"){let f=e.indexOf(u,l+1);if(f===-1)return-1;l=f+1;continue}if(u===">"){d=e.charAt(l-1)==="/",l++;break}l++}if(o.lastIndex=l,c==="template"){if(a){if(r--,r===0)return i.index}else if(!d)r++;continue}if(!a&&(c==="script"||c==="style"||c==="textarea"||c==="title")){let u=t.indexOf(`</${c}`,l);if(u===-1)return-1;let f=e.indexOf(">",u);if(f===-1)return-1;o.lastIndex=f+1}}return-1}function mc(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 Ae(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Lr(e){let t=(e??"").trim();if(!t)return{};let n=t.startsWith("//")?`https:${t}`:t;if(/^https?:\/\//i.test(n))try{let r=new URL(n);return{host:r.host,path:r.pathname||"/"}}catch{}let o=t.split(/[?#]/)[0];if(!o)return{};return{path:o.startsWith("/")?o:`/${o}`}}function pc(e){let t=e.trim();if(/^(https?:)?\/\//i.test(t)){let{host:o,path:r}=Lr(t);return o?{host:o,path:r}:null}let n=t.split("/")[0];if(n&&n.includes(".")){let{host:o,path:r}=Lr(`//${t}`);return o?{host:o,path:r}:null}return null}function Ur(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 fc(e){let t=[e.ModuleID,e.ModuleName,e.mDomain,e.url].filter((n)=>Boolean(n));return[...new Set(t.flatMap((n)=>[n.toLowerCase(),Ae(n),n.replace(/^\/+|\/+$/g,"").toLowerCase()]))]}function Gn(e,t){let n=t.trim().toLowerCase().replace(/^\/+|\/+$/g,""),o=Ae(t);return e.filter((r)=>{let s=fc(r);return s.includes(n)||s.includes(o)})}function De(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 ot{paths;api;constructor(e,t){this.paths=e;this.api=t}baseDir(e,t){return t?O(this.paths.projectDir(e),"modules",t):this.paths.projectDir(e)}async lookupModuleInfo(e,t){let n=t(await Ar(this.paths,e));if(n)return{found:n,refreshed:!1};let o;try{o=await this.api.listModules(e),await tt(this.paths,e,o)}catch(r){return{found:null,refreshed:!1,refreshError:r.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 ce(t))return null;let n=(await this.api.listProjects()).find((o)=>o.ProjectID===e);if(!n)return null;return await Ze(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=Ae(e),o=(await xr(this.paths)).filter((a)=>a.toLowerCase()===t||Ae(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 r;try{r=await this.api.listProjects()}catch{return e}let s=r.filter((a)=>Ae(a.ProjectName)===n||a.ProjectName.trim().toLowerCase()===t);if(s.length===1)return s[0].ProjectID;if(s.length>1)throw new m(`project hint "${e}" matched several projects by name: ${s.map((a)=>`${a.ProjectName} (${a.ProjectID})`).join(", ")}`);let i=r.filter((a)=>a.ProjectName.toLowerCase().includes(t)||Ae(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),r=(await this.api.getRuntimeModuleVersion(e,n[0]??"",n[1]??""))[0];if(!r||r.Status!==1||!r.ProjectID||!r.ModuleID)throw new m(`cannot resolve a module from domain "${e}"${t?` path "${t}"`:""} via FUI_Runtime_ModuleVersionSelect${r?` (Status=${r.Status})`:" (no result)"}; check the URL or look it up: fui project list / fui module list`);let s=await this.resolveModuleInfoFromCache(r.ProjectID,r.ModuleID);if(!s.found)throw De(r.ProjectID,r.ModuleID,s);return{projectId:r.ProjectID,moduleId:r.ModuleID,moduleInfo:s.found,matchedBy:"runtime_lookup"}}async resolveProjectAndModuleHint(e){let t=pc(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 r=Ur(e);return{projectId:await this.resolveProjectIdFromHint(r.projectHint),moduleHint:r.moduleHint,matchedBy:"target_path"}}catch(r){throw new m(`cannot resolve target "${e}": as URL/domain: ${o.message}; as /projectId/moduleId: ${r.message}`)}}let n=Ur(e);return{projectId:await this.resolveProjectIdFromHint(n.projectHint),moduleHint:n.moduleHint,matchedBy:"target_path"}}async resolveModuleTarget(e,t,n){if(t&&n){let s=await this.resolveModuleInfoFromCache(t,n);if(!s.found)throw De(t,n,s);return{projectId:t,moduleId:n,moduleInfo:s.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 r=await this.lookupModuleInfo(o.projectId,(s)=>{let i=Gn(s,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(!r.found)throw De(o.projectId,o.moduleHint,r);return{projectId:o.projectId,moduleId:r.found.ModuleID,moduleInfo:r.found,matchedBy:"target_path"}}async fetchModuleBundle(e){let{ProjectID:t,ModuleID:n}=e,[o,r,s,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:r,script:s,css:i,dependencies:a}}async fetchProjectBundle(e){let[t,n,o,r,s]=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:r,projectImports:s}}async downloadImportContents(e){let t={},n=[];for(let o of e){if(!Re(o.ContentType))continue;try{let s=(await this.api.getFileContent(o.fileID))?.data?.FileContent??"";t[`imports/${Qe(o.FileName,o.fileID,o.FileType)}`]=s}catch(r){n.push(`${o.FileName} (fileID ${o.fileID}): ${r?.message??String(r)}`)}}return{files:t,failures:n}}async uploadImportContents(e){let t=await A(O(e,"imports/_imports.json"),[]);for(let n of t){if(!n.fileID||!n.filePath||!Re(n.contentType??""))continue;let o=await N(O(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})),r=this.baseDir(e,t),s=await Tr(this.paths,r,new Set(o.map((i)=>i.comName)));return await Ze(r,{"components/_components.json":JSON.stringify(o,null,2)}),{count:n.length,removed:s}}async syncImportsLocal(e,t){let n=await this.api.listImportFiles(e,t),o=t?n.filter((c)=>c.Scope!=="project"):n,r=this.baseDir(e,t),s=ie(o),i=new Set(s.map((c)=>c.filePath).filter((c)=>!!c)),a=await et(this.paths,r,i);return await Ze(r,{"imports/_imports.json":JSON.stringify(s,null,2)}),{list:n,count:o.length,removed:a}}async publishStagedModuleDir(e){let t=await A(O(e,"_moduleInfo.json"),null);if(!t)throw new m(`missing _moduleInfo.json in ${e}; run fui module get first`);let n=await N(O(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 r=await N(O(e,"header.html")),s=await N(O(e,"body.html")),i=await N(O(e,"script.js")),a=await N(O(e,"style.css")),{pageHeader:c,pageBody:l,mTitle:d}=Or(r,s,t.mTitle??""),{ProjectID:u,ModuleID:f}=t;if(await this.api.updateModule(u,f,{moduleUI:o,title:d,pageHeader:c,pageBody:l}),i.trim())await this.api.updateModuleScript(u,f,i);if(a.trim())await this.api.updateModuleCSS(u,f,a);let h=O(e,"components"),j=await A(O(h,"_components.json"),[]),P=new Map(j.map((g)=>[g.comName,g.comID])),_=await Ne(h),F=new Set(_.map((g)=>g.replace(/\.vue$/i,"")));for(let g of _){let y=g.replace(/\.vue$/i,""),{template:k,script:v,style:I}=nt(await N(O(h,g)));await this.api.updateComponent({ProjectID:u,ModuleID:f,ComID:P.get(y),ComName:y,ComTemplate:k,ComScript:v,ComCSS:I})}for(let g of j)if(!F.has(g.comName))await this.api.updateComponent({ProjectID:u,ModuleID:f,ComID:g.comID,ComName:"",ComTemplate:"",ComScript:""});await this.syncComponentsLocal(u,f),await this.uploadImportContents(e);let C=["module.json","header.html","body.html"];if(i.trim())C.push("script.js");if(a.trim())C.push("style.css");for(let g of _)C.push(H(O("components",g)));return await J(e,C).catch(()=>{return}),t}async previewStagedModuleDir(e){let t=await A(O(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=Ee(n.moduleInfo,n.content,n.components,n.script,n.css,n.dependencies),r;try{r=JSON.parse(await N(O(e,"module.json")))}catch(f){throw new m(`module.json has invalid JSON syntax: ${f.message}; fix it before previewing`)}let s=await N(O(e,"header.html")),i=await N(O(e,"body.html")),a=await N(O(e,"script.js")),c=await N(O(e,"style.css")),l=await A(O(e,"imports/_imports.json"),[]),d={};for(let f of await Ne(O(e,"components")))d[f.replace(/\.vue$/i,"")]=await N(O(e,"components",f));let u=[];if(le(r)!==le(JSON.parse(o["module.json"])))u.push("module.json will be updated");for(let f of["header.html","body.html","script.js","style.css"]){let h={"header.html":s,"body.html":i,"script.js":a,"style.css":c}[f];if(ae(h)!==ae(o[f]||""))u.push(`${f} will be updated`)}if(le(l)!==le(JSON.parse(o["imports/_imports.json"]||"[]")))u.push("imports/_imports.json differs locally (metadata only, not sent)");return u.push(...Fr(n.components,d)),{moduleInfo:t,changes:u,remoteSummary:Et(n.content)}}}import{cp as hc,mkdir as zn,readdir as gc,rm as wc}from"fs/promises";import{dirname as yc,isAbsolute as bc,join as K,relative as jc,sep as Ic}from"path";var we="fui-skill",kc=["references/controls-patterns.md","references/module-structure.md","references/INDEX.md"],Wr=`# FUI Skill Summary
89
+
90
+ Use these rules whenever editing a FUI module:
91
+
92
+ - 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.
93
+ - In module.json, keep the metadata-driven structure: data, watch, controls, set.
94
+ - controls must start with the mandatory grid wrapper: container > rows > cols.
95
+ - Inside attr, use JSON-safe directives like v-on:click, never @click.
96
+ - In action IN blocks, force literal strings when needed by using backtick or quoted-string patterns.
97
+ - Reference reactive state with vueData. prefixes.
98
+ - Custom Vue components should use the uc- prefix.
99
+ - 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.
100
+ - 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.
101
+ - 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.
102
+ `,$c="https://github.com/fui-org/fui-skill";function vc(){return`${Wr}
103
+
104
+ ## Source
105
+
106
+ - Bundled with fui-mcp from ${$c}
107
+
108
+ ## Notes
109
+
110
+ - This fallback file exists so every editor can read a project-local skill snapshot after checkout.
111
+ - fui-mcp copies the package-level src/skills/ folder into each staged project workspace.
112
+ - 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 Sc(){let e=import.meta.dir;for(let t of[K(e,"..","..","..","skills"),K(e,"..","skills")])if(await Bun.file(K(t,we,"SKILL.md")).exists())return t;return K(e,"..","..","..","skills")}async function xe(){return K(await Sc(),we)}function Pc(e){return K(e,"_skills",we,"SKILL.md")}async function Tt(e){let t=K(e,"_skills"),n=K(t,we);await zn(e,{recursive:!0});try{let o=await xe();if(!await Bun.file(K(o,"SKILL.md")).exists())throw Error(`bundled skill missing at ${o}`);return await wc(t,{recursive:!0,force:!0}),await zn(t,{recursive:!0}),await hc(o,n,{recursive:!0,filter:(r)=>!r.split(Ic).includes(".git")}),{workspaceRoot:e,skillDir:n,source:"bundled-copy"}}catch(o){let r=`Could not copy bundled skills/ into ${n}: ${o.message}`;return await zn(n,{recursive:!0}),await Bun.write(K(n,"SKILL.md"),`${vc()}
113
+
114
+ ## Sync Warning
115
+
116
+ ${r}
117
+ `),{workspaceRoot:e,skillDir:n,source:"bundled"}}}async function Br(e){if(e){let t=Pc(e),n=await G(t);if(n&&n.trim()){let o=[n];for(let r of kc){let s=await G(K(yc(t),r));if(s?.trim())o.push(`
118
+
119
+ ---
120
+ <!-- ${r} -->
121
+
122
+ ${s}`)}return{text:o.join(""),source:t}}}return{text:Wr.trim(),source:"bundled-summary"}}async function Hr(e,t){return(await Promise.all(t.map(async(o)=>{let r=K(e,o),s=jc(e,r);if(s.startsWith("..")||bc(s))return`
123
+
124
+ ---
125
+ <!-- ${o} -->
126
+
127
+ invalid path (outside the skill folder)`;try{return`
128
+
129
+ ---
130
+ <!-- ${o} -->
131
+
132
+ ${await T(r)}`}catch{return`
133
+
134
+ ---
135
+ <!-- ${o} -->
136
+
137
+ not found at ${r}`}}))).join("")}async function Yn(){let e=K(await xe(),"design-md");return(await gc(e,{withFileTypes:!0})).filter((n)=>n.isDirectory()).map((n)=>n.name).sort()}async function L(e){return new ot(e.paths,await e.api())}function qr(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 ye(e,t){let n=await e.wasSkillLoaded();if(!n&&$e())throw new q(`[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 Jr(e,t){let n=await(await L(e)).resolveModuleTarget(t);return{notes:[],data:`project ${n.projectId}
138
+ module ${n.moduleId}
139
+ matchedBy ${n.matchedBy}
140
+ moduleDir ${e.paths.projectDir(n.projectId)}/modules/${n.moduleId}
141
+ `}}async function Vr(e,t){let{projectId:n,moduleId:o}=t,r=t.moduleDir,s="module_dir";if(!r){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 L(e)).resolveModuleTarget(t.target,n,o);n=a.projectId,o=a.moduleId,r=X(e.paths.projectDir(n),"modules",o),s=a.matchedBy}else{let a=await A(X(r,"_moduleInfo.json"),null);if(!a)throw new m(`missing _moduleInfo.json in ${r}`);n=a.ProjectID,o=a.ModuleID}let i=t.moduleDir?Xn(Xn(Xn(r))):e.paths.root;return await e.session.setTarget({projectId:n??"",moduleId:o??"",moduleDir:r,workspaceRoot:i,target:t.target}),{notes:[`active ${n}/${o} (${s}) -> ${r}`,`session ${e.session.file}`]}}async function Kr(e){return(await e.api()).listProjects()}async function Gr(e,t){let n=await L(e),o=e.paths.projectDir(t),r=await n.fetchProjectBundle(t),s=Fn(r.projectInfo,r.projectJson,r.modules,r.projectComponents,r.projectImports),{"modules/_modules.json":i,...a}=s,c=new Set(ie(r.projectImports).map((P)=>P.filePath).filter((P)=>!!P)),l=await et(e.paths,o,c),d=await n.downloadImportContents(r.projectImports),u=await Wn(e.paths,o,{...a,...d.files}),f=await Nr(e.paths,o,new Set(r.modules.map((P)=>P.ModuleID)));await tt(e.paths,t,r.modules);let h=!1,j=await e.session.getTarget();if(j?.projectId===t&&j.moduleId&&f.includes(j.moduleId))await e.session.clearTarget(),h=!0;return{notes:[`ok synced project ${t} -> ${o}`,`modules ${r.modules.length}, project components ${r.projectComponents.length}, project imports ${r.projectImports.length} (contents ${Object.keys(d.files).length})`,`files: ${kn(u)}`,...l.length>0?[`removed orphaned import files: ${l.join(", ")}`]:[],...f.length>0?[`removed orphaned module dirs (archived): ${f.join(", ")}`]:[],...h?["session target cleared: it pointed to a removed module; run fui use / fui module get again"]:[],...$n(u.archived),...qr(d.failures)]}}async function zr(e,t){let n=await(await e.api()).listModules(t),o;try{await tt(e.paths,t,n),o="cache updated: modules/_modules.json (+ _moduleInfo.json of checked-out modules)"}catch(r){o=`warning: could not write the local cache: ${r.message}`}return{rows:n,notes:[o]}}async function Yr(e,t){let n=await L(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 r={...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,r)).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 ${r.Framework}`];if(a)try{await _t(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 Xr(e,t){let n=await L(e),{projectId:o,moduleId:r,newModuleId:s}=t,i=X(e.paths.projectDir(o),"modules",r),a=await A(X(i,"_moduleInfo.json"),null),c=a?null:await n.resolveModuleInfoFromCache(o,r),l=a??c?.found;if(!l)throw De(o,r,c??void 0);let d=[];if(t.framework&&t.framework!==l.Framework)d.push(`warning: framework ${l.Framework} -> ${t.framework} switches the CDN set (V2/V3); re-check header/body/script and components`);if(s&&s!==r)d.push(`warning: ModuleID "${r}" -> "${s}" changes the module URL; links and menus to the old URL break`);let{mTitle:u,...f}=l,h={...f,...t.extra,Framework:t.framework??l.Framework,mDomain:t.mDomain??l.mDomain??"",ModuleID:s??r,ModuleName:t.moduleName??l.ModuleName,HTMLOnly:t.htmlOnly??l.HTMLOnly,dialogModel:"edit"},P=((await n.api.editModule(o,h)).data??[]).find((C)=>C.ModuleID===h.ModuleID),_={...l,Framework:h.Framework,mDomain:h.mDomain,ModuleID:h.ModuleID,ModuleName:h.ModuleName,HTMLOnly:h.HTMLOnly};if(s&&s!==r)_.id=`${o}/${s}`,_.url=`/${o}/${s}`;if(P)_={..._,...P};await _t(e.paths,o,_,r);let F=["modules/_modules.json updated"];if(a){let C=i;if(s&&s!==r){let y=X(e.paths.projectDir(o),"modules",s);if(await ce(y))d.push(`warning: cannot rename the local folder: ${y} exists; _moduleInfo.json updated in place`);else await Cc(i,y),C=y,F.push(`local folder renamed: modules/${r} -> modules/${s}`)}await Bun.write(X(C,"_moduleInfo.json"),JSON.stringify({...a,..._},null,2)),F.push("_moduleInfo.json updated");let g=await e.session.getTarget();if(g&&g.projectId===o&&g.moduleId===r)await e.session.setTarget({projectId:o,moduleId:_.ModuleID,moduleDir:C,workspaceRoot:g.workspaceRoot,target:g.target}),F.push("session target updated")}return{notes:[`ok updated ${_.id} url ${_.url} name ${_.ModuleName} framework ${_.Framework} htmlOnly ${_.HTMLOnly}`,...F,...d]}}async function Qr(e,t,n){await(await e.api()).deleteModule(t,n);let o=[`ok deleted module ${n} from project ${t}`];try{o.push(await Dr(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 r=X(e.paths.projectDir(t),"modules",n);if(await ce(r)){await ht(e.paths,r,{suffix:"deleted"});let{rm:i}=await import("fs/promises");await i(r,{recursive:!0,force:!0}),o.push(`local folder removed (archived to _history first): modules/${n}`)}let s=await e.session.getTarget();if(s&&s.projectId===t&&s.moduleId===n)await e.session.clearTarget(),o.push("session target cleared (it pointed to the deleted module)");return{notes:o}}async function Zr(e,t){let n=await L(e),o,r,s,i;if(t.projectId&&t.moduleId)o=t.projectId,r=t.moduleId,s="explicit_ids";else if(t.target){let R=await n.resolveProjectAndModuleHint(t.target);o=R.projectId,r=R.moduleInfo?.ModuleID??R.moduleHint,s=R.matchedBy,i=R.moduleInfo}else{let R=await e.targets.resolveProjectModule();o=R.projectId,r=R.moduleId,s=R.source==="cwd"?"cwd":"active_session"}let a=e.paths.projectDir(o),c=await A(X(a,"_projectInfo.json"),null)!==null,l=[];if(!c){let R=await n.fetchProjectBundle(o);await Wn(e.paths,a,Fn(R.projectInfo,R.projectJson,R.modules,R.projectComponents,R.projectImports));let je=await n.downloadImportContents(R.projectImports);if(l.push(...je.failures),Object.keys(je.files).length>0)await V(e.paths,a,je.files)}let d=i??null;if(!d){let R=await n.lookupModuleInfo(o,(je)=>{let uo=je.find((Kt)=>Kt.ModuleID===r);if(uo||s!=="target_path")return uo??null;let Vt=Gn(je,r);if(Vt.length>1)throw new m(`target "${t.target}" matched several modules in project ${o}: ${Vt.map((Kt)=>Kt.ModuleID).join(", ")}`);return Vt[0]??null});if(d=R.found,!d)throw De(o,r,R)}let{content:u,components:f,script:h,css:j,dependencies:P}=await n.fetchModuleBundle(d),_=Ee(d,u,f,h,j,P),F=X(e.paths.projectDir(d.ProjectID),"modules",d.ModuleID),C=new Set(ie(P.filter((R)=>R.Scope!=="project")).map((R)=>R.filePath).filter((R)=>!!R)),g=await et(e.paths,F,C),[y,k]=await Promise.all([n.downloadImportContents(P.filter((R)=>R.Scope==="module")),n.downloadImportContents(c?P.filter((R)=>R.Scope==="project"):[])]);l.push(...y.failures,...k.failures);let v=await Er(e.paths,F,d,u,{..._,...y.files}),I=Object.keys(k.files).length>0?await V(e.paths,a,k.files):null;await _t(e.paths,o,{...d,Framework:u.Framework,HTMLOnly:u.HTMLOnly});let E=await Tt(e.paths.root);await e.session.setTarget({projectId:d.ProjectID,moduleId:d.ModuleID,moduleDir:v.moduleDir,workspaceRoot:e.paths.root,target:t.target});let z=[...v.removedFiles.map((R)=>_c(R)),...g],U=[...v.sync.archived,...I?.archived??[]],lt=c?await Ce(a,"project data (project.json, components, imports)",`fui project sync -p ${d.ProjectID}`,"_projectInfo.json"):null;return{notes:[`ok fetched ${d.ProjectID}/${d.ModuleID} (${s}) -> ${v.moduleDir}`,`files: ${kn(v.sync)}`,...z.length>0?[`removed orphaned local files: ${z.join(", ")}`]:[],...$n(U),c?"project data already local (not fetched); refresh: fui project sync":"project data bootstrapped (project.json, components, imports, modules/_modules.json)",...lt?[lt]:[],`skills: ${E.skillDir}${E.source==="bundled"?" (fallback summary; copy failed)":""}`,...v.warnings.map((R)=>`warning: ${R}`),...qr(l),...Et(u),"now active for fui module preview / publish / validate"],data:`${Pr(d.ModuleID,{"_moduleInfo.json":"{}",..._})}
142
+ `}}async function es(e,t,n,o){let r=await e.api(),s=o==="ui"?await r.getModuleUI(t,n):await r.getModuleHtml(t,n),i=s.data,a=X(e.paths.projectDir(t),"modules",n),c=[];if(o==="ui"&&i?.moduleUI!==void 0&&i.moduleUI!==null&&await ce(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 V(e.paths,a,{"module.json":l}),c.push(`module.json written to ${a}`)}if(o==="html"&&typeof i?.mBody==="string"&&await ce(a))await V(e.paths,a,{"body.html":i.mBody}),c.push(`body.html written to ${a}`);return{notes:c,data:`${JSON.stringify(s)}
143
+ `}}async function Me(e,t,n){let o=t,r=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,r=n||a.moduleId||void 0}let s=r?X(e.paths.projectDir(o),"modules",r):e.paths.projectDir(o);return{projectId:o,moduleId:r,baseDir:s}}async function ts(e,t,n){let o=await(await e.api()).getModuleComponents(t.projectId,t.moduleId),r=o.find((a)=>a.comName===n),s=t.moduleId?`module ${t.moduleId}`:`project ${t.projectId} (global)`;if(!r)throw new m(`component "${n}" not found in ${s}; available: ${o.map((a)=>a.comName).join(", ")}`);let i=[`comID ${r.comID} scope ${t.moduleId?`module:${t.moduleId}`:"project (global)"}`];if(await ce(t.baseDir))await V(e.paths,t.baseDir,{[`components/${n}.vue`]:r.comCode}),i.push(`${n}.vue written to ${t.baseDir}/components/`);return{notes:i,data:r.comCode}}function Oe(e){if(e===void 0)return;let t=Array.isArray(e)?e:[e],n={};for(let o of t){let r=String(o),s=r.indexOf("=");if(s<=0)throw new m(`--set expects key=value, got "${r}"`);let i=r.slice(s+1);try{n[r.slice(0,s)]=JSON.parse(i)}catch{n[r.slice(0,s)]=i}}return n}async function Qn(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 Bun.file(n).text():t==="-"?await Bun.stdin.text():t,r;try{r=JSON.parse(o)}catch(s){throw new m(`--data is not valid JSON: ${s.message}`)}if(!r||typeof r!=="object"||Array.isArray(r))throw new m("--data must be a JSON object");return r}var Fe={module:{type:"string",short:"m"}};function Nt(e){for(let t of e.notes)b(`# ${t}`);if(e.data)w(e.data)}function rt(e){if(w(`${e.notes.join(`
144
+ `)}
145
+ `),e.data)w(e.data)}async function Zn(e){return(await e.targets.resolveProject(p(e.values,"project"))).projectId}async function os(e){return e.targets.resolveProjectModule(p(e.values,"project"),p(e.values,"module"))}function Rc(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 rs(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}var Ec={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:{...Fe,"module-dir":{type:"string"},clear:{type:"boolean"}},async run(e){let{values:t,positionals:n,session:o,render:r}=e;if(t.clear===!0){await o.clearTarget(),w(`session target cleared
146
+ `);return}let s=n[0],i=p(t,"project"),a=p(t,"module"),c=p(t,"module-dir");if(!s&&!i&&!a&&!c){let l=await o.getTarget();if(!l){b("# no session target; set one: fui use <project>/<module>");return}w(D([{...l,session:o.file}],r));return}if(i&&!a||!i&&a)throw new m("pass both -p and -m");rt(await Vr(S(e),{target:s,projectId:i,moduleId:a,moduleDir:c}))}},Tc={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>");Nt(await Jr(S(e),t))}},Nc={name:"project list",mcpName:"project_list",usage:"fui project list",summary:"List projects you can access",async run(e){let t=(await Kr(S(e))).map((n)=>({group:n.GroupName||"Khac",id:n.ProjectID,name:n.ProjectName,framework:n.Framework??"",domain:n.pDomain??""}));w(D(t,e.render))}},Ac={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){rt(await Gr(S(e),await Zn(e)))}},Dc={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 zr(S(e),await Zn(e));for(let o of n)b(`# ${o}`);w(D(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))}},xc={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=p(e.values,"name");if(!t||!n)throw new m("need <moduleId> and --name");rt(await Yr(S(e),{projectId:await Zn(e),moduleId:t,moduleName:n,framework:rs(p(e.values,"framework")),htmlOnly:e.values["html-only"]===!0?!0:void 0,mDomain:p(e.values,"domain"),extra:Oe(e.values.set)}))}},Mc={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:{...Fe,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 os(e);rt(await Xr(S(e),{projectId:t,moduleId:n,moduleName:p(e.values,"name"),framework:rs(p(e.values,"framework")),htmlOnly:Rc(p(e.values,"html-only"),"--html-only"),mDomain:p(e.values,"domain"),newModuleId:p(e.values,"new-id"),extra:Oe(e.values.set)}))}},Oc={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:Fe,async run(e){let t=p(e.values,"project"),n=p(e.values,"module");if(!t||!n)throw new m("module delete needs explicit -p <project> -m <module>");rt(await Qr(S(e),t,n))}},Fc={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:Fe,async run(e){let t=p(e.values,"project"),n=p(e.values,"module");if(t&&!n||!t&&n)throw new m("pass both -p and -m, or a target");Nt(await Zr(S(e),{target:e.positionals[0],projectId:t,moduleId:n}))}};function ns(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:Fe,async run(t){let{projectId:n,moduleId:o}=await os(t);Nt(await es(S(t),n,o,e))}}}var Lc={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:Fe,async run(e){let t=e.positionals[0];if(!t)throw new m("missing <name>");let n=S(e),o=await Me(n,p(e.values,"project"),p(e.values,"module"));Nt(await ts(n,o,t))}},ss=[Ec,Tc,Nc,Ac,Dc,xc,Mc,Oc,Fc,ns("ui"),ns("html"),Lc];import{join as ue}from"path";import re from"path";import{join as is}from"path";async function as(e,t){let n={functionRights:null,systemRights:null},o;try{o=JSON.parse(await T(is(e,"_db","rights.json")))}catch{return n}let r=Object.keys(o??{});if(r.length===0)return n;let s=(l)=>r.find((d)=>d.toLowerCase()===l.toLowerCase())??null,i;if(t)i=s(t);else if(r.length===1)i=r[0];else{let l=await A(is(e,"_db","_connections.json"),null);i=l?.default?s(l.default):null}if(!i)return n;let a=o[i],c=(l,d)=>Array.isArray(l)?new Set(l.map((u)=>u&&typeof u==="object"?String(u[d]??""):"").filter(Boolean)):null;return{functionRights:c(a?.functionRights,"FunctionCode"),systemRights:c(a?.systemRights,"SysRight")}}function cs(e){return us("FunctionRight",e)}function ls(e){return us("SystemRight",e)}function us(e,t){let n=new Set,o=new RegExp(`${e}['"]?\\s*:\\s*\\[([^\\]]*)\\]`,"g"),r=new RegExp(`${e}\\s*\\.\\s*includes\\(\\s*['"]([^'"]+)['"]`,"g");for(let s of t){if(!s)continue;let i;o.lastIndex=0;while(i=o.exec(s))for(let a of i[1].split(",")){let c=a.trim().replace(/^['"]|['"]$/g,"");if(c)n.add(c)}r.lastIndex=0;while(i=r.exec(s))n.add(i[1])}return Array.from(n).sort()}var Uc=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"]),ds=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"]),Wc={"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 to=new Set(["API","CALL","IF","FUN","EXE","MESS","MESSBOX","ARRAY","CONFIRM","IN","OUT","THEN","ELSE","CALLBACK","ERROROUT","ERRORCALLBACK","CANCEL","METHOD","HEADER","COL"]),ms=new Set(["item","index","v_old","user","vueData","p_router","p_routers","v_Set","moment"]);function x(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function Bc(e){if(Array.isArray(e))return e.length>0&&e.every((t)=>x(t)||typeof t==="string")&&e.some((t)=>x(t)&&Object.keys(t).some((n)=>to.has(n)));if(!x(e))return!1;return Object.keys(e).some((t)=>to.has(t))}function be(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 st(e){return!!e&&!e.includes(" ")&&!e.includes("{{")&&!e.includes("(")&&!e.startsWith("`")&&!e.startsWith("#")&&!e.startsWith("!")&&/^[A-Za-z_$]/.test(e.replace(/^vueData\./,""))}function Hc(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 qc(e){let t=e.split(""),n=null;for(let o=0;o<e.length;o++){let r=e[o];if(n){if(r==="\\"){if(t[o]=" ",o+1<e.length)t[o+1]=" ",o++;continue}if(r===n){n=null;continue}t[o]=" "}else if(r==="'"||r==='"'||r==="`")n=r}return t.join("")}var Jc=[{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 Vc(e,t,n,o){if(typeof t!=="string")return;if(!(e.startsWith(":")&&!e.startsWith("::")||e.startsWith("v-bind:")))return;let s=qc(t),i=[];for(let{re:a,what:c}of Jc)if(a.test(s))i.push(c);if(s.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 te(e,t,n="$",o=!1){if(Array.isArray(e))e.forEach((r,s)=>te(r,t,`${n}[${s}]`,o));else if(x(e)){t(e,n,o);for(let[r,s]of Object.entries(e))te(s,t,`${n}.${r}`,o||r.startsWith("#"))}}var Kc="(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)",Gc=new Set([400,550,600,650,800]),zc=/(x\u00F3a|xo\u00E1|\bdelete\b|\bremove\b)/i,Yc=/(delete|remove|xoa)/i,Xc=new Set(["v-text-field","v-textarea","v-select","v-combobox","v-autocomplete","f-date","f-time","f-search"]),ps=new Set(["9"]);function Qc(e,t,n){let o=JSON.stringify(e),r=cs([o]),s=ls([o]).filter((l)=>!ps.has(l)),i=r.length+s.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 (${[...r,...s].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&&r.length>0){let l=a,d=r.filter((u)=>!l.has(u));for(let u of d)n.push(`FunctionRight "${u}" 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(u)?u:`"${u}"`}, 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&&s.length>0){let l=c,d=s.filter((u)=>!l.has(u));for(let u of d)n.push(`SystemRight ${u} 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)"}${ps.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: ${u}, note: "\u2026" }); ho\u1EB7c ch\u1EA1y right_system_list n\u1EBFu cache c\u0169.`)}return i}function Zc(e,t){let n=(a)=>t.push(`[design] ${a} ${Kc}`),o=(a)=>typeof a==="string"?a:"";te(e,(a,c)=>{let l=a.API;if(typeof l==="string"&&Yc.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 r=new Set;te(e,(a,c)=>{if(r.has(a))return;let l=x(a.attr)?a.attr:a;if(l!==a)r.add(l);let d=o(l.label)||o(l.text)||(typeof a.innerHTML==="string"?a.innerHTML:""),u=o(l["icon-text"]);if(!zc.test(d)&&u!=="mdi-delete")return;let f=o(l.color)||o(l[":color"]);if(/\b(error|red)\b/i.test(f)&&!f.includes("?"))n(`N\xFAt/m\u1EE5c x\xF3a (t\u1EA1i ${c}) \u0111ang m\xE0u "${f}" \u2014 n\xFAt x\xF3a d\xF9ng m\xE0u "warning" (cam), kh\xF4ng d\xF9ng error/\u0111\u1ECF.`)});let s=(a,c,l,d)=>{if(Array.isArray(a)){a.forEach((C,g)=>s(C,`${c}[${g}]`,l,d));return}if(!x(a))return;let u=o(a.el),f=o(a.prop),h=x(a.attr)?a.attr:{},j=l||u==="v-dialog"||u==="f-dialog"||u==="hidden-container"||f.includes("hidden-container"),P=d||u==="v-card-actions";if(u==="v-toolbar"&&["color",":color","dark",":dark"].some((C)=>(C in h)))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(u==="v-dialog"||u==="f-dialog"){let C=h.width??h[":width"],g=typeof C==="number"?C:typeof C==="string"&&/^\d+$/.test(C.trim())?parseInt(C,10):NaN;if(!Number.isNaN(g)&&!Gc.has(g))n(`Dialog (t\u1EA1i ${c}) width=${g} \u2014 b\u1ED9 width chu\u1EA9n: 400 / 550 / 600 / 650 / 800.`)}if(j&&u==="v-card"&&["elevation",":elevation","outlined",":outlined"].some((C)=>(C in h)))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 _=o(h.class),F=_.includes("flex-md-grow-0")&&_.includes("ml-2");if(F)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(j&&!F&&Xc.has(u)&&!("label"in h)&&!(":label"in h)&&!("placeholder"in h))n(`${u} trong dialog (t\u1EA1i ${c}) thi\u1EBFu "label" \u2014 form dialog d\xF9ng floating label.`);if(u==="v-snackbar"||u==="v-alert")n(`${u} (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 ${u} trong controls.`);if(u==="f-table"||u==="f-sheet"){let C=h[":height"]??h.height;if(C===void 0)n(`${u} (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 C==="string"&&/\$\s*\(\s*window\s*\)\s*\.\s*height\s*\(\s*\)/.test(C))n(`${u} (t\u1EA1i ${c}) t\xEDnh ":height" th\u1EE7 c\xF4ng theo viewport (\`${C}\`) \u2014 kh\xF4ng c\u1EA7n n\u1EEFa: ${u} 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(P&&(u==="f-button"||u==="v-btn")){let C=o(h.label)||(typeof a.innerHTML==="string"?a.innerHTML:"");if(/^\s*(\u0111\u00F3ng|dong|close)\s*$/i.test(C))n(`N\xFAt "${C.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[C,g]of Object.entries(a))s(g,`${c}.${C}`,j,P)};(Array.isArray(e.controls)?e.controls:[]).forEach((a,c)=>s(a,`$.controls[${c}]`,!1,!1))}var el=12,eo=25,hs=/\b(shrink|flex-grow-0)\b/;function gs(e){return typeof e==="string"?e:x(e)?[e.class,e[":class"]].filter((t)=>typeof t==="string").join(" "):""}function tl(e){return e.w===void 0||e.w===null||e.w===""||e.w===0}function nl(e,t){te(e.controls,(n,o)=>{if(!Array.isArray(n.cols))return;ol(n.cols,o,t),n.cols.forEach((r,s)=>{if(!x(r))return;let i=r,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<=el)return;let l=`${o}.cols[${s}]${typeof i.el==="string"?` (el: ${i.el})`:""}`;if(c<eo){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 >= ${eo} (px, k\xE8m \`"col": { "class": "shrink" }\`).`);return}let d=i.col;if(hs.test(gs(d)))return;t.push(x(d)&&"shrink"in d?`${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 >= ${eo} 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 ol(e,t,n){let o=(r)=>x(r)&&r.el==="v-spacer";if(!e.some(o))return;e.forEach((r,s)=>{if(!x(r))return;let i=r;if(typeof i.el!=="string"||!i.el)return;let a=`${t}.cols[${s}] (el: ${i.el})`,c=i.col,l=hs.test(gs(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(!tl(i)||l)return;n.push(x(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 fs=["API","CALL","EXE","FUN","IF","MESS","MESSBOX"],rl=new Set(["IN","OUT","HEADER","METHOD","CALLBACK","ERROROUT","ERRORCALLBACK","THEN","ELSE","CONFIRM","CANCEL","ARRAY","COL","MAP"]);function sl(e,t,n){for(let[o,r]of Object.entries(e)){if(o==="set")continue;te(r,(s,i)=>{let a=Object.keys(s),c=fs.filter((h)=>a.includes(h));if(c.length===0)return;let l=c[0],d=new Set([l]);if(l==="API"&&c.includes("MESS"))d.add("MESS");let u=c.filter((h)=>!d.has(h));if(u.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 ${u.map((h)=>`\`${h}\``).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 f=a.filter((h)=>!d.has(h)&&!rl.has(h)&&!fs.includes(h));if(f.length>0)n.push(`${i} c\xF3 l\u1EC7nh \`${l}\` n\u1EB1m chung object v\u1EDBi key g\xE1n d\u1EEF li\u1EC7u ${f.map((h)=>`\`${h}\``).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: \`[{ ${f.map((h)=>`"${h}": \u2026`).join(", ")} }, { "${l}": \u2026 }]\`.`)},`$.${o}`)}}function il(e,t){let n=[],o=[];if(t.schemaWarning)o.push(t.schemaWarning);if(!x(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:no()};let r=e,s=new Set(["data","watch","controls","set"]);for(let g of Object.keys(r))if(!s.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(r.data!==void 0&&!Array.isArray(r.data))n.push(`"data" ph\u1EA3i l\xE0 M\u1EA2NG (array), hi\u1EC7n l\xE0 ${typeof r.data}.`);if(r.watch!==void 0&&!x(r.watch))n.push(`"watch" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(r.watch)?"array":typeof r.watch}.`);if(r.controls!==void 0&&!Array.isArray(r.controls))n.push(`"controls" ph\u1EA3i l\xE0 M\u1EA2NG container, hi\u1EC7n l\xE0 ${typeof r.controls}.`);if(r.set!==void 0&&!x(r.set))n.push(`"set" ph\u1EA3i l\xE0 OBJECT, hi\u1EC7n l\xE0 ${Array.isArray(r.set)?"array":typeof r.set}.`);let i=new Set(ms),a=new Set,c=Array.isArray(r.data)?r.data:[];for(let g of c){if(!x(g)){o.push(`data[] ch\u1EE9a ph\u1EA7n t\u1EED kh\xF4ng ph\u1EA3i object: ${JSON.stringify(g)?.slice(0,60)}`);continue}for(let[y,k]of Object.entries(g)){if(to.has(y))continue;if(y.startsWith("#"))continue;if(Bc(k))a.add(y);else i.add(be(y))}}let l=0;te(r,(g,y,k)=>{if(k)return;for(let v of["OUT","ERROROUT"]){let I=g[v];if(typeof I==="string"){if(st(I))i.add(be(I));else if(I.trim())l++}else if(x(I))for(let E of Object.keys(I))i.add(be(E))}});let d=new Map,u=new Map;te(r,(g,y,k)=>{let v=g.CALL;if(typeof v==="string"){if(st(v))(k?u:d).set(be(v),y);else if(v.trim()&&!k)l++}let I=g.API;if(typeof I==="string"&&I.trim()&&!I.startsWith("/")&&!st(I))l++;for(let[E,z]of Object.entries(g)){if(typeof z!=="string")continue;let U=k||E.startsWith("#")?u:d;for(let lt of z.matchAll(/\bCALL\(\s*([A-Za-z_$][\w$.]*)\s*\)/g))U.set(be(lt[1]),y)}});for(let[g,y]of d)if(!a.has(g))n.push(`CALL "${g}" (t\u1EA1i ${y}) kh\xF4ng kh\u1EDBp named action n\xE0o trong data[]. Named actions hi\u1EC7n c\xF3: ${[...a].join(", ")||"(kh\xF4ng c\xF3)"}.`);let f=0,h=0,j=Array.isArray(r.controls)?r.controls:[];for(let g=0;g<j.length;g++){let y=j[g];if(f++,!x(y)||!Array.isArray(y.rows)){n.push(`controls[${g}] ph\u1EA3i l\xE0 container c\xF3 "rows" (grid b\u1EAFt bu\u1ED9c: container > rows > cols).`);continue}for(let k=0;k<y.rows.length;k++){let v=y.rows[k];if(!x(v)||!Array.isArray(v.cols)){n.push(`controls[${g}].rows[${k}] ph\u1EA3i c\xF3 "cols" (m\u1EA3ng Control Object).`);continue}for(let I=0;I<v.cols.length;I++){let E=v.cols[I];if(h++,!x(E))continue;if(E.w===void 0&&E.el!==void 0)o.push(`controls[${g}].rows[${k}].cols[${I}] (el: ${String(E.el)}) thi\u1EBFu "w" \u2014 b\u1EAFt bu\u1ED9c cho item trong cols.`)}}}let P=new Set;if(te(r,(g,y)=>{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 ${y}) \u2014 d\xF9ng "innerHTML" (m\u1EA3ng Control Object) \u0111\u1EC3 l\u1ED3ng.`);let k=g.el;if(typeof k==="string"&&k){if(P.add(k),(k.startsWith("f-")||k.startsWith("fp-"))&&!Uc.has(k)&&!t.localComponents.has(k))o.push(`Component "${k}" (t\u1EA1i ${y}) 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(k.startsWith("t-")&&!ds.has(k))o.push(`Cell component "${k}" (t\u1EA1i ${y}) kh\xF4ng c\xF3 trong danh s\xE1ch t-* h\u1EE3p l\u1EC7: ${[...ds].join(", ")}.`);else if(k.startsWith("uc-")&&!t.localComponents.has(k))o.push(`Custom component "${k}" (t\u1EA1i ${y}) ch\u01B0a th\u1EA5y file .vue/local metadata \u2014 t\u1EA1o component tr\u01B0\u1EDBc ho\u1EB7c ch\u1EA1y component_list.`)}let v=g.attr;if(x(v))for(let[I,E]of Object.entries(v)){if(I.startsWith("@"))n.push(`attr "${I}" (t\u1EA1i ${y}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 shorthand "@"; d\xF9ng "v-on:${I.slice(1)}".`);else if(I.startsWith("#"))n.push(`attr "${I}" (t\u1EA1i ${y}) \u2014 module.json KH\xD4NG h\u1ED7 tr\u1EE3 slot shorthand "#"; d\xF9ng "v-slot:${I.slice(1)}".`);else if((I==="style"||I===":style")&&typeof E==="string"){if(E.split(";").map((U)=>U.trim()).filter(Boolean).filter((U)=>!/^(max-|min-)?width\s*:/i.test(U)).filter((U)=>!(g.el==="v-card"&&/^padding\s*:\s*0(px)?$/i.test(U))).length>0)o.push(`attr style "${E}" (t\u1EA1i ${y}) \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((I.startsWith(":")||I.startsWith("v-bind:"))&&typeof E==="string"&&!Hc(E))l++;else if(I==="v-model"&&typeof E==="string"){if(st(E)){let z=be(E);if(!i.has(z)&&!a.has(z))o.push(`v-model "${E}" (t\u1EA1i ${y}) \u2014 bi\u1EBFn g\u1ED1c "${z}" 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(E.trim())l++}Vc(I,E,y,n)}}),x(r.watch)){let g=(y,k)=>{for(let v of Object.keys(y)){if(v==="deep-watch"){if(x(y[v]))g(y[v],`${k}.deep-watch`);continue}let I=be(v);if(!i.has(I))o.push(`watch "${v}" (${k}) \u2014 bi\u1EBFn "${I}" ch\u01B0a khai b\xE1o trong data[], watcher s\u1EBD kh\xF4ng bao gi\u1EDD ch\u1EA1y.`)}};g(r.watch,"watch")}for(let g of P){let y=Wc[g];if(!y)continue;let k=y.filter((v)=>!t.importsText.includes(v.toLowerCase()));if(k.length>0)n.push(`"${g}" c\u1EA7n import: ${k.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 _=0,F=new Set;if(t.schemaProcs){let g=new Set;for(let y of t.schemaProcs.values())for(let k of y)g.add(k);if(te(r,(y,k)=>{let v=y.API;if(typeof v!=="string"||!v||v.includes("{{"))return;_++;let I=null,E=null;if(v.startsWith("/")){let U=v.replace(/^\/+/,"").split("/").filter(Boolean);if(U.length<2)return;if(E=t.schemaProcs.get(U[0].toLowerCase())??null,!E){F.add(U[0]);return}I=U[1]==="auth"?U[2]??null:U[1]}else if(st(v))I=v,E=g;if(!I||I.includes("`")||!E)return;if(![`spAPI_${I}`,`spAPI_AUTH_${I}`,`spAPIFILE_${I}`,`spAPIFILE_UPLOAD_${I}`,I].some((U)=>E.has(U)))o.push(`API "${v}" (t\u1EA1i ${k}) \u2014 kh\xF4ng t\xECm th\u1EA5y spAPI_${I} 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).`)}),F.size>0){let y=[...F].sort();o.push(`${y.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: ${y.join(", ")}. `+`K\u1EBFt n\u1ED1i t\u1EEBng c\xE1i b\u1EB1ng db_connect({ apiName: "${y[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 C=Qc(r,t,o);return Zc(r,o),nl(r,o),sl(r,n,o),{errors:n,warnings:o,stats:{stateKeys:i.size-ms.size,namedActions:a.size,containers:f,controls:h,callRefs:d.size,apiRefs:_,crossWindowCalls:u.size,dynamicRefs:l,rightRefs:C}}}function no(){return{stateKeys:0,namedActions:0,containers:0,controls:0,callRefs:0,apiRefs:0,crossWindowCalls:0,dynamicRefs:0,rightRefs:0}}function al(e){return e.match(/^CREATE (?:PROCEDURE|FUNCTION)\s+(\S+)\s*\(/i)?.[1]??null}async function cl(e){let t=re.dirname(re.dirname(e)),n=new Set;for(let c of[re.join(e,"components"),re.join(t,"components")]){for(let d of await Ne(c))n.add(d.replace(/\.vue$/i,""));let l=await A(re.join(c,"_components.json"),[]);for(let d of l)if(d?.comName)n.add(d.comName)}let o=[];for(let c of[e,t])o.push(await N(re.join(c,"imports","_imports.json")));let r=await A(re.join(t,"_db","_connections.json"),null),s=new Map,i=[];for(let c of Object.keys(r?.connections??{})){let l=await A(re.join(t,"_db",c,"schema.json"),null),d=Array.isArray(l?.procedures)?l.procedures:null;if(d===null)continue;if(!d.every((u)=>typeof u==="string")){i.push(c);continue}s.set(c.toLowerCase(),new Set(d.map(al).filter((u)=>u!==null)))}let a=await as(t);return{localComponents:n,importsText:o.join(`
147
+ `).toLowerCase(),schemaProcs:s.size>0?s: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 At(e){let t=await N(re.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:no()};let n;try{n=JSON.parse(t)}catch(r){return{errors:[`module.json c\xF3 l\u1ED7i JSON syntax: ${r.message}`],warnings:[],stats:no()}}let o=await cl(e);return il(n,o)}function Dt(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(`
148
+ `)}async function Ue(e){let t=jn(await ze(e));return t?[t]:[]}async function ws(e,t){let n=await At(e);if(n.errors.length===0&&n.warnings.length===0)return[];let o=Dt(n);if(n.errors.length>0&&$e())throw new q(`[FUI_MCP_STRICT] ${t} blocked: module.json has semantic errors; fix them and retry
149
+
150
+ ${o}`,"hard","error");return["--- module validate ---",o]}async function ys(e){let t=await A(ue(e,"_moduleInfo.json"),null),n=t?.ProjectID&&t?.ModuleID?`fui module get -p ${t.ProjectID} -m ${t.ModuleID}`:"fui module get";return Ce(e,"local module",n,"_moduleInfo.json")}async function bs(e){let t=await ys(e),n=await At(e);return{notes:[...t?[t]:[],...await Ue(e)],data:`${Dt(n)}
151
+ `}}async function js(e,t){let o=await(await L(e)).previewStagedModuleDir(t),r=await e.session.createApproval({projectId:o.moduleInfo.ProjectID,moduleId:o.moduleInfo.ModuleID,moduleDir:t,contentHash:await Rt(t)}),s=await ys(t),i=await At(t);return{token:r.token,notes:[...s?[s]:[],`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 ---",Dt(i),`approval token: ${r.token}`,"ask the user; only after explicit approval run: fui module publish html --approval <token>"]}}async function Is(e,t,n){let o=await ye(e.session,"module publish html"),r=(c)=>new q(c,"hard","error");if(!n)throw r("missing --approval: run fui module preview, show it to the user, and publish only after explicit approval");let s=await e.session.getApproval();if(!s)throw r("no pending publish approval; run fui module preview first");if(s.token!==n)throw r("invalid approval token; run fui module preview again and use the latest token after approval");if(s.moduleDir!==t)throw r(`approval token is for ${s.moduleDir}, not ${t}`);if(await Rt(t)!==s.contentHash)throw r("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 ws(t,"module publish html"),a=await(await L(e)).publishStagedModuleDir(t);return await e.session.clearApproval(),{notes:[...o,`ok published ${a.ProjectID}/${a.ModuleID} (${t})`,...i,...await Ue(t)]}}async function Mt(e,t){let n=await ye(e.session,"module publish json"),o=await N(ue(t.moduleDir,"module.json"));if(!o.trim())throw new m("local module.json is empty or missing");try{JSON.parse(o)}catch(s){throw new m(`module.json has invalid JSON syntax: ${s.message}`)}if(await Pe(t.moduleDir,"module.json",o))return{notes:[...n,`skip module.json unchanged since the last publish/sync of ${t.projectId}/${t.moduleId}`]};let r=await ws(t.moduleDir,"module publish json");return await(await e.api()).updateModuleUI(t.projectId,t.moduleId,o),await J(t.moduleDir,["module.json"]).catch(()=>{return}),{notes:[...n,`ok published module.json to ${t.projectId}/${t.moduleId}`,...r,...await Ue(t.moduleDir)]}}async function Ot(e,t,n){let o=`module publish ${n==="script.js"?"script":"css"}`,r=await ye(e.session,o),s=await Un(ue(t.moduleDir,n),n);if(await Pe(t.moduleDir,n,s))return{notes:[...r,`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,s);else await i.updateModuleCSS(t.projectId,t.moduleId,s);return await J(t.moduleDir,[n]).catch(()=>{return}),{notes:[...r,`ok published ${n} to ${t.projectId}/${t.moduleId}`,...await Ue(t.moduleDir)]}}function Le(e){return e.moduleId?`module (${e.moduleId})`:"project (global, loaded in all modules)"}async function ks(e,t){let n=await N(ue(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 xt(e){return A(ue(e,"components","_components.json"),[])}async function $s(e,t){let{count:n,removed:o}=await(await L(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 Ft(e,t,n){let o=await ye(e.session,"component new"),r=await ks(t,n),s=await L(e);await s.syncComponentsLocal(t.projectId,t.moduleId);let i=(await xt(t.baseDir)).find((h)=>h.comName===n);if(i)throw new m(`component "${n}" already exists in ${Le(t)} (ComID ${i.comID}); overwrite it: fui component update ${n}, or pick another name`);let{template:a,script:c,style:l}=nt(r);await s.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:d,removed:u}=await s.syncComponentsLocal(t.projectId,t.moduleId),f=(await xt(t.baseDir)).find((h)=>h.comName===n);return await J(t.baseDir,[H(ue("components",`${n}.vue`))]).catch(()=>{return}),{notes:[...o,`ok created component ${n} in ${Le(t)} (ComID ${f?.comID??"not visible yet; run fui component list"})`,`${d} component(s) synced${u.length>0?`; removed orphaned: ${u.join(", ")}`:""}`,...await Ue(t.baseDir)]}}async function Lt(e,t,n){let o=await ye(e.session,"component update"),r=await ks(t,n),s=(await xt(t.baseDir)).find((h)=>h.comName===n)?.comID;if(s===void 0)throw new m(`component "${n}" is not in _components.json of ${Le(t)}, so there is no ComID to overwrite. new component: fui component new ${n}; stale cache: fui component list, then retry`);let i=H(ue("components",`${n}.vue`));if(await Pe(t.baseDir,i,r))return{notes:[...o,`skip ${n}.vue unchanged since the last publish/sync`]};let{template:a,script:c,style:l}=nt(r),d=await L(e);await d.api.updateComponent({ProjectID:t.projectId,ModuleID:t.moduleId,ComID:s,ComName:n,ComTemplate:a,ComScript:c,ComCSS:l});let{count:u,removed:f}=await d.syncComponentsLocal(t.projectId,t.moduleId);return await J(t.baseDir,[i]).catch(()=>{return}),{notes:[...o,`ok updated component ${n} in ${Le(t)} (ComID ${s})`,`${u} component(s) synced${f.length>0?`; removed orphaned: ${f.join(", ")}`:""}`,...await Ue(t.baseDir)]}}async function vs(e,t,n){let o=(await xt(t.baseDir)).find((a)=>a.comName===n);if(!o)throw new m(`component "${n}" not in the local _components.json of ${Le(t)}; run fui component list first`);let r=await L(e);await r.api.updateComponent({ProjectID:t.projectId,...t.moduleId?{ModuleID:t.moduleId}:{},ComID:o.comID,ComName:"",ComTemplate:"",ComScript:""});let{count:s,removed:i}=await r.syncComponentsLocal(t.projectId,t.moduleId);return{notes:[`ok deleted component ${n} from ${Le(t)}; ${s} remain`,...i.length>0?[`local files removed (archived): ${i.join(", ")}`]:[]]}}async function Ss(e,t){let{id:n,...o}=t,r=await L(e),s=await r.api.editProject(o),i=await r.projectInfoRefreshNote(Cs(o,s));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(s)}
152
+ `}}async function Ps(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 L(e),r=await o.api.editProject(t),s=await o.projectInfoRefreshNote(Cs(t,r));return{notes:["ok project updated",...s?[s]:[]],data:`${JSON.stringify(r)}
153
+ `}}function Cs(e,t){let n=ge(e)?e.id??e.ProjectID:void 0;if(typeof n==="string"&&n.trim())return n;let o=ge(t)?t.data:void 0,r=Array.isArray(o)?o[0]:o,s=ge(r)?r.ProjectID??r.id:void 0;return typeof s==="string"&&s.trim()?s:null}async function Ut(e,t){let n=await ye(e.session,"project publish-config"),o=e.paths.projectDir(t),r=await N(ue(o,"project.json"));if(!r.trim())throw new m(`cannot read ${t}/project.json (missing or empty); run fui project sync -p ${t}, edit it, then publish`);let s;try{s=JSON.parse(r)}catch(l){throw new m(`${t}/project.json has invalid JSON: ${l.message}; nothing published`)}let{config:i,wasEnveloped:a}=Kn(s);if(await Pe(o,"project.json",r))return{notes:[...n,`skip ${t}/project.json unchanged since the last publish/sync`]};let c=await(await e.api()).updateProjectData(t,i);return await J(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)}
154
+ `}}var We={module:{type:"string",short:"m"}},Rs={data:{type:"string"},"data-file":{type:"string"}};function de(e){if(w(`${e.notes.join(`
155
+ `)}
156
+ `),e.data)w(e.data)}async function Es(e){return e.targets.resolveProjectModule(p(e.values,"project"),p(e.values,"module"))}async function oo(e){return e.targets.resolveModuleDir(p(e.values,"module-dir"),p(e.values,"project"),p(e.values,"module"))}var ll={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:{...We,"module-dir":{type:"string"}},async run(e){let t=await bs(await oo(e));for(let n of t.notes)b(`# ${n}`);w(t.data??"")}},ul={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:{...We,"module-dir":{type:"string"}},async run(e){de(await js(S(e),await oo(e)))}},dl={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:{...We,"module-dir":{type:"string"},approval:{type:"string"}},async run(e){de(await Is(S(e),await oo(e),p(e.values,"approval")))}},ml={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:We,async run(e){de(await Mt(S(e),await Es(e)))}};function _s(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:We,async run(n){de(await Ot(S(n),await Es(n),t))}}}function Wt(e,t,n){return{name:`component ${e}`,mcpName:`component_${e}`,usage:`fui component ${e}${e==="list"?"":" <name>"} [-p P [-m M]]`,summary:n,options:We,async run(o){let r=o.positionals[0];if(e!=="list"&&!r)throw new m("missing <name>");de(await t(o,r))}}}async function Bt(e){return Me(S(e),p(e.values,"project"),p(e.values,"module"))}var pl=[Wt("list",async(e)=>$s(S(e),await Bt(e)),"Refresh _components.json from the server (archives .vue files the server dropped)"),Wt("new",async(e,t)=>Ft(S(e),await Bt(e),t),"Create a component on the server from local components/<name>.vue"),Wt("update",async(e,t)=>Lt(S(e),await Bt(e),t),"Overwrite an existing component from local components/<name>.vue (skips when unchanged)"),Wt("delete",async(e,t)=>vs(S(e),await Bt(e),t),"Delete a component on the server (ask the user first); archives the local .vue")],fl={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:Rs,async run(e){de(await Ss(S(e),await Qn(e.values)))}},hl={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:Rs,async run(e){de(await Ps(S(e),await Qn(e.values)))}},gl={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(p(e.values,"project"));de(await Ut(S(e),t))}},Ts=[ll,ul,dl,ml,_s("script"),_s("css"),...pl,fl,hl,gl];import{rename as wl}from"fs/promises";import{join as Ns}from"path";async function Ds(e,t,n){let o=await L(e);if(!t)return{rows:await o.api.listImportFiles(void 0,n),notes:["no project given: read-only query, local workspace not synced"]};let r=await o.syncImportsLocal(t,n);return{rows:r.list,notes:[`_imports.json synced for ${n?`module ${n}`:`project ${t}`} (${r.count} file(s), metadata only; content: fui import get <fileId>)`,...r.removed.length>0?[`removed orphaned local files: ${r.removed.join(", ")}`]:[]]}}async function xs(e,t){return(await e.api()).listFrameworkImportFiles(t)}async function Ms(e,t){let o=(await(await e.api()).getFileContent(t))?.data?.FileContent??"",r=await Jn(e.paths,t,o);return{notes:r.length>0?r.map((s)=>`written: ${s}`):["no local _imports.json declares this fileID yet; content printed only. materialize it: fui import list -p <project> [-m <module>]"],data:o}}async function Os(e,t,n){let o=await(await e.api()).uploadFileContent(t,n),r=await Jn(e.paths,t,n);return{notes:[`ok uploaded content of import ${t}`,...r.map((s)=>`local copy synced: ${s}`)],data:`${JSON.stringify(o)}
157
+ `}}async function Fs(e,t){let n=await L(e),{effectiveSort:o,note:r}=Vn(t.fileSort,t.moduleId?"module":"project"),s=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",...r?[r]:[]];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(s)}
158
+ `}}async function Ls(e,t){let n=await L(e),o=await qn(e.paths,t.fileId),r=t.fileSort,s="";if(t.fileSort!==void 0&&o.length>0){let d=Vn(t.fileSort,o.some((u)=>u.moduleId)?"module":"project");r=d.effectiveSort,s=d.note}let i=await n.api.updateImportFile(t.fileId,{...t.extra,FileType:t.fileType,ContentType:t.contentType,FileName:t.fileName,FileSort:r}),a=Re(t.contentType)?`imports/${Qe(t.fileName,t.fileId,t.fileType)}`:void 0,c=[],l=new Set;for(let d of o){let u=d.entry.filePath;if(u&&a&&a!==u)try{await wl(Ns(d.baseDir,u),Ns(d.baseDir,a)),c.push(`local file moved: ${u} -> ${a}`)}catch{}let f=`${d.projectId}::${d.moduleId??""}`;if(l.has(f))continue;l.add(f);let h=d.moduleId?`module ${d.moduleId}`:`project ${d.projectId}`;try{let{removed:j}=await n.syncImportsLocal(d.projectId,d.moduleId);c.push(`${h}: _imports.json synced${j.length>0?`, removed orphaned ${j.join(", ")}`:""}`)}catch{c.push(`${h}: sync failed`)}}return{notes:["ok import updated",...s?[s]:[],...c],data:`${JSON.stringify(i)}
159
+ `}}async function Us(e,t){let n=await L(e),o=await qn(e.paths,t),r=await n.api.deleteImportFile(t),s=[],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:d}=await n.syncImportsLocal(a.projectId,a.moduleId);s.push(`${l}: _imports.json synced${d.length>0?`, removed ${d.join(", ")}`:""}`)}catch{s.push(`${l}: sync failed`)}}return{notes:[`ok import ${t} deleted`,...s],data:`${JSON.stringify(r)}
160
+ `}}var yl={1:"View only",2:"Develop",3:"Admin"};function it(e){let t=typeof e==="number"?e:Number(e);if(!Number.isFinite(t))return String(e??"");let n=yl[t];return n?`${t} (${n})`:String(t)}function As(e,t){let n=t.trim().toLowerCase();return e.find((o)=>String(o.UserID??"").trim().toLowerCase()===n)}function Ht(e,t){return e.map((n)=>({userId:String(n.UserID??"?"),username:n.UserName||"\u2014",fullName:n.FullName||"\u2014",...t?{right:it(n.UserRight)}:{}}))}async function Ws(e,t,n){return(await e.api()).searchUsers(t,n??"%")}async function Bs(e,t){return(await e.api()).listProjectUserRights(t)}async function Hs(e,t,n){let o=await e.api(),r=`project ${t.projectId}`,s=t.userId.trim();if(!s)throw new m("empty userId; find the real UserID: fui user search <text>");let i=As(await o.listProjectUserRights(t.projectId),s);if(n==="new"&&i)throw new m(`user "${s}" already has right ${it(i.UserRight)} on ${r}; change it: fui user set ${s} --right N`);if(n!=="new"&&!i)throw new m(`user "${s}" has no right on ${r}; ${n==="update"?`grant one: fui user add ${s} --right N`:"nothing to revoke; see: fui user list"}`);let a=n==="delete"?0:t.userRight;await o.updateProjectUserRight(t.projectId,a,JSON.stringify([s]));let c=await o.listProjectUserRights(t.projectId),l=As(c,s),d=n==="delete"?`ok revoked the right of "${s}" on ${r}`:`ok set right ${it(a)} for "${s}" on ${r}`,u=n==="delete"?l?`warning: post-check: the user is still listed with right ${it(l.UserRight)}; the server did not remove it`:"post-check: user no longer listed":l?`post-check: ${s} has ${it(l.UserRight)}`:"warning: post-check: user not found in the list after the write; check: fui user list";return{notes:[d,u,`current rights on ${r} (${c.length}):`],rows:c}}var Js={module:{type:"string",short:"m"}},Vs={type:{type:"string"},"content-type":{type:"string"},name:{type:"string"},sort:{type:"string"},set:{type:"string",multiple:!0}};function qt(e){if(w(`${e.notes.join(`
161
+ `)}
162
+ `),e.data)w(e.data)}function Ks(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 Jt(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 qs(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 bl={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:{...Js,framework:{type:"string"}},async run(e){let t=S(e),n=p(e.values,"framework");if(n){w(D(qs(await xs(t,n.toUpperCase())),e.render));return}let{rows:o,notes:r}=await Ds(t,p(e.values,"project"),p(e.values,"module"));for(let s of r)b(`# ${s}`);w(D(qs(o),e.render))}},jl={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 Ms(S(e),Jt(e));for(let o of t.notes)b(`# ${o}`);let n=p(e.values,"out");if(n)await Bun.write(n,t.data??""),w(`${n}
163
+ `);else w(t.data??"")}},Il={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=Jt(e),n=p(e.values,"file"),o=e.positionals[1];if(n===void 0===(o!=="-"))throw new m("pass the content with -f <file> or - (stdin)");let r=n?await Bun.file(n).text():await mt();qt(await Os(S(e),t,r))}},kl={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:{...Js,...Vs},async run(e){let{values:t}=e,n=p(t,"type"),o=p(t,"content-type"),r=p(t,"name");if(!n||!o||!r)throw new m("need --type, --content-type and --name");qt(await Fs(S(e),{projectId:p(t,"project"),moduleId:p(t,"module"),fileType:n,contentType:o,fileName:r,fileSort:Ks(p(t,"sort")),extra:Oe(t.set)}))}},$l={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:Vs,async run(e){let{values:t}=e,n=p(t,"type"),o=p(t,"content-type"),r=p(t,"name");if(!n||!o||!r)throw new m("need --type, --content-type and --name (the server replaces all three)");qt(await Ls(S(e),{fileId:Jt(e),fileType:n,contentType:o,fileName:r,fileSort:Ks(p(t,"sort")),extra:Oe(t.set)}))}},vl={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){qt(await Us(S(e),Jt(e)))}};async function Gs(e){return(await e.targets.resolveProject(p(e.values,"project"))).projectId}function Sl(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)}var Pl={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 Ws(S(e),t,p(e.values,"id-like"));if(n.length===0){b(`# no user matches "${t}"; the user may not exist, or --id-like filtered it out (default % = no filter)`);return}b("# grant: fui user add <UserID> --right 3 (1=View only, 2=Develop, 3=Admin)"),w(D(Ht(n,!1),e.render))}},Cl={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 Gs(e),n=await Bs(S(e),t);if(n.length===0){b(`# nobody has a right on project ${t}; find a user: fui user search <text>`);return}w(D(Ht(n,!0),e.render))}};function ro(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 r=o.positionals[0];if(r===void 0)throw new m("missing <UserID>; find it: fui user search <text>");let s=await Hs(S(o),{projectId:await Gs(o),userId:r,userRight:t==="delete"?void 0:Sl(p(o.values,"right"))},t);w(`${s.notes.join(`
164
+ `)}
165
+ `),w(D(Ht(s.rows,!0),o.render))}}}var zs=[bl,jl,Il,kl,$l,vl,Pl,Cl,ro("add","new","Grant a right to a user who has none (exact UserID from fui user search)"),ro("set","update","Change the right level of a user who already has one"),ro("remove","delete","Revoke a user's right (ask the user first)")];import{join as _l}from"path";var Rl={module:{type:"string",short:"m"}},so=["load","ui","html","script","css","components","imports"],io=["info","config","modules","components","imports"];async function Ys(e,t){let n=typeof t==="string"?t:`${JSON.stringify(t,null,e.json?0:2)}
166
+ `,o=p(e.values,"out");if(o)await Bun.write(o,n),w(`${o}
167
+ `);else w(n)}var El={name:"remote module",usage:`fui remote module [-p P -m M] --part ${so.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:{...Rl,part:{type:"string"},out:{type:"string",short:"o"}},async run(e){let t=p(e.values,"part");if(!t||!so.includes(t))throw new m(`--part must be one of: ${so.join(", ")}`);let{projectId:n,moduleId:o}=await e.targets.resolveProjectModule(p(e.values,"project"),p(e.values,"module")),r=await S(e).api(),s=await{load:()=>r.getModuleContent(n,o),ui:async()=>(await r.getModuleUI(n,o)).data,html:async()=>String((await r.getModuleHtml(n,o)).data?.mBody??""),script:()=>r.getModuleScript(n,o),css:()=>r.getModuleCSS(n,o),components:()=>r.getModuleComponents(n,o),imports:()=>r.getModuleDependencies(n,o)}[t]();await Ys(e,s)}},Tl={name:"remote project",usage:`fui remote project [-p P] --part ${io.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=p(e.values,"part");if(!t||!io.includes(t))throw new m(`--part must be one of: ${io.join(", ")}`);let{projectId:n}=await e.targets.resolveProject(p(e.values,"project")),o=await S(e).api(),r=await{info:async()=>(await o.listProjects()).find((s)=>s.ProjectID===n)??null,config:()=>o.getProjectJson(n),modules:()=>o.listModules(n),components:()=>o.getModuleComponents(n),imports:()=>o.listImportFiles(n)}[t]();await Ys(e,r)}};function Xs(e,t){let n=Pt(e.paths,t);if(!n)throw new m(`${t} is not inside a project of workspace ${e.paths.root}`);return n}var Nl={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((s)=>Xs(e,s)),n=new ot(e.paths,await S(e).api()),o=new Map,r=[];for(let s of t){if(!s.moduleId){r.push(`${s.rel} unsupported only module files can be diffed (use fui remote project --part config)`);continue}let i=`${s.projectId}/${s.moduleId}`,a=o.get(i);if(!a){let u=(await n.resolveModuleInfoFromCache(s.projectId,s.moduleId)).found;if(!u)throw new m(`module ${i} unknown; run fui module list -p ${s.projectId}`);let f=await n.fetchModuleBundle(u);a=Ee(u,f.content,f.components,f.script,f.css,f.dependencies),o.set(i,a)}let c=await N(s.abs,"\x00missing"),l=a[s.rel],d;if(c==="\x00missing")d=l===void 0?"absent":"only-server";else if(l===void 0)d="only-local";else if(s.rel.endsWith(".json")){let u=!1;try{u=le(JSON.parse(c))===le(JSON.parse(l))}catch{}d=u?"same":"differs"}else d=ae(c)===ae(l)?"same":"differs";r.push(`${s.rel} ${d} ${_l(s.projectId,"modules",s.moduleId)}`)}w(`${r.join(`
168
+ `)}
169
+ `)}},Al={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=S(e),n=0;for(let o of e.positionals){let r=Xs(e,o),s;if(!r.moduleId&&r.rel==="project.json")s=await Ut(t,r.projectId);else if(r.rel.startsWith("components/")&&r.rel.endsWith(".vue")&&!r.rel.slice(11).includes("/")){let i=await Me(t,r.projectId,r.moduleId),a=r.rel.slice(11,-4);s=e.values.new===!0?await Ft(t,i,a):await Lt(t,i,a)}else if(r.moduleId&&(r.rel==="module.json"||r.rel==="script.js"||r.rel==="style.css")){let i={projectId:r.projectId,moduleId:r.moduleId,moduleDir:r.baseDir};s=r.rel==="module.json"?await Mt(t,i):await Ot(t,i,r.rel)}else if(r.rel==="header.html"||r.rel==="body.html"){b(`# ${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{b(`# ${o}: no publisher for this file`),n=Math.max(n,2);continue}w(`${s.notes.join(`
170
+ `)}
171
+ `)}return n}},Qs=[El,Tl,Nl,Al];function Dl(e){if(e===void 0)return cr;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 xl={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,r=await qe(n[0],p(t,"file")),s=Dl(p(t,"limit")),{rows:i,connection:a}=await Ir(await W(e),r,{db:p(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)b(`# ${a.note}`);let c=Mo(i,s);w(D(c.rows,o)),b(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},Ml={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,r=await qe(n[0],p(t,"file")),{result:s,connection:i,notes:a}=await kr(await W(e),r,{db:p(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)b(`# ${i.note}`);let l=_e(s);w(o?ln({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
172
+ `);for(let d of a)b(`# ${d}`)}},Zs=[xl,Ml];function ao(e){if(e===void 0)return;let t=gn.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new m(`--type must be one of: ${gn.join(", ")}`);return t}var Ol={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 pr(await W(e),{db:p(e.values,"db"),objectType:ao(p(e.values,"type")),search:p(e.values,"search")});for(let n of t.notes)b(`# ${n}`);w(t.data??"")}},Fl={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 mr(await W(e),p(e.values,"db"),e.values["prune-local-only"]===!0);w(`${t.notes.join(`
173
+ `)}
174
+ `)}},ei=[Ol,Fl];import{cp as Ul,mkdir as Wl,rm as Bl}from"fs/promises";import{homedir as Hl}from"os";import{join as at}from"path";var co="---\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\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_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_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_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\nNot in fui yet (use the Fui MCP tool): `right_*`, `db_alias_*`, `db_connect_by_name`, `db_sp_verify`, `db_sp_delete`, `db_sp_rename`, `db_token_rebuild`, `db_user_token_set`, `module_outline`, `module_simulate`, `component_preview`, `error_log_*`.\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. `fui module validate`: fix every error; it only reads local files.\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\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.\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- `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 ti(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var ql={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){w(co)}},Jl={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 Br(await ti(e));await e.session.markSkillLoaded(),b(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),w(`${t.text}
175
+ `)}},Vl={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 ti(e),n=t?at(t,"_skills",we):await xe();b(`# skill dir: ${n}`),w(`${(await Hr(n,e.positionals)).trimStart()}
176
+ `)}},Kl={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(),w(`skill marked loaded for this session
177
+ `)}},Gl={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 Tt(e.root);w(`${t.skillDir} (${t.source})
178
+ `)}},zl={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=p(e,"dir")??at(Hl(),".claude","skills"),n=at(t,"fui","SKILL.md");await Bun.write(n,co);let o=at(t,we);await Bl(o,{recursive:!0,force:!0}),await Wl(t,{recursive:!0}),await Ul(await xe(),o,{recursive:!0}),w(`${n}
179
+ ${o}
180
+ `)}},Yl={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 Yn();b(`# ${t.length} brands`),w(e.json?`${JSON.stringify(t)}
181
+ `:`${t.join(`
67
182
  `)}
68
- `)}},tn={name:"schema pull",usage:"fui schema pull [--db ref]",summary:"Refresh schema.json and local .sql bodies from the server (MSSQL)",async run({store:e,values:t}){let n=await e.resolve({db:u(t,"db"),project:u(t,"project")}),o=await Ze(e,n),r=[`ok ${n.projectId}/${n.alias} ${Zt(o.schema)}`];if(o.refreshed.length>0)r.push(`refreshed (old copy in _history): ${o.refreshed.join(" ")}`);if(o.localOnly.length>0)r.push(`local only, not on server: ${o.localOnly.join(" ")}`);f(`${r.join(`
183
+ `)}},Xl={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 Yn();if(!n.includes(t))throw new m(`unknown brand "${t}"; available: ${n.join(", ")}`);let o=at(await xe(),"design-md",t,"DESIGN.md");b(`# source: ${o}`),w(await T(o))}},ni=[ql,Jl,Vl,Kl,Gl,zl,Yl,Xl];function ct(e){let t=e[0];if(!t)throw new m("missing <name>");return t}var Ql={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 fr(await W(e),{db:p(e.values,"db"),objectType:ao(p(e.values,"type")),search:p(e.values,"search")});for(let s of n)b(`# ${s}`);let o=new Map,r=[];for(let s of t){let i=o.get(s.db);if(!i){let[a,c]=s.db.split("/");i=new Set((await e.store.localSqlNames(a,c)).map((l)=>l.toLowerCase())),o.set(s.db,i)}r.push({...s,local:i.has(s.name.toLowerCase())?"yes":"no"})}w(D(r,e.render))}},Zl={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 gr(await W(e),ct(e.positionals),p(e.values,"db"));for(let o of t.notes)b(`# ${o}`);let n=p(e.values,"out");if(n)await Bun.write(n,t.body),w(`${n}
184
+ `);else w(e.values.print===!0?t.body:`${t.path}
185
+ `)}},eu={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=ct(e.positionals),n=p(e.values,"sql"),o=e.positionals[1];if(n!==void 0&&o!==void 0)throw new m("pass --sql or - / -f, not both");let r=await qe(n??o,p(e.values,"file")),s=await wr(await W(e),t,r,p(e.values,"db"));for(let i of s.notes)b(`# ${i}`);w(`${s.path}
186
+ next: fui sp deploy ${t} --db ${s.alias}
187
+ `)}},tu={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=Ye(ct(e.positionals)),n=await W(e),o=await e.store.resolve({db:p(e.values,"db"),project:n.project,contextProject:n.contextProject,objectName:t});w(`${e.store.paths.spFile(o.projectId,o.alias,t)}
188
+ `)}},nu={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 br(await W(e),ct(e.positionals),p(e.values,"db"));w(`${t.notes.join(`
69
189
  `)}
70
- `)}},et=[en,tn];import{homedir as rn}from"os";import{join as tt}from"path";var ge="---\nname: fui\ndescription: Use the `fui` CLI to work with FUI (low-code platform) databases \u2014 list DB connections, run SQL, read schema, fetch/edit/deploy stored procedures (tAPI spAPI_*). Use instead of the Fui MCP db_* tools whenever the `fui` command is available.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for the Fui MCP `db_*` tools. Run it with your shell tool. It shares the Fui MCP workspace, so connections added in either tool work in both. `fui config` shows which workspace folder is in use and why.\n\n## Output contract\n\n- stdout carries data only. Query results are TSV with a header row; `NULL` means SQL null; tab, newline and backslash inside cells are escaped as `\\t`, `\\n`, `\\\\`.\n- stderr carries `# ...` notes (row counts, truncation) and `error: ...` / `guard: ...` messages.\n- Add `--json` for compact single-line JSON instead of TSV.\n- Exit codes: `0` ok, `1` runtime error (network/SQL), `2` usage error (read the message; it lists valid choices), `3` blocked by a safety guard.\n\n## Pick a connection\n\n```bash\nfui db list # db, engine, default, apiDomain, database, userToken\n```\n\n- `--db <alias>` or `--db <project>/<alias>`. `-p <project>` or env `FUI_PROJECT` also selects the project.\n- With no `--db`: the only connection, else the project default. `sp` commands infer the connection from the object name.\n- Writes (`exec`, write SQL in `query`) never fall back to the default when a project has several connections. Pass `--db`.\n- Engines come from the token. `mssql` goes through tAPI over HTTPS. `postgres`, `mysql` and `sqlite` connect directly with Bun.SQL. `schema` and `sp` commands are MSSQL-only.\n\n## Read 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 # --limit 0 = no row cap (default 200)\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` before raising `--limit`. Every row costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --filter student # one compact DDL line per table/view/proc\nfui schema --procs --filter spAPI_ # only procedures/functions\nfui schema pull # refresh cache + local .sql bodies from server\nfui sp list --filter Student # name, kind (API, API File, Procedure, Function, local-only), local\n```\n\nPrefer `fui schema` over querying `sys.*` yourself. The cache pulls automatically the first time.\n\n## Change stored procedures (MSSQL)\n\n1. `fui sp get spAPI_Student_Select` saves the live body to `_db/<alias>/<name>.sql` and prints the path.\n For a new SP: `fui sp path spAPI_New --db <ref>` prints where to create the file.\n2. Edit that file with your file tools. `CREATE` or `ALTER` both work; deploy fixes the verb.\n3. `fui sp deploy spAPI_Student_Select` does all of this:\n - archives the live body to `_history/` as the rollback copy\n - runs the file\n - grants `EXECUTE` to `[public]` for new procedures and verifies the grant\n - clears the tAPI parameter cache for `spAPI_*` names\n4. If the output says the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"` line.\n\n`fui sp help spAPI_X` shows the tAPI parameter list and clears its cache without redeploying.\n\n## Write data (needs the user)\n\n```bash\nfui exec \"UPDATE tblX SET Status = 1 WHERE ID = 42\" --db lhu-docs/docs --confirm-write\n```\n\n- Any `INSERT`/`UPDATE`/`DELETE`/`MERGE`/`TRUNCATE` or `ALTER COLUMN` exits `3` until `--confirm-write` is added.\n- Add `--confirm-write` **only after the user approved that exact write in chat**. Never add it on your own to get past the guard.\n- These are always blocked, with no flag to unlock them: `DROP TABLE`, `ALTER TABLE ... DROP COLUMN`/`SWITCH`, and `DELETE`/`UPDATE` without `WHERE`. Show the SQL to the user to run by hand.\n- Never run a write or call a live API SP \"to test\" it. Read the SP body instead.\n\n## FUI conventions that matter here\n\n- tAPI exposes `spAPI_<Fn>` as `/<alias>/<Fn>`, `spAPI_AUTH_<Fn>` as `/<alias>/auth/<Fn>`, and `spAPIFILE_*` for file endpoints. The alias is the connection name.\n- tAPI SP parameters must not have default values (`@x int = 0`). Deploy blocks them with exit `3`.\n- Signal errors in SPs with `RAISERROR`. Callers read `Message`.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. Deploy handles it; raw `fui exec CREATE PROCEDURE` does not.\n\n## Connections and tokens\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn\nfui db add local -p demo --conn-env PG_URL # postgres://user:pass@host/db\nfui db add cache -p demo --conn-env SQLITE_URL # sqlite:///abs/path/app.db\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # engine, server, database, user (no password)\nfui token encode --server S --database D --uid U --pwd-env PWD # Base64 MSSQL dbToken\n```\n\n- A dbToken is Base64 of a connection string.\n- Secrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo, log or paste token values.\n- `db add` tests with `SELECT 1` before saving (`--no-test` skips the test). The previous `_connections.json` is archived to `_history/`.\n\n## Workspace location\n\nThe workspace is picked in this order: `--workdir` flag, then `FUI_MCP_WORKDIR` env, then `fui config set workdir`, then the Fui MCP workdir in `~/.claude.json`, then `~/.fui-mcp/workspaces`.\n\n```bash\nfui config # effective workdir and its source\nfui config set workdir ~/work/fui # persist a custom location\nfui config unset workdir\n```\n\n## Help\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";var on={name:"skill",usage:"fui skill",summary:"Print the agent skill (how to use this CLI)",async run(){f(ge)}},sn={name:"skill install",usage:"fui skill install [--dir <dir>]",summary:"Write SKILL.md to ~/.claude/skills/fui/ (or --dir)",options:{dir:{type:"string"}},async run({values:e}){let t=u(e,"dir")??tt(rn(),".claude","skills","fui"),n=tt(t,"SKILL.md");await Bun.write(n,ge),f(`${n}
71
- `)}},nt=[on,sn];var an=/^[A-Za-z0-9_]+$/,rt=2000;function Q(e){let t=e[0];if(!t)throw new a("missing <name>");if(!an.test(t))throw new a(`invalid object name "${t}": letters, digits and "_" only`);return t}async function V({store:e,values:t},n){let o=await e.resolve({db:u(t,"db"),project:u(t,"project"),objectName:n});return U(o,"sp"),o}async function ot(e,t){let n=await e.query(`SELECT ISNULL(OBJECT_DEFINITION(OBJECT_ID(${F(t)})), '') AS sqlText`);return String(n[0]?.sqlText??"")}async function cn(e,t){try{let n=await e.query(Xe(t)),o=Number(n[0]?.granted??Number.NaN);return Number.isFinite(o)?o>0:null}catch{return null}}var un={name:"sp list",usage:"fui sp list [--db ref] [--filter text]",summary:"List procedures/functions from the schema cache plus local-only .sql files",options:{filter:{type:"string"}},async run(e){let{store:t,values:n,json:o}=e,r=await t.resolve({db:u(n,"db"),project:u(n,"project")});U(r,"sp list");let s=await fe(t,r),i=new Set((await t.localSqlNames(r.projectId,r.alias)).map((p)=>p.toLowerCase())),c=u(n,"filter")?.toLowerCase(),l=[],d=new Set;for(let p of s.procedures){let m=G(p);if(!m)continue;d.add(m.toLowerCase()),l.push({name:m,kind:K(p),local:i.has(m.toLowerCase())?"yes":"no"})}for(let p of await t.localSqlNames(r.projectId,r.alias))if(!d.has(p.toLowerCase()))l.push({name:p,kind:"local-only",local:"yes"});let g=l.filter((p)=>!c||p.name.toLowerCase().includes(c));h(`# ${r.projectId}/${r.alias} count=${g.length}`),f(T(g,o))}},ln={name:"sp get",usage:"fui sp get <name> [--db ref] [--print]",summary:"Fetch a definition from the server into _db/<alias>/<name>.sql",options:{print:{type:"boolean"}},async run(e){let t=Q(e.positionals),n=await V(e,t),o=await E(n,(i)=>ot(i,t));if(!o.trim())throw Error(`"${t}" not found on ${n.projectId}/${n.alias}`);let r=e.store.paths.spFile(n.projectId,n.alias,t),s=Bun.file(r);if(await s.exists()&&await s.text()!==o)h(`# old local copy archived: ${await R(e.store.paths,r)}`);await Bun.write(r,o),f(e.values.print===!0?o:`${r}
72
- `)}},dn={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=Q(e.positionals),n=await V(e,t);f(`${e.store.paths.spFile(n.projectId,n.alias,t)}
73
- `)}},pn={name:"sp deploy",usage:"fui sp deploy <name> [--db ref]",summary:"Deploy the local .sql: archive live body, align CREATE/ALTER, grant, clear tAPI cache",async run(e){let t=Q(e.positionals),n=await V(e,t),{projectId:o,alias:r}=n,s=e.store.paths,i=s.spFile(o,r,t),c=Bun.file(i);if(!await c.exists())throw new a(`file not found: ${i}; run: fui sp get ${t} or create it`);let l=await c.text();if(!l.trim())throw new a(`file is empty: ${i}`);let d=Je(l)??t,g=me(d);if(g){let w=Ye(l);if(w.length>0)throw new P(`tAPI SP parameters cannot have default values; remove "= ..." then deploy again:
74
- `+w.map((y)=>` ${y}`).join(`
75
- `))}let p=`${o}/${r}`,m=[];await E(n,async(w)=>{let y=null,N="";try{N=(await ot(w,d)).trim(),y=N.length>0}catch{}if(N){let b=await R(s,i,{content:N});m.push(`rollback copy: ${b}`)}let k=Ve(l,y);await w.exec(k.sql),m.unshift(`ok deployed ${d} to ${p}${k.changed?` (ran as ${k.to}, file unchanged)`:""}`);let M=Qe(l),he=y===null?/\bCREATE\s+(?:PROC|PROCEDURE)\b/i.test(l):!y;if(he&&!M)try{await w.exec(J(d)),m.push("grant EXECUTE to [public]: applied")}catch(b){m.push(`grant FAILED (${b.message}); do not redeploy, run: fui exec "${J(d)}" --db ${p}`)}if(!M){let b=await cn(w,d);if(b===!1)m.push(`[public] lacks EXECUTE; tAPI calls will fail. run: fui exec "${J(d)}" --db ${p}`);else if(b===!0&&!he)m.push("[public] has EXECUTE")}if(g)try{let b=await ae(D(n.config.apiDomain),r,g.isAuth,g.fnName,n.config.userToken);m.push(b.ok?`tAPI cache cleared (HTTP ${b.status})`:`tAPI /help HTTP ${b.status}; ignored`)}catch(b){m.push(`tAPI /help failed (${b.message}); ignored`)}}),f(`${m.join(`
190
+ `)}},ou={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 jr(await W(e),ct(e.positionals),p(e.values,"db"),e.values.full===!0?0:lr);for(let n of t.notes)b(`# ${n}`);w(t.data??"")}},oi=[Ql,Zl,eu,tu,nu,ou];var ru={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:p(t,"db"),project:p(t,"project")}),r={db:`${o.projectId}/${o.alias}`,...pt(o.dbToken)};w(D([r],n))}},su={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=p(e,"server"),n=p(e,"database"),o=p(e,"uid"),r=await se(void 0,p(e,"pwd-env"),"--pwd");if(!t||!n||!o||!r)throw new m("need --server, --database, --uid and --pwd-env");w(`${So(t,n,o,r)}
191
+ `)}},ri=[ru,su];import{appendFile as iu,mkdir as au,rename as cu,stat as lu}from"fs/promises";import{dirname as uu,join as du}from"path";var mu=1048576,pu=["\u274C","\u26D4","\u2718","\u26A0\uFE0F"];function fu(e){return du(e,"_logs","tool-errors.jsonl")}function hu(e,t){let n=e.split(`
192
+ `).map((i)=>i.trim()).filter(Boolean),o=[],r=new Set,s=0;for(let i of pu)for(let a of n){if(!a.startsWith(i)||r.has(a))continue;let c=a.length>200?`${a.slice(0,200)}\u2026`:a;if(s+c.length+1>t)return o;r.add(a),o.push(c),s+=c.length+1}return o}function gu(e,t,n){if(e.length<=t+n)return e;let o=e.slice(t,e.length-n),r=hu(o,Math.round((t+n)/2)),s=r.length>0?[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1 \u2014 gi\u1EEF l\u1EA1i ${r.length} d\xF2ng l\xFD do h\u1ECFng]\u2026`,...r]:[`\u2026[c\u1EAFt ${o.length} k\xFD t\u1EF1]\u2026`];return`${e.slice(0,t)}
193
+ ${s.join(`
76
194
  `)}
77
- `)}},mn={name:"sp help",usage:"fui sp help <name> [--db ref]",summary:"Call tAPI /help for an spAPI_* SP: shows params and clears the tAPI cache",async run(e){let t=Q(e.positionals),n=me(t);if(!n)throw new a(`"${t}" is not a tAPI SP (spAPI_*, spAPIFILE_*)`);let o=await V(e,t),r=await ae(D(o.config.apiDomain),o.alias,n.isAuth,n.fnName,o.config.userToken),s=r.text.length>rt?`${r.text.slice(0,rt)}...`:r.text;if(h(`# GET ${r.url} -> HTTP ${r.status}`),f(`${s}
78
- `),!r.ok)process.exitCode=1}},st=[un,ln,dn,pn,mn];var fn={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,json:n}){let o=await e.resolve({db:u(t,"db"),project:u(t,"project")}),r={db:`${o.projectId}/${o.alias}`,...W(o.dbToken)};f(T([r],n))}},gn={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=u(e,"server"),n=u(e,"database"),o=u(e,"uid"),r=await I(void 0,u(e,"pwd-env"),"--pwd");if(!t||!n||!o||!r)throw new a("need --server, --database, --uid and --pwd-env");f(`${Ee(t,n,o,r)}
79
- `)}},it=[fn,gn];var Y=[...De,...it,...qe,...et,...st,...nt,...je],ct=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json -h/--help
80
- exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`;function at(){let e=Math.max(...Y.map((n)=>n.name.length));return`fui: FUI platform CLI (DB)
195
+ ${e.slice(-n)}`}async function si(e,t,n,o){try{let r=fu(e);await au(uu(r),{recursive:!0});try{if((await lu(r)).size>mu)await cu(r,r.replace(/\.jsonl$/,".1.jsonl")).catch(()=>{return})}catch{}let s={ts:new Date().toISOString(),pid:process.pid,tool:t,message:gu(n,400,600),...o&&o!=="error"?{kind:o}:{}};await iu(r,`${JSON.stringify(s)}
196
+ `,"utf-8")}catch{}}var lo=[...$r,...ri,...Zs,...ei,...oi,...ss,...Ts,...zs,...Qs,...Sr,...ni,...Fo],ai=`global: -p/--project <id> --db <alias|project/alias> --workdir <dir> --json --no-header -h/--help
197
+ exit: 0 ok, 1 runtime error, 2 usage error, 3 guard blocked`;function ii(){let e=Math.max(...lo.map((n)=>n.name.length));return`fui: FUI platform CLI (DB)
81
198
 
82
199
  commands:
83
- ${Y.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`).join(`
200
+ ${lo.map((n)=>` ${n.name.padEnd(e)} ${n.summary}`).join(`
84
201
  `)}
85
202
 
86
- ${ct}
203
+ ${ai}
87
204
  run "fui skill" for the agent guide
88
- `}function wn(e){return`${e.usage}
205
+ `}function yu(e){return`${e.usage}
89
206
  ${e.summary}
90
- ${ct}
91
- `}function yn(e){let[t,n]=e;if(!t||t.startsWith("-"))return null;if(n&&!n.startsWith("-")){let r=Y.find((s)=>s.name===`${t} ${n}`);if(r)return{command:r,rest:e.slice(2)}}let o=Y.find((r)=>r.name===t);return o?{command:o,rest:e.slice(1)}:null}async function bn(e){let t=yn(e);if(!t){let r=e[0];if(!r||r==="-h"||r==="--help"||r==="help")return f(at()),C.Ok;return h(`unknown command: ${e.slice(0,2).join(" ")}
92
- `),h(at()),C.Usage}let{command:n,rest:o}=t;try{let r;try{r=hn({args:o,options:{...Ne,...n.options},allowPositionals:!0,strict:!0})}catch(s){throw new a(`${s.message}
93
- usage: ${n.usage}`)}if(r.values.help===!0)return f(wn(n)),C.Ok;return await n.run(await Pe(r.values,r.positionals)),typeof process.exitCode==="number"?process.exitCode:C.Ok}catch(r){let s=we(r),i=s===C.Guard?"guard":"error";return h(`${i}: ${r.message}`),s}}if(import.meta.main)process.exitCode=await bn(Bun.argv.slice(2));export{bn as main};
207
+ ${ai}
208
+ `}var bu=3;function ju(e){let t=[];for(let n of e.slice(0,bu)){if(n.startsWith("-"))break;t.push(n)}for(let n=t.length;n>0;n--){let o=t.slice(0,n).join(" "),r=lo.find((s)=>s.name===o);if(r)return{command:r,rest:e.slice(n)}}return null}async function Iu(e,t,n,o){if(n instanceof q&&n.severity==="soft")return;let r=n instanceof q?n.logKind:"error";await si(e,t.mcpName??`fui ${t.name}`,o,r)}async function ku(e){let t=ju(e);if(!t){let s=e[0];if(!s||s==="-h"||s==="--help"||s==="help")return w(ii()),ne.Ok;return b(`unknown command: ${e.slice(0,2).join(" ")}
209
+ `),b(ii()),ne.Usage}let{command:n,rest:o}=t,r;try{let s;try{s=wu({args:o,options:{...Ao,...n.options},allowPositionals:!0,strict:!0})}catch(i){throw new m(`${i.message}
210
+ usage: ${n.usage}`)}if(s.values.help===!0)return w(yu(n)),ne.Ok;return r=await Do(s.values,s.positionals),await n.run(r)??ne.Ok}catch(s){let i=mo(s),a=i===ne.Guard?"guard":"error",c=s.message;if(b(`${a}: ${c}`),r)await Iu(r.paths.root,n,s,c);return i}}if(import.meta.main)process.exitCode=await ku(Bun.argv.slice(2));export{ku as main};