tmux-weblink 1.4.2 → 1.4.4

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,33 +1,48 @@
1
- var wt="https://esm.sh/ghostty-web@0.4.0",bt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Z(t){return t.replace(bt,"")}var G;function Lt(){return G||(document.querySelector("link[data-tmux-web-xterm-css]")?G=Promise.resolve():(G=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)}),G))}var Se=class Ze{terminal;fitAddon;container;constructor(e,n,s){this.container=e,this.terminal=n,this.fitAddon=s,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,s){let[{Terminal:o},{FitAddon:l}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Lt()]),d=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:s});return new Ze(e,d,new l)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}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))}},Et=class Ge{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,s){let o=await import(wt);await o.init();let l=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:s});return new Ge(e,l)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}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 n=Math.floor(e.width/this.charW),s=Math.floor(e.height/this.charH);if(n<10||s<5)return!1;if(n!==this.colsValue||s!==this.rowsValue){this.colsValue=n,this.rowsValue=s,this.terminal.resize(n,s);for(let o of this.resizeCallbacks)o({cols:n,rows:s})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}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 n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let s=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=s,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function He(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function kt(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let s;if(n.renderer==="ghostty")try{s=await Et.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),s=await Se.create(t,n.scrollback,n.theme)}else s=await Se.create(t,n.scrollback,n.theme);let o,l=0,d,r=null,v=0,y="connecting",T=0,K=!1,X=[],D="",pe=!1,A=1e3,Qe=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),et="/api/session/"+encodeURIComponent(e)+"/upload",tt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function nt(){return X.join("")+D}async function st(i,a=0){let c=i?s.viewportY():0,b=i?s.baseY():0,k=Z(nt());if(s.reset(),!k){i||s.scrollToBottom();return}await s.write(k),i?s.scrollToLine(b+c+a):s.scrollToBottom()}function R(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function it(i){let a;try{a=JSON.parse(i)}catch{y==="live"&&(D+=i,s.write(Z(i)));return}if(a.type==="auth.required"){let c=localStorage.getItem("tmux-web-token");c?R({type:"auth.token",token:c}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),A=1e3,z(),R({type:"resize",cols:s.cols,rows:s.rows}),w();return}if(a.type==="auth.failed"){if(a.permanentLock||a.retryAfterMs&&a.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(a.message);return}return}if(a.type==="snapshot"&&typeof a.data=="string"){let c=Z(a.data);X=[c],D="",T=typeof a.lines=="number"?a.lines:n.terminal.initialLines,y="live",s.reset(),s.write(c).then(()=>s.scrollToBottom());return}if(a.type==="window_changed"&&typeof a.activeIndex=="number"){let c=new URL(location.href);c.searchParams.get("window")!==String(a.activeIndex)&&(c.searchParams.set("window",String(a.activeIndex)),history.replaceState(history.state,"",c)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:a.windows}));return}if(a.type==="history"&&typeof a.data=="string"){K=!1,a.lines>0&&a.data&&(X.unshift(a.data),T+=a.lines,st(!0,a.lines));return}if(a.type==="data"&&typeof a.data=="string"){let c=Z(a.data);if(y==="connecting"){y="live",D=c,s.write(c);return}y==="live"&&(D+=c,s.write(c))}}function be(){let i=Qe,a=localStorage.getItem("tmux-web-token");a&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),o=new WebSocket(i),o.onopen=()=>{y="connecting",T=0,K=!1,X=[],D="",s.reset(),A=1e3},o.onmessage=c=>{typeof c.data=="string"&&it(c.data)},o.onclose=()=>{pe||(y="connecting",setTimeout(()=>{A=Math.min(A*2,1e4),pe||be()},A))},o.onerror=()=>o?.close()}function _(i){R({type:"input",data:i})}function z(){return s.fit()}function ue(){t.classList.remove("terminal-pending")}function w(){l&&cancelAnimationFrame(l),d&&clearTimeout(d),z(),l=requestAnimationFrame(()=>{l=0,z()&&ue()}),d=setTimeout(()=>{z()&&ue()},120)}function at(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function Le(i){return typeof i=="string"&&i.startsWith("image/")}function Ee(i){return i?.files?.length?i.files[0]??null:null}function ot(i){if(!i?.items)return null;for(let a=0;a<i.items.length;a++){let c=i.items[a];if(c?.kind==="file"&&Le(c.type))return c.getAsFile()}return null}async function lt(i){let a=new FormData,c=i instanceof File&&i.name?i.name:"upload";a.append("file",i,c);let b=localStorage.getItem("tmux-web-token"),k={};b&&(k.Authorization="Bearer "+b);let me=await fetch(et,{method:"POST",body:a,headers:k});if(!me.ok){let Ie="upload failed";try{let Ce=await me.json();Ce.error&&(Ie=Ce.error)}catch{}throw new Error(Ie)}let Be=await me.json();if(!Be.path)throw new Error("no path in response");return Be.path}async function fe(i){try{let a=await lt(i);_(a)}catch{}}s.onData(i=>_(i)),s.onResize(({cols:i,rows:a})=>R({type:"resize",cols:i,rows:a})),s.onScroll(()=>{y!=="live"||K||!s.isNearScrollbackTop()||(K=!0,R({type:"load_history",before:T}))}),s.attachCustomKeyEventHandler(i=>i.type!=="keydown"?!0:(i.ctrlKey||i.metaKey)&&i.code==="KeyV"?(i.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let a=await navigator.clipboard.read();for(let c of a)for(let b of c.types)if(Le(b)){let k=await c.getType(b);await fe(k);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&s.pasteText(ke(a))}catch{}})(),!1):!0);let J=0;function ke(i){return i.split(`\r
1
+ var Ft="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ue(e){return e.replace(Vt,"")}var pe;function Yt(){return pe||(document.querySelector("link[data-tmux-web-xterm-css]")?pe=Promise.resolve():(pe=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),pe))}var Ze=class Lt{terminal;fitAddon;container;constructor(t,n,s){this.container=t,this.terminal=n,this.fitAddon=s,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,n,s){let[{Terminal:l},{FitAddon:o}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Yt()]),c=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:s});return new Lt(t,c,new o)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(n=>this.terminal.write(t,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t){this.terminal.scrollToLine(Math.max(0,t))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=Ge(this.container);return t.width<=0||t.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return!!t&&(t===this.container||this.container.contains(t))}},_t=class Et{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(t,n){this.container=t,this.terminal=n,this.terminal.open(t)}static async create(t,n,s){let l=await import(Ft);await l.init();let o=new l.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:s});return new Et(t,o)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(t){return new Promise(n=>this.terminal.write(t,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(t){this.terminal.scrollToLine?.(Math.max(0,t))}isNearScrollbackTop(){let t=this.terminal.buffer?.active;return t?t.viewportY>=t.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let t=Ge(this.container);if(t.width<=0||t.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let n=Math.floor(t.width/this.charW),s=Math.floor(t.height/this.charH);if(n<10||s<5)return!1;if(n!==this.colsValue||s!==this.rowsValue){this.colsValue=n,this.rowsValue=s,this.terminal.resize(n,s);for(let l of this.resizeCallbacks)l({cols:n,rows:s})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(t){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+t+"\x1B[201~":t)}input(t){this.terminal.input?.(t,!1)}onData(t){this.terminal.onData(n=>t(n))}onResize(t){this.resizeCallbacks.push(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return!!t&&(t===this.container||t===this.terminal.textarea||this.container.contains(t))}updateCellMetrics(t=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let s=n.offsetWidth/this.colsValue,l=n.offsetHeight/this.rowsValue;(t||!this.charW||!this.charH)&&(this.charW=s,this.charH=l)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Ge(e){let t=e.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(t.width,n.width),height:Math.max(0,Math.min(t.height,n.height-Math.max(0,t.top)))}:t}function Ot(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let s;if(n.renderer==="ghostty")try{s=await _t.create(e,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),s=await Ze.create(e,n.scrollback,n.theme)}else s=await Ze.create(e,n.scrollback,n.theme);let l,o=0,c,r=null,f=0,g="connecting",B=0,oe=!1,re=[],W="",Be=!1,_=1e3,kt=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(t),xt="/api/session/"+encodeURIComponent(t)+"/upload",Tt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function It(){return re.join("")+W}async function Mt(i,a=0){let d=i?s.viewportY():0,E=i?s.baseY():0,T=ue(It());if(s.reset(),!T){i||s.scrollToBottom();return}await s.write(T),i?s.scrollToLine(E+d+a):s.scrollToBottom()}function O(i){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(i))}function Bt(i){let a;try{a=JSON.parse(i)}catch{g==="live"&&(W+=i,s.write(ue(i)));return}if(a.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?O({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),_=1e3,K(),O({type:"resize",cols:s.cols,rows:s.rows}),L();return}if(a.type==="auth.failed"){if(a.permanentLock||a.retryAfterMs&&a.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(a.message);return}return}if(a.type==="snapshot"&&typeof a.data=="string"){let d=ue(a.data);re=[d],W="",B=typeof a.lines=="number"?a.lines:n.terminal.initialLines,g="live",s.reset(),s.write(d).then(()=>s.scrollToBottom());return}if(a.type==="window_changed"&&typeof a.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(a.activeIndex)&&(d.searchParams.set("window",String(a.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:a.windows}));return}if(a.type==="history"&&typeof a.data=="string"){oe=!1,a.lines>0&&a.data&&(re.unshift(a.data),B+=a.lines,Mt(!0,a.lines));return}if(a.type==="data"&&typeof a.data=="string"){let d=ue(a.data);if(g==="connecting"){g="live",W=d,s.write(d);return}g==="live"&&(W+=d,s.write(d))}}function je(){let i=kt,a=localStorage.getItem("tmux-web-token");a&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),l=new WebSocket(i),l.onopen=()=>{g="connecting",B=0,oe=!1,re=[],W="",s.reset(),_=1e3},l.onmessage=d=>{typeof d.data=="string"&&Bt(d.data)},l.onclose=()=>{Be||(g="connecting",setTimeout(()=>{_=Math.min(_*2,1e4),Be||je()},_))},l.onerror=()=>l?.close()}function ce(i){O({type:"input",data:i})}function K(){return s.fit()}function Ce(){e.classList.remove("terminal-pending")}function L(){o&&cancelAnimationFrame(o),c&&clearTimeout(c),K(),o=requestAnimationFrame(()=>{o=0,K()&&Ce()}),c=setTimeout(()=>{K()&&Ce()},120)}function Ct(){L(),setTimeout(L,50),setTimeout(L,150),setTimeout(L,300)}function Ue(i){return typeof i=="string"&&i.startsWith("image/")}function Fe(i){return i?.files?.length?i.files[0]??null:null}function St(i){if(!i?.items)return null;for(let a=0;a<i.items.length;a++){let d=i.items[a];if(d?.kind==="file"&&Ue(d.type))return d.getAsFile()}return null}async function Ht(i){let a=new FormData,d=i instanceof File&&i.name?i.name:"upload";a.append("file",i,d);let E=localStorage.getItem("tmux-web-token"),T={};E&&(T.Authorization="Bearer "+E);let He=await fetch(xt,{method:"POST",body:a,headers:T});if(!He.ok){let Xe="upload failed";try{let Je=await He.json();Je.error&&(Xe=Je.error)}catch{}throw new Error(Xe)}let Ke=await He.json();if(!Ke.path)throw new Error("no path in response");return Ke.path}async function Se(i){try{let a=await Ht(i);ce(a)}catch{}}s.onData(i=>ce(i)),s.onResize(({cols:i,rows:a})=>O({type:"resize",cols:i,rows:a})),s.onScroll(()=>{g!=="live"||oe||!s.isNearScrollbackTop()||(oe=!0,O({type:"load_history",before:B}))}),s.attachCustomKeyEventHandler(i=>i.type!=="keydown"?!0:(i.ctrlKey||i.metaKey)&&i.code==="KeyV"?(i.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let a=await navigator.clipboard.read();for(let d of a)for(let E of d.types)if(Ue(E)){let T=await d.getType(E);await Se(T);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&s.pasteText(Ve(a))}catch{}})(),!1):!0);let de=0;function Ve(i){return i.split(`\r
2
2
  `).join(`
3
3
  `).split(`
4
- `).join("\r")}async function rt(i){let a=ot(i.clipboardData);if(a){i.preventDefault(),i.stopPropagation();let k=Date.now();if(k-J<50)return;J=k,await fe(a);return}let c=i.clipboardData?.getData("text/plain");if(!c)return;i.preventDefault(),i.stopPropagation();let b=Date.now();b-J<50||(J=b,s.pasteText(ke(c)))}let P=0;function ct(i){i.preventDefault(),P++,Ee(i.dataTransfer)&&t.classList.add("terminal-drag-over")}function dt(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function pt(i){i.preventDefault(),P--,P<=0&&(P=0,t.classList.remove("terminal-drag-over"))}async function ut(i){i.preventDefault(),P=0,t.classList.remove("terminal-drag-over");let a=Ee(i.dataTransfer);a&&await fe(a)}function xe(i){!tt||i.key!=="Escape"||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),_("\x1B"))}t.addEventListener("paste",rt),t.addEventListener("dragenter",ct),t.addEventListener("dragover",dt),t.addEventListener("dragleave",pt),t.addEventListener("drop",ut),document.addEventListener("keydown",xe,!0);function ft(i){if(i.touches.length!==1){r=null;return}let a=i.touches[0];a&&(r={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,scrolling:!1},i.stopPropagation())}function mt(i){if(!r||i.touches.length!==1)return;let a=i.touches[0];if(!a)return;let c=a.clientY-r.startY,b=a.clientX-r.startX;if(!r.scrolling){if(Math.abs(c)<8||Math.abs(c)<Math.abs(b))return;r.scrolling=!0,v=Date.now()+500}i.preventDefault(),i.stopPropagation(),gt(-(a.clientY-r.lastY),a.clientX,a.clientY),r.lastX=a.clientX,r.lastY=a.clientY}function ht(i){if(!r)return;let a=r.scrolling;r=null,i.stopPropagation(),a?(v=Date.now()+500,i.preventDefault()):s.focus()}function vt(i){r=null,i.stopPropagation()}function yt(i){i.pointerType==="touch"&&Date.now()<v&&(i.preventDefault(),i.stopPropagation())}function gt(i,a,c){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:i,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:a,clientY:c,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",ft,{passive:!0,capture:!0}),t.addEventListener("touchmove",mt,{passive:!1,capture:!0}),t.addEventListener("touchend",ht,{passive:!1,capture:!0}),t.addEventListener("touchcancel",vt,{passive:!0,capture:!0}),t.addEventListener("pointerup",yt,!0);function Te(){w()}window.addEventListener("resize",Te),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);let Me=new ResizeObserver(()=>w());return Me.observe(t),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),t.addEventListener("focusin",at,!0),requestAnimationFrame(()=>{z()&&ue(),be(),w()}),{destroy(){if(pe=!0,o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",Te),Me.disconnect(),document.removeEventListener("keydown",xe,!0)},focus(){s.focus()},sendInput(i){_(i)}}})()}var W=window.__TMUX_WEB_SHELL__,u=null,h=null,De="sessions",B="",Q="",ee="",$e="",f=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),j=document.getElementById("file-editor"),xt=document.getElementById("fe-path"),E=document.getElementById("fe-status"),N=document.getElementById("fe-content"),S=document.getElementById("fe-save"),Tt=document.getElementById("fe-delete"),Mt=document.getElementById("fe-back"),Ae=document.getElementById("fe-new-name"),Bt=document.getElementById("fe-new-btn"),he=document.getElementById("settings-backdrop"),Re=document.getElementById("settings-popover"),ze=document.querySelector(".sidebar");function It(){return window.matchMedia("(max-width: 640px)").matches}function ve(){It()&&ze.classList.add("collapsed")}function Pe(){ze.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Pe(),We("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Pe(),We("files")});function We(t){De=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?Y():Pt()}async function Y(){f.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 n of e){let s=document.createElement("div");s.className="session-item"+(n.name===h?" active":""),s.setAttribute("data-session",n.name),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.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 Dt(i){let a=St(i.clipboardData);if(a){i.preventDefault(),i.stopPropagation();let T=Date.now();if(T-de<50)return;de=T,await Se(a);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let E=Date.now();E-de<50||(de=E,s.pasteText(Ve(d)))}let X=0;function $t(i){i.preventDefault(),X++,Fe(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function Rt(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function At(i){i.preventDefault(),X--,X<=0&&(X=0,e.classList.remove("terminal-drag-over"))}async function Pt(i){i.preventDefault(),X=0,e.classList.remove("terminal-drag-over");let a=Fe(i.dataTransfer);a&&await Se(a)}function Ye(i){!Tt||i.key!=="Escape"||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),ce("\x1B"))}e.addEventListener("paste",Dt),e.addEventListener("dragenter",$t),e.addEventListener("dragover",Rt),e.addEventListener("dragleave",At),e.addEventListener("drop",Pt),document.addEventListener("keydown",Ye,!0);function zt(i){if(i.touches.length!==1){r=null;return}let a=i.touches[0];a&&(r={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,scrolling:!1},i.stopPropagation())}function Nt(i){if(!r||i.touches.length!==1)return;let a=i.touches[0];if(!a)return;let d=a.clientY-r.startY,E=a.clientX-r.startX;if(!r.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(E))return;r.scrolling=!0,f=Date.now()+500}i.preventDefault(),i.stopPropagation(),Ut(-(a.clientY-r.lastY),a.clientX,a.clientY),r.lastX=a.clientX,r.lastY=a.clientY}function Wt(i){if(!r)return;let a=r.scrolling;r=null,i.stopPropagation(),a?(f=Date.now()+500,i.preventDefault()):s.focus()}function qt(i){r=null,i.stopPropagation()}function jt(i){i.pointerType==="touch"&&Date.now()<f&&(i.preventDefault(),i.stopPropagation())}function Ut(i,a,d){(e.querySelector(".xterm-screen")??e).dispatchEvent(new WheelEvent("wheel",{deltaY:i,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:a,clientY:d,bubbles:!0,cancelable:!0}))}e.addEventListener("touchstart",zt,{passive:!0,capture:!0}),e.addEventListener("touchmove",Nt,{passive:!1,capture:!0}),e.addEventListener("touchend",Wt,{passive:!1,capture:!0}),e.addEventListener("touchcancel",qt,{passive:!0,capture:!0}),e.addEventListener("pointerup",jt,!0);function _e(){L()}window.addEventListener("resize",_e),window.visualViewport?.addEventListener("resize",L),window.visualViewport?.addEventListener("scroll",L);let Oe=new ResizeObserver(()=>L());return Oe.observe(e),document.fonts?.ready.then(()=>{L(),setTimeout(L,50),setTimeout(L,200)}),e.addEventListener("focusin",Ct,!0),requestAnimationFrame(()=>{K()&&Ce(),je(),L()}),{destroy(){if(Be=!0,l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",_e),Oe.disconnect(),document.removeEventListener("keydown",Ye,!0)},focus(){s.focus()},sendInput(i){ce(i)}}})()}var J=window.__TMUX_WEB_SHELL__,u=null,v=null,De="sessions",D="",fe="",me="",Qe="",m=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),w=document.getElementById("terminal-container"),$=document.getElementById("file-editor"),Kt=document.getElementById("fe-path"),k=document.getElementById("fe-status"),Z=document.getElementById("fe-content"),P=document.getElementById("fe-save"),Xt=document.getElementById("fe-delete"),Jt=document.getElementById("fe-back"),et=document.getElementById("fe-new-name"),Zt=document.getElementById("fe-new-btn"),$e=document.getElementById("settings-backdrop"),tt=document.getElementById("settings-popover"),nt=document.querySelector(".sidebar");function Gt(){return window.matchMedia("(max-width: 640px)").matches}function Re(){Gt()&&nt.classList.add("collapsed")}function he(){nt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{he(),Ae("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{he(),Ae("files")}),document.getElementById("mode-browser").addEventListener("click",()=>{he(),Ae("browser")});function Ae(e){De=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":e==="files"?"mode-files":"mode-browser");t&&t.classList.add("active"),e==="sessions"?(se(),G()):e==="files"?(se(),rn()):(Ln(),I())}async function G(){m.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let t=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let n of t){let s=document.createElement("div");s.className="session-item"+(n.name===v?" active":""),s.setAttribute("data-session",n.name),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.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(n.name)}</span>
6
6
  <button class="session-edit-btn" data-session="${p(n.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
- `,s.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||te(n.name)}),f.appendChild(s);let o=s.querySelector(".session-edit-btn");o&&o.addEventListener("click",l=>{l.stopPropagation(),Ft(n.name,o)})}}catch{f.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(!(h===t&&u)){j.style.display="none",C.style.display="none",L.style.display="",L.classList.add("terminal-pending"),ve(),u&&(ce(),u.destroy(),u=null),h=t;for(let e of f.querySelectorAll(".session-item"))e.classList.toggle("active",e.getAttribute("data-session")===t);try{u=await kt(L,t,{terminal:W.terminal,scrollback:W.scrollback,theme:W.theme,renderer:W.renderer}),Xe()}catch(e){console.error("[shell] terminal init failed:",e);let n=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+n,L.classList.remove("terminal-pending")}}}var ne=[],m=null,je=!1;function Ct(t){if(!m||!m.files.length)return null;let e=m.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let s of m.files)if(s.path===n)return s.status;return null}async function St(t){je=!0;try{let e=await fetch("/api/git/status?path="+encodeURIComponent(t));e.ok?m=await e.json():m=null}catch{m=null}finally{je=!1}}function Ht(){if(!m||!m.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=m,s=`<div class="sidebar-git"><div class="git-branch-badge">
7
+ `,s.addEventListener("click",o=>{o.target.closest(".session-edit-btn")||Q(n.name)}),m.appendChild(s);let l=s.querySelector(".session-edit-btn");l&&l.addEventListener("click",o=>{o.stopPropagation(),kn(n.name,l)})}}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function p(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function Q(e){if(!(v===e&&u)){se(),$.style.display="none",C.style.display="none",w.style.display="",w.classList.add("terminal-pending"),Re(),u&&(U(),u.destroy(),u=null),v=e;for(let t of m.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e);try{u=await Ot(w,e,{terminal:J.terminal,scrollback:J.scrollback,theme:J.theme,renderer:J.renderer}),Ne()}catch(t){console.error("[shell] terminal init failed:",t);let n=t&&typeof t=="object"&&"message"in t?t.message:String(t);w.textContent="Failed to open terminal for "+e+": "+n,w.classList.remove("terminal-pending")}}}var ve=[],y=null,st=!1;function Qt(e){if(!y||!y.files.length)return null;let t=y.repoRoot;if(!t||!e.startsWith(t))return null;let n=e.slice(t.length+1);for(let s of y.files)if(s.path===n)return s.status;return null}async function en(e){st=!0;try{let t=await fetch("/api/git/status?path="+encodeURIComponent(e));t.ok?y=await t.json():y=null}catch{y=null}finally{st=!1}}function tn(){if(!y||!y.branch)return"";let{branch:e,linesAdded:t,linesRemoved:n}=y,s=`<div class="sidebar-git"><div class="git-branch-badge">
8
8
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
9
- ${p(t)}</div>`;return(e>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),s+"</div>"}f.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&$&&m&&(t.stopPropagation(),Oe())});var F=document.getElementById("header-git"),Ne=document.getElementById("git-diff-view"),Ye=document.getElementById("gd-path"),se=document.getElementById("gd-status"),ie=document.getElementById("gd-content"),Dt=document.getElementById("gd-back"),$=null;function V(t){if(!t||!t.branch){F.style.display="none",$=null;return}$=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:s}=t,o=`<span class="branch">${p(e)}</span>`;(n>0||s>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),F.innerHTML=o,F.style.display="block"}F.addEventListener("click",t=>{$&&m&&(t.stopPropagation(),Oe())});var g=document.getElementById("git-popover"),ae=document.getElementById("git-popover-backdrop");function ye(){let t=F.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function Fe(){let t=$,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(le(),t){De="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),I(t)}})}var Ve="";function Oe(){if(!m)return;let t=m.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
10
- <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let s=t[n],o=s.additions||0,l=s.deletions||0,d="";switch(s.status){case"M":d='<span class="git-file-status M">M</span>';break;case"A":d='<span class="git-file-status A">A</span>';break;case"D":d='<span class="git-file-status D">D</span>';break;case"?":d='<span class="git-file-status ?">?</span>';break;case"R":d='<span class="git-file-status R">R</span>';break}e+=`<div class="git-popover-item" data-idx="${n}">
11
- ${d}<span class="file-path">${p(s.path)}</span>
12
- <span class="file-add">${o>0?"+"+o:""}</span>
13
- <span class="file-del">${l>0?"-"+l:""}</span>
14
- </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Ve=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let s=parseInt(n.dataset.idx||"0",10),o=t[s];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>Ue(o.path)))})}ye(),g.classList.add("open"),ae.classList.add("open"),Fe()}function $t(){return window.innerWidth>=1024}function qe(t,e){let n=(t+(e&&t?`
15
- `:"")+e).split(`
16
- `),s="",o=0,l=0;for(let d of n){let r=d;if(r.startsWith("@@")){let v=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);v&&(o=parseInt(v[1],10)-1,l=parseInt(v[2],10)-1),s+=`<div class="diff-line hunk"><span class="ln-body">${p(r)}</span></div>`;continue}if(r.startsWith("diff --git")||r.startsWith("index ")||r.startsWith("--- ")||r.startsWith("+++ ")||r.startsWith("new file")||r.startsWith("deleted file")){r.startsWith("diff --git")&&(s+=`<div class="diff-line header"><span class="ln-body">${p(r)}</span></div>`);continue}if(r.startsWith("+"))l++,s+=`<div class="diff-line add"><span class="ln-no">${l}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("-"))o++,s+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("\\"))s+=`<div class="diff-line"><span class="ln-body">${p(r)}</span></div>`;else{if(!r&&!o&&!l)continue;o++,l++,s+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${l}</span><span class="ln-body">${p(r)}</span></div>`}}return{html:s,lineCount:n.length}}var oe=null;async function Ue(t){let e=$;if(e)if(le(),$t()){L.style.display="none",u&&(ce(),u.destroy(),u=null,oe=h,h=null),V(null),j.style.display="none",C.style.display="none",Ne.style.display="flex",Ye.textContent=t,se.textContent="Loading\u2026",ie.innerHTML="";try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let s=await n.json();Rt(t,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);se.textContent="Error",ie.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(s)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
9
+ ${p(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&q&&y&&(e.stopPropagation(),ot())});var ee=document.getElementById("header-git"),Pe=document.getElementById("git-diff-view"),it=document.getElementById("gd-path"),ye=document.getElementById("gd-status"),ge=document.getElementById("gd-content"),nn=document.getElementById("gd-back"),q=null;function j(e){if(!e||!e.branch){ee.style.display="none",q=null;return}q=e.repoRoot;let{branch:t,linesAdded:n,linesRemoved:s}=e,l=`<span class="branch">${p(t)}</span>`;(n>0||s>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),ee.innerHTML=l,ee.style.display="block"}ee.addEventListener("click",e=>{q&&y&&(e.stopPropagation(),ot())});var b=document.getElementById("git-popover"),we=document.getElementById("git-popover-backdrop");function ze(){let e=ee.getBoundingClientRect();b.style.top=e.bottom+4+"px",b.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function at(){let e=q,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Le(),e){De="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),R(e)}})}var lt="";function ot(){if(!y)return;let e=y.files||[];if(!e.length)b.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
10
+ <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){let s=e[n],l=s.additions||0,o=s.deletions||0,c="";switch(s.status){case"M":c='<span class="git-file-status M">M</span>';break;case"A":c='<span class="git-file-status A">A</span>';break;case"D":c='<span class="git-file-status D">D</span>';break;case"?":c='<span class="git-file-status ?">?</span>';break;case"R":c='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${n}">
11
+ ${c}<span class="file-path">${p(s.path)}</span>
12
+ <span class="file-add">${l>0?"+"+l:""}</span>
13
+ <span class="file-del">${o>0?"-"+o:""}</span>
14
+ </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',lt=t,b.innerHTML=t,b.querySelectorAll(".git-popover-item").forEach(n=>{let s=parseInt(n.dataset.idx||"0",10),l=e[s];!l||l.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>ct(l.path)))})}ze(),b.classList.add("open"),we.classList.add("open"),at()}function sn(){return window.innerWidth>=1024}function rt(e,t){let n=(e+(t&&e?`
15
+ `:"")+t).split(`
16
+ `),s="",l=0,o=0;for(let c of n){let r=c;if(r.startsWith("@@")){let f=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);f&&(l=parseInt(f[1],10)-1,o=parseInt(f[2],10)-1),s+=`<div class="diff-line hunk"><span class="ln-body">${p(r)}</span></div>`;continue}if(r.startsWith("diff --git")||r.startsWith("index ")||r.startsWith("--- ")||r.startsWith("+++ ")||r.startsWith("new file")||r.startsWith("deleted file")){r.startsWith("diff --git")&&(s+=`<div class="diff-line header"><span class="ln-body">${p(r)}</span></div>`);continue}if(r.startsWith("+"))o++,s+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("-"))l++,s+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("\\"))s+=`<div class="diff-line"><span class="ln-body">${p(r)}</span></div>`;else{if(!r&&!l&&!o)continue;l++,o++,s+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${p(r)}</span></div>`}}return{html:s,lineCount:n.length}}var be=null;async function ct(e){let t=q;if(t)if(Le(),se(),sn()){w.style.display="none",u&&(U(),u.destroy(),u=null,be=v,v=null),j(null),$.style.display="none",C.style.display="none",Pe.style.display="flex",it.textContent=e,ye.textContent="Loading\u2026",ge.innerHTML="";try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());let s=await n.json();ln(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);ye.textContent="Error",ge.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(s)}</div>`}}else{b.innerHTML=`<div class="git-diff-view"><div class="diff-header">
17
17
  <button class="diff-back">\u2190 Back</button>
18
- <span class="diff-filename">${p(t)}</span>
18
+ <span class="diff-filename">${p(e)}</span>
19
19
  <span class="diff-loading">Loading\u2026</span>
20
- </div></div>`,ye(),g.classList.add("open"),ae.classList.add("open");try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let s=await n.json();At(t,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
20
+ </div></div>`,ze(),b.classList.add("open"),we.classList.add("open");try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());let s=await n.json();an(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);b.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
21
  <button class="diff-back">\u2190 Back</button>
22
- <span class="diff-filename">${p(t)}</span>
23
- </div><div class="diff-empty">Error loading diff: ${p(s)}</div></div>`}}}function At(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
22
+ <span class="diff-filename">${p(e)}</span>
23
+ </div><div class="diff-empty">Error loading diff: ${p(s)}</div></div>`}}}function an(e,t,n){if(!t&&!n){b.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
- <span class="diff-filename">${p(t)}</span>
26
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:o}=qe(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
25
+ <span class="diff-filename">${p(e)}</span>
26
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:l}=rt(t,n);b.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
27
  <button class="diff-back">\u2190 Back</button>
28
- <span class="diff-filename">${p(t)}</span>
29
- <span class="diff-filesize">${o} lines</span>
30
- </div><div class="diff-content">${s}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=Ve,ye(),m&&g.querySelectorAll(".git-popover-item").forEach(l=>{let d=parseInt(l.dataset.idx||"0",10),r=m.files[d];!r||r.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>Ue(r.path)))}),Fe()})}function Rt(t,e,n){if(!e&&!n){se.textContent="No diff",ie.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:s,lineCount:o}=qe(e,n);Ye.textContent=t,se.textContent=o+" lines",ie.innerHTML=s}function zt(){Ne.style.display="none",oe?(L.style.display="",L.classList.add("terminal-pending"),te(oe),oe=null):h?(L.style.display="",L.classList.add("terminal-pending"),Xe()):C.style.display="flex"}Dt.addEventListener("click",zt);function le(){g.classList.remove("open"),ae.classList.remove("open")}ae.addEventListener("click",le),document.addEventListener("keydown",t=>{t.key==="Escape"&&(le(),de())});async function Ke(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let s=await n.json();m=s,V(s);return}}}catch{}m=null,V(null)}var re=null;function Xe(){ce(),h&&(Ke(h),re=setInterval(()=>{h&&Ke(h)},3e4))}function ce(){re&&(clearInterval(re),re=null)}function Pt(){if(ne=W.fsRoots||[],ne.length===0){if(h){Wt(h);return}f.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}f.innerHTML="";for(let t of ne){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",()=>I(t)),f.appendChild(e)}}async function Wt(t){f.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?I(e.path):f.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{f.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function I(t){B=t,f.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=St(t);try{let n=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();await e,f.innerHTML="",V(m),f.innerHTML+=Ht();let s=jt(t);if(s&&!ne.some(l=>t===l)){let l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>I(s)),f.appendChild(l)}if(n.dirs)for(let l of n.dirs){let d=l.split("/").pop()||l.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.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(d)}`,r.addEventListener("click",()=>I(l)),f.appendChild(r)}else f.innerHTML="";let o=n.files||[];for(let l of o){let d=l.split("/").pop()||l.split("\\").pop(),r=Ct(l),v="",y="";if(r)switch(r){case"M":v="git-mod",y='<span class="git-file-status M">M</span>';break;case"A":v="git-add",y='<span class="git-file-status A">A</span>';break;case"D":v="git-del",y='<span class="git-file-status D">D</span>';break;case"?":v="git-untracked",y='<span class="git-file-status ?">?</span>';break;case"R":v="git-mod",y='<span class="git-file-status R">R</span>';break}let T=document.createElement("div");T.className="file-tree-item file"+(v?" "+v:""),T.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${y}${p(d)}`,T.addEventListener("click",()=>Nt(l)),f.appendChild(T)}}catch{f.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function jt(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function Nt(t){Q=t,L.style.display="none",u&&(ce(),u.destroy(),u=null,h=null),V(null),ve(),C.style.display="none",j.style.display="flex",xt.textContent=t,E.textContent="Loading...",S.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let s=await e.json();E.textContent=s.error||"Failed to load",N.value="";return}let n=await e.json();N.value=n.content,ee=n.content,$e=n.content,E.textContent=n.size+" bytes",S.style.display=""}catch{E.textContent="Failed to load",N.value=""}}N.addEventListener("input",()=>{ee=N.value}),S.addEventListener("click",async()=>{S.textContent="Saving...",S.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Q,content:ee})});if(!t.ok){let e=await t.json();E.textContent=e.error||"Save failed";return}$e=ee,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{S.disabled=!1,S.textContent="Save"}}),Tt.addEventListener("click",async()=>{if(confirm("Delete "+Q+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Q})});if(!t.ok){let e=await t.json();E.textContent=e.error||"Delete failed";return}j.style.display="none",C.style.display="flex",B&&I(B)}catch{E.textContent="Delete failed"}}),Mt.addEventListener("click",()=>{j.style.display="none",C.style.display="flex",B&&I(B)}),Bt.addEventListener("click",async()=>{let t=Ae.value.trim();if(!t)return;let e=B+"/"+t;try{let n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let s=await n.json();E.textContent=s.error||"Create failed";return}Ae.value="",B&&I(B)}catch{E.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=Re.classList.toggle("open");he.classList.toggle("open",t)}),he.addEventListener("click",()=>{Re.classList.remove("open"),he.classList.remove("open")});var H="",O=document.createElement("div");O.className="session-popover-backdrop",document.body.appendChild(O);var M=document.createElement("div");M.id="session-popover",M.className="session-popover",M.style.display="none",M.innerHTML=`<div class="sp-field">
28
+ <span class="diff-filename">${p(e)}</span>
29
+ <span class="diff-filesize">${l} lines</span>
30
+ </div><div class="diff-content">${s}</div></div>`,b.querySelector(".diff-back")?.addEventListener("click",()=>{b.innerHTML=lt,ze(),y&&b.querySelectorAll(".git-popover-item").forEach(o=>{let c=parseInt(o.dataset.idx||"0",10),r=y.files[c];!r||r.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>ct(r.path)))}),at()})}function ln(e,t,n){if(!t&&!n){ye.textContent="No diff",ge.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:s,lineCount:l}=rt(t,n);it.textContent=e,ye.textContent=l+" lines",ge.innerHTML=s}function on(){Pe.style.display="none",be?(w.style.display="",w.classList.add("terminal-pending"),Q(be),be=null):v?(w.style.display="",w.classList.add("terminal-pending"),Ne()):C.style.display="flex"}nn.addEventListener("click",on);function Le(){b.classList.remove("open"),we.classList.remove("open")}we.addEventListener("click",Le),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Le(),Me())});async function dt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(n.ok){let s=await n.json();y=s,j(s);return}}}catch{}y=null,j(null)}var Ee=null;function Ne(){U(),v&&(dt(v),Ee=setInterval(()=>{v&&dt(v)},3e4))}function U(){Ee&&(clearInterval(Ee),Ee=null)}function rn(){if(ve=J.fsRoots||[],ve.length===0){if(v){cn(v);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let e of ve){let 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> ${p(e)}`,t.addEventListener("click",()=>R(e)),m.appendChild(t)}}async function cn(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();t.path?R(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function R(e){D=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=en(e);try{let n=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();await t,m.innerHTML="",j(y),m.innerHTML+=tn();let s=dn(e);if(s&&!ve.some(o=>e===o)){let o=document.createElement("div");o.className="file-tree-item",o.innerHTML="..",o.addEventListener("click",()=>R(s)),m.appendChild(o)}if(n.dirs)for(let o of n.dirs){let c=o.split("/").pop()||o.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.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)}`,r.addEventListener("click",()=>R(o)),m.appendChild(r)}else m.innerHTML="";let l=n.files||[];for(let o of l){let c=o.split("/").pop()||o.split("\\").pop(),r=Qt(o),f="",g="";if(r)switch(r){case"M":f="git-mod",g='<span class="git-file-status M">M</span>';break;case"A":f="git-add",g='<span class="git-file-status A">A</span>';break;case"D":f="git-del",g='<span class="git-file-status D">D</span>';break;case"?":f="git-untracked",g='<span class="git-file-status ?">?</span>';break;case"R":f="git-mod",g='<span class="git-file-status R">R</span>';break}let B=document.createElement("div");B.className="file-tree-item file"+(f?" "+f:""),B.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${g}${p(c)}`,B.addEventListener("click",()=>un(o)),m.appendChild(B)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function dn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function un(e){fe=e,se(),w.style.display="none",u&&(U(),u.destroy(),u=null,v=null),j(null),Re(),C.style.display="none",$.style.display="flex",Kt.textContent=e,k.textContent="Loading...",P.style.display="none";try{let t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){let s=await t.json();k.textContent=s.error||"Failed to load",Z.value="";return}let n=await t.json();Z.value=n.content,me=n.content,Qe=n.content,k.textContent=n.size+" bytes",P.style.display=""}catch{k.textContent="Failed to load",Z.value=""}}Z.addEventListener("input",()=>{me=Z.value}),P.addEventListener("click",async()=>{P.textContent="Saving...",P.disabled=!0;try{let e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:fe,content:me})});if(!e.ok){let t=await e.json();k.textContent=t.error||"Save failed";return}Qe=me,k.textContent="Saved"}catch{k.textContent="Save failed"}finally{P.disabled=!1,P.textContent="Save"}}),Xt.addEventListener("click",async()=>{if(confirm("Delete "+fe+"?"))try{let e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:fe})});if(!e.ok){let t=await e.json();k.textContent=t.error||"Delete failed";return}$.style.display="none",C.style.display="flex",D&&R(D)}catch{k.textContent="Delete failed"}}),Jt.addEventListener("click",()=>{$.style.display="none",C.style.display="flex",D&&R(D)}),Zt.addEventListener("click",async()=>{let e=et.value.trim();if(!e)return;let t=D+"/"+e;try{let n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){let s=await n.json();k.textContent=s.error||"Create failed";return}et.value="",D&&R(D)}catch{k.textContent="Create failed"}});var ut=document.getElementById("browser-view"),pt=document.getElementById("br-back"),ft=document.getElementById("br-fwd"),pn=document.getElementById("br-reload"),x=document.getElementById("br-address"),fn=document.getElementById("br-new"),z=document.getElementById("browser-content"),mt=document.getElementById("mobile-keys"),S=[],h=null,ht=0,ke=!1,F="none",xe=null,mn="https://html.duckduckgo.com/html/?q=";function V(){return h?S.find(e=>e.id===h)??null:null}function Te(e){return`<svg viewBox="0 0 24 24" fill="currentColor" width="${e}" height="${e}"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>`}function hn(e){let t=e.trim();return/^https?:\/\//i.test(t)||/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t)?t:!/\s/.test(t)&&/\./.test(t)?"https://"+t:/^(localhost|127\.0\.0\.1)(:\d+)?$/i.test(t)?"http://"+t:mn+encodeURIComponent(t)}function te(){ht++;let e="t"+ht;return S.push({id:e,title:"New Tab",url:null,history:[],historyIdx:-1,error:null,seq:0}),h=e,I(),Ie(),e}function I(){if(De!=="browser")return;let e='<div class="sidebar-section-label">Tabs</div>';for(let n of S){let s=n.id===h?" active":"";e+=`<div class="tab-item${s}" data-tab="${n.id}">
31
+ <span class="tab-fav">${Te(16)}</span>
32
+ <span class="tab-title">${p(n.title)}</span>
33
+ <button class="tab-close" data-close="${n.id}" title="Close tab" aria-label="Close tab">&times;</button>
34
+ </div>`}e+='<button class="tab-new" id="tab-new-btn"><svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> New Tab</button>',m.innerHTML=e,m.querySelectorAll(".tab-item").forEach(n=>{n.addEventListener("click",s=>{s.target.closest(".tab-close")||vt(n.dataset.tab)})}),m.querySelectorAll(".tab-close").forEach(n=>{n.addEventListener("click",s=>{s.stopPropagation(),vn(n.dataset.close)})});let t=document.getElementById("tab-new-btn");t&&t.addEventListener("click",()=>{te(),x.focus()})}function vt(e){h=e,I();let t=V();t&&(x.value=t.url||"",Y(),t.error?ne(t.error):t.url?A(t.id,t.url,!1):Ie())}function vn(e){let t=S.findIndex(n=>n.id===e);if(t!==-1){if(S.splice(t,1),S.length===0){h=null,I(),Ie();return}h===e?(h=S[Math.max(0,t-1)].id,vt(h)):I()}}function Y(){let e=V();pt.disabled=!e||e.historyIdx<=0,ft.disabled=!e||e.historyIdx>=e.history.length-1}function Ie(){z.innerHTML=`<div class="browser-state">
35
+ <div class="big">${Te(44)}</div>
36
+ <div>Type a URL or search terms in the address bar above.</div>
37
+ <div class="hint">Ctrl/Cmd-click any link to open it in a new tab.</div>
38
+ </div>`,Y()}function ne(e){z.innerHTML=`<div class="browser-state">
39
+ <div class="big" style="color:#ef4444">${Te(44)}</div>
40
+ <div>Could not load page</div>
41
+ <div class="hint" style="color:#ef4444">${p(e)}</div>
42
+ </div>`}function yt(){z.innerHTML=`<div class="browser-state">
43
+ <div class="big">${Te(44)}</div>
44
+ <div>Loading\u2026</div>
45
+ </div>`}async function A(e,t,n){let s=S.find(c=>c.id===e);if(!s)return;let l=hn(t);if(!l)return;let o=++s.seq;n&&(s.history=s.history.slice(0,s.historyIdx+1),s.history.push(l),s.historyIdx=s.history.length-1),s.url=l,s.error=null,h===e&&(x.value=l,Y(),yt());try{let c=await fetch("/api/browse?url="+encodeURIComponent(l)+"&tab="+encodeURIComponent(e)),r=await c.json();if(s.seq!==o)return;if(!c.ok||r.error){let g=r.error||"HTTP "+c.status;s.error=g,h===e&&ne(g),I();return}let f=r.finalUrl||l;s.url=f,s.title=r.title||s.title,s.history[s.historyIdx]=f,h===e&&(x.value=f,Y(),gt(r)),I()}catch(c){if(s.seq!==o)return;s.error="Network error: "+(c instanceof Error?c.message:String(c)),h===e&&ne(s.error),I()}}function yn(){let e=V();!e||e.historyIdx<=0||(e.historyIdx--,A(e.id,e.history[e.historyIdx],!1))}function gn(){let e=V();!e||e.historyIdx>=e.history.length-1||(e.historyIdx++,A(e.id,e.history[e.historyIdx],!1))}function gt(e){let t="";e.truncated&&(t+='<div class="browser-bar"><span class="err">Response truncated \u2014 page may be incomplete.</span></div>'),t+=e.html||"",z.innerHTML=t,wn(),z.scrollTop=0}function wn(){z.querySelectorAll("a[href]").forEach(e=>{e.addEventListener("click",t=>{let n=e.getAttribute("href")||"";if(n.startsWith("/api/browse?")){t.preventDefault();let s=t,l=null;try{l=new URL(n,location.origin).searchParams.get("url")}catch{return}if(!l)return;if(s.ctrlKey||s.metaKey||s.shiftKey){let o=te();A(o,l,!0)}else A(h,l,!0)}else/^(about|blob|file):/i.test(n)&&t.preventDefault()})}),z.querySelectorAll("form").forEach(e=>{e.addEventListener("submit",t=>{t.preventDefault();let n=e.getAttribute("action")||"";if(!n.startsWith("/api/browse/submit?"))return;let s=null;try{s=new URL(n,location.origin).searchParams.get("tab")||h}catch{s=h}if(!s)return;let l=new FormData(e),o=new URLSearchParams;l.forEach((c,r)=>o.append(r,String(c))),bn(s,o)})})}async function bn(e,t){let n=S.find(o=>o.id===e);if(!n)return;let s=++n.seq,l=t.get("__browse_action")||"";t.delete("__browse_action"),t.delete("__browse_method"),h===e&&yt();try{let o=await fetch("/api/browse/submit?tab="+encodeURIComponent(e),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()}),c=await o.json();if(n.seq!==s)return;if(!o.ok||c.error){let f=c.error||"HTTP "+o.status;n.error=f,h===e&&ne(f);return}let r=c.finalUrl||l||n.url||"";n.url=r,n.title=c.title||n.title,n.history=n.history.slice(0,n.historyIdx+1),n.history.push(r),n.historyIdx=n.history.length-1,n.error=null,h===e&&(x.value=r,Y(),gt(c)),I()}catch(o){if(n.seq!==s)return;n.error="Network error: "+(o instanceof Error?o.message:String(o)),h===e&&ne(n.error)}}function Ln(){if(!ke){if(ke=!0,$.style.display!=="none"?(F="files",u&&(U(),u.destroy(),u=null)):u||w.style.display!=="none"?(F="terminal",xe=v,u&&(U(),u.destroy(),u=null),v=null):F="none",w.style.display="none",$.style.display="none",Pe.style.display="none",C.style.display="none",j(null),mt.style.display="none",ut.style.display="flex",!h)te();else{I();let e=V();e&&(x.value=e.url||"",Y(),e.url?A(e.id,e.url,!1):Ie())}he()}}function se(){if(ke){if(ke=!1,ut.style.display="none",mt.style.display="",F==="files")$.style.display="flex";else if(F==="terminal"&&xe){let e=xe;xe=null,Q(e)}else v?(w.style.display="",w.classList.add("terminal-pending"),Ne()):C.style.display="flex";F="none"}}pt.addEventListener("click",yn),ft.addEventListener("click",gn),pn.addEventListener("click",()=>{let e=V();e&&e.url&&A(e.id,e.url,!1)}),fn.addEventListener("click",()=>{te(),x.focus(),x.select()}),x.addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();let t=h||te();A(t,x.value,!0),x.blur()}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=tt.classList.toggle("open");$e.classList.toggle("open",e)}),$e.addEventListener("click",()=>{tt.classList.remove("open"),$e.classList.remove("open")});var N="",ie=document.createElement("div");ie.className="session-popover-backdrop",document.body.appendChild(ie);var H=document.createElement("div");H.id="session-popover",H.className="session-popover",H.style.display="none",H.innerHTML=`<div class="sp-field">
31
46
  <label>Rename session</label>
32
47
  <input type="text" id="sp-name" />
33
48
  </div>
@@ -36,5 +51,5 @@ var wt="https://esm.sh/ghostty-web@0.4.0",bt=/\x1b\](?:10|11|110|111|104)(?:;[^\
36
51
  <button class="btn" id="sp-cancel">Cancel</button>
37
52
  </div>
38
53
  <hr class="sp-divider" />
39
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(M);function Yt(t){let e=t.getBoundingClientRect();M.style.top=e.bottom+4+"px",M.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function Ft(t,e){H=t;let n=document.getElementById("sp-name");n.value=t,O.classList.add("open"),M.style.display="block",Yt(e),setTimeout(()=>n.focus(),50)}function q(){O.classList.remove("open"),M.style.display="none"}O.addEventListener("click",q),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===H){q();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:H,newName:t})})).ok)return;q(),Y(),h===H&&te(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+H+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:H})})).ok)return;q(),h===H&&(u&&(u.destroy(),u=null),h=null,L.style.display="none",C.style.display="flex"),Y()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",q);var Vt={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||!u)return;let n=Vt[e.dataset.key];n&&(u.sendInput(n),u.focus())});var x=document.getElementById("mk-input"),_e=document.getElementById("mk-send");function Je(){if(!x||!u)return;let t=x.value;t&&(u.sendInput(t+"\r"),x.value="",x.rows=1,u.focus())}if(x&&(x.addEventListener("input",()=>{x.rows=1;let t=x.value.split(`
40
- `).length;x.rows=Math.min(t,4)}),x.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),Je())})),_e&&_e.addEventListener("click",Je),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})}var ge=document.getElementById("process-panel"),U=document.getElementById("proc-list"),Ot=document.getElementById("proc-close"),we=document.getElementById("proc-backdrop"),qt=document.getElementById("header-ram");function Ut(){U.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){U.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let s=n.rss,o=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}U.innerHTML=e;for(let n of U.querySelectorAll(".proc-row")){let s=n.querySelector(".proc-kill");s&&s.addEventListener("click",o=>{let l=o.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(d=>d.json()).then(d=>{d.ok?l.style.opacity="0.3":alert("Failed: "+(d.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{U.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Kt(){ge.classList.add("open"),we.classList.add("open"),Ut()}function de(){ge.classList.remove("open"),we.classList.remove("open")}qt.addEventListener("click",t=>{t.stopPropagation(),ge.classList.contains("open")?de():Kt()}),Ot.addEventListener("click",de),we.addEventListener("click",de),window.__openSession=te,window.__refreshSidebar=Y,Y(),ve();
54
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(H);function En(e){let t=e.getBoundingClientRect();H.style.top=t.bottom+4+"px",H.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function kn(e,t){N=e;let n=document.getElementById("sp-name");n.value=e,ie.classList.add("open"),H.style.display="block",En(t),setTimeout(()=>n.focus(),50)}function ae(){ie.classList.remove("open"),H.style.display="none"}ie.addEventListener("click",ae),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===N){ae();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:N,newName:e})})).ok)return;ae(),G(),v===N&&Q(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+N+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:N})})).ok)return;ae(),v===N&&(u&&(u.destroy(),u=null),v=null,w.style.display="none",C.style.display="flex"),G()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ae);var xn={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=>{let t=e.target.closest("button[data-key]");if(!t||!u)return;let n=xn[t.dataset.key];n&&(u.sendInput(n),u.focus())});var M=document.getElementById("mk-input"),wt=document.getElementById("mk-send");function bt(){if(!M||!u)return;let e=M.value;e&&(u.sendInput(e+"\r"),M.value="",M.rows=1,u.focus())}if(M&&(M.addEventListener("input",()=>{M.rows=1;let e=M.value.split(`
55
+ `).length;M.rows=Math.min(e,4)}),M.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),bt())})),wt&&wt.addEventListener("click",bt),window.visualViewport){let 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})}var We=document.getElementById("process-panel"),le=document.getElementById("proc-list"),Tn=document.getElementById("proc-close"),qe=document.getElementById("proc-backdrop"),In=document.getElementById("header-ram");function Mn(){le.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){le.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let n of e){let s=n.rss,l=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}le.innerHTML=t;for(let n of le.querySelectorAll(".proc-row")){let s=n.querySelector(".proc-kill");s&&s.addEventListener("click",l=>{let o=l.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?o.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{le.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Bn(){We.classList.add("open"),qe.classList.add("open"),Mn()}function Me(){We.classList.remove("open"),qe.classList.remove("open")}In.addEventListener("click",e=>{e.stopPropagation(),We.classList.contains("open")?Me():Bn()}),Tn.addEventListener("click",Me),qe.addEventListener("click",Me),window.__openSession=Q,window.__refreshSidebar=G,G(),Re();
@@ -1,30 +1,45 @@
1
- import{initTerminal as be}from"./terminal-core.js";const I=window.__TMUX_WEB_SHELL__;let r=null,u=null,ee="sessions",L="",P="",j="",te="";const p=document.getElementById("sidebar-content"),E=document.getElementById("main-placeholder"),v=document.getElementById("terminal-container"),M=document.getElementById("file-editor"),Le=document.getElementById("fe-path"),g=document.getElementById("fe-status"),T=document.getElementById("fe-content"),k=document.getElementById("fe-save"),we=document.getElementById("fe-delete"),Ee=document.getElementById("fe-back"),ne=document.getElementById("fe-new-name"),ke=document.getElementById("fe-new-btn"),K=document.getElementById("settings-backdrop"),se=document.getElementById("settings-popover"),ie=document.querySelector(".sidebar");function Be(){return window.matchMedia("(max-width: 640px)").matches}function X(){Be()&&ie.classList.add("collapsed")}function oe(){ie.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{oe(),ae("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{oe(),ae("files")});function ae(e){ee=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"?S():Re()}async function S(){p.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const s=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(const i of s){const a=document.createElement("div");a.className="session-item"+(i.name===u?" active":""),a.setAttribute("data-session",i.name),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.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
- <span>${c(i.name)}</span>
3
- <button class="session-edit-btn" data-session="${c(i.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
- `,a.addEventListener("click",d=>{d.target.closest(".session-edit-btn")||A(i.name)}),p.appendChild(a);const o=a.querySelector(".session-edit-btn");o&&o.addEventListener("click",d=>{d.stopPropagation(),je(i.name,o)})}}catch{p.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 c(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function A(e){if(!(u===e&&r)){M.style.display="none",E.style.display="none",v.style.display="",v.classList.add("terminal-pending"),X(),r&&(_(),r.destroy(),r=null),u=e;for(const t of p.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e);try{r=await be(v,e,{terminal:I.terminal,scrollback:I.scrollback,theme:I.theme,renderer:I.renderer}),ye()}catch(t){console.error("[shell] terminal init failed:",t);const n=t&&typeof t=="object"&&"message"in t?t.message:String(t);v.textContent="Failed to open terminal for "+e+": "+n,v.classList.remove("terminal-pending")}}}let W=[],f=null,le=!1;function xe(e){if(!f||!f.files.length)return null;const t=f.repoRoot;if(!t||!e.startsWith(t))return null;const n=e.slice(t.length+1);for(const s of f.files)if(s.path===n)return s.status;return null}async function Ce(e){le=!0;try{const t=await fetch("/api/git/status?path="+encodeURIComponent(e));t.ok?f=await t.json():f=null}catch{f=null}finally{le=!1}}function Ie(){if(!f||!f.branch)return"";const{branch:e,linesAdded:t,linesRemoved:n}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
1
+ import{initTerminal as _e}from"./terminal-core.js";const q=window.__TMUX_WEB_SHELL__;let c=null,u=null,pe="sessions",B="",X="",Y="",Le="";const f=document.getElementById("sidebar-content"),E=document.getElementById("main-placeholder"),v=document.getElementById("terminal-container"),I=document.getElementById("file-editor"),We=document.getElementById("fe-path"),g=document.getElementById("fe-status"),U=document.getElementById("fe-content"),M=document.getElementById("fe-save"),Ge=document.getElementById("fe-delete"),Oe=document.getElementById("fe-back"),Ee=document.getElementById("fe-new-name"),Ve=document.getElementById("fe-new-btn"),ue=document.getElementById("settings-backdrop"),ke=document.getElementById("settings-popover"),xe=document.querySelector(".sidebar");function Ke(){return window.matchMedia("(max-width: 640px)").matches}function me(){Ke()&&xe.classList.add("collapsed")}function Q(){xe.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Q(),ve("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Q(),ve("files")}),document.getElementById("mode-browser").addEventListener("click",()=>{Q(),ve("browser")});function ve(e){pe=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));const t=document.getElementById(e==="sessions"?"mode-sessions":e==="files"?"mode-files":"mode-browser");t&&t.classList.add("active"),e==="sessions"?(O(),j()):e==="files"?(O(),st()):(yt(),w())}async function j(){f.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const s=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(const i of s){const a=document.createElement("div");a.className="session-item"+(i.name===u?" active":""),a.setAttribute("data-session",i.name),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.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
+ <span>${d(i.name)}</span>
3
+ <button class="session-edit-btn" data-session="${d(i.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
+ `,a.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||z(i.name)}),f.appendChild(a);const o=a.querySelector(".session-edit-btn");o&&o.addEventListener("click",l=>{l.stopPropagation(),gt(i.name,o)})}}catch{f.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 d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function z(e){if(!(u===e&&c)){O(),I.style.display="none",E.style.display="none",v.style.display="",v.classList.add("terminal-pending"),me(),c&&(N(),c.destroy(),c=null),u=e;for(const t of f.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e);try{c=await _e(v,e,{terminal:q.terminal,scrollback:q.scrollback,theme:q.theme,renderer:q.renderer}),ge()}catch(t){console.error("[shell] terminal init failed:",t);const n=t&&typeof t=="object"&&"message"in t?t.message:String(t);v.textContent="Failed to open terminal for "+e+": "+n,v.classList.remove("terminal-pending")}}}let ee=[],m=null,Be=!1;function Je(e){if(!m||!m.files.length)return null;const t=m.repoRoot;if(!t||!e.startsWith(t))return null;const n=e.slice(t.length+1);for(const s of m.files)if(s.path===n)return s.status;return null}async function Ze(e){Be=!0;try{const t=await fetch("/api/git/status?path="+encodeURIComponent(e));t.ok?m=await t.json():m=null}catch{m=null}finally{Be=!1}}function Xe(){if(!m||!m.branch)return"";const{branch:e,linesAdded:t,linesRemoved:n}=m;let s=`<div class="sidebar-git"><div class="git-branch-badge">
5
5
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
6
- ${c(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}p.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&C&&f&&(e.stopPropagation(),fe())});const H=document.getElementById("header-git"),ce=document.getElementById("git-diff-view"),de=document.getElementById("gd-path"),G=document.getElementById("gd-status"),z=document.getElementById("gd-content"),Me=document.getElementById("gd-back");let C=null;function $(e){if(!e||!e.branch){H.style.display="none",C=null;return}C=e.repoRoot;const{branch:t,linesAdded:n,linesRemoved:s}=e;let i=`<span class="branch">${c(t)}</span>`;(n>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),H.innerHTML=i,H.style.display="block"}H.addEventListener("click",e=>{C&&f&&(e.stopPropagation(),fe())});const m=document.getElementById("git-popover"),O=document.getElementById("git-popover-backdrop");function Y(){const e=H.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function re(){const e=C,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(U(),e){ee="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const n=document.getElementById("mode-files");n&&n.classList.add("active"),w(e)}})}let pe="";function fe(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
6
+ ${d(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}f.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&R&&m&&(e.stopPropagation(),Me())});const _=document.getElementById("header-git"),ye=document.getElementById("git-diff-view"),Ie=document.getElementById("gd-path"),te=document.getElementById("gd-status"),ne=document.getElementById("gd-content"),Ye=document.getElementById("gd-back");let R=null;function D(e){if(!e||!e.branch){_.style.display="none",R=null;return}R=e.repoRoot;const{branch:t,linesAdded:n,linesRemoved:s}=e;let i=`<span class="branch">${d(t)}</span>`;(n>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),_.innerHTML=i,_.style.display="block"}_.addEventListener("click",e=>{R&&m&&(e.stopPropagation(),Me())});const y=document.getElementById("git-popover"),se=document.getElementById("git-popover-backdrop");function he(){const e=_.getBoundingClientRect();y.style.top=e.bottom+4+"px",y.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Ce(){const e=R,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(oe(),e){pe="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const n=document.getElementById("mode-files");n&&n.classList.add("active"),C(e)}})}let Te="";function Me(){if(!m)return;const e=m.files||[];if(!e.length)y.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
7
7
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){const s=e[n],i=s.additions||0,a=s.deletions||0;let o="";switch(s.status){case"M":o='<span class="git-file-status M">M</span>';break;case"A":o='<span class="git-file-status A">A</span>';break;case"D":o='<span class="git-file-status D">D</span>';break;case"?":o='<span class="git-file-status ?">?</span>';break;case"R":o='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${n}">
8
- ${o}<span class="file-path">${c(s.path)}</span>
8
+ ${o}<span class="file-path">${d(s.path)}</span>
9
9
  <span class="file-add">${i>0?"+"+i:""}</span>
10
10
  <span class="file-del">${a>0?"-"+a:""}</span>
11
- </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',pe=t,m.innerHTML=t,m.querySelectorAll(".git-popover-item").forEach(n=>{const s=parseInt(n.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>me(i.path)))})}Y(),m.classList.add("open"),O.classList.add("open"),re()}function Te(){return window.innerWidth>=1024}function ue(e,t){const s=(e+(t&&e?`
11
+ </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Te=t,y.innerHTML=t,y.querySelectorAll(".git-popover-item").forEach(n=>{const s=parseInt(n.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>He(i.path)))})}he(),y.classList.add("open"),se.classList.add("open"),Ce()}function Qe(){return window.innerWidth>=1024}function Se(e,t){const s=(e+(t&&e?`
12
12
  `:"")+t).split(`
13
- `);let i="",a=0,o=0;for(const d of s){const l=d;if(l.startsWith("@@")){const y=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);y&&(a=parseInt(y[1],10)-1,o=parseInt(y[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${c(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${c(l)}</span></div>`);continue}if(l.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${c(l)}</span></div>`;else if(l.startsWith("-"))a++,i+=`<div class="diff-line del"><span class="ln-no">${a}</span><span class="ln-body">${c(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${c(l)}</span></div>`;else{if(!l&&!a&&!o)continue;a++,o++,i+=`<div class="diff-line"><span class="ln-no">${a}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${c(l)}</span></div>`}}return{html:i,lineCount:s.length}}let q=null;async function me(e){const t=C;if(t)if(U(),Te()){v.style.display="none",r&&(_(),r.destroy(),r=null,q=u,u=null),$(null),M.style.display="none",E.style.display="none",ce.style.display="flex",de.textContent=e,G.textContent="Loading\u2026",z.innerHTML="";try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();He(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);G.textContent="Error",z.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${c(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
13
+ `);let i="",a=0,o=0;for(const l of s){const r=l;if(r.startsWith("@@")){const h=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);h&&(a=parseInt(h[1],10)-1,o=parseInt(h[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(r)}</span></div>`;continue}if(r.startsWith("diff --git")||r.startsWith("index ")||r.startsWith("--- ")||r.startsWith("+++ ")||r.startsWith("new file")||r.startsWith("deleted file")){r.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(r)}</span></div>`);continue}if(r.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(r)}</span></div>`;else if(r.startsWith("-"))a++,i+=`<div class="diff-line del"><span class="ln-no">${a}</span><span class="ln-body">${d(r)}</span></div>`;else if(r.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(r)}</span></div>`;else{if(!r&&!a&&!o)continue;a++,o++,i+=`<div class="diff-line"><span class="ln-no">${a}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(r)}</span></div>`}}return{html:i,lineCount:s.length}}let ie=null;async function He(e){const t=R;if(t)if(oe(),O(),Qe()){v.style.display="none",c&&(N(),c.destroy(),c=null,ie=u,u=null),D(null),I.style.display="none",E.style.display="none",ye.style.display="flex",Ie.textContent=e,te.textContent="Loading\u2026",ne.innerHTML="";try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();tt(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);te.textContent="Error",ne.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
14
14
  <button class="diff-back">\u2190 Back</button>
15
- <span class="diff-filename">${c(e)}</span>
15
+ <span class="diff-filename">${d(e)}</span>
16
16
  <span class="diff-loading">Loading\u2026</span>
17
- </div></div>`,Y(),m.classList.add("open"),O.classList.add("open");try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();Se(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
17
+ </div></div>`,he(),y.classList.add("open"),se.classList.add("open");try{const n=await fetch("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e));if(!n.ok)throw new Error(await n.text());const s=await n.json();et(e,s.diff,s.stagedDiff)}catch(n){const s=n instanceof Error?n.message:String(n);y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
18
18
  <button class="diff-back">\u2190 Back</button>
19
- <span class="diff-filename">${c(e)}</span>
20
- </div><div class="diff-empty">Error loading diff: ${c(s)}</div></div>`}}}function Se(e,t,n){if(!t&&!n){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
+ <span class="diff-filename">${d(e)}</span>
20
+ </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function et(e,t,n){if(!t&&!n){y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
21
  <button class="diff-back">\u2190 Back</button>
22
- <span class="diff-filename">${c(e)}</span>
23
- </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=ue(t,n);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
22
+ <span class="diff-filename">${d(e)}</span>
23
+ </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=Se(t,n);y.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
- <span class="diff-filename">${c(e)}</span>
25
+ <span class="diff-filename">${d(e)}</span>
26
26
  <span class="diff-filesize">${i} lines</span>
27
- </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=pe,Y(),f&&m.querySelectorAll(".git-popover-item").forEach(a=>{const o=parseInt(a.dataset.idx||"0",10),d=f.files[o];!d||d.status==="D"||(a.style.cursor="pointer",a.addEventListener("click",()=>me(d.path)))}),re()})}function He(e,t,n){if(!t&&!n){G.textContent="No diff",z.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=ue(t,n);de.textContent=e,G.textContent=i+" lines",z.innerHTML=s}function $e(){ce.style.display="none",q?(v.style.display="",v.classList.add("terminal-pending"),A(q),q=null):u?(v.style.display="",v.classList.add("terminal-pending"),ye()):E.style.display="flex"}Me.addEventListener("click",$e);function U(){m.classList.remove("open"),O.classList.remove("open")}O.addEventListener("click",U),document.addEventListener("keydown",e=>{e.key==="Escape"&&(U(),J())});async function ve(e){try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(n.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(n.path));if(s.ok){const i=await s.json();f=i,$(i);return}}}catch{}f=null,$(null)}let V=null;function ye(){_(),u&&(ve(u),V=setInterval(()=>{u&&ve(u)},3e4))}function _(){V&&(clearInterval(V),V=null)}function Re(){if(W=I.fsRoots||[],W.length===0){if(u){De(u);return}p.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}p.innerHTML="";for(const e of W){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> ${c(e)}`,t.addEventListener("click",()=>w(e)),p.appendChild(t)}}async function De(e){p.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();n.path?w(n.path):p.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{p.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function w(e){L=e,p.innerHTML='<div class="file-tree-empty">Loading...</div>';const t=Ce(e);try{const s=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();await t,p.innerHTML="",$(f),p.innerHTML+=Ie();const i=Ne(e);if(i&&!W.some(d=>e===d)){const d=document.createElement("div");d.className="file-tree-item",d.innerHTML="..",d.addEventListener("click",()=>w(i)),p.appendChild(d)}if(s.dirs)for(const o of s.dirs){const d=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> ${c(d)}`,l.addEventListener("click",()=>w(o)),p.appendChild(l)}else p.innerHTML="";const a=s.files||[];for(const o of a){const d=o.split("/").pop()||o.split("\\").pop(),l=xe(o);let y="",x="";if(l)switch(l){case"M":y="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":y="git-add",x='<span class="git-file-status A">A</span>';break;case"D":y="git-del",x='<span class="git-file-status D">D</span>';break;case"?":y="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":y="git-mod",x='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(y?" "+y:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${c(d)}`,F.addEventListener("click",()=>Fe(o)),p.appendChild(F)}}catch{p.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Ne(e){const t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Fe(e){P=e,v.style.display="none",r&&(_(),r.destroy(),r=null,u=null),$(null),X(),E.style.display="none",M.style.display="flex",Le.textContent=e,g.textContent="Loading...",k.style.display="none";try{const t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){const s=await t.json();g.textContent=s.error||"Failed to load",T.value="";return}const n=await t.json();T.value=n.content,j=n.content,te=n.content,g.textContent=n.size+" bytes",k.style.display=""}catch{g.textContent="Failed to load",T.value=""}}T.addEventListener("input",()=>{j=T.value}),k.addEventListener("click",async()=>{k.textContent="Saving...",k.disabled=!0;try{const e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:P,content:j})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Save failed";return}te=j,g.textContent="Saved"}catch{g.textContent="Save failed"}finally{k.disabled=!1,k.textContent="Save"}}),we.addEventListener("click",async()=>{if(confirm("Delete "+P+"?"))try{const e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:P})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Delete failed";return}M.style.display="none",E.style.display="flex",L&&w(L)}catch{g.textContent="Delete failed"}}),Ee.addEventListener("click",()=>{M.style.display="none",E.style.display="flex",L&&w(L)}),ke.addEventListener("click",async()=>{const e=ne.value.trim();if(!e)return;const t=L+"/"+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 s=await n.json();g.textContent=s.error||"Create failed";return}ne.value="",L&&w(L)}catch{g.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=se.classList.toggle("open");K.classList.toggle("open",e)}),K.addEventListener("click",()=>{se.classList.remove("open"),K.classList.remove("open")});let B="";const R=document.createElement("div");R.className="session-popover-backdrop",document.body.appendChild(R);const b=document.createElement("div");b.id="session-popover",b.className="session-popover",b.style.display="none",b.innerHTML=`<div class="sp-field">
27
+ </div><div class="diff-content">${s}</div></div>`,y.querySelector(".diff-back")?.addEventListener("click",()=>{y.innerHTML=Te,he(),m&&y.querySelectorAll(".git-popover-item").forEach(a=>{const o=parseInt(a.dataset.idx||"0",10),l=m.files[o];!l||l.status==="D"||(a.style.cursor="pointer",a.addEventListener("click",()=>He(l.path)))}),Ce()})}function tt(e,t,n){if(!t&&!n){te.textContent="No diff",ne.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=Se(t,n);Ie.textContent=e,te.textContent=i+" lines",ne.innerHTML=s}function nt(){ye.style.display="none",ie?(v.style.display="",v.classList.add("terminal-pending"),z(ie),ie=null):u?(v.style.display="",v.classList.add("terminal-pending"),ge()):E.style.display="flex"}Ye.addEventListener("click",nt);function oe(){y.classList.remove("open"),se.classList.remove("open")}se.addEventListener("click",oe),document.addEventListener("keydown",e=>{e.key==="Escape"&&(oe(),fe())});async function $e(e){try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(n.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(n.path));if(s.ok){const i=await s.json();m=i,D(i);return}}}catch{}m=null,D(null)}let ae=null;function ge(){N(),u&&($e(u),ae=setInterval(()=>{u&&$e(u)},3e4))}function N(){ae&&(clearInterval(ae),ae=null)}function st(){if(ee=q.fsRoots||[],ee.length===0){if(u){it(u);return}f.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}f.innerHTML="";for(const e of ee){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> ${d(e)}`,t.addEventListener("click",()=>C(e)),f.appendChild(t)}}async function it(e){f.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const n=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();n.path?C(n.path):f.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{f.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function C(e){B=e,f.innerHTML='<div class="file-tree-empty">Loading...</div>';const t=Ze(e);try{const s=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();await t,f.innerHTML="",D(m),f.innerHTML+=Xe();const i=ot(e);if(i&&!ee.some(l=>e===l)){const l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>C(i)),f.appendChild(l)}if(s.dirs)for(const o of s.dirs){const l=o.split("/").pop()||o.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.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> ${d(l)}`,r.addEventListener("click",()=>C(o)),f.appendChild(r)}else f.innerHTML="";const a=s.files||[];for(const o of a){const l=o.split("/").pop()||o.split("\\").pop(),r=Je(o);let h="",$="";if(r)switch(r){case"M":h="git-mod",$='<span class="git-file-status M">M</span>';break;case"A":h="git-add",$='<span class="git-file-status A">A</span>';break;case"D":h="git-del",$='<span class="git-file-status D">D</span>';break;case"?":h="git-untracked",$='<span class="git-file-status ?">?</span>';break;case"R":h="git-mod",$='<span class="git-file-status R">R</span>';break}const Z=document.createElement("div");Z.className="file-tree-item file"+(h?" "+h:""),Z.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${$}${d(l)}`,Z.addEventListener("click",()=>at(o)),f.appendChild(Z)}}catch{f.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ot(e){const t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function at(e){X=e,O(),v.style.display="none",c&&(N(),c.destroy(),c=null,u=null),D(null),me(),E.style.display="none",I.style.display="flex",We.textContent=e,g.textContent="Loading...",M.style.display="none";try{const t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){const s=await t.json();g.textContent=s.error||"Failed to load",U.value="";return}const n=await t.json();U.value=n.content,Y=n.content,Le=n.content,g.textContent=n.size+" bytes",M.style.display=""}catch{g.textContent="Failed to load",U.value=""}}U.addEventListener("input",()=>{Y=U.value}),M.addEventListener("click",async()=>{M.textContent="Saving...",M.disabled=!0;try{const e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:X,content:Y})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Save failed";return}Le=Y,g.textContent="Saved"}catch{g.textContent="Save failed"}finally{M.disabled=!1,M.textContent="Save"}}),Ge.addEventListener("click",async()=>{if(confirm("Delete "+X+"?"))try{const e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:X})});if(!e.ok){const t=await e.json();g.textContent=t.error||"Delete failed";return}I.style.display="none",E.style.display="flex",B&&C(B)}catch{g.textContent="Delete failed"}}),Oe.addEventListener("click",()=>{I.style.display="none",E.style.display="flex",B&&C(B)}),Ve.addEventListener("click",async()=>{const e=Ee.value.trim();if(!e)return;const t=B+"/"+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 s=await n.json();g.textContent=s.error||"Create failed";return}Ee.value="",B&&C(B)}catch{g.textContent="Create failed"}});const Re=document.getElementById("browser-view"),De=document.getElementById("br-back"),Ne=document.getElementById("br-fwd"),rt=document.getElementById("br-reload"),b=document.getElementById("br-address"),lt=document.getElementById("br-new"),S=document.getElementById("browser-content"),Pe=document.getElementById("mobile-keys");let k=[],p=null,Ae=0,re=!1,P="none",le=null;const ct="https://html.duckduckgo.com/html/?q=";function A(){return p?k.find(e=>e.id===p)??null:null}function ce(e){return`<svg viewBox="0 0 24 24" fill="currentColor" width="${e}" height="${e}"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>`}function dt(e){let t=e.trim();return/^https?:\/\//i.test(t)||/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t)?t:!/\s/.test(t)&&/\./.test(t)?"https://"+t:/^(localhost|127\.0\.0\.1)(:\d+)?$/i.test(t)?"http://"+t:ct+encodeURIComponent(t)}function W(){Ae++;const e="t"+Ae;return k.push({id:e,title:"New Tab",url:null,history:[],historyIdx:-1,error:null,seq:0}),p=e,w(),de(),e}function w(){if(pe!=="browser")return;let e='<div class="sidebar-section-label">Tabs</div>';for(const n of k){const s=n.id===p?" active":"";e+=`<div class="tab-item${s}" data-tab="${n.id}">
28
+ <span class="tab-fav">${ce(16)}</span>
29
+ <span class="tab-title">${d(n.title)}</span>
30
+ <button class="tab-close" data-close="${n.id}" title="Close tab" aria-label="Close tab">&times;</button>
31
+ </div>`}e+='<button class="tab-new" id="tab-new-btn"><svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> New Tab</button>',f.innerHTML=e,f.querySelectorAll(".tab-item").forEach(n=>{n.addEventListener("click",s=>{s.target.closest(".tab-close")||Fe(n.dataset.tab)})}),f.querySelectorAll(".tab-close").forEach(n=>{n.addEventListener("click",s=>{s.stopPropagation(),ft(n.dataset.close)})});const t=document.getElementById("tab-new-btn");t&&t.addEventListener("click",()=>{W(),b.focus()})}function Fe(e){p=e,w();const t=A();t&&(b.value=t.url||"",F(),t.error?G(t.error):t.url?T(t.id,t.url,!1):de())}function ft(e){const t=k.findIndex(n=>n.id===e);if(t!==-1){if(k.splice(t,1),k.length===0){p=null,w(),de();return}p===e?(p=k[Math.max(0,t-1)].id,Fe(p)):w()}}function F(){const e=A();De.disabled=!e||e.historyIdx<=0,Ne.disabled=!e||e.historyIdx>=e.history.length-1}function de(){S.innerHTML=`<div class="browser-state">
32
+ <div class="big">${ce(44)}</div>
33
+ <div>Type a URL or search terms in the address bar above.</div>
34
+ <div class="hint">Ctrl/Cmd-click any link to open it in a new tab.</div>
35
+ </div>`,F()}function G(e){S.innerHTML=`<div class="browser-state">
36
+ <div class="big" style="color:#ef4444">${ce(44)}</div>
37
+ <div>Could not load page</div>
38
+ <div class="hint" style="color:#ef4444">${d(e)}</div>
39
+ </div>`}function qe(){S.innerHTML=`<div class="browser-state">
40
+ <div class="big">${ce(44)}</div>
41
+ <div>Loading\u2026</div>
42
+ </div>`}async function T(e,t,n){const s=k.find(o=>o.id===e);if(!s)return;const i=dt(t);if(!i)return;const a=++s.seq;n&&(s.history=s.history.slice(0,s.historyIdx+1),s.history.push(i),s.historyIdx=s.history.length-1),s.url=i,s.error=null,p===e&&(b.value=i,F(),qe());try{const o=await fetch("/api/browse?url="+encodeURIComponent(i)+"&tab="+encodeURIComponent(e)),l=await o.json();if(s.seq!==a)return;if(!o.ok||l.error){const h=l.error||"HTTP "+o.status;s.error=h,p===e&&G(h),w();return}const r=l.finalUrl||i;s.url=r,s.title=l.title||s.title,s.history[s.historyIdx]=r,p===e&&(b.value=r,F(),Ue(l)),w()}catch(o){if(s.seq!==a)return;s.error="Network error: "+(o instanceof Error?o.message:String(o)),p===e&&G(s.error),w()}}function pt(){const e=A();!e||e.historyIdx<=0||(e.historyIdx--,T(e.id,e.history[e.historyIdx],!1))}function ut(){const e=A();!e||e.historyIdx>=e.history.length-1||(e.historyIdx++,T(e.id,e.history[e.historyIdx],!1))}function Ue(e){let t="";e.truncated&&(t+='<div class="browser-bar"><span class="err">Response truncated \u2014 page may be incomplete.</span></div>'),t+=e.html||"",S.innerHTML=t,mt(),S.scrollTop=0}function mt(){S.querySelectorAll("a[href]").forEach(e=>{e.addEventListener("click",t=>{const n=e.getAttribute("href")||"";if(n.startsWith("/api/browse?")){t.preventDefault();const s=t;let i=null;try{i=new URL(n,location.origin).searchParams.get("url")}catch{return}if(!i)return;if(s.ctrlKey||s.metaKey||s.shiftKey){const a=W();T(a,i,!0)}else T(p,i,!0)}else/^(about|blob|file):/i.test(n)&&t.preventDefault()})}),S.querySelectorAll("form").forEach(e=>{e.addEventListener("submit",t=>{t.preventDefault();const n=e.getAttribute("action")||"";if(!n.startsWith("/api/browse/submit?"))return;let s=null;try{s=new URL(n,location.origin).searchParams.get("tab")||p}catch{s=p}if(!s)return;const i=new FormData(e),a=new URLSearchParams;i.forEach((o,l)=>a.append(l,String(o))),vt(s,a)})})}async function vt(e,t){const n=k.find(a=>a.id===e);if(!n)return;const s=++n.seq,i=t.get("__browse_action")||"";t.delete("__browse_action"),t.delete("__browse_method"),p===e&&qe();try{const a=await fetch("/api/browse/submit?tab="+encodeURIComponent(e),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()}),o=await a.json();if(n.seq!==s)return;if(!a.ok||o.error){const r=o.error||"HTTP "+a.status;n.error=r,p===e&&G(r);return}const l=o.finalUrl||i||n.url||"";n.url=l,n.title=o.title||n.title,n.history=n.history.slice(0,n.historyIdx+1),n.history.push(l),n.historyIdx=n.history.length-1,n.error=null,p===e&&(b.value=l,F(),Ue(o)),w()}catch(a){if(n.seq!==s)return;n.error="Network error: "+(a instanceof Error?a.message:String(a)),p===e&&G(n.error)}}function yt(){if(!re){if(re=!0,I.style.display!=="none"?(P="files",c&&(N(),c.destroy(),c=null)):c||v.style.display!=="none"?(P="terminal",le=u,c&&(N(),c.destroy(),c=null),u=null):P="none",v.style.display="none",I.style.display="none",ye.style.display="none",E.style.display="none",D(null),Pe.style.display="none",Re.style.display="flex",!p)W();else{w();const e=A();e&&(b.value=e.url||"",F(),e.url?T(e.id,e.url,!1):de())}Q()}}function O(){if(re){if(re=!1,Re.style.display="none",Pe.style.display="",P==="files")I.style.display="flex";else if(P==="terminal"&&le){const e=le;le=null,z(e)}else u?(v.style.display="",v.classList.add("terminal-pending"),ge()):E.style.display="flex";P="none"}}De.addEventListener("click",pt),Ne.addEventListener("click",ut),rt.addEventListener("click",()=>{const e=A();e&&e.url&&T(e.id,e.url,!1)}),lt.addEventListener("click",()=>{W(),b.focus(),b.select()}),b.addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();const t=p||W();T(t,b.value,!0),b.blur()}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=ke.classList.toggle("open");ue.classList.toggle("open",e)}),ue.addEventListener("click",()=>{ke.classList.remove("open"),ue.classList.remove("open")});let H="";const V=document.createElement("div");V.className="session-popover-backdrop",document.body.appendChild(V);const x=document.createElement("div");x.id="session-popover",x.className="session-popover",x.style.display="none",x.innerHTML=`<div class="sp-field">
28
43
  <label>Rename session</label>
29
44
  <input type="text" id="sp-name" />
30
45
  </div>
@@ -33,5 +48,5 @@ import{initTerminal as be}from"./terminal-core.js";const I=window.__TMUX_WEB_SHE
33
48
  <button class="btn" id="sp-cancel">Cancel</button>
34
49
  </div>
35
50
  <hr class="sp-divider" />
36
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(b);function Pe(e){const t=e.getBoundingClientRect();b.style.top=t.bottom+4+"px",b.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function je(e,t){B=e;const n=document.getElementById("sp-name");n.value=e,R.classList.add("open"),b.style.display="block",Pe(t),setTimeout(()=>n.focus(),50)}function D(){R.classList.remove("open"),b.style.display="none"}R.addEventListener("click",D),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===B){D();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:B,newName:e})})).ok)return;D(),S(),u===B&&A(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+B+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B})})).ok)return;D(),u===B&&(r&&(r.destroy(),r=null),u=null,v.style.display="none",E.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",D);const Ae={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||!r)return;const n=Ae[t.dataset.key];n&&(r.sendInput(n),r.focus())});const h=document.getElementById("mk-input"),ge=document.getElementById("mk-send");function he(){if(!h||!r)return;const e=h.value;e&&(r.sendInput(e+"\r"),h.value="",h.rows=1,r.focus())}if(h&&(h.addEventListener("input",()=>{h.rows=1;const e=h.value.split(`
37
- `).length;h.rows=Math.min(e,4)}),h.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),he())})),ge&&ge.addEventListener("click",he),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})}const Z=document.getElementById("process-panel"),N=document.getElementById("proc-list"),We=document.getElementById("proc-close"),Q=document.getElementById("proc-backdrop"),Ge=document.getElementById("header-ram");function ze(){N.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){N.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(const n of e){const s=n.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+c(n.command)+'">'+c(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}N.innerHTML=t;for(const n of N.querySelectorAll(".proc-row")){const s=n.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const a=i.target.closest(".proc-row");!a||!confirm("Kill PID "+a.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(a.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?a.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{N.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Oe(){Z.classList.add("open"),Q.classList.add("open"),ze()}function J(){Z.classList.remove("open"),Q.classList.remove("open")}Ge.addEventListener("click",e=>{e.stopPropagation(),Z.classList.contains("open")?J():Oe()}),We.addEventListener("click",J),Q.addEventListener("click",J),window.__openSession=A,window.__refreshSidebar=S,S(),X();
51
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(x);function ht(e){const t=e.getBoundingClientRect();x.style.top=t.bottom+4+"px",x.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function gt(e,t){H=e;const n=document.getElementById("sp-name");n.value=e,V.classList.add("open"),x.style.display="block",ht(t),setTimeout(()=>n.focus(),50)}function K(){V.classList.remove("open"),x.style.display="none"}V.addEventListener("click",K),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===H){K();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:H,newName:e})})).ok)return;K(),j(),u===H&&z(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+H+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:H})})).ok)return;K(),u===H&&(c&&(c.destroy(),c=null),u=null,v.style.display="none",E.style.display="flex"),j()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",K);const bt={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||!c)return;const n=bt[t.dataset.key];n&&(c.sendInput(n),c.focus())});const L=document.getElementById("mk-input"),je=document.getElementById("mk-send");function ze(){if(!L||!c)return;const e=L.value;e&&(c.sendInput(e+"\r"),L.value="",L.rows=1,c.focus())}if(L&&(L.addEventListener("input",()=>{L.rows=1;const e=L.value.split(`
52
+ `).length;L.rows=Math.min(e,4)}),L.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),ze())})),je&&je.addEventListener("click",ze),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})}const be=document.getElementById("process-panel"),J=document.getElementById("proc-list"),wt=document.getElementById("proc-close"),we=document.getElementById("proc-backdrop"),Lt=document.getElementById("header-ram");function Et(){J.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){J.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(const n of e){const s=n.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(n.command)+'">'+d(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}J.innerHTML=t;for(const n of J.querySelectorAll(".proc-row")){const s=n.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const a=i.target.closest(".proc-row");!a||!confirm("Kill PID "+a.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(a.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?a.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{J.innerHTML='<div class="proc-empty">Failed to load</div>'})}function kt(){be.classList.add("open"),we.classList.add("open"),Et()}function fe(){be.classList.remove("open"),we.classList.remove("open")}Lt.addEventListener("click",e=>{e.stopPropagation(),be.classList.contains("open")?fe():kt()}),wt.addEventListener("click",fe),we.addEventListener("click",fe),window.__openSession=z,window.__refreshSidebar=j,j(),me();
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{chmodSync as $e,existsSync as V,mkdirSync as Be,readdirSync as ce,readFileSync as ue,statSync as D,unlinkSync as Ve,writeFileSync as Ge}from"node:fs";import{readFile as ze}from"node:fs/promises";import{execSync as G,execFileSync as le}from"node:child_process";import{createRequire as Je}from"node:module";import j from"node:path";import{fileURLToPath as Ke}from"node:url";import{Hono as Qe}from"hono";import{serve as Xe}from"@hono/node-server";import{WebSocketServer as Ye,WebSocket as me}from"ws";import*as Ze from"node-pty";try{const e=j.dirname(Je(import.meta.url).resolve("node-pty/package.json")),t=j.join(e,"prebuilds");if(V(t))for(const r of ce(t)){const n=j.join(t,r,"spawn-helper");V(n)&&!(D(n).mode&73)&&$e(n,493)}}catch{}import{listSessions as _}from"./sessions.js";import{renderLoginPage as et,renderNotesIndex as tt,renderNotesPage as rt,renderSettings as nt,renderThemeSettings as ot,renderScheduleIndex as st,renderHistoryIndex as it,renderQuickCommandsPage as at,renderFilesIndex as dt,renderShell as ct}from"./frontend.js";import{loadSecurityConfig as ut,saveSecurityConfig as fe}from"./lib/security-config.js";import{hashPassword as pe,verifyPassword as he,validatePassword as ye,TokenStore as lt}from"./lib/auth.js";import{RateLimiter as mt}from"./lib/rateLimiter.js";import{atomicWriteFileSync as ft}from"./lib/atomicWrite.js";import{resolveFsPath as H,resolveFsRoots as ge,MAX_FILE_BYTES as we,walkRecursive as pt}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as ht}from"./lib/tmux-windows.js";import{audit as l}from"./lib/auditLog.js";import{db as S}from"./lib/db.js";import{getSessionAccessMap as I}from"./lib/session-access.js";import{listWindowHistory as yt,clearWindowHistory as gt}from"./lib/window-history.js";import{loadExtensions as wt,spawnExtensionBackend as jt,registerExtensionRoutes as kt}from"./lib/ext-loader.js";import{SchedulerService as vt,isValidScheduleInput as St,isValidRescheduleInput as xt}from"./lib/scheduler.js";import{getScheduleDelayError as je}from"./lib/schedule-delay.js";import{handleClientMessage as Tt}from"./lib/ws-message.js";import{loadDotEnv as bt}from"./lib/load-env.js";import{cmdAdd as At,cmdRemove as qt,cmdList as _t,cmdSetup as Ct,cmdTheme as It,printUsage as ke,printVersion as Et}from"./lib/cli.js";import{readSettings as ee,writeSettings as Rt}from"./lib/settings.js";import{readActiveTheme as Ot,setActiveThemeTemplate as ve}from"./lib/theme-store.js";import{isThemeTemplateId as Se,THEME_TEMPLATE_IDS as Mt}from"./lib/themes/index.js";import{installPlugin as Lt,uninstallPlugin as Pt}from"./lib/plugins.js";import{buildCommandbarSessions as O}from"./lib/commandbar.js";import{pinView as Nt,unpinView as Dt,listPinnedViews as Ht}from"./lib/pinned-views.js";import{listWindowLabels as Ft,setWindowLabel as Ut}from"./lib/window-labels.js";import{captureAndStoreWindows as xe,getStoredWindows as Te}from"./lib/session-windows.js";import{acquireControlClient as Wt,killAllControlClients as $t}from"./lib/tmux-control.js";import{buildSidebarSessions as Bt}from"./lib/sessions-sidebar.js";import{createQuickCommand as Vt,deleteQuickCommand as Gt,listQuickCommands as be,updateQuickCommand as zt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Jt,capturePaneTail as Kt,capturePaneHistoryChunk as Qt,isAlternateScreen as Xt,toCrlf as Ae}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Yt}from"./lib/terminal-config.js";import{ImageUploadError as Zt,saveUploadedImage as er}from"./lib/image-upload.js";import{getSystemStatus as tr,getTopProcesses as rr,killProcess as nr}from"./lib/system-monitor.js";import{listSessionWindows as or,selectSessionWindow as sr,newSessionWindow as ir,renameSessionWindow as ar,newTmuxSession as dr,renameSession as cr,killSession as ur,TmuxWindowsError as J}from"./lib/tmux-windows.js";bt();const K=Yt(),m=ut(),g=new lt,M=new mt;let F=!1;const Q="tmux-web-token",lr=365;function qe(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function X(e){if(m.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 mr(e){if(m.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function te(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 _e(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 gn(e){const t=te(e);return t?g.validateToken(t):null}function wn(e){const t=_e(e);return t?g.validateToken(t):null}function Ce(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=${lr*86400}${r?"; Secure":""}`)}function fr(e){e.header("Set-Cookie",`${Q}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function a(){return async(e,t)=>{const r=te(e);return!r||!g.validateToken(r)?(l("http_unauthorized",{ip:X(e)}),e.json({error:"unauthorized"},401)):t()}}function pr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function E(){return async(e,t)=>{const r=te(e);return!r||!g.validateToken(r)?pr(e):t()}}const U=new Map;function hr(e){let t=0;for(const r of U.values())r.ip===e&&t++;return t}function re(e,t,r){try{e.close(t,r)}catch{}}function jn(e,t){e.readyState===me.OPEN&&e.send(JSON.stringify(t))}function yr(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 gr=7;function Ie(e){return typeof e!="number"||!Number.isFinite(e)?gr:Math.min(365,Math.max(1,Math.round(e)))}const Ee=process.argv.slice(2);{const e=Ee.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 At(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await qt(r),process.exit(0);case"list":case"ls":await _t(),process.exit(0);case"setup":await Ct(e),process.exit(0);case"theme":await It(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":ke(),process.exit(0);case"-V":case"--version":case"-v":Et(),process.exit(0);default:console.error(`unknown argument: ${t}`),ke(),process.exit(1)}}}function x(e,t){e.readyState===me.OPEN&&e.send(JSON.stringify(t))}const W=new Set,Re=[];await S.read(),S.data.sessionAccess??=[],S.data.pinnedViews??=[],S.data.watchedPanes??=[],S.data.triggeredTasks??=[],S.data.quickCommands??=[];const ne=await ee();let T=await Ot();const k=ne.commandbar===!0,oe=yr(Ee,ne.terminalRenderer),Oe=Ie(ne.scheduleHistoryDays),Me=j.join(process.cwd(),"extensions"),Le=await wt(Me);for(const e of Le)e.start&&Re.push(jt(e.dir,e));const L=new vt({db:S,historyRetentionMs:Oe*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await L.restoreFromDb();const s=new Qe;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()}),kt(s,Me,Le);const wr=j.dirname(Ke(import.meta.url)),jr=[j.join(wr,"assets"),j.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 jr){const n=j.join(r,t);if(!V(n))continue;const i=await ze(n),o=j.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","Cache-Control":o===".js"||o===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const kr=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ import{chmodSync as Ge,existsSync as B,mkdirSync as ze,readdirSync as fe,readFileSync as me,statSync as D,unlinkSync as Je,writeFileSync as Ke}from"node:fs";import{readFile as Qe}from"node:fs/promises";import{execSync as V,execFileSync as pe}from"node:child_process";import{createRequire as Xe}from"node:module";import j from"node:path";import{fileURLToPath as Ye}from"node:url";import{Hono as Ze}from"hono";import{serve as et}from"@hono/node-server";import{WebSocketServer as tt,WebSocket as he}from"ws";import*as rt from"node-pty";try{const e=j.dirname(Xe(import.meta.url).resolve("node-pty/package.json")),t=j.join(e,"prebuilds");if(B(t))for(const r of fe(t)){const n=j.join(t,r,"spawn-helper");B(n)&&!(D(n).mode&73)&&Ge(n,493)}}catch{}import{listSessions as A}from"./sessions.js";import{renderLoginPage as nt,renderNotesIndex as ot,renderNotesPage as st,renderSettings as it,renderThemeSettings as at,renderScheduleIndex as dt,renderHistoryIndex as ut,renderQuickCommandsPage as ct,renderFilesIndex as lt,renderShell as ft}from"./frontend.js";import{loadSecurityConfig as mt,saveSecurityConfig as ye}from"./lib/security-config.js";import{hashPassword as we,verifyPassword as ge,validatePassword as je,TokenStore as pt}from"./lib/auth.js";import{RateLimiter as ht}from"./lib/rateLimiter.js";import{atomicWriteFileSync as yt}from"./lib/atomicWrite.js";import{resolveFsPath as H,resolveFsRoots as ke,MAX_FILE_BYTES as ve,walkRecursive as wt}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as gt}from"./lib/tmux-windows.js";import{audit as l}from"./lib/auditLog.js";import{db as S}from"./lib/db.js";import{getSessionAccessMap as E}from"./lib/session-access.js";import{listWindowHistory as jt,clearWindowHistory as kt}from"./lib/window-history.js";import{loadExtensions as vt,spawnExtensionBackend as bt,registerExtensionRoutes as St}from"./lib/ext-loader.js";import{SchedulerService as xt,isValidScheduleInput as Tt,isValidRescheduleInput as qt}from"./lib/scheduler.js";import{getScheduleDelayError as be}from"./lib/schedule-delay.js";import{handleClientMessage as _t}from"./lib/ws-message.js";import{loadDotEnv as At}from"./lib/load-env.js";import{cmdAdd as Ct,cmdRemove as It,cmdList as Et,cmdSetup as Rt,cmdTheme as Ot,printUsage as Se,printVersion as Mt}from"./lib/cli.js";import{readSettings as te,writeSettings as Lt}from"./lib/settings.js";import{readActiveTheme as Pt,setActiveThemeTemplate as xe}from"./lib/theme-store.js";import{isThemeTemplateId as Te,THEME_TEMPLATE_IDS as Nt}from"./lib/themes/index.js";import{installPlugin as Dt,uninstallPlugin as Ht}from"./lib/plugins.js";import{buildCommandbarSessions as O}from"./lib/commandbar.js";import{pinView as Ft,unpinView as Ut,listPinnedViews as Wt}from"./lib/pinned-views.js";import{listWindowLabels as $t,setWindowLabel as Bt}from"./lib/window-labels.js";import{captureAndStoreWindows as qe,getStoredWindows as _e}from"./lib/session-windows.js";import{acquireControlClient as Vt,killAllControlClients as Gt}from"./lib/tmux-control.js";import{buildSidebarSessions as zt}from"./lib/sessions-sidebar.js";import{createQuickCommand as Jt,deleteQuickCommand as Kt,listQuickCommands as Ae,updateQuickCommand as Qt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Xt,capturePaneTail as Yt,capturePaneHistoryChunk as Zt,toCrlf as Ce}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as er}from"./lib/terminal-config.js";import{ImageUploadError as tr,saveUploadedImage as rr}from"./lib/image-upload.js";import{browsePage as nr,browseAsset as or,browseSubmit as sr,BrowseError as re}from"./lib/web-browse.js";import{getSystemStatus as ir,getTopProcesses as ar,killProcess as dr}from"./lib/system-monitor.js";import{listSessionWindows as ur,selectSessionWindow as cr,newSessionWindow as lr,renameSessionWindow as fr,newTmuxSession as mr,renameSession as pr,killSession as hr,TmuxWindowsError as K}from"./lib/tmux-windows.js";At();const Q=er(),f=mt(),w=new pt,M=new ht;let F=!1;const X="tmux-web-token",yr=365;function Ie(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function Y(e){if(f.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 wr(e){if(f.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function ne(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*)${X}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Ee(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*)${X}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Sn(e){const t=ne(e);return t?w.validateToken(t):null}function xn(e){const t=Ee(e);return t?w.validateToken(t):null}function Re(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${X}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${yr*86400}${r?"; Secure":""}`)}function gr(e){e.header("Set-Cookie",`${X}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function d(){return async(e,t)=>{const r=ne(e);return!r||!w.validateToken(r)?(l("http_unauthorized",{ip:Y(e)}),e.json({error:"unauthorized"},401)):t()}}function jr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function R(){return async(e,t)=>{const r=ne(e);return!r||!w.validateToken(r)?jr(e):t()}}const U=new Map;function kr(e){let t=0;for(const r of U.values())r.ip===e&&t++;return t}function oe(e,t,r){try{e.close(t,r)}catch{}}function Tn(e,t){e.readyState===he.OPEN&&e.send(JSON.stringify(t))}function vr(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 br=7;function Oe(e){return typeof e!="number"||!Number.isFinite(e)?br:Math.min(365,Math.max(1,Math.round(e)))}const Me=process.argv.slice(2);{const e=Me.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 Ct(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await It(r),process.exit(0);case"list":case"ls":await Et(),process.exit(0);case"setup":await Rt(e),process.exit(0);case"theme":await Ot(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":Se(),process.exit(0);case"-V":case"--version":case"-v":Mt(),process.exit(0);default:console.error(`unknown argument: ${t}`),Se(),process.exit(1)}}}function k(e,t){e.readyState===he.OPEN&&e.send(JSON.stringify(t))}const W=new Set,Le=[];await S.read(),S.data.sessionAccess??=[],S.data.pinnedViews??=[],S.data.watchedPanes??=[],S.data.triggeredTasks??=[],S.data.quickCommands??=[];const se=await te();let x=await Pt();const v=se.commandbar===!0,ie=vr(Me,se.terminalRenderer),Pe=Oe(se.scheduleHistoryDays),Ne=j.join(process.cwd(),"extensions"),De=await vt(Ne);for(const e of De)e.start&&Le.push(bt(e.dir,e));const L=new xt({db:S,historyRetentionMs:Pe*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await L.restoreFromDb();const s=new Ze;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()}),St(s,Ne,De);const Sr=j.dirname(Ye(import.meta.url)),xr=[j.join(Sr,"assets"),j.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 xr){const n=j.join(r,t);if(!B(n))continue;const i=await Qe(n),o=j.extname(t),a={".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":a[o]??"application/octet-stream","Cache-Control":o===".js"||o===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const Tr=`<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>`,Pe=e=>e.body(kr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Pe),s.get("/favicon.ico",Pe);const vr={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(vr,200,{"Cache-Control":"public, max-age=3600"}));const Sr=`// tmux-weblink Service Worker
6
+ </svg>`,He=e=>e.body(Tr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",He),s.get("/favicon.ico",He);const qr={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(qr,200,{"Cache-Control":"public, max-age=3600"}));const _r=`// tmux-weblink Service Worker
7
7
  const CACHE = "tmux-weblink-v1";
8
8
  const ASSETS = ["/", "/favicon.svg"];
9
9
  self.addEventListener("install", (e) => {
@@ -14,14 +14,14 @@ self.addEventListener("activate", (e) => { e.waitUntil(clients.claim()); });
14
14
  self.addEventListener("fetch", (e) => {
15
15
  e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
16
16
  });
17
- `;s.get("/sw.js",e=>e.body(Sr,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!m.passwordHash,n=e.req.query("error");return e.html(et({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:T}))}),s.get("/",E(),e=>{const t=k?O(_(),I()):[],r=ge();return e.html(ct({theme:T,commandbarEnabled:k,commandbarSessions:t,fsRoots:r,terminalCfg:K,renderer:oe,scrollback:K.initialLines+2*K.historyChunk}))}),s.get("/notes",E(),e=>{const t=k?O(_(),I()):[];return e.html(tt(S.data.notes,T,k,t))}),s.get("/notes/:session",E(),e=>{const t=decodeURIComponent(e.req.param("session")),r=k?O(_(),I()):[];return e.html(rt(t,T,k,r))}),s.get("/schedule",E(),e=>{const t=k?O(_(),I()):[];return e.html(st(L.list(),L.listTriggered(),T,Oe,k,t))}),s.get("/history",E(),e=>{const t=_(),r=k?O(t,I()):[],n=new Set(t.map(i=>i.name));return e.html(it(yt(),T,k,r,n))}),s.get("/quick-commands",E(),e=>{const t=k?O(_(),I()):[];return e.html(at(be(),T,k,t))}),s.get("/files",E(),e=>{const t=k?O(_(),I()):[],r=ge();return e.html(dt(T,k,t,r))}),s.post("/api/history/clear",a(),async e=>(await gt(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(be())),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 Vt(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 zt(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 Gt(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=M.check(t);if(!r.allowed)return l("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(!m.passwordHash){if(!m.security.allowRemoteSetup&&!qe(t))return l("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(F)return e.json({error:"Password setup in progress"},409);const f=ye(i);if(f)return e.json({error:f},400);F=!0;try{m.passwordHash=await pe(i),fe(m),l("password_set",{ip:t})}finally{F=!1}const{plaintext:c}=g.createAccessToken("setup",m.security.tokenTtlDays);return Ce(e,c),e.json({ok:!0,token:c,setupMode:!0})}if(!await he(i,m.passwordHash)){const f=M.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:f.failures,permanentLock:f.permanentLock}),f.permanentLock?(g.revokeAll(),l("permanent_lock",{ip:t,failures:f.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):f.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:f.retryAfterMs},429)}M.recordSuccess(t);const d=`browser-${t}`,{plaintext:w}=g.createAccessToken(d,m.security.tokenTtlDays);return Ce(e,w),l("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:w})}),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:m.security.tokenTtlDays,{stored:o,plaintext:d}=g.createAccessToken(n,i);return l("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(g.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=>g.revoke(e.req.param("id"))?(l("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=>(fr(e),e.json({ok:!0}))),s.get("/settings",E(),async e=>{const t=await ee(),r=t.terminalRenderer??"xterm";return e.html(nt({settings:t,renderer:oe,rendererOverridden:oe!==r,theme:T,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 ee(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Ie(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Rt({...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 Pt(n):r==="add"?await Lt(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=>e.html(ot({theme:T,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"||!Se(r)?e.redirect("/settings/theme",303):(T=await ve(r),e.redirect("/settings/theme?saved=1",303))});const xr={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:T.template,templates:Mt.map(t=>({id:t,name:xr[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"||!Se(r)?e.json({error:"invalid theme template"},400):(T=await ve(r),e.json({ok:!0,active:T.template}))}),s.get("/api/system/status",a(),e=>e.json(tr())),s.get("/api/system/processes",a(),e=>e.json(rr())),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=nr(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/sessions",a(),e=>k?e.json(O(_(),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(_().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return dr(r,n),e.json({ok:!0})}catch(o){const d=o instanceof J?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)?(cr(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?(ur(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=ht(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=j.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",d=r.endsWith("/");if(!d)try{d=D(r).isDirectory()}catch{}d||(i=j.dirname(r),o=j.basename(r).toLowerCase());try{const w=ce(i),f=[],c=[];for(const y of w)if(!y.startsWith(".")&&!(o&&!y.toLowerCase().startsWith(o))){try{const p=j.join(i,y);D(p).isDirectory()?(f.push(p),n&&pt(p,f,c,0)):c.push(p)}catch{}if(f.length+c.length>=5e3)break}return e.json({dirs:f,files:c})}catch{return e.json({dirs:[],files:[]})}});function Tr(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=G("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=G("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=G("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let i="";try{i=G("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const o=[];for(const c of i.split(`
18
- `)){if(!c.trim())continue;const y=c[0],p=c[1],b=c.substring(3).trim(),R=p!==" "?p:y;o.push({path:b,status:R,staged:y,unstaged:p,additions:0,deletions:0})}const d=new Map;try{const c=G("git diff --numstat HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3});for(const y of c.split(`
19
- `)){if(!y.trim())continue;const p=y.split(" ");if(p.length>=3){const b=parseInt(p[0],10)||0,R=parseInt(p[1],10)||0;d.set(p[2],{added:b,deleted:R})}}}catch{}for(const c of o){const y=d.get(c.path);y&&(c.additions=y.added,c.deletions=y.deleted)}let w=0,f=0;for(const c of o)w+=c.additions,f+=c.deletions;for(const c of o)if(c.status==="?"&&c.additions===0)try{const y=j.join(r,c.path),b=ue(y,"utf-8").split(`
20
- `).length;c.additions=b,w+=b}catch{}return{repoRoot:r,branch:n,files:o,linesAdded:w,linesRemoved:f}}catch{return t}}s.get("/api/git/status",a(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=H(t),n=Tr(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",a(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=H(t);if(console.error("[git/diff] path=%s resolved=%s file=%s",t,n,r),!V(n)||!D(n).isDirectory())return console.error("[git/diff] resolved path does not exist or is not a directory: %s",n),e.json({error:"invalid repository path: "+n},400);try{const i=le("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),o=le("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:i,stagedDiff:o})}catch(i){throw console.error("[git/diff] execFileSync failed:",i?.code,i.message),i}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),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=H(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>we)return e.json({error:"file too large",size:n,maxBytes:we},413);const i=ue(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=H(t.path);return ft(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=H(t.path);return D(r).isFile()?(Ve(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=H(t.path);return V(r)?e.json({error:"file already exists"},409):(Be(j.dirname(r),{recursive:!0}),Ge(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 se(e){return{...Bt(_(),I(),Ht()),currentSession:e??null}}function Ne(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(se(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(Te(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=Ne(t);return"error"in r?e.json({error:r.error},400):(await Nt(r.sessionName,r.windowIndex),e.json(se()))}),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=Ne(t);return"error"in r?e.json({error:r.error},400):(await Dt(r.sessionName,r.windowIndex),e.json(se()))}),s.get("/api/notes",a(),e=>{const t=[...S.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=S.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=S.data.notes.findIndex(o=>o.scope===t);return i>=0?S.data.notes[i]=n:S.data.notes.push(n),await S.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!_().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:w}=await er(d,i.type||void 0,i.name||void 0);return e.json({path:w})}catch(o){return o instanceof Zt?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(Ft(t).map(o=>[o.windowIndex,o.label])),n=new Map(Te(t).map(o=>[o.index,o])),i=or(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 Ut(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 sr(t,n),e.json({ok:!0})}catch(i){return i instanceof J?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 ar(t,n,r.name),xe(t),e.json({ok:!0})}catch(i){return i instanceof J?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 ir(t),xe(t),e.json({ok:!0})}catch(r){return r instanceof J?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(L.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=je(t);if(r)return e.json({error:r},400);if(!St(t))return e.json({error:"invalid body"},400);const n=await L.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await L.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=je(t);if(r)return e.json({error:r},400);if(!xt(t))return e.json({error:"invalid body"},400);const n=await L.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const br=parseInt(process.env.PORT||"21000",10),Ar=Xe({fetch:s.fetch,port:br,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),ie=new Ye({noServer:!0});function De(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
17
+ `;s.get("/sw.js",e=>e.body(_r,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!f.passwordHash,n=e.req.query("error");return e.html(nt({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:x}))}),s.get("/",R(),e=>{const t=v?O(A(),E()):[],r=ke();return e.html(ft({theme:x,commandbarEnabled:v,commandbarSessions:t,fsRoots:r,terminalCfg:Q,renderer:ie,scrollback:Q.initialLines+2*Q.historyChunk}))}),s.get("/notes",R(),e=>{const t=v?O(A(),E()):[];return e.html(ot(S.data.notes,x,v,t))}),s.get("/notes/:session",R(),e=>{const t=decodeURIComponent(e.req.param("session")),r=v?O(A(),E()):[];return e.html(st(t,x,v,r))}),s.get("/schedule",R(),e=>{const t=v?O(A(),E()):[];return e.html(dt(L.list(),L.listTriggered(),x,Pe,v,t))}),s.get("/history",R(),e=>{const t=A(),r=v?O(t,E()):[],n=new Set(t.map(i=>i.name));return e.html(ut(jt(),x,v,r,n))}),s.get("/quick-commands",R(),e=>{const t=v?O(A(),E()):[];return e.html(ct(Ae(),x,v,t))}),s.get("/files",R(),e=>{const t=v?O(A(),E()):[],r=ke();return e.html(lt(x,v,t,r))}),s.post("/api/history/clear",d(),async e=>(await kt(),e.json({ok:!0}))),s.get("/api/quick-commands",d(),e=>e.json(Ae())),s.post("/api/quick-commands",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Jt(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Qt(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",d(),async e=>await Kt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{const t=Y(e),r=M.check(t);if(!r.allowed)return l("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(!f.passwordHash){if(!f.security.allowRemoteSetup&&!Ie(t))return l("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(F)return e.json({error:"Password setup in progress"},409);const m=je(i);if(m)return e.json({error:m},400);F=!0;try{f.passwordHash=await we(i),ye(f),l("password_set",{ip:t})}finally{F=!1}const{plaintext:u}=w.createAccessToken("setup",f.security.tokenTtlDays);return Re(e,u),e.json({ok:!0,token:u,setupMode:!0})}if(!await ge(i,f.passwordHash)){const m=M.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:m.failures,permanentLock:m.permanentLock}),m.permanentLock?(w.revokeAll(),l("permanent_lock",{ip:t,failures:m.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):m.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:m.retryAfterMs},429)}M.recordSuccess(t);const a=`browser-${t}`,{plaintext:g}=w.createAccessToken(a,f.security.tokenTtlDays);return Re(e,g),l("auth_success",{ip:t,method:"password",tokenName:a}),e.json({ok:!0,token:g})}),s.post("/api/auth/token",d(),async e=>{const t=Y(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:f.security.tokenTtlDays,{stored:o,plaintext:a}=w.createAccessToken(n,i);return l("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:a,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",d(),e=>e.json(w.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",d(),e=>w.revoke(e.req.param("id"))?(l("token_revoked",{ip:Y(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",d(),e=>(gr(e),e.json({ok:!0}))),s.get("/settings",R(),async e=>{const t=await te(),r=t.terminalRenderer??"xterm";return e.html(it({settings:t,renderer:ie,rendererOverridden:ie!==r,theme:x,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",d(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await te(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Oe(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Lt({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:i,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",d(),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 Ht(n):r==="add"?await Dt(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",R(),e=>e.html(at({theme:x,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",d(),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"||!Te(r)?e.redirect("/settings/theme",303):(x=await xe(r),e.redirect("/settings/theme?saved=1",303))});const Ar={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",d(),e=>e.json({active:x.template,templates:Nt.map(t=>({id:t,name:Ar[t]}))})),s.post("/api/theme",d(),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"||!Te(r)?e.json({error:"invalid theme template"},400):(x=await xe(r),e.json({ok:!0,active:x.template}))}),s.get("/api/system/status",d(),e=>e.json(ir())),s.get("/api/system/processes",d(),e=>e.json(ar())),s.post("/api/system/kill",d(),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=dr(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/sessions",d(),e=>v?e.json(O(A(),E())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",d(),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(A().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return mr(r,n),e.json({ok:!0})}catch(o){const a=o instanceof K?o.message:"failed to create session";return e.json({error:a},500)}}),s.post("/api/sessions/rename",d(),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)?(pr(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",d(),async e=>{try{const{name:t}=await e.req.json();return t?(hr(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",d(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=gt(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",d(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=j.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",a=r.endsWith("/");if(!a)try{a=D(r).isDirectory()}catch{}a||(i=j.dirname(r),o=j.basename(r).toLowerCase());try{const g=fe(i),m=[],u=[];for(const y of g)if(!y.startsWith(".")&&!(o&&!y.toLowerCase().startsWith(o))){try{const p=j.join(i,y);D(p).isDirectory()?(m.push(p),n&&wt(p,m,u,0)):u.push(p)}catch{}if(m.length+u.length>=5e3)break}return e.json({dirs:m,files:u})}catch{return e.json({dirs:[],files:[]})}});function Cr(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=V("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=V("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=V("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let i="";try{i=V("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const o=[];for(const u of i.split(`
18
+ `)){if(!u.trim())continue;const y=u[0],p=u[1],q=u.substring(3).trim(),C=p!==" "?p:y;o.push({path:q,status:C,staged:y,unstaged:p,additions:0,deletions:0})}const a=new Map;try{const u=V("git diff --numstat HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3});for(const y of u.split(`
19
+ `)){if(!y.trim())continue;const p=y.split(" ");if(p.length>=3){const q=parseInt(p[0],10)||0,C=parseInt(p[1],10)||0;a.set(p[2],{added:q,deleted:C})}}}catch{}for(const u of o){const y=a.get(u.path);y&&(u.additions=y.added,u.deletions=y.deleted)}let g=0,m=0;for(const u of o)g+=u.additions,m+=u.deletions;for(const u of o)if(u.status==="?"&&u.additions===0)try{const y=j.join(r,u.path),q=me(y,"utf-8").split(`
20
+ `).length;u.additions=q,g+=q}catch{}return{repoRoot:r,branch:n,files:o,linesAdded:g,linesRemoved:m}}catch{return t}}s.get("/api/git/status",d(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=H(t),n=Cr(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",d(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=H(t);if(console.error("[git/diff] path=%s resolved=%s file=%s",t,n,r),!B(n)||!D(n).isDirectory())return console.error("[git/diff] resolved path does not exist or is not a directory: %s",n),e.json({error:"invalid repository path: "+n},400);try{const i=pe("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),o=pe("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:i,stagedDiff:o})}catch(i){throw console.error("[git/diff] execFileSync failed:",i?.code,i.message),i}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",d(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=H(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>ve)return e.json({error:"file too large",size:n,maxBytes:ve},413);const i=me(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",d(),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=H(t.path);return yt(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",d(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=H(t.path);return D(r).isFile()?(Je(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",d(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=H(t.path);return B(r)?e.json({error:"file already exists"},409):(ze(j.dirname(r),{recursive:!0}),Ke(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 ae(e){return typeof e=="string"&&e.length<=64?e:"default"}s.get("/api/browse",d(),async e=>{const t=e.req.query("url");if(!t)return e.json({error:"url is required"},400);const r=ae(e.req.query("tab"));try{const n=await nr(t,r);return e.json(n)}catch(n){return n instanceof re?e.json({error:n.message},n.status):(console.error("[browse]",n),e.json({error:"browse failed"},500))}}),s.get("/api/browse/asset",d(),async e=>{const t=e.req.query("url");if(!t)return e.json({error:"url is required"},400);const r=ae(e.req.query("tab"));try{const{buf:n,contentType:i}=await or(t,r);return e.body(n,200,{"Content-Type":i,"Cache-Control":"no-cache"})}catch(n){return n instanceof re?e.json({error:n.message},n.status):(console.error("[browse/asset]",n),e.json({error:"asset fetch failed"},500))}}),s.post("/api/browse/submit",d(),async e=>{const t=ae(e.req.query("tab"));let r;try{r=await e.req.parseBody()}catch{return e.json({error:"invalid form body"},400)}const n=typeof r.__browse_action=="string"?r.__browse_action:"",i=typeof r.__browse_method=="string"&&r.__browse_method.toLowerCase()==="get"?"get":"post";if(!n)return e.json({error:"missing form action"},400);const o=[];for(const[a,g]of Object.entries(r))a==="__browse_action"||a==="__browse_method"||o.push([a,String(g)]);try{const a=await sr(n,o,i,t);return e.json(a)}catch(a){return a instanceof re?e.json({error:a.message},a.status):(console.error("[browse/submit]",a),e.json({error:"form submit failed"},500))}});function de(e){return{...zt(A(),E(),Wt()),currentSession:e??null}}function Fe(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",d(),e=>{const t=e.req.query("currentSession");return e.json(de(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",d(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(_e(t))}),s.post("/api/pinned-views",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Fe(t);return"error"in r?e.json({error:r.error},400):(await Ft(r.sessionName,r.windowIndex),e.json(de()))}),s.delete("/api/pinned-views",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Fe(t);return"error"in r?e.json({error:r.error},400):(await Ut(r.sessionName,r.windowIndex),e.json(de()))}),s.get("/api/notes",d(),e=>{const t=[...S.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",d(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=S.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",d(),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=S.data.notes.findIndex(o=>o.scope===t);return i>=0?S.data.notes[i]=n:S.data.notes.push(n),await S.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",d(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!A().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(),a=Buffer.from(o),{path:g}=await rr(a,i.type||void 0,i.name||void 0);return e.json({path:g})}catch(o){return o instanceof tr?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",d(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map($t(t).map(o=>[o.windowIndex,o.label])),n=new Map(_e(t).map(o=>[o.index,o])),i=ur(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",d(),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 Bt(t,n,i);return e.json(o)}),s.post("/api/session/:session/select-window",d(),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 cr(t,n),e.json({ok:!0})}catch(i){return i instanceof K?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",d(),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 fr(t,n,r.name),qe(t),e.json({ok:!0})}catch(i){return i instanceof K?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",d(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return lr(t),qe(t),e.json({ok:!0})}catch(r){return r instanceof K?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",d(),e=>e.json(L.list(e.req.query("session")))),s.post("/api/schedule",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=be(t);if(r)return e.json({error:r},400);if(!Tt(t))return e.json({error:"invalid body"},400);const n=await L.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",d(),async e=>await L.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",d(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=be(t);if(r)return e.json({error:r},400);if(!qt(t))return e.json({error:"invalid body"},400);const n=await L.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const Ir=parseInt(process.env.PORT||"21000",10),Er=et({fetch:s.fetch,port:Ir,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),ue=new tt({noServer:!0});function Ue(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
21
21
  \r
22
- `),e.destroy()}Ar.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=mr(e),d=e.headers.origin,w=m.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(w.length===0||!w.includes(d))){l("ws_rejected_origin",{ip:o,origin:d}),De(t,403,"Origin not allowed");return}const f=hr(o);if(f>=m.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:o,liveFromIp:f}),De(t,429,"Too many connections");return}const c=_e(e)||n.searchParams.get("token")||"",y=c?g.validateToken(c):null;ie.handleUpgrade(e,t,r,p=>{ie.emit("connection",p,e,decodeURIComponent(i[1]),o,y)})}),ie.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};U.set(e,o),l("ws_connected",{ip:n});let d=null;const{initialLines:w,historyChunk:f,syncIdleMs:c,syncMaxMs:y}=K;let p=!0,b=null,R=null,$=r,P=null;const z=()=>{b&&(clearTimeout(b),b=null),R&&(clearTimeout(R),R=null)},ae=()=>{if(p){z(),p=!1;try{$=Jt(r)}catch{$=r}if(!Xt($))try{const v=Kt($,w);x(e,{type:"snapshot",data:Ae(v),lines:w})}catch{x(e,{type:"data",data:`\r
23
- `})}}},Fe=()=>{p&&(b&&clearTimeout(b),b=setTimeout(ae,c))};function de(){try{d=Ze.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){x(e,{type:"data",data:`\r
24
- \x1B[31mFailed to attach to tmux session "${r}": ${u.message}\x1B[0m\r
25
- `}),re(e,1011,"pty spawn failed");return}W.add(d);let v=-1,h="";P=Wt(r,({activeIndex:u,windows:A})=>{x(e,{type:"window_changed",activeIndex:u,windows:A});const N=A.map(q=>q.index).join(","),B=u!==v||N!==h;v=u,h=N}),R=setTimeout(ae,y),d.onData(u=>{if(p){Fe();return}x(e,{type:"data",data:u})}),d.onExit(({exitCode:u})=>{z(),d&&W.delete(d),d=null,x(e,{type:"data",data:`\r
26
- \x1B[2m--- tmux exited (code ${u}) ---\x1B[0m\r
27
- `}),re(e,1e3,"pty exited")})}function Y(v,h){o.authenticated=!0,o.authTimeout&&(clearTimeout(o.authTimeout),o.authTimeout=null),l("auth_success",{ip:n,method:v,tokenName:h}),x(e,{type:"auth.ok",setupMode:!m.passwordHash}),de()}function C(v,h={}){x(e,{type:"auth.failed",message:v,...h})}async function Ue(v){let h;try{h=JSON.parse(v)}catch{return!1}if(h.type==="auth.token"&&typeof h.token=="string"){const u=g.validateToken(h.token);if(!u){l("token_auth_failed",{ip:n});const A=M.recordFailure(n);return A.permanentLock&&(g.revokeAll(),l("permanent_lock",{ip:n,failures:A.failures})),C("Invalid or expired token",{permanentLock:A.permanentLock,retryAfterMs:A.retryAfterMs}),!0}return g.touch(u.tokenHash),Y("token",u.name),!0}if(h.type==="auth"&&typeof h.password=="string"){const u=M.check(n);if(!u.allowed)return l("rate_limited",{ip:n,retryAfterMs:u.retryAfterMs,permanentLock:u.permanentLock}),C("Too many attempts",{permanentLock:u.permanentLock,retryAfterMs:u.retryAfterMs}),!0;if(!m.passwordHash){if(!m.security.allowRemoteSetup&&!qe(n))return l("setup_rejected_remote",{ip:n}),C("First-run setup must be performed from localhost"),!0;if(F)return C("Password setup in progress"),!0;const q=ye(h.password);if(q)return C(q),!0;F=!0;try{m.passwordHash=await pe(h.password),fe(m),l("password_set",{ip:n})}finally{F=!1}const{stored:Z,plaintext:We}=g.createAccessToken("setup",m.security.tokenTtlDays);return x(e,{type:"auth.ok",setupMode:!0,token:We}),l("token_created",{ip:n,name:Z.name,tokenId:Z.id}),l("auth_success",{ip:n,method:"password",tokenName:Z.name}),de(),!0}if(!m.passwordHash)return C("Server not configured"),!0;if(!await he(h.password,m.passwordHash)){const q=M.recordFailure(n);return l("auth_failed",{ip:n,method:"password",failures:q.failures,permanentLock:q.permanentLock}),q.permanentLock&&(g.revokeAll(),l("permanent_lock",{ip:n,failures:q.failures})),C("Incorrect password",{permanentLock:q.permanentLock,retryAfterMs:q.retryAfterMs}),!0}M.recordSuccess(n);const N=`ws-${n}`,{plaintext:B}=g.createAccessToken(N,m.security.tokenTtlDays);return Y("password",N),x(e,{type:"auth.ok",setupMode:!1,token:B}),!0}return!1}i?(g.touch(i.tokenHash),Y("token",i.name)):(x(e,{type:"auth.required",setupMode:!m.passwordHash}),o.authTimeout=setTimeout(()=>{o.authenticated||(l("auth_timeout",{ip:n}),C("Authentication timeout"),re(e,4e3,"Auth timeout"))},m.security.authTimeoutMs)),e.on("message",async v=>{const h=typeof v=="string"?v:v.toString("utf-8");if(!o.authenticated){if(h.length>1e6){x(e,{type:"auth.failed",message:"Message too large"});return}await Ue(h)||C("Authentication required");return}if(!d)return;try{if(Tt(h,d))return}catch{return}let u;try{u=JSON.parse(h)}catch{return}if(u.type==="load_history"&&typeof u.before=="number"){const A=Math.max(0,Math.floor(u.before));try{const{data:N,lines:B}=Qt($,A,f);x(e,{type:"history",data:Ae(N),before:A,lines:B})}catch{x(e,{type:"history",data:"",before:A,lines:0})}}}),e.on("close",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),l("ws_disconnected",{ip:n}),z(),P&&(P(),P=null),d&&(W.delete(d),d.kill(),d=null)}),e.on("error",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),z(),P&&(P(),P=null),d&&(W.delete(d),d.kill(),d=null)})});function He(){L.cleanup(),$t();for(const[e,t]of U){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}U.clear();for(const e of W)try{e.kill()}catch{}W.clear();for(const e of Re)try{e.kill("SIGTERM")}catch{}M.dispose(),process.exit(0)}process.on("SIGINT",He),process.on("SIGTERM",He),setInterval(()=>{g.purgeExpired()},3600*1e3).unref();
22
+ `),e.destroy()}Er.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=wr(e),a=e.headers.origin,g=f.security.allowedOrigins;if(a&&!(a===`http://${e.headers.host}`||a===`https://${e.headers.host}`)&&(g.length===0||!g.includes(a))){l("ws_rejected_origin",{ip:o,origin:a}),Ue(t,403,"Origin not allowed");return}const m=kr(o);if(m>=f.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:o,liveFromIp:m}),Ue(t,429,"Too many connections");return}const u=Ee(e)||n.searchParams.get("token")||"",y=u?w.validateToken(u):null;ue.handleUpgrade(e,t,r,p=>{ue.emit("connection",p,e,decodeURIComponent(i[1]),o,y)})}),ue.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};U.set(e,o),l("ws_connected",{ip:n});let a=null;const{initialLines:g,historyChunk:m,syncIdleMs:u,syncMaxMs:y}=Q;let p=!0,q="",C=null,G=null,z=r,P=null;const J=()=>{C&&(clearTimeout(C),C=null),G&&(clearTimeout(G),G=null)},ce=()=>{if(p){J(),p=!1;try{z=Xt(r)}catch{z=r}try{const b=Yt(z,g);k(e,{type:"snapshot",data:Ce(b),lines:g})}catch{k(e,{type:"data",data:`\r
23
+ `})}q&&(k(e,{type:"data",data:q}),q="")}},$e=()=>{p&&(C&&clearTimeout(C),C=setTimeout(ce,u))};function le(){try{a=rt.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(c){k(e,{type:"data",data:`\r
24
+ \x1B[31mFailed to attach to tmux session "${r}": ${c.message}\x1B[0m\r
25
+ `}),oe(e,1011,"pty spawn failed");return}W.add(a);let b=-1,h="";P=Vt(r,({activeIndex:c,windows:T})=>{k(e,{type:"window_changed",activeIndex:c,windows:T});const N=T.map(_=>_.index).join(","),$=c!==b||N!==h;b=c,h=N}),G=setTimeout(ce,y),a.onData(c=>{if(p){q+=c,$e();return}k(e,{type:"data",data:c})}),a.onExit(({exitCode:c})=>{J(),a&&W.delete(a),a=null,k(e,{type:"data",data:`\r
26
+ \x1B[2m--- tmux exited (code ${c}) ---\x1B[0m\r
27
+ `}),oe(e,1e3,"pty exited")})}function Z(b,h){o.authenticated=!0,o.authTimeout&&(clearTimeout(o.authTimeout),o.authTimeout=null),l("auth_success",{ip:n,method:b,tokenName:h}),k(e,{type:"auth.ok",setupMode:!f.passwordHash}),le()}function I(b,h={}){k(e,{type:"auth.failed",message:b,...h})}async function Be(b){let h;try{h=JSON.parse(b)}catch{return!1}if(h.type==="auth.token"&&typeof h.token=="string"){const c=w.validateToken(h.token);if(!c){l("token_auth_failed",{ip:n});const T=M.recordFailure(n);return T.permanentLock&&(w.revokeAll(),l("permanent_lock",{ip:n,failures:T.failures})),I("Invalid or expired token",{permanentLock:T.permanentLock,retryAfterMs:T.retryAfterMs}),!0}return w.touch(c.tokenHash),Z("token",c.name),!0}if(h.type==="auth"&&typeof h.password=="string"){const c=M.check(n);if(!c.allowed)return l("rate_limited",{ip:n,retryAfterMs:c.retryAfterMs,permanentLock:c.permanentLock}),I("Too many attempts",{permanentLock:c.permanentLock,retryAfterMs:c.retryAfterMs}),!0;if(!f.passwordHash){if(!f.security.allowRemoteSetup&&!Ie(n))return l("setup_rejected_remote",{ip:n}),I("First-run setup must be performed from localhost"),!0;if(F)return I("Password setup in progress"),!0;const _=je(h.password);if(_)return I(_),!0;F=!0;try{f.passwordHash=await we(h.password),ye(f),l("password_set",{ip:n})}finally{F=!1}const{stored:ee,plaintext:Ve}=w.createAccessToken("setup",f.security.tokenTtlDays);return k(e,{type:"auth.ok",setupMode:!0,token:Ve}),l("token_created",{ip:n,name:ee.name,tokenId:ee.id}),l("auth_success",{ip:n,method:"password",tokenName:ee.name}),le(),!0}if(!f.passwordHash)return I("Server not configured"),!0;if(!await ge(h.password,f.passwordHash)){const _=M.recordFailure(n);return l("auth_failed",{ip:n,method:"password",failures:_.failures,permanentLock:_.permanentLock}),_.permanentLock&&(w.revokeAll(),l("permanent_lock",{ip:n,failures:_.failures})),I("Incorrect password",{permanentLock:_.permanentLock,retryAfterMs:_.retryAfterMs}),!0}M.recordSuccess(n);const N=`ws-${n}`,{plaintext:$}=w.createAccessToken(N,f.security.tokenTtlDays);return Z("password",N),k(e,{type:"auth.ok",setupMode:!1,token:$}),!0}return!1}i?(w.touch(i.tokenHash),Z("token",i.name)):(k(e,{type:"auth.required",setupMode:!f.passwordHash}),o.authTimeout=setTimeout(()=>{o.authenticated||(l("auth_timeout",{ip:n}),I("Authentication timeout"),oe(e,4e3,"Auth timeout"))},f.security.authTimeoutMs)),e.on("message",async b=>{const h=typeof b=="string"?b:b.toString("utf-8");if(!o.authenticated){if(h.length>1e6){k(e,{type:"auth.failed",message:"Message too large"});return}await Be(h)||I("Authentication required");return}if(!a)return;try{if(_t(h,a))return}catch{return}let c;try{c=JSON.parse(h)}catch{return}if(c.type==="load_history"&&typeof c.before=="number"){const T=Math.max(0,Math.floor(c.before));try{const{data:N,lines:$}=Zt(z,T,m);k(e,{type:"history",data:Ce(N),before:T,lines:$})}catch{k(e,{type:"history",data:"",before:T,lines:0})}}}),e.on("close",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),l("ws_disconnected",{ip:n}),J(),P&&(P(),P=null),a&&(W.delete(a),a.kill(),a=null)}),e.on("error",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),J(),P&&(P(),P=null),a&&(W.delete(a),a.kill(),a=null)})});function We(){L.cleanup(),Gt();for(const[e,t]of U){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}U.clear();for(const e of W)try{e.kill()}catch{}W.clear();for(const e of Le)try{e.kill("SIGTERM")}catch{}M.dispose(),process.exit(0)}process.on("SIGINT",We),process.on("SIGTERM",We),setInterval(()=>{w.purgeExpired()},3600*1e3).unref();
@@ -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 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
+ 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};
package/dist/lib/icons.js CHANGED
@@ -1 +1 @@
1
- const a={menu:"M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z",close:"M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7a1 1 0 1 0-1.41 1.41L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 1 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z",search:"M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",refresh:"M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 8 8h-2a6 6 0 1 1-6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z",add:"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",edit:"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM5.92 19H5v-.92l9.06-9.06.92.92L5.92 19zm13.66-12.83a1 1 0 0 0 0-1.41l-1.34-1.34a1 1 0 0 0-1.41 0l-1.05 1.05 2.75 2.75 1.05-1.05z",delete:"M15 4V3H9v1H4v2h1.5l.81 13.05A2 2 0 0 0 8.3 21h7.4a2 2 0 0 0 1.99-1.95L18.5 6H20V4h-5zm-5.5 2h5l-.75 12h-3.5L9.5 6z",check:"M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",sessions:"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z",notes:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2 5 5h-5V4zM6 20V4h5v7h7v9H6z",schedule:"M19 4h-1V2h-2v2H8V2H6v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 16H5V10h14v10zM9 14H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm-8 4H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2z",agents:"M12 2a5 5 0 1 0 0 10 5 5 0 0 0 0-10zm0 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm-7 13v-2a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v2h2v-2a7 7 0 0 0-7-7H10a7 7 0 0 0-7 7v2h2z",history:"M13 3a9 9 0 0 0-9 9H1l4 4 4-4H6a7 7 0 1 1 7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.96 8.96 0 0 0 13 21a9 9 0 0 0 0-18zm-1 5v5l4.25 2.55.75-1.23-3.5-2.08V8H12z","quick-commands":"M13 3 4 14h7l-1 7 9-11h-7l1-7z",windows:"M4 6h16v2H4V6zm0 5h10v2H4v-2zm0 5h16v2H4v-2z",terminal:"M4 17.6 6.4 20l8-8-8-8L4 6.4 11.6 12 4 17.6zM20 18h-8v2h8v-2z",microphone:"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5-3c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z",keyboard:"M20 5H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z",settings:"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6a3.6 3.6 0 1 1 0-7.2 3.6 3.6 0 0 1 0 7.2z",pin:"M16 12V4h1V2H7v2h1v8l-4 4v2h5.2v6h1.6v-6H20v-2l-4-4z","pin-outline":"M16 12V4h1V2H7v2h1v8l-4 4v2h5.2v6h1.6v-6H20v-2l-4-4zm-4.8 7.2V11l2-2V4h1.6v5l2 2v5.2H11.2z",folder:"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",file:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2 5 5h-5V4zM6 20V4h5v7h7v9H6z",worktree:"M6 3a3 3 0 0 0-1 5.83v6.34a3 3 0 1 0 2 0V8.83A3 3 0 0 0 6 3zm0 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0 12a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM18 3a3 3 0 0 0-1 5.83V9a4 4 0 0 1-4 4h-2a3 3 0 1 0 0 2h2a6 6 0 0 0 6-6v-.17A3 3 0 0 0 18 3zm0 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM9 13a1 1 0 1 1 0 2 1 1 0 0 1 0-2z",copy:"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z",export:"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z",chevronRight:"M9.29 6.71a1 1 0 0 0 0 1.41L13.17 12l-3.88 3.88a1 1 0 1 0 1.41 1.41l4.59-4.59a1 1 0 0 0 0-1.41L10.7 6.7a1 1 0 0 0-1.41.01z",lightning:"M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.33.51-.89.73-1.27L13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15c-.22.46-.58 1.18-.78 1.58L11 21z","git-branch":"M13.5 7a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm0 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zM11 9.5v5.1a4.03 4.03 0 0 0-3-.1V9.4a4.03 4.03 0 0 0 3 .1z",external:"M14 3h7v7h-2V6l-9.29 9.29-1.41-1.41L17.59 4.5H14V3zm4 16H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7h-2v7z",play:"M8 5v14l11-7L8 5z",stop:"M6 6h12v12H6V6z",warning:"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z",info:"M11 7h2v2h-2V7zm0 4h2v6h-2v-6zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z",spinner:"M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8z",dots:"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"};function l(h){return a[h]??a.menu}function c(h,v=""){return`<svg viewBox="0 0 24 24" fill="currentColor"${v?" "+v:""}><path d="${l(h)}"/></svg>`}function H(h){return`<path d="${l(h)}"/>`}const m={};function n(h,v){const z=m[h];return z?c(z):v?.trim().startsWith("<svg")?v:`<span style="font-size:18px;line-height:1">${v??""}</span>`}export{n as extIcon,c as icon,l as iconPath,H as iconPathString};
1
+ const c={menu:"M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z",close:"M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7a1 1 0 1 0-1.41 1.41L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 1 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z",search:"M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",refresh:"M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 8 8h-2a6 6 0 1 1-6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z",add:"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",edit:"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM5.92 19H5v-.92l9.06-9.06.92.92L5.92 19zm13.66-12.83a1 1 0 0 0 0-1.41l-1.34-1.34a1 1 0 0 0-1.41 0l-1.05 1.05 2.75 2.75 1.05-1.05z",delete:"M15 4V3H9v1H4v2h1.5l.81 13.05A2 2 0 0 0 8.3 21h7.4a2 2 0 0 0 1.99-1.95L18.5 6H20V4h-5zm-5.5 2h5l-.75 12h-3.5L9.5 6z",check:"M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",sessions:"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z",browser:"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z",back:"M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z",forward:"M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z",notes:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2 5 5h-5V4zM6 20V4h5v7h7v9H6z",schedule:"M19 4h-1V2h-2v2H8V2H6v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 16H5V10h14v10zM9 14H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm-8 4H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2z",agents:"M12 2a5 5 0 1 0 0 10 5 5 0 0 0 0-10zm0 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm-7 13v-2a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v2h2v-2a7 7 0 0 0-7-7H10a7 7 0 0 0-7 7v2h2z",history:"M13 3a9 9 0 0 0-9 9H1l4 4 4-4H6a7 7 0 1 1 7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.96 8.96 0 0 0 13 21a9 9 0 0 0 0-18zm-1 5v5l4.25 2.55.75-1.23-3.5-2.08V8H12z","quick-commands":"M13 3 4 14h7l-1 7 9-11h-7l1-7z",windows:"M4 6h16v2H4V6zm0 5h10v2H4v-2zm0 5h16v2H4v-2z",terminal:"M4 17.6 6.4 20l8-8-8-8L4 6.4 11.6 12 4 17.6zM20 18h-8v2h8v-2z",microphone:"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5-3c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z",keyboard:"M20 5H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z",settings:"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6a3.6 3.6 0 1 1 0-7.2 3.6 3.6 0 0 1 0 7.2z",pin:"M16 12V4h1V2H7v2h1v8l-4 4v2h5.2v6h1.6v-6H20v-2l-4-4z","pin-outline":"M16 12V4h1V2H7v2h1v8l-4 4v2h5.2v6h1.6v-6H20v-2l-4-4zm-4.8 7.2V11l2-2V4h1.6v5l2 2v5.2H11.2z",folder:"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",file:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2 5 5h-5V4zM6 20V4h5v7h7v9H6z",worktree:"M6 3a3 3 0 0 0-1 5.83v6.34a3 3 0 1 0 2 0V8.83A3 3 0 0 0 6 3zm0 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0 12a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM18 3a3 3 0 0 0-1 5.83V9a4 4 0 0 1-4 4h-2a3 3 0 1 0 0 2h2a6 6 0 0 0 6-6v-.17A3 3 0 0 0 18 3zm0 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM9 13a1 1 0 1 1 0 2 1 1 0 0 1 0-2z",copy:"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z",export:"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z",chevronRight:"M9.29 6.71a1 1 0 0 0 0 1.41L13.17 12l-3.88 3.88a1 1 0 1 0 1.41 1.41l4.59-4.59a1 1 0 0 0 0-1.41L10.7 6.7a1 1 0 0 0-1.41.01z",lightning:"M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.33.51-.89.73-1.27L13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15c-.22.46-.58 1.18-.78 1.58L11 21z","git-branch":"M13.5 7a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm0 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zM11 9.5v5.1a4.03 4.03 0 0 0-3-.1V9.4a4.03 4.03 0 0 0 3 .1z",external:"M14 3h7v7h-2V6l-9.29 9.29-1.41-1.41L17.59 4.5H14V3zm4 16H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7h-2v7z",play:"M8 5v14l11-7L8 5z",stop:"M6 6h12v12H6V6z",warning:"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z",info:"M11 7h2v2h-2V7zm0 4h2v6h-2v-6zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z",spinner:"M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8z",dots:"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"};function l(h){return c[h]??c.menu}function a(h,v=""){return`<svg viewBox="0 0 24 24" fill="currentColor"${v?" "+v:""}><path d="${l(h)}"/></svg>`}function m(h){return`<path d="${l(h)}"/>`}const H={};function n(h,v){const z=H[h];return z?a(z):v?.trim().startsWith("<svg")?v:`<span style="font-size:18px;line-height:1">${v??""}</span>`}export{n as extIcon,a as icon,l as iconPath,m as iconPathString};
@@ -1,4 +1,4 @@
1
- import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarHTML as b,commandbarScript as x}from"../commandbar.js";import{newSessionModalCSS as m,newSessionModalHTML as v,newSessionModalScript as u,reducedMotion as h}from"../shared-layout.js";import{icon as i}from"../icons.js";function C(o){return o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function n(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function $(o){const{theme:e,commandbarEnabled:t,commandbarSessions:a,fsRoots:s,terminalCfg:d,renderer:r,scrollback:p}=o,l=`
1
+ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as b,commandbarHTML as g,commandbarScript as x}from"../commandbar.js";import{newSessionModalCSS as m,newSessionModalHTML as v,newSessionModalScript as u,reducedMotion as h}from"../shared-layout.js";import{icon as e}from"../icons.js";function $(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function o(i="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${i}; outline: none;`}function C(i){const{theme:t,commandbarEnabled:r,commandbarSessions:a,fsRoots:s,terminalCfg:l,renderer:n,scrollback:d}=i,p=`
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
  html, body {
4
4
  background: var(--page-bg); color: var(--page-fg);
@@ -87,7 +87,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
87
87
  }
88
88
  .sidebar-footer .mode-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
89
89
  .sidebar-footer .mode-btn.active { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 10%, transparent); }
90
- .sidebar-footer .mode-btn:focus-visible { ${n()} }
90
+ .sidebar-footer .mode-btn:focus-visible { ${o()} }
91
91
 
92
92
  /* \u2500\u2500 Sidebar session mode \u2500\u2500 */
93
93
  .session-item {
@@ -109,7 +109,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
109
109
  }
110
110
  .session-item:hover .session-edit-btn { display: flex; align-items: center; }
111
111
  .session-edit-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
112
- .session-edit-btn:focus-visible { ${n()} }
112
+ .session-edit-btn:focus-visible { ${o()} }
113
113
 
114
114
  .sidebar-section-label {
115
115
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
@@ -126,7 +126,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
126
126
  color: var(--panel-accent); border-color: var(--panel-accent);
127
127
  background: color-mix(in srgb, var(--panel-accent) 6%, transparent);
128
128
  }
129
- .new-session-sidebar-btn:focus-visible { ${n()} }
129
+ .new-session-sidebar-btn:focus-visible { ${o()} }
130
130
  .new-session-sidebar-btn svg { width: 16px; height: 16px; fill: currentColor; }
131
131
 
132
132
  /* \u2500\u2500 Sidebar files mode \u2500\u2500 */
@@ -294,6 +294,97 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
294
294
  font-size: 11px;
295
295
  }
296
296
 
297
+ /* \u2500\u2500 Browser mode tabs (sidebar) \u2500\u2500 */
298
+ .tab-item {
299
+ display: flex; align-items: center; gap: 8px;
300
+ padding: 8px 10px; border-radius: 8px;
301
+ cursor: pointer; font-size: var(--text-sm); color: var(--page-fg);
302
+ margin-bottom: 2px; min-width: 0;
303
+ transition: background 0.1s;
304
+ }
305
+ .tab-item:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
306
+ .tab-item.active { background: color-mix(in srgb, var(--panel-accent) 12%, transparent); color: var(--panel-accent); font-weight: 500; }
307
+ .tab-fav {
308
+ width: 16px; height: 16px; flex-shrink: 0;
309
+ font-size: 13px; line-height: 1; text-align: center;
310
+ }
311
+ .tab-fav svg { width: 16px; height: 16px; fill: currentColor; }
312
+ .tab-title {
313
+ flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
314
+ }
315
+ .tab-close {
316
+ display: none; align-items: center; justify-content: center;
317
+ width: 20px; height: 20px; flex-shrink: 0;
318
+ background: none; border: none; border-radius: 4px;
319
+ color: var(--panel-muted); cursor: pointer; font-size: 14px; line-height: 1;
320
+ }
321
+ .tab-item:hover .tab-close { display: flex; }
322
+ .tab-close:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
323
+ .tab-close:focus-visible { ${o()} }
324
+ .tab-new {
325
+ display: flex; align-items: center; justify-content: center; gap: 6px;
326
+ width: 100%; padding: 8px; margin-top: 6px;
327
+ background: none; border: 1px dashed var(--panel-border); border-radius: 8px;
328
+ color: var(--panel-muted); cursor: pointer; font-size: var(--text-sm); font-family: inherit;
329
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
330
+ }
331
+ .tab-new:hover {
332
+ color: var(--panel-accent); border-color: var(--panel-accent);
333
+ background: color-mix(in srgb, var(--panel-accent) 6%, transparent);
334
+ }
335
+ .tab-new:focus-visible { ${o()} }
336
+ .tab-new svg { width: 16px; height: 16px; fill: currentColor; }
337
+
338
+ /* \u2500\u2500 Browser mode (main area) \u2500\u2500 */
339
+ #browser-view { overflow: hidden; }
340
+ .browser-toolbar {
341
+ display: flex; align-items: center; gap: 6px;
342
+ padding: 8px 12px; border-bottom: 1px solid var(--panel-border);
343
+ flex-wrap: nowrap;
344
+ }
345
+ .bbtn {
346
+ display: inline-flex; align-items: center; justify-content: center;
347
+ width: 32px; height: 32px; flex-shrink: 0;
348
+ background: none; border: none; border-radius: 8px;
349
+ color: var(--panel-muted); cursor: pointer;
350
+ transition: color 0.15s, background 0.15s;
351
+ }
352
+ .bbtn:hover:not(:disabled) { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
353
+ .bbtn:disabled { opacity: 0.35; cursor: default; }
354
+ .bbtn:focus-visible { ${o()} }
355
+ .bbtn svg { width: 18px; height: 18px; fill: currentColor; }
356
+ .br-address {
357
+ flex: 1; min-width: 0;
358
+ padding: 7px 12px;
359
+ background: var(--page-bg); border: 1px solid var(--panel-border);
360
+ border-radius: 8px; color: var(--page-fg); font-size: var(--text-sm);
361
+ font-family: var(--font-mono); outline: none;
362
+ }
363
+ .br-address:focus { border-color: var(--panel-accent); }
364
+ #browser-content {
365
+ flex: 1; overflow: auto;
366
+ background: var(--page-bg); color: var(--page-fg);
367
+ font-size: var(--text-sm); line-height: 1.6;
368
+ padding: 16px;
369
+ word-wrap: break-word;
370
+ }
371
+ #browser-content img { max-width: 100%; height: auto; }
372
+ #browser-content a { color: var(--panel-accent); }
373
+ #browser-content .browser-bar {
374
+ font-family: var(--font-mono); font-size: 11px;
375
+ color: var(--panel-muted); padding: 4px 0;
376
+ border-bottom: 1px solid var(--panel-border); margin-bottom: 12px;
377
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
378
+ }
379
+ #browser-content .browser-bar .err { color: #ef4444; }
380
+ .browser-state {
381
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
382
+ gap: 12px; height: 100%; color: var(--panel-muted); text-align: center;
383
+ padding: 24px; font-size: var(--text-sm); line-height: 1.6;
384
+ }
385
+ .browser-state .big { font-size: 40px; color: var(--panel-accent); opacity: 0.5; }
386
+ .browser-state .hint { font-family: var(--font-mono); font-size: 11px; }
387
+
297
388
  /* \u2500\u2500 Process panel (RAM click) \u2500\u2500 */
298
389
  #process-panel {
299
390
  position: fixed; top: 52px; right: 12px; z-index: 300;
@@ -400,7 +491,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
400
491
  color: var(--page-fg); transition: opacity 0.15s; text-decoration: none;
401
492
  }
402
493
  .btn:hover { opacity: 0.85; }
403
- .btn:focus-visible { ${n()} }
494
+ .btn:focus-visible { ${o()} }
404
495
  .btn.primary { background: var(--panel-accent); border-color: var(--panel-accent); color: var(--panel-accent-on); font-weight: 500; }
405
496
  .btn.primary:hover { opacity: 0.9; }
406
497
  .btn.danger { color: #b91c1c; border-color: color-mix(in srgb, #b91c1c 30%, transparent); }
@@ -555,7 +646,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
555
646
  .file-roots-list li svg { width: 20px; height: 20px; flex-shrink: 0; fill: var(--panel-accent); }
556
647
 
557
648
  /* \u2500\u2500 Commandbar \u2500\u2500 */
558
- ${t?g():""}
649
+ ${r?b():""}
559
650
  ${h()}
560
651
  ${m()}
561
652
  @media (max-width: 640px) {
@@ -568,7 +659,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
568
659
  .mobile-keys .mk-buttons { display: none; }
569
660
  .mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
570
661
  }
571
- `,y=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),c=JSON.stringify({terminal:d,scrollback:p,theme:e.terminal,renderer:r,fsRoots:s,commandbarEnabled:t}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
662
+ `,w=JSON.stringify(t.terminal).replace(/</g,"\\u003c"),c=JSON.stringify({terminal:l,scrollback:d,theme:t.terminal,renderer:n,fsRoots:s,commandbarEnabled:r}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
572
663
  <html lang="en">
573
664
  <head>
574
665
  <meta charset="UTF-8" />
@@ -581,8 +672,8 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
581
672
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
582
673
  <title>tmux-weblink</title>
583
674
  <style>
584
- ${f(e.shell)}
585
- ${l}
675
+ ${f(t.shell)}
676
+ ${p}
586
677
  </style>
587
678
  </head>
588
679
  <body>
@@ -607,7 +698,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
607
698
  <header class="fixed-header">
608
699
  <div class="brand" id="brand-toggle">tmux<span>-weblink</span></div>
609
700
  <div class="header-actions">
610
- ${t?`<button class="header-btn" id="cmdbar-btn" title="Search" aria-label="Search">${i("search")}</button>`:""}
701
+ ${r?`<button class="header-btn" id="cmdbar-btn" title="Search" aria-label="Search">${e("search")}</button>`:""}
611
702
  </div>
612
703
  <div id="header-git" title="Click to browse repository"></div>
613
704
  <span id="header-ram"></span>
@@ -617,9 +708,10 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
617
708
  <aside class="sidebar">
618
709
  <div class="sidebar-content" id="sidebar-content"></div>
619
710
  <div class="sidebar-footer">
620
- <button class="mode-btn active" id="mode-sessions" title="Sessions">${i("sessions",'width="20" height="20"')}</button>
621
- <button class="mode-btn" id="mode-files" title="Files">${i("folder",'width="20" height="20"')}</button>
622
- <button class="mode-btn" id="mode-settings" title="Settings">${i("settings",'width="20" height="20"')}</button>
711
+ <button class="mode-btn active" id="mode-sessions" title="Sessions">${e("sessions",'width="20" height="20"')}</button>
712
+ <button class="mode-btn" id="mode-files" title="Files">${e("folder",'width="20" height="20"')}</button>
713
+ <button class="mode-btn" id="mode-browser" title="Browser">${e("browser",'width="20" height="20"')}</button>
714
+ <button class="mode-btn" id="mode-settings" title="Settings">${e("settings",'width="20" height="20"')}</button>
623
715
  </div>
624
716
  </aside>
625
717
 
@@ -652,6 +744,16 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
652
744
  </div>
653
745
  <div class="gd-content" id="gd-content"></div>
654
746
  </div>
747
+ <div id="browser-view" style="display:none;flex:1;flex-direction:column">
748
+ <div class="browser-toolbar">
749
+ <button class="bbtn" id="br-back" title="Back" aria-label="Back" disabled>${e("back")}</button>
750
+ <button class="bbtn" id="br-fwd" title="Forward" aria-label="Forward" disabled>${e("forward")}</button>
751
+ <button class="bbtn" id="br-reload" title="Reload" aria-label="Reload">${e("refresh")}</button>
752
+ <input type="text" id="br-address" class="br-address" placeholder="Enter URL or search terms\u2026" spellcheck="false" autocomplete="off" autocapitalize="off" autocorrect="off" />
753
+ <button class="bbtn" id="br-new" title="New tab" aria-label="New tab">${e("add")}</button>
754
+ </div>
755
+ <div id="browser-content"></div>
756
+ </div>
655
757
  <div class="mobile-keys" id="mobile-keys">
656
758
  <div class="mk-input">
657
759
  <textarea id="mk-input" placeholder="Type or voice input\u2026" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" rows="1"></textarea>
@@ -679,21 +781,21 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
679
781
  <div class="settings-popover" id="settings-popover">
680
782
  <div class="settings-item">
681
783
  <span>Command bar</span>
682
- <button class="toggle${t?" on":""}" id="set-commandbar"></button>
784
+ <button class="toggle${r?" on":""}" id="set-commandbar"></button>
683
785
  </div>
684
786
  <div class="settings-item">
685
787
  </div>
686
788
  <hr class="settings-divider" />
687
789
  <div class="settings-item">
688
790
  <span>Terminal renderer</span>
689
- <span style="font-size:var(--text-xs);color:var(--panel-muted)">${r==="ghostty"?"Ghostty":"xterm.js"}</span>
791
+ <span style="font-size:var(--text-xs);color:var(--panel-muted)">${n==="ghostty"?"Ghostty":"xterm.js"}</span>
690
792
  </div>
691
793
  <hr class="settings-divider" />
692
794
  <div style="padding:4px 0">
693
- <button class="theme-option" data-theme="vscode" title="VS Code"><span class="theme-dot" style="background:#007acc"></span>VS Code${e.template==="vscode"?'<span class="theme-check">\u2713</span>':""}</button>
694
- <button class="theme-option" data-theme="ghostty" title="Ghostty"><span class="theme-dot" style="background:#ff5f00"></span>Ghostty${e.template==="ghostty"?'<span class="theme-check">\u2713</span>':""}</button>
695
- <button class="theme-option" data-theme="warm-clay" title="Warm Clay"><span class="theme-dot" style="background:#b86b52"></span>Warm Clay${e.template==="warm-clay"?'<span class="theme-check">\u2713</span>':""}</button>
696
- <button class="theme-option" data-theme="dark-cove" title="Dark Cove"><span class="theme-dot" style="background:#7aa2f7"></span>Dark Cove${e.template==="dark-cove"?'<span class="theme-check">\u2713</span>':""}</button>
795
+ <button class="theme-option" data-theme="vscode" title="VS Code"><span class="theme-dot" style="background:#007acc"></span>VS Code${t.template==="vscode"?'<span class="theme-check">\u2713</span>':""}</button>
796
+ <button class="theme-option" data-theme="ghostty" title="Ghostty"><span class="theme-dot" style="background:#ff5f00"></span>Ghostty${t.template==="ghostty"?'<span class="theme-check">\u2713</span>':""}</button>
797
+ <button class="theme-option" data-theme="warm-clay" title="Warm Clay"><span class="theme-dot" style="background:#b86b52"></span>Warm Clay${t.template==="warm-clay"?'<span class="theme-check">\u2713</span>':""}</button>
798
+ <button class="theme-option" data-theme="dark-cove" title="Dark Cove"><span class="theme-dot" style="background:#7aa2f7"></span>Dark Cove${t.template==="dark-cove"?'<span class="theme-check">\u2713</span>':""}</button>
697
799
  </div>
698
800
  <hr class="settings-divider" />
699
801
  <div class="settings-item" style="cursor:pointer" id="set-plugins-link">
@@ -735,14 +837,14 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
735
837
  </script>
736
838
 
737
839
  ${v()}
738
- ${t?b():""}
840
+ ${r?g():""}
739
841
 
740
842
  <script>
741
843
  window.__TMUX_WEB_SHELL__ = ${c};
742
844
  </script>
743
845
  <script type="module">
744
846
  await import('/assets/shell-client.js');
745
- ${t?x(a,[]):""}
847
+ ${r?x(a,[]):""}
746
848
  // Wrapper: refresh sidebar list, then open session
747
849
  window.__onSessionCreated = async function(name) {
748
850
  await window.__refreshSidebar();
@@ -811,4 +913,4 @@ if ('serviceWorker' in navigator) {
811
913
  }
812
914
  </script>
813
915
  </body>
814
- </html>`}export{$ as renderShell};
916
+ </html>`}export{C as renderShell};
@@ -1,5 +1,5 @@
1
1
  import{execFileSync as f}from"node:child_process";const l=3e3;function g(n){return n.replace(/\r?\n/g,`\r
2
- `)}function o(n){return f("tmux",n,{encoding:"utf-8",timeout:l}).trimEnd()}function S(n){return o(["display-message","-p","-t",n,"#{session_name}:#{window_index}.#{pane_index}"])}function m(n){const t=o(["display-message","-p","-t",n,"#{history_size}"]),e=parseInt(t,10);return Number.isFinite(e)?e:0}function _(n){return o(["display-message","-p","-t",n,"#{alternate_on}"])==="1"}function c(n,t,e){const i=["capture-pane","-t",n,"-p","-S",String(t)];return e!==void 0&&i.push("-E",String(e)),o(i)}function h(n,t){return c(n,-t)}function y(n,t,e){const i=m(n);if(t>=i)return{data:"",lines:0};const p=-(t+e),a=-(t+1);let r=c(n,p,a);const u=r===""?0:r.split(`
2
+ `)}function o(n){return f("tmux",n,{encoding:"utf-8",timeout:l}).trimEnd()}function S(n){return o(["display-message","-p","-t",n,"#{session_name}:#{window_index}.#{pane_index}"])}function m(n){const t=o(["display-message","-p","-t",n,"#{history_size}"]),e=parseInt(t,10);return Number.isFinite(e)?e:0}function _(n){return o(["display-message","-p","-t",n,"#{alternate_on}"])==="1"}function c(n,t,e){const i=["capture-pane","-t",n,"-p","-e","-S",String(t)];return e!==void 0&&i.push("-E",String(e)),o(i)}function h(n,t){return c(n,-t)}function y(n,t,e){const i=m(n);if(t>=i)return{data:"",lines:0};const p=-(t+e),a=-(t+1);let r=c(n,p,a);const u=r===""?0:r.split(`
3
3
  `).length,s=Math.min(e,u,i-t);return s<u&&s>0&&(r=r.split(`
4
4
  `).slice(-s).join(`
5
5
  `)),{data:r,lines:s}}export{y as capturePaneHistoryChunk,c as capturePaneLines,h as capturePaneTail,m as getPaneHistorySize,S as getSessionPaneTarget,_ as isAlternateScreen,g as toCrlf};
@@ -1 +1 @@
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};
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};
@@ -0,0 +1 @@
1
+ import{lookup as P}from"node:dns/promises";const A=3*1024*1024,U=8*1024*1024,$=15e3;class x extends Error{status;constructor(n,o=400){super(n),this.status=o,this.name="BrowseError"}}const k=new Map;function J(t){k.delete(t)}function L(t,n,o){let e=k.get(t);e||(e=new Map,k.set(t,e));const r=new URL(n).hostname.toLowerCase(),s=typeof o.getSetCookie=="function"?o.getSetCookie():[];for(const a of s){const c=a.split(";")[0]?.trim();if(!c)continue;const l=/;\s*domain=([^;\s]+)/i.exec(a)?.[1]?.trim().replace(/^\./,"").toLowerCase();e.set(l??r,c)}}function R(t,n){const o=k.get(t);if(!o||o.size===0)return"";const e=new URL(n).hostname.toLowerCase(),r=[];for(const[s,a]of o)(e===s||e.endsWith("."+s))&&r.push(a);return r.join("; ")}function M(t){const n=t.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);if(n){const[e,r]=[Number(n[1]),Number(n[2])];return e===0||e===10||e===127||e===169&&r===254||e===172&&r>=16&&r<=31||e===192&&r===168||e>=224}const o=t.toLowerCase();return o==="::1"||o==="::"||o.startsWith("fe80:")||o.startsWith("fc")||o.startsWith("fd")||o.startsWith("fec0:")}async function T(t){const n=new URL(t);if(n.protocol!=="http:"&&n.protocol!=="https:")throw new x("only http/https URLs are supported");if(process.env.TMUX_WEB_BROWSE_ALLOW_PRIVATE==="1")return;const o=n.hostname;if(M(o))throw new x("private network addresses are blocked");try{const e=await P(o,{all:!0});for(const{address:r}of e)if(M(r))throw new x("private network addresses are blocked")}catch(e){if(e instanceof x)throw e}}async function _(t,n,o){const e=new AbortController,r=setTimeout(()=>e.abort(),$);try{const s=await fetch(t,{...n,redirect:"follow",signal:e.signal});if(!s.ok)throw new x(`the site responded with HTTP ${s.status}`,502);if(!s.body)return{res:s,buf:Buffer.alloc(0),truncated:!1};const a=s.body.getReader(),c=[];let l=0,u=!1;for(;;){const{done:i,value:f}=await a.read();if(i)break;if(l+=f.byteLength,l>o){u=!0,await a.cancel();break}c.push(f)}return{res:s,buf:Buffer.concat(c),truncated:u}}catch(s){throw s instanceof x?s:s instanceof Error&&s.name==="AbortError"?new x("timed out after "+$+"ms",504):new x(s.message||"request failed",502)}finally{clearTimeout(r)}}function W(t,n){const o=/charset=([\w.-]+)/i.exec(t||"");if(o)return o[1];const e=n.subarray(0,4096).toString("latin1"),r=/<meta[^>]+charset=["']?([\w.-]+)/i.exec(e);return r?r[1]:"utf-8"}const q={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:"\xA0",copy:"\xA9",reg:"\xAE",trade:"\u2122",hellip:"\u2026",mdash:"\u2014",ndash:"\u2013",lsquo:"\u2018",rsquo:"\u2019",ldquo:"\u201C",rdquo:"\u201D"};function E(t){return t.replace(/&#x([0-9a-f]+);/gi,(n,o)=>String.fromCodePoint(parseInt(o,16)||0)).replace(/&#(\d+);/g,(n,o)=>String.fromCodePoint(parseInt(o,10)||0)).replace(/&([a-zA-Z][a-zA-Z0-9]*);/g,(n,o)=>q[o]??n)}function H(t,n,o){return`${o==="nav"?"/api/browse":"/api/browse/asset"}?url=${encodeURIComponent(t)}&tab=${encodeURIComponent(n)}`}function y(t,n,o,e){const r=t.trim();if(!r)return t;if(r.startsWith("#"))return r;if(/^(javascript|data|vbscript):/i.test(r))return e==="nav"?"":t;if(/^(mailto|tel|sms|about|blob|file):/i.test(r))return t;let s;try{s=new URL(r,n).href}catch{return t}return/^https?:/i.test(s)?H(s,o,e):t}function z(t,n,o){return t.split(",").map(e=>{const r=e.trim().match(/^(\S+)(\s+.+)?$/);if(!r)return e;const s=y(r[1],n,o,"asset");return s?s+(r[2]??""):e}).join(", ")}function B(t,n,o){return t.replace(/url\(\s*(['"]?)(.*?)\1\s*\)/g,(e,r,s)=>{const a=E(s.trim()),c=y(a,n,o,"asset");return c?`url(${c})`:e})}function N(t){const n=/([a-zA-Z-]+)(?:\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g,o=[];let e;for(;e=n.exec(t);)o.push({name:e[1],value:e[3]??e[4]??e[5]??""});return o}function S(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function j(t,n,o){const e=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(t),r=e?E(e[1]).replace(/\s+/g," ").trim():"",s=n,a=[];let c=0;const l=/<(\/)?([a-zA-Z][a-zA-Z0-9]*)((?:\s+[^<>]*?)?)(\/?)>/gs;let u;for(;u=l.exec(t);){a.push(t.slice(c,u.index));const[i,f,d,h,b]=u,p=d.toLowerCase();if(c=u.index+i.length,f){a.push(i);continue}if(p==="script"||p==="style"){const m=u.index+i.length,v=new RegExp(`</${p}\\s*>`,"gi"),g=t.slice(m),w=v.exec(g),O=w?g.slice(0,w.index):g,C=w?w[0]:"";p==="style"?a.push(i+B(O,s,o)+C):a.push(i+C),c=w?m+w.index+C.length:t.length,l.lastIndex=c;continue}if(p==="iframe"||p==="object"||p==="embed"){if(!b){const m=u.index+i.length,v=new RegExp(`</${p}\\s*>`,"gi"),g=t.slice(m),w=v.exec(g);c=w?m+w.index+w[0].length:t.length}l.lastIndex=c;continue}if(p==="base"){l.lastIndex=c;continue}if(p==="meta"&&/http-equiv=["']?refresh/i.test(h)){l.lastIndex=c;continue}a.push(Z(i,p,h,s,o,u.index,t))}return a.push(t.slice(c)),{html:a.join(""),title:r}}function Z(t,n,o,e,r,s,a){const c=N(o),l=[];for(const i of c){const f=i.name.toLowerCase(),d=i.value;if(f==="href"){if(n==="a"||n==="area")/^javascript:/i.test(d.trim())?i.remove=!0:i.value=y(d,e,r,"nav");else if(n==="link"){const h=(c.find(b=>b.name.toLowerCase()==="rel")?.value||"").toLowerCase();h.includes("stylesheet")||h.includes("icon")?i.value=y(d,e,r,"asset"):i.remove=!0}}else f==="src"&&(n==="img"||n==="source"||n==="video"||n==="audio"||n==="input")?i.value=y(d,e,r,"asset"):f==="srcset"&&n==="img"?i.value=z(d,e,r):f==="poster"&&n==="video"?i.value=y(d,e,r,"asset"):f==="style"?i.value=B(d,e,r):f.startsWith("on")&&(i.remove=!0)}if(n==="form"){let i=c.find(h=>h.name.toLowerCase()==="action"),f=e;if(i&&i.value.trim())try{f=new URL(E(i.value.trim()),e).href}catch{f=e}const d=(c.find(h=>h.name.toLowerCase()==="method")?.value||"get").toLowerCase();i?i.value=`/api/browse/submit?tab=${encodeURIComponent(r)}`:c.push({name:"action",value:`/api/browse/submit?tab=${encodeURIComponent(r)}`}),l.push(`<input type="hidden" name="__browse_action" value="${S(f)}">`),l.push(`<input type="hidden" name="__browse_method" value="${S(d)}">`)}let u="<"+n;for(const i of c)i.remove||i.name==="href"&&n==="a"&&i.value===""||(u+=" "+i.name+'="'+S(i.value)+'"');return u+=">",l.length&&(u+=l.join("")),u}async function X(t,n){await T(t);const o=R(n,t),{res:e,buf:r,truncated:s}=await _(t,{headers:o?{cookie:o}:void 0},A);L(n,t,e.headers);const a=e.headers.get("content-type")||"",c=W(a,r),l=new TextDecoder(c).decode(r),u=e.url||t;if(/html/i.test(a)||!/^(text|application)\/(json|xml|xhtml)/i.test(a)&&/<html[\s>]/i.test(l.slice(0,2e3))){const{html:f,title:d}=j(l,u,n);return{url:t,finalUrl:u,title:d,html:f,contentType:a,truncated:s}}if(/json/i.test(a))try{const f=JSON.stringify(JSON.parse(l),null,2);return{url:t,finalUrl:u,title:new URL(u).hostname,html:`<pre>${S(f)}</pre>`,contentType:a,truncated:s}}catch{}const i=S(l);return{url:t,finalUrl:u,title:new URL(u).hostname,html:`<pre style="white-space:pre-wrap;word-break:break-word;font-family:var(--font-mono);font-size:12px;line-height:1.6;padding:16px">${i}</pre>`,contentType:a,truncated:s}}async function V(t,n){await T(t);const o=R(n,t),{res:e,buf:r,truncated:s}=await _(t,{headers:o?{cookie:o}:void 0},U);return L(n,t,e.headers),{buf:r,contentType:e.headers.get("content-type")||"application/octet-stream"}}async function Y(t,n,o,e){let r=t,s;if(o==="get"){const m=new URL(t);for(const[v,g]of n)m.searchParams.append(v,g);r=m.href,s={}}else{const m=new URLSearchParams;for(const[v,g]of n)m.append(v,g);s={method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:m.toString()}}await T(r);const a=R(e,r),{res:c,buf:l,truncated:u}=await _(r,{...s,headers:{...s.headers,...a?{cookie:a}:{}}},A);L(e,r,c.headers);const i=c.headers.get("content-type")||"",f=W(i,l),d=new TextDecoder(f).decode(l),h=c.url||r,{html:b,title:p}=j(d,h,e);return{url:r,finalUrl:h,title:p,html:b,contentType:i,truncated:u}}export{U as BROWSE_MAX_ASSET_BYTES,A as BROWSE_MAX_HTML_BYTES,$ as BROWSE_TIMEOUT_MS,x as BrowseError,V as browseAsset,X as browsePage,Y as browseSubmit,J as clearTabCookies,j as rewriteHtml};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {