tmux-weblink 1.2.0 → 1.2.2

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.
@@ -1,10 +1,10 @@
1
- var Ke="https://esm.sh/ghostty-web@0.4.0",Xe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function W(t){return t.replace(Xe,"")}var q;function _e(){return q||(document.querySelector("link[data-tmux-web-xterm-css]")?q=Promise.resolve():(q=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),q))}var me=class xe{terminal;fitAddon;container;constructor(e,a,o){this.container=e,this.terminal=a,this.fitAddon=o,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,a,o){let[{Terminal:s},{FitAddon:c}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),_e()]),u=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new xe(e,u,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=pe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}},Ue=class ke{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,a){this.container=e,this.terminal=a,this.terminal.open(e)}static async create(e,a,o){let s=await import(Ke);await s.init();let c=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new ke(e,c)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=pe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let a=Math.floor(e.width/this.charW),o=Math.floor(e.height/this.charH);if(a<10||o<5)return!1;if(a!==this.colsValue||o!==this.rowsValue){this.colsValue=a,this.rowsValue=o,this.terminal.resize(a,o);for(let s of this.resizeCallbacks)s({cols:a,rows:o})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let a=this.terminal.getMode?.(2004)??!1;this.input(a?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(a=>e(a))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){let a=this.container.querySelector("canvas");if(a instanceof HTMLElement&&a.offsetWidth>0&&a.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let o=a.offsetWidth/this.colsValue,s=a.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=o,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function pe(t){let e=t.getBoundingClientRect(),a=window.visualViewport;return a?{width:Math.min(e.width,a.width),height:Math.max(0,Math.min(e.height,a.height-Math.max(0,e.top)))}:e}function Je(t,e,a){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let o;if(a.renderer==="ghostty")try{o=await Ue.create(t,a.scrollback,a.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),o=await me.create(t,a.scrollback,a.theme)}else o=await me.create(t,a.scrollback,a.theme);let s,c=0,u,m=null,J=0,b="connecting",N=0,j=!1,A=[],C="",$=!1,B=1e3,Te=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),Ce="/api/session/"+encodeURIComponent(e)+"/upload",Be=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Se(){return A.join("")+C}async function Me(n,i=0){let r=n?o.viewportY():0,h=n?o.baseY():0,v=W(Se());if(o.reset(),!v){n||o.scrollToBottom();return}await o.write(v),n?o.scrollToLine(h+r+i):o.scrollToBottom()}function S(n){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(n))}function Ie(n){let i;try{i=JSON.parse(n)}catch{b==="live"&&(C+=n,o.write(W(n)));return}if(i.type==="auth.required"){let r=localStorage.getItem("tmux-web-token");r?S({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(i.type==="auth.ok"){i.token&&localStorage.setItem("tmux-web-token",i.token),B=1e3,M(),S({type:"resize",cols:o.cols,rows:o.rows}),p();return}if(i.type==="auth.failed"){if(i.permanentLock||i.retryAfterMs&&i.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(i.message);return}return}if(i.type==="snapshot"&&typeof i.data=="string"){let r=W(i.data);A=[r],C="",N=typeof i.lines=="number"?i.lines:a.terminal.initialLines,b="live",o.reset(),o.write(r).then(()=>o.scrollToBottom());return}if(i.type==="window_changed"&&typeof i.activeIndex=="number"){let r=new URL(location.href);r.searchParams.get("window")!==String(i.activeIndex)&&(r.searchParams.set("window",String(i.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:i.windows}));return}if(i.type==="history"&&typeof i.data=="string"){j=!1,i.lines>0&&i.data&&(A.unshift(i.data),N+=i.lines,Me(!0,i.lines));return}if(i.type==="data"&&typeof i.data=="string"){let r=W(i.data);if(b==="connecting"){b="live",C=r,o.write(r);return}b==="live"&&(C+=r,o.write(r))}}function ne(){let n=Te,i=localStorage.getItem("tmux-web-token");i&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(i)),s=new WebSocket(n),s.onopen=()=>{b="connecting",N=0,j=!1,A=[],C="",o.reset(),B=1e3},s.onmessage=r=>{typeof r.data=="string"&&Ie(r.data)},s.onclose=()=>{$||(b="connecting",setTimeout(()=>{B=Math.min(B*2,1e4),$||ne()},B))},s.onerror=()=>s?.close()}function R(n){S({type:"input",data:n})}function M(){return o.fit()}function Z(){t.classList.remove("terminal-pending")}function p(){c&&cancelAnimationFrame(c),u&&clearTimeout(u),M(),c=requestAnimationFrame(()=>{c=0,M()&&Z()}),u=setTimeout(()=>{M()&&Z()},120)}function He(){p(),setTimeout(p,50),setTimeout(p,150),setTimeout(p,300)}function ie(n){return typeof n=="string"&&n.startsWith("image/")}function ae(n){return n?.files?.length?n.files[0]??null:null}function De(n){if(!n?.items)return null;for(let i=0;i<n.items.length;i++){let r=n.items[i];if(r?.kind==="file"&&ie(r.type))return r.getAsFile()}return null}async function ze(n){let i=new FormData,r=n instanceof File&&n.name?n.name:"upload";i.append("file",n,r);let h=localStorage.getItem("tmux-web-token"),v={};h&&(v.Authorization="Bearer "+h);let Q=await fetch(Ce,{method:"POST",body:i,headers:v});if(!Q.ok){let de="upload failed";try{let ue=await Q.json();ue.error&&(de=ue.error)}catch{}throw new Error(de)}let ce=await Q.json();if(!ce.path)throw new Error("no path in response");return ce.path}async function G(n){try{let i=await ze(n);R(i)}catch{}}o.onData(n=>R(n)),o.onResize(({cols:n,rows:i})=>S({type:"resize",cols:n,rows:i})),o.onScroll(()=>{b!=="live"||j||!o.isNearScrollbackTop()||(j=!0,S({type:"load_history",before:N}))}),o.attachCustomKeyEventHandler(n=>n.type!=="keydown"?!0:(n.ctrlKey||n.metaKey)&&n.code==="KeyV"?(n.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let i=await navigator.clipboard.read();for(let r of i)for(let h of r.types)if(ie(h)){let v=await r.getType(h);await G(v);return}}}catch{}try{let i=await navigator.clipboard?.readText();i&&o.pasteText(oe(i))}catch{}})(),!1):!0);let O=0;function oe(n){return n.split(`\r
1
+ var Ke="https://esm.sh/ghostty-web@0.4.0",Xe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function W(t){return t.replace(Xe,"")}var q;function _e(){return q||(document.querySelector("link[data-tmux-web-xterm-css]")?q=Promise.resolve():(q=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),q))}var pe=class xe{terminal;fitAddon;container;constructor(e,a,o){this.container=e,this.terminal=a,this.fitAddon=o,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,a,o){let[{Terminal:s},{FitAddon:c}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),_e()]),u=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new xe(e,u,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=he(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}},Ue=class ke{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,a){this.container=e,this.terminal=a,this.terminal.open(e)}static async create(e,a,o){let s=await import(Ke);await s.init();let c=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,theme:o});return new ke(e,c)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(a=>this.terminal.write(e,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=he(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let a=Math.floor(e.width/this.charW),o=Math.floor(e.height/this.charH);if(a<10||o<5)return!1;if(a!==this.colsValue||o!==this.rowsValue){this.colsValue=a,this.rowsValue=o,this.terminal.resize(a,o);for(let s of this.resizeCallbacks)s({cols:a,rows:o})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let a=this.terminal.getMode?.(2004)??!1;this.input(a?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(a=>e(a))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){let a=this.container.querySelector("canvas");if(a instanceof HTMLElement&&a.offsetWidth>0&&a.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let o=a.offsetWidth/this.colsValue,s=a.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=o,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function he(t){let e=t.getBoundingClientRect(),a=window.visualViewport;return a?{width:Math.min(e.width,a.width),height:Math.max(0,Math.min(e.height,a.height-Math.max(0,e.top)))}:e}function Je(t,e,a){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let o;if(a.renderer==="ghostty")try{o=await Ue.create(t,a.scrollback,a.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),o=await pe.create(t,a.scrollback,a.theme)}else o=await pe.create(t,a.scrollback,a.theme);let s,c=0,u,m=null,J=0,b="connecting",N=0,j=!1,A=[],C="",$=!1,B=1e3,Te=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),Ce="/api/session/"+encodeURIComponent(e)+"/upload",Be=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Se(){return A.join("")+C}async function Me(n,i=0){let r=n?o.viewportY():0,h=n?o.baseY():0,v=W(Se());if(o.reset(),!v){n||o.scrollToBottom();return}await o.write(v),n?o.scrollToLine(h+r+i):o.scrollToBottom()}function S(n){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(n))}function Ie(n){let i;try{i=JSON.parse(n)}catch{b==="live"&&(C+=n,o.write(W(n)));return}if(i.type==="auth.required"){let r=localStorage.getItem("tmux-web-token");r?S({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(i.type==="auth.ok"){i.token&&localStorage.setItem("tmux-web-token",i.token),B=1e3,M(),S({type:"resize",cols:o.cols,rows:o.rows}),p();return}if(i.type==="auth.failed"){if(i.permanentLock||i.retryAfterMs&&i.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(i.message);return}return}if(i.type==="snapshot"&&typeof i.data=="string"){let r=W(i.data);A=[r],C="",N=typeof i.lines=="number"?i.lines:a.terminal.initialLines,b="live",o.reset(),o.write(r).then(()=>o.scrollToBottom());return}if(i.type==="window_changed"&&typeof i.activeIndex=="number"){let r=new URL(location.href);r.searchParams.get("window")!==String(i.activeIndex)&&(r.searchParams.set("window",String(i.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:i.windows}));return}if(i.type==="history"&&typeof i.data=="string"){j=!1,i.lines>0&&i.data&&(A.unshift(i.data),N+=i.lines,Me(!0,i.lines));return}if(i.type==="data"&&typeof i.data=="string"){let r=W(i.data);if(b==="connecting"){b="live",C=r,o.write(r);return}b==="live"&&(C+=r,o.write(r))}}function ie(){let n=Te,i=localStorage.getItem("tmux-web-token");i&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(i)),s=new WebSocket(n),s.onopen=()=>{b="connecting",N=0,j=!1,A=[],C="",o.reset(),B=1e3},s.onmessage=r=>{typeof r.data=="string"&&Ie(r.data)},s.onclose=()=>{$||(b="connecting",setTimeout(()=>{B=Math.min(B*2,1e4),$||ie()},B))},s.onerror=()=>s?.close()}function R(n){S({type:"input",data:n})}function M(){return o.fit()}function Z(){t.classList.remove("terminal-pending")}function p(){c&&cancelAnimationFrame(c),u&&clearTimeout(u),M(),c=requestAnimationFrame(()=>{c=0,M()&&Z()}),u=setTimeout(()=>{M()&&Z()},120)}function He(){p(),setTimeout(p,50),setTimeout(p,150),setTimeout(p,300)}function ae(n){return typeof n=="string"&&n.startsWith("image/")}function oe(n){return n?.files?.length?n.files[0]??null:null}function De(n){if(!n?.items)return null;for(let i=0;i<n.items.length;i++){let r=n.items[i];if(r?.kind==="file"&&ae(r.type))return r.getAsFile()}return null}async function ze(n){let i=new FormData,r=n instanceof File&&n.name?n.name:"upload";i.append("file",n,r);let h=localStorage.getItem("tmux-web-token"),v={};h&&(v.Authorization="Bearer "+h);let Q=await fetch(Ce,{method:"POST",body:i,headers:v});if(!Q.ok){let ue="upload failed";try{let me=await Q.json();me.error&&(ue=me.error)}catch{}throw new Error(ue)}let de=await Q.json();if(!de.path)throw new Error("no path in response");return de.path}async function G(n){try{let i=await ze(n);R(i)}catch{}}o.onData(n=>R(n)),o.onResize(({cols:n,rows:i})=>S({type:"resize",cols:n,rows:i})),o.onScroll(()=>{b!=="live"||j||!o.isNearScrollbackTop()||(j=!0,S({type:"load_history",before:N}))}),o.attachCustomKeyEventHandler(n=>n.type!=="keydown"?!0:(n.ctrlKey||n.metaKey)&&n.code==="KeyV"?(n.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let i=await navigator.clipboard.read();for(let r of i)for(let h of r.types)if(ae(h)){let v=await r.getType(h);await G(v);return}}}catch{}try{let i=await navigator.clipboard?.readText();i&&o.pasteText(se(i))}catch{}})(),!1):!0);let O=0;function se(n){return n.split(`\r
2
2
  `).join(`
3
3
  `).split(`
4
- `).join("\r")}async function Ye(n){let i=De(n.clipboardData);if(i){n.preventDefault(),n.stopPropagation();let v=Date.now();if(v-O<50)return;O=v,await G(i);return}let r=n.clipboardData?.getData("text/plain");if(!r)return;n.preventDefault(),n.stopPropagation();let h=Date.now();h-O<50||(O=h,o.pasteText(oe(r)))}let I=0;function Pe(n){n.preventDefault(),I++,ae(n.dataTransfer)&&t.classList.add("terminal-drag-over")}function Ve(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Fe(n){n.preventDefault(),I--,I<=0&&(I=0,t.classList.remove("terminal-drag-over"))}async function Ne(n){n.preventDefault(),I=0,t.classList.remove("terminal-drag-over");let i=ae(n.dataTransfer);i&&await G(i)}function se(n){!Be||n.key!=="Escape"||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||o.isFocused()&&(n.preventDefault(),n.stopPropagation(),R("\x1B"))}t.addEventListener("paste",Ye),t.addEventListener("dragenter",Pe),t.addEventListener("dragover",Ve),t.addEventListener("dragleave",Fe),t.addEventListener("drop",Ne),document.addEventListener("keydown",se,!0);function je(n){if(n.touches.length!==1){m=null;return}let i=n.touches[0];i&&(m={startX:i.clientX,startY:i.clientY,lastX:i.clientX,lastY:i.clientY,scrolling:!1},n.stopPropagation())}function Ae(n){if(!m||n.touches.length!==1)return;let i=n.touches[0];if(!i)return;let r=i.clientY-m.startY,h=i.clientX-m.startX;if(!m.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(h))return;m.scrolling=!0,J=Date.now()+500}n.preventDefault(),n.stopPropagation(),qe(-(i.clientY-m.lastY),i.clientX,i.clientY),m.lastX=i.clientX,m.lastY=i.clientY}function Re(n){if(!m)return;let i=m.scrolling;m=null,n.stopPropagation(),i?(J=Date.now()+500,n.preventDefault()):o.focus()}function Oe(n){m=null,n.stopPropagation()}function We(n){n.pointerType==="touch"&&Date.now()<J&&(n.preventDefault(),n.stopPropagation())}function qe(n,i,r){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:n,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:i,clientY:r,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",je,{passive:!0,capture:!0}),t.addEventListener("touchmove",Ae,{passive:!1,capture:!0}),t.addEventListener("touchend",Re,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Oe,{passive:!0,capture:!0}),t.addEventListener("pointerup",We,!0);function re(){p()}window.addEventListener("resize",re),window.visualViewport?.addEventListener("resize",p),window.visualViewport?.addEventListener("scroll",p);let le=new ResizeObserver(()=>p());return le.observe(t),document.fonts?.ready.then(()=>{p(),setTimeout(p,50),setTimeout(p,200)}),t.addEventListener("focusin",He,!0),requestAnimationFrame(()=>{M()&&Z(),ne(),p()}),{destroy(){if($=!0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",re),le.disconnect(),document.removeEventListener("keydown",se,!0)},focus(){o.focus()},sendInput(n){R(n)}}})()}var H=window.__TMUX_WEB_SHELL__,l=null,w=null,$e="sessions",E="",K="",X="",he="",d=document.getElementById("sidebar-content"),D=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),_=document.getElementById("file-editor"),Ze=document.getElementById("fe-path"),f=document.getElementById("fe-status"),z=document.getElementById("fe-content"),x=document.getElementById("fe-save"),Ge=document.getElementById("fe-delete"),Qe=document.getElementById("fe-back"),fe=document.getElementById("fe-new-name"),et=document.getElementById("fe-new-btn"),ee=document.getElementById("settings-backdrop"),ve=document.getElementById("settings-popover"),ye=document.querySelector(".sidebar");function tt(){return window.matchMedia("(max-width: 640px)").matches}function we(){tt()&&ye.classList.add("collapsed")}function ge(){ye.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ge(),be("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ge(),be("files")});function be(t){$e=t,document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?Y():nt()}async function Y(){d.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let a of e){let o=document.createElement("div");o.className="session-item"+(a.name===w?" active":""),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${a.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
4
+ `).join("\r")}async function Ye(n){let i=De(n.clipboardData);if(i){n.preventDefault(),n.stopPropagation();let v=Date.now();if(v-O<50)return;O=v,await G(i);return}let r=n.clipboardData?.getData("text/plain");if(!r)return;n.preventDefault(),n.stopPropagation();let h=Date.now();h-O<50||(O=h,o.pasteText(se(r)))}let I=0;function Pe(n){n.preventDefault(),I++,oe(n.dataTransfer)&&t.classList.add("terminal-drag-over")}function Ve(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Fe(n){n.preventDefault(),I--,I<=0&&(I=0,t.classList.remove("terminal-drag-over"))}async function Ne(n){n.preventDefault(),I=0,t.classList.remove("terminal-drag-over");let i=oe(n.dataTransfer);i&&await G(i)}function re(n){!Be||n.key!=="Escape"||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||o.isFocused()&&(n.preventDefault(),n.stopPropagation(),R("\x1B"))}t.addEventListener("paste",Ye),t.addEventListener("dragenter",Pe),t.addEventListener("dragover",Ve),t.addEventListener("dragleave",Fe),t.addEventListener("drop",Ne),document.addEventListener("keydown",re,!0);function je(n){if(n.touches.length!==1){m=null;return}let i=n.touches[0];i&&(m={startX:i.clientX,startY:i.clientY,lastX:i.clientX,lastY:i.clientY,scrolling:!1},n.stopPropagation())}function Ae(n){if(!m||n.touches.length!==1)return;let i=n.touches[0];if(!i)return;let r=i.clientY-m.startY,h=i.clientX-m.startX;if(!m.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(h))return;m.scrolling=!0,J=Date.now()+500}n.preventDefault(),n.stopPropagation(),qe(-(i.clientY-m.lastY),i.clientX,i.clientY),m.lastX=i.clientX,m.lastY=i.clientY}function Re(n){if(!m)return;let i=m.scrolling;m=null,n.stopPropagation(),i?(J=Date.now()+500,n.preventDefault()):o.focus()}function Oe(n){m=null,n.stopPropagation()}function We(n){n.pointerType==="touch"&&Date.now()<J&&(n.preventDefault(),n.stopPropagation())}function qe(n,i,r){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:n,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:i,clientY:r,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",je,{passive:!0,capture:!0}),t.addEventListener("touchmove",Ae,{passive:!1,capture:!0}),t.addEventListener("touchend",Re,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Oe,{passive:!0,capture:!0}),t.addEventListener("pointerup",We,!0);function le(){p()}window.addEventListener("resize",le),window.visualViewport?.addEventListener("resize",p),window.visualViewport?.addEventListener("scroll",p);let ce=new ResizeObserver(()=>p());return ce.observe(t),document.fonts?.ready.then(()=>{p(),setTimeout(p,50),setTimeout(p,200)}),t.addEventListener("focusin",He,!0),requestAnimationFrame(()=>{M()&&Z(),ie(),p()}),{destroy(){if($=!0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",le),ce.disconnect(),document.removeEventListener("keydown",re,!0)},focus(){o.focus()},sendInput(n){R(n)}}})()}var H=window.__TMUX_WEB_SHELL__,l=null,w=null,$e="sessions",E="",K="",X="",fe="",d=document.getElementById("sidebar-content"),D=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),_=document.getElementById("file-editor"),Ze=document.getElementById("fe-path"),f=document.getElementById("fe-status"),z=document.getElementById("fe-content"),x=document.getElementById("fe-save"),Ge=document.getElementById("fe-delete"),Qe=document.getElementById("fe-back"),ve=document.getElementById("fe-new-name"),et=document.getElementById("fe-new-btn"),ee=document.getElementById("settings-backdrop"),ye=document.getElementById("settings-popover"),we=document.querySelector(".sidebar");function tt(){return window.matchMedia("(max-width: 640px)").matches}function te(){tt()&&we.classList.add("collapsed")}function ge(){we.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ge(),be("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ge(),be("files")});function be(t){$e=t,document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?Y():nt()}async function Y(){d.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let a of e){let o=document.createElement("div");o.className="session-item"+(a.name===w?" active":""),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${a.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
5
5
  <span>${P(a.name)}</span>
6
6
  <button class="session-edit-btn" data-session="${P(a.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
7
- `,o.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||te(a.name)}),d.appendChild(o);let s=o.querySelector(".session-edit-btn");s&&s.addEventListener("click",c=>{c.stopPropagation(),rt(a.name,s)})}}catch{d.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function P(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function te(t){if(!(w===t&&l)){_.style.display="none",D.style.display="none",L.style.display="",L.classList.add("terminal-pending"),we(),l&&(l.destroy(),l=null),w=t;try{l=await Je(L,t,{terminal:H.terminal,scrollback:H.scrollback,theme:H.theme,renderer:H.renderer})}catch(e){console.error("[shell] terminal init failed:",e);let a=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+a,L.classList.remove("terminal-pending")}}}var U=[];function nt(){if(U=H.fsRoots||[],U.length===0){if(w){it(w);return}d.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}d.innerHTML="";for(let t of U){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${P(t)}`,e.addEventListener("click",()=>k(t)),d.appendChild(e)}}async function it(t){d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?k(e.path):d.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{d.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(t){E=t,d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();d.innerHTML="";let a=at(t);if(a&&!U.some(s=>t===s)){let s=document.createElement("div");s.className="file-tree-item",s.innerHTML="..",s.addEventListener("click",()=>k(a)),d.appendChild(s)}if(e.dirs)for(let s of e.dirs){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item",u.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${P(c)}`,u.addEventListener("click",()=>k(s)),d.appendChild(u)}else d.innerHTML="";let o=e.files||[];for(let s of o){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item file",u.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${P(c)}`,u.addEventListener("click",()=>ot(s)),d.appendChild(u)}}catch{d.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function at(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function ot(t){K=t,L.style.display="none",l&&(l.destroy(),l=null,w=null),we(),D.style.display="none",_.style.display="flex",Ze.textContent=t,f.textContent="Loading...",x.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let o=await e.json();f.textContent=o.error||"Failed to load",z.value="";return}let a=await e.json();z.value=a.content,X=a.content,he=a.content,f.textContent=a.size+" bytes",x.style.display=""}catch{f.textContent="Failed to load",z.value=""}}z.addEventListener("input",()=>{X=z.value}),x.addEventListener("click",async()=>{x.textContent="Saving...",x.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K,content:X})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Save failed";return}he=X,f.textContent="Saved"}catch{f.textContent="Save failed"}finally{x.disabled=!1,x.textContent="Save"}}),Ge.addEventListener("click",async()=>{if(confirm("Delete "+K+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Delete failed";return}_.style.display="none",D.style.display="flex",E&&k(E)}catch{f.textContent="Delete failed"}}),Qe.addEventListener("click",()=>{_.style.display="none",D.style.display="flex",E&&k(E)}),et.addEventListener("click",async()=>{let t=fe.value.trim();if(!t)return;let e=E+"/"+t;try{let a=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!a.ok){let o=await a.json();f.textContent=o.error||"Create failed";return}fe.value="",E&&k(E)}catch{f.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=ve.classList.toggle("open");ee.classList.toggle("open",t)}),ee.addEventListener("click",()=>{ve.classList.remove("open"),ee.classList.remove("open")});var T="",V=document.createElement("div");V.className="session-popover-backdrop",document.body.appendChild(V);var g=document.createElement("div");g.id="session-popover",g.className="session-popover",g.style.display="none",g.innerHTML=`<div class="sp-field">
7
+ `,o.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||ne(a.name)}),d.appendChild(o);let s=o.querySelector(".session-edit-btn");s&&s.addEventListener("click",c=>{c.stopPropagation(),rt(a.name,s)})}}catch{d.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function P(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ne(t){if(!(w===t&&l)){_.style.display="none",D.style.display="none",L.style.display="",L.classList.add("terminal-pending"),te(),l&&(l.destroy(),l=null),w=t;try{l=await Je(L,t,{terminal:H.terminal,scrollback:H.scrollback,theme:H.theme,renderer:H.renderer})}catch(e){console.error("[shell] terminal init failed:",e);let a=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+a,L.classList.remove("terminal-pending")}}}var U=[];function nt(){if(U=H.fsRoots||[],U.length===0){if(w){it(w);return}d.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}d.innerHTML="";for(let t of U){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${P(t)}`,e.addEventListener("click",()=>k(t)),d.appendChild(e)}}async function it(t){d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?k(e.path):d.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{d.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(t){E=t,d.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();d.innerHTML="";let a=at(t);if(a&&!U.some(s=>t===s)){let s=document.createElement("div");s.className="file-tree-item",s.innerHTML="..",s.addEventListener("click",()=>k(a)),d.appendChild(s)}if(e.dirs)for(let s of e.dirs){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item",u.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${P(c)}`,u.addEventListener("click",()=>k(s)),d.appendChild(u)}else d.innerHTML="";let o=e.files||[];for(let s of o){let c=s.split("/").pop()||s.split("\\").pop(),u=document.createElement("div");u.className="file-tree-item file",u.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${P(c)}`,u.addEventListener("click",()=>ot(s)),d.appendChild(u)}}catch{d.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function at(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function ot(t){K=t,L.style.display="none",l&&(l.destroy(),l=null,w=null),te(),D.style.display="none",_.style.display="flex",Ze.textContent=t,f.textContent="Loading...",x.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let o=await e.json();f.textContent=o.error||"Failed to load",z.value="";return}let a=await e.json();z.value=a.content,X=a.content,fe=a.content,f.textContent=a.size+" bytes",x.style.display=""}catch{f.textContent="Failed to load",z.value=""}}z.addEventListener("input",()=>{X=z.value}),x.addEventListener("click",async()=>{x.textContent="Saving...",x.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K,content:X})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Save failed";return}fe=X,f.textContent="Saved"}catch{f.textContent="Save failed"}finally{x.disabled=!1,x.textContent="Save"}}),Ge.addEventListener("click",async()=>{if(confirm("Delete "+K+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:K})});if(!t.ok){let e=await t.json();f.textContent=e.error||"Delete failed";return}_.style.display="none",D.style.display="flex",E&&k(E)}catch{f.textContent="Delete failed"}}),Qe.addEventListener("click",()=>{_.style.display="none",D.style.display="flex",E&&k(E)}),et.addEventListener("click",async()=>{let t=ve.value.trim();if(!t)return;let e=E+"/"+t;try{let a=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!a.ok){let o=await a.json();f.textContent=o.error||"Create failed";return}ve.value="",E&&k(E)}catch{f.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=ye.classList.toggle("open");ee.classList.toggle("open",t)}),ee.addEventListener("click",()=>{ye.classList.remove("open"),ee.classList.remove("open")});var T="",V=document.createElement("div");V.className="session-popover-backdrop",document.body.appendChild(V);var g=document.createElement("div");g.id="session-popover",g.className="session-popover",g.style.display="none",g.innerHTML=`<div class="sp-field">
8
8
  <label>Rename session</label>
9
9
  <input type="text" id="sp-name" />
10
10
  </div>
@@ -13,5 +13,5 @@ var Ke="https://esm.sh/ghostty-web@0.4.0",Xe=/\x1b\](?:10|11|110|111|104)(?:;[^\
13
13
  <button class="btn" id="sp-cancel">Cancel</button>
14
14
  </div>
15
15
  <hr class="sp-divider" />
16
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(g);function st(t){let e=t.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function rt(t,e){T=t;let a=document.getElementById("sp-name");a.value=t,V.classList.add("open"),g.style.display="block",st(e),setTimeout(()=>a.focus(),50)}function F(){V.classList.remove("open"),g.style.display="none"}V.addEventListener("click",F),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===T){F();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:T,newName:t})})).ok)return;F(),Y(),w===T&&te(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+T+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:T})})).ok)return;F(),w===T&&(l&&(l.destroy(),l=null),w=null,L.style.display="none",D.style.display="flex"),Y()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",F);var lt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!l)return;let a=lt[e.dataset.key];a&&(l.sendInput(a),l.focus())});var y=document.getElementById("mk-input"),Ee=document.getElementById("mk-send");function Le(){if(!y||!l)return;let t=y.value;t&&(l.sendInput(t+"\r"),y.value="",y.rows=1,l.focus())}if(y&&(y.addEventListener("input",()=>{y.rows=1;let t=y.value.split(`
17
- `).length;y.rows=Math.min(t,4)}),y.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),Le())})),Ee&&Ee.addEventListener("click",Le),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}window.__openSession=te,window.__refreshSidebar=Y,Y();
16
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(g);function st(t){let e=t.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function rt(t,e){T=t;let a=document.getElementById("sp-name");a.value=t,V.classList.add("open"),g.style.display="block",st(e),setTimeout(()=>a.focus(),50)}function F(){V.classList.remove("open"),g.style.display="none"}V.addEventListener("click",F),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===T){F();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:T,newName:t})})).ok)return;F(),Y(),w===T&&ne(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+T+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:T})})).ok)return;F(),w===T&&(l&&(l.destroy(),l=null),w=null,L.style.display="none",D.style.display="flex"),Y()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",F);var lt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!l)return;let a=lt[e.dataset.key];a&&(l.sendInput(a),l.focus())});var y=document.getElementById("mk-input"),Ee=document.getElementById("mk-send");function Le(){if(!y||!l)return;let t=y.value;t&&(l.sendInput(t+"\r"),y.value="",y.rows=1,l.focus())}if(y&&(y.addEventListener("input",()=>{y.rows=1;let t=y.value.split(`
17
+ `).length;y.rows=Math.min(t,4)}),y.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),Le())})),Ee&&Ee.addEventListener("click",Le),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}window.__openSession=ne,window.__refreshSidebar=Y,Y(),te();
@@ -1,7 +1,7 @@
1
- import{initTerminal as V}from"./terminal-core.js";const E=window.__TMUX_WEB_SHELL__;let s=null,r=null,q="sessions",u="",B="",T="",N="";const i=document.getElementById("sidebar-content"),w=document.getElementById("main-placeholder"),y=document.getElementById("terminal-container"),S=document.getElementById("file-editor"),$=document.getElementById("fe-path"),c=document.getElementById("fe-status"),b=document.getElementById("fe-content"),v=document.getElementById("fe-save"),J=document.getElementById("fe-delete"),U=document.getElementById("fe-back"),F=document.getElementById("fe-new-name"),A=document.getElementById("fe-new-btn"),M=document.getElementById("settings-backdrop"),j=document.getElementById("settings-popover"),D=document.querySelector(".sidebar");function W(){return window.matchMedia("(max-width: 640px)").matches}function O(){W()&&D.classList.add("collapsed")}function z(){D.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{z(),P("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{z(),P("files")});function P(e){q=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));const t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?L():K()}async function L(){i.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const m=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(const f of m){const o=document.createElement("div");o.className="session-item"+(f.name===r?" active":""),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${f.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
1
+ import{initTerminal as V}from"./terminal-core.js";const E=window.__TMUX_WEB_SHELL__;let s=null,r=null,q="sessions",u="",B="",T="",F="";const i=document.getElementById("sidebar-content"),w=document.getElementById("main-placeholder"),y=document.getElementById("terminal-container"),S=document.getElementById("file-editor"),$=document.getElementById("fe-path"),c=document.getElementById("fe-status"),b=document.getElementById("fe-content"),v=document.getElementById("fe-save"),J=document.getElementById("fe-delete"),U=document.getElementById("fe-back"),j=document.getElementById("fe-new-name"),A=document.getElementById("fe-new-btn"),M=document.getElementById("settings-backdrop"),D=document.getElementById("settings-popover"),O=document.querySelector(".sidebar");function W(){return window.matchMedia("(max-width: 640px)").matches}function H(){W()&&O.classList.add("collapsed")}function z(){O.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{z(),P("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{z(),P("files")});function P(e){q=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));const t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?L():K()}async function L(){i.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const m=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(const f of m){const o=document.createElement("div");o.className="session-item"+(f.name===r?" active":""),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${f.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
2
  <span>${k(f.name)}</span>
3
3
  <button class="session-edit-btn" data-session="${k(f.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
4
- `,o.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||H(f.name)}),i.appendChild(o);const a=o.querySelector(".session-edit-btn");a&&a.addEventListener("click",l=>{l.stopPropagation(),Q(f.name,a)})}}catch{i.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function k(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function H(e){if(!(r===e&&s)){S.style.display="none",w.style.display="none",y.style.display="",y.classList.add("terminal-pending"),O(),s&&(s.destroy(),s=null),r=e;try{s=await V(y,e,{terminal:E.terminal,scrollback:E.scrollback,theme:E.theme,renderer:E.renderer})}catch(t){console.error("[shell] terminal init failed:",t);const n=t&&typeof t=="object"&&"message"in t?t.message:String(t);y.textContent="Failed to open terminal for "+e+": "+n,y.classList.remove("terminal-pending")}}}let I=[];function K(){if(I=E.fsRoots||[],I.length===0){if(r){X(r);return}i.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}i.innerHTML="";for(const e of I){const t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${k(e)}`,t.addEventListener("click",()=>h(e)),i.appendChild(t)}}async function X(e){i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();n.path?h(n.path):i.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{i.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function h(e){u=e,i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();i.innerHTML="";const m=Y(e);if(m&&!I.some(a=>e===a)){const a=document.createElement("div");a.className="file-tree-item",a.innerHTML="..",a.addEventListener("click",()=>h(m)),i.appendChild(a)}if(n.dirs)for(const o of n.dirs){const a=o.split("/").pop()||o.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${k(a)}`,l.addEventListener("click",()=>h(o)),i.appendChild(l)}else i.innerHTML="";const f=n.files||[];for(const o of f){const a=o.split("/").pop()||o.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item file",l.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${k(a)}`,l.addEventListener("click",()=>Z(o)),i.appendChild(l)}}catch{i.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Y(e){const t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Z(e){B=e,y.style.display="none",s&&(s.destroy(),s=null,r=null),O(),w.style.display="none",S.style.display="flex",$.textContent=e,c.textContent="Loading...",v.style.display="none";try{const t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){const m=await t.json();c.textContent=m.error||"Failed to load",b.value="";return}const n=await t.json();b.value=n.content,T=n.content,N=n.content,c.textContent=n.size+" bytes",v.style.display=""}catch{c.textContent="Failed to load",b.value=""}}b.addEventListener("input",()=>{T=b.value}),v.addEventListener("click",async()=>{v.textContent="Saving...",v.disabled=!0;try{const e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B,content:T})});if(!e.ok){const t=await e.json();c.textContent=t.error||"Save failed";return}N=T,c.textContent="Saved"}catch{c.textContent="Save failed"}finally{v.disabled=!1,v.textContent="Save"}}),J.addEventListener("click",async()=>{if(confirm("Delete "+B+"?"))try{const e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B})});if(!e.ok){const t=await e.json();c.textContent=t.error||"Delete failed";return}S.style.display="none",w.style.display="flex",u&&h(u)}catch{c.textContent="Delete failed"}}),U.addEventListener("click",()=>{S.style.display="none",w.style.display="flex",u&&h(u)}),A.addEventListener("click",async()=>{const e=F.value.trim();if(!e)return;const t=u+"/"+e;try{const n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){const m=await n.json();c.textContent=m.error||"Create failed";return}F.value="",u&&h(u)}catch{c.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=j.classList.toggle("open");M.classList.toggle("open",e)}),M.addEventListener("click",()=>{j.classList.remove("open"),M.classList.remove("open")});let g="";const C=document.createElement("div");C.className="session-popover-backdrop",document.body.appendChild(C);const p=document.createElement("div");p.id="session-popover",p.className="session-popover",p.style.display="none",p.innerHTML=`<div class="sp-field">
4
+ `,o.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||N(f.name)}),i.appendChild(o);const a=o.querySelector(".session-edit-btn");a&&a.addEventListener("click",l=>{l.stopPropagation(),Q(f.name,a)})}}catch{i.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function k(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function N(e){if(!(r===e&&s)){S.style.display="none",w.style.display="none",y.style.display="",y.classList.add("terminal-pending"),H(),s&&(s.destroy(),s=null),r=e;try{s=await V(y,e,{terminal:E.terminal,scrollback:E.scrollback,theme:E.theme,renderer:E.renderer})}catch(t){console.error("[shell] terminal init failed:",t);const n=t&&typeof t=="object"&&"message"in t?t.message:String(t);y.textContent="Failed to open terminal for "+e+": "+n,y.classList.remove("terminal-pending")}}}let I=[];function K(){if(I=E.fsRoots||[],I.length===0){if(r){X(r);return}i.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}i.innerHTML="";for(const e of I){const t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${k(e)}`,t.addEventListener("click",()=>h(e)),i.appendChild(t)}}async function X(e){i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();n.path?h(n.path):i.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{i.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function h(e){u=e,i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();i.innerHTML="";const m=Y(e);if(m&&!I.some(a=>e===a)){const a=document.createElement("div");a.className="file-tree-item",a.innerHTML="..",a.addEventListener("click",()=>h(m)),i.appendChild(a)}if(n.dirs)for(const o of n.dirs){const a=o.split("/").pop()||o.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${k(a)}`,l.addEventListener("click",()=>h(o)),i.appendChild(l)}else i.innerHTML="";const f=n.files||[];for(const o of f){const a=o.split("/").pop()||o.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item file",l.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${k(a)}`,l.addEventListener("click",()=>Z(o)),i.appendChild(l)}}catch{i.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Y(e){const t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Z(e){B=e,y.style.display="none",s&&(s.destroy(),s=null,r=null),H(),w.style.display="none",S.style.display="flex",$.textContent=e,c.textContent="Loading...",v.style.display="none";try{const t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){const m=await t.json();c.textContent=m.error||"Failed to load",b.value="";return}const n=await t.json();b.value=n.content,T=n.content,F=n.content,c.textContent=n.size+" bytes",v.style.display=""}catch{c.textContent="Failed to load",b.value=""}}b.addEventListener("input",()=>{T=b.value}),v.addEventListener("click",async()=>{v.textContent="Saving...",v.disabled=!0;try{const e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B,content:T})});if(!e.ok){const t=await e.json();c.textContent=t.error||"Save failed";return}F=T,c.textContent="Saved"}catch{c.textContent="Save failed"}finally{v.disabled=!1,v.textContent="Save"}}),J.addEventListener("click",async()=>{if(confirm("Delete "+B+"?"))try{const e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B})});if(!e.ok){const t=await e.json();c.textContent=t.error||"Delete failed";return}S.style.display="none",w.style.display="flex",u&&h(u)}catch{c.textContent="Delete failed"}}),U.addEventListener("click",()=>{S.style.display="none",w.style.display="flex",u&&h(u)}),A.addEventListener("click",async()=>{const e=j.value.trim();if(!e)return;const t=u+"/"+e;try{const n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){const m=await n.json();c.textContent=m.error||"Create failed";return}j.value="",u&&h(u)}catch{c.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=D.classList.toggle("open");M.classList.toggle("open",e)}),M.addEventListener("click",()=>{D.classList.remove("open"),M.classList.remove("open")});let g="";const C=document.createElement("div");C.className="session-popover-backdrop",document.body.appendChild(C);const p=document.createElement("div");p.id="session-popover",p.className="session-popover",p.style.display="none",p.innerHTML=`<div class="sp-field">
5
5
  <label>Rename session</label>
6
6
  <input type="text" id="sp-name" />
7
7
  </div>
@@ -10,5 +10,5 @@ import{initTerminal as V}from"./terminal-core.js";const E=window.__TMUX_WEB_SHEL
10
10
  <button class="btn" id="sp-cancel">Cancel</button>
11
11
  </div>
12
12
  <hr class="sp-divider" />
13
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(p);function G(e){const t=e.getBoundingClientRect();p.style.top=t.bottom+4+"px",p.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Q(e,t){g=e;const n=document.getElementById("sp-name");n.value=e,C.classList.add("open"),p.style.display="block",G(t),setTimeout(()=>n.focus(),50)}function x(){C.classList.remove("open"),p.style.display="none"}C.addEventListener("click",x),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===g){x();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:g,newName:e})})).ok)return;x(),L(),r===g&&H(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+g+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:g})})).ok)return;x(),r===g&&(s&&(s.destroy(),s=null),r=null,y.style.display="none",w.style.display="flex"),L()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",x);const ee={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const t=e.target.closest("button[data-key]");if(!t||!s)return;const n=ee[t.dataset.key];n&&(s.sendInput(n),s.focus())});const d=document.getElementById("mk-input"),R=document.getElementById("mk-send");function _(){if(!d||!s)return;const e=d.value;e&&(s.sendInput(e+"\r"),d.value="",d.rows=1,s.focus())}if(d&&(d.addEventListener("input",()=>{d.rows=1;const e=d.value.split(`
14
- `).length;d.rows=Math.min(e,4)}),d.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),_())})),R&&R.addEventListener("click",_),window.visualViewport){const e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}window.__openSession=H,window.__refreshSidebar=L,L();
13
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(p);function G(e){const t=e.getBoundingClientRect();p.style.top=t.bottom+4+"px",p.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Q(e,t){g=e;const n=document.getElementById("sp-name");n.value=e,C.classList.add("open"),p.style.display="block",G(t),setTimeout(()=>n.focus(),50)}function x(){C.classList.remove("open"),p.style.display="none"}C.addEventListener("click",x),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===g){x();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:g,newName:e})})).ok)return;x(),L(),r===g&&N(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+g+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:g})})).ok)return;x(),r===g&&(s&&(s.destroy(),s=null),r=null,y.style.display="none",w.style.display="flex"),L()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",x);const ee={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const t=e.target.closest("button[data-key]");if(!t||!s)return;const n=ee[t.dataset.key];n&&(s.sendInput(n),s.focus())});const d=document.getElementById("mk-input"),R=document.getElementById("mk-send");function _(){if(!d||!s)return;const e=d.value;e&&(s.sendInput(e+"\r"),d.value="",d.rows=1,s.focus())}if(d&&(d.addEventListener("input",()=>{d.rows=1;const e=d.value.split(`
14
+ `).length;d.rows=Math.min(e,4)}),d.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),_())})),R&&R.addEventListener("click",_),window.visualViewport){const e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}window.__openSession=N,window.__refreshSidebar=L,L(),H();
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{chmodSync as Fe,existsSync as V,mkdirSync as Ue,readdirSync as de,readFileSync as We,statSync as N,unlinkSync as $e,writeFileSync as Be}from"node:fs";import{readFile as Ve}from"node:fs/promises";import{createRequire as Ge}from"node:module";import w from"node:path";import{fileURLToPath as ze}from"node:url";import{Hono as Ke}from"hono";import{serve as Qe}from"@hono/node-server";import{WebSocketServer as Xe,WebSocket as ue}from"ws";import*as Ye from"node-pty";try{const e=w.dirname(Ge(import.meta.url).resolve("node-pty/package.json")),t=w.join(e,"prebuilds");if(V(t))for(const r of de(t)){const n=w.join(t,r,"spawn-helper");V(n)&&!(N(n).mode&73)&&Fe(n,493)}}catch{}import{listSessions as q}from"./sessions.js";import{renderLoginPage as Je,renderNotesIndex as Ze,renderNotesPage as et,renderSettings as tt,renderThemeSettings as rt,renderScheduleIndex as nt,renderHistoryIndex as ot,renderQuickCommandsPage as st,renderFilesIndex as it,renderShell as at}from"./frontend.js";import{loadSecurityConfig as dt,saveSecurityConfig as ce}from"./lib/security-config.js";import{hashPassword as le,verifyPassword as me,validatePassword as pe,TokenStore as ut}from"./lib/auth.js";import{RateLimiter as ct}from"./lib/rateLimiter.js";import{atomicWriteFileSync as lt}from"./lib/atomicWrite.js";import{resolveFsPath as G,resolveFsRoots as fe,MAX_FILE_BYTES as he,walkRecursive as mt}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as pt}from"./lib/tmux-windows.js";import{audit as c}from"./lib/auditLog.js";import{db as g}from"./lib/db.js";import{getSessionAccessMap as I}from"./lib/session-access.js";import{listWindowHistory as ft,clearWindowHistory as ht}from"./lib/window-history.js";import{loadExtensions as yt,spawnExtensionBackend as wt,registerExtensionRoutes as gt}from"./lib/ext-loader.js";import{SchedulerService as jt,isValidScheduleInput as kt,isValidRescheduleInput as vt}from"./lib/scheduler.js";import{getScheduleDelayError as ye}from"./lib/schedule-delay.js";import{handleClientMessage as xt}from"./lib/ws-message.js";import{loadDotEnv as Tt}from"./lib/load-env.js";import{cmdAdd as St,cmdRemove as bt,cmdList as qt,cmdSetup as At,cmdTheme as It,printUsage as we,printVersion as _t}from"./lib/cli.js";import{readSettings as Z,writeSettings as Ct}from"./lib/settings.js";import{readActiveTheme as Rt,setActiveThemeTemplate as ge}from"./lib/theme-store.js";import{isThemeTemplateId as je,THEME_TEMPLATE_IDS as Et}from"./lib/themes/index.js";import{installPlugin as Mt,uninstallPlugin as Ot}from"./lib/plugins.js";import{buildCommandbarSessions as C}from"./lib/commandbar.js";import{pinView as Lt,unpinView as Pt,listPinnedViews as Nt}from"./lib/pinned-views.js";import{listWindowLabels as Dt,setWindowLabel as Ht}from"./lib/window-labels.js";import{captureAndStoreWindows as ke,getStoredWindows as ve}from"./lib/session-windows.js";import{acquireControlClient as Ft,killAllControlClients as Ut}from"./lib/tmux-control.js";import{buildSidebarSessions as Wt}from"./lib/sessions-sidebar.js";import{createQuickCommand as $t,deleteQuickCommand as Bt,listQuickCommands as xe,updateQuickCommand as Vt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Gt,capturePaneTail as zt,capturePaneHistoryChunk as Kt,isAlternateScreen as Qt,toCrlf as Te}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Xt}from"./lib/terminal-config.js";import{ImageUploadError as Yt,saveUploadedImage as Jt}from"./lib/image-upload.js";import{getSystemStatus as Zt}from"./lib/system-monitor.js";import{listSessionWindows as er,selectSessionWindow as tr,newSessionWindow as rr,renameSessionWindow as nr,newTmuxSession as or,renameSession as sr,killSession as ir,TmuxWindowsError as z}from"./lib/tmux-windows.js";Tt();const K=Xt(),l=dt(),f=new ut,R=new ct;let D=!1;const Q="tmux-web-token",ar=365;function Se(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function X(e){if(l.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function dr(e){if(l.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function ee(e){const t=e.req.header("authorization");if(t?.startsWith("Bearer "))return t.slice(7);const r=e.req.header("cookie");if(!r)return null;const n=r.match(new RegExp(`(?:^|;\\s*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function be(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const n=r.match(new RegExp(`(?:^|;\\s*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function un(e){const t=ee(e);return t?f.validateToken(t):null}function cn(e){const t=be(e);return t?f.validateToken(t):null}function qe(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${Q}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ar*86400}${r?"; Secure":""}`)}function ur(e){e.header("Set-Cookie",`${Q}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function a(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?(c("http_unauthorized",{ip:X(e)}),e.json({error:"unauthorized"},401)):t()}}function cr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function _(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?cr(e):t()}}const H=new Map;function lr(e){let t=0;for(const r of H.values())r.ip===e&&t++;return t}function te(e,t,r){try{e.close(t,r)}catch{}}function ln(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}function mr(e,t){let r="xterm";(t==="ghostty"||t==="xterm")&&(r=t);const n=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(n==="ghostty"||n==="xterm")&&(r=n);for(const i of e)i==="--ghostty"&&(r="ghostty"),i==="--xterm"&&(r="xterm");return r}const pr=7;function Ae(e){return typeof e!="number"||!Number.isFinite(e)?pr:Math.min(365,Math.max(1,Math.round(e)))}const Ie=process.argv.slice(2);{const e=Ie.filter(t=>t!=="--ghostty"&&t!=="--xterm");if(e.length>0){const[t,r]=e;switch(t){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await St(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await bt(r),process.exit(0);case"list":case"ls":await qt(),process.exit(0);case"setup":await At(e),process.exit(0);case"theme":await It(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":we(),process.exit(0);case"-V":case"--version":case"-v":_t(),process.exit(0);default:console.error(`unknown argument: ${t}`),we(),process.exit(1)}}}function v(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}const F=new Set,_e=[];await g.read(),g.data.sessionAccess??=[],g.data.pinnedViews??=[],g.data.watchedPanes??=[],g.data.triggeredTasks??=[],g.data.quickCommands??=[];const re=await Z();let j=await Rt();const h=re.commandbar===!0,ne=mr(Ie,re.terminalRenderer),Ce=Ae(re.scheduleHistoryDays),Re=w.join(process.cwd(),"extensions"),Ee=await yt(Re);for(const e of Ee)e.start&&_e.push(wt(e.dir,e));const E=new jt({db:g,historyRetentionMs:Ce*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await E.restoreFromDb();const s=new Ke;s.use("*",async(e,t)=>{await t(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline'","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self'","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,t)=>{const r=e.req.method;if(r==="GET"||r==="HEAD"||r==="OPTIONS")return t();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):t()}),gt(s,Re,Ee);const fr=w.dirname(ze(import.meta.url)),hr=[w.join(fr,"assets"),w.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of hr){const n=w.join(r,t);if(!V(n))continue;const i=await Ve(n),o=w.extname(t),d={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(i,200,{"Content-Type":d[o]??"application/octet-stream"})}return e.notFound()});const yr=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ import{chmodSync as Fe,existsSync as V,mkdirSync as Ue,readdirSync as de,readFileSync as We,statSync as N,unlinkSync as $e,writeFileSync as Be}from"node:fs";import{readFile as Ve}from"node:fs/promises";import{createRequire as Ge}from"node:module";import w from"node:path";import{fileURLToPath as ze}from"node:url";import{Hono as Ke}from"hono";import{serve as Qe}from"@hono/node-server";import{WebSocketServer as Xe,WebSocket as ue}from"ws";import*as Ye from"node-pty";try{const e=w.dirname(Ge(import.meta.url).resolve("node-pty/package.json")),t=w.join(e,"prebuilds");if(V(t))for(const r of de(t)){const n=w.join(t,r,"spawn-helper");V(n)&&!(N(n).mode&73)&&Fe(n,493)}}catch{}import{listSessions as q}from"./sessions.js";import{renderLoginPage as Je,renderNotesIndex as Ze,renderNotesPage as et,renderSettings as tt,renderThemeSettings as rt,renderScheduleIndex as nt,renderHistoryIndex as ot,renderQuickCommandsPage as st,renderFilesIndex as it,renderShell as at}from"./frontend.js";import{loadSecurityConfig as dt,saveSecurityConfig as ce}from"./lib/security-config.js";import{hashPassword as le,verifyPassword as me,validatePassword as pe,TokenStore as ut}from"./lib/auth.js";import{RateLimiter as ct}from"./lib/rateLimiter.js";import{atomicWriteFileSync as lt}from"./lib/atomicWrite.js";import{resolveFsPath as G,resolveFsRoots as fe,MAX_FILE_BYTES as he,walkRecursive as mt}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as pt}from"./lib/tmux-windows.js";import{audit as c}from"./lib/auditLog.js";import{db as g}from"./lib/db.js";import{getSessionAccessMap as I}from"./lib/session-access.js";import{listWindowHistory as ft,clearWindowHistory as ht}from"./lib/window-history.js";import{loadExtensions as yt,spawnExtensionBackend as wt,registerExtensionRoutes as gt}from"./lib/ext-loader.js";import{SchedulerService as jt,isValidScheduleInput as kt,isValidRescheduleInput as vt}from"./lib/scheduler.js";import{getScheduleDelayError as ye}from"./lib/schedule-delay.js";import{handleClientMessage as xt}from"./lib/ws-message.js";import{loadDotEnv as Tt}from"./lib/load-env.js";import{cmdAdd as St,cmdRemove as bt,cmdList as qt,cmdSetup as At,cmdTheme as It,printUsage as we,printVersion as _t}from"./lib/cli.js";import{readSettings as Z,writeSettings as Ct}from"./lib/settings.js";import{readActiveTheme as Rt,setActiveThemeTemplate as ge}from"./lib/theme-store.js";import{isThemeTemplateId as je,THEME_TEMPLATE_IDS as Et}from"./lib/themes/index.js";import{installPlugin as Mt,uninstallPlugin as Ot}from"./lib/plugins.js";import{buildCommandbarSessions as C}from"./lib/commandbar.js";import{pinView as Lt,unpinView as Pt,listPinnedViews as Nt}from"./lib/pinned-views.js";import{listWindowLabels as Dt,setWindowLabel as Ht}from"./lib/window-labels.js";import{captureAndStoreWindows as ke,getStoredWindows as ve}from"./lib/session-windows.js";import{acquireControlClient as Ft,killAllControlClients as Ut}from"./lib/tmux-control.js";import{buildSidebarSessions as Wt}from"./lib/sessions-sidebar.js";import{createQuickCommand as $t,deleteQuickCommand as Bt,listQuickCommands as xe,updateQuickCommand as Vt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Gt,capturePaneTail as zt,capturePaneHistoryChunk as Kt,isAlternateScreen as Qt,toCrlf as Te}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Xt}from"./lib/terminal-config.js";import{ImageUploadError as Yt,saveUploadedImage as Jt}from"./lib/image-upload.js";import{getSystemStatus as Zt,getTopProcesses as er,killProcess as tr}from"./lib/system-monitor.js";import{listSessionWindows as rr,selectSessionWindow as nr,newSessionWindow as or,renameSessionWindow as sr,newTmuxSession as ir,renameSession as ar,killSession as dr,TmuxWindowsError as z}from"./lib/tmux-windows.js";Tt();const K=Xt(),l=dt(),f=new ut,R=new ct;let D=!1;const Q="tmux-web-token",ur=365;function Se(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function X(e){if(l.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function cr(e){if(l.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function ee(e){const t=e.req.header("authorization");if(t?.startsWith("Bearer "))return t.slice(7);const r=e.req.header("cookie");if(!r)return null;const n=r.match(new RegExp(`(?:^|;\\s*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function be(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const n=r.match(new RegExp(`(?:^|;\\s*)${Q}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function ln(e){const t=ee(e);return t?f.validateToken(t):null}function mn(e){const t=be(e);return t?f.validateToken(t):null}function qe(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${Q}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ur*86400}${r?"; Secure":""}`)}function lr(e){e.header("Set-Cookie",`${Q}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function a(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?(c("http_unauthorized",{ip:X(e)}),e.json({error:"unauthorized"},401)):t()}}function mr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function _(){return async(e,t)=>{const r=ee(e);return!r||!f.validateToken(r)?mr(e):t()}}const H=new Map;function pr(e){let t=0;for(const r of H.values())r.ip===e&&t++;return t}function te(e,t,r){try{e.close(t,r)}catch{}}function pn(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}function fr(e,t){let r="xterm";(t==="ghostty"||t==="xterm")&&(r=t);const n=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(n==="ghostty"||n==="xterm")&&(r=n);for(const i of e)i==="--ghostty"&&(r="ghostty"),i==="--xterm"&&(r="xterm");return r}const hr=7;function Ae(e){return typeof e!="number"||!Number.isFinite(e)?hr:Math.min(365,Math.max(1,Math.round(e)))}const Ie=process.argv.slice(2);{const e=Ie.filter(t=>t!=="--ghostty"&&t!=="--xterm");if(e.length>0){const[t,r]=e;switch(t){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await St(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await bt(r),process.exit(0);case"list":case"ls":await qt(),process.exit(0);case"setup":await At(e),process.exit(0);case"theme":await It(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":we(),process.exit(0);case"-V":case"--version":case"-v":_t(),process.exit(0);default:console.error(`unknown argument: ${t}`),we(),process.exit(1)}}}function v(e,t){e.readyState===ue.OPEN&&e.send(JSON.stringify(t))}const F=new Set,_e=[];await g.read(),g.data.sessionAccess??=[],g.data.pinnedViews??=[],g.data.watchedPanes??=[],g.data.triggeredTasks??=[],g.data.quickCommands??=[];const re=await Z();let j=await Rt();const h=re.commandbar===!0,ne=fr(Ie,re.terminalRenderer),Ce=Ae(re.scheduleHistoryDays),Re=w.join(process.cwd(),"extensions"),Ee=await yt(Re);for(const e of Ee)e.start&&_e.push(wt(e.dir,e));const E=new jt({db:g,historyRetentionMs:Ce*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await E.restoreFromDb();const s=new Ke;s.use("*",async(e,t)=>{await t(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline'","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self'","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,t)=>{const r=e.req.method;if(r==="GET"||r==="HEAD"||r==="OPTIONS")return t();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):t()}),gt(s,Re,Ee);const yr=w.dirname(ze(import.meta.url)),wr=[w.join(yr,"assets"),w.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of wr){const n=w.join(r,t);if(!V(n))continue;const i=await Ve(n),o=w.extname(t),d={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(i,200,{"Content-Type":d[o]??"application/octet-stream"})}return e.notFound()});const gr=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
3
3
  <rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
4
4
  <path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
5
5
  <line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
6
- </svg>`,Me=e=>e.body(yr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Me),s.get("/favicon.ico",Me),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(Je({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:j}))}),s.get("/",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(at({theme:j,commandbarEnabled:h,commandbarSessions:t,fsRoots:r,terminalCfg:K,renderer:ne,scrollback:K.initialLines+2*K.historyChunk}))}),s.get("/notes",_(),e=>{const t=h?C(q(),I()):[];return e.html(Ze(g.data.notes,j,h,t))}),s.get("/notes/:session",_(),e=>{const t=decodeURIComponent(e.req.param("session")),r=h?C(q(),I()):[];return e.html(et(t,j,h,r))}),s.get("/schedule",_(),e=>{const t=h?C(q(),I()):[];return e.html(nt(E.list(),E.listTriggered(),j,Ce,h,t))}),s.get("/history",_(),e=>{const t=q(),r=h?C(t,I()):[],n=new Set(t.map(i=>i.name));return e.html(ot(ft(),j,h,r,n))}),s.get("/quick-commands",_(),e=>{const t=h?C(q(),I()):[];return e.html(st(xe(),j,h,t))}),s.get("/files",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(it(j,h,t,r))}),s.post("/api/history/clear",a(),async e=>(await ht(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(xe())),s.post("/api/quick-commands",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await $t(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Vt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",a(),async e=>await Bt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{const t=X(e),r=R.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Se(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(D)return e.json({error:"Password setup in progress"},409);const p=pe(i);if(p)return e.json({error:p},400);D=!0;try{l.passwordHash=await le(i),ce(l),c("password_set",{ip:t})}finally{D=!1}const{plaintext:T}=f.createAccessToken("setup",l.security.tokenTtlDays);return qe(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await me(i,l.passwordHash)){const p=R.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:p.failures,permanentLock:p.permanentLock}),p.permanentLock?(f.revokeAll(),c("permanent_lock",{ip:t,failures:p.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):p.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:p.retryAfterMs},429)}R.recordSuccess(t);const d=`browser-${t}`,{plaintext:x}=f.createAccessToken(d,l.security.tokenTtlDays);return qe(e,x),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:x})}),s.post("/api/auth/token",a(),async e=>{const t=X(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=f.createAccessToken(n,i);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",a(),e=>e.json(f.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>f.revoke(e.req.param("id"))?(c("token_revoked",{ip:X(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(ur(e),e.json({ok:!0}))),s.get("/settings",_(),async e=>{const t=await Z(),r=t.terminalRenderer??"xterm";return e.html(tt({settings:t,renderer:ne,rendererOverridden:ne!==r,theme:j,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await Z(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Ae(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Ct({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:i,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const i=r==="remove"?await Ot(n):r==="add"?await Mt(n):{ok:!1,output:"unknown action"};return i.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(i.output.slice(0,800)),303)}),s.get("/settings/theme",_(),e=>e.html(rt({theme:j,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!je(r)?e.redirect("/settings/theme",303):(j=await ge(r),e.redirect("/settings/theme?saved=1",303))});const wr={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:j.template,templates:Et.map(t=>({id:t,name:wr[t]}))})),s.post("/api/theme",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!je(r)?e.json({error:"invalid theme template"},400):(j=await ge(r),e.json({ok:!0,active:j.template}))}),s.get("/api/system/status",a(),e=>e.json(Zt())),s.get("/api/sessions",a(),e=>h?e.json(C(q(),I())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(q().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return or(r,n),e.json({ok:!0})}catch(o){const d=o instanceof z?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",a(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(sr(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",a(),async e=>{try{const{name:t}=await e.req.json();return t?(ir(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=pt(t),i=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:i})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=w.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",d=r.endsWith("/");if(!d)try{d=N(r).isDirectory()}catch{}d||(i=w.dirname(r),o=w.basename(r).toLowerCase());try{const x=de(i),p=[],T=[];for(const M of x)if(!M.startsWith(".")&&!(o&&!M.toLowerCase().startsWith(o))){try{const S=w.join(i,M);N(S).isDirectory()?(p.push(S),n&&mt(S,p,T,0)):T.push(S)}catch{}if(p.length+T.length>=5e3)break}return e.json({dirs:p,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",a(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=G(t);if(!N(r).isFile())return e.json({error:"not a file"},400);const n=N(r).size;if(n>he)return e.json({error:"file too large",size:n,maxBytes:he},413);const i=We(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=G(t.path);return lt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return N(r).isFile()?($e(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return V(r)?e.json({error:"file already exists"},409):(Ue(w.dirname(r),{recursive:!0}),Be(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function oe(e){return{...Wt(q(),I(),Nt()),currentSession:e??null}}function Oe(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",a(),e=>{const t=e.req.query("currentSession");return e.json(oe(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(ve(t))}),s.post("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Lt(r.sessionName,r.windowIndex),e.json(oe()))}),s.delete("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Pt(r.sessionName,r.windowIndex),e.json(oe()))}),s.get("/api/notes",a(),e=>{const t=[...g.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",a(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=g.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},i=g.data.notes.findIndex(o=>o.scope===t);return i>=0?g.data.notes[i]=n:g.data.notes.push(n),await g.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!q().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const i=n.file;if(!(i instanceof File))return e.json({error:"missing file field"},400);try{const o=await i.arrayBuffer(),d=Buffer.from(o),{path:x}=await Jt(d,i.type||void 0,i.name||void 0);return e.json({path:x})}catch(o){return o instanceof Yt?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Dt(t).map(o=>[o.windowIndex,o.label])),n=new Map(ve(t).map(o=>[o.index,o])),i=er(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(i)}),s.post("/api/session/:session/window-label",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const i=typeof r.label=="string"?r.label:"",o=await Ht(t,n,i);return e.json(o)}),s.post("/api/session/:session/select-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return tr(t,n),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[select-window]",i),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return nr(t,n,r.name),ke(t),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[rename-window]",i),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return rr(t),ke(t),e.json({ok:!0})}catch(r){return r instanceof z?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(E.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!kt(t))return e.json({error:"invalid body"},400);const n=await E.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await E.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!vt(t))return e.json({error:"invalid body"},400);const n=await E.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const gr=parseInt(process.env.PORT||"21000",10),jr=Qe({fetch:s.fetch,port:gr,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),se=new Xe({noServer:!0});function Le(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
6
+ </svg>`,Me=e=>e.body(gr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Me),s.get("/favicon.ico",Me),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(Je({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:j}))}),s.get("/",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(at({theme:j,commandbarEnabled:h,commandbarSessions:t,fsRoots:r,terminalCfg:K,renderer:ne,scrollback:K.initialLines+2*K.historyChunk}))}),s.get("/notes",_(),e=>{const t=h?C(q(),I()):[];return e.html(Ze(g.data.notes,j,h,t))}),s.get("/notes/:session",_(),e=>{const t=decodeURIComponent(e.req.param("session")),r=h?C(q(),I()):[];return e.html(et(t,j,h,r))}),s.get("/schedule",_(),e=>{const t=h?C(q(),I()):[];return e.html(nt(E.list(),E.listTriggered(),j,Ce,h,t))}),s.get("/history",_(),e=>{const t=q(),r=h?C(t,I()):[],n=new Set(t.map(i=>i.name));return e.html(ot(ft(),j,h,r,n))}),s.get("/quick-commands",_(),e=>{const t=h?C(q(),I()):[];return e.html(st(xe(),j,h,t))}),s.get("/files",_(),e=>{const t=h?C(q(),I()):[],r=fe();return e.html(it(j,h,t,r))}),s.post("/api/history/clear",a(),async e=>(await ht(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(xe())),s.post("/api/quick-commands",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await $t(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Vt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",a(),async e=>await Bt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{const t=X(e),r=R.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Se(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(D)return e.json({error:"Password setup in progress"},409);const p=pe(i);if(p)return e.json({error:p},400);D=!0;try{l.passwordHash=await le(i),ce(l),c("password_set",{ip:t})}finally{D=!1}const{plaintext:T}=f.createAccessToken("setup",l.security.tokenTtlDays);return qe(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await me(i,l.passwordHash)){const p=R.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:p.failures,permanentLock:p.permanentLock}),p.permanentLock?(f.revokeAll(),c("permanent_lock",{ip:t,failures:p.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):p.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:p.retryAfterMs},429)}R.recordSuccess(t);const d=`browser-${t}`,{plaintext:x}=f.createAccessToken(d,l.security.tokenTtlDays);return qe(e,x),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:x})}),s.post("/api/auth/token",a(),async e=>{const t=X(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=f.createAccessToken(n,i);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",a(),e=>e.json(f.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>f.revoke(e.req.param("id"))?(c("token_revoked",{ip:X(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(lr(e),e.json({ok:!0}))),s.get("/settings",_(),async e=>{const t=await Z(),r=t.terminalRenderer??"xterm";return e.html(tt({settings:t,renderer:ne,rendererOverridden:ne!==r,theme:j,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await Z(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Ae(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Ct({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:i,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const i=r==="remove"?await Ot(n):r==="add"?await Mt(n):{ok:!1,output:"unknown action"};return i.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(i.output.slice(0,800)),303)}),s.get("/settings/theme",_(),e=>e.html(rt({theme:j,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!je(r)?e.redirect("/settings/theme",303):(j=await ge(r),e.redirect("/settings/theme?saved=1",303))});const jr={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:j.template,templates:Et.map(t=>({id:t,name:jr[t]}))})),s.post("/api/theme",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!je(r)?e.json({error:"invalid theme template"},400):(j=await ge(r),e.json({ok:!0,active:j.template}))}),s.get("/api/system/status",a(),e=>e.json(Zt())),s.get("/api/system/processes",a(),e=>e.json(er())),s.post("/api/system/kill",a(),async e=>{const{pid:t}=await e.req.json();if(typeof t!="number"||!Number.isInteger(t)||t<=0)return e.json({error:"invalid pid"},400);const r=tr(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/sessions",a(),e=>h?e.json(C(q(),I())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(q().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return ir(r,n),e.json({ok:!0})}catch(o){const d=o instanceof z?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",a(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(ar(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",a(),async e=>{try{const{name:t}=await e.req.json();return t?(dr(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=pt(t),i=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:i})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=w.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",d=r.endsWith("/");if(!d)try{d=N(r).isDirectory()}catch{}d||(i=w.dirname(r),o=w.basename(r).toLowerCase());try{const x=de(i),p=[],T=[];for(const M of x)if(!M.startsWith(".")&&!(o&&!M.toLowerCase().startsWith(o))){try{const S=w.join(i,M);N(S).isDirectory()?(p.push(S),n&&mt(S,p,T,0)):T.push(S)}catch{}if(p.length+T.length>=5e3)break}return e.json({dirs:p,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",a(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=G(t);if(!N(r).isFile())return e.json({error:"not a file"},400);const n=N(r).size;if(n>he)return e.json({error:"file too large",size:n,maxBytes:he},413);const i=We(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=G(t.path);return lt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return N(r).isFile()?($e(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=G(t.path);return V(r)?e.json({error:"file already exists"},409):(Ue(w.dirname(r),{recursive:!0}),Be(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function oe(e){return{...Wt(q(),I(),Nt()),currentSession:e??null}}function Oe(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",a(),e=>{const t=e.req.query("currentSession");return e.json(oe(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(ve(t))}),s.post("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Lt(r.sessionName,r.windowIndex),e.json(oe()))}),s.delete("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Oe(t);return"error"in r?e.json({error:r.error},400):(await Pt(r.sessionName,r.windowIndex),e.json(oe()))}),s.get("/api/notes",a(),e=>{const t=[...g.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",a(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=g.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},i=g.data.notes.findIndex(o=>o.scope===t);return i>=0?g.data.notes[i]=n:g.data.notes.push(n),await g.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!q().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const i=n.file;if(!(i instanceof File))return e.json({error:"missing file field"},400);try{const o=await i.arrayBuffer(),d=Buffer.from(o),{path:x}=await Jt(d,i.type||void 0,i.name||void 0);return e.json({path:x})}catch(o){return o instanceof Yt?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Dt(t).map(o=>[o.windowIndex,o.label])),n=new Map(ve(t).map(o=>[o.index,o])),i=rr(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(i)}),s.post("/api/session/:session/window-label",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const i=typeof r.label=="string"?r.label:"",o=await Ht(t,n,i);return e.json(o)}),s.post("/api/session/:session/select-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return nr(t,n),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[select-window]",i),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return sr(t,n,r.name),ke(t),e.json({ok:!0})}catch(i){return i instanceof z?e.json({error:i.message},i.status):(console.error("[rename-window]",i),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return or(t),ke(t),e.json({ok:!0})}catch(r){return r instanceof z?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(E.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!kt(t))return e.json({error:"invalid body"},400);const n=await E.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await E.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ye(t);if(r)return e.json({error:r},400);if(!vt(t))return e.json({error:"invalid body"},400);const n=await E.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const kr=parseInt(process.env.PORT||"21000",10),vr=Qe({fetch:s.fetch,port:kr,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),se=new Xe({noServer:!0});function Le(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
7
7
  \r
8
- `),e.destroy()}jr.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),i=n.pathname.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const o=dr(e),d=e.headers.origin,x=l.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(x.length===0||!x.includes(d))){c("ws_rejected_origin",{ip:o,origin:d}),Le(t,403,"Origin not allowed");return}const p=lr(o);if(p>=l.security.maxConnectionsPerIp){c("ws_rejected_per_ip_cap",{ip:o,liveFromIp:p}),Le(t,429,"Too many connections");return}const T=be(e)||n.searchParams.get("token")||"",M=T?f.validateToken(T):null;se.handleUpgrade(e,t,r,S=>{se.emit("connection",S,e,decodeURIComponent(i[1]),o,M)})}),se.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};H.set(e,o),c("ws_connected",{ip:n});let d=null;const{initialLines:x,historyChunk:p,syncIdleMs:T,syncMaxMs:M}=K;let S=!0,P=null,$=null,U=r,O=null;const B=()=>{P&&(clearTimeout(P),P=null),$&&(clearTimeout($),$=null)},ie=()=>{if(S){B(),S=!1;try{U=Gt(r)}catch{U=r}if(!Qt(U))try{const y=zt(U,x);v(e,{type:"snapshot",data:Te(y),lines:x})}catch{}}},Ne=()=>{S&&(P&&clearTimeout(P),P=setTimeout(ie,T))};function ae(){try{d=Ye.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){v(e,{type:"data",data:`\r
8
+ `),e.destroy()}vr.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),i=n.pathname.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const o=cr(e),d=e.headers.origin,x=l.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(x.length===0||!x.includes(d))){c("ws_rejected_origin",{ip:o,origin:d}),Le(t,403,"Origin not allowed");return}const p=pr(o);if(p>=l.security.maxConnectionsPerIp){c("ws_rejected_per_ip_cap",{ip:o,liveFromIp:p}),Le(t,429,"Too many connections");return}const T=be(e)||n.searchParams.get("token")||"",M=T?f.validateToken(T):null;se.handleUpgrade(e,t,r,S=>{se.emit("connection",S,e,decodeURIComponent(i[1]),o,M)})}),se.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};H.set(e,o),c("ws_connected",{ip:n});let d=null;const{initialLines:x,historyChunk:p,syncIdleMs:T,syncMaxMs:M}=K;let S=!0,P=null,$=null,U=r,O=null;const B=()=>{P&&(clearTimeout(P),P=null),$&&(clearTimeout($),$=null)},ie=()=>{if(S){B(),S=!1;try{U=Gt(r)}catch{U=r}if(!Qt(U))try{const y=zt(U,x);v(e,{type:"snapshot",data:Te(y),lines:x})}catch{}}},Ne=()=>{S&&(P&&clearTimeout(P),P=setTimeout(ie,T))};function ae(){try{d=Ye.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){v(e,{type:"data",data:`\r
9
9
  \x1B[31mFailed to attach to tmux session "${r}": ${u.message}\x1B[0m\r
10
10
  `}),te(e,1011,"pty spawn failed");return}F.add(d);let y=-1,m="";O=Ft(r,({activeIndex:u,windows:k})=>{v(e,{type:"window_changed",activeIndex:u,windows:k});const L=k.map(b=>b.index).join(","),W=u!==y||L!==m;y=u,m=L}),$=setTimeout(ie,M),d.onData(u=>{if(S){Ne();return}v(e,{type:"data",data:u})}),d.onExit(({exitCode:u})=>{B(),d&&F.delete(d),d=null,v(e,{type:"data",data:`\r
11
11
  \x1B[2m--- tmux exited (code ${u}) ---\x1B[0m\r
@@ -1,2 +1,2 @@
1
- import{execFileSync as k}from"node:child_process";import{capturePaneLines as v}from"./tmux-capture.js";import{listSessionPanes as S}from"./tmux-panes.js";import{getWatchedPanes as B,pruneWatchedPanes as M,recordWatchedPane as D}from"./watched-panes.js";import{identifyAgent as y,detectState as b}from"./agent-detect.js";function C(){const e=new Map;try{const o=k("ps",["-axo","pid=,ppid=,command="],{encoding:"utf-8",timeout:3e3,maxBuffer:8388608});for(const a of o.split(`
2
- `)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&e.set(parseInt(c[1],10),{ppid:parseInt(c[2],10),command:c[3]})}}catch{}return e}function A(e,o){if(o.size===0||e<=0)return[];const a=new Map;for(const[n,t]of o){const s=a.get(t.ppid);s?s.push(n):a.set(t.ppid,[n])}const c=[],f=[...a.get(e)??[]],p=o.get(e);p&&c.push(p.command);let m=0;for(;f.length&&m<200;){m++;const n=f.shift(),t=o.get(n);t&&c.push(t.command);const s=a.get(n);s&&f.push(...s)}return c}const l=new Map;function T(e){let o=5381;for(let a=0;a<e.length;a++)o=(o<<5)+o+e.charCodeAt(a)>>>0;return o}async function N(){const e=B();if(e.length===0)return[];const o=new Map;for(const n of e)o.has(n.sessionName)||o.set(n.sessionName,S(n.sessionName));const a=new Set;for(const n of o.values())for(const t of n)a.add(t.paneId);await M(a);for(const n of l.keys())a.has(n)||l.delete(n);const c=C(),f=new Set(e.map(n=>n.paneId)),p=[...e];for(const[n,t]of o){const s=t.find(d=>d.active&&d.windowActive);if(!s||f.has(s.paneId)||!y(s.command,A(s.pid,c)))continue;const u={paneId:s.paneId,sessionName:n,windowIndex:s.windowIndex,paneIndex:s.paneIndex};await D(u),f.add(s.paneId),p.unshift({...u,watchedAt:Date.now()})}const m=[];for(const n of p){const t=o.get(n.sessionName)?.find(h=>h.paneId===n.paneId);if(!t)continue;const s=A(t.pid,c),u=y(t.command,s);if(!u)continue;let d="unknown";try{const h=v(t.target,0);d=b(u,h);const g=T(h),x=l.get(t.paneId);l.set(t.paneId,g),d==="idle"&&x!==void 0&&x!==g&&(d="working")}catch{}m.push({paneId:t.paneId,sessionName:n.sessionName,windowIndex:t.windowIndex,paneIndex:t.paneIndex,agent:u,state:d,watchedAt:n.watchedAt})}return m}let i=null,I=[],P=0,r=null;const W=1500;function w(){P=Date.now(),N().then(e=>{I=e}).catch(()=>{})}function q(){return I}function z(){return i!==null}function F(){if(!i||r)return;const e=Date.now()-P;if(e>=W){w();return}r=setTimeout(()=>{r=null,w()},W-e),typeof r.unref=="function"&&r.unref()}function L(e=3500){i||(w(),i=setInterval(w,e),typeof i.unref=="function"&&i.unref())}function H(){i&&(clearInterval(i),i=null),r&&(clearTimeout(r),r=null),I=[]}export{q as getCachedAgentStatuses,z as isBackgroundWatchRunning,N as probeWatchedPanes,F as requestProbe,L as startBackgroundWatch,H as stopBackgroundWatch};
1
+ import{execFileSync as k}from"node:child_process";import{capturePaneLines as v}from"./tmux-capture.js";import{listSessionPanes as S}from"./tmux-panes.js";import{getWatchedPanes as B,pruneWatchedPanes as M,recordWatchedPane as D}from"./watched-panes.js";import{identifyAgent as y,detectState as b}from"./agent-detect.js";function C(){const t=new Map;try{const o=k("ps",["-axo","pid=,ppid=,command="],{encoding:"utf-8",timeout:3e3,maxBuffer:8388608});for(const a of o.split(`
2
+ `)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&t.set(parseInt(c[1],10),{ppid:parseInt(c[2],10),command:c[3]})}}catch{}return t}function A(t,o){if(o.size===0||t<=0)return[];const a=new Map;for(const[n,e]of o){const s=a.get(e.ppid);s?s.push(n):a.set(e.ppid,[n])}const c=[],f=[...a.get(t)??[]],p=o.get(t);p&&c.push(p.command);let m=0;for(;f.length&&m<200;){m++;const n=f.shift(),e=o.get(n);e&&c.push(e.command);const s=a.get(n);s&&f.push(...s)}return c}const l=new Map;function T(t){let o=5381;for(let a=0;a<t.length;a++)o=(o<<5)+o+t.charCodeAt(a)>>>0;return o}async function N(){const t=B();if(t.length===0)return[];const o=new Map;for(const n of t)o.has(n.sessionName)||o.set(n.sessionName,S(n.sessionName));const a=new Set;for(const n of o.values())for(const e of n)a.add(e.paneId);await M(a);for(const n of l.keys())a.has(n)||l.delete(n);const c=C(),f=new Set(t.map(n=>n.paneId)),p=[...t];for(const[n,e]of o){const s=e.find(d=>d.active&&d.windowActive);if(!s||f.has(s.paneId)||!y(s.command,A(s.pid,c)))continue;const u={paneId:s.paneId,sessionName:n,windowIndex:s.windowIndex,paneIndex:s.paneIndex};await D(u),f.add(s.paneId),p.unshift({...u,watchedAt:Date.now()})}const m=[];for(const n of p){const e=o.get(n.sessionName)?.find(h=>h.paneId===n.paneId);if(!e)continue;const s=A(e.pid,c),u=y(e.command,s);if(!u)continue;let d="unknown";try{const h=v(e.target,0);d=b(u,h);const g=T(h),x=l.get(e.paneId);l.set(e.paneId,g),d==="idle"&&x!==void 0&&x!==g&&(d="working")}catch{}m.push({paneId:e.paneId,sessionName:n.sessionName,windowIndex:e.windowIndex,paneIndex:e.paneIndex,agent:u,state:d,watchedAt:n.watchedAt})}return m}let i=null,I=[],P=0,r=null;const W=1500;function w(){P=Date.now(),N().then(t=>{I=t}).catch(()=>{})}function q(){return I}function z(){return i!==null}function F(){if(!i||r)return;const t=Date.now()-P;if(t>=W){w();return}r=setTimeout(()=>{r=null,w()},W-t),typeof r.unref=="function"&&r.unref()}function L(t=3500){i||(w(),i=setInterval(w,t),typeof i.unref=="function"&&i.unref())}function H(){i&&(clearInterval(i),i=null),r&&(clearTimeout(r),r=null),I=[]}export{q as getCachedAgentStatuses,z as isBackgroundWatchRunning,N as probeWatchedPanes,F as requestProbe,L as startBackgroundWatch,H as stopBackgroundWatch};
@@ -1,4 +1,4 @@
1
- import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarHTML as x,commandbarScript as g}from"../commandbar.js";import{newSessionModalCSS as f,newSessionModalHTML as u,newSessionModalScript as h,reducedMotion as v}from"../shared-layout.js";import{icon as n}from"../icons.js";function $(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function o(a="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${a}; outline: none;`}function C(a){const{theme:e,commandbarEnabled:t,commandbarSessions:i,fsRoots:s,terminalCfg:l,renderer:r,scrollback:d}=a,p=`
1
+ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarHTML as f,commandbarScript as v}from"../commandbar.js";import{newSessionModalCSS as h,newSessionModalHTML as y,newSessionModalScript as k,reducedMotion as w}from"../shared-layout.js";import{icon as n}from"../icons.js";import{getSystemStatus as S}from"../system-monitor.js";function M(o){return o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function r(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function P(o){const{theme:e,commandbarEnabled:t,commandbarSessions:l,fsRoots:p,terminalCfg:d,renderer:i,scrollback:c}=o,s=S(),b=s.memory.percent,a=s.cpu.loadAvg[0],m=`
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
  html, body {
4
4
  background: var(--page-bg); color: var(--page-fg);
@@ -25,30 +25,67 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
25
25
 
26
26
  /* \u2500\u2500 System status in header \u2500\u2500 */
27
27
  .sys-status {
28
- display: flex; align-items: center; gap: 8px;
28
+ display: flex; align-items: center;
29
29
  font-size: 11px; font-family: var(--font-mono);
30
- color: var(--panel-muted); cursor: default;
30
+ color: color-mix(in srgb, var(--page-fg) 55%, transparent);
31
+ cursor: pointer;
31
32
  margin: 0 12px;
33
+ white-space: nowrap;
34
+ padding: 4px 6px;
35
+ border-radius: 4px;
36
+ transition: background 0.15s;
37
+ }
38
+ .sys-status:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
39
+
40
+ /* \u2500\u2500 Process popover \u2500\u2500 */
41
+ #process-panel {
42
+ position: fixed; top: 52px; right: 12px; z-index: 300;
43
+ background: var(--panel-bg); border: 1px solid var(--panel-border);
44
+ border-radius: 8px;
45
+ box-shadow: 0 8px 24px rgba(0,0,0,0.25);
46
+ min-width: 340px; max-width: 480px;
47
+ max-height: 70vh;
48
+ display: none; flex-direction: column;
49
+ font-size: 12px;
50
+ }
51
+ #process-panel.open { display: flex; }
52
+ #process-panel .panel-header {
53
+ display: flex; justify-content: space-between; align-items: center;
54
+ padding: 8px 12px;
55
+ border-bottom: 1px solid var(--panel-border);
56
+ font-weight: 600; font-size: 11px; text-transform: uppercase;
57
+ letter-spacing: 0.5px; color: var(--panel-muted);
32
58
  }
33
- .sys-status .mem-bar {
34
- width: 40px; height: 6px;
35
- background: color-mix(in srgb, var(--panel-muted) 20%, transparent);
36
- border-radius: 3px; overflow: hidden;
59
+ #process-panel .panel-header button {
60
+ background: none; border: none; color: var(--panel-muted);
61
+ cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 14px;
37
62
  }
38
- .sys-status .mem-bar-fill {
39
- height: 100%; border-radius: 3px;
40
- transition: width 0.5s ease;
63
+ #process-panel .panel-header button:hover { color: var(--page-fg); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
64
+ #process-panel .proc-list { overflow-y: auto; flex: 1; padding: 4px 0; }
65
+ #process-panel .proc-row {
66
+ display: flex; align-items: center; gap: 6px;
67
+ padding: 4px 12px;
41
68
  }
42
- .sys-status .mem-bar-fill.low { background: var(--panel-success, #16a34a); }
43
- .sys-status .mem-bar-fill.mid { background: #eab308; }
44
- .sys-status .mem-bar-fill.high { background: #ef4444; }
45
- .sys-status .mem-text { min-width: 3ch; text-align: right; }
46
- .sys-status .cpu-text { color: color-mix(in srgb, var(--panel-muted) 70%, transparent); }
47
- @media (max-width: 720px) {
48
- .sys-status .cpu-text { display: none; }
69
+ #process-panel .proc-row:hover { background: color-mix(in srgb, var(--panel-accent) 4%, transparent); }
70
+ #process-panel .proc-mem { width: 44px; text-align: right; font-family: var(--font-mono); color: var(--panel-muted); flex-shrink: 0; }
71
+ #process-panel .proc-rss { width: 60px; text-align: right; font-family: var(--font-mono); color: var(--panel-muted); flex-shrink: 0; }
72
+ #process-panel .proc-cmd {
73
+ flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
74
+ color: var(--page-fg);
49
75
  }
50
- @media (max-width: 540px) {
51
- .sys-status { display: none; }
76
+ #process-panel .proc-kill {
77
+ background: none; border: none; color: var(--panel-muted);
78
+ cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 13px; flex-shrink: 0;
79
+ }
80
+ #process-panel .proc-kill:hover { color: #ef4444; background: color-mix(in srgb, #ef4444 10%, transparent); }
81
+ #process-panel .proc-empty { padding: 24px; text-align: center; color: var(--panel-muted); }
82
+
83
+ @media (max-width: 600px) {
84
+ #process-panel {
85
+ top: auto; bottom: 0; left: 0; right: 0;
86
+ min-width: 0; max-width: none; max-height: 60vh;
87
+ border-radius: 12px 12px 0 0;
88
+ }
52
89
  }
53
90
 
54
91
  /* \u2500\u2500 App layout \u2500\u2500 */
@@ -91,7 +128,7 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
91
128
  }
92
129
  .sidebar-footer .mode-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
93
130
  .sidebar-footer .mode-btn.active { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 10%, transparent); }
94
- .sidebar-footer .mode-btn:focus-visible { ${o()} }
131
+ .sidebar-footer .mode-btn:focus-visible { ${r()} }
95
132
 
96
133
  /* \u2500\u2500 Sidebar session mode \u2500\u2500 */
97
134
  .session-item {
@@ -113,7 +150,7 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
113
150
  }
114
151
  .session-item:hover .session-edit-btn { display: flex; align-items: center; }
115
152
  .session-edit-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
116
- .session-edit-btn:focus-visible { ${o()} }
153
+ .session-edit-btn:focus-visible { ${r()} }
117
154
 
118
155
  .sidebar-section-label {
119
156
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
@@ -130,7 +167,7 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
130
167
  color: var(--panel-accent); border-color: var(--panel-accent);
131
168
  background: color-mix(in srgb, var(--panel-accent) 6%, transparent);
132
169
  }
133
- .new-session-sidebar-btn:focus-visible { ${o()} }
170
+ .new-session-sidebar-btn:focus-visible { ${r()} }
134
171
  .new-session-sidebar-btn svg { width: 16px; height: 16px; fill: currentColor; }
135
172
 
136
173
  /* \u2500\u2500 Sidebar files mode \u2500\u2500 */
@@ -204,7 +241,7 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
204
241
  color: var(--page-fg); transition: opacity 0.15s; text-decoration: none;
205
242
  }
206
243
  .btn:hover { opacity: 0.85; }
207
- .btn:focus-visible { ${o()} }
244
+ .btn:focus-visible { ${r()} }
208
245
  .btn.primary { background: var(--panel-accent); border-color: var(--panel-accent); color: var(--panel-accent-on); font-weight: 500; }
209
246
  .btn.primary:hover { opacity: 0.9; }
210
247
  .btn.danger { color: #b91c1c; border-color: color-mix(in srgb, #b91c1c 30%, transparent); }
@@ -339,9 +376,6 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
339
376
  .mobile-keys .mk-input button:hover {
340
377
  background: color-mix(in srgb, var(--panel-success) 12%, transparent);
341
378
  }
342
- @media (max-width: 640px) {
343
- .mobile-keys { display: flex; }
344
- }
345
379
 
346
380
  /* \u2500\u2500 File list view (when no root selected) \u2500\u2500 */
347
381
  .file-roots-list { list-style: none; padding: 0; margin: 16px; }
@@ -356,16 +390,20 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
356
390
  .file-roots-list li svg { width: 20px; height: 20px; flex-shrink: 0; fill: var(--panel-accent); }
357
391
 
358
392
  /* \u2500\u2500 Commandbar \u2500\u2500 */
359
- ${t?m():""}
360
- ${v()}
361
- ${f()}
393
+ ${t?x():""}
394
+ ${w()}
395
+ ${h()}
362
396
  @media (max-width: 640px) {
363
397
  .app-layout { flex-direction: column; }
364
398
  .sidebar { flex: 0 0 auto; border-right: none; border-bottom: 1px solid var(--panel-border); transition: max-height 0.2s; }
365
399
  .sidebar.collapsed .sidebar-content { display: none; }
400
+ .sidebar.collapsed .sidebar-footer { display: none; }
366
401
  .sidebar.collapsed { max-height: 48px; overflow: hidden; }
402
+ .mobile-keys { display: flex; }
403
+ .mobile-keys .mk-buttons { display: none; }
404
+ .mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
367
405
  }
368
- `,y=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),c=JSON.stringify({terminal:l,scrollback:d,theme:e.terminal,renderer:r,fsRoots:s,commandbarEnabled:t}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
406
+ `,z=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),u=JSON.stringify({terminal:d,scrollback:c,theme:e.terminal,renderer:i,fsRoots:p,commandbarEnabled:t}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
369
407
  <html lang="en">
370
408
  <head>
371
409
  <meta charset="UTF-8" />
@@ -373,8 +411,8 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
373
411
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
374
412
  <title>tmux-weblink</title>
375
413
  <style>
376
- ${b(e.shell)}
377
- ${p}
414
+ ${g(e.shell)}
415
+ ${m}
378
416
  </style>
379
417
  </head>
380
418
  <body>
@@ -397,17 +435,21 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
397
435
  </script>
398
436
 
399
437
  <header class="fixed-header">
400
- <div class="brand"><a href="/">tmux<span>-weblink</span></a></div>
401
- <div class="sys-status" id="sys-status">
402
- <span class="mem-text" id="sys-mem-text">--</span>
403
- <span class="mem-bar"><span class="mem-bar-fill" id="sys-mem-bar" style="width:0%"></span></span>
404
- <span class="cpu-text" id="sys-cpu-text">--</span>
405
- </div>
438
+ <div class="brand" id="brand-toggle">tmux<span>-weblink</span></div>
406
439
  <div class="header-actions">
407
440
  ${t?`<button class="header-btn" id="cmdbar-btn" title="Search" aria-label="Search">${n("search")}</button>`:""}
408
441
  </div>
442
+ <div class="sys-status" id="sys-status">RAM ${b}% / CPU ${a<10?a.toFixed(1):Math.round(a)}</div>
409
443
  </header>
410
444
 
445
+ <div id="process-panel">
446
+ <div class="panel-header">
447
+ <span>Top Processes</span>
448
+ <button id="proc-close" aria-label="Close">&times;</button>
449
+ </div>
450
+ <div class="proc-list" id="proc-list"></div>
451
+ </div>
452
+
411
453
  <div class="app-layout">
412
454
  <aside class="sidebar">
413
455
  <div class="sidebar-content" id="sidebar-content"></div>
@@ -444,17 +486,19 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
444
486
  <textarea id="mk-input" placeholder="Type or voice input\u2026" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" rows="1"></textarea>
445
487
  <button id="mk-send" type="button" title="Send (Enter)">&#9166;</button>
446
488
  </div>
447
- <button data-key="esc">ESC</button>
448
- <button data-key="tab">Tab</button>
449
- <button data-key="s-tab">S-Tab</button>
450
- <button data-key="up">\u2191</button>
451
- <button data-key="down">\u2193</button>
452
- <button data-key="left">\u2190</button>
453
- <button data-key="right">\u2192</button>
454
- <button data-key="space">\u2423</button>
455
- <button data-key="enter">\u21B5 Enter</button>
456
- <button data-key="exit">Exit</button>
457
- <button data-key="yes">Yes</button>
489
+ <div class="mk-buttons" id="mk-buttons">
490
+ <button data-key="esc">ESC</button>
491
+ <button data-key="tab">Tab</button>
492
+ <button data-key="s-tab">S-Tab</button>
493
+ <button data-key="up">\u2191</button>
494
+ <button data-key="down">\u2193</button>
495
+ <button data-key="left">\u2190</button>
496
+ <button data-key="right">\u2192</button>
497
+ <button data-key="space">\u2423</button>
498
+ <button data-key="enter">\u21B5 Enter</button>
499
+ <button data-key="exit">Exit</button>
500
+ <button data-key="yes">Yes</button>
501
+ </div>
458
502
  </div>
459
503
  </main>
460
504
  </div>
@@ -471,7 +515,7 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
471
515
  <hr class="settings-divider" />
472
516
  <div class="settings-item">
473
517
  <span>Terminal renderer</span>
474
- <span style="font-size:var(--text-xs);color:var(--panel-muted)">${r==="ghostty"?"Ghostty":"xterm.js"}</span>
518
+ <span style="font-size:var(--text-xs);color:var(--panel-muted)">${i==="ghostty"?"Ghostty":"xterm.js"}</span>
475
519
  </div>
476
520
  <hr class="settings-divider" />
477
521
  <div style="padding:4px 0">
@@ -505,21 +549,21 @@ import{cssVarsStyle as b}from"../theme.js";import{commandbarCSS as m,commandbarH
505
549
  })();
506
550
  </script>
507
551
 
508
- ${u()}
509
- ${t?x():""}
552
+ ${y()}
553
+ ${t?f():""}
510
554
 
511
555
  <script>
512
- window.__TMUX_WEB_SHELL__ = ${c};
556
+ window.__TMUX_WEB_SHELL__ = ${u};
513
557
  </script>
514
558
  <script type="module">
515
559
  await import('/assets/shell-client.js');
516
- ${t?g(i,[]):""}
560
+ ${t?v(l,[]):""}
517
561
  // Wrapper: refresh sidebar list, then open session
518
562
  window.__onSessionCreated = async function(name) {
519
563
  await window.__refreshSidebar();
520
564
  window.__openSession(name);
521
565
  };
522
- ${h("__onSessionCreated")}
566
+ ${k("__onSessionCreated")}
523
567
  </script>
524
568
  <script>
525
569
  // Sync app-layout height to visual viewport so mobile keyboard doesn't overlap
@@ -534,23 +578,125 @@ ${h("__onSessionCreated")}
534
578
  </script>
535
579
  <script>
536
580
  (function(){
537
- var memText = document.getElementById('sys-mem-text');
538
- var memBar = document.getElementById('sys-mem-bar');
539
- var cpuText = document.getElementById('sys-cpu-text');
540
- if (!memText) return;
581
+ var el = document.getElementById('sys-status');
582
+ if (!el) return;
541
583
  function poll(){
542
584
  fetch('/api/system/status').then(function(r){ return r.json(); }).then(function(s){
543
585
  var p = s.memory.percent;
544
- memText.textContent = p + '%';
545
- memBar.style.width = p + '%';
546
- memBar.className = 'mem-bar-fill ' + (p >= 85 ? 'high' : p >= 70 ? 'mid' : 'low');
547
586
  var load = s.cpu.loadAvg[0];
548
- cpuText.textContent = 'CPU ' + (load < 10 ? load.toFixed(1) : Math.round(load));
587
+ el.textContent = 'RAM ' + p + '% / CPU ' + (load < 10 ? load.toFixed(1) : Math.round(load));
549
588
  }).catch(function(){});
550
589
  }
551
590
  poll();
552
591
  setInterval(poll, 5000);
553
592
  })();
554
593
  </script>
594
+ <script>
595
+ (function(){
596
+ var statusEl = document.getElementById('sys-status');
597
+ var panel = document.getElementById('process-panel');
598
+ var list = document.getElementById('proc-list');
599
+ var closeBtn = document.getElementById('proc-close');
600
+ if (!statusEl || !panel || !list || !closeBtn) return;
601
+
602
+ function loadProcs(){
603
+ list.innerHTML = '<div class="proc-empty">Loading\u2026</div>';
604
+ fetch('/api/system/processes').then(function(r){ return r.json(); }).then(function(procs){
605
+ if (!Array.isArray(procs) || !procs.length) {
606
+ list.innerHTML = '<div class="proc-empty">No processes</div>';
607
+ return;
608
+ }
609
+ var html = '';
610
+ for (var i = 0; i < procs.length; i++) {
611
+ var p = procs[i];
612
+ var rss = p.rss;
613
+ var rssStr = rss < 1048576 ? (rss / 1024).toFixed(0) + 'K' : (rss / 1048576).toFixed(1) + 'M';
614
+ html += '<div class="proc-row" data-pid="' + p.pid + '">'
615
+ + '<span class="proc-mem">' + p.mem + '%</span>'
616
+ + '<span class="proc-rss">' + rssStr + '</span>'
617
+ + '<span class="proc-cmd" title="' + escAttr(p.command) + '">' + escHtml(p.command) + '</span>'
618
+ + '<button class="proc-kill" title="Kill PID ' + p.pid + '">&times;</button>'
619
+ + '</div>';
620
+ }
621
+ list.innerHTML = html;
622
+ // Wire kill buttons
623
+ var rows = list.querySelectorAll('.proc-row');
624
+ for (var j = 0; j < rows.length; j++) {
625
+ var btn = rows[j].querySelector('.proc-kill');
626
+ if (!btn) continue;
627
+ btn.addEventListener('click', function(e){
628
+ var row = e.target.closest('.proc-row');
629
+ if (!row || !confirm('Kill PID ' + row.dataset.pid + '?')) return;
630
+ fetch('/api/system/kill', {
631
+ method: 'POST',
632
+ headers: {'Content-Type': 'application/json'},
633
+ body: JSON.stringify({ pid: parseInt(row.dataset.pid, 10) }),
634
+ }).then(function(r){ return r.json(); }).then(function(data){
635
+ if (data.ok) { row.style.opacity = '0.3'; }
636
+ else { alert('Failed: ' + (data.error || 'unknown')); }
637
+ }).catch(function(){ alert('Network error'); });
638
+ });
639
+ }
640
+ }).catch(function(){
641
+ list.innerHTML = '<div class="proc-empty">Failed to load</div>';
642
+ });
643
+ }
644
+
645
+ function openPanel(){
646
+ panel.classList.add('open');
647
+ loadProcs();
648
+ }
649
+
650
+ function closePanel(){ panel.classList.remove('open'); }
651
+
652
+ statusEl.addEventListener('click', function(e){
653
+ if (panel.classList.contains('open')) { closePanel(); }
654
+ else { openPanel(); }
655
+ });
656
+ closeBtn.addEventListener('click', closePanel);
657
+ document.addEventListener('click', function(e){
658
+ if (panel.classList.contains('open') && !panel.contains(e.target) && e.target !== statusEl) {
659
+ closePanel();
660
+ }
661
+ });
662
+ document.addEventListener('keydown', function(e){
663
+ if (e.key === 'Escape' && panel.classList.contains('open')) closePanel();
664
+ });
665
+
666
+ function escHtml(s){ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
667
+ function escAttr(s){ return s.replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
668
+ })();
669
+ </script>
670
+ <script>
671
+ (function(){
672
+ /* Logo toggle sidebar */
673
+ var brand = document.getElementById('brand-toggle');
674
+ var sbar = document.querySelector('.sidebar');
675
+ if (brand && sbar) {
676
+ brand.addEventListener('click', function(e) {
677
+ sbar.classList.toggle('collapsed');
678
+ });
679
+ }
680
+
681
+ /* Mobile keys: show buttons on input focus, hide on blur */
682
+ var mkInput = document.getElementById('mk-input');
683
+ var mobileKeys = document.getElementById('mobile-keys');
684
+ var hideTimer = null;
685
+ if (mkInput && mobileKeys) {
686
+ mkInput.addEventListener('focus', function() {
687
+ clearTimeout(hideTimer);
688
+ mobileKeys.classList.add('mk-focused');
689
+ });
690
+ mkInput.addEventListener('blur', function() {
691
+ hideTimer = setTimeout(function() {
692
+ mobileKeys.classList.remove('mk-focused');
693
+ }, 200);
694
+ });
695
+ mobileKeys.addEventListener('mousedown', function() {
696
+ clearTimeout(hideTimer);
697
+ });
698
+ }
699
+ })();
700
+ </script>
555
701
  </body>
556
- </html>`}export{C as renderShell};
702
+ </html>`}export{P as renderShell};
@@ -1 +1,2 @@
1
- import*as t from"node:os";function m(){const o=t.totalmem(),r=t.freemem(),s=o-r;return{memory:{total:o,free:r,used:s,percent:o>0?Math.round(s/o*100):0},cpu:{loadAvg:t.loadavg(),cores:t.cpus().length},uptime:{system:t.uptime(),process:process.uptime()},process:{rss:process.memoryUsage().rss,heapTotal:process.memoryUsage().heapTotal,heapUsed:process.memoryUsage().heapUsed},hostname:t.hostname(),platform:`${t.platform()} ${t.release()}`}}function p(o){return o<1024?`${o} B`:o<1024*1024?`${(o/1024).toFixed(1)} KB`:o<1024*1024*1024?`${(o/(1024*1024)).toFixed(1)} MB`:`${(o/(1024*1024*1024)).toFixed(1)} GB`}function n(o){const r=Math.floor(o/86400),s=Math.floor(o%86400/3600),a=Math.floor(o%3600/60),e=[];return r>0&&e.push(`${r}d`),s>0&&e.push(`${s}h`),a>0&&e.push(`${a}m`),e.length===0&&e.push(`${Math.floor(o)}s`),e.join(" ")}export{p as formatBytes,n as formatDuration,m as getSystemStatus};
1
+ import*as r from"node:os";import{execFileSync as i}from"node:child_process";function p(){const t=r.totalmem(),e=r.freemem(),o=t-e;return{memory:{total:t,free:e,used:o,percent:t>0?Math.round(o/t*100):0},cpu:{loadAvg:r.loadavg(),cores:r.cpus().length},uptime:{system:r.uptime(),process:process.uptime()},process:{rss:process.memoryUsage().rss,heapTotal:process.memoryUsage().heapTotal,heapUsed:process.memoryUsage().heapUsed},hostname:r.hostname(),platform:`${r.platform()} ${r.release()}`}}function l(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:t<1024*1024*1024?`${(t/(1024*1024)).toFixed(1)} MB`:`${(t/(1024*1024*1024)).toFixed(1)} GB`}function f(t){const e=Math.floor(t/86400),o=Math.floor(t%86400/3600),m=Math.floor(t%3600/60),s=[];return e>0&&s.push(`${e}d`),o>0&&s.push(`${o}h`),m>0&&s.push(`${m}m`),s.length===0&&s.push(`${Math.floor(t)}s`),s.join(" ")}const c=10;function h(){try{const e=i("ps",["aux","--sort=-%mem"],{encoding:"utf-8",timeout:3e3}).trim().split(`
2
+ `),o=[];for(let m=1;m<e.length&&o.length<c;m++){const n=e[m].match(/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(.+)$/);if(!n)continue;const a=parseInt(n[6],10);o.push({pid:parseInt(n[2],10),user:n[1],cpu:n[3],mem:n[4],rss:isNaN(a)?0:a*1024,command:n[7]})}return o}catch{return[]}}function g(t){try{return i("kill",[String(t)],{timeout:3e3}),{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:"kill failed"}}}export{l as formatBytes,f as formatDuration,p as getSystemStatus,h as getTopProcesses,g as killProcess};
@@ -1 +1 @@
1
- import{db as a}from"./db.js";const d=50;async function s(e){const t={...e,watchedAt:Date.now()},n=a.data.watchedPanes.filter(c=>c.paneId!==e.paneId);a.data.watchedPanes=[t,...n].slice(0,d),await a.write()}function h(){return a.data.watchedPanes}async function r(e){const t=a.data.watchedPanes.length;a.data.watchedPanes=a.data.watchedPanes.filter(n=>e.has(n.paneId)),a.data.watchedPanes.length!==t&&await a.write()}export{d as MAX_WATCHED_PANES,h as getWatchedPanes,r as pruneWatchedPanes,s as recordWatchedPane};
1
+ import{db as a}from"./db.js";const d=50;async function c(t){const e={...t,watchedAt:Date.now()},n=a.data.watchedPanes.filter(s=>s.paneId!==t.paneId);a.data.watchedPanes=[e,...n].slice(0,d),await a.write()}function h(){return a.data.watchedPanes}async function r(t){const e=a.data.watchedPanes.length;a.data.watchedPanes=a.data.watchedPanes.filter(n=>t.has(n.paneId)),a.data.watchedPanes.length!==e&&await a.write()}export{d as MAX_WATCHED_PANES,h as getWatchedPanes,r as pruneWatchedPanes,c as recordWatchedPane};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {