citadel_cli 1.1.4 → 1.1.6
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 +59 -106
- package/dist/citadel.css +1 -1
- package/dist/citadel.es.js +652 -560
- package/dist/citadel.umd.js +46 -20
- package/dist/components/Citadel/Citadel.d.ts +18 -5
- package/dist/components/Citadel/__tests__/CitadelTty.test.d.ts +1 -0
- package/dist/components/Citadel/__tests__/InlineController.test.d.ts +1 -0
- package/dist/components/Citadel/__tests__/PanelController.test.d.ts +1 -0
- package/dist/components/Citadel/__tests__/integration.test.d.ts +1 -0
- package/dist/components/Citadel/__tests__/user-journey.test.d.ts +1 -0
- package/dist/components/Citadel/components/CitadelTty.d.ts +9 -0
- package/dist/components/Citadel/config/types.d.ts +6 -0
- package/dist/components/Citadel/controllers/InlineController.d.ts +2 -0
- package/dist/components/Citadel/controllers/PanelController.d.ts +2 -0
- package/dist/components/Citadel/types/command-registry.d.ts +14 -3
- package/dist/examples/basicCommands.d.ts +6 -0
- package/package.json +6 -2
package/dist/citadel.umd.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
(function(w,
|
|
1
|
+
(function(w,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("react"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","react-dom/client"],a):(w=typeof globalThis<"u"?globalThis:w||self,a(w.Citadel={},w.jsxRuntime,w.React,w.client))})(this,function(w,a,s,pe){"use strict";var $e=Object.defineProperty;var Ke=(w,a,s)=>a in w?$e(w,a,{enumerable:!0,configurable:!0,writable:!0,value:s}):w[a]=s;var x=(w,a,s)=>Ke(w,typeof a!="symbol"?a+"":a,s);var D=(r=>(r.Pending="pending",r.Success="success",r.Failure="failure",r.Timeout="timeout",r))(D||{});class P{constructor(e=Date.now()){x(this,"_status","pending");this.timestamp=e}get status(){return this._status}markSuccess(){this._status="success"}markFailure(){this._status="failure"}markTimeout(){this._status="timeout"}}class he extends P{constructor(e,n){super(n),this.data=e}render(){return a.jsx("pre",{className:"text-gray-200",children:JSON.stringify(this.data,null,2)})}}class V extends P{constructor(e,n){super(n),this.text=e}render(){return a.jsx("div",{className:"text-gray-200 whitespace-pre font-mono",children:this.text})}}class G extends P{constructor(e,n){super(n),this.error=e,this.markFailure()}render(){return a.jsx("div",{className:"mt-1 text-red-400",children:this.error})}}class J extends P{render(){return a.jsx("div",{className:"text-gray-400",children:"..."})}}class ge extends P{constructor(e,n="",t){super(t),this.imageUrl=e,this.altText=n}render(){return a.jsx("div",{className:"my-2",children:a.jsx("img",{src:this.imageUrl,alt:this.altText,className:"max-w-[400px] max-h-[300px] h-auto rounded-lg object-contain"})})}}const fe=r=>async function(){const e=r.commands.filter(n=>n.fullPath[0]!=="help").map(n=>`${n.segments.map(o=>o.type==="argument"?`<${o.name}>`:o.name).join(" ")} - ${n.description}`).sort();return e.push("help - Show available commands"),new V(e.length>0?`Available Commands:
|
|
2
2
|
`+e.join(`
|
|
3
|
-
`):"No commands available yet. Add some commands to get started!")};var $=(r=>(r[r.NONE=0]="NONE",r[r.ERROR=1]="ERROR",r[r.WARN=2]="WARN",r[r.INFO=3]="INFO",r[r.DEBUG=4]="DEBUG",r[r.TRACE=5]="TRACE",r))($||{});class S{static configure(e){this.level=e.level,this.prefix=e.prefix||"[Citadel]"}static trace(...e){this.level>=5&&process.env.NODE_ENV!=="production"&&console.trace(this.prefix,...e)}static debug(...e){this.level>=4&&process.env.NODE_ENV!=="production"&&console.debug(this.prefix,...e)}static info(...e){this.level>=3&&console.info(this.prefix,...e)}static warn(...e){this.level>=2&&console.warn(this.prefix,...e)}static error(...e){this.level>=1&&console.error(this.prefix,...e)}}y(S,"level",0),y(S,"prefix","");const N={commandTimeoutMs:1e4,cursorColor:"var(--cursor-color, #fff)",cursorSpeed:530,cursorType:"blink",includeHelpCommand:!0,initialHeight:"40vh",logLevel:process.env.NODE_ENV==="production"?$.ERROR:$.DEBUG,maxHeight:"80vh",minHeight:"200",outputFontSize:"0.875rem",resetStateOnHide:!1,showCitadelKey:".",storage:{type:"localStorage",maxCommands:100}};class J{constructor(e){y(this,"config");this.config={type:"localStorage",maxCommands:100,...e}}async addStoredCommand(e){const n=await this.getStoredCommands();for(n.push(e);n.length>this.config.maxCommands;)n.shift();await this.saveCommands(n)}}class we extends J{constructor(n){super(n);y(this,"storageKey","citadel_command_history")}async getStoredCommands(){try{const n=window.localStorage.getItem(this.storageKey);return n?JSON.parse(n).map(o=>({commandSegments:o.commandSegments||[],timestamp:o.timestamp})):[]}catch(n){return console.warn("Failed to load commands from localStorage:",n),[]}}async clear(){try{window.localStorage.removeItem(this.storageKey)}catch(n){console.warn("Failed to clear localStorage:",n)}}async saveCommands(n){try{const t=n.map(o=>({commandSegments:Array.isArray(o.commandSegments)?[...o.commandSegments]:[],timestamp:o.timestamp}));window.localStorage.setItem(this.storageKey,JSON.stringify(t))}catch(t){throw console.warn("Failed to save commands to localStorage:",t),t}}}class be extends J{constructor(n){super(n);y(this,"storedCommands",[])}async getStoredCommands(){return this.storedCommands.map(n=>({commandSegments:Array.isArray(n.commandSegments)?[...n.commandSegments]:[],timestamp:n.timestamp}))}async clear(){this.storedCommands=[]}async saveCommands(n){this.storedCommands=n.map(t=>({commandSegments:Array.isArray(t.commandSegments)?[...t.commandSegments]:[],timestamp:t.timestamp}))}}const D=class D{constructor(){y(this,"currentStorage")}static getInstance(){return D.instance||(D.instance=new D),D.instance}initializeStorage(e){if(!this.currentStorage)try{this.currentStorage=new we(e)}catch(n){console.warn("Failed to create storage, falling back to memory storage:",n),this.currentStorage=new be(e)}}getStorage(){if(!this.currentStorage)throw new Error("Storage not initialized. Call initializeStorage first.");return this.currentStorage}};y(D,"instance");let K=D;const Q=async()=>new V("");class W{constructor(e,n,t){this.type=e,this.name=n,this.description=t}toString(){return this.name}}class X extends W{constructor(){super("null",">null<","Empty segment")}}class ye extends W{constructor(e,n){super("word",e,n)}}class xe extends W{constructor(e,n,t,o){super("argument",e,n),this.value=t,this.valid=o}}class Ce{constructor(e,n,t=Q){y(this,"_segments");y(this,"_description");y(this,"_handler");this._segments=e,this._description=n,this._handler=t}get segments(){return this._segments}get description(){return this._description}get handler(){return this._handler}get hasArguments(){return this.segments.some(e=>e.type==="argument")}get fullPath(){return this.segments.map(e=>e.name)}get fullPath_s(){return this.fullPath.join(" ")}equals(e){return this.fullPath.join(" ")===e.fullPath.join(" ")}}class L{constructor(){y(this,"_commands",[])}get commands(){return this._commands}addCommand(e,n,t=Q){if(e===void 0||e.length===0)throw new Error("Command path cannot be empty");const o=new Ce(e,n,t),a=this._commands.find(c=>{const m=c.segments.map(d=>d.type==="argument"?"*":d.name).join(" "),u=e.map(d=>d.type==="argument"?"*":d.name).join(" ");return m===u});if(a)throw new Error(`Duplicate commands: '${a.fullPath_s}' and '${o.fullPath_s}'`);this._commands.push(o)}getCommand(e){return this._commands.find(n=>{const t=n.fullPath.join(" "),o=e.join(" ");if(t===o)return!0;const c=n.segments.filter(m=>m.type==="word").map(m=>m.name);return c.length===e.length&&c.join(" ")===o})}commandExistsForPath(e){const n=this._commands.map(o=>o.segments.map(a=>a.type==="argument"?"*":a.name).join(" ")),t=e.map((o,a)=>this._commands.some(m=>{var u;return((u=m.segments[a])==null?void 0:u.type)==="argument"})?"*":o).join(" ");return n.includes(t)}getCompletions_s(e){return this.getCompletions(e).map(n=>n.name)}getCompletions(e){if(S.debug("[getCompletions] path: ",e),!e.length){const a=this._commands.map(u=>u.segments[0]),c=(u,d)=>u.type===d.type&&u.name===d.name;return a.filter((u,d,p)=>d===p.findIndex(g=>c(g,u)))}const n=e.length;return this._commands.filter(a=>{const c=a.segments;if(c.length<=n-1)return!1;for(let m=0;m<n;m++){const u=e[m],d=c[m];if(!(u==="*"&&d.type==="argument")&&u!==d.name)return!1}return!0}).filter(a=>a.segments.length>n).map(a=>{const c=a.segments[n],m=c.type==="argument"?xe:ye;return new m(c.name,c.description)}).filter((a,c,m)=>c===m.findIndex(u=>u.type===a.type&&u.name===a.name))}hasNextSegment(e){return this.getCompletions(e).length>0}}class Z{constructor(){y(this,"segments",[]);y(this,"nullSegment",new X);y(this,"observers",[])}subscribe(e){this.observers.push(e)}unsubscribe(e){this.observers=this.observers.filter(n=>n!==e)}notifyObservers(){this.observers.forEach(e=>e.update())}clear(){this.segments=[],this.notifyObservers()}push(e){this.segments.push(e),this.notifyObservers()}pushAll(e){e.forEach(n=>this.push(n))}pop(){const e=this.segments.pop()||this.nullSegment;return this.notifyObservers(),e}peek(){return this.segments[this.segments.length-1]||this.nullSegment}size(){return this.segments.length}isEmpty(){return this.segments.length===0}get hasArguments(){return this.segments.some(e=>e.type==="argument")}get arguments(){return this.segments.filter(e=>e.type==="argument")}path(){return this.segments.map(e=>e.name)}toArray(){return[...this.segments]}}const ve={config:N,commands:new L,segmentStack:new Z},U=s.createContext(ve),Se=({config:r=N,commandRegistry:e,children:n})=>{const[t,o]=s.useState(),a={...N,...r,storage:{...N.storage,...r.storage},cursorType:r.cursorType??N.cursorType,cursorColor:r.cursorColor??N.cursorColor,cursorSpeed:r.cursorSpeed??N.cursorSpeed,showCitadelKey:r.showCitadelKey||"."};s.useEffect(()=>{K.getInstance().initializeStorage(a.storage??N.storage),o(K.getInstance().getStorage())},[]),s.useEffect(()=>{if(e&&a.includeHelpCommand&&!e.commandExistsForPath(["help"])){const m=fe(e);e.addCommand([{type:"word",name:"help"}],"Show available commands",m)}},[e,a.includeHelpCommand]);const c={config:a,commands:e||new L,storage:t,segmentStack:new Z};return i.jsx(U.Provider,{value:c,children:n})},F=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelConfig must be used within a CitadelConfigProvider");return r.config},q=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelCommands must be used within a CitadelConfigProvider");return r.commands},R=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelStorage must be used within a CitadelConfigProvider");return r.storage},j=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useSegmentStack must be used within a CitadelConfigProvider");return r.segmentStack};class ee{constructor(e,n){y(this,"timestamp");y(this,"command");y(this,"result");this.command=e.toArray().map(t=>t.type==="argument"?t.value||"":t.name),this.timestamp=Date.now(),this.result=n??new G}}function ke(r){return{commandSegments:r,timestamp:Date.now()}}function ne(){const r=R(),[e,n]=s.useState({storedCommands:[],position:null}),t=s.useCallback(async m=>{if(r)try{const u=ke(m);await r.addStoredCommand(u),n(d=>({...d,storedCommands:[...d.storedCommands,u],position:null}))}catch(u){console.warn("Failed to save command to history:",u)}},[r]),o=s.useCallback(async()=>r?await r.getStoredCommands():[],[r]);s.useEffect(()=>{if(!r)return;(async()=>{try{const u=await r.getStoredCommands();return n(d=>({...d,storedCommands:u})),u}catch(u){console.warn("Failed to load command history:",u)}})()},[r]);const a=s.useCallback(async m=>{if((await o()).length===0)return{segments:null,position:null};let d=null;return m==="up"?e.position===null?d=e.storedCommands.length-1:e.position>0?d=e.position-1:d=0:e.position===null||e.position>=e.storedCommands.length-1?d=null:d=e.position+1,n(g=>({...g,position:d})),d===null?{segments:[],position:null}:{segments:d!==null?e.storedCommands[d].commandSegments:null,position:d}},[e,o]),c=s.useCallback(async()=>{try{if(!r)return;await r.clear(),n({storedCommands:[],position:null})}catch(m){console.warn("Failed to clear command history:",m)}},[r]);return{history:e,addStoredCommand:t,getStoredCommands:o,navigateHistory:a,clear:c}}const te=()=>{const r=F(),e=q(),n=ne(),t=j(),o=R(),[a,c]=s.useState({currentInput:"",isEnteringArg:!1,output:[],history:{commands:[],position:null,storage:o}});s.useEffect(()=>{},[o]),s.useEffect(()=>{c(p=>({...p,history:{commands:n.history.storedCommands,position:n.history.position,storage:o}}))},[n.history,o]);const m={setCurrentInput:s.useCallback(p=>{S.debug("[CitadelActions] setCurrentInput: ",p),c(g=>({...g,currentInput:p}))},[]),setIsEnteringArg:s.useCallback(p=>{S.debug("[CitadelActions] setIsEnteringArg: ",p),c(g=>({...g,isEnteringArg:p}))},[]),addOutput:s.useCallback(p=>{S.debug("[CitadelActions]addOutput: ",p),c(g=>({...g,output:[...g.output,p]}))},[]),executeCommand:s.useCallback(async()=>{const p=t.path(),g=e.getCommand(p);if(!g){console.error("[CitadelActions][executeCommand] Cannot execute command because no command was found for the given path: ",p);return}const k=new ee(t);c(x=>({...x,output:[...x.output,k]}));try{const x=new Promise((l,h)=>{setTimeout(()=>{h(new Error("Request timed out"))},r.commandTimeoutMs)}),A=t.arguments.map(l=>l.value||""),_=await Promise.race([g.handler(A),x]);if(!(_ instanceof H))throw new Error(`The ${p.join(".")} command returned an invalid result type. Commands must return an instance of a CommandResult.
|
|
3
|
+
`):"No commands available yet. Add some commands to get started!")};var F=(r=>(r[r.NONE=0]="NONE",r[r.ERROR=1]="ERROR",r[r.WARN=2]="WARN",r[r.INFO=3]="INFO",r[r.DEBUG=4]="DEBUG",r[r.TRACE=5]="TRACE",r))(F||{});class E{static configure(e){this.level=e.level,this.prefix=e.prefix||"[Citadel]"}static trace(...e){this.level>=5&&process.env.NODE_ENV!=="production"&&console.trace(this.prefix,...e)}static debug(...e){this.level>=4&&process.env.NODE_ENV!=="production"&&console.debug(this.prefix,...e)}static info(...e){this.level>=3&&console.info(this.prefix,...e)}static warn(...e){this.level>=2&&console.warn(this.prefix,...e)}static error(...e){this.level>=1&&console.error(this.prefix,...e)}}x(E,"level",0),x(E,"prefix","");const A={commandTimeoutMs:1e4,cursorColor:"var(--cursor-color, #fff)",cursorSpeed:530,cursorType:"blink",includeHelpCommand:!0,initialHeight:"40vh",logLevel:process.env.NODE_ENV==="production"?F.ERROR:F.DEBUG,maxHeight:"80vh",minHeight:"200",outputFontSize:"0.875rem",resetStateOnHide:!1,showCitadelKey:".",displayMode:"panel",storage:{type:"localStorage",maxCommands:100}};class Q{constructor(e){x(this,"config");this.config={type:"localStorage",maxCommands:100,...e}}async addStoredCommand(e){const n=await this.getStoredCommands();for(n.push(e);n.length>this.config.maxCommands;)n.shift();await this.saveCommands(n)}}class we extends Q{constructor(n){super(n);x(this,"storageKey","citadel_command_history")}async getStoredCommands(){try{const n=window.localStorage.getItem(this.storageKey);return n?JSON.parse(n).map(o=>({commandSegments:o.commandSegments||[],timestamp:o.timestamp})):[]}catch(n){return console.warn("Failed to load commands from localStorage:",n),[]}}async clear(){try{window.localStorage.removeItem(this.storageKey)}catch(n){console.warn("Failed to clear localStorage:",n)}}async saveCommands(n){try{const t=n.map(o=>({commandSegments:Array.isArray(o.commandSegments)?[...o.commandSegments]:[],timestamp:o.timestamp}));window.localStorage.setItem(this.storageKey,JSON.stringify(t))}catch(t){throw console.warn("Failed to save commands to localStorage:",t),t}}}class be extends Q{constructor(n){super(n);x(this,"storedCommands",[])}async getStoredCommands(){return this.storedCommands.map(n=>({commandSegments:Array.isArray(n.commandSegments)?[...n.commandSegments]:[],timestamp:n.timestamp}))}async clear(){this.storedCommands=[]}async saveCommands(n){this.storedCommands=n.map(t=>({commandSegments:Array.isArray(t.commandSegments)?[...t.commandSegments]:[],timestamp:t.timestamp}))}}const H=class H{constructor(){x(this,"currentStorage")}static getInstance(){return H.instance||(H.instance=new H),H.instance}initializeStorage(e){if(!this.currentStorage)try{this.currentStorage=new we(e)}catch(n){console.warn("Failed to create storage, falling back to memory storage:",n),this.currentStorage=new be(e)}}getStorage(){if(!this.currentStorage)throw new Error("Storage not initialized. Call initializeStorage first.");return this.currentStorage}};x(H,"instance");let O=H;const X=async()=>new V("");class W{constructor(e,n,t){this.type=e,this.name=n,this.description=t}toString(){return this.name}}class Z extends W{constructor(){super("null",">null<","Empty segment")}}class ye extends W{constructor(e,n){super("word",e,n)}}class xe extends W{constructor(e,n,t,o){super("argument",e,n),this.value=t,this.valid=o}}class Ce{constructor(e,n,t=X){x(this,"_segments");x(this,"_description");x(this,"_handler");this._segments=e,this._description=n,this._handler=t}get segments(){return this._segments}get description(){return this._description}get handler(){return this._handler}get hasArguments(){return this.segments.some(e=>e.type==="argument")}get fullPath(){return this.segments.map(e=>e.name)}get fullPath_s(){return this.fullPath.join(" ")}equals(e){return this.fullPath.join(" ")===e.fullPath.join(" ")}}class L{constructor(){x(this,"_commands",[])}get commands(){return this._commands}addCommand(e,n,t=X){if(e===void 0||e.length===0)throw new Error("Command path cannot be empty");const o=new Ce(e,n,t),i=this._commands.find(l=>{const m=l.segments.map(d=>d.type==="argument"?"*":d.name).join(" "),u=e.map(d=>d.type==="argument"?"*":d.name).join(" ");return m===u});if(i)throw new Error(`Duplicate commands: '${i.fullPath_s}' and '${o.fullPath_s}'`);this._commands.push(o)}removeCommand(e){const n=e.join(" "),t=this._commands.findIndex(o=>o.fullPath.join(" ")===n);return t===-1?!1:(this._commands.splice(t,1),!0)}getCommand(e){return this._commands.find(n=>{const t=n.fullPath.join(" "),o=e.join(" ");if(t===o)return!0;const l=n.segments.filter(m=>m.type==="word").map(m=>m.name);return l.length===e.length&&l.join(" ")===o})}commandExistsForPath(e){const n=this._commands.map(o=>o.segments.map(i=>i.type==="argument"?"*":i.name).join(" ")),t=e.map((o,i)=>this._commands.some(m=>{var u;return((u=m.segments[i])==null?void 0:u.type)==="argument"})?"*":o).join(" ");return n.includes(t)}getCompletions_s(e){return this.getCompletions(e).map(n=>n.name)}getCompletions(e){if(E.debug("[getCompletions] path: ",e),!e.length){const i=this._commands.map(u=>u.segments[0]),l=(u,d)=>u.type===d.type&&u.name===d.name;return i.filter((u,d,p)=>d===p.findIndex(g=>l(g,u)))}const n=e.length;return this._commands.filter(i=>{const l=i.segments;if(l.length<=n-1)return!1;for(let m=0;m<n;m++){const u=e[m],d=l[m];if(!(u==="*"&&d.type==="argument")&&u!==d.name)return!1}return!0}).filter(i=>i.segments.length>n).map(i=>{const l=i.segments[n],m=l.type==="argument"?xe:ye;return new m(l.name,l.description)}).filter((i,l,m)=>l===m.findIndex(u=>u.type===i.type&&u.name===i.name))}hasNextSegment(e){return this.getCompletions(e).length>0}}class R{constructor(){x(this,"segments",[]);x(this,"nullSegment",new Z);x(this,"observers",[])}subscribe(e){this.observers.push(e)}unsubscribe(e){this.observers=this.observers.filter(n=>n!==e)}notifyObservers(){this.observers.forEach(e=>e.update())}clear(){this.segments=[],this.notifyObservers()}push(e){this.segments.push(e),this.notifyObservers()}pushAll(e){e.forEach(n=>this.push(n))}pop(){const e=this.segments.pop()||this.nullSegment;return this.notifyObservers(),e}peek(){return this.segments[this.segments.length-1]||this.nullSegment}size(){return this.segments.length}isEmpty(){return this.segments.length===0}get hasArguments(){return this.segments.some(e=>e.type==="argument")}get arguments(){return this.segments.filter(e=>e.type==="argument")}path(){return this.segments.map(e=>e.name)}toArray(){return[...this.segments]}}const ve={config:A,commands:new L,segmentStack:new R},U=s.createContext(ve),Se=({config:r=A,commandRegistry:e,children:n})=>{const[t,o]=s.useState(),i={...A,...r,storage:{...A.storage,...r.storage},cursorType:r.cursorType??A.cursorType,cursorColor:r.cursorColor??A.cursorColor,cursorSpeed:r.cursorSpeed??A.cursorSpeed,showCitadelKey:r.showCitadelKey||"."};s.useEffect(()=>{O.getInstance().initializeStorage(i.storage??A.storage),o(O.getInstance().getStorage())},[]),s.useEffect(()=>{if(e){if(i.includeHelpCommand){if(!e.commandExistsForPath(["help"])){const m=fe(e);e.addCommand([{type:"word",name:"help"}],"Show available commands",m)}return}e.removeCommand(["help"])}},[e,i.includeHelpCommand]);const l={config:i,commands:e||new L,storage:t,segmentStack:new R};return a.jsx(U.Provider,{value:l,children:n})},M=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelConfig must be used within a CitadelConfigProvider");return r.config},$=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelCommands must be used within a CitadelConfigProvider");return r.commands},ee=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useCitadelStorage must be used within a CitadelConfigProvider");return r.storage},j=()=>{const r=s.useContext(U);if(r===void 0)throw new Error("useSegmentStack must be used within a CitadelConfigProvider");return r.segmentStack};class ne{constructor(e,n){x(this,"timestamp");x(this,"command");x(this,"result");this.command=e.toArray().map(t=>t.type==="argument"?t.value||"":t.name),this.timestamp=Date.now(),this.result=n??new J}}function ke(r){return{commandSegments:r,timestamp:Date.now()}}function te(){const r=ee(),[e,n]=s.useState({storedCommands:[],position:null}),t=s.useCallback(async m=>{if(r)try{const u=ke(m);await r.addStoredCommand(u),n(d=>({...d,storedCommands:[...d.storedCommands,u],position:null}))}catch(u){console.warn("Failed to save command to history:",u)}},[r]),o=s.useCallback(async()=>r?await r.getStoredCommands():[],[r]);s.useEffect(()=>{if(!r)return;(async()=>{try{const u=await r.getStoredCommands();return n(d=>({...d,storedCommands:u})),u}catch(u){console.warn("Failed to load command history:",u)}})()},[r]);const i=s.useCallback(async m=>{if((await o()).length===0)return{segments:null,position:null};let d=null;return m==="up"?e.position===null?d=e.storedCommands.length-1:e.position>0?d=e.position-1:d=0:e.position===null||e.position>=e.storedCommands.length-1?d=null:d=e.position+1,n(g=>({...g,position:d})),d===null?{segments:[],position:null}:{segments:d!==null?e.storedCommands[d].commandSegments:null,position:d}},[e,o]),l=s.useCallback(async()=>{try{if(!r)return;await r.clear(),n({storedCommands:[],position:null})}catch(m){console.warn("Failed to clear command history:",m)}},[r]);return{history:e,addStoredCommand:t,getStoredCommands:o,navigateHistory:i,clear:l}}const Y=()=>{const r=M(),e=$(),n=te(),t=j(),o=ee(),[i,l]=s.useState({currentInput:"",isEnteringArg:!1,output:[],history:{commands:[],position:null,storage:o}});s.useEffect(()=>{},[o]),s.useEffect(()=>{l(p=>({...p,history:{commands:n.history.storedCommands,position:n.history.position,storage:o}}))},[n.history,o]);const m={setCurrentInput:s.useCallback(p=>{E.debug("[CitadelActions] setCurrentInput: ",p),l(g=>({...g,currentInput:p}))},[]),setIsEnteringArg:s.useCallback(p=>{E.debug("[CitadelActions] setIsEnteringArg: ",p),l(g=>({...g,isEnteringArg:p}))},[]),addOutput:s.useCallback(p=>{E.debug("[CitadelActions]addOutput: ",p),l(g=>({...g,output:[...g.output,p]}))},[]),executeCommand:s.useCallback(async()=>{const p=t.path(),g=e.getCommand(p);if(!g){console.error("[CitadelActions][executeCommand] Cannot execute command because no command was found for the given path: ",p);return}const S=new ne(t);l(b=>({...b,output:[...b.output,S]}));try{const b=new Promise((c,h)=>{setTimeout(()=>{h(new Error("Request timed out"))},r.commandTimeoutMs)}),C=t.arguments.map(c=>c.value||""),_=await Promise.race([g.handler(C),b]);if(!(_ instanceof P))throw new Error(`The ${p.join(".")} command returned an invalid result type. Commands must return an instance of a CommandResult.
|
|
4
4
|
For example:
|
|
5
5
|
return new JsonCommandResult({ text: "Hello World" });
|
|
6
|
-
Check the definition of the ${p.join(".")} command and update the return type for its handler.`);_.markSuccess(),c(l=>({...l,output:l.output.map(h=>h.timestamp===k.timestamp?{...h,result:_}:h)}))}catch(x){const A=new B(x instanceof Error?x.message:"Unknown error");A.markFailure(),c(_=>({..._,output:_.output.map(l=>l.timestamp===k.timestamp?{...l,result:A}:l)}))}},[e,r.commandTimeoutMs,t]),clearHistory:s.useCallback(async()=>{try{await n.clear()}catch(p){console.warn("Failed to clear history:",p)}},[n])},u=s.useCallback(()=>e.getCompletions_s(t.path()),[t,e]),d=s.useCallback(()=>e.getCompletions(t.path()),[t,e]);return{state:a,actions:m,getAvailableCommands_s:u,getAvailableCommandSegments:d}},re={blink:{character:"▋",speed:530,color:"#fff"},spin:{character:"⠋",speed:120,color:"#fff"},solid:{character:"▋",speed:0,color:"#fff"},bbs:{character:"|",speed:120,color:"#fff"}},oe=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],se=["|","/","-","\\"],Ee=({style:r={type:"blink"},isValid:e=!0,errorMessage:n})=>{const t=s.useMemo(()=>({...re[r.type],...r}),[r]),[o,a]=s.useState(!0),[c,m]=s.useState(0);s.useEffect(()=>{if(t.speed===0)return;const p=setInterval(()=>{t.type==="blink"?a(g=>!g):["spin","bbs"].includes(t.type)&&m(g=>(g+1)%(t.type==="bbs"?se.length:oe.length))},t.speed);return()=>clearInterval(p)},[t.type,t.speed]);const u=s.useMemo(()=>({color:e?t.color:"#ff4444",transition:"color 0.15s ease-in-out"}),[e,t.color]),d=()=>!e&&n?"✗":["spin","bbs"].includes(t.type)?(t.type==="bbs"?se:oe)[c]:t.type==="solid"||o?t.character:" ";return i.jsx("div",{className:"relative inline-block",children:i.jsx("span",{className:`command-cursor ${e?"":"animate-shake"}`,style:u,title:n,children:d()})})};function _e(r,e){switch(e.type){case"set":return S.debug(`[inputStateReducer] InputState changing from ${r} to ${e.state}`),e.state;default:return r}}const Ne=()=>{const{state:r}=te(),e=q(),n=ne(),t=j(),[o,a]=s.useReducer(_e,"idle"),c=l=>{a({type:"set",state:l})},m=s.useCallback(()=>{const h=e.getCompletions(t.path())[0]||t.nullSegment;return S.debug("[getNextExpectedSegment] ",h),h},[e,t]),u=s.useCallback(()=>e.getCompletions_s(t.path()).map(h=>e.getCommand([...t.path(),h])).filter(h=>h!==void 0),[e,t]),d=s.useCallback((l,h)=>{if(!l)return h;const f=h.reduce((b,C)=>{const v=m();return(v==null?void 0:v.type)==="word"&&b.set(v.name,C),b},new Map);return Array.from(f.values()).filter(()=>{const b=m();return b.type!=="word"?!1:b.name.toLowerCase().startsWith(l.toLowerCase())})},[m]),p=s.useCallback(l=>{const f=e.getCompletions(t.path()).filter(E=>E.type==="word").filter(E=>E.name.toLowerCase().startsWith(l.toLowerCase()));return f.length===1?f[0]:t.nullSegment},[e,t]),g=s.useCallback(l=>{const h=t.path(),f=e.getCompletions(h);return f.length===0&&l?!1:f.some(b=>b.type==="argument")?!0:f.some(b=>b.type==="word"&&b.name.toLowerCase().startsWith(l.toLowerCase()))},[e,t]),k=s.useCallback(l=>{S.debug("[tryAutoComplete] input: ",l);const h=p(l);return!h||h.name===l?new X:(S.debug("[tryAutoComplete] result: ",h),h)},[p]),x=s.useCallback((l,h)=>{if(r.history.position===null){if(h.setCurrentInput(l),S.debug("[useCommandParser][handleInputChange] newValue: ",l),o==="entering_argument"){const f=ae(l);if(f.isQuoted)if(f.isComplete){const E=m();if(E.type==="argument"){const b=E;b.value=l.trim()||"",S.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",b),t.push(b),h.setCurrentInput(""),c("idle");return}}else return;else if(f.isComplete){const E=m();E.value=l.trim()||"",S.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",E),t.push(E),h.setCurrentInput(""),c("idle");return}else return}if(o=="entering_command"){const f=k(l);if(f.type==="word"){S.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",f),t.push(f),h.setCurrentInput(""),c("idle");return}}}},[k,r,m,o,t]),A=s.useCallback(l=>{l.setCurrentInput(""),l.setIsEnteringArg(!1),t.clear(),c("idle")},[t]),_=s.useCallback((l,h,f)=>{if(!(l.key==="Backspace"||l.key==="Enter"||l.key==="ArrowUp"||l.key==="ArrowDown"||l.key==="ArrowLeft"||l.key==="ArrowRight"||l.key==="Escape"||l.key==="Delete"||l.key==="Home"||l.key==="End"||l.key.length===1))return!0;const{currentInput:b,isEnteringArg:C}=h,v=ae(b);switch(l.key){case"Backspace":return b===""&&(l.preventDefault(),t.size()>0&&t.pop(),c("idle")),!0;case"Enter":{if(l.preventDefault(),v.isQuoted&&!v.isComplete)return!0;if(o==="entering_argument"||C&&b.trim()){const P=m();P.value=b,S.debug("[handleKeyDown][Enter]['entering_argument'] pushing: ",P),t.push(P)}const z=t.path(),I=e.getCommand(z);if(I){const M=I.segments.filter(O=>O.type==="argument"),P=t.arguments;if(M.length>P.length)return!1}return S.debug("[handleKeyDown][Enter] calling actions.executeCommand. segmentStack: ",t),f.executeCommand(),n.addStoredCommand(t.toArray()),A(f),!0}case"ArrowUp":return l.preventDefault(),(async()=>{const z=await n.navigateHistory("up",t.toArray());return z.segments&&(t.clear(),t.pushAll(z.segments),f.setCurrentInput("")),!0})();case"ArrowDown":return l.preventDefault(),(async()=>{const z=await n.navigateHistory("down",t.toArray());return z.segments&&(t.clear(),t.pushAll(z.segments),f.setCurrentInput("")),!0})();default:{if(!C&&l.key.length===1){const z=b+l.key;if(!g(z))return l.preventDefault(),!1}return!0}}},[o,g,m,n,A,t]);return{handleInputChange:x,handleKeyDown:_,inputState:o,setInputStateWithLogging:c,findMatchingCommands:d,getAutocompleteSuggestion:p,getAvailableNodes:u,getNextExpectedSegment:m,isValidCommandInput:g}};function ae(r){const e=[];let n="",t=!1,o;for(let a=0;a<r.length;a++){const c=r[a];(c==='"'||c==="'")&&(!t||c===o)?t?(e.push(n),n="",t=!1,o=void 0):(n&&(e.push(n),n=""),t=!0,o=c):!t&&c===" "?n&&(e.push(n),n=""):n+=c}return{words:e,currentWord:n,isQuoted:t,quoteChar:o,isComplete:!t&&!n}}const Ae=()=>{const r=j(),[e,n]=s.useState(0);return s.useEffect(()=>{const t={update:()=>{n(o=>o+1)}};return r.subscribe(t),()=>{r.unsubscribe(t)}},[r]),e},ze=({state:r,actions:e})=>{const n=s.useRef(null),t=q(),o=j(),{handleKeyDown:a,handleInputChange:c,inputState:m,setInputStateWithLogging:u,getNextExpectedSegment:d}=Ne(),[p,g]=s.useState(!1),k=F(),x=Ae(),A=async C=>{const v=a(C,r,e);await Promise.resolve(v)===!1&&(g(!0),setTimeout(()=>g(!1),500))},_=C=>{c(C.target.value,e)},l=C=>{C.preventDefault();const v=C.clipboardData.getData("text");c(v,e)};s.useEffect(()=>{n.current&&n.current.focus(),m!=="entering_command"&&u("entering_command")},[m,u]),s.useEffect(()=>{if(m!=="idle")return;const C=d();let v="idle";switch(C.type){case"word":v="entering_command",e.setIsEnteringArg(!1);break;case"argument":v="entering_argument",e.setIsEnteringArg(!0);break}u(v)},[x,m,d,u,e]);const[h,f]=s.useState([]);s.useEffect(()=>{const C=[],v=o.toArray().map((I,M)=>{C.push(I.name);const P=t.hasNextSegment(C);if(I.type==="argument"){const O=I;return i.jsxs(s.Fragment,{children:[i.jsx("span",{className:"text-gray-200 whitespace-pre",children:O.value}),M<o.size()&&P&&i.jsx("span",{className:"text-gray-200 whitespace-pre",children:" "})]},"arg-"+O.name+O.value)}return i.jsxs(s.Fragment,{children:[i.jsx("span",{className:"text-blue-400 whitespace-pre",children:I.name}),M<o.size()&&P&&i.jsx("span",{className:"text-blue-400 whitespace-pre",children:" "})]},"word-"+I.name)});f([i.jsx("div",{className:"flex items-center gap-1","data-testid":"user-input-area",children:v},"{segmentStackVersion}")])},[x,t,o]);const[E,b]=s.useState("");return s.useEffect(()=>{const C=d();C.type==="argument"?b(C.name):b("")},[x,d]),i.jsxs("div",{className:"flex flex-col w-full bg-gray-900 rounded-lg p-4",children:[i.jsx("style",{children:`
|
|
6
|
+
Check the definition of the ${p.join(".")} command and update the return type for its handler.`);_.markSuccess(),l(c=>({...c,output:c.output.map(h=>h.timestamp===S.timestamp?{...h,result:_}:h)}))}catch(b){const C=new G(b instanceof Error?b.message:"Unknown error");C.markFailure(),l(_=>({..._,output:_.output.map(c=>c.timestamp===S.timestamp?{...c,result:C}:c)}))}},[e,r.commandTimeoutMs,t]),clearHistory:s.useCallback(async()=>{try{await n.clear()}catch(p){console.warn("Failed to clear history:",p)}},[n])},u=s.useCallback(()=>e.getCompletions_s(t.path()),[t,e]),d=s.useCallback(()=>e.getCompletions(t.path()),[t,e]);return{state:i,actions:m,getAvailableCommands_s:u,getAvailableCommandSegments:d}},Ee=({onOpen:r,onClose:e,isVisible:n,showCitadelKey:t})=>{s.useEffect(()=>{const o=i=>{var l,m;!n&&i.key===t&&!["input","textarea"].includes(((m=(l=i.target)==null?void 0:l.tagName)==null?void 0:m.toLowerCase())||"")&&(i.preventDefault(),r()),n&&i.key==="Escape"&&(i.preventDefault(),e())};return document.addEventListener("keydown",o),()=>document.removeEventListener("keydown",o)},[r,e,n,t])},re={panelContainer:"_panelContainer_1pav9_3",innerContainer:"_innerContainer_1pav9_19",inputSection:"_inputSection_1pav9_29",resizeHandle:"_resizeHandle_1pav9_36",citadel_slideUp:"_citadel_slideUp_1pav9_65",citadel_slideDown:"_citadel_slideDown_1pav9_69",inlineContainer:"_inlineContainer_1pav9_73"},_e=r=>{const{isVisible:e,isClosing:n,onAnimationComplete:t}=r,o=s.useMemo(()=>e?n?re.slideDown:re.slideUp:"",[e,n]);return s.useEffect(()=>{if(t){const l=setTimeout(()=>{t()},200);return()=>clearTimeout(l)}},[n,t]),{style:s.useMemo(()=>({opacity:e?1:0,transform:e?"translateY(0)":n?"translateY(100%)":"translateY(-100%)",transition:"opacity 200ms ease-in-out, transform 200ms ease-in-out"}),[e,n]),animationClass:o}},Ne=()=>a.jsx("div",{"data-testid":"spinner",className:"animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-gray-600"}),Ae=({command:r,timestamp:e,status:n})=>a.jsxs("div",{className:"flex items-center gap-2 font-mono text-sm",children:[a.jsxs("span",{className:"text-gray-200",children:["> ",r.split(" ").map((t,o)=>{const i=t.startsWith("<")&&t.endsWith(">");return a.jsxs("span",{className:i?"text-green-400":"text-gray-200",children:[o>0?" ":"",t]},o)})]}),a.jsx("span",{className:"text-gray-400",children:"·"}),a.jsx("span",{className:"text-gray-500",children:e}),n===D.Pending&&a.jsx(Ne,{}),n===D.Success&&a.jsx("div",{"data-testid":"success-indicator",className:"w-4 h-4 rounded-full bg-green-500"}),(n===D.Timeout||n===D.Failure)&&a.jsx("div",{"data-testid":"success-indicator",className:"w-4 h-4 rounded-full bg-red-500"})]}),ze=({output:r,outputRef:e})=>{const n=M(),t=s.useCallback(()=>{if(e.current){const o=e.current;requestAnimationFrame(()=>{o.scrollTop=o.scrollHeight})}},[e]);return s.useEffect(()=>{if(t(),e.current){const o=e.current.getElementsByTagName("img"),i=o[o.length-1];if(i&&!i.complete)return i.addEventListener("load",t),()=>i.removeEventListener("load",t)}},[r,t,e]),a.jsx("div",{ref:e,className:"h-full overflow-y-auto border border-gray-700 rounded-lg p-3 text-left","data-testid":"citadel-command-output",children:r.map((o,i)=>a.jsxs("div",{className:"mb-4 last:mb-0",children:[a.jsx(Ae,{command:o.command.join(" "),timestamp:new Date(o.timestamp).toLocaleTimeString(),status:o.result.status}),a.jsx("pre",{className:`text-gray-200 whitespace-pre font-mono ${n.outputFontSize}`,children:o.result.render()})]},i))})},oe={blink:{character:"▋",speed:530,color:"#fff"},spin:{character:"⠋",speed:120,color:"#fff"},solid:{character:"▋",speed:0,color:"#fff"},bbs:{character:"|",speed:120,color:"#fff"}},se=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],ae=["|","/","-","\\"],Ie=({style:r={type:"blink"},isValid:e=!0,errorMessage:n})=>{const t=s.useMemo(()=>({...oe[r.type],...r}),[r]),[o,i]=s.useState(!0),[l,m]=s.useState(0);s.useEffect(()=>{if(t.speed===0)return;const p=setInterval(()=>{t.type==="blink"?i(g=>!g):["spin","bbs"].includes(t.type)&&m(g=>(g+1)%(t.type==="bbs"?ae.length:se.length))},t.speed);return()=>clearInterval(p)},[t.type,t.speed]);const u=s.useMemo(()=>({color:e?t.color:"#ff4444",transition:"color 0.15s ease-in-out"}),[e,t.color]),d=()=>!e&&n?"✗":["spin","bbs"].includes(t.type)?(t.type==="bbs"?ae:se)[l]:t.type==="solid"||o?t.character:" ";return a.jsx("div",{className:"relative inline-block",children:a.jsx("span",{className:`command-cursor ${e?"":"animate-shake"}`,style:u,title:n,children:d()})})};function Pe(r,e){switch(e.type){case"set":return E.debug(`[inputStateReducer] InputState changing from ${r} to ${e.state}`),e.state;default:return r}}const He=()=>{const{state:r}=Y(),e=$(),n=te(),t=j(),[o,i]=s.useReducer(Pe,"idle"),l=c=>{i({type:"set",state:c})},m=s.useCallback(()=>{const h=e.getCompletions(t.path())[0]||t.nullSegment;return E.debug("[getNextExpectedSegment] ",h),h},[e,t]),u=s.useCallback(()=>e.getCompletions_s(t.path()).map(h=>e.getCommand([...t.path(),h])).filter(h=>h!==void 0),[e,t]),d=s.useCallback((c,h)=>{if(!c)return h;const f=h.reduce((y,v)=>{const k=m();return(k==null?void 0:k.type)==="word"&&y.set(k.name,v),y},new Map);return Array.from(f.values()).filter(()=>{const y=m();return y.type!=="word"?!1:y.name.toLowerCase().startsWith(c.toLowerCase())})},[m]),p=s.useCallback(c=>{const f=e.getCompletions(t.path()).filter(N=>N.type==="word").filter(N=>N.name.toLowerCase().startsWith(c.toLowerCase()));return f.length===1?f[0]:t.nullSegment},[e,t]),g=s.useCallback(c=>{const h=t.path(),f=e.getCompletions(h);return f.length===0&&c?!1:f.some(y=>y.type==="argument")?!0:f.some(y=>y.type==="word"&&y.name.toLowerCase().startsWith(c.toLowerCase()))},[e,t]),S=s.useCallback(c=>{E.debug("[tryAutoComplete] input: ",c);const h=p(c);return!h||h.name===c?new Z:(E.debug("[tryAutoComplete] result: ",h),h)},[p]),b=s.useCallback((c,h)=>{if(r.history.position===null){if(h.setCurrentInput(c),E.debug("[useCommandParser][handleInputChange] newValue: ",c),o==="entering_argument"){const f=ie(c);if(f.isQuoted)if(f.isComplete){const N=m();if(N.type==="argument"){const y=N;y.value=c.trim()||"",E.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",y),t.push(y),h.setCurrentInput(""),l("idle");return}}else return;else if(f.isComplete){const N=m();N.value=c.trim()||"",E.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",N),t.push(N),h.setCurrentInput(""),l("idle");return}else return}if(o=="entering_command"){const f=S(c);if(f.type==="word"){E.debug("[useCommandParser][handleInputChange][entering_command] pushing: ",f),t.push(f),h.setCurrentInput(""),l("idle");return}}}},[S,r,m,o,t]),C=s.useCallback(c=>{c.setCurrentInput(""),c.setIsEnteringArg(!1),t.clear(),l("idle")},[t]),_=s.useCallback((c,h,f)=>{if(!(c.key==="Backspace"||c.key==="Enter"||c.key==="ArrowUp"||c.key==="ArrowDown"||c.key==="ArrowLeft"||c.key==="ArrowRight"||c.key==="Escape"||c.key==="Delete"||c.key==="Home"||c.key==="End"||c.key.length===1))return!0;const{currentInput:y,isEnteringArg:v}=h,k=ie(y);switch(c.key){case"Backspace":return y===""&&(c.preventDefault(),t.size()>0&&t.pop(),l("idle")),!0;case"Enter":{if(c.preventDefault(),k.isQuoted&&!k.isComplete)return!0;if(o==="entering_argument"||v&&y.trim()){const B=m();B.value=y,E.debug("[handleKeyDown][Enter]['entering_argument'] pushing: ",B),t.push(B)}const z=t.path(),I=e.getCommand(z);if(!I)return!1;const K=I.segments.filter(T=>T.type==="argument"),q=t.arguments;return K.length>q.length?!1:(E.debug("[handleKeyDown][Enter] calling actions.executeCommand. segmentStack: ",t),f.executeCommand(),n.addStoredCommand(t.toArray()),C(f),!0)}case"ArrowUp":return c.preventDefault(),(async()=>{const z=await n.navigateHistory("up",t.toArray());return z.segments&&(t.clear(),t.pushAll(z.segments),f.setCurrentInput("")),!0})();case"ArrowDown":return c.preventDefault(),(async()=>{const z=await n.navigateHistory("down",t.toArray());return z.segments&&(t.clear(),t.pushAll(z.segments),f.setCurrentInput("")),!0})();default:{if(!v&&c.key.length===1){const z=y+c.key;if(!g(z))return c.preventDefault(),!1}return!0}}},[o,g,m,n,C,e,t]);return{handleInputChange:b,handleKeyDown:_,inputState:o,setInputStateWithLogging:l,findMatchingCommands:d,getAutocompleteSuggestion:p,getAvailableNodes:u,getNextExpectedSegment:m,isValidCommandInput:g}};function ie(r){const e=[];let n="",t=!1,o;for(let i=0;i<r.length;i++){const l=r[i];(l==='"'||l==="'")&&(!t||l===o)?t?(e.push(n),n="",t=!1,o=void 0):(n&&(e.push(n),n=""),t=!0,o=l):!t&&l===" "?n&&(e.push(n),n=""):n+=l}return{words:e,currentWord:n,isQuoted:t,quoteChar:o,isComplete:!t&&!n}}const De=()=>{const r=j(),[e,n]=s.useState(0);return s.useEffect(()=>{const t={update:()=>{n(o=>o+1)}};return r.subscribe(t),()=>{r.unsubscribe(t)}},[r]),e},Me=({state:r,actions:e})=>{const n=s.useRef(null),t=$(),o=j(),{handleKeyDown:i,handleInputChange:l,inputState:m,setInputStateWithLogging:u,getNextExpectedSegment:d}=He(),[p,g]=s.useState(!1),S=M(),b=De(),C=async v=>{const k=i(v,r,e);await Promise.resolve(k)===!1&&(g(!0),setTimeout(()=>g(!1),500))},_=v=>{l(v.target.value,e)},c=v=>{v.preventDefault();const k=v.clipboardData.getData("text");l(k,e)};s.useEffect(()=>{n.current&&n.current.focus(),m!=="entering_command"&&u("entering_command")},[m,u]),s.useEffect(()=>{if(m!=="idle")return;const v=d();let k="idle";switch(v.type){case"word":k="entering_command",e.setIsEnteringArg(!1);break;case"argument":k="entering_argument",e.setIsEnteringArg(!0);break}u(k)},[b,m,d,u,e]);const[h,f]=s.useState([]);s.useEffect(()=>{const v=[],k=o.toArray().map((I,K)=>{v.push(I.name);const q=t.hasNextSegment(v);if(I.type==="argument"){const T=I;return a.jsxs(s.Fragment,{children:[a.jsx("span",{className:"text-gray-200 whitespace-pre",children:T.value}),K<o.size()&&q&&a.jsx("span",{className:"text-gray-200 whitespace-pre",children:" "})]},"arg-"+T.name+T.value)}return a.jsxs(s.Fragment,{children:[a.jsx("span",{className:"text-blue-400 whitespace-pre",children:I.name}),K<o.size()&&q&&a.jsx("span",{className:"text-blue-400 whitespace-pre",children:" "})]},"word-"+I.name)});f([a.jsx("div",{className:"flex items-center gap-1","data-testid":"user-input-area",children:k},"{segmentStackVersion}")])},[b,t,o]);const[N,y]=s.useState("");return s.useEffect(()=>{const v=d();v.type==="argument"?y(v.name):y("")},[b,d]),a.jsxs("div",{className:"flex flex-col w-full bg-gray-900 rounded-lg p-4",children:[a.jsx("style",{children:`
|
|
7
7
|
@keyframes subtleGlow {
|
|
8
8
|
0%, 100% { box-shadow: 0 0 0 rgba(239, 68, 68, 0); }
|
|
9
9
|
50% { box-shadow: 0 0 8px rgba(239, 68, 68, 0.6); }
|
|
@@ -11,7 +11,7 @@ Check the definition of the ${p.join(".")} command and update the return type fo
|
|
|
11
11
|
.invalid-input-animation {
|
|
12
12
|
animation: subtleGlow 0.4s ease-in-out;
|
|
13
13
|
}
|
|
14
|
-
`}),
|
|
14
|
+
`}),a.jsxs("div",{className:"flex items-center gap-2",children:[a.jsx("div",{className:"text-gray-400 font-mono",children:">"}),a.jsxs("div",{className:"flex-1 font-mono flex items-center",children:[h,a.jsxs("div",{className:"relative flex-1",children:[a.jsx("input",{ref:n,type:"text",role:"textbox",value:r.currentInput,onChange:_,onKeyDown:C,onPaste:c,"data-testid":"citadel-command-input",className:`w-full bg-transparent outline-none text-gray-200 caret-transparent ${p?"invalid-input-animation":""}`,spellCheck:!1,autoComplete:"off",placeholder:N}),a.jsx("div",{className:"absolute top-0 pointer-events-none",style:{left:`${r.currentInput.length}ch`,transition:"left 0.05s ease-out"},children:a.jsx(Ie,{style:{type:S.cursorType??A.cursorType,color:S.cursorColor||A.cursorColor,speed:S.cursorSpeed||A.cursorSpeed}})})]})]})]})]})},Te=()=>{const r=$(),e=M(),n=j(),t="mt-2 border-t border-gray-700 px-4 py-2",o="text-gray-300",i=r.getCompletions(n.path());E.debug("[AvailableCommands] nextCommandSegments: ",i);const l=s.useMemo(()=>{const d=[...i],p=C=>C.name.toLowerCase()==="help",S=d.filter(C=>!p(C)).sort((C,_)=>C.name.localeCompare(_.name,void 0,{sensitivity:"base"}));if(!e.includeHelpCommand)return S;const b=d.find(p);return b?[...S,b]:S},[i,e.includeHelpCommand]),m=i.some(d=>d.type==="argument"),u=i[0];return a.jsx("div",{className:t,"data-testid":"available-commands",children:a.jsx("div",{className:o,children:m?i.length>0?a.jsxs(a.Fragment,{children:[a.jsx("span",{className:"text-blue-400",children:u.name}),u.description&&a.jsxs("span",{className:"text-gray-400 ml-2",children:["- ",u.description]})]}):null:a.jsx("div",{className:"flex flex-wrap gap-2",children:l==null?void 0:l.map(d=>{const p=l==null?void 0:l.reduce((g,S)=>{if(S===d)return g;let b=0;for(;b<d.name.length&&b<S.name.length&&d.name[b].toLowerCase()===S.name[b].toLowerCase();)b++;return Math.max(g,b+1)},1);return a.jsx("div",{className:"px-2 py-1 rounded bg-gray-800 mr-2 last:mr-0",children:a.jsxs("span",{className:"font-mono text-white",children:[a.jsx("strong",{className:"underline",children:d.name.slice(0,p)}),d.name.slice(p)]})},d.name)})})})})},le=({state:r,actions:e,outputRef:n})=>a.jsxs("div",{className:"innerContainer",children:[a.jsx("div",{className:"flex-1 min-h-0 pt-3 px-4",children:a.jsx(ze,{output:r.output,outputRef:n})}),a.jsxs("div",{children:[a.jsx(Me,{state:r,actions:e}),a.jsx(Te,{})]})]}),Ue=()=>{const[r,e]=s.useState(!1),[n,t]=s.useState(!1),o=M(),[i,l]=s.useState(()=>o.initialHeight||null),m=s.useRef(null),u=s.useRef(null),d=s.useRef(!1),p=s.useRef(0),g=s.useRef(0),{state:S,actions:b}=Y();Ee({onOpen:()=>e(!0),onClose:()=>t(!0),isVisible:r,showCitadelKey:o.showCitadelKey||"."});const C=s.useCallback(f=>{var k;if(!d.current)return;const N=f.clientY-p.current,y=(k=o.maxHeight)!=null&&k.endsWith("vh")?window.innerHeight*parseInt(o.maxHeight,10)/100:parseInt(o.maxHeight||"80vh",10),v=Math.min(Math.max(g.current-N,parseInt(o.minHeight||"200",10)),y);u.current&&(u.current.style.height=`${v}px`,u.current.style.bottom="0",l(`${v}px`))},[o.maxHeight,o.minHeight]),_=s.useCallback(()=>{d.current=!1,document.documentElement.style.userSelect="",document.documentElement.style.webkitUserSelect="",document.documentElement.style.mozUserSelect="",document.documentElement.style.msUserSelect="",document.removeEventListener("mousemove",C),document.removeEventListener("mouseup",_)},[C]),c=s.useCallback(f=>{u.current&&(d.current=!0,p.current=f.clientY,g.current=u.current.offsetHeight,document.documentElement.style.userSelect="none",document.documentElement.style.webkitUserSelect="none",document.documentElement.style.mozUserSelect="none",document.documentElement.style.msUserSelect="none",document.addEventListener("mousemove",C),document.addEventListener("mouseup",_))},[C,_]);s.useEffect(()=>()=>{document.removeEventListener("mousemove",C),document.removeEventListener("mouseup",_)},[C,_]);const h=s.useCallback(()=>{n&&(e(!1),t(!1))},[n]);return _e({isVisible:r,isClosing:n,onAnimationComplete:h}),r?a.jsxs("div",{ref:u,className:`panelContainer ${r?"citadel_slideUp":""} ${n?"citadel_slideDown":""}`,style:{...i?{height:i}:void 0,maxHeight:o.maxHeight},children:[a.jsx("div",{className:"resizeHandle",onMouseDown:c}),a.jsx(le,{state:S,actions:b,outputRef:m})]}):null},je=()=>{const{state:r,actions:e}=Y(),n=s.useRef(null);return a.jsx("div",{className:"inlineContainer","data-testid":"citadel-inline-container",children:a.jsx(le,{state:r,actions:e,outputRef:n})})},ce=`:host {
|
|
15
15
|
--citadel-bg: rgb(17, 24, 39);
|
|
16
16
|
--citadel-text: rgba(255, 255, 255, 0.87);
|
|
17
17
|
--citadel-border: rgb(55, 65, 81);
|
|
@@ -22,23 +22,38 @@ Check the definition of the ${p.join(".")} command and update the return type fo
|
|
|
22
22
|
--citadel-default-height: 35vh;
|
|
23
23
|
--citadel-error: rgb(239, 68, 68);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
display: block;
|
|
26
|
+
pointer-events: auto;
|
|
27
|
+
font-synthesis: none;
|
|
28
|
+
text-rendering: optimizeLegibility;
|
|
29
|
+
-webkit-font-smoothing: antialiased;
|
|
30
|
+
-moz-osx-font-smoothing: grayscale;
|
|
31
|
+
}
|
|
29
32
|
|
|
33
|
+
:host([data-display-mode="panel"]) {
|
|
30
34
|
position: fixed;
|
|
31
35
|
bottom: 0;
|
|
32
36
|
left: 0;
|
|
33
37
|
right: 0;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: var(--citadel-default-height);
|
|
40
|
+
max-height: var(--citadel-max-height);
|
|
41
|
+
min-height: var(--citadel-min-height);
|
|
34
42
|
z-index: 2147483647; /* Maximum z-index value */
|
|
35
43
|
overflow: hidden;
|
|
44
|
+
}
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
:host([data-display-mode="inline"]) {
|
|
47
|
+
position: relative;
|
|
48
|
+
bottom: auto;
|
|
49
|
+
left: auto;
|
|
50
|
+
right: auto;
|
|
51
|
+
z-index: auto;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
max-height: none;
|
|
55
|
+
min-height: 0;
|
|
56
|
+
overflow: hidden;
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
button {
|
|
@@ -100,10 +115,9 @@ a:hover {
|
|
|
100
115
|
.text-left {
|
|
101
116
|
text-align: left;
|
|
102
117
|
}
|
|
118
|
+
`,de=`/* Keep only component-specific styles here */
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.container {
|
|
120
|
+
.panelContainer {
|
|
107
121
|
position: fixed;
|
|
108
122
|
height: var(--citadel-default-height);
|
|
109
123
|
min-height: var(--citadel-min-height);
|
|
@@ -121,6 +135,7 @@ a:hover {
|
|
|
121
135
|
|
|
122
136
|
.innerContainer {
|
|
123
137
|
height: 100%;
|
|
138
|
+
flex: 1;
|
|
124
139
|
width: 100%;
|
|
125
140
|
display: flex;
|
|
126
141
|
flex-direction: column;
|
|
@@ -171,10 +186,21 @@ a:hover {
|
|
|
171
186
|
.citadel_slideDown {
|
|
172
187
|
animation: citadel_slideDown 0.2s ease-out forwards;
|
|
173
188
|
}
|
|
174
|
-
|
|
189
|
+
|
|
190
|
+
.inlineContainer {
|
|
191
|
+
position: relative;
|
|
192
|
+
width: 100%;
|
|
193
|
+
height: 100%;
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
background-color: var(--citadel-bg);
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
box-sizing: border-box;
|
|
199
|
+
}
|
|
200
|
+
`,me=`@tailwind base;
|
|
175
201
|
@tailwind components;
|
|
176
202
|
@tailwind utilities;
|
|
177
|
-
`,
|
|
203
|
+
`,Fe=`*, ::before, ::after {
|
|
178
204
|
--tw-border-spacing-x: 0;
|
|
179
205
|
--tw-border-spacing-y: 0;
|
|
180
206
|
--tw-translate-x: 0;
|
|
@@ -991,5 +1017,5 @@ video {
|
|
|
991
1017
|
.last\\:mr-0:last-child {
|
|
992
1018
|
margin-right: 0px;
|
|
993
1019
|
}
|
|
994
|
-
`,
|
|
995
|
-
`),o=document.createElement("style");o.textContent=t,this.shadow.appendChild(o)}const n=document.createElement("div");n.id="citadel-root",this.shadow.appendChild(n),this.root=
|
|
1020
|
+
`,Oe=({config:r=A,commandRegistry:e=new L,containerId:n=null})=>{const t=s.useRef(null),o=r.displayMode??A.displayMode??"panel";return s.useEffect(()=>{E.configure({level:r.logLevel||A.logLevel||F.ERROR,prefix:"[Citadel]"});const i=new ue(e,r),l=o==="inline"&&!n,m=l?t.current:n?document.getElementById(n):document.body;if(m)m.appendChild(i);else{if(l){console.warn("[Citadel] No host available for inline mode; skipping mount.");return}console.warn(`Container with id "${n}" not found, falling back to body`),document.body.appendChild(i)}return()=>{var u;(u=i.parentElement)==null||u.removeChild(i)}},[e,n,r,o]),o==="inline"&&!n?a.jsx("div",{ref:t,style:{width:"100%",height:"100%"}}):null};class ue extends HTMLElement{constructor(n,t){var i;super();x(this,"shadow");x(this,"root",null);x(this,"commandRegistry");x(this,"config");this.shadow=this.attachShadow({mode:"open"}),this.commandRegistry=n,this.config=t;const o=((i=this.config)==null?void 0:i.displayMode)??"panel";this.setAttribute("data-display-mode",o)}connectedCallback(){try{const t=[ce,de,me,Fe].map(o=>{const i=new CSSStyleSheet;return i.replaceSync(o),i});this.shadow.adoptedStyleSheets=[...t]}catch{const t=[ce,de,me].join(`
|
|
1021
|
+
`),o=document.createElement("style");o.textContent=t,this.shadow.appendChild(o)}const n=document.createElement("div");n.id="citadel-root",n.style.width="100%",n.style.height="100%",this.shadow.appendChild(n),this.root=pe.createRoot(n),this.root.render(a.jsx(Se,{config:this.config||A,commandRegistry:this.commandRegistry,children:a.jsx(Le,{})}))}}customElements.define("citadel-element",ue);const Le=()=>(M().displayMode??"panel")==="inline"?a.jsx(je,{}):a.jsx(Ue,{});w.Citadel=Oe,w.CommandRegistry=L,w.CommandResult=P,w.CommandStatus=D,w.DEFAULT_CURSOR_CONFIGS=oe,w.ErrorCommandResult=G,w.ImageCommandResult=ge,w.JsonCommandResult=he,w.OutputItem=ne,w.PendingCommandResult=J,w.TextCommandResult=V,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
1
2
|
import { CitadelConfig } from './config/types';
|
|
2
3
|
import { CommandRegistry } from './types/command-registry';
|
|
3
|
-
|
|
4
|
-
config?: CitadelConfig
|
|
5
|
-
commandRegistry?: CommandRegistry
|
|
6
|
-
containerId?:
|
|
7
|
-
}
|
|
4
|
+
interface CitadelProps {
|
|
5
|
+
config?: CitadelConfig;
|
|
6
|
+
commandRegistry?: CommandRegistry;
|
|
7
|
+
containerId?: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Top-level entry point for embedding Citadel.
|
|
11
|
+
*
|
|
12
|
+
* @param config Optional `CitadelConfig` describing runtime behaviour (keyboard shortcuts, logging, sizing)
|
|
13
|
+
* with `defaultConfig` used when omitted.
|
|
14
|
+
* @param commandRegistry Optional pre-populated registry. A fresh instance is created by default so consumers
|
|
15
|
+
* can register commands before mounting.
|
|
16
|
+
* @param containerId Optional DOM id where the custom element should be appended. When not supplied the
|
|
17
|
+
* component appends to `document.body` in panel mode and to an internal host in inline mode.
|
|
18
|
+
*/
|
|
19
|
+
export declare const Citadel: React.FC<CitadelProps>;
|
|
8
20
|
export declare class CitadelElement extends HTMLElement {
|
|
9
21
|
private shadow;
|
|
10
22
|
private root;
|
|
@@ -13,3 +25,4 @@ export declare class CitadelElement extends HTMLElement {
|
|
|
13
25
|
constructor(commandRegistry: CommandRegistry, config?: CitadelConfig);
|
|
14
26
|
connectedCallback(): void;
|
|
15
27
|
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { CitadelState, CitadelActions } from '../types/state';
|
|
3
|
+
interface CitadelTtyProps {
|
|
4
|
+
state: CitadelState;
|
|
5
|
+
actions: CitadelActions;
|
|
6
|
+
outputRef: React.RefObject<HTMLDivElement>;
|
|
7
|
+
}
|
|
8
|
+
export declare const CitadelTty: React.FC<CitadelTtyProps>;
|
|
9
|
+
export {};
|
|
@@ -61,6 +61,12 @@ export interface CitadelConfig {
|
|
|
61
61
|
* The keyboard key that shows the command interface.
|
|
62
62
|
*/
|
|
63
63
|
showCitadelKey?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Presentation mode for rendering the Citadel interface.
|
|
66
|
+
* - 'panel': Renders as an overlay panel anchored to the viewport bottom and toggled via keyboard shortcuts.
|
|
67
|
+
* - 'inline': Renders directly within the host container and remains visible at all times.
|
|
68
|
+
*/
|
|
69
|
+
displayMode?: 'panel' | 'inline';
|
|
64
70
|
/**
|
|
65
71
|
* Configuration for command history storage
|
|
66
72
|
*/
|
|
@@ -55,13 +55,24 @@ export declare class CommandRegistry {
|
|
|
55
55
|
private _commands;
|
|
56
56
|
get commands(): CommandNode[];
|
|
57
57
|
/**
|
|
58
|
-
* Registers a new command
|
|
58
|
+
* Registers a new command composed of ordered segments.
|
|
59
59
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* Each segment describes either a literal word or an argument placeholder. The resulting
|
|
61
|
+
* path must be unique across the registry once argument placeholders are normalized.
|
|
62
62
|
*
|
|
63
|
+
* @param segments Ordered command path definition.
|
|
64
|
+
* @param description Human-readable summary surfaced by help and search results.
|
|
65
|
+
* @param handler Async handler executed when the command is submitted; defaults to `NoopHandler`.
|
|
66
|
+
* @throws {Error} If the segment list is empty or the path collides with an existing command.
|
|
63
67
|
*/
|
|
64
68
|
addCommand(segments: CommandSegment[], description: string, handler?: CommandHandler): void;
|
|
69
|
+
/**
|
|
70
|
+
* Removes a command that exactly matches the provided path.
|
|
71
|
+
*
|
|
72
|
+
* @param path The command path to remove.
|
|
73
|
+
* @returns True if a command was removed; otherwise false.
|
|
74
|
+
*/
|
|
75
|
+
removeCommand(path: string[]): boolean;
|
|
65
76
|
/**
|
|
66
77
|
* Retrieves a command from the registry for the given path.
|
|
67
78
|
*
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CommandRegistry } from '../components/Citadel/types/command-registry';
|
|
2
|
+
/**
|
|
3
|
+
* Build a fresh registry populated with the basic sample commands.
|
|
4
|
+
* Used by the demo app and example bundles.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createBasicCommandRegistry(): CommandRegistry;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"type": "git",
|
|
18
18
|
"url": "git+https://github.com/jchilders/citadel_cli.git"
|
|
19
19
|
},
|
|
20
|
-
"version": "1.1.
|
|
20
|
+
"version": "1.1.6",
|
|
21
21
|
"type": "module",
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && vite build",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"preview": "vite preview",
|
|
28
28
|
"push": "NODE_ENV=production && npm run build && npm push",
|
|
29
29
|
"test": "vitest --run",
|
|
30
|
-
"
|
|
30
|
+
"test:e2e": "playwright test",
|
|
31
|
+
"test:e2e:ui": "playwright test --ui",
|
|
32
|
+
"coverage": "vitest run --coverage",
|
|
33
|
+
"postinstall": "playwright install"
|
|
31
34
|
},
|
|
32
35
|
"exports": {
|
|
33
36
|
".": {
|
|
@@ -54,6 +57,7 @@
|
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
59
|
"@eslint/js": "^9.13.0",
|
|
60
|
+
"@playwright/test": "^1.56.0",
|
|
57
61
|
"@testing-library/jest-dom": "^6.6.3",
|
|
58
62
|
"@testing-library/react": "^16.1.0",
|
|
59
63
|
"@testing-library/user-event": "^14.5.2",
|