clay-util
Version:
A beautiful, modern terminal for the web - Perfect for ChromeOS users without terminal access
31 lines (30 loc) • 9.8 kB
JavaScript
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("xterm"),u=require("xterm-addon-fit"),m=require("xterm-addon-web-links"),p=require("xterm-addon-canvas");class c{constructor(e="ws://127.0.0.1:8765/ws"){this.ws=null,this.sessionId=null,this.isConnected=!1,this.onOutputCallback=null,this.onExitCallback=null,this.onErrorCallback=null,this.bridgeUrl=e}async connect(){return new Promise((e,t)=>{try{this.ws=new WebSocket(this.bridgeUrl),this.ws.onopen=()=>{this.isConnected=!0},this.ws.onmessage=s=>{try{const n=JSON.parse(s.data);switch(n.type){case"connected":this.sessionId=n.sessionId,this.onOutputCallback&&this.onOutputCallback(`\x1B[32m[Connected]\x1B[0m Bridge: ${n.shell}\r
`),e();break;case"output":this.onOutputCallback&&n.sessionId===this.sessionId&&this.onOutputCallback(n.data);break;case"exit":this.onExitCallback&&n.sessionId===this.sessionId&&this.onExitCallback(n.code||0,n.signal||0);break;case"error":this.onErrorCallback&&this.onErrorCallback(n.message),t(new Error(n.message));break}}catch(n){console.error("Error parsing message:",n)}},this.ws.onerror=s=>{this.isConnected=!1,this.onErrorCallback&&this.onErrorCallback("Connection error"),t(s)},this.ws.onclose=()=>{this.isConnected=!1}}catch(s){t(s)}})}disconnect(){this.ws&&(this.ws.close(),this.ws=null),this.isConnected=!1}sendInput(e){this.ws&&this.isConnected&&this.sessionId&&this.ws.send(JSON.stringify({type:"input",sessionId:this.sessionId,data:e}))}resize(e,t){this.ws&&this.isConnected&&this.sessionId&&this.ws.send(JSON.stringify({type:"resize",sessionId:this.sessionId,cols:e,rows:t}))}onOutput(e){this.onOutputCallback=e}onExit(e){this.onExitCallback=e}onError(e){this.onErrorCallback=e}getConnected(){return this.isConnected}getSessionId(){return this.sessionId}async executeCommand(e,t){try{const n=await(await fetch("http://127.0.0.1:8765/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({command:e,cwd:t})})).json();return{output:n.output||"",exitCode:n.exitCode||(n.success?0:1)}}catch(s){return{output:`Error: ${s.message}`,exitCode:1}}}async getSystemInfo(){try{return await(await fetch("http://127.0.0.1:8765/api/info")).json()}catch{return null}}async healthCheck(){try{return(await(await fetch("http://127.0.0.1:8765/api/health")).json()).status==="ok"}catch{return!1}}}class l{constructor(){this.sessionId=null,this.isConnected=!1,this.onOutputCallback=null,this.onExitCallback=null,this.onErrorCallback=null}async connect(){return this.isConnected=!0,this.sessionId=`session_${Date.now()}`,this.onOutputCallback&&(this.onOutputCallback(`\x1B[33m[INFO]\x1B[0m Web Worker backend (stub mode)\r
`),this.onOutputCallback(`\x1B[33m[INFO]\x1B[0m For full functionality, use BridgeBackend or provide worker implementation\r
`)),Promise.resolve()}disconnect(){this.isConnected=!1,this.sessionId=null}sendInput(e){if(this.onOutputCallback&&e.trim()){const t=e.trim();if(t.endsWith(`\r
`)||t.endsWith(`
`)){const s=t.replace(/\r?\n$/,"");s&&(this.onOutputCallback(`\r
$ ${s}\r
`),s==="help"?this.onOutputCallback(`Available commands: help, echo, pwd\r
`):s.startsWith("echo ")?this.onOutputCallback(s.substring(5)+`\r
`):s==="pwd"?this.onOutputCallback(`/home/user\r
`):this.onOutputCallback(`\x1B[33m[Note]\x1B[0m For full command support, use BridgeBackend with bridge server\r
`))}}}resize(e,t){}onOutput(e){this.onOutputCallback=e}onExit(e){this.onExitCallback=e}onError(e){this.onErrorCallback=e}getConnected(){return this.isConnected}async executeCommand(e,t){return e.trim()==="help"?{output:`Available commands: help, echo, pwd
`,exitCode:0}:e.startsWith("echo ")?{output:e.substring(5)+`
`,exitCode:0}:e.trim()==="pwd"?{output:`/home/user
`,exitCode:0}:{output:`Command not available in stub mode. Use BridgeBackend for full support.
`,exitCode:1}}async getSystemInfo(){return{platform:"web",shell:"/bin/bash",cwd:"/home/user",homeDir:"/home/user"}}}class h{constructor(e){this.backend=null,this.isConnected=!1,this.commandHistory=[],this.sessionCommands=[],this.outputCallbacks=[],this.errorCallbacks=[],this.statusCallbacks=[],this.config=e,this.terminal=new d.Terminal({theme:e.theme||this.getDefaultTheme(),fontSize:e.fontSize||13,fontFamily:e.fontFamily||'"SF Mono", "Menlo", "Monaco", "DejaVu Sans Mono", "Lucida Console", monospace',cursorBlink:!0,cursorStyle:"block",allowTransparency:!0,lineHeight:1.6,letterSpacing:.3}),this.fitAddon=new u.FitAddon,this.terminal.loadAddon(this.fitAddon),this.terminal.loadAddon(new m.WebLinksAddon),this.terminal.loadAddon(new p.CanvasAddon)}async initialize(){if(!this.config.container)throw new Error("Container element is required");this.terminal.open(this.config.container),setTimeout(()=>{this.fitAddon.fit()},100);let e;window.addEventListener("resize",()=>{clearTimeout(e),e=setTimeout(()=>{if(this.fitAddon.fit(),this.backend&&this.backend.getConnected()){const s=this.fitAddon.proposeDimensions();s&&this.backend.resize(s.cols,s.rows)}},150)}),this.terminal.onData(s=>{this.isConnected&&this.backend&&this.backend.getConnected()&&this.backend.sendInput(s)}),await this.setupBackend(),this.config.showWelcome!==!1&&this.printWelcomeMessage()}async setupBackend(){if(this.config.bridgeUrl||this.config.autoConnectBridge){const e=this.config.bridgeUrl||"ws://127.0.0.1:8765/ws";this.backend=new c(e);try{if(await this.backend.healthCheck()){await this.connectToBackend();return}}catch{console.warn("Bridge not available, using Web Worker fallback")}}this.backend=new l,await this.connectToBackend()}async connectToBackend(){if(this.backend){this.backend.onOutput(e=>{this.terminal.write(e),this.outputCallbacks.forEach(t=>t(e))}),this.backend.onExit((e,t)=>{this.terminal.write(`\r
\x1B[33m[Process exited]\x1B[0m Code: ${e}\r
`)}),this.backend.onError(e=>{this.terminal.write(`\r
\x1B[31m[Connection Error]\x1B[0m ${e}\r
`),this.errorCallbacks.forEach(t=>t(e)),this.updateStatus({backend:"error",ai:"idle"})});try{await this.backend.connect(),this.isConnected=!0,this.updateStatus({backend:"connected",ai:"idle"})}catch(e){throw this.updateStatus({backend:"error",ai:"idle"}),e}}}write(e){this.terminal.write(e)}async executeCommand(e){if(!this.backend||!this.backend.getConnected())throw new Error("Backend not connected");this.sessionCommands.push(e),this.backend.sendInput(e+`\r
`)}getHistory(){return[...this.commandHistory]}getSessionCommands(){return[...this.sessionCommands]}clear(){this.terminal.clear()}resize(){if(this.fitAddon.fit(),this.backend&&this.backend.getConnected()){const e=this.fitAddon.proposeDimensions();e&&this.backend.resize(e.cols,e.rows)}}onOutput(e){this.outputCallbacks.push(e)}onError(e){this.errorCallbacks.push(e)}onStatusChange(e){this.statusCallbacks.push(e)}updateStatus(e){this.statusCallbacks.forEach(t=>t(e))}dispose(){this.backend&&this.backend.disconnect(),this.terminal.dispose()}getDefaultTheme(){return{background:"#1e1e2e",foreground:"#cdd6f4",cursor:"#cba6f7",black:"#45475a",red:"#f38ba8",green:"#a6e3a1",yellow:"#f9e2af",blue:"#89b4fa",magenta:"#cba6f7",cyan:"#89dceb",white:"#cdd6f4",brightBlack:"#585b70",brightRed:"#f38ba8",brightGreen:"#a6e3a1",brightYellow:"#f9e2af",brightBlue:"#89b4fa",brightMagenta:"#cba6f7",brightCyan:"#89dceb",brightWhite:"#f5e0dc"}}printWelcomeMessage(){this.terminal.write(`\r
`),this.terminal.write(`\x1B[1m\x1B[35m╔════════════════════════════════════════╗\x1B[0m\r
`),this.terminal.write(`\x1B[1m\x1B[35m║\x1B[0m \x1B[1m\x1B[36mClay Terminal\x1B[0m - The best terminal experience \x1B[1m\x1B[35m║\x1B[0m\r
`),this.terminal.write(`\x1B[1m\x1B[35m╚════════════════════════════════════════╝\x1B[0m\r
`),this.terminal.write(`\r
`),this.terminal.write(` \x1B[33m✨\x1B[0m Perfect for ChromeOS users\r
`),this.terminal.write(` \x1B[33m✨\x1B[0m Type \x1B[33mhelp\x1B[0m for available commands\r
`),this.terminal.write(`\r
`)}}class b{static encode(e){if(e.length===0)return"";const t=JSON.stringify(e),s=this.compressString(t);return btoa(s).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static decode(e){if(!e||e.length===0)return[];try{const t=e.replace(/-/g,"+").replace(/_/g,"/"),s=(4-t.length%4)%4,n=t+"=".repeat(s),i=atob(n),r=this.decompressString(i),a=JSON.parse(r);return Array.isArray(a)?a:[]}catch(t){return console.error("Failed to decode session:",t),[]}}static compressString(e){let t=e;const s={"cd ":"c","ls ":"l","cat ":"t","echo ":"e","mkdir ":"m","rm ":"r","grep ":"g","find ":"f","git ":"i","npm ":"n","python ":"p","node ":"o","sudo ":"s","../":"u","./":"d"," && ":"&"," | ":"|","~":"h"};for(const[n,i]of Object.entries(s))t=t.replace(new RegExp(this.escapeRegex(n),"g"),i);return t=t.replace(/\s{3,}/g,n=>`_${n.length}`),t}static decompressString(e){let t=e;t=t.replace(/_(\d+)/g,(i,r)=>" ".repeat(parseInt(r)));const n=Object.entries({c:"cd ",l:"ls ",t:"cat ",e:"echo ",m:"mkdir ",r:"rm ",g:"grep ",f:"find ",i:"git ",n:"npm ",p:"python ",o:"node ",s:"sudo ",u:"../",d:"./","&":" && ","|":" | ",h:"~"}).sort((i,r)=>r[0].length-i[0].length);for(const[i,r]of n)t=t.replace(new RegExp(this.escapeRegex(i),"g"),r);return t}static escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}static generateShareUrl(e,t){const s=this.encode(e);return s?`${t||window.location.origin+window.location.pathname}?sesh=${s}`:""}static parseShareUrl(e){const t=e||window.location.href,n=new URL(t).searchParams.get("sesh");return n?this.decode(n):[]}}async function f(o){const e=new h(o);return await e.initialize(),e}exports.BridgeBackend=c;exports.ClayTerminal=h;exports.SessionEncoder=b;exports.WebWorkerBackend=l;exports.createClayTerminal=f;
//# sourceMappingURL=clay-util.js.map