@fui-org/fui-cli 2.1.0 → 2.1.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.
@@ -72,7 +72,7 @@ ${t}`,e.severity==="block"?"hard":"soft")}function Fa(e,t){let n=Sa(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 Ao(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 nc(e,t){let n=await Bn(e),o=await xr(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 oc(e,t,n){let{projectId:o,call:s,target:r}=await nc(e,n),a=(await s(t==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",r)).data??[];return await _r(e.store.paths,o,r.APIName,t==="system"?{system:a}:{functions:a}),{apiName:r.APIName,rows:a}}async function rc(e,t,n){let{projectId:o,call:s,target:r}=await nc(e,n),i=t.kind==="system"?"SM_SystemRight_Select":"SM_FunctionRight_Select",a;if(t.kind==="system"){if(t.mode==="new"){let d=((await s(i,r)).data??[]).find((p)=>Number(p.SysRight)===t.sysRight);if(d)throw new m(`SystemRight ${t.sysRight} of "${r.APIName}" is already defined ("${String(d.Note??"")}"); edit: fui right system update ${t.sysRight} --note ...; remove: fui right system delete ${t.sysRight}`)}await s("SM_SystemRight_Update",{...r,SysRight:t.sysRight,Note:t.note??null,Delete:t.mode==="delete"?1:0});let u=`SystemRight ${t.sysRight}${t.note&&t.mode!=="delete"?` (${t.note})`:""}`;a=`ok ${t.mode==="new"?"created":t.mode==="update"?"saved":"deleted"} ${u} of "${r.APIName}"`}else await s("SM_FunctionRight_Update",{...r,FunctionID:t.functionId,FunctionCode:t.functionCode,FunctionName:t.functionName??null,Delete:t.mode==="delete"?1:0}),a=t.mode==="new"?`ok created FunctionRight code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:t.mode==="update"?`ok saved FunctionRight ID ${t.functionId}: code ${t.functionCode}${t.functionName?` (${t.functionName})`:""} for "${r.APIName}"`:`ok deleted FunctionRight ID ${t.functionId} and its user assignments`;let c=await s(i,r).catch(()=>null),l=c?.data??[];if(c)await _r(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 is={"api-domain":{type:"string"},"user-token":{type:"string"},"user-token-env":{type:"string"}};async function as(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){k(`${e.join(`
74
74
  `)}
75
- `)}var tm={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 m("pass --from or --token/--token-env, not both");let o=await Qa(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},nm={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 m("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new m("missing --pwd - or --pwd-env VAR");Jn((await Ya(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},om={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"},...is},async run(e){let t=await Za(await X(e),{...await as(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)k(z(t.rows,e.render))}},rm={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"},...is},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new m("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new m("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new m("--module-id must be an integer");let u=await ec(await X(e),{...await as(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},sm={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"},...is},async run(e){let t=e.positionals[0];if(!t)throw new m("missing <apiName>; see: fui db alias list");let n=await tc(await X(e),{...await as(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function ac(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function cc(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 lc={"api-name":{type:"string"}};function ss(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new m(`${t} must be ${o}`);return s}function sc(e,t,n){if(e!==void 0&&e.length>t)throw new m(`${n} is at most ${t} characters`);return e}function ic(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:lc,async run(t){let{apiName:n,rows:o}=await oc(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}k(z(e==="system"?ac(o):cc(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:{...lc,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:ss(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:sc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:ss(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:ss(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:sc(f(r,"name"),100,"--name")}}let c=await rc(await X(s),a,f(r,"api-name"));Jn(c.notes),k(z(e==="system"?ac(c.rows):cc(c.rows),s.render))}}}var uc=[tm,nm,om,rm,sm,ic("system"),cn("system","new"),cn("system","update"),cn("system","delete"),ic("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as gm}from"node:fs/promises";import{execFile as am,spawn as cm}from"node:child_process";import{realpathSync as lm}from"node:fs";import{join as dc,sep as kt}from"node:path";var le="2.1.0";var xo="@fui-org/fui-cli",um=3600000,dm=5000;function pc(e=process.env){return dc(rt(e),"..","update.json")}async function Kn(e=process.env){try{let t=await Yt(pc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ut(e,t=process.env){await F(pc(t),`${JSON.stringify(e,null,2)}
75
+ `)}var tm={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 m("pass --from or --token/--token-env, not both");let o=await Qa(await X(e),{db:f(e.values,"db"),userToken:t,tokenSource:n});Jn(o.notes)}},nm={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 m("missing --uid");let n=await Te(f(e.values,"pwd"),f(e.values,"pwd-env"),"--pwd");if(!n)throw new m("missing --pwd - or --pwd-env VAR");Jn((await Ya(await X(e),{db:f(e.values,"db"),uid:t,password:n})).notes)}},om={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"},...is},async run(e){let t=await Za(await X(e),{...await as(e),search:f(e.values,"search")});for(let n of t.notes)T(`# ${n}`);if(t.rows.length>0)k(z(t.rows,e.render))}},rm={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"},...is},async run(e){let{values:t}=e,n=e.positionals[0],o=f(t,"module-name"),s=f(t,"sql-server"),r=f(t,"database"),i=f(t,"username");if(!n||!o||!s||!r||!i)throw new m("need <apiName>, --module-name, --sql-server, --database and --username");let a=await Te(f(t,"password"),f(t,"password-env"),"--password");if(!a)throw new m("missing --password - or --password-env VAR");let c=f(t,"module-id"),l=c===void 0?void 0:Number(c);if(l!==void 0&&!Number.isInteger(l))throw new m("--module-id must be an integer");let u=await ec(await X(e),{...await as(e),apiName:n,moduleName:o,sqlServer:s,database:r,username:i,password:a,moduleId:l});Jn(u.notes)}},sm={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"},...is},async run(e){let t=e.positionals[0];if(!t)throw new m("missing <apiName>; see: fui db alias list");let n=await tc(await X(e),{...await as(e),apiName:t,connectionName:f(e.values,"name"),borrowFrom:f(e.values,"borrow-from"),makeDefault:e.values.default===!0});Jn(n.notes)}};function ac(e){return e.map((t)=>({sysRight:String(t.SysRight??""),note:String(t.Note??"")}))}function cc(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 lc={"api-name":{type:"string"}};function ss(e,t,n,o){let s=e===void 0?Number.NaN:Number(e);if(!Number.isInteger(s)||!n(s))throw new m(`${t} must be ${o}`);return s}function sc(e,t,n){if(e!==void 0&&e.length>t)throw new m(`${n} is at most ${t} characters`);return e}function ic(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:lc,async run(t){let{apiName:n,rows:o}=await oc(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}k(z(e==="system"?ac(o):cc(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:{...lc,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:ss(i[0],"<level>",(l)=>l>=0&&l<=9,"an integer 0-9"),note:t==="delete"?void 0:sc(f(r,"note"),50,"--note")};else{let l=f(r,"code");a={kind:e,mode:t,functionId:t==="new"?0:ss(i[0],"<functionId>",(u)=>u>0,"a positive integer"),functionCode:t==="delete"&&l===void 0?0:ss(l,"--code",()=>!0,"an integer"),functionName:t==="delete"?void 0:sc(f(r,"name"),100,"--name")}}let c=await rc(await X(s),a,f(r,"api-name"));Jn(c.notes),k(z(e==="system"?ac(c.rows):cc(c.rows),s.render))}}}var uc=[tm,nm,om,rm,sm,ic("system"),cn("system","new"),cn("system","update"),cn("system","delete"),ic("function"),cn("function","new"),cn("function","update"),cn("function","delete")];import{mkdir as gm}from"node:fs/promises";import{execFile as am,spawn as cm}from"node:child_process";import{realpathSync as lm}from"node:fs";import{join as dc,sep as kt}from"node:path";var le="2.1.1";var xo="@fui-org/fui-cli",um=3600000,dm=5000;function pc(e=process.env){return dc(rt(e),"..","update.json")}async function Kn(e=process.env){try{let t=await Yt(pc(e));if(t&&typeof t==="object"&&!Array.isArray(t))return t}catch{}return{}}async function ut(e,t=process.env){await F(pc(t),`${JSON.stringify(e,null,2)}
76
76
  `)}var pm=`${kt}node_modules${kt}@fui-org${kt}fui-cli${kt}`,mm=[[`${kt}install${kt}global`,"bun",!1],[`${kt}lib`,"npm",!1],[`${kt}npm`,"npm",!0]];function _o(e=ht()){let t;try{t=lm(e)}catch{return null}for(let[n,o,s]of mm){let r=`${n}${pm}`,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 Mo(e=ht()){return _o(e)?.dir??null}function Oo(e,t){return e==="bun"?`bun add -g ${xo}@${t}`:`npm install -g ${xo}@${t}`}function cs(e,t,n=process.env){return new Promise((o)=>{am(e,t,{env:n,shell:process.platform==="win32",maxBuffer:16777216,windowsHide:!0},(s,r,i)=>o({ok:!s,output:`${r}${i}`.trim()}))})}async function ls(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 us(e=process.env){let t=(e.FUI_UPDATE_REGISTRY||"https://registry.npmjs.org").replace(/\/+$/,""),n=await fetch(`${t}/${xo.replace("/","%2F")}/latest`,{signal:AbortSignal.timeout(dm),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 ds(e,t=le){return Ci(e,t)===1}async function hm(e,t){let n=`${xo}@${t}`;if(e.manager==="npm")return cs("npm",["install","-g","--prefix",e.prefix,n]);return cs("bun",["add","-g",n],{...process.env,BUN_INSTALL:e.prefix})}async function ps(e=process.env){let t=await Kn(e),n=new Date().toISOString(),o;try{o=await us(e)}catch(i){throw await ut({...t,checkedAt:n,error:i.message},e),i}if(!ds(o))return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"current"};let s=_o();if(!s)return await ut({...t,checkedAt:n,latest:o,error:void 0},e),{latest:o,action:"manual",detail:`${Oo("bun",o)} (or ${Oo("npm",o)})`};let r=await hm(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 fm(e),{latest:o,action:"installed"}}async function fm(e){let t=Mo();if(!t)return;await cs(process.execPath,[dc(t,"dist","fui.js"),"skill","install"],{...e,FUI_AUTO_SKILLS:"off",FUI_AUTO_UPDATE:"off"})}async function mc(e=process.env){if(!Mo()||!await ls(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<um)return n;return await ut({...t,checkedAt:new Date().toISOString()},e),cm(process.execPath,[ht(),"update","--background"],{stdio:"ignore",env:{...e,FUI_AUTO_UPDATE:"off"},detached:!0,windowsHide:!0}).unref(),n}function hc(e){let t=e[0];if(!t)throw new m(`missing <key>; keys: ${go.join(", ")}`);if(!go.includes(t))throw new m(`unknown key "${t}"; keys: ${go.join(", ")}`);return t}async function wm(){let e=await ls(),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 ym={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 wo();T(`# config file: ${rt()}`),k(z([{key:"workdir",value:e.path,source:e.source,from:e.detail},{key:"api-base",value:n.value??"",source:n.source,from:n.detail},{key:"api-token",value:o.value?"(set)":"",source:o.source,from:o.detail},await wm()],t))}},bm={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=hc(e),n=e[1];if(!n)throw new m(`missing <value> for ${t}`);let o;if(t==="workdir")o=Nn(n),await gm(o,{recursive:!0});else if(t==="auto-update"){if(n!=="on"&&n!=="off")throw new m("auto-update takes on or off");o=n}else if(t==="api-token")o=await Te(n,void 0,"api-token");else o=n.trim().replace(/\/+$/,"");let s=await gt();s[t]=o;let r=await kr(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`);k(`set ${t}${t==="api-token"?"":`=${o}`} in ${r}
77
77
  `)}},km={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=hc(e),n=await gt();delete n[t];let o=await kr(n);k(`unset ${t} in ${o}
78
78
  `)}},fc=[ym,bm,km];var vm={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?[So(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}k(z(r,n))}},$m={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-cx180e19.js");await e();
2
+ import{enableCompileCache as a}from"node:module";a?.();var{run:e}=await import("./fui-kchph3mb.js");await e();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fui-org/fui-cli",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Token-lean CLI for the FUI low-code platform (Node.js)",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,37 +1,88 @@
1
1
  import { atom, read, update } from "claude-code";
2
- import type { Engine, Register } from "claude-code";
2
+ import type { EngineInterface as Engine, Register } from "claude-code";
3
3
 
4
4
  import type { FuiTarget } from "../types";
5
5
 
6
+ type SessionTarget = { projectId?: string; moduleId?: string; moduleDir?: string; workspaceRoot?: string };
7
+
6
8
  const target = atom({ plugin: "fui-target", key: "target" } as const, null);
7
9
 
8
- const FUI_CALL = /(?:^|[\s;&|(])fui\s+([a-z-]+)(?:\s+([a-z-]+))?/;
10
+ // fui must be the program a shell segment runs, so `grep fui src` or `cd fui-cli` never count.
11
+ const FUI_CALL = /(?:^|[;&|(]|\bthen|\bdo)\s*(?:[A-Za-z_]\w*=\S*\s+)*(?:bunx\s+)?fui\s+([a-z-]+)(?:\s+([a-z-]+))?/m;
9
12
 
10
13
  // Mirrors sessionFile() in src/core/session/state.ts.
11
14
  async function sessionFile($: Engine, command: string) {
12
15
  const key = command.match(/FUI_SESSION=([A-Za-z0-9_-]+)/)?.[1] ?? (await $.env.get("FUI_SESSION")) ?? "default";
13
- const dir =
14
- (await $.env.get("FUI_SESSION_DIR")) ??
15
- (await $.env.get("FUI_MCP_SESSION_DIR")) ??
16
- `${await $.env.get("HOME")}/.fui-mcp/session`;
16
+ const dir = (await $.env.get("FUI_SESSION_DIR")) ?? `${await $.env.get("HOME")}/.fui/session`;
17
17
  return `${dir}/cli-${key}.json`;
18
18
  }
19
19
 
20
- async function sync($: Engine, command: string, lastCommand: string) {
21
- const text = await $.fs.read(await sessionFile($, command)).catch(() => "");
22
- let state: { target?: { projectId?: string; moduleId?: string } } = {};
20
+ async function readJson($: Engine, path: string): Promise<unknown> {
21
+ try {
22
+ return JSON.parse(await $.fs.read(path));
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ }
27
+
28
+ function httpsUrl(raw: unknown): string | undefined {
29
+ if (typeof raw !== "string" || !raw.trim()) return undefined;
30
+ const text = raw.trim();
23
31
  try {
24
- state = JSON.parse(text || "{}");
25
- } catch {}
26
-
27
- await update($, target, () => ({
28
- project: state.target?.projectId,
29
- module: state.target?.moduleId,
30
- lastCommand,
31
- }));
32
+ return new URL(text.startsWith("//") ? `https:${text}` : /^https?:\/\//i.test(text) ? text : `https://${text}`)
33
+ .href;
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ // pDomain is a host, or on multi-domain projects a JSON array of hosts kept as a string (see parseDomainField).
40
+ function firstDomain(raw: unknown): string | undefined {
41
+ if (typeof raw !== "string") return undefined;
42
+ const text = raw.trim();
43
+ if (!text.startsWith("[")) return text || undefined;
44
+ try {
45
+ const hosts: unknown = JSON.parse(text);
46
+ return Array.isArray(hosts) && typeof hosts[0] === "string" ? hosts[0].trim() : undefined;
47
+ } catch {
48
+ return text;
49
+ }
50
+ }
51
+
52
+ async function moduleUrl($: Engine, session: SessionTarget) {
53
+ const info = (await readJson($, `${session.moduleDir}/_moduleInfo.json`)) as { url?: unknown } | undefined;
54
+ if (info?.url) return httpsUrl(info.url);
55
+ const list = await readJson($, `${session.workspaceRoot}/${session.projectId}/modules/_modules.json`);
56
+ const row = Array.isArray(list) ? list.find((item) => item?.ModuleID === session.moduleId) : undefined;
57
+ return httpsUrl(row?.url);
58
+ }
59
+
60
+ async function projectUrl($: Engine, session: SessionTarget) {
61
+ const info = (await readJson($, `${session.workspaceRoot}/${session.projectId}/_projectInfo.json`)) as
62
+ | { pDomain?: unknown }
63
+ | undefined;
64
+ return httpsUrl(firstDomain(info?.pDomain));
65
+ }
66
+
67
+ async function sync($: Engine, command: string, lastCommand: string) {
68
+ const state = (await readJson($, await sessionFile($, command))) as { target?: SessionTarget } | undefined;
69
+ const session = state?.target;
70
+ const found: FuiTarget = { lastCommand };
71
+ if (session?.projectId) {
72
+ found.project = session.projectId;
73
+ found.module = session.moduleId;
74
+ found.projectUrl = await projectUrl($, session);
75
+ found.moduleUrl = session.moduleId ? await moduleUrl($, session) : undefined;
76
+ }
77
+ await update($, target, () => found);
32
78
  }
33
79
 
34
80
  export const register: Register = (on) => {
81
+ on("prompt.submit", async ($, e, next) => {
82
+ await update($, target, () => null);
83
+ return next(e);
84
+ });
85
+
35
86
  on("tool.call", { tool: "Bash" }, async ($, e, next) => {
36
87
  const result = await next(e);
37
88
  const call = e.command.match(FUI_CALL);
@@ -45,21 +96,26 @@ export const register: Register = (on) => {
45
96
  const current: FuiTarget | null = await read($, target);
46
97
  if (e.props.hasSurvey || current === null) return next(e);
47
98
 
48
- const { Box, Text } = $.ui.resolve(e);
99
+ const { Box, Text, Link } = $.ui.resolve(e);
49
100
  return (
50
101
  <Box flexDirection="row" columnGap={1}>
51
102
  <Text>🧩</Text>
52
103
  {current.project ? (
53
104
  <Text>
54
- <Text bold color="cyan">
55
- {current.project}
56
- </Text>
57
- {current.module && (
58
- <Text>
59
- <Text dimColor> › </Text>
60
- <Text bold>{current.module}</Text>
105
+ {current.projectUrl ? (
106
+ <Link href={current.projectUrl}>{current.project}</Link>
107
+ ) : (
108
+ <Text bold color="cyan">
109
+ {current.project}
61
110
  </Text>
62
111
  )}
112
+ {current.module && <Text dimColor> › </Text>}
113
+ {current.module &&
114
+ (current.moduleUrl ? (
115
+ <Link href={current.moduleUrl}>{current.module}</Link>
116
+ ) : (
117
+ <Text bold>{current.module}</Text>
118
+ ))}
63
119
  </Text>
64
120
  ) : (
65
121
  <Text dimColor>no active module</Text>
@@ -0,0 +1,18 @@
1
+ {
2
+ // Claude Code compiles hooks against its global `h`. Run /plugin-types from the repo root to write .claude/types.
3
+ "compilerOptions": {
4
+ "target": "es2023",
5
+ "lib": ["es2023"],
6
+ "types": [],
7
+ "module": "esnext",
8
+ "moduleResolution": "bundler",
9
+ "strict": true,
10
+ "noUncheckedIndexedAccess": true,
11
+ "noEmit": true,
12
+ "skipLibCheck": true,
13
+ "jsx": "react",
14
+ "jsxFactory": "h",
15
+ "jsxFragmentFactory": "Fragment"
16
+ },
17
+ "include": ["../../.claude/types", "hooks", "types"]
18
+ }
@@ -1,4 +1,10 @@
1
- export type FuiTarget = { project?: string; module?: string; lastCommand?: string };
1
+ export type FuiTarget = {
2
+ project?: string;
3
+ module?: string;
4
+ projectUrl?: string;
5
+ moduleUrl?: string;
6
+ lastCommand?: string;
7
+ };
2
8
 
3
9
  declare module "claude-code" {
4
10
  interface PluginState {