@fui-org/fui-cli 0.4.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/fui.js +2 -2
- package/package.json +1 -1
- package/skills/fui/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ This puts `fui` on your `PATH` (in `~/.bun/bin`). To run it once without install
|
|
|
27
27
|
bunx --package @fui-org/fui-cli fui --help
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A global install updates itself. At most once
|
|
30
|
+
A global install updates itself. At most once an hour, a command starts a background check against the npm registry. When a newer release exists, the check runs `bun add -g @fui-org/fui-cli@<latest>`. The command you ran is never delayed, and the next run prints one line saying what changed. Run `fui skill install` afterwards if you installed the skills.
|
|
31
31
|
|
|
32
32
|
- `fui update` updates now; `fui update --check` only reports.
|
|
33
33
|
- Turn it off with `fui config set auto-update off` or `FUI_AUTO_UPDATE=0`.
|
package/dist/fui.js
CHANGED
|
@@ -74,7 +74,7 @@ ${t}`,e.severity==="block"?"hard":"soft")}function Ki(e,t){let n=Ni(t.schema),o=
|
|
|
74
74
|
${u.message}
|
|
75
75
|
likely the login borrowed from ${a} has no access to this database (other group); pass --borrow-from <project/alias> or use fui db add`)}let l=await _n(e,n,{alias:t.apiName,dbToken:c,apiDomain:o.apiDomain,sqlServer:r.SQLServer,database:r.DatabaseName,userToken:o.userToken,makeDefault:t.makeDefault,test:!1});return{notes:[...o.note?[o.note]:[],...l.notes,`sql server: ${r.SQLServer} database: ${r.DatabaseName} login borrowed from ${a} (not printed)`]}}async function da(e,t){let n=await Dn(e),o=await ir(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Pn(s,r,i,a),target:{ModuleID:0,APIName:t??o.alias}}}async function pa(e,t,n){let{projectId:o,call:s,target:r}=await da(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await cr(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function ma(e,t,n){let{projectId:o,call:s,target:r}=await da(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new f(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await cr(e.store.paths,o,r.APIName,t.kind==="system"?{system:l}:{functions:l});return{apiName:r.APIName,notes:[a,`current list (${l.length}):`],rows:l}}var Nr={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function _r(e){return{apiDomain:h(e.values,"api-domain"),userToken:await Pe(h(e.values,"user-token"),h(e.values,"user-token-env"),"--user-token")}}function xn(e){k(`${e.join(`
|
|
76
76
|
`)}
|
|
77
|
-
`)}var Zd={name:"db user-token",mcpName:"db_user_token_set",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Pe(h(e.values,"token"),h(e.values,"token-env"),"--token"),n=h(e.values,"from");if(n&&t)throw new f("pass --from or --token/--token-env, not both");let o=await aa(await z(e),{db:h(e.values,"db"),userToken:t,tokenSource:n});xn(o.notes)}},ep={name:"db token-rebuild",mcpName:"db_token_rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=h(e.values,"uid");if(!t)throw new f("missing --uid");let n=await Pe(h(e.values,"pwd"),h(e.values,"pwd-env"),"--pwd");if(!n)throw new f("missing --pwd - or --pwd-env VAR");xn((await ia(await z(e),{db:h(e.values,"db"),uid:t,password:n})).notes)}},tp={name:"db alias list",mcpName:"db_alias_list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...Nr},async run(e){let t=await ca(await z(e),{...await _r(e),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);if(t.rows.length>0)k(X(t.rows,e.render))}},np={name:"db alias new",mcpName:"db_alias_new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...Nr},async run(e){let{values:t}=e,n=e.positionals[0],o=h(t,"module-name"),s=h(t,"sql-server"),r=h(t,"database"),i=h(t,"username");if(!n||!o||!s||!r||!i)throw new f("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Pe(h(t,"password"),h(t,"password-env"),"--password");if(!a)throw new f("missing --password - or --password-env VAR");let c=h(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new f("--module-id must be an integer");let u=await la(await z(e),{...await _r(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});xn(u.notes)}},op={name:"db add-by-name",mcpName:"db_connect_by_name",usage:"fui db add-by-name <apiName> [-p P] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{"borrow-from":{type:"string"},default:{type:"boolean"},...Nr},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <apiName>; see: fui db alias list");let n=await ua(await z(e),{...await _r(e),apiName:t,borrowFrom:h(e.values,"borrow-from"),makeDefault:e.values.default===!0});xn(n.notes)}};function ga(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function wa(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var ya={"api-name":{type:"string"}};function Ar(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new f(`${t} must be ${o}`);return s}function fa(e,t,n){if(e!==void 0&&e.length>t)throw new f(`${n} is at most ${t} characters`);return e}function ha(e){return{name:`right ${e} list`,mcpName:`right_${e}_list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:ya,async run(t){let{apiName:n,rows:o}=await pa(await z(t),e,h(t.values,"api-name"));if(E(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){E(`# none defined; create one: fui right ${e} new ...`);return}k(X(e==="system"?ga(o):wa(o),t.render))}}}function Qt(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,mcpName:`right_${e}_${t}`,usage:n,summary:o[e][t],options:{...ya,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:Ar(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:fa(h(r,"note"),50,"--note")};else{let l=h(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:Ar(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:Ar(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:fa(h(r,"name"),100,"--name")}}let c=await ma(await z(s),a,h(r,"api-name"));xn(c.notes),k(X(e==="system"?ga(c.rows):wa(c.rows),s.render))}}}var ba=[Zd,ep,tp,np,op,ha("system"),Qt("system","new"),Qt("system","update"),Qt("system","delete"),ha("function"),Qt("function","new"),Qt("function","update"),Qt("function","delete")];import{mkdir as up}from"fs/promises";import{realpathSync as sp}from"fs";import{join as ip,sep as Zt}from"path";var je="0.
|
|
77
|
+
`)}var Zd={name:"db user-token",mcpName:"db_user_token_set",usage:"fui db user-token [--db ref] (--from <project/alias> | --token - | --token-env VAR) no source: list fingerprints",summary:"Save a userToken (Bearer for spAPI_* calls) into one connection; never prints tokens",options:{from:{type:"string"},token:{type:"string"},"token-env":{type:"string"}},async run(e){let t=await Pe(h(e.values,"token"),h(e.values,"token-env"),"--token"),n=h(e.values,"from");if(n&&t)throw new f("pass --from or --token/--token-env, not both");let o=await aa(await z(e),{db:h(e.values,"db"),userToken:t,tokenSource:n});xn(o.notes)}},ep={name:"db token-rebuild",mcpName:"db_token_rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=h(e.values,"uid");if(!t)throw new f("missing --uid");let n=await Pe(h(e.values,"pwd"),h(e.values,"pwd-env"),"--pwd");if(!n)throw new f("missing --pwd - or --pwd-env VAR");xn((await ia(await z(e),{db:h(e.values,"db"),uid:t,password:n})).notes)}},tp={name:"db alias list",mcpName:"db_alias_list",usage:"fui db alias list [--search text] [--api-domain host] [--user-token-env VAR]",summary:"Databases you may manage on the acc system (APIName, SQLServer, DatabaseName); read-only",options:{search:{type:"string"},...Nr},async run(e){let t=await ca(await z(e),{...await _r(e),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);if(t.rows.length>0)k(X(t.rows,e.render))}},np={name:"db alias new",mcpName:"db_alias_new",usage:"fui db alias new <apiName> --module-name N --sql-server S --database D --username U (--password - | --password-env VAR) [--module-id N]",summary:"Register a new database as a tAPI alias on acc (ask the user first; app login, never db_owner)",options:{"module-name":{type:"string"},"sql-server":{type:"string"},database:{type:"string"},username:{type:"string"},password:{type:"string"},"password-env":{type:"string"},"module-id":{type:"string"},...Nr},async run(e){let{values:t}=e,n=e.positionals[0],o=h(t,"module-name"),s=h(t,"sql-server"),r=h(t,"database"),i=h(t,"username");if(!n||!o||!s||!r||!i)throw new f("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Pe(h(t,"password"),h(t,"password-env"),"--password");if(!a)throw new f("missing --password - or --password-env VAR");let c=h(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new f("--module-id must be an integer");let u=await la(await z(e),{...await _r(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});xn(u.notes)}},op={name:"db add-by-name",mcpName:"db_connect_by_name",usage:"fui db add-by-name <apiName> [-p P] [--borrow-from <project/alias>] [--default] [--api-domain host] [--user-token-env VAR]",summary:"Connect by alias: look up SQLServer/Database on acc, borrow a SQL login, test, save, fetch schema",options:{"borrow-from":{type:"string"},default:{type:"boolean"},...Nr},async run(e){let t=e.positionals[0];if(!t)throw new f("missing <apiName>; see: fui db alias list");let n=await ua(await z(e),{...await _r(e),apiName:t,borrowFrom:h(e.values,"borrow-from"),makeDefault:e.values.default===!0});xn(n.notes)}};function ga(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function wa(e){return e.map((t)=>({functionId:String(t.FunctionID??""),code:String(t.FunctionCode??""),name:String(t.FunctionName??""),enabled:t.Enable===!1||t.Enable===0?"no":"yes"}))}var ya={"api-name":{type:"string"}};function Ar(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new f(`${t} must be ${o}`);return s}function fa(e,t,n){if(e!==void 0&&e.length>t)throw new f(`${n} is at most ${t} characters`);return e}function ha(e){return{name:`right ${e} list`,mcpName:`right_${e}_list`,usage:`fui right ${e} list [-p P] [--api-name alias]`,summary:e==="system"?"SystemRight levels (0-9, 9 = Project Admin) of a project on acc; refreshes _db/rights.json":"FunctionRight codes of a project on acc (vueData.user.FunctionRight); refreshes _db/rights.json",options:ya,async run(t){let{apiName:n,rows:o}=await pa(await z(t),e,h(t.values,"api-name"));if(E(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){E(`# none defined; create one: fui right ${e} new ...`);return}k(X(e==="system"?ga(o):wa(o),t.render))}}}function Qt(e,t){let n=e==="system"?`fui right system ${t} <0-9>${t==="delete"?"":" [--note text]"} [--api-name alias]`:t==="new"?"fui right function new --code N [--name text] [--api-name alias]":t==="update"?"fui right function update <functionId> --code N [--name text] [--api-name alias]":"fui right function delete <functionId> [--code N] [--api-name alias]",o={system:{new:"Define a SystemRight level that does not exist yet (server pre-check; 9 is always Project Admin)",update:"Change the note of an existing SystemRight level",delete:"DELETE a SystemRight level (ask the user first; the server refuses while users hold it)"},function:{new:"Create a FunctionRight code (the server assigns FunctionID; duplicates rejected)",update:"Edit an existing FunctionRight by its FunctionID",delete:"DELETE a FunctionRight and every user assignment of it (ask the user first)"}};return{name:`right ${e} ${t}`,mcpName:`right_${e}_${t}`,usage:n,summary:o[e][t],options:{...ya,note:{type:"string"},code:{type:"string"},name:{type:"string"}},async run(s){let{values:r,positionals:i}=s,a;if(e==="system")a={kind:e,mode:t,sysRight:Ar(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:fa(h(r,"note"),50,"--note")};else{let l=h(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:Ar(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:Ar(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:fa(h(r,"name"),100,"--name")}}let c=await ma(await z(s),a,h(r,"api-name"));xn(c.notes),k(X(e==="system"?ga(c.rows):wa(c.rows),s.render))}}}var ba=[Zd,ep,tp,np,op,ha("system"),Qt("system","new"),Qt("system","update"),Qt("system","delete"),ha("function"),Qt("function","new"),Qt("function","update"),Qt("function","delete")];import{mkdir as up}from"fs/promises";import{realpathSync as sp}from"fs";import{join as ip,sep as Zt}from"path";var je="1.0.1";var On="@fui-org/fui-cli",ap=3600000,cp=5000;function ka(e=Bun.env){return ip(Ze(e),"..","update.json")}async function $a(e=Bun.env){try{let t=await Bun.file(ka(e)).json();if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function At(e,t=Bun.env){await Bun.write(ka(t),`${JSON.stringify(e,null,2)}
|
|
78
78
|
`)}function va(e=Bun.main){let t;try{t=sp(e)}catch{return null}let n=`${Zt}install${Zt}global${Zt}node_modules${Zt}@fui-org${Zt}fui-cli${Zt}`,o=t.indexOf(n);return o<0?null:t.slice(0,o+n.length-1)}async function Dr(e=Bun.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await ut(e))["auto-update"]!=="off"}catch{return!0}}async function xr(e=Bun.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${On.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(cp),headers:{accept:"application/json"}});if(!n.ok)throw Error(`registry answered HTTP ${n.status}`);let o=await n.json();if(typeof o.version!=="string")throw Error("registry answer has no version");return o.version}function Or(e,t=je){return Bun.semver.order(e,t)===1}async function lp(e){let t=await Bun.$`${process.execPath} add -g ${`${On}@${e}`}`.quiet().nothrow();return{ok:t.exitCode===0,output:`${t.stdout}${t.stderr}`.trim()}}async function Mr(e=Bun.env){let t=await $a(e),n=new Date().toISOString(),o;try{o=await xr(e)}catch(r){throw await At({...t,checkedAt:n,error:r.message},e),r}if(!Or(o))return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};if(!va())return await At({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`bun add -g ${On}@${o}`};let s=await lp(o);if(!s.ok)return await At({...t,checkedAt:n,latest:o,error:s.output.slice(-500)},e),{latest:o,action:"failed",detail:s.output};return await At({checkedAt:n,latest:o,updatedFrom:je,updatedTo:o,noticeShown:!1},e),{latest:o,action:"installed"}}async function Sa(e=Bun.env){if(!va()||!await Dr(e))return null;let t=await $a(e),n=null;if(t.updatedTo===je&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${je}; refresh installed skills: fui skill install`,t.noticeShown=!0,await At(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<ap)return n;return await At({...t,checkedAt:new Date().toISOString()},e),Bun.spawn([process.execPath,Bun.main,"update","--background"],{stdio:["ignore","ignore","ignore"],env:{...e,FUI_AUTO_UPDATE:"off"}}).unref(),n}function ja(e){let t=e[0];if(!t)throw new f(`missing <key>; keys: ${no.join(", ")}`);if(!no.includes(t))throw new f(`unknown key "${t}"; keys: ${no.join(", ")}`);return t}async function dp(){let e=await Dr(),t=Bun.env.FUI_AUTO_UPDATE?.trim(),n=(await ut())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?Ze():"built-in (global bun installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var pp={name:"config",usage:"fui config",summary:"Show settings with their effective value and source",async run({workdir:e,render:t}){let{base:n,token:o}=await oo();E(`# config file: ${Ze()}`),k(X([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await dp()],t))}},mp={name:"config set",usage:"fui config set workdir <path> | api-base <url> | api-token - | auto-update on|off",summary:"Save a setting to the fui config file (api-token only from stdin)",async run({positionals:e}){let t=ja(e),n=e[1];if(!n)throw new f(`missing <value> for ${t}`);let o;if(t==="workdir")o=Wt(n),await up(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new f("auto-update takes on or off");o=n}else if(t==="api-token")o=await Pe(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await ut();s[t]=o;let r=await Ko(s),i={workdir:"FUI_MCP_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(Bun.env[i])E(`# note: ${i} is set in this shell and overrides the config file`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
|
|
79
79
|
`)}},fp={name:"config unset",usage:"fui config unset <workdir|api-base|api-token|auto-update>",summary:"Remove a setting from the fui config file",async run({positionals:e}){let t=ja(e),n=await ut();delete n[t];let o=await Ko(n);k(`unset ${t} in ${o}
|
|
80
80
|
`)}},Ia=[pp,mp,fp];var hp={name:"db list",mcpName:"db_config_read",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=h(t,"project"),s=o?[tr(o)]:[...new Set([...await e.listProjects(),...await e.listLegacyProjects()])].sort(),r=[];for(let i of s){let a=await e.load(i),c=await e.loadLegacy(i),l=c?.apiName??"legacy";if(c&&!me(a,l))r.push({db:`${i}/${l}`,engine:"legacy",default:"",apiDomain:c.apiDomain??"",database:c.database??"",userToken:c.userToken?"yes":"no"}),E(`# ${i}: _db/config.json is the pre-1.4 format and unused; run: fui db import-legacy -p ${i}`);for(let[u,d]of Object.entries(a.connections)){let p="invalid",m=d.database??"";try{if(d.dbToken){let w=io(d.dbToken);p=w.engine,m||=w.database}}catch{}r.push({db:`${i}/${u}`,engine:p,default:a.default===u?"*":"",apiDomain:d.apiDomain??"",database:m,userToken:d.userToken?"yes":"no"})}}if(r.length===0){E("no connections; add one: fui db add <alias> -p <project> --token -");return}k(X(r,n))}},gp={name:"db add",mcpName:"db_connect",usage:`fui db add <alias> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
|
|
@@ -348,7 +348,7 @@ log file: ${r}
|
|
|
348
348
|
`)}return n}},nu=[wh,yh,bh,kh];function $h(e){if(e===void 0)return qi;let t=Number(e);if(!Number.isInteger(t)||t<0)throw new f("--limit must be an integer >= 0 (0 = no limit)");return t}var vh={name:"query",mcpName:"db_sql_execute",usage:'fui query ("<sql>" | -f file | -) [--db ref] [--limit 200] [--confirm-write] [--json]',summary:"Run SQL that returns rows (TSV); writes need --confirm-write",options:{file:{type:"string",short:"f"},limit:{type:"string"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,render:o}=e,s=await vn(n[0],h(t,"file")),r=$h(h(t,"limit")),{rows:i,connection:a}=await ta(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0});if(a.note)E(`# ${a.note}`);let c=ii(i,r);k(X(c.rows,o)),E(c.truncated?`# rows: ${c.rows.length} of ${c.total} (truncated; use --limit 0 or TOP/WHERE)`:`# rows: ${c.total}`)}},Sh={name:"exec",mcpName:"db_sql_execute_nonquery",usage:'fui exec ("<sql>" | -f file | -) [--db ref] [--confirm-write]',summary:"Run DDL/DML that returns no rows; several connections need --db",options:{file:{type:"string",short:"f"},"confirm-write":{type:"boolean"}},async run(e){let{values:t,positionals:n,json:o}=e,s=await vn(n[0],h(t,"file")),{result:r,connection:i,notes:a}=await na(await z(e),s,{db:h(t,"db"),confirmWrite:t["confirm-write"]===!0}),c=`${i.projectId}/${i.alias}`;if(i.note)E(`# ${i.note}`);let l=Xt(r);k(o?nr({db:c,result:l}):`ok ${c} ${JSON.stringify(l)}
|
|
349
349
|
`);for(let u of a)E(`# ${u}`)}},ou=[vh,Sh];function As(e){if(e===void 0)return;let t=pr.find((n)=>n.toLowerCase()===e.toLowerCase());if(!t)throw new f(`--type must be one of: ${pr.join(", ")}`);return t}var jh={name:"schema",mcpName:"db_schema_read",usage:'fui schema [--db ref] [--type Table|View|API|"API File"|Procedure|Function] [--search text]',summary:"Print compact DDL (one line per object) from the local cache; --search matches the whole line",options:{type:{type:"string"},search:{type:"string"}},async run(e){let t=await zi(await z(e),{db:h(e.values,"db"),objectType:As(h(e.values,"type")),search:h(e.values,"search")});for(let n of t.notes)E(`# ${n}`);k(t.data??"")}},Ih={name:"schema pull",mcpName:"db_schema_get",usage:"fui schema pull [--db ref] [--prune-local-only]",summary:"Refresh schema.json and local .sql bodies; archive files of objects dropped on the server",options:{"prune-local-only":{type:"boolean"}},async run(e){let t=await Gi(await z(e),h(e.values,"db"),e.values["prune-local-only"]===!0);k(`${t.notes.join(`
|
|
350
350
|
`)}
|
|
351
|
-
`)}},ru=[jh,Ih];import{cp as Rh,mkdir as Ph,rm as Eh}from"fs/promises";import{homedir as Th}from"os";import{join as Xn}from"path";var Ns="---\nname: fui\ndescription: Use the `fui` CLI for FUI (low-code platform) work \u2014 projects, modules (get, validate, preview, publish), components, imports, project users, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), workspace files, publish ledger and history. Prefer it over Fui MCP tools whenever `fui` covers the tool (see the map below); same guards, a fraction of the tokens.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for Fui MCP tools. Run it with your shell tool. It uses the same workspace, connection files, publish ledger, `_history` and error log as Fui MCP, so you can mix the two during the transition.\n\nDomain rules (module.json, controls, tAPI conventions) live in the `fui-skill` skill. Read its `references/` as needed, then run `fui skill loaded`; strict mode (`FUI_MCP_STRICT=1`) blocks deploys until you do.\n\n## Output contract\n\n- stdout is data only: TSV with a header when piped, an aligned table in a terminal, `--json` for one-line JSON, `--no-header` to drop the header.\n- stderr carries `# ...` notes and `error:` / `guard:` lines.\n- Exit codes:\n - `0`: ok, including \"skipped: unchanged\".\n - `1`: runtime error (network or SQL).\n - `2`: usage error. Read the message; it lists the valid choices.\n - `3`: blocked by a guard, or waiting for `--confirm-write`.\n\n## Targets\n\n- **Project and module**, first match wins:\n 1. `-p <project> -m <module>`. Passing only one of them is an error.\n 2. The current directory, if it is inside `<workspace>/<project>/modules/<module>/`.\n 3. The session target.\n- **Session:** one per `FUI_SESSION` (default `default`). Parallel agents should each set their own `FUI_SESSION`.\n- **Connection:**\n - `--db <alias>` or `--db <project>/<alias>`.\n - With no `--db`: the only connection, else one inferred from the object name (`sp` commands), else the default.\n - Writes never use the default when a project has several connections.\n- `fui file where` prints what resolved and why.\n- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.\n- A global install updates itself in the background, at most once a day. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.\n\n## Fui MCP tool \u2192 fui command\n\nImplemented (same guards, same files, same requests as Fui MCP 1.4.0):\n\n| MCP tool | fui |\n| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| session_set / session_read / session_clear / session_resolve | `use <target>` / `use` / `use --clear` / `resolve <target>` |\n| project_list / project_sync | `project list` / `project sync` |\n| project_new / project_update / project_publish_config | `project new --data {\u2026}` / `project update --data {id:\u2026}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set \u2026` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] \u2026` / `module delete -p P -m M` |\n| module_get / module_get_ui / module_get_html | `module get [<target>]` / `module get-ui` / `module get-html` |\n| module_validate / module_preview | `module validate` / `module preview` |\n| module_outline / module_simulate | `module outline` / `module simulate [--scenario [...]] [--mocks {...}] [--user {...}] [--url-params {...}]` |\n| module_simulate renderUI / component_preview | `module simulate --render [--device mobile] [--vue-data {...}]` / `component preview <uc-name> [--cases [...]] [--device desktop,mobile]` |\n| error_log_read / error_log_clear | `log read [--tool T] [--limit N\\|0] [--summary]` / `log clear` |\n| module_publish_html / json / script / css | `module publish html --approval <token>` / `module publish json` / `\u2026 script` / `\u2026 css` |\n| component_list / get / new / update / delete | `component list` / `component get <name>` / `component new <name>` / `component update <name>` / `component delete <name>` |\n| file_import_list / get / new / update / upload_content / delete | `import list` / `import get <id>` / `import new \u2026` / `import update <id> \u2026` / `import upload <id> -f file` / `import delete <id>` |\n| db_config_read / db_connect | `db list` / `db add <alias> --token - --api-domain host` |\n| db_connect_by_name / db_alias_list / db_alias_new | `db add-by-name <alias>` / `db alias list [--search s]` / `db alias new <alias> \u2026` |\n| db_user_token_set / db_token_rebuild | `db user-token [--from p/alias]` / `db token-rebuild --uid U --pwd-env VAR` |\n| right_system_* / right_function_* | `right system list\\|new <0-9>\\|update <0-9>\\|delete <0-9>` / `right function list\\|new --code N\\|update <id>\\|delete <id>` |\n| db_schema_get / db_schema_read | `schema pull [--prune-local-only]` / `schema [--type T] [--search text]` |\n| db_sp_list / db_sp_get / db_sp_save | `sp list` / `sp get <name> [--print]` / `sp save <name> -f file` |\n| db_sp_deploy / db_sp_help | `sp deploy <name>` / `sp help <name\\|Fn> [--full]` |\n| db_sp_verify / db_sp_delete / db_sp_rename | `sp verify <name\\|Fn> [--params {\u2026}] [--check-grant]` / `sp delete <name> --type Procedure\\|Function` / `sp rename <old> <new>` |\n| db_sql_execute / db_sql_execute_nonquery | `query \"<sql>\"` / `exec \"<sql>\"` (+ `--confirm-write`) |\n| skill_get / skill_reference_get / skill_sync | `skill get` / `skill ref <path...>` / `skill sync` |\n| design_list / design_read | `design list` / `design read <brand>` |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n\n## Modules\n\n1. `fui module get /project/module` (also a live URL or display name).\n - It archives local files that differ, deletes `.vue` files the server no longer has, and makes the module the session target.\n2. Edit the files in `<workspace>/<project>/modules/<module>/`. Running `fui` from inside that folder targets it.\n3. Check, cheapest first:\n - `fui module validate`: fix every error (exit 1 while any remain); it only reads local files.\n - `fui module outline`: the layout tree (grid widths, bindings, actions, dialogs) to confirm the structure is what you meant.\n - `fui module simulate --scenario '[{\"click\":\"Save\"},{\"assert\":\"vueData.list.length > 0\"}]'`: runs the module in a simulated runtime; exit 1 when an assert fails. `--mocks` wins over live data. An unmocked static API may call the real tAPI when its local `.sql` body reads as read-only (max 8 calls, cached in `_testdata/`). For an existing SP take mocks from `fui sp verify`, never invent them.\n - `fui component preview <uc-name> --cases '[{\"name\":\"empty\",\"props\":{\"items\":[]}}]'`: one local `.vue` alone in a real browser; do it before wiring a component in and before publishing a project-scope one.\n - `fui module simulate --render [--device mobile]`: the real page in headless Chromium, last and only for problems that need pixels. Project-level assets come from the server, so publish project files first. It prints screenshot paths: open and read them, then fix what you see. Needs Playwright once: `bun add -g playwright && bunx playwright install chromium`.\n4. Publish:\n - **Whole module, including header/body:** `fui module preview` \u2192 show the changes to the user \u2192 after approval `fui module publish html --approval <token>`.\n - The token breaks if any payload file changes after the preview.\n - **One file:** `fui module publish json|script|css`, `fui component update <name>`, or `fui file push <path...>`, which routes each path to the right publisher.\n - Unchanged files are skipped via the publish ledger.\n5. `fui file status` lists what is still unpublished.\n\n`fui remote module --part ui|html|script|css|load|components|imports` and `fui remote project --part config|modules|\u2026` show the server copy without touching the workspace. `fui remote diff <path...>` compares local files with the server.\n\nAsk the user before `module delete`, `component delete`, `import delete` and `user remove`.\n\n## Data\n\n```bash\nfui query \"SELECT TOP 20 * FROM tblStudent WHERE ClassID = 5\" --db lhu-docs/docs\nfui query -f report.sql --limit 0 # default cap 200 rows; 0 = all\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` first. Every row you read costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --search studentid # compact DDL lines; --search matches the whole line (columns, params)\nfui schema --type API --search Student # Table View API \"API File\" Procedure Function\nfui schema pull # refresh schema.json + every local .sql body\nfui sp list --search Student # columns: db name kind count local\n```\n\n- With no cache, `schema` fails and tells you to run `fui schema pull`. It never pulls on its own.\n- A `# warning: ... last synced N days ago` note means pull before trusting the names.\n- `schema pull` archives and deletes local `.sql` files whose objects were dropped on the server.\n- It only lists drafts that exist in neither schema. `--prune-local-only` archives those too, but only once the user confirms they are stale.\n\n## Stored procedures (MSSQL)\n\n1. `fui sp get <name>` saves the live body to `_db/<alias>/<name>.sql`. A differing local copy is archived first.\n - For a new SP: `fui sp path <name> --db <ref>`, or `fui sp save <name> -f file.sql --db <ref>`.\n2. Edit the file. `CREATE` versus `ALTER` does not matter.\n3. `fui sp deploy <name>`:\n - saves the live body as a rollback copy\n - fixes the verb\n - grants `EXECUTE` to `[public]` on new procedures and checks it\n - clears the tAPI cache\n4. If the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"`.\n\nBefore writing `apiMocks` for an SP that exists, run `fui sp verify <name>`. It reads the real body (never calls the endpoint) and prints the params, the response shape, the error contract, read/write and a mock snippet. `sp delete` and `sp rename` change the server: ask the user first.\n\n## Writes need the user\n\n- Writes to real data exit `3` until you add `--confirm-write`. That covers `INSERT`, `UPDATE`, `DELETE`, `MERGE` and `TRUNCATE` in `query` or `exec`, and `ALTER TABLE ... ALTER COLUMN`.\n- Add `--confirm-write` **only after the user approved that exact write in chat.**\n- Always blocked, with no flag to unlock them. Show the SQL to the user to run by hand:\n - `DROP TABLE`\n - `ALTER TABLE ... DROP COLUMN` or `SWITCH`\n - `DELETE`/`UPDATE` without `WHERE` in `exec`\n- Never write or call a live API SP \"to test\" it. Read its body.\n\n## Workspace files (local, no network)\n\n```bash\nfui file where # workspace, project, module, dirs, and why\nfui file status [--all] # files changed since the last publish/sync, with the command to push each\nfui file history <path> # archived versions in _history, newest first\nfui file restore <path> [--at YYYYMMDD_HHMMSS] # the current file is archived first\nfui file ledger <path...> [--mark|--forget]\n```\n\n## Compose with shell\n\nFilter and loop in the shell. Piped output costs no tokens. Every table also takes `--filter text`, `--where col=value` and `--fields a,b` (applied after the request, so nothing else changes).\n\n```bash\nfui sp list --no-header --type API | cut -f2 # API SP names\nfui sp list --no-header | awk -F'\\t' '$5==\"yes\"{print $2}' # SPs with a local copy\nfui schema --type Procedure | grep -i tblStudent | cut -d' ' -f3 # procedures mentioning a word\nfui sp list --no-header --search Student | cut -f2 | xargs -n1 fui sp get\nfui query \"SELECT TOP 5 ID, Name FROM tblX\" --json | jq -r '.[].Name'\n```\n\n- Check exit codes. An exit `3` inside a loop means ask the user; do not retry.\n- Never put `--confirm-write` in a loop or `xargs`.\n\n## Connections and secrets\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn --user-token-env UT\nfui db add local -p demo --conn-env PG_URL # postgres:// mysql:// sqlite:// connect directly\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # never shows the password\n```\n\nSecrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo or paste token values.\n\n- Never `cat`, `jq` or otherwise print `_db/_connections.json`, `_db/config.json` or MCP config files. Use `fui db list`, `fui token decode` or `fui config`; they never print secrets.\n- No token at hand? `fui db user-token` lists fingerprints of the tokens already in the workspace; copy one with `--from <project/alias>`. The value never appears.\n- Know the alias but not the connection string? `fui db alias list`, then `fui db add-by-name <alias>`: it borrows the SQL login of a connection on the same apiDomain.\n- `right *` and `db alias new` write to the shared acc system. Ask the user first, especially for `delete`.\n- `fui db list` shows a pre-1.4 `_db/config.json` as `engine=legacy`. Migrate it with `fui db import-legacy -p <project>`.\n\n## FUI conventions\n\n- tAPI routes:\n - `spAPI_<Fn>` \u2192 `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` \u2192 `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` \u2192 file endpoints\n- tAPI parameters must not have default values (`@x int = 0`). Deploy blocks them.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. `sp deploy` does it; raw `exec` only reports it.\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";async function su(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Ah={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Ns)}},Nh={name:"skill get",mcpName:"skill_get",usage:"fui skill get",summary:"Print the FUI domain skill (SKILL.md + Tier-A refs with an active target, else the summary); marks it loaded",async run(e){let t=await Ja(await su(e));await e.session.markSkillLoaded(),E(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${t.text}
|
|
351
|
+
`)}},ru=[jh,Ih];import{cp as Rh,mkdir as Ph,rm as Eh}from"fs/promises";import{homedir as Th}from"os";import{join as Xn}from"path";var Ns="---\nname: fui\ndescription: Use the `fui` CLI for FUI (low-code platform) work \u2014 projects, modules (get, validate, preview, publish), components, imports, project users, DB connections, SQL, schema, stored procedures (tAPI spAPI_*), workspace files, publish ledger and history. Prefer it over Fui MCP tools whenever `fui` covers the tool (see the map below); same guards, a fraction of the tokens.\n---\n\n# fui CLI\n\n`fui` is the shell replacement for Fui MCP tools. Run it with your shell tool. It uses the same workspace, connection files, publish ledger, `_history` and error log as Fui MCP, so you can mix the two during the transition.\n\nDomain rules (module.json, controls, tAPI conventions) live in the `fui-skill` skill. Read its `references/` as needed, then run `fui skill loaded`; strict mode (`FUI_MCP_STRICT=1`) blocks deploys until you do.\n\n## Output contract\n\n- stdout is data only: TSV with a header when piped, an aligned table in a terminal, `--json` for one-line JSON, `--no-header` to drop the header.\n- stderr carries `# ...` notes and `error:` / `guard:` lines.\n- Exit codes:\n - `0`: ok, including \"skipped: unchanged\".\n - `1`: runtime error (network or SQL).\n - `2`: usage error. Read the message; it lists the valid choices.\n - `3`: blocked by a guard, or waiting for `--confirm-write`.\n\n## Targets\n\n- **Project and module**, first match wins:\n 1. `-p <project> -m <module>`. Passing only one of them is an error.\n 2. The current directory, if it is inside `<workspace>/<project>/modules/<module>/`.\n 3. The session target.\n- **Session:** one per `FUI_SESSION` (default `default`). Parallel agents should each set their own `FUI_SESSION`.\n- **Connection:**\n - `--db <alias>` or `--db <project>/<alias>`.\n - With no `--db`: the only connection, else one inferred from the object name (`sp` commands), else the default.\n - Writes never use the default when a project has several connections.\n- `fui file where` prints what resolved and why.\n- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.\n- A global install updates itself in the background, at most once an hour. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.\n\n## Fui MCP tool \u2192 fui command\n\nImplemented (same guards, same files, same requests as Fui MCP 1.4.0):\n\n| MCP tool | fui |\n| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| session_set / session_read / session_clear / session_resolve | `use <target>` / `use` / `use --clear` / `resolve <target>` |\n| project_list / project_sync | `project list` / `project sync` |\n| project_new / project_update / project_publish_config | `project new --data {\u2026}` / `project update --data {id:\u2026}` / `project publish-config` |\n| project_user_search / list / new / update / delete | `user search <text>` / `user list` / `user add <UserID> --right N` / `user set \u2026` / `user remove <UserID>` |\n| module_list / new / update / delete | `module list` / `module new <id> --name N` / `module update [--new-id] [--framework] \u2026` / `module delete -p P -m M` |\n| module_get / module_get_ui / module_get_html | `module get [<target>]` / `module get-ui` / `module get-html` |\n| module_validate / module_preview | `module validate` / `module preview` |\n| module_outline / module_simulate | `module outline` / `module simulate [--scenario [...]] [--mocks {...}] [--user {...}] [--url-params {...}]` |\n| module_simulate renderUI / component_preview | `module simulate --render [--device mobile] [--vue-data {...}]` / `component preview <uc-name> [--cases [...]] [--device desktop,mobile]` |\n| error_log_read / error_log_clear | `log read [--tool T] [--limit N\\|0] [--summary]` / `log clear` |\n| module_publish_html / json / script / css | `module publish html --approval <token>` / `module publish json` / `\u2026 script` / `\u2026 css` |\n| component_list / get / new / update / delete | `component list` / `component get <name>` / `component new <name>` / `component update <name>` / `component delete <name>` |\n| file_import_list / get / new / update / upload_content / delete | `import list` / `import get <id>` / `import new \u2026` / `import update <id> \u2026` / `import upload <id> -f file` / `import delete <id>` |\n| db_config_read / db_connect | `db list` / `db add <alias> --token - --api-domain host` |\n| db_connect_by_name / db_alias_list / db_alias_new | `db add-by-name <alias>` / `db alias list [--search s]` / `db alias new <alias> \u2026` |\n| db_user_token_set / db_token_rebuild | `db user-token [--from p/alias]` / `db token-rebuild --uid U --pwd-env VAR` |\n| right_system_* / right_function_* | `right system list\\|new <0-9>\\|update <0-9>\\|delete <0-9>` / `right function list\\|new --code N\\|update <id>\\|delete <id>` |\n| db_schema_get / db_schema_read | `schema pull [--prune-local-only]` / `schema [--type T] [--search text]` |\n| db_sp_list / db_sp_get / db_sp_save | `sp list` / `sp get <name> [--print]` / `sp save <name> -f file` |\n| db_sp_deploy / db_sp_help | `sp deploy <name>` / `sp help <name\\|Fn> [--full]` |\n| db_sp_verify / db_sp_delete / db_sp_rename | `sp verify <name\\|Fn> [--params {\u2026}] [--check-grant]` / `sp delete <name> --type Procedure\\|Function` / `sp rename <old> <new>` |\n| db_sql_execute / db_sql_execute_nonquery | `query \"<sql>\"` / `exec \"<sql>\"` (+ `--confirm-write`) |\n| skill_get / skill_reference_get / skill_sync | `skill get` / `skill ref <path...>` / `skill sync` |\n| design_list / design_read | `design list` / `design read <brand>` |\n\nEvery Fui MCP 1.4.0 tool has a fui command (the SSE-only `workspace_*` and `*_stage` tools are out of scope).\n\n## Modules\n\n1. `fui module get /project/module` (also a live URL or display name).\n - It archives local files that differ, deletes `.vue` files the server no longer has, and makes the module the session target.\n2. Edit the files in `<workspace>/<project>/modules/<module>/`. Running `fui` from inside that folder targets it.\n3. Check, cheapest first:\n - `fui module validate`: fix every error (exit 1 while any remain); it only reads local files.\n - `fui module outline`: the layout tree (grid widths, bindings, actions, dialogs) to confirm the structure is what you meant.\n - `fui module simulate --scenario '[{\"click\":\"Save\"},{\"assert\":\"vueData.list.length > 0\"}]'`: runs the module in a simulated runtime; exit 1 when an assert fails. `--mocks` wins over live data. An unmocked static API may call the real tAPI when its local `.sql` body reads as read-only (max 8 calls, cached in `_testdata/`). For an existing SP take mocks from `fui sp verify`, never invent them.\n - `fui component preview <uc-name> --cases '[{\"name\":\"empty\",\"props\":{\"items\":[]}}]'`: one local `.vue` alone in a real browser; do it before wiring a component in and before publishing a project-scope one.\n - `fui module simulate --render [--device mobile]`: the real page in headless Chromium, last and only for problems that need pixels. Project-level assets come from the server, so publish project files first. It prints screenshot paths: open and read them, then fix what you see. Needs Playwright once: `bun add -g playwright && bunx playwright install chromium`.\n4. Publish:\n - **Whole module, including header/body:** `fui module preview` \u2192 show the changes to the user \u2192 after approval `fui module publish html --approval <token>`.\n - The token breaks if any payload file changes after the preview.\n - **One file:** `fui module publish json|script|css`, `fui component update <name>`, or `fui file push <path...>`, which routes each path to the right publisher.\n - Unchanged files are skipped via the publish ledger.\n5. `fui file status` lists what is still unpublished.\n\n`fui remote module --part ui|html|script|css|load|components|imports` and `fui remote project --part config|modules|\u2026` show the server copy without touching the workspace. `fui remote diff <path...>` compares local files with the server.\n\nAsk the user before `module delete`, `component delete`, `import delete` and `user remove`.\n\n## Data\n\n```bash\nfui query \"SELECT TOP 20 * FROM tblStudent WHERE ClassID = 5\" --db lhu-docs/docs\nfui query -f report.sql --limit 0 # default cap 200 rows; 0 = all\necho \"SELECT 1 AS x\" | fui query -\n```\n\nNarrow with `TOP`/`WHERE` first. Every row you read costs tokens.\n\n## Structure (MSSQL)\n\n```bash\nfui schema --search studentid # compact DDL lines; --search matches the whole line (columns, params)\nfui schema --type API --search Student # Table View API \"API File\" Procedure Function\nfui schema pull # refresh schema.json + every local .sql body\nfui sp list --search Student # columns: db name kind count local\n```\n\n- With no cache, `schema` fails and tells you to run `fui schema pull`. It never pulls on its own.\n- A `# warning: ... last synced N days ago` note means pull before trusting the names.\n- `schema pull` archives and deletes local `.sql` files whose objects were dropped on the server.\n- It only lists drafts that exist in neither schema. `--prune-local-only` archives those too, but only once the user confirms they are stale.\n\n## Stored procedures (MSSQL)\n\n1. `fui sp get <name>` saves the live body to `_db/<alias>/<name>.sql`. A differing local copy is archived first.\n - For a new SP: `fui sp path <name> --db <ref>`, or `fui sp save <name> -f file.sql --db <ref>`.\n2. Edit the file. `CREATE` versus `ALTER` does not matter.\n3. `fui sp deploy <name>`:\n - saves the live body as a rollback copy\n - fixes the verb\n - grants `EXECUTE` to `[public]` on new procedures and checks it\n - clears the tAPI cache\n4. If the grant failed, do **not** redeploy. Run the printed `fui exec \"GRANT ...\"`.\n\nBefore writing `apiMocks` for an SP that exists, run `fui sp verify <name>`. It reads the real body (never calls the endpoint) and prints the params, the response shape, the error contract, read/write and a mock snippet. `sp delete` and `sp rename` change the server: ask the user first.\n\n## Writes need the user\n\n- Writes to real data exit `3` until you add `--confirm-write`. That covers `INSERT`, `UPDATE`, `DELETE`, `MERGE` and `TRUNCATE` in `query` or `exec`, and `ALTER TABLE ... ALTER COLUMN`.\n- Add `--confirm-write` **only after the user approved that exact write in chat.**\n- Always blocked, with no flag to unlock them. Show the SQL to the user to run by hand:\n - `DROP TABLE`\n - `ALTER TABLE ... DROP COLUMN` or `SWITCH`\n - `DELETE`/`UPDATE` without `WHERE` in `exec`\n- Never write or call a live API SP \"to test\" it. Read its body.\n\n## Workspace files (local, no network)\n\n```bash\nfui file where # workspace, project, module, dirs, and why\nfui file status [--all] # files changed since the last publish/sync, with the command to push each\nfui file history <path> # archived versions in _history, newest first\nfui file restore <path> [--at YYYYMMDD_HHMMSS] # the current file is archived first\nfui file ledger <path...> [--mark|--forget]\n```\n\n## Compose with shell\n\nFilter and loop in the shell. Piped output costs no tokens. Every table also takes `--filter text`, `--where col=value` and `--fields a,b` (applied after the request, so nothing else changes).\n\n```bash\nfui sp list --no-header --type API | cut -f2 # API SP names\nfui sp list --no-header | awk -F'\\t' '$5==\"yes\"{print $2}' # SPs with a local copy\nfui schema --type Procedure | grep -i tblStudent | cut -d' ' -f3 # procedures mentioning a word\nfui sp list --no-header --search Student | cut -f2 | xargs -n1 fui sp get\nfui query \"SELECT TOP 5 ID, Name FROM tblX\" --json | jq -r '.[].Name'\n```\n\n- Check exit codes. An exit `3` inside a loop means ask the user; do not retry.\n- Never put `--confirm-write` in a loop or `xargs`.\n\n## Connections and secrets\n\n```bash\nprintf %s \"$TOKEN\" | fui db add docs -p lhu-docs --token - --api-domain tapi.lhu.edu.vn --user-token-env UT\nfui db add local -p demo --conn-env PG_URL # postgres:// mysql:// sqlite:// connect directly\nfui db default lhu-docs/docs\nfui db test --db lhu-docs/docs\nfui token decode --db lhu-docs/docs # never shows the password\n```\n\nSecrets are accepted only from stdin (`-`) or `--*-env VAR`. Never echo or paste token values.\n\n- Never `cat`, `jq` or otherwise print `_db/_connections.json`, `_db/config.json` or MCP config files. Use `fui db list`, `fui token decode` or `fui config`; they never print secrets.\n- No token at hand? `fui db user-token` lists fingerprints of the tokens already in the workspace; copy one with `--from <project/alias>`. The value never appears.\n- Know the alias but not the connection string? `fui db alias list`, then `fui db add-by-name <alias>`: it borrows the SQL login of a connection on the same apiDomain.\n- `right *` and `db alias new` write to the shared acc system. Ask the user first, especially for `delete`.\n- `fui db list` shows a pre-1.4 `_db/config.json` as `engine=legacy`. Migrate it with `fui db import-legacy -p <project>`.\n\n## FUI conventions\n\n- tAPI routes:\n - `spAPI_<Fn>` \u2192 `/<alias>/<Fn>`\n - `spAPI_AUTH_<Fn>` \u2192 `/<alias>/auth/<Fn>`\n - `spAPIFILE_*` \u2192 file endpoints\n- tAPI parameters must not have default values (`@x int = 0`). Deploy blocks them.\n- Every new procedure needs `GRANT EXECUTE ... TO [public]`. `sp deploy` does it; raw `exec` only reports it.\n\n`fui --help` lists commands. `fui <command> --help` shows usage for one command.\n";async function su(e){if(e.targets.cwdTarget())return e.paths.root;return(await e.session.getTarget())?.workspaceRoot}var Ah={name:"skill",usage:"fui skill",summary:"Print the fui CLI agent guide",async run(){k(Ns)}},Nh={name:"skill get",mcpName:"skill_get",usage:"fui skill get",summary:"Print the FUI domain skill (SKILL.md + Tier-A refs with an active target, else the summary); marks it loaded",async run(e){let t=await Ja(await su(e));await e.session.markSkillLoaded(),E(`# source: ${t.source}; more: fui skill ref <references/x.md ...>`),k(`${t.text}
|
|
352
352
|
`)}},_h={name:"skill ref",mcpName:"skill_reference_get",usage:"fui skill ref <path> [path...] e.g. references/db-workflow.md",summary:"Print skill reference files; marks the skill loaded",async run(e){if(e.positionals.length===0)throw new f("missing <path>; see references/INDEX.md");await e.session.markSkillLoaded();let t=await su(e),n=t?Xn(t,"_skills",Dt):await sn();E(`# skill dir: ${n}`),k(`${(await Va(n,e.positionals)).trimStart()}
|
|
353
353
|
`)}},Dh={name:"skill loaded",usage:"fui skill loaded",summary:"Record that the skill was read (after reading installed skill files yourself); satisfies strict-mode gates",async run({session:e}){await e.markSkillLoaded(),k(`skill marked loaded for this session
|
|
354
354
|
`)}},xh={name:"skill sync",mcpName:"skill_sync",usage:"fui skill sync",summary:"Re-copy the bundled fui-skill into <workspace>/_skills/",async run({paths:e}){let t=await Io(e.root);k(`${t.skillDir} (${t.source})
|
package/package.json
CHANGED
package/skills/fui/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ Domain rules (module.json, controls, tAPI conventions) live in the `fui-skill` s
|
|
|
32
32
|
- Writes never use the default when a project has several connections.
|
|
33
33
|
- `fui file where` prints what resolved and why.
|
|
34
34
|
- `fui config` shows the workspace and API settings with their sources. They inherit from the Fui MCP entry in `~/.claude.json`.
|
|
35
|
-
- A global install updates itself in the background, at most once
|
|
35
|
+
- A global install updates itself in the background, at most once an hour. A line starting `# fui updated` means a new version is running; run `fui skill install` if the skills were installed as files. `fui --version` prints the version.
|
|
36
36
|
|
|
37
37
|
## Fui MCP tool → fui command
|
|
38
38
|
|