@fui-org/fui-cli 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,7 +72,7 @@ ${t}`,e.severity==="block"?"hard":"soft")}function Va(e,t){let n=Ra(t.schema),o=
72
72
  ${u.message}
73
73
  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 xo(e,n,{alias:t.apiName,connectionName:t.connectionName,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 ic(e,t){let n=await Bn(e),o=await _r(e.store,n,t),{apiDomain:s,userToken:r}=o.config;return{projectId:n,call:(i,a)=>Un(s,r,i,a),target:{ModuleID:0,APIName:t??o.config.apiName??o.alias}}}async function ac(e,t,n){let{projectId:o,call:s,target:r}=await ic(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await Lr(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function cc(e,t,n){let{projectId:o,call:s,target:r}=await ic(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new h(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await Lr(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 ls={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function us(e){return{apiDomain:f(e.values,"api-domain"),userToken:await Te(f(e.values,"user-token"),f(e.values,"user-token-env"),"--user-token")}}function Jn(e){b(`${e.join(`
74
74
  `)}
75
- `)}var cm={name:"db user-token",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 Te(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new h("pass --from or --token/--token-env, not both");let o=await nc(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},lm={name:"db token-rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=f(e.values,"uid");if(!t)throw new h("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new h("missing --pwd - or --pwd-env VAR");Jn((await tc(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},um={name:"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"},...ls},async run(e){let t=await oc(await X(e),{...await us(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)b(z(t.rows,e.render))}},dm={name:"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"},...ls},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new h("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new h("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new h("--module-id must be an integer");let u=await rc(await X(e),{...await us(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},pm={name:"db add-by-name",usage:"fui db add-by-name <apiName> [-p P] [--name connectionName] [--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:{name:{type:"string"},"borrow-from":{type:"string"},default:{type:"boolean"},...ls},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <apiName>; see: fui db alias list");let n=await sc(await X(e),{...await us(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function dc(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function pc(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 mc={"api-name":{type:"string"}};function cs(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new h(`${t} must be ${o}`);return s}function lc(e,t,n){if(e!==void 0&&e.length>t)throw new h(`${n} is at most ${t} characters`);return e}function uc(e){return{name:`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:mc,async run(t){let{apiName:n,rows:o}=await ac(await X(t),e,f(t.values,"api-name"));if(T(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){T(`# none defined; create one: fui right ${e} new ...`);return}b(z(e==="system"?dc(o):pc(o),t.render))}}}function cn(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}`,usage:n,summary:o[e][t],options:{...mc,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:cs(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:lc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:cs(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:cs(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:lc(f(r,"name"),100,"--name")}}let c=await cc(await X(s),a,f(r,"api-name"));Jn(c.notes),b(z(e==="system"?dc(c.rows):pc(c.rows),s.render))}}}var hc=[cm,lm,um,dm,pm,uc("system"),cn("system","new"),cn("system","update"),cn("system","delete"),uc("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as Sm}from"node:fs/promises";import{execFile as hm,spawn as fm}from"node:child_process";import{realpathSync as gm}from"node:fs";import{join as fc,sep as kt}from"node:path";var le="2.1.2";var _o="@fui-org/fui-cli",wm=3600000,ym=5000;function gc(e=process.env){return fc(rt(e),"..","update.json")}async function Kn(e=process.env){try{let t=await Yt(gc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ut(e,t=process.env){await L(gc(t),`${JSON.stringify(e,null,2)}
75
+ `)}var cm={name:"db user-token",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 Te(f(e.values,"token"),f(e.values,"token-env"),"--token"),n=f(e.values,"from");if(n&&t)throw new h("pass --from or --token/--token-env, not both");let o=await nc(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},lm={name:"db token-rebuild",usage:"fui db token-rebuild [--db ref] --uid <login> (--pwd - | --pwd-env VAR)",summary:"Rebuild a connection's dbToken after a UID/PWD change (sqlServer/database from the saved connection), test, save",options:{uid:{type:"string"},pwd:{type:"string"},"pwd-env":{type:"string"}},async run(e){let t=f(e.values,"uid");if(!t)throw new h("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new h("missing --pwd - or --pwd-env VAR");Jn((await tc(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},um={name:"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"},...ls},async run(e){let t=await oc(await X(e),{...await us(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)b(z(t.rows,e.render))}},dm={name:"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"},...ls},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new h("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new h("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new h("--module-id must be an integer");let u=await rc(await X(e),{...await us(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},pm={name:"db add-by-name",usage:"fui db add-by-name <apiName> [-p P] [--name connectionName] [--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:{name:{type:"string"},"borrow-from":{type:"string"},default:{type:"boolean"},...ls},async run(e){let t=e.positionals[0];if(!t)throw new h("missing <apiName>; see: fui db alias list");let n=await sc(await X(e),{...await us(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function dc(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function pc(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 mc={"api-name":{type:"string"}};function cs(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new h(`${t} must be ${o}`);return s}function lc(e,t,n){if(e!==void 0&&e.length>t)throw new h(`${n} is at most ${t} characters`);return e}function uc(e){return{name:`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:mc,async run(t){let{apiName:n,rows:o}=await ac(await X(t),e,f(t.values,"api-name"));if(T(`# ${e==="system"?"SystemRight":"FunctionRight"} of "${n}": ${o.length}`),o.length===0){T(`# none defined; create one: fui right ${e} new ...`);return}b(z(e==="system"?dc(o):pc(o),t.render))}}}function cn(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}`,usage:n,summary:o[e][t],options:{...mc,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:cs(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:lc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:cs(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:cs(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:lc(f(r,"name"),100,"--name")}}let c=await cc(await X(s),a,f(r,"api-name"));Jn(c.notes),b(z(e==="system"?dc(c.rows):pc(c.rows),s.render))}}}var hc=[cm,lm,um,dm,pm,uc("system"),cn("system","new"),cn("system","update"),cn("system","delete"),uc("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as Sm}from"node:fs/promises";import{execFile as hm,spawn as fm}from"node:child_process";import{realpathSync as gm}from"node:fs";import{join as fc,sep as kt}from"node:path";var le="2.1.3";var _o="@fui-org/fui-cli",wm=3600000,ym=5000;function gc(e=process.env){return fc(rt(e),"..","update.json")}async function Kn(e=process.env){try{let t=await Yt(gc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ut(e,t=process.env){await L(gc(t),`${JSON.stringify(e,null,2)}
76
76
  `)}var bm=`${kt}node_modules${kt}@fui-org${kt}fui-cli${kt}`,km=[[`${kt}install${kt}global`,"bun",!1],[`${kt}lib`,"npm",!1],[`${kt}npm`,"npm",!0]];function Lo(e=ht()){let t;try{t=gm(e)}catch{return null}for(let[n,o,s]of km){let r=`${n}${bm}`,i=t.indexOf(r);if(i>=0){let a=t.slice(0,s?i+n.length:i);return{dir:t.slice(0,i+r.length-1),manager:o,prefix:a}}}return null}function Fo(e=ht()){return Lo(e)?.dir??null}function Mo(e,t){return e==="bun"?`bun add -g ${_o}@${t}`:`npm install -g ${_o}@${t}`}function ds(e,t,n=process.env){return new Promise((o)=>{hm(e,t,{env:n,shell:process.platform==="win32",maxBuffer:16777216,windowsHide:!0},(s,r,i)=>o({ok:!s,output:`${r}${i}`.trim()}))})}async function ps(e=process.env){let t=e.FUI_AUTO_UPDATE?.trim().toLowerCase();if(t)return!["0","off","false","no"].includes(t);try{return(await gt(e))["auto-update"]!=="off"}catch{return!0}}async function ms(e=process.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${_o.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(ym),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 hs(e,t=le){return Ai(e,t)===1}async function vm(e,t){let n=`${_o}@${t}`;if(e.manager==="npm")return ds("npm",["install","-g","--prefix",e.prefix,n]);return ds("bun",["add","-g",n],{...process.env,BUN_INSTALL:e.prefix})}async function fs(e=process.env){let t=await Kn(e),n=new Date().toISOString(),o;try{o=await ms(e)}catch(i){throw await ut({...t,checkedAt:n,error:i.message},e),i}if(!hs(o))return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};let s=Lo();if(!s)return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`${Mo("bun",o)} (or ${Mo("npm",o)})`};let r=await vm(s,o);if(!r.ok)return await ut({...t,checkedAt:n,latest:o,error:r.output.slice(-500)},e),{latest:o,action:"failed",detail:r.output};return await ut({checkedAt:n,latest:o,updatedFrom:le,updatedTo:o,noticeShown:!1,skillsVersion:t.skillsVersion,skillsAgents:t.skillsAgents},e),await $m(e),{latest:o,action:"installed"}}async function $m(e){let t=Fo();if(!t)return;await ds(process.execPath,[fc(t,"dist","fui.js"),"skill","install"],{...e,FUI_AUTO_SKILLS:"off",FUI_AUTO_UPDATE:"off"})}async function wc(e=process.env){if(!Fo()||!await ps(e))return null;let t=await Kn(e),n=null;if(t.updatedTo===le&&t.noticeShown===!1)n=`# fui updated ${t.updatedFrom} -> ${le}`,t.noticeShown=!0,await ut(t,e);let o=t.checkedAt?Date.parse(t.checkedAt):0;if(Date.now()-o<wm)return n;return await ut({...t,checkedAt:new Date().toISOString()},e),fm(process.execPath,[ht(),"update","--background"],{stdio:"ignore",env:{...e,FUI_AUTO_UPDATE:"off"},detached:!0,windowsHide:!0}).unref(),n}function yc(e){let t=e[0];if(!t)throw new h(`missing <key>; keys: ${wo.join(", ")}`);if(!wo.includes(t))throw new h(`unknown key "${t}"; keys: ${wo.join(", ")}`);return t}async function jm(){let e=await ps(),t=process.env.FUI_AUTO_UPDATE?.trim(),n=(await gt())["auto-update"],o=t?"env":n?"config":"default",s=t?"FUI_AUTO_UPDATE":n?rt():"built-in (global bun or npm installs only)";return{key:"auto-update",value:e?"on":"off",source:o,from:s}}var Im={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 yo();T(`# config file: ${rt()}`),b(z([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await jm()],t))}},Cm={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=yc(e),n=e[1];if(!n)throw new h(`missing <value> for ${t}`);let o;if(t==="workdir")o=An(n),await Sm(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new h("auto-update takes on or off");o=n}else if(t==="api-token")o=await Te(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await gt();s[t]=o;let r=await $r(s),i={workdir:"FUI_WORKDIR","api-base":"FUI_API_BASE","api-token":"FUI_API_TOKEN","auto-update":"FUI_AUTO_UPDATE"}[t];if(process.env[i])T(`# note: ${i} is set in this shell and overrides the config file`);b(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
77
77
  `)}},Rm={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=yc(e),n=await gt();delete n[t];let o=await $r(n);b(`unset ${t} in ${o}
78
78
  `)}},bc=[Im,Cm,Rm];var Tm={name:"db list",usage:"fui db list [-p project]",summary:"List connections across the workspace (no secrets)",async run({store:e,values:t,render:n}){let o=f(t,"project"),s=o?[jo(o)]:await e.listProjects(),r=[];for(let i of s){let a;try{a=await e.load(i)}catch(c){T(`# ${i}: ${c.message}`);continue}for(let[c,l]of Object.entries(a.connections)){let u="invalid",d=l.database??"";try{if(l.dbToken){let p=tn(l.dbToken);u=p.engine,d||=p.database}}catch{}r.push({db:`${i}/${c}`,engine:u,default:a.default===c?"*":"",apiDomain:l.apiDomain??"",database:d,userToken:l.userToken?"yes":"no",apiName:l.apiName??c})}}if(r.length===0){T("no connections; add one: fui db add <apiName> -p <project> --token -");return}b(z(r,n))}},Pm={name:"db add",usage:`fui db add <apiName> -p <project> (--token - | --token-env VAR | --conn - | --conn-env VAR)
package/dist/fui.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-x86j3m2z.js");await e();
2
+ import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-hk5frcn2.js");await e();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fui-org/fui-cli",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Token-lean CLI for the FUI low-code platform (Node.js)",
5
5
  "keywords": [
6
6
  "ai",
@@ -35,8 +35,10 @@ async function runFui($: Engine, args: string[], session: string | undefined) {
35
35
  }
36
36
 
37
37
  // Runs in the background: the numbers land on the line, never in the model's context.
38
- async function remoteDiff($: Engine, session: string | undefined): Promise<FuiDiff | undefined> {
39
- const { exitCode, stdout } = await runFui($, ["remote", "diff", "--stat", "--json"], session).catch(() => ({
38
+ async function remoteDiff($: Engine, current: FuiTarget): Promise<FuiDiff | undefined> {
39
+ const args = ["remote", "diff", "--stat", "--json"];
40
+ if (current.project && current.module) args.push("-p", current.project, "-m", current.module);
41
+ const { exitCode, stdout } = await runFui($, args, session).catch(() => ({
40
42
  exitCode: 1,
41
43
  stdout: "",
42
44
  }));
@@ -100,9 +102,7 @@ async function projectUrl($: Engine, session: SessionTarget) {
100
102
  return httpsUrl(firstDomain(info?.pDomain));
101
103
  }
102
104
 
103
- async function sync($: Engine, command: string, lastCommand: string) {
104
- const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
105
- const session = state?.target;
105
+ async function describe($: Engine, session: SessionTarget | undefined, lastCommand?: string): Promise<FuiTarget> {
106
106
  const found: FuiTarget = { lastCommand };
107
107
  if (session?.projectId) {
108
108
  found.project = session.projectId;
@@ -111,10 +111,33 @@ async function sync($: Engine, command: string, lastCommand: string) {
111
111
  found.projectUrl = await projectUrl($, session);
112
112
  found.moduleUrl = session.moduleId ? await moduleUrl($, session) : undefined;
113
113
  }
114
- await update($, target, () => found);
114
+ return found;
115
+ }
116
+
117
+ async function readSessionTarget($: Engine, command: string) {
118
+ const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
119
+ return state?.target;
120
+ }
121
+
122
+ // A folder holding _db or _projectInfo.json is a fui project; <project>/modules/<module> inside it is a module.
123
+ async function folderTarget($: Engine, cwd: string): Promise<SessionTarget | undefined> {
124
+ const parts = cwd.replace(/\/+$/, "").split("/");
125
+ for (let end = parts.length; end > 1; end--) {
126
+ const projectDir = parts.slice(0, end).join("/");
127
+ if (!(await $.fs.exists(`${projectDir}/_db`)) && !(await $.fs.exists(`${projectDir}/_projectInfo.json`))) continue;
128
+ const info = (await readJson($, `${projectDir}/_projectInfo.json`)) as { ProjectID?: unknown } | undefined;
129
+ const projectId = typeof info?.ProjectID === "string" ? info.ProjectID : parts[end - 1];
130
+ const workspaceRoot = parts.slice(0, end - 1).join("/");
131
+ const moduleId = parts[end] === "modules" ? parts[end + 1] : undefined;
132
+ if (moduleId) return { projectId, moduleId, moduleDir: `${projectDir}/modules/${moduleId}`, workspaceRoot };
133
+ const active = await readSessionTarget($, "");
134
+ return active?.projectId === projectId ? active : { projectId, workspaceRoot };
135
+ }
136
+ return undefined;
115
137
  }
116
138
 
117
139
  let session: string | undefined;
140
+ let isPinned = false;
118
141
  let isStale = false;
119
142
  let running: Promise<void> | undefined;
120
143
 
@@ -129,7 +152,8 @@ function refreshDiff($: Engine) {
129
152
  do {
130
153
  isStale = false;
131
154
  await showDiff($, "pending");
132
- await showDiff($, await remoteDiff($, session));
155
+ const current: FuiTarget | null = await read($, target);
156
+ if (current?.moduleDir) await showDiff($, await remoteDiff($, current));
133
157
  } while (isStale);
134
158
  })().finally(() => {
135
159
  running = undefined;
@@ -137,8 +161,21 @@ function refreshDiff($: Engine) {
137
161
  }
138
162
 
139
163
  export const register: Register = (on) => {
164
+ on("session.start", async ($, e, next) => {
165
+ const started = await next(e);
166
+ const folder = await folderTarget($, e.cwd);
167
+ isPinned = folder !== undefined;
168
+ if (folder) {
169
+ await update($, target, () => null);
170
+ const found = await describe($, folder);
171
+ await update($, target, () => found);
172
+ refreshDiff($);
173
+ }
174
+ return started;
175
+ });
176
+
140
177
  on("prompt.submit", async ($, e, next) => {
141
- await update($, target, () => null);
178
+ if (!isPinned) await update($, target, () => null);
142
179
  return next(e);
143
180
  });
144
181
 
@@ -164,7 +201,12 @@ export const register: Register = (on) => {
164
201
  if (!call) return result;
165
202
 
166
203
  session = sessionKey(e.command);
167
- await sync($, e.command, [call[1], call[2]].filter(Boolean).join(" "));
204
+ const found = await describe(
205
+ $,
206
+ await readSessionTarget($, e.command),
207
+ [call[1], call[2]].filter(Boolean).join(" "),
208
+ );
209
+ await update($, target, () => found);
168
210
  if (!(call[1] === "remote" && call[2] === "diff")) refreshDiff($);
169
211
  return result;
170
212
  });