tmux-weblink 2.0.18 → 2.0.19

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,38 +1,38 @@
1
- var zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ee(e){return e.replace(zt,"")}var ye;function Yt(){return ye||(document.querySelector("link[data-tmux-web-xterm-css]")?ye=Promise.resolve():(ye=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)}),ye))}var jt=class gt{terminal;fitAddon;container;constructor(t,i,a){this.container=t,this.terminal=i,this.fitAddon=a,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,i,a){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Yt()]),c=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:a});return new gt(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(i=>this.terminal.write(t,i))}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=Ft(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||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,i){let a=this.terminal.buffer.active,[o,r]=t<=i?[t,i]:[i,t],c=a.viewportY,l=[];for(let p=o;p<=r;p++){let u=a.getLine(c+p);l.push(u?u.translateToString(!0):"")}return l.join(`
2
- `)}selectRows(t,i){let a=this.terminal.buffer.active,[o,r]=t<=i?[t,i]:[i,t];this.terminal.selectLines(a.viewportY+o,a.viewportY+r)}selectCells(t,i,a,o){let r=this.terminal.buffer.active,c=Math.min(i,o),l=Math.max(i,o);if(c===l){let p=Math.min(t,a),u=Math.max(t,a);this.terminal.select(p,r.viewportY+c,u-p+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+l)}getCellsText(t,i,a,o){let r=this.terminal.buffer.active,c=Math.min(i,o),l=Math.max(i,o),p=i<o?t:i>o?a:Math.min(t,a),u=i<o?a:i>o?t:Math.max(t,a),M=r.viewportY,$=[];for(let B=c;B<=l;B++){let D=r.getLine(M+B);if(!D){$.push("");continue}let H=B===c?p:0,_=B===l?u:this.terminal.cols-1,de="";for(let O=H;O<=_;O++){let pe=D.getCell(O);pe&&(de+=pe.getChars())}$.push(de.replace(/\s+$/,""))}return $.join(`
3
- `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function Ft(e){let t=e.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(t.width,i.width),height:Math.max(0,Math.min(t.height,i.height-Math.max(0,t.top)))}:t}function Wt(e,t,i){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let a=await jt.create(e,i.scrollback,i.theme),o,r=0,c,l=null,p=0,u="connecting",M=0,$=!1,B=[],D="",H=!1,_=1e3,de=location.protocol==="https:"?"wss:":"ws:",O=i.wsBase||"/ws",pe=de+"//"+location.host+O+"/"+encodeURIComponent(t),yt="/api/session/"+encodeURIComponent(t)+"/upload",wt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function bt(){return B.join("")+D}async function Lt(n,s=0){let d=n?a.viewportY():0,h=n?a.baseY():0,C=ee(bt());if(a.reset(),!C){n||a.scrollToBottom();return}await a.write(C),n?a.scrollToLine(h+d+s):a.scrollToBottom()}function K(n){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(n))}function Et(n){let s;try{s=JSON.parse(n)}catch{u==="live"&&(D+=n,a.write(ee(n)));return}if(s.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?K({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="attach_failed"&&typeof s.message=="string"){ue=!0,u="dead",a.reset(),a.write(`\r
4
- \x1B[31m`+ee(s.message)+`\x1B[0m\r
5
- `).then(()=>{me();try{o?.close()}catch{}});return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),_=1e3,q(),K({type:"resize",cols:a.cols,rows:a.rows}),x();return}if(s.type==="auth.failed"){if(s.permanentLock||s.retryAfterMs&&s.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(s.message);return}return}if(s.type==="snapshot"&&typeof s.data=="string"){let d=ee(s.data);B=[d],D="",M=typeof s.lines=="number"?s.lines:i.terminal.initialLines,u="live",a.reset(),a.write(d).then(()=>a.scrollToBottom());return}if(s.type==="window_changed"&&typeof s.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(s.activeIndex)&&(d.searchParams.set("window",String(s.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:s.windows}));return}if(s.type==="history"&&typeof s.data=="string"){$=!1,s.lines>0&&s.data&&(B.unshift(s.data),M+=s.lines,Lt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let d=ee(s.data);if(u==="connecting"){u="live",D=d,a.write(d);return}u==="live"&&(D+=d,a.write(d))}}let ue=!1;function Ye(){if(ue)return;let n=pe,s=localStorage.getItem("tmux-web-token");s&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(n),o.onopen=()=>{u="connecting",M=0,$=!1,B=[],D="",a.reset(),_=1e3},o.onmessage=d=>{typeof d.data=="string"&&Et(d.data)},o.onclose=()=>{H||(u="connecting",!ue&&setTimeout(()=>{_=Math.min(_*2,1e4),!H&&!ue&&Ye()},_))},o.onerror=()=>o?.close()}function fe(n){K({type:"input",data:n})}function q(){return a.fit()}function me(){e.classList.remove("terminal-pending")}function x(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),q(),r=requestAnimationFrame(()=>{r=0,q()&&me()}),c=setTimeout(()=>{q()&&me()},120)}function xt(){x(),setTimeout(x,50),setTimeout(x,150),setTimeout(x,300)}function je(n){return typeof n=="string"&&n.startsWith("image/")}function Fe(n){return n?.files?.length?n.files[0]??null:null}function kt(n){if(!n?.items)return null;for(let s=0;s<n.items.length;s++){let d=n.items[s];if(d?.kind==="file"&&je(d.type))return d.getAsFile()}return null}async function Ct(n){let s=new FormData,d=n instanceof File&&n.name?n.name:"upload";s.append("file",n,d);let h=localStorage.getItem("tmux-web-token"),C={};h&&(C.Authorization="Bearer "+h);let G=await fetch(yt,{method:"POST",body:s,headers:C});if(!G.ok){let Je="upload failed";try{let Qe=await G.json();Qe.error&&(Je=Qe.error)}catch{}throw new Error(Je)}let Xe=await G.json();if(!Xe.path)throw new Error("no path in response");return Xe.path}async function Se(n){try{let s=await Ct(n);fe(s)}catch{}}a.onData(n=>fe(n)),a.onResize(({cols:n,rows:s})=>K({type:"resize",cols:n,rows:s})),a.onScroll(()=>{u!=="live"||$||!a.isNearScrollbackTop()||($=!0,K({type:"load_history",before:M}))});let V=!1,X=!1,w=null,b=null,J=document.createElement("div");J.className="terminal-copy-banner",J.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',J.hidden=!0,e.appendChild(J);let A=document.createElement("div");A.className="terminal-copy-banner terminal-copy-toast",A.textContent="Copied",A.hidden=!0,e.appendChild(A);function We(n,s){let d=e.querySelector(".xterm-screen, canvas"),h=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),C=h.height>0&&a.rows>0?h.height/a.rows:18,G=h.width>0&&a.cols>0?h.width/a.cols:9;return{row:Math.max(0,Math.min(a.rows-1,Math.floor((s-h.top)/C))),col:Math.max(0,Math.min(a.cols-1,Math.floor((n-h.left)/G)))}}async function ve(n){if(!n)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(n);return}}catch{}let s=document.createElement("textarea");s.value=n,s.style.position="fixed",s.style.opacity="0",s.style.pointerEvents="none",document.body.appendChild(s),s.focus(),s.select();try{document.execCommand("copy")}catch{}s.remove()}let Q;function he(){A.hidden=!1,Q&&clearTimeout(Q),Q=setTimeout(()=>{A.hidden=!0},1e3)}function F(n){V=n,J.hidden=!n,e.classList.toggle("terminal-copy-mode-active",n),n&&a.focus()}function Ue(){F(!V)}function Tt(n){H||n.button!==0||X||(V||n.shiftKey)&&(n.preventDefault(),n.stopPropagation(),X=!0,w=We(n.clientX,n.clientY),b=w,a.selectCells(w.col,w.row,b.col,b.row),document.addEventListener("mousemove",De,!0),document.addEventListener("mouseup",He,!0))}function De(n){H||!X||!w||(n.preventDefault(),n.stopPropagation(),b=We(n.clientX,n.clientY),a.selectCells(w.col,w.row,b.col,b.row))}function He(n){if(H||!X||!w||!b)return;n.preventDefault(),n.stopPropagation(),document.removeEventListener("mousemove",De,!0),document.removeEventListener("mouseup",He,!0),X=!1;let s=a.getCellsText(w.col,w.row,b.col,b.row);w=null,b=null,a.clearSelection(),F(!1),s&&(A.textContent="\u5DF2\u590D\u5236",ve(s).then(he)),a.focus()}e.addEventListener("mousedown",Tt,!0),document.addEventListener("keydown",Mt,!0);function Mt(n){if(!(H||!a.isFocused()||n.defaultPrevented||n.isComposing)){if(V){if(n.key==="Escape"){n.preventDefault(),n.stopPropagation(),w=null,b=null,a.clearSelection(),F(!1);return}if(n.key==="Enter"||(n.ctrlKey||n.metaKey)&&n.code==="KeyC"){n.preventDefault(),n.stopPropagation(),ve(a.getScreenText()).then(he),F(!1);return}}if(n.altKey&&n.code==="KeyC"&&!n.ctrlKey&&!n.metaKey){if(n.preventDefault(),n.stopPropagation(),w&&b){let s=a.getCellsText(w.col,w.row,b.col,b.row);w=null,b=null,a.clearSelection(),F(!1),s&&(A.textContent="Copied",ve(s).then(he))}else{let s=a.getSelection();s?(a.clearSelection(),A.textContent="Copied",ve(s).then(he)):Ue()}return}if((n.ctrlKey||n.metaKey)&&n.code==="KeyV"){n.preventDefault(),n.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let d of s)for(let h of d.types)if(je(h)){let C=await d.getType(h);await Se(C);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&a.pasteText(Oe(s))}catch{}})();return}}}let ge=0;function Oe(n){return n.split(`\r
1
+ var Yt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ee(e){return e.replace(Yt,"")}var ge;function jt(){return ge||(document.querySelector("link[data-tmux-web-xterm-css]")?ge=Promise.resolve():(ge=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)}),ge))}var Ft=class yt{terminal;fitAddon;container;constructor(t,s,i){this.container=t,this.terminal=s,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,s,i){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),jt()]),c=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:s,convertEol:!1,theme:i});return new yt(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(s=>this.terminal.write(t,s))}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=Wt(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||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,s){let i=this.terminal.buffer.active,[o,r]=t<=s?[t,s]:[s,t],c=i.viewportY,l=[];for(let p=o;p<=r;p++){let u=i.getLine(c+p);l.push(u?u.translateToString(!0):"")}return l.join(`
2
+ `)}selectRows(t,s){let i=this.terminal.buffer.active,[o,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(i.viewportY+o,i.viewportY+r)}selectCells(t,s,i,o){let r=this.terminal.buffer.active,c=Math.min(s,o),l=Math.max(s,o);if(c===l){let p=Math.min(t,i),u=Math.max(t,i);this.terminal.select(p,r.viewportY+c,u-p+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+l)}getCellsText(t,s,i,o){let r=this.terminal.buffer.active,c=Math.min(s,o),l=Math.max(s,o),p=s<o?t:s>o?i:Math.min(t,i),u=s<o?i:s>o?t:Math.max(t,i),M=r.viewportY,R=[];for(let B=c;B<=l;B++){let D=r.getLine(M+B);if(!D){R.push("");continue}let H=B===c?p:0,z=B===l?u:this.terminal.cols-1,de="";for(let q=H;q<=z;q++){let pe=D.getCell(q);pe&&(de+=pe.getChars())}R.push(de.replace(/\s+$/,""))}return R.join(`
3
+ `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function Wt(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function Ut(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let i=await Ft.create(e,s.scrollback,s.theme),o,r=0,c,l=null,p=0,u="connecting",M=0,R=!1,B=[],D="",H=!1,z=1e3,de=location.protocol==="https:"?"wss:":"ws:",q=s.wsBase||"/ws",pe=de+"//"+location.host+q+"/"+encodeURIComponent(t),gt="/api/session/"+encodeURIComponent(t)+"/upload",wt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function bt(){return B.join("")+D}async function Lt(n,a=0){let d=n?i.viewportY():0,h=n?i.baseY():0,C=ee(bt());if(i.reset(),!C){n||i.scrollToBottom();return}await i.write(C),n?i.scrollToLine(h+d+a):i.scrollToBottom()}function _(n){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(n))}function Et(n){let a;try{a=JSON.parse(n)}catch{u==="live"&&(D+=n,i.write(ee(n)));return}if(a.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?_({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="attach_failed"&&typeof a.message=="string"){ue=!0,u="dead",i.reset(),i.write(`\r
4
+ \x1B[31m`+ee(a.message)+`\x1B[0m\r
5
+ `).then(()=>{me();try{o?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),z=1e3,Y(),_({type:"resize",cols:i.cols,rows:i.rows}),x();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=ee(a.data);B=[d],D="",M=typeof a.lines=="number"?a.lines:s.terminal.initialLines,u="live",i.reset(),Y(),_({type:"resize",cols:i.cols,rows:i.rows}),i.write(d).then(()=>i.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"){R=!1,a.lines>0&&a.data&&(B.unshift(a.data),M+=a.lines,Lt(!0,a.lines));return}if(a.type==="data"&&typeof a.data=="string"){let d=ee(a.data);if(u==="connecting"){u="live",D=d,i.write(d);return}u==="live"&&(D+=d,i.write(d))}}let ue=!1;function Ye(){if(ue)return;let n=pe,a=localStorage.getItem("tmux-web-token");a&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),n+=(n.includes("?")?"&":"?")+"cols="+i.cols+"&rows="+i.rows,o=new WebSocket(n),o.onopen=()=>{u="connecting",M=0,R=!1,B=[],D="",i.reset(),z=1e3},o.onmessage=d=>{typeof d.data=="string"&&Et(d.data)},o.onclose=()=>{H||(u="connecting",!ue&&setTimeout(()=>{z=Math.min(z*2,1e4),!H&&!ue&&Ye()},z))},o.onerror=()=>o?.close()}function fe(n){_({type:"input",data:n})}function Y(){return i.fit()}function me(){e.classList.remove("terminal-pending")}function x(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),Y(),r=requestAnimationFrame(()=>{r=0,Y()&&me()}),c=setTimeout(()=>{Y()&&me()},120)}function xt(){x(),setTimeout(x,50),setTimeout(x,150),setTimeout(x,300)}function je(n){return typeof n=="string"&&n.startsWith("image/")}function Fe(n){return n?.files?.length?n.files[0]??null:null}function kt(n){if(!n?.items)return null;for(let a=0;a<n.items.length;a++){let d=n.items[a];if(d?.kind==="file"&&je(d.type))return d.getAsFile()}return null}async function Ct(n){let a=new FormData,d=n instanceof File&&n.name?n.name:"upload";a.append("file",n,d);let h=localStorage.getItem("tmux-web-token"),C={};h&&(C.Authorization="Bearer "+h);let G=await fetch(gt,{method:"POST",body:a,headers:C});if(!G.ok){let Je="upload failed";try{let Qe=await G.json();Qe.error&&(Je=Qe.error)}catch{}throw new Error(Je)}let Ve=await G.json();if(!Ve.path)throw new Error("no path in response");return Ve.path}async function Se(n){try{let a=await Ct(n);fe(a)}catch{}}i.onData(n=>fe(n)),i.onResize(({cols:n,rows:a})=>_({type:"resize",cols:n,rows:a})),i.onScroll(()=>{u!=="live"||R||!i.isNearScrollbackTop()||(R=!0,_({type:"load_history",before:M}))});let X=!1,V=!1,w=null,b=null,J=document.createElement("div");J.className="terminal-copy-banner",J.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',J.hidden=!0,e.appendChild(J);let A=document.createElement("div");A.className="terminal-copy-banner terminal-copy-toast",A.textContent="Copied",A.hidden=!0,e.appendChild(A);function We(n,a){let d=e.querySelector(".xterm-screen, canvas"),h=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),C=h.height>0&&i.rows>0?h.height/i.rows:18,G=h.width>0&&i.cols>0?h.width/i.cols:9;return{row:Math.max(0,Math.min(i.rows-1,Math.floor((a-h.top)/C))),col:Math.max(0,Math.min(i.cols-1,Math.floor((n-h.left)/G)))}}async function ve(n){if(!n)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(n);return}}catch{}let a=document.createElement("textarea");a.value=n,a.style.position="fixed",a.style.opacity="0",a.style.pointerEvents="none",document.body.appendChild(a),a.focus(),a.select();try{document.execCommand("copy")}catch{}a.remove()}let Q;function he(){A.hidden=!1,Q&&clearTimeout(Q),Q=setTimeout(()=>{A.hidden=!0},1e3)}function U(n){X=n,J.hidden=!n,e.classList.toggle("terminal-copy-mode-active",n),n&&i.focus()}function Ue(){U(!X)}function Tt(n){H||n.button!==0||V||(X||n.shiftKey)&&(n.preventDefault(),n.stopPropagation(),V=!0,w=We(n.clientX,n.clientY),b=w,i.selectCells(w.col,w.row,b.col,b.row),document.addEventListener("mousemove",De,!0),document.addEventListener("mouseup",He,!0))}function De(n){H||!V||!w||(n.preventDefault(),n.stopPropagation(),b=We(n.clientX,n.clientY),i.selectCells(w.col,w.row,b.col,b.row))}function He(n){if(H||!V||!w||!b)return;n.preventDefault(),n.stopPropagation(),document.removeEventListener("mousemove",De,!0),document.removeEventListener("mouseup",He,!0),V=!1;let a=i.getCellsText(w.col,w.row,b.col,b.row);w=null,b=null,i.clearSelection(),U(!1),a&&(A.textContent="\u5DF2\u590D\u5236",ve(a).then(he)),i.focus()}e.addEventListener("mousedown",Tt,!0),document.addEventListener("keydown",Mt,!0);function Mt(n){if(!(H||!i.isFocused()||n.defaultPrevented||n.isComposing)){if(X){if(n.key==="Escape"){n.preventDefault(),n.stopPropagation(),w=null,b=null,i.clearSelection(),U(!1);return}if(n.key==="Enter"||(n.ctrlKey||n.metaKey)&&n.code==="KeyC"){n.preventDefault(),n.stopPropagation(),ve(i.getScreenText()).then(he),U(!1);return}}if(n.altKey&&n.code==="KeyC"&&!n.ctrlKey&&!n.metaKey){if(n.preventDefault(),n.stopPropagation(),w&&b){let a=i.getCellsText(w.col,w.row,b.col,b.row);w=null,b=null,i.clearSelection(),U(!1),a&&(A.textContent="Copied",ve(a).then(he))}else{let a=i.getSelection();a?(i.clearSelection(),A.textContent="Copied",ve(a).then(he)):Ue()}return}if((n.ctrlKey||n.metaKey)&&n.code==="KeyV"){n.preventDefault(),n.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let a=await navigator.clipboard.read();for(let d of a)for(let h of d.types)if(je(h)){let C=await d.getType(h);await Se(C);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&i.pasteText(Oe(a))}catch{}})();return}}}let ye=0;function Oe(n){return n.split(`\r
6
6
  `).join(`
7
7
  `).split(`
8
- `).join("\r")}async function It(n){let s=kt(n.clipboardData);if(s){n.preventDefault(),n.stopPropagation();let C=Date.now();if(C-ge<50)return;ge=C,await Se(s);return}let d=n.clipboardData?.getData("text/plain");if(!d)return;n.preventDefault(),n.stopPropagation();let h=Date.now();h-ge<50||(ge=h,a.pasteText(Oe(d)))}let Z=0;function Bt(n){n.preventDefault(),Z++,Fe(n.dataTransfer)&&e.classList.add("terminal-drag-over")}function St(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Dt(n){n.preventDefault(),Z--,Z<=0&&(Z=0,e.classList.remove("terminal-drag-over"))}async function Ht(n){n.preventDefault(),Z=0,e.classList.remove("terminal-drag-over");let s=Fe(n.dataTransfer);s&&await Se(s)}function Ke(n){!wt||n.key!=="Escape"||V||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||a.isFocused()&&(n.preventDefault(),n.stopPropagation(),fe("\x1B"))}e.addEventListener("paste",It),e.addEventListener("dragenter",Bt),e.addEventListener("dragover",St),e.addEventListener("dragleave",Dt),e.addEventListener("drop",Ht),document.addEventListener("keydown",Ke,!0);function At(n){if(n.touches.length!==1){l=null;return}let s=n.touches[0];s&&(l={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},n.stopPropagation())}function Rt(n){if(!l||n.touches.length!==1)return;let s=n.touches[0];if(!s)return;let d=s.clientY-l.startY,h=s.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(h))return;l.scrolling=!0,p=Date.now()+500}n.preventDefault(),n.stopPropagation(),_t(-(s.clientY-l.lastY),s.clientX,s.clientY),l.lastX=s.clientX,l.lastY=s.clientY}function $t(n){if(!l)return;let s=l.scrolling;l=null,n.stopPropagation(),s?(p=Date.now()+500,n.preventDefault()):a.focus()}function Pt(n){l=null,n.stopPropagation()}function Nt(n){n.pointerType==="touch"&&Date.now()<p&&(n.preventDefault(),n.stopPropagation())}function _t(n,s,d){(e.querySelector(".xterm-screen")??e).dispatchEvent(new WheelEvent("wheel",{deltaY:n,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:d,bubbles:!0,cancelable:!0}))}e.addEventListener("touchstart",At,{passive:!0,capture:!0}),e.addEventListener("touchmove",Rt,{passive:!1,capture:!0}),e.addEventListener("touchend",$t,{passive:!1,capture:!0}),e.addEventListener("touchcancel",Pt,{passive:!0,capture:!0}),e.addEventListener("pointerup",Nt,!0);function qe(){x()}window.addEventListener("resize",qe),window.visualViewport?.addEventListener("resize",x),window.visualViewport?.addEventListener("scroll",x);let Ve=new ResizeObserver(()=>x());return Ve.observe(e),document.fonts?.ready.then(()=>{x(),setTimeout(x,50),setTimeout(x,200)}),e.addEventListener("focusin",xt,!0),requestAnimationFrame(()=>{q()&&me(),Ye(),x()}),{destroy(){if(H=!0,F(!1),Q&&clearTimeout(Q),document.removeEventListener("mousemove",De,!0),document.removeEventListener("mouseup",He,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",qe),Ve.disconnect(),document.removeEventListener("keydown",Ke,!0)},focus(){a.focus()},sendInput(n){fe(n)},toggleCopyMode(){Ue()}}})()}var te=window.__TMUX_WEB_SHELL__,v=null,y=null,Ze=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,E=Ze;window.__tmuxWebScopeAgent=Ze;var Ae="sessions",P="",we="",be="",Ge="";function k(e){return E?"/a/"+encodeURIComponent(E)+e:e}var m=document.getElementById("sidebar-content"),z=document.getElementById("main-placeholder"),T=document.getElementById("terminal-container"),ne=document.getElementById("file-editor"),Ut=document.getElementById("fe-path"),I=document.getElementById("fe-status"),ie=document.getElementById("fe-content"),Y=document.getElementById("fe-save"),Ot=document.getElementById("fe-delete"),Kt=document.getElementById("fe-back"),et=document.getElementById("fe-new-name"),qt=document.getElementById("fe-new-btn"),Re=document.getElementById("settings-backdrop"),tt=document.getElementById("settings-popover"),nt=document.querySelector(".sidebar");function Vt(){return window.matchMedia("(max-width: 640px)").matches}function $e(){Vt()&&nt.classList.add("collapsed")}function it(){nt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{it(),at("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{it(),at("files")});function at(e){Ae=e,document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(Pe="",W()):sn()}var Pe="";async function W(){if(Ae!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),i=JSON.stringify({recent:t.recent,agents:t.agents});if(i===Pe)return;Pe=i,Xt(t)}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 Xt(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],i=document.createElement("div");i.className="sidebar-section-label"+(E?" clickable":""),i.textContent=E?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",E&&(i.title="Back to hub",i.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(i);for(let o of t){let r=document.createElement("div");r.className="session-item"+(o.name===y&&!E?" active":""),r.setAttribute("data-session",o.name),r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${o.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>
8
+ `).join("\r")}async function It(n){let a=kt(n.clipboardData);if(a){n.preventDefault(),n.stopPropagation();let C=Date.now();if(C-ye<50)return;ye=C,await Se(a);return}let d=n.clipboardData?.getData("text/plain");if(!d)return;n.preventDefault(),n.stopPropagation();let h=Date.now();h-ye<50||(ye=h,i.pasteText(Oe(d)))}let Z=0;function Bt(n){n.preventDefault(),Z++,Fe(n.dataTransfer)&&e.classList.add("terminal-drag-over")}function St(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Dt(n){n.preventDefault(),Z--,Z<=0&&(Z=0,e.classList.remove("terminal-drag-over"))}async function Ht(n){n.preventDefault(),Z=0,e.classList.remove("terminal-drag-over");let a=Fe(n.dataTransfer);a&&await Se(a)}function Ke(n){!wt||n.key!=="Escape"||X||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||i.isFocused()&&(n.preventDefault(),n.stopPropagation(),fe("\x1B"))}e.addEventListener("paste",It),e.addEventListener("dragenter",Bt),e.addEventListener("dragover",St),e.addEventListener("dragleave",Dt),e.addEventListener("drop",Ht),document.addEventListener("keydown",Ke,!0);function At(n){if(n.touches.length!==1){l=null;return}let a=n.touches[0];a&&(l={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,scrolling:!1},n.stopPropagation())}function Pt(n){if(!l||n.touches.length!==1)return;let a=n.touches[0];if(!a)return;let d=a.clientY-l.startY,h=a.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(h))return;l.scrolling=!0,p=Date.now()+500}n.preventDefault(),n.stopPropagation(),zt(-(a.clientY-l.lastY),a.clientX,a.clientY),l.lastX=a.clientX,l.lastY=a.clientY}function Rt(n){if(!l)return;let a=l.scrolling;l=null,n.stopPropagation(),a?(p=Date.now()+500,n.preventDefault()):i.focus()}function $t(n){l=null,n.stopPropagation()}function Nt(n){n.pointerType==="touch"&&Date.now()<p&&(n.preventDefault(),n.stopPropagation())}function zt(n,a,d){(e.querySelector(".xterm-screen")??e).dispatchEvent(new WheelEvent("wheel",{deltaY:n,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:a,clientY:d,bubbles:!0,cancelable:!0}))}e.addEventListener("touchstart",At,{passive:!0,capture:!0}),e.addEventListener("touchmove",Pt,{passive:!1,capture:!0}),e.addEventListener("touchend",Rt,{passive:!1,capture:!0}),e.addEventListener("touchcancel",$t,{passive:!0,capture:!0}),e.addEventListener("pointerup",Nt,!0);function qe(){x()}window.addEventListener("resize",qe),window.visualViewport?.addEventListener("resize",x),window.visualViewport?.addEventListener("scroll",x);let Xe=new ResizeObserver(()=>x());Xe.observe(e),document.fonts?.ready.then(()=>{x(),setTimeout(x,50),setTimeout(x,200)}),e.addEventListener("focusin",xt,!0);async function _t(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(n=>setTimeout(n,1e3))])}catch{}}return requestAnimationFrame(()=>{Y()&&me(),(async()=>(await _t(),Y()&&_({type:"resize",cols:i.cols,rows:i.rows}),Ye(),x()))()}),{destroy(){if(H=!0,U(!1),Q&&clearTimeout(Q),document.removeEventListener("mousemove",De,!0),document.removeEventListener("mouseup",He,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",qe),Xe.disconnect(),document.removeEventListener("keydown",Ke,!0)},focus(){i.focus()},sendInput(n){fe(n)},toggleCopyMode(){Ue()}}})()}var te=window.__TMUX_WEB_SHELL__,v=null,g=null,Ze=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,E=Ze;window.__tmuxWebScopeAgent=Ze;var Ae="sessions",$="",we="",be="",Ge="";function k(e){return E?"/a/"+encodeURIComponent(E)+e:e}var m=document.getElementById("sidebar-content"),j=document.getElementById("main-placeholder"),T=document.getElementById("terminal-container"),ne=document.getElementById("file-editor"),Ot=document.getElementById("fe-path"),I=document.getElementById("fe-status"),ie=document.getElementById("fe-content"),F=document.getElementById("fe-save"),Kt=document.getElementById("fe-delete"),qt=document.getElementById("fe-back"),et=document.getElementById("fe-new-name"),Xt=document.getElementById("fe-new-btn"),Pe=document.getElementById("settings-backdrop"),tt=document.getElementById("settings-popover"),nt=document.querySelector(".sidebar");function Vt(){return window.matchMedia("(max-width: 640px)").matches}function Re(){Vt()&&nt.classList.add("collapsed")}function it(){nt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{it(),st("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{it(),st("files")});function st(e){Ae=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?($e="",O()):on()}var $e="";async function O(){if(Ae!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===$e)return;$e=s,Jt(t)}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 Jt(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(E?" clickable":""),s.textContent=E?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",E&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(s);for(let o of t){let r=document.createElement("div");r.className="session-item"+(o.name===g&&!E?" active":""),r.setAttribute("data-session",o.name),r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${o.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>
9
9
  <span>${f(o.name)}</span>
10
10
  <button class="session-edit-btn" data-session="${f(o.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>
11
- `,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||ae(o.name)}),m.appendChild(r);let c=r.querySelector(".session-edit-btn");c&&c.addEventListener("click",l=>{l.stopPropagation(),dn(o.name,c)})}let a=e.agents||[];for(let o of a){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===E,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!r&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!r&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),m.appendChild(c);for(let l of o.sessions){let p=document.createElement("div");p.className="session-item"+(l.name===y&&E===o.agentId?" active":"")+(o.online?"":" session-offline"),p.setAttribute("data-session",l.name),p.setAttribute("data-agent",o.agentId),p.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${l.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>
12
- <span>${f(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&p.addEventListener("click",u=>{u.target.closest(".session-edit-btn")||ae(l.name,o.agentId)}),m.appendChild(p)}}}function f(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ae(e,t){if(!(y===e&&v&&E===(t||null))){ne.style.display="none",z.style.display="none",T.style.display="",T.classList.add("terminal-pending"),$e(),v&&(Ie(),v.destroy(),v=null),y=e,E=t||null,window.__tmuxWebScopeAgent=E;for(let i of m.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===E);try{v=await Wt(T,e,{terminal:te.terminal,scrollback:te.scrollback,theme:te.theme,wsBase:E?"/ws/a/"+encodeURIComponent(E):te.wsBase||"/ws"}),mt()}catch(i){console.error("[shell] terminal init failed:",i);let a=i&&typeof i=="object"&&"message"in i?i.message:String(i);T.textContent="Failed to open terminal for "+e+": "+a,T.classList.remove("terminal-pending")}}}var Le=[],g=null,st=!1;function Jt(e){if(!g||!g.files.length)return null;let t=g.repoRoot;if(!t||!e.startsWith(t))return null;let i=e.slice(t.length+1);for(let a of g.files)if(a.path===i)return a.status;return null}async function Qt(e){st=!0;try{let t=await fetch(k("/api/git/status?path="+encodeURIComponent(e)));t.ok?g=await t.json():g=null}catch{g=null}finally{st=!1}}function Zt(){if(!g||!g.branch)return"";let{branch:e,linesAdded:t,linesRemoved:i}=g,a=`<div class="sidebar-git"><div class="git-branch-badge">
11
+ `,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||se(o.name)}),m.appendChild(r);let c=r.querySelector(".session-edit-btn");c&&c.addEventListener("click",l=>{l.stopPropagation(),pn(o.name,c)})}let i=e.agents||[];for(let o of i){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===E,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!r&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!r&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),m.appendChild(c);for(let l of o.sessions){let p=document.createElement("div");p.className="session-item"+(l.name===g&&E===o.agentId?" active":"")+(o.online?"":" session-offline"),p.setAttribute("data-session",l.name),p.setAttribute("data-agent",o.agentId),p.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${l.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>
12
+ <span>${f(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&p.addEventListener("click",u=>{u.target.closest(".session-edit-btn")||se(l.name,o.agentId)}),m.appendChild(p)}}}function f(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function se(e,t){if(!(g===e&&v&&E===(t||null))){ne.style.display="none",j.style.display="none",T.style.display="",T.classList.add("terminal-pending"),Re(),v&&(Ie(),v.destroy(),v=null),g=e,E=t||null,window.__tmuxWebScopeAgent=E;for(let s of m.querySelectorAll(".session-item"))s.classList.toggle("active",s.getAttribute("data-session")===e&&(s.getAttribute("data-agent")||null)===E);try{v=await Ut(T,e,{terminal:te.terminal,scrollback:te.scrollback,theme:te.theme,wsBase:E?"/ws/a/"+encodeURIComponent(E):te.wsBase||"/ws"}),mt()}catch(s){console.error("[shell] terminal init failed:",s);let i=s&&typeof s=="object"&&"message"in s?s.message:String(s);T.textContent="Failed to open terminal for "+e+": "+i,T.classList.remove("terminal-pending")}}}var Le=[],y=null,at=!1;function Qt(e){if(!y||!y.files.length)return null;let t=y.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let i of y.files)if(i.path===s)return i.status;return null}async function Zt(e){at=!0;try{let t=await fetch(k("/api/git/status?path="+encodeURIComponent(e)));t.ok?y=await t.json():y=null}catch{y=null}finally{at=!1}}function Gt(){if(!y||!y.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=y,i=`<div class="sidebar-git"><div class="git-branch-badge">
13
13
  <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>
14
- ${f(e)}</div>`;return(t>0||i>0)&&(a+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${i}</span></div>`),a+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&U&&g&&(e.stopPropagation(),dt())});var se=document.getElementById("header-git"),ot=document.getElementById("git-diff-view"),lt=document.getElementById("gd-path"),Ee=document.getElementById("gd-status"),xe=document.getElementById("gd-content"),Gt=document.getElementById("gd-back"),U=null;function oe(e){if(!e||!e.branch){se.style.display="none",U=null;return}U=e.repoRoot;let{branch:t,linesAdded:i,linesRemoved:a}=e,o=`<span class="branch">${f(t)}</span>`;(i>0||a>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${i}</span> <span class="diff-del">-${a}</span>`),se.innerHTML=o,se.style.display="block"}se.addEventListener("click",e=>{U&&g&&(e.stopPropagation(),dt())});var L=document.getElementById("git-popover"),ke=document.getElementById("git-popover-backdrop");function Ne(){let e=se.getBoundingClientRect();L.style.top=e.bottom+4+"px",L.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function rt(){let e=U,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Te(),e){Ae="files",document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let i=document.getElementById("mode-files");i&&i.classList.add("active"),N(e)}})}var ct="";function dt(){if(!g)return;let e=g.files||[];if(!e.length)L.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
15
- <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 i=0;i<e.length;i++){let a=e[i],o=a.additions||0,r=a.deletions||0,c="";switch(a.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="${i}">
16
- ${c}<span class="file-path">${f(a.path)}</span>
14
+ ${f(e)}</div>`;return(t>0||s>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),i+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&K&&y&&(e.stopPropagation(),dt())});var ae=document.getElementById("header-git"),ot=document.getElementById("git-diff-view"),lt=document.getElementById("gd-path"),Ee=document.getElementById("gd-status"),xe=document.getElementById("gd-content"),en=document.getElementById("gd-back"),K=null;function oe(e){if(!e||!e.branch){ae.style.display="none",K=null;return}K=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:i}=e,o=`<span class="branch">${f(t)}</span>`;(s>0||i>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${i}</span>`),ae.innerHTML=o,ae.style.display="block"}ae.addEventListener("click",e=>{K&&y&&(e.stopPropagation(),dt())});var L=document.getElementById("git-popover"),ke=document.getElementById("git-popover-backdrop");function Ne(){let e=ae.getBoundingClientRect();L.style.top=e.bottom+4+"px",L.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function rt(){let e=K,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Te(),e){Ae="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),N(e)}})}var ct="";function dt(){if(!y)return;let e=y.files||[];if(!e.length)L.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
15
+ <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 s=0;s<e.length;s++){let i=e[s],o=i.additions||0,r=i.deletions||0,c="";switch(i.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="${s}">
16
+ ${c}<span class="file-path">${f(i.path)}</span>
17
17
  <span class="file-add">${o>0?"+"+o:""}</span>
18
18
  <span class="file-del">${r>0?"-"+r:""}</span>
19
- </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ct=t,L.innerHTML=t,L.querySelectorAll(".git-popover-item").forEach(i=>{let a=parseInt(i.dataset.idx||"0",10),o=e[a];!o||o.status==="D"||(i.classList.add("clickable"),i.style.cursor="pointer",i.addEventListener("click",()=>ut(o.path)))})}Ne(),L.classList.add("open"),ke.classList.add("open"),rt()}function en(){return window.innerWidth>=1024}function pt(e,t){let i=(e+(t&&e?`
19
+ </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ct=t,L.innerHTML=t,L.querySelectorAll(".git-popover-item").forEach(s=>{let i=parseInt(s.dataset.idx||"0",10),o=e[i];!o||o.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>ut(o.path)))})}Ne(),L.classList.add("open"),ke.classList.add("open"),rt()}function tn(){return window.innerWidth>=1024}function pt(e,t){let s=(e+(t&&e?`
20
20
  `:"")+t).split(`
21
- `),a="",o=0,r=0;for(let c of i){let l=c;if(l.startsWith("@@")){let p=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,r=parseInt(p[2],10)-1),a+=`<div class="diff-line hunk"><span class="ln-body">${f(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")&&(a+=`<div class="diff-line header"><span class="ln-body">${f(l)}</span></div>`);continue}if(l.startsWith("+"))r++,a+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${f(l)}</span></div>`;else if(l.startsWith("-"))o++,a+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${f(l)}</span></div>`;else if(l.startsWith("\\"))a+=`<div class="diff-line"><span class="ln-body">${f(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,a+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${f(l)}</span></div>`}}return{html:a,lineCount:i.length}}var Ce=null;async function ut(e){let t=U;if(t)if(Te(),en()){T.style.display="none",v&&(Ie(),v.destroy(),v=null,Ce=y,y=null),oe(null),ne.style.display="none",z.style.display="none",ot.style.display="flex",lt.textContent=e,Ee.textContent="Loading\u2026",xe.innerHTML="";try{let i=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!i.ok)throw new Error(await i.text());let a=await i.json();nn(e,a.diff,a.stagedDiff)}catch(i){let a=i instanceof Error?i.message:String(i);Ee.textContent="Error",xe.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${f(a)}</div>`}}else{L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
+ `),i="",o=0,r=0;for(let c of s){let l=c;if(l.startsWith("@@")){let p=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,r=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${f(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">${f(l)}</span></div>`);continue}if(l.startsWith("+"))r++,i+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${f(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${f(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${f(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${f(l)}</span></div>`}}return{html:i,lineCount:s.length}}var Ce=null;async function ut(e){let t=K;if(t)if(Te(),tn()){T.style.display="none",v&&(Ie(),v.destroy(),v=null,Ce=g,g=null),oe(null),ne.style.display="none",j.style.display="none",ot.style.display="flex",lt.textContent=e,Ee.textContent="Loading\u2026",xe.innerHTML="";try{let s=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let i=await s.json();sn(e,i.diff,i.stagedDiff)}catch(s){let i=s instanceof Error?s.message:String(s);Ee.textContent="Error",xe.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${f(i)}</div>`}}else{L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
22
22
  <button class="diff-back">\u2190 Back</button>
23
23
  <span class="diff-filename">${f(e)}</span>
24
24
  <span class="diff-loading">Loading\u2026</span>
25
- </div></div>`,Ne(),L.classList.add("open"),ke.classList.add("open");try{let i=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!i.ok)throw new Error(await i.text());let a=await i.json();tn(e,a.diff,a.stagedDiff)}catch(i){let a=i instanceof Error?i.message:String(i);L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
25
+ </div></div>`,Ne(),L.classList.add("open"),ke.classList.add("open");try{let s=await fetch(k("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let i=await s.json();nn(e,i.diff,i.stagedDiff)}catch(s){let i=s instanceof Error?s.message:String(s);L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
26
26
  <button class="diff-back">\u2190 Back</button>
27
27
  <span class="diff-filename">${f(e)}</span>
28
- </div><div class="diff-empty">Error loading diff: ${f(a)}</div></div>`}}}function tn(e,t,i){if(!t&&!i){L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
28
+ </div><div class="diff-empty">Error loading diff: ${f(i)}</div></div>`}}}function nn(e,t,s){if(!t&&!s){L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
29
29
  <button class="diff-back">\u2190 Back</button>
30
30
  <span class="diff-filename">${f(e)}</span>
31
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:a,lineCount:o}=pt(t,i);L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
31
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:i,lineCount:o}=pt(t,s);L.innerHTML=`<div class="git-diff-view"><div class="diff-header">
32
32
  <button class="diff-back">\u2190 Back</button>
33
33
  <span class="diff-filename">${f(e)}</span>
34
34
  <span class="diff-filesize">${o} lines</span>
35
- </div><div class="diff-content">${a}</div></div>`,L.querySelector(".diff-back")?.addEventListener("click",()=>{L.innerHTML=ct,Ne(),g&&L.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=g.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>ut(l.path)))}),rt()})}function nn(e,t,i){if(!t&&!i){Ee.textContent="No diff",xe.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:a,lineCount:o}=pt(t,i);lt.textContent=e,Ee.textContent=o+" lines",xe.innerHTML=a}function an(){ot.style.display="none",Ce?(T.style.display="",T.classList.add("terminal-pending"),ae(Ce),Ce=null):y?(T.style.display="",T.classList.add("terminal-pending"),mt()):z.style.display="flex"}Gt.addEventListener("click",an);function Te(){L.classList.remove("open"),ke.classList.remove("open")}ke.addEventListener("click",Te),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Te(),Be())});async function ft(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let i=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(i.ok){let a=await i.json();g=a,oe(a);return}}}catch{}g=null,oe(null)}var Me=null;function mt(){Ie(),y&&(ft(y),Me=setInterval(()=>{y&&ft(y)},3e4))}function Ie(){Me&&(clearInterval(Me),Me=null)}function sn(){if(Le=te.fsRoots||[],Le.length===0){if(y){on(y);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 Le){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> ${f(e)}`,t.addEventListener("click",()=>N(e)),m.appendChild(t)}}async function on(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(k("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?N(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 N(e){P=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Qt(e);try{let i=await(await fetch(k("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",oe(g),m.innerHTML+=Zt();let a=ln(e);if(a&&!Le.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>N(a)),m.appendChild(r)}if(i.dirs)for(let r of i.dirs){let c=r.split("/").pop()||r.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> ${f(c)}`,l.addEventListener("click",()=>N(r)),m.appendChild(l)}else m.innerHTML="";let o=i.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=Jt(r),p="",u="";if(l)switch(l){case"M":p="git-mod",u='<span class="git-file-status M">M</span>';break;case"A":p="git-add",u='<span class="git-file-status A">A</span>';break;case"D":p="git-del",u='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",u='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",u='<span class="git-file-status R">R</span>';break}let M=document.createElement("div");M.className="file-tree-item file"+(p?" "+p:""),M.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${u}${f(c)}`,M.addEventListener("click",()=>rn(r)),m.appendChild(M)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ln(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function rn(e){we=e,T.style.display="none",v&&(Ie(),v.destroy(),v=null,y=null),oe(null),$e(),z.style.display="none",ne.style.display="flex",Ut.textContent=e,I.textContent="Loading...",Y.style.display="none";try{let t=await fetch(k("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let a=await t.json();I.textContent=a.error||"Failed to load",ie.value="";return}let i=await t.json();ie.value=i.content,be=i.content,Ge=i.content,I.textContent=i.size+" bytes",Y.style.display=""}catch{I.textContent="Failed to load",ie.value=""}}ie.addEventListener("input",()=>{be=ie.value}),Y.addEventListener("click",async()=>{Y.textContent="Saving...",Y.disabled=!0;try{let e=await fetch(k("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we,content:be})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Save failed";return}Ge=be,I.textContent="Saved"}catch{I.textContent="Save failed"}finally{Y.disabled=!1,Y.textContent="Save"}}),Ot.addEventListener("click",async()=>{if(confirm("Delete "+we+"?"))try{let e=await fetch(k("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Delete failed";return}ne.style.display="none",z.style.display="flex",P&&N(P)}catch{I.textContent="Delete failed"}}),Kt.addEventListener("click",()=>{ne.style.display="none",z.style.display="flex",P&&N(P)}),qt.addEventListener("click",async()=>{let e=et.value.trim();if(!e)return;let t=P+"/"+e;try{let i=await fetch(k("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!i.ok){let a=await i.json();I.textContent=a.error||"Create failed";return}et.value="",P&&N(P)}catch{I.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=tt.classList.toggle("open");Re.classList.toggle("open",e)}),Re.addEventListener("click",()=>{tt.classList.remove("open"),Re.classList.remove("open")});var j="",le=document.createElement("div");le.className="session-popover-backdrop",document.body.appendChild(le);var R=document.createElement("div");R.id="session-popover",R.className="session-popover",R.style.display="none",R.innerHTML=`<div class="sp-field">
35
+ </div><div class="diff-content">${i}</div></div>`,L.querySelector(".diff-back")?.addEventListener("click",()=>{L.innerHTML=ct,Ne(),y&&L.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=y.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>ut(l.path)))}),rt()})}function sn(e,t,s){if(!t&&!s){Ee.textContent="No diff",xe.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:i,lineCount:o}=pt(t,s);lt.textContent=e,Ee.textContent=o+" lines",xe.innerHTML=i}function an(){ot.style.display="none",Ce?(T.style.display="",T.classList.add("terminal-pending"),se(Ce),Ce=null):g?(T.style.display="",T.classList.add("terminal-pending"),mt()):j.style.display="flex"}en.addEventListener("click",an);function Te(){L.classList.remove("open"),ke.classList.remove("open")}ke.addEventListener("click",Te),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Te(),Be())});async function ft(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let i=await s.json();y=i,oe(i);return}}}catch{}y=null,oe(null)}var Me=null;function mt(){Ie(),g&&(ft(g),Me=setInterval(()=>{g&&ft(g)},3e4))}function Ie(){Me&&(clearInterval(Me),Me=null)}function on(){if(Le=te.fsRoots||[],Le.length===0){if(g){ln(g);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 Le){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> ${f(e)}`,t.addEventListener("click",()=>N(e)),m.appendChild(t)}}async function ln(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(k("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?N(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 N(e){$=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Zt(e);try{let s=await(await fetch(k("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",oe(y),m.innerHTML+=Gt();let i=rn(e);if(i&&!Le.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>N(i)),m.appendChild(r)}if(s.dirs)for(let r of s.dirs){let c=r.split("/").pop()||r.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> ${f(c)}`,l.addEventListener("click",()=>N(r)),m.appendChild(l)}else m.innerHTML="";let o=s.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=Qt(r),p="",u="";if(l)switch(l){case"M":p="git-mod",u='<span class="git-file-status M">M</span>';break;case"A":p="git-add",u='<span class="git-file-status A">A</span>';break;case"D":p="git-del",u='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",u='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",u='<span class="git-file-status R">R</span>';break}let M=document.createElement("div");M.className="file-tree-item file"+(p?" "+p:""),M.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${u}${f(c)}`,M.addEventListener("click",()=>cn(r)),m.appendChild(M)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function rn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function cn(e){we=e,T.style.display="none",v&&(Ie(),v.destroy(),v=null,g=null),oe(null),Re(),j.style.display="none",ne.style.display="flex",Ot.textContent=e,I.textContent="Loading...",F.style.display="none";try{let t=await fetch(k("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let i=await t.json();I.textContent=i.error||"Failed to load",ie.value="";return}let s=await t.json();ie.value=s.content,be=s.content,Ge=s.content,I.textContent=s.size+" bytes",F.style.display=""}catch{I.textContent="Failed to load",ie.value=""}}ie.addEventListener("input",()=>{be=ie.value}),F.addEventListener("click",async()=>{F.textContent="Saving...",F.disabled=!0;try{let e=await fetch(k("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we,content:be})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Save failed";return}Ge=be,I.textContent="Saved"}catch{I.textContent="Save failed"}finally{F.disabled=!1,F.textContent="Save"}}),Kt.addEventListener("click",async()=>{if(confirm("Delete "+we+"?"))try{let e=await fetch(k("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:we})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Delete failed";return}ne.style.display="none",j.style.display="flex",$&&N($)}catch{I.textContent="Delete failed"}}),qt.addEventListener("click",()=>{ne.style.display="none",j.style.display="flex",$&&N($)}),Xt.addEventListener("click",async()=>{let e=et.value.trim();if(!e)return;let t=$+"/"+e;try{let s=await fetch(k("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let i=await s.json();I.textContent=i.error||"Create failed";return}et.value="",$&&N($)}catch{I.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=tt.classList.toggle("open");Pe.classList.toggle("open",e)}),Pe.addEventListener("click",()=>{tt.classList.remove("open"),Pe.classList.remove("open")});var W="",le=document.createElement("div");le.className="session-popover-backdrop",document.body.appendChild(le);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
36
36
  <label>Rename session</label>
37
37
  <input type="text" id="sp-name" />
38
38
  </div>
@@ -41,5 +41,5 @@ var zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
41
41
  <button class="btn" id="sp-cancel">Cancel</button>
42
42
  </div>
43
43
  <hr class="sp-divider" />
44
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(R);function cn(e){let t=e.getBoundingClientRect();R.style.top=t.bottom+4+"px",R.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function dn(e,t){j=e;let i=document.getElementById("sp-name");i.value=e,le.classList.add("open"),R.style.display="block",cn(t),setTimeout(()=>i.focus(),50)}function re(){le.classList.remove("open"),R.style.display="none"}le.addEventListener("click",re),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===j){re();return}try{if(!(await fetch(k("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:j,newName:e})})).ok)return;re(),W(),y===j&&ae(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+j+'"?'))try{if(!(await fetch(k("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:j})})).ok)return;re(),y===j&&(v&&(v.destroy(),v=null),y=null,T.style.display="none",z.style.display="flex"),W()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",re);var pn={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||!v)return;let i=pn[t.dataset.key];i&&(v.sendInput(i),v.focus())});var S=document.getElementById("mk-input"),vt=document.getElementById("mk-send");function ht(){if(!S||!v)return;let e=S.value;e&&(v.sendInput(e+"\r"),S.value="",S.rows=1,v.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;let e=S.value.split(`
45
- `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),ht())})),vt&&vt.addEventListener("click",ht),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 _e=document.getElementById("process-panel"),ce=document.getElementById("proc-list"),un=document.getElementById("proc-close"),ze=document.getElementById("proc-backdrop"),fn=document.getElementById("header-ram");function mn(){ce.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(k("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){ce.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let i of e){let a=i.rss,o=a<1048576?(a/1024).toFixed(0)+"K":(a/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+i.pid+'"><span class="proc-mem">'+i.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+f(i.command)+'">'+f(i.command)+'</span><button class="proc-kill" title="Kill PID '+i.pid+'">&times;</button></div>'}ce.innerHTML=t;for(let i of ce.querySelectorAll(".proc-row")){let a=i.querySelector(".proc-kill");a&&a.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(k("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ce.innerHTML='<div class="proc-empty">Failed to load</div>'})}function vn(){_e.classList.add("open"),ze.classList.add("open"),mn()}function Be(){_e.classList.remove("open"),ze.classList.remove("open")}fn.addEventListener("click",e=>{e.stopPropagation(),_e.classList.contains("open")?Be():vn()}),un.addEventListener("click",Be),ze.addEventListener("click",Be),window.__openSession=ae,window.__refreshSidebar=W,W(),$e(),setInterval(()=>{document.visibilityState==="visible"&&W()},1e4);
44
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function dn(e){let t=e.getBoundingClientRect();P.style.top=t.bottom+4+"px",P.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function pn(e,t){W=e;let s=document.getElementById("sp-name");s.value=e,le.classList.add("open"),P.style.display="block",dn(t),setTimeout(()=>s.focus(),50)}function re(){le.classList.remove("open"),P.style.display="none"}le.addEventListener("click",re),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===W){re();return}try{if(!(await fetch(k("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:W,newName:e})})).ok)return;re(),O(),g===W&&se(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+W+'"?'))try{if(!(await fetch(k("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:W})})).ok)return;re(),g===W&&(v&&(v.destroy(),v=null),g=null,T.style.display="none",j.style.display="flex"),O()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",re);var un={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||!v)return;let s=un[t.dataset.key];s&&(v.sendInput(s),v.focus())});var S=document.getElementById("mk-input"),vt=document.getElementById("mk-send");function ht(){if(!S||!v)return;let e=S.value;e&&(v.sendInput(e+"\r"),S.value="",S.rows=1,v.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;let e=S.value.split(`
45
+ `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),ht())})),vt&&vt.addEventListener("click",ht),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 ze=document.getElementById("process-panel"),ce=document.getElementById("proc-list"),fn=document.getElementById("proc-close"),_e=document.getElementById("proc-backdrop"),mn=document.getElementById("header-ram");function vn(){ce.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(k("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){ce.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let i=s.rss,o=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+f(s.command)+'">'+f(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">&times;</button></div>'}ce.innerHTML=t;for(let s of ce.querySelectorAll(".proc-row")){let i=s.querySelector(".proc-kill");i&&i.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(k("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ce.innerHTML='<div class="proc-empty">Failed to load</div>'})}function hn(){ze.classList.add("open"),_e.classList.add("open"),vn()}function Be(){ze.classList.remove("open"),_e.classList.remove("open")}mn.addEventListener("click",e=>{e.stopPropagation(),ze.classList.contains("open")?Be():hn()}),fn.addEventListener("click",Be),_e.addEventListener("click",Be),window.__openSession=se,window.__refreshSidebar=O,O(),Re(),setInterval(()=>{document.visibilityState==="visible"&&O()},1e4);
@@ -1,8 +1,8 @@
1
- const Me=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function A(a){return a.replace(Me,"")}let B;function Pe(){return B||(document.querySelector("link[data-tmux-web-xterm-css]")?B=Promise.resolve():(B=new Promise(o=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>o(),{once:!0}),i.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(i)}),B))}class J{terminal;fitAddon;container;constructor(o,i,n){this.container=o,this.terminal=i,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,i,n){const[{Terminal:s},{FitAddon:c}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),Pe()]),p=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:n});return new J(o,p,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(o){return new Promise(i=>this.terminal.write(o,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(o){this.terminal.scrollToLine(Math.max(0,o))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){const o=ke(this.container);return o.width<=0||o.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(o){this.terminal.paste(o)}input(o){this.terminal.input(o,!1)}onData(o){this.terminal.onData(o),this.terminal.onBinary(o)}onResize(o){this.terminal.onResize(o)}onScroll(o){this.terminal.onScroll(o)}attachCustomKeyEventHandler(o){this.terminal.attachCustomKeyEventHandler(o)}isFocused(){const o=document.activeElement;return o?o===this.container||o===this.terminal.textarea||this.container.contains(o)||!!o.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(o,i){const n=this.terminal.buffer.active,[s,c]=o<=i?[o,i]:[i,o],p=n.viewportY,l=[];for(let w=s;w<=c;w++){const f=n.getLine(p+w);l.push(f?f.translateToString(!0):"")}return l.join(`
2
- `)}selectRows(o,i){const n=this.terminal.buffer.active,[s,c]=o<=i?[o,i]:[i,o];this.terminal.selectLines(n.viewportY+s,n.viewportY+c)}selectCells(o,i,n,s){const c=this.terminal.buffer.active,p=Math.min(i,s),l=Math.max(i,s);if(p===l){const w=Math.min(o,n),f=Math.max(o,n);this.terminal.select(w,c.viewportY+p,f-w+1)}else this.terminal.selectLines(c.viewportY+p,c.viewportY+l)}getCellsText(o,i,n,s){const c=this.terminal.buffer.active,p=Math.min(i,s),l=Math.max(i,s),w=i<s?o:i>s?n:Math.min(o,n),f=i<s?n:i>s?o:Math.max(o,n),L=c.viewportY,b=[];for(let y=p;y<=l;y++){const v=c.getLine(L+y);if(!v){b.push("");continue}const x=y===p?w:0,E=y===l?f:this.terminal.cols-1;let z="";for(let S=x;S<=E;S++){const X=v.getCell(S);X&&(z+=X.getChars())}b.push(z.replace(/\s+$/,""))}return b.join(`
3
- `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function ke(a){const o=a.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Ye(a,o,i){return a.innerHTML="",a.classList.add("terminal-pending"),(async()=>{const n=await J.create(a,i.scrollback,i.theme);let s,c=0,p,l=null,w=0,f="connecting",L=0,b=!1,y=[],v="",x=!1,E=1e3;const z=location.protocol==="https:"?"wss:":"ws:",S=i.wsBase||"/ws",X=z+"//"+location.host+S+"/"+encodeURIComponent(o),se="/api/session/"+encodeURIComponent(o)+"/upload",le=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function ce(){return y.join("")+v}async function ue(e,t=0){const r=e?n.viewportY():0,h=e?n.baseY():0,u=A(ce());if(n.reset(),!u){e||n.scrollToBottom();return}await n.write(u),e?n.scrollToLine(h+r+t):n.scrollToBottom()}function D(e){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(e))}function fe(e){let t;try{t=JSON.parse(e)}catch{f==="live"&&(v+=e,n.write(A(e)));return}if(t.type==="auth.required"){const r=localStorage.getItem("tmux-web-token");r?D({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(t.type==="attach_failed"&&typeof t.message=="string"){O=!0,f="dead",n.reset(),n.write(`\r
1
+ const Pe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function A(s){return s.replace(Pe,"")}let B;function ke(){return B||(document.querySelector("link[data-tmux-web-xterm-css]")?B=Promise.resolve():(B=new Promise(o=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>o(),{once:!0}),i.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(i)}),B))}class J{terminal;fitAddon;container;constructor(o,i,n){this.container=o,this.terminal=i,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,i,n){const[{Terminal:a},{FitAddon:c}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),ke()]),p=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:n});return new J(o,p,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(o){return new Promise(i=>this.terminal.write(o,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(o){this.terminal.scrollToLine(Math.max(0,o))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){const o=Ye(this.container);return o.width<=0||o.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(o){this.terminal.paste(o)}input(o){this.terminal.input(o,!1)}onData(o){this.terminal.onData(o),this.terminal.onBinary(o)}onResize(o){this.terminal.onResize(o)}onScroll(o){this.terminal.onScroll(o)}attachCustomKeyEventHandler(o){this.terminal.attachCustomKeyEventHandler(o)}isFocused(){const o=document.activeElement;return o?o===this.container||o===this.terminal.textarea||this.container.contains(o)||!!o.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(o,i){const n=this.terminal.buffer.active,[a,c]=o<=i?[o,i]:[i,o],p=n.viewportY,l=[];for(let w=a;w<=c;w++){const f=n.getLine(p+w);l.push(f?f.translateToString(!0):"")}return l.join(`
2
+ `)}selectRows(o,i){const n=this.terminal.buffer.active,[a,c]=o<=i?[o,i]:[i,o];this.terminal.selectLines(n.viewportY+a,n.viewportY+c)}selectCells(o,i,n,a){const c=this.terminal.buffer.active,p=Math.min(i,a),l=Math.max(i,a);if(p===l){const w=Math.min(o,n),f=Math.max(o,n);this.terminal.select(w,c.viewportY+p,f-w+1)}else this.terminal.selectLines(c.viewportY+p,c.viewportY+l)}getCellsText(o,i,n,a){const c=this.terminal.buffer.active,p=Math.min(i,a),l=Math.max(i,a),w=i<a?o:i>a?n:Math.min(o,n),f=i<a?n:i>a?o:Math.max(o,n),S=c.viewportY,b=[];for(let y=p;y<=l;y++){const v=c.getLine(S+y);if(!v){b.push("");continue}const T=y===p?w:0,E=y===l?f:this.terminal.cols-1;let z="";for(let M=T;M<=E;M++){const X=v.getCell(M);X&&(z+=X.getChars())}b.push(z.replace(/\s+$/,""))}return b.join(`
3
+ `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Ye(s){const o=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Fe(s,o,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await J.create(s,i.scrollback,i.theme);let a,c=0,p,l=null,w=0,f="connecting",S=0,b=!1,y=[],v="",T=!1,E=1e3;const z=location.protocol==="https:"?"wss:":"ws:",M=i.wsBase||"/ws",X=z+"//"+location.host+M+"/"+encodeURIComponent(o),ae="/api/session/"+encodeURIComponent(o)+"/upload",le=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function ce(){return y.join("")+v}async function ue(e,t=0){const r=e?n.viewportY():0,h=e?n.baseY():0,u=A(ce());if(n.reset(),!u){e||n.scrollToBottom();return}await n.write(u),e?n.scrollToLine(h+r+t):n.scrollToBottom()}function L(e){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(e))}function fe(e){let t;try{t=JSON.parse(e)}catch{f==="live"&&(v+=e,n.write(A(e)));return}if(t.type==="auth.required"){const r=localStorage.getItem("tmux-web-token");r?L({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(t.type==="attach_failed"&&typeof t.message=="string"){O=!0,f="dead",n.reset(),n.write(`\r
4
4
  \x1B[31m`+A(t.message)+`\x1B[0m\r
5
- `).then(()=>{j();try{s?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),E=1e3,M(),D({type:"resize",cols:n.cols,rows:n.rows}),g();return}if(t.type==="auth.failed"){if(t.permanentLock||t.retryAfterMs&&t.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(t.message);return}return}if(t.type==="snapshot"&&typeof t.data=="string"){const r=A(t.data);y=[r],v="",L=typeof t.lines=="number"?t.lines:i.terminal.initialLines,f="live",n.reset(),n.write(r).then(()=>n.scrollToBottom());return}if(t.type==="window_changed"&&typeof t.activeIndex=="number"){const r=new URL(location.href);r.searchParams.get("window")!==String(t.activeIndex)&&(r.searchParams.set("window",String(t.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:t.windows}));return}if(t.type==="history"&&typeof t.data=="string"){b=!1,t.lines>0&&t.data&&(y.unshift(t.data),L+=t.lines,ue(!0,t.lines));return}if(t.type==="data"&&typeof t.data=="string"){const r=A(t.data);if(f==="connecting"){f="live",v=r,n.write(r);return}f==="live"&&(v+=r,n.write(r))}}let O=!1;function G(){if(O)return;let e=X;const t=localStorage.getItem("tmux-web-token");t&&(e+=(e.includes("?")?"&":"?")+"token="+encodeURIComponent(t)),s=new WebSocket(e),s.onopen=()=>{f="connecting",L=0,b=!1,y=[],v="",n.reset(),E=1e3},s.onmessage=r=>{typeof r.data=="string"&&fe(r.data)},s.onclose=()=>{x||(f="connecting",!O&&setTimeout(()=>{E=Math.min(E*2,1e4),!x&&!O&&G()},E))},s.onerror=()=>s?.close()}function U(e){D({type:"input",data:e})}function M(){return n.fit()}function j(){a.classList.remove("terminal-pending")}function g(){c&&cancelAnimationFrame(c),p&&clearTimeout(p),M(),c=requestAnimationFrame(()=>{c=0,M()&&j()}),p=setTimeout(()=>{M()&&j()},120)}function de(){g(),setTimeout(g,50),setTimeout(g,150),setTimeout(g,300)}function $(e){return typeof e=="string"&&e.startsWith("image/")}function Q(e){return e?.files?.length?e.files[0]??null:null}function me(e){if(!e?.items)return null;for(let t=0;t<e.items.length;t++){const r=e.items[t];if(r?.kind==="file"&&$(r.type))return r.getAsFile()}return null}async function pe(e){const t=new FormData,r=e instanceof File&&e.name?e.name:"upload";t.append("file",e,r);const h=localStorage.getItem("tmux-web-token"),u={};h&&(u.Authorization="Bearer "+h);const K=await fetch(se,{method:"POST",body:t,headers:u});if(!K.ok){let ie="upload failed";try{const ae=await K.json();ae.error&&(ie=ae.error)}catch{}throw new Error(ie)}const H=await K.json();if(!H.path)throw new Error("no path in response");return H.path}async function W(e){try{const t=await pe(e);U(t)}catch{}}n.onData(e=>U(e)),n.onResize(({cols:e,rows:t})=>D({type:"resize",cols:e,rows:t})),n.onScroll(()=>{f!=="live"||b||!n.isNearScrollbackTop()||(b=!0,D({type:"load_history",before:L}))});let P=!1,k=!1,d=null,m=null;const Y=document.createElement("div");Y.className="terminal-copy-banner",Y.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',Y.hidden=!0,a.appendChild(Y);const T=document.createElement("div");T.className="terminal-copy-banner terminal-copy-toast",T.textContent="Copied",T.hidden=!0,a.appendChild(T);function Z(e,t){const r=a.querySelector(".xterm-screen, canvas"),u=(r&&r.getBoundingClientRect().width>0?r:a).getBoundingClientRect(),K=u.height>0&&n.rows>0?u.height/n.rows:18,H=u.width>0&&n.cols>0?u.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((t-u.top)/K))),col:Math.max(0,Math.min(n.cols-1,Math.floor((e-u.left)/H)))}}async function q(e){if(!e)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return}}catch{}const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",t.style.pointerEvents="none",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")}catch{}t.remove()}let F;function N(){T.hidden=!1,F&&clearTimeout(F),F=setTimeout(()=>{T.hidden=!0},1e3)}function C(e){P=e,Y.hidden=!e,a.classList.toggle("terminal-copy-mode-active",e),e&&n.focus()}function ee(){C(!P)}function he(e){x||e.button!==0||k||(P||e.shiftKey)&&(e.preventDefault(),e.stopPropagation(),k=!0,d=Z(e.clientX,e.clientY),m=d,n.selectCells(d.col,d.row,m.col,m.row),document.addEventListener("mousemove",R,!0),document.addEventListener("mouseup",V,!0))}function R(e){x||!k||!d||(e.preventDefault(),e.stopPropagation(),m=Z(e.clientX,e.clientY),n.selectCells(d.col,d.row,m.col,m.row))}function V(e){if(x||!k||!d||!m)return;e.preventDefault(),e.stopPropagation(),document.removeEventListener("mousemove",R,!0),document.removeEventListener("mouseup",V,!0),k=!1;const t=n.getCellsText(d.col,d.row,m.col,m.row);d=null,m=null,n.clearSelection(),C(!1),t&&(T.textContent="\u5DF2\u590D\u5236",q(t).then(N)),n.focus()}a.addEventListener("mousedown",he,!0),document.addEventListener("keydown",ge,!0);function ge(e){if(!(x||!n.isFocused()||e.defaultPrevented||e.isComposing)){if(P){if(e.key==="Escape"){e.preventDefault(),e.stopPropagation(),d=null,m=null,n.clearSelection(),C(!1);return}if(e.key==="Enter"||(e.ctrlKey||e.metaKey)&&e.code==="KeyC"){e.preventDefault(),e.stopPropagation(),q(n.getScreenText()).then(N),C(!1);return}}if(e.altKey&&e.code==="KeyC"&&!e.ctrlKey&&!e.metaKey){if(e.preventDefault(),e.stopPropagation(),d&&m){const t=n.getCellsText(d.col,d.row,m.col,m.row);d=null,m=null,n.clearSelection(),C(!1),t&&(T.textContent="Copied",q(t).then(N))}else{const t=n.getSelection();t?(n.clearSelection(),T.textContent="Copied",q(t).then(N)):ee()}return}if((e.ctrlKey||e.metaKey)&&e.code==="KeyV"){e.preventDefault(),e.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){const t=await navigator.clipboard.read();for(const r of t)for(const h of r.types)if($(h)){const u=await r.getType(h);await W(u);return}}}catch{}try{const t=await navigator.clipboard?.readText();t&&n.pasteText(te(t))}catch{}})();return}}}let _=0;function te(e){return e.split(`\r
5
+ `).then(()=>{j();try{a?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),E=1e3,C(),L({type:"resize",cols:n.cols,rows:n.rows}),g();return}if(t.type==="auth.failed"){if(t.permanentLock||t.retryAfterMs&&t.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(t.message);return}return}if(t.type==="snapshot"&&typeof t.data=="string"){const r=A(t.data);y=[r],v="",S=typeof t.lines=="number"?t.lines:i.terminal.initialLines,f="live",n.reset(),C(),L({type:"resize",cols:n.cols,rows:n.rows}),n.write(r).then(()=>n.scrollToBottom());return}if(t.type==="window_changed"&&typeof t.activeIndex=="number"){const r=new URL(location.href);r.searchParams.get("window")!==String(t.activeIndex)&&(r.searchParams.set("window",String(t.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:t.windows}));return}if(t.type==="history"&&typeof t.data=="string"){b=!1,t.lines>0&&t.data&&(y.unshift(t.data),S+=t.lines,ue(!0,t.lines));return}if(t.type==="data"&&typeof t.data=="string"){const r=A(t.data);if(f==="connecting"){f="live",v=r,n.write(r);return}f==="live"&&(v+=r,n.write(r))}}let O=!1;function G(){if(O)return;let e=X;const t=localStorage.getItem("tmux-web-token");t&&(e+=(e.includes("?")?"&":"?")+"token="+encodeURIComponent(t)),e+=(e.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,a=new WebSocket(e),a.onopen=()=>{f="connecting",S=0,b=!1,y=[],v="",n.reset(),E=1e3},a.onmessage=r=>{typeof r.data=="string"&&fe(r.data)},a.onclose=()=>{T||(f="connecting",!O&&setTimeout(()=>{E=Math.min(E*2,1e4),!T&&!O&&G()},E))},a.onerror=()=>a?.close()}function U(e){L({type:"input",data:e})}function C(){return n.fit()}function j(){s.classList.remove("terminal-pending")}function g(){c&&cancelAnimationFrame(c),p&&clearTimeout(p),C(),c=requestAnimationFrame(()=>{c=0,C()&&j()}),p=setTimeout(()=>{C()&&j()},120)}function de(){g(),setTimeout(g,50),setTimeout(g,150),setTimeout(g,300)}function $(e){return typeof e=="string"&&e.startsWith("image/")}function Q(e){return e?.files?.length?e.files[0]??null:null}function me(e){if(!e?.items)return null;for(let t=0;t<e.items.length;t++){const r=e.items[t];if(r?.kind==="file"&&$(r.type))return r.getAsFile()}return null}async function pe(e){const t=new FormData,r=e instanceof File&&e.name?e.name:"upload";t.append("file",e,r);const h=localStorage.getItem("tmux-web-token"),u={};h&&(u.Authorization="Bearer "+h);const K=await fetch(ae,{method:"POST",body:t,headers:u});if(!K.ok){let ie="upload failed";try{const se=await K.json();se.error&&(ie=se.error)}catch{}throw new Error(ie)}const H=await K.json();if(!H.path)throw new Error("no path in response");return H.path}async function W(e){try{const t=await pe(e);U(t)}catch{}}n.onData(e=>U(e)),n.onResize(({cols:e,rows:t})=>L({type:"resize",cols:e,rows:t})),n.onScroll(()=>{f!=="live"||b||!n.isNearScrollbackTop()||(b=!0,L({type:"load_history",before:S}))});let P=!1,k=!1,d=null,m=null;const Y=document.createElement("div");Y.className="terminal-copy-banner",Y.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',Y.hidden=!0,s.appendChild(Y);const x=document.createElement("div");x.className="terminal-copy-banner terminal-copy-toast",x.textContent="Copied",x.hidden=!0,s.appendChild(x);function Z(e,t){const r=s.querySelector(".xterm-screen, canvas"),u=(r&&r.getBoundingClientRect().width>0?r:s).getBoundingClientRect(),K=u.height>0&&n.rows>0?u.height/n.rows:18,H=u.width>0&&n.cols>0?u.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((t-u.top)/K))),col:Math.max(0,Math.min(n.cols-1,Math.floor((e-u.left)/H)))}}async function q(e){if(!e)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return}}catch{}const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",t.style.pointerEvents="none",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")}catch{}t.remove()}let F;function N(){x.hidden=!1,F&&clearTimeout(F),F=setTimeout(()=>{x.hidden=!0},1e3)}function D(e){P=e,Y.hidden=!e,s.classList.toggle("terminal-copy-mode-active",e),e&&n.focus()}function ee(){D(!P)}function he(e){T||e.button!==0||k||(P||e.shiftKey)&&(e.preventDefault(),e.stopPropagation(),k=!0,d=Z(e.clientX,e.clientY),m=d,n.selectCells(d.col,d.row,m.col,m.row),document.addEventListener("mousemove",R,!0),document.addEventListener("mouseup",V,!0))}function R(e){T||!k||!d||(e.preventDefault(),e.stopPropagation(),m=Z(e.clientX,e.clientY),n.selectCells(d.col,d.row,m.col,m.row))}function V(e){if(T||!k||!d||!m)return;e.preventDefault(),e.stopPropagation(),document.removeEventListener("mousemove",R,!0),document.removeEventListener("mouseup",V,!0),k=!1;const t=n.getCellsText(d.col,d.row,m.col,m.row);d=null,m=null,n.clearSelection(),D(!1),t&&(x.textContent="\u5DF2\u590D\u5236",q(t).then(N)),n.focus()}s.addEventListener("mousedown",he,!0),document.addEventListener("keydown",ge,!0);function ge(e){if(!(T||!n.isFocused()||e.defaultPrevented||e.isComposing)){if(P){if(e.key==="Escape"){e.preventDefault(),e.stopPropagation(),d=null,m=null,n.clearSelection(),D(!1);return}if(e.key==="Enter"||(e.ctrlKey||e.metaKey)&&e.code==="KeyC"){e.preventDefault(),e.stopPropagation(),q(n.getScreenText()).then(N),D(!1);return}}if(e.altKey&&e.code==="KeyC"&&!e.ctrlKey&&!e.metaKey){if(e.preventDefault(),e.stopPropagation(),d&&m){const t=n.getCellsText(d.col,d.row,m.col,m.row);d=null,m=null,n.clearSelection(),D(!1),t&&(x.textContent="Copied",q(t).then(N))}else{const t=n.getSelection();t?(n.clearSelection(),x.textContent="Copied",q(t).then(N)):ee()}return}if((e.ctrlKey||e.metaKey)&&e.code==="KeyV"){e.preventDefault(),e.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){const t=await navigator.clipboard.read();for(const r of t)for(const h of r.types)if($(h)){const u=await r.getType(h);await W(u);return}}}catch{}try{const t=await navigator.clipboard?.readText();t&&n.pasteText(te(t))}catch{}})();return}}}let _=0;function te(e){return e.split(`\r
6
6
  `).join(`
7
7
  `).split(`
8
- `).join("\r")}async function we(e){const t=me(e.clipboardData);if(t){e.preventDefault(),e.stopPropagation();const u=Date.now();if(u-_<50)return;_=u,await W(t);return}const r=e.clipboardData?.getData("text/plain");if(!r)return;e.preventDefault(),e.stopPropagation();const h=Date.now();h-_<50||(_=h,n.pasteText(te(r)))}let I=0;function ye(e){e.preventDefault(),I++,Q(e.dataTransfer)&&a.classList.add("terminal-drag-over")}function ve(e){e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="copy")}function xe(e){e.preventDefault(),I--,I<=0&&(I=0,a.classList.remove("terminal-drag-over"))}async function Te(e){e.preventDefault(),I=0,a.classList.remove("terminal-drag-over");const t=Q(e.dataTransfer);t&&await W(t)}function ne(e){!le||e.key!=="Escape"||P||e.defaultPrevented||e.repeat||e.metaKey||e.ctrlKey||e.altKey||n.isFocused()&&(e.preventDefault(),e.stopPropagation(),U("\x1B"))}a.addEventListener("paste",we),a.addEventListener("dragenter",ye),a.addEventListener("dragover",ve),a.addEventListener("dragleave",xe),a.addEventListener("drop",Te),document.addEventListener("keydown",ne,!0);function be(e){if(e.touches.length!==1){l=null;return}const t=e.touches[0];t&&(l={startX:t.clientX,startY:t.clientY,lastX:t.clientX,lastY:t.clientY,scrolling:!1},e.stopPropagation())}function Ee(e){if(!l||e.touches.length!==1)return;const t=e.touches[0];if(!t)return;const r=t.clientY-l.startY,h=t.clientX-l.startX;if(!l.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(h))return;l.scrolling=!0,w=Date.now()+500}e.preventDefault(),e.stopPropagation(),De(-(t.clientY-l.lastY),t.clientX,t.clientY),l.lastX=t.clientX,l.lastY=t.clientY}function Le(e){if(!l)return;const t=l.scrolling;l=null,e.stopPropagation(),t?(w=Date.now()+500,e.preventDefault()):n.focus()}function Ce(e){l=null,e.stopPropagation()}function Se(e){e.pointerType==="touch"&&Date.now()<w&&(e.preventDefault(),e.stopPropagation())}function De(e,t,r){(a.querySelector(".xterm-screen")??a).dispatchEvent(new WheelEvent("wheel",{deltaY:e,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:t,clientY:r,bubbles:!0,cancelable:!0}))}a.addEventListener("touchstart",be,{passive:!0,capture:!0}),a.addEventListener("touchmove",Ee,{passive:!1,capture:!0}),a.addEventListener("touchend",Le,{passive:!1,capture:!0}),a.addEventListener("touchcancel",Ce,{passive:!0,capture:!0}),a.addEventListener("pointerup",Se,!0);function oe(){g()}window.addEventListener("resize",oe),window.visualViewport?.addEventListener("resize",g),window.visualViewport?.addEventListener("scroll",g);const re=new ResizeObserver(()=>g());return re.observe(a),document.fonts?.ready.then(()=>{g(),setTimeout(g,50),setTimeout(g,200)}),a.addEventListener("focusin",de,!0),requestAnimationFrame(()=>{M()&&j(),G(),g()}),{destroy(){if(x=!0,C(!1),F&&clearTimeout(F),document.removeEventListener("mousemove",R,!0),document.removeEventListener("mouseup",V,!0),s){try{s.close(1e3,"session switch")}catch{}s=void 0}a.innerHTML="",a.classList.remove("terminal-pending"),window.removeEventListener("resize",oe),re.disconnect(),document.removeEventListener("keydown",ne,!0)},focus(){n.focus()},sendInput(e){U(e)},toggleCopyMode(){ee()}}})()}export{Ye as initTerminal};
8
+ `).join("\r")}async function we(e){const t=me(e.clipboardData);if(t){e.preventDefault(),e.stopPropagation();const u=Date.now();if(u-_<50)return;_=u,await W(t);return}const r=e.clipboardData?.getData("text/plain");if(!r)return;e.preventDefault(),e.stopPropagation();const h=Date.now();h-_<50||(_=h,n.pasteText(te(r)))}let I=0;function ye(e){e.preventDefault(),I++,Q(e.dataTransfer)&&s.classList.add("terminal-drag-over")}function ve(e){e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="copy")}function Te(e){e.preventDefault(),I--,I<=0&&(I=0,s.classList.remove("terminal-drag-over"))}async function xe(e){e.preventDefault(),I=0,s.classList.remove("terminal-drag-over");const t=Q(e.dataTransfer);t&&await W(t)}function ne(e){!le||e.key!=="Escape"||P||e.defaultPrevented||e.repeat||e.metaKey||e.ctrlKey||e.altKey||n.isFocused()&&(e.preventDefault(),e.stopPropagation(),U("\x1B"))}s.addEventListener("paste",we),s.addEventListener("dragenter",ye),s.addEventListener("dragover",ve),s.addEventListener("dragleave",Te),s.addEventListener("drop",xe),document.addEventListener("keydown",ne,!0);function be(e){if(e.touches.length!==1){l=null;return}const t=e.touches[0];t&&(l={startX:t.clientX,startY:t.clientY,lastX:t.clientX,lastY:t.clientY,scrolling:!1},e.stopPropagation())}function Ee(e){if(!l||e.touches.length!==1)return;const t=e.touches[0];if(!t)return;const r=t.clientY-l.startY,h=t.clientX-l.startX;if(!l.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(h))return;l.scrolling=!0,w=Date.now()+500}e.preventDefault(),e.stopPropagation(),De(-(t.clientY-l.lastY),t.clientX,t.clientY),l.lastX=t.clientX,l.lastY=t.clientY}function Le(e){if(!l)return;const t=l.scrolling;l=null,e.stopPropagation(),t?(w=Date.now()+500,e.preventDefault()):n.focus()}function Ce(e){l=null,e.stopPropagation()}function Se(e){e.pointerType==="touch"&&Date.now()<w&&(e.preventDefault(),e.stopPropagation())}function De(e,t,r){(s.querySelector(".xterm-screen")??s).dispatchEvent(new WheelEvent("wheel",{deltaY:e,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:t,clientY:r,bubbles:!0,cancelable:!0}))}s.addEventListener("touchstart",be,{passive:!0,capture:!0}),s.addEventListener("touchmove",Ee,{passive:!1,capture:!0}),s.addEventListener("touchend",Le,{passive:!1,capture:!0}),s.addEventListener("touchcancel",Ce,{passive:!0,capture:!0}),s.addEventListener("pointerup",Se,!0);function oe(){g()}window.addEventListener("resize",oe),window.visualViewport?.addEventListener("resize",g),window.visualViewport?.addEventListener("scroll",g);const re=new ResizeObserver(()=>g());re.observe(s),document.fonts?.ready.then(()=>{g(),setTimeout(g,50),setTimeout(g,200)}),s.addEventListener("focusin",de,!0);async function Me(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(e=>setTimeout(e,1e3))])}catch{}}return requestAnimationFrame(()=>{C()&&j(),(async()=>(await Me(),C()&&L({type:"resize",cols:n.cols,rows:n.rows}),G(),g()))()}),{destroy(){if(T=!0,D(!1),F&&clearTimeout(F),document.removeEventListener("mousemove",R,!0),document.removeEventListener("mouseup",V,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}s.innerHTML="",s.classList.remove("terminal-pending"),window.removeEventListener("resize",oe),re.disconnect(),document.removeEventListener("keydown",ne,!0)},focus(){n.focus()},sendInput(e){U(e)},toggleCopyMode(){ee()}}})()}export{Fe as initTerminal};
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{chmodSync as le,existsSync as W,readdirSync as ue,statSync as de}from"node:fs";import{createRequire as D}from"node:module";import{hostname as E}from"node:os";const me=D(import.meta.url),{version:fe}=me("../package.json");import O from"node:path";import{fileURLToPath as pe}from"node:url";import{serve as he}from"@hono/node-server";import{WebSocketServer as ge,WebSocket as F}from"ws";try{const e=O.dirname(D(import.meta.url).resolve("node-pty/package.json")),n=O.join(e,"prebuilds");if(W(n))for(const s of ue(n)){const t=O.join(n,s,"spawn-helper");W(t)&&!(de(t).mode&73)&&le(t,493)}}catch{}import{loadSecurityConfig as ye}from"./lib/security-config.js";import{TokenStore as ke,hashPassword as _e,verifyPassword as Te,validatePassword as Se}from"./lib/auth.js";import{RateLimiter as Ae}from"./lib/rateLimiter.js";import{audit as l}from"./lib/auditLog.js";import{db as P}from"./lib/db.js";import{handleClientMessage as J}from"./lib/ws-message.js";import{loadDotEnv as Ce}from"./lib/load-env.js";import{listSessions as ve,resolveTmuxSocketPath as we}from"./sessions.js";import{cmdSetup as xe,cmdTheme as be,printUsage as G,printVersion as Ie}from"./lib/cli.js";import{readSettings as Me}from"./lib/settings.js";import{readActiveTheme as Re}from"./lib/theme-store.js";import{buildApp as z}from"./lib/build-app.js";import{attachTerminal as Oe,killAllAttachedPtys as Pe}from"./lib/attach-terminal.js";import{acquireControlClient as V,killAllControlClients as $e}from"./lib/tmux-control.js";import{getSessionPaneTarget as X,capturePaneTail as Y,capturePaneHistoryChunk as K,toCrlf as Q}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Le}from"./lib/terminal-config.js";import{AgentRegistry as Ee}from"./lib/agent-registry.js";import{AgentChannel as Ue,MAX_RELAY_CONNS_PER_AGENT as Ne}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as Fe,listAgentTokens as He}from"./lib/agent-tokens.js";import{saveSecurityConfig as je}from"./lib/security-config.js";import{readFederationConfig as Be,writeFederationConfig as We}from"./lib/federation-config.js";import{startAgentClient as De}from"./lib/agent-client.js";Ce();const U=Le(),h=ye(),y=new ke,A=new Ae;let H=!1;const Je="tmux-web-token";function j(e){if(h.security.trustProxy){const n=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(n)return n}return e.socket.remoteAddress||"unknown"}function B(e){const n=e.headers.authorization;if(typeof n=="string"&&n.startsWith("Bearer "))return n.slice(7);const s=e.headers.cookie;if(typeof s!="string")return null;const t=s.match(new RegExp(`(?:^|;\\s*)${Je}=([^;]+)`));return t?decodeURIComponent(t[1]):null}function Lt(e){const n=B(e);return n?y.validateToken(n):null}const M=new Map;function Ge(e){let n=0;for(const s of M.values())s.ip===e&&n++;return n}function v(e,n,s){try{e.close(n,s)}catch{}}function Z(e,n){e.readyState===F.OPEN&&e.send(JSON.stringify(n))}const q=process.argv.slice(2),$=q;if($.length>0){const[e,n]=$;switch(e){case"setup":await xe($),process.exit(0);case"theme":await be($.slice(1)),process.exit(0);case"agent":{const s=O.join(O.dirname(pe(import.meta.url)),"agent.js"),{spawn:t}=await import("node:child_process"),m=t(process.execPath,[s,...q],{stdio:"inherit"});m.on("error",r=>{console.error(r),process.exit(1)}),m.on("exit",r=>process.exit(r??0)),await new Promise(()=>{})}case"agent-token":await rt($.slice(1)),process.exit(0);case"help":case"--help":case"-h":G(),process.exit(0);case"-V":case"--version":case"-v":Ie(),process.exit(0);default:console.error(`unknown argument: ${e}`),G(),process.exit(1)}}function b(e,n){e.readyState===F.OPEN&&e.send(JSON.stringify(n))}const ee=new Set;await P.read(),P.data.sessionAccess??=[],P.data.pinnedViews??=[],P.data.watchedPanes??=[],P.data.quickCommands??=[];const te=await Me();let ze=await Re();const g=new Ee({onChange:()=>{}}),T=new Map;function ne(e){return g.get(e)?T.get(e)??null:null}const oe={activeTheme:ze,settingUpPassword:!1},Ve=z({mode:"hub",securityConfig:h,tokenStore:y,rateLimiter:A,settings:te,terminalBufferConfig:U,state:oe,federation:{getConfig:()=>x,setConfig:async e=>{x=e,await We(e),se(e)},status:Ye,hostname:E()},hub:{agents:g,getChannel:ne}}),Xe=z({mode:"agent",securityConfig:h,tokenStore:y,rateLimiter:A,settings:te,terminalBufferConfig:U,state:oe});let x=await Be(),R=null;function re(){R&&(R.stop(),R=null)}function se(e){re(),!(!e.enabled||!e.hub||!e.token)&&(R=De({hub:e.hub,token:e.token,name:e.name?.trim()||E(),version:fe,app:Xe,listSessions:ve,terminalBufferConfig:U,getSessionPaneTarget:X,capturePaneTail:Y,capturePaneHistoryChunk:K,toCrlf:Q,handleClientMessage:J,acquireControlClient:V}))}function Ye(){return x.enabled?R?R.status():{state:"idle",agentId:null,hub:x.hub??"",name:x.name??E()}:{state:"idle",agentId:null,hub:x.hub??"",name:x.name??E()}}se(x);const Ke=parseInt(process.env.PORT||"21000",10),Qe=he({fetch:Ve.fetch,port:Ke,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),L=new ge({noServer:!0});function I(e,n,s){e.write(`HTTP/1.1 ${n} ${s}\r
2
+ import{chmodSync as ue,existsSync as D,readdirSync as de,statSync as me}from"node:fs";import{createRequire as J}from"node:module";import{hostname as U}from"node:os";const fe=J(import.meta.url),{version:pe}=fe("../package.json");import O from"node:path";import{fileURLToPath as he}from"node:url";import{serve as ge}from"@hono/node-server";import{WebSocketServer as ye,WebSocket as H}from"ws";try{const e=O.dirname(J(import.meta.url).resolve("node-pty/package.json")),n=O.join(e,"prebuilds");if(D(n))for(const s of de(n)){const t=O.join(n,s,"spawn-helper");D(t)&&!(me(t).mode&73)&&ue(t,493)}}catch{}import{loadSecurityConfig as ke}from"./lib/security-config.js";import{TokenStore as _e,hashPassword as Te,verifyPassword as we,validatePassword as Se}from"./lib/auth.js";import{RateLimiter as Ae}from"./lib/rateLimiter.js";import{audit as d}from"./lib/auditLog.js";import{db as N}from"./lib/db.js";import{handleClientMessage as z}from"./lib/ws-message.js";import{loadDotEnv as Ce}from"./lib/load-env.js";import{listSessions as ve,resolveTmuxSocketPath as be}from"./sessions.js";import{cmdSetup as xe,cmdTheme as Ie,printUsage as G,printVersion as Re}from"./lib/cli.js";import{readSettings as Pe}from"./lib/settings.js";import{readActiveTheme as Me}from"./lib/theme-store.js";import{buildApp as V}from"./lib/build-app.js";import{attachTerminal as Oe,killAllAttachedPtys as Ne}from"./lib/attach-terminal.js";import{acquireControlClient as X,killAllControlClients as $e}from"./lib/tmux-control.js";import{getSessionPaneTarget as Y,capturePaneTail as K,capturePaneHistoryChunk as Q,toCrlf as Z}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Le}from"./lib/terminal-config.js";import{AgentRegistry as Ue}from"./lib/agent-registry.js";import{AgentChannel as Ee,MAX_RELAY_CONNS_PER_AGENT as Fe}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as He,listAgentTokens as je}from"./lib/agent-tokens.js";import{saveSecurityConfig as Be}from"./lib/security-config.js";import{readFederationConfig as We,writeFederationConfig as De}from"./lib/federation-config.js";import{startAgentClient as Je}from"./lib/agent-client.js";Ce();const E=Le(),h=ke(),k=new _e,A=new Ae;let j=!1;const ze="tmux-web-token";function B(e){if(h.security.trustProxy){const n=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(n)return n}return e.socket.remoteAddress||"unknown"}function W(e){const n=e.headers.authorization;if(typeof n=="string"&&n.startsWith("Bearer "))return n.slice(7);const s=e.headers.cookie;if(typeof s!="string")return null;const t=s.match(new RegExp(`(?:^|;\\s*)${ze}=([^;]+)`));return t?decodeURIComponent(t[1]):null}function Lt(e){const n=W(e);return n?k.validateToken(n):null}const P=new Map;function Ge(e){let n=0;for(const s of P.values())s.ip===e&&n++;return n}function v(e,n,s){try{e.close(n,s)}catch{}}function q(e,n){e.readyState===H.OPEN&&e.send(JSON.stringify(n))}const ee=process.argv.slice(2),$=ee;if($.length>0){const[e,n]=$;switch(e){case"setup":await xe($),process.exit(0);case"theme":await Ie($.slice(1)),process.exit(0);case"agent":{const s=O.join(O.dirname(he(import.meta.url)),"agent.js"),{spawn:t}=await import("node:child_process"),m=t(process.execPath,[s,...ee],{stdio:"inherit"});m.on("error",r=>{console.error(r),process.exit(1)}),m.on("exit",r=>process.exit(r??0)),await new Promise(()=>{})}case"agent-token":await st($.slice(1)),process.exit(0);case"help":case"--help":case"-h":G(),process.exit(0);case"-V":case"--version":case"-v":Re(),process.exit(0);default:console.error(`unknown argument: ${e}`),G(),process.exit(1)}}function I(e,n){e.readyState===H.OPEN&&e.send(JSON.stringify(n))}const te=new Set;await N.read(),N.data.sessionAccess??=[],N.data.pinnedViews??=[],N.data.watchedPanes??=[],N.data.quickCommands??=[];const ne=await Pe();let Ve=await Me();const y=new Ue({onChange:()=>{}}),T=new Map;function oe(e){return y.get(e)?T.get(e)??null:null}const re={activeTheme:Ve,settingUpPassword:!1},Xe=V({mode:"hub",securityConfig:h,tokenStore:k,rateLimiter:A,settings:ne,terminalBufferConfig:E,state:re,federation:{getConfig:()=>x,setConfig:async e=>{x=e,await De(e),ae(e)},status:Ke,hostname:U()},hub:{agents:y,getChannel:oe}}),Ye=V({mode:"agent",securityConfig:h,tokenStore:k,rateLimiter:A,settings:ne,terminalBufferConfig:E,state:re});let x=await We(),M=null;function se(){M&&(M.stop(),M=null)}function ae(e){se(),!(!e.enabled||!e.hub||!e.token)&&(M=Je({hub:e.hub,token:e.token,name:e.name?.trim()||U(),version:pe,app:Ye,listSessions:ve,terminalBufferConfig:E,getSessionPaneTarget:Y,capturePaneTail:K,capturePaneHistoryChunk:Q,toCrlf:Z,handleClientMessage:z,acquireControlClient:X}))}function Ke(){return x.enabled?M?M.status():{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}:{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}}ae(x);const Qe=parseInt(process.env.PORT||"21000",10),Ze=ge({fetch:Xe.fetch,port:Qe,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),L=new ye({noServer:!0});function R(e,n,s){e.write(`HTTP/1.1 ${n} ${s}\r
3
3
  \r
4
- `),e.destroy()}Qe.on("upgrade",(e,n,s)=>{const t=new URL(e.url||"/",`http://${e.headers.host}`),m=t.pathname;if(m==="/agent/ws"){tt(e,n,s);return}const r=m.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(r){Ze(e,n,s,decodeURIComponent(r[1]),decodeURIComponent(r[2]));return}const o=m.match(/^\/ws\/(.+)$/);if(!o){n.destroy();return}const f=j(e),i=e.headers.origin,u=h.security.allowedOrigins;if(i&&!(i===`http://${e.headers.host}`||i===`https://${e.headers.host}`)&&(u.length===0||!u.includes(i))){l("ws_rejected_origin",{ip:f,origin:i}),I(n,403,"Origin not allowed");return}const k=Ge(f);if(k>=h.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:f,liveFromIp:k}),I(n,429,"Too many connections");return}const p=B(e)||t.searchParams.get("token")||"",S=p?y.validateToken(p):null;L.handleUpgrade(e,n,s,a=>{L.emit("connection",a,e,decodeURIComponent(o[1]),f,S)})}),L.on("connection",(e,n,s,t,m)=>{const r={ws:e,ip:t,authenticated:!1,authTimeout:null};M.set(e,r),l("ws_connected",{ip:t});let o=null,f=null;function i(a,c){r.authenticated=!0,r.authTimeout&&(clearTimeout(r.authTimeout),r.authTimeout=null),l("auth_success",{ip:t,method:a,tokenName:c}),b(e,{type:"auth.ok",setupMode:!h.passwordHash}),k()}function u(a,c={}){b(e,{type:"auth.failed",message:a,...c})}function k(){o||(o=Oe(s,{send:a=>b(e,a),onMessage:a=>{f=a},onClose:a=>{p=a}},{terminalBufferConfig:U,getSessionPaneTarget:X,capturePaneTail:Y,capturePaneHistoryChunk:K,toCrlf:Q,handleClientMessage:J,acquireControlClient:V,tmuxSocketPath:we()??void 0,onPtyExit:(a,c)=>v(e,1e3,"pty exited"),onSpawnError:(a,c)=>v(e,1011,"pty spawn failed")}))}let p=null;async function S(a){let c;try{c=JSON.parse(a)}catch{return!1}if(c.type==="auth.token"&&typeof c.token=="string"){const d=y.validateToken(c.token);if(!d){l("token_auth_failed",{ip:t});const w=A.recordFailure(t);return w.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:w.failures})),u("Invalid or expired token",{permanentLock:w.permanentLock,retryAfterMs:w.retryAfterMs}),!0}return y.touch(d.tokenHash),i("token",d.name),!0}if(c.type==="auth"&&typeof c.password=="string"){const d=A.check(t);if(!d.allowed)return l("rate_limited",{ip:t,retryAfterMs:d.retryAfterMs,permanentLock:d.permanentLock}),u("Too many attempts",{permanentLock:d.permanentLock,retryAfterMs:d.retryAfterMs}),!0;if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!ot(t))return l("setup_rejected_remote",{ip:t}),u("First-run setup must be performed from localhost"),!0;if(H)return u("Password setup in progress"),!0;const C=Se(c.password);if(C)return u(C),!0;H=!0;try{h.passwordHash=await _e(c.password),je(h),l("password_set",{ip:t})}finally{H=!1}const{stored:N,plaintext:ce}=y.createAccessToken("setup",h.security.tokenTtlDays);return b(e,{type:"auth.ok",setupMode:!0,token:ce}),l("token_created",{ip:t,name:N.name,tokenId:N.id}),l("auth_success",{ip:t,method:"password",tokenName:N.name}),k(),!0}if(!h.passwordHash)return u("Server not configured"),!0;if(!await Te(c.password,h.passwordHash)){const C=A.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:C.failures,permanentLock:C.permanentLock}),C.permanentLock&&(y.revokeAll(),l("permanent_lock",{ip:t,failures:C.failures})),u("Incorrect password",{permanentLock:C.permanentLock,retryAfterMs:C.retryAfterMs}),!0}A.recordSuccess(t);const _=`ws-${t}`,{plaintext:ie}=y.createAccessToken(_,h.security.tokenTtlDays);return i("password",_),b(e,{type:"auth.ok",setupMode:!1,token:ie}),!0}return!1}m?(y.touch(m.tokenHash),i("token",m.name)):(b(e,{type:"auth.required",setupMode:!h.passwordHash}),r.authTimeout=setTimeout(()=>{r.authenticated||(l("auth_timeout",{ip:t}),u("Authentication timeout"),v(e,4e3,"Auth timeout"))},h.security.authTimeoutMs)),e.on("message",async a=>{const c=typeof a=="string"?a:a.toString("utf-8");if(!r.authenticated){if(c.length>1e6){b(e,{type:"auth.failed",message:"Message too large"});return}await S(c)||u("Authentication required");return}if(f)try{f(c)}catch{}}),e.on("close",()=>{r.authTimeout&&clearTimeout(r.authTimeout),M.delete(e),l("ws_disconnected",{ip:t}),p?.(),o&&(o.dispose(),o=null)}),e.on("error",()=>{r.authTimeout&&clearTimeout(r.authTimeout),M.delete(e),p?.(),o&&(o.dispose(),o=null)})});function Ze(e,n,s,t,m){const r=j(e),o=new URL(e.url||"/",`http://${e.headers.host}`),f=B(e)||o.searchParams.get("token")||"";if(!(f?y.validateToken(f):null)){l("ws_rejected_remote_auth",{ip:r,agentId:t}),I(n,401,"Unauthorized");return}const u=ne(t);if(!u){l("ws_rejected_remote_offline",{ip:r,agentId:t}),I(n,503,"Agent offline");return}if(u.relayCount>=Ne){I(n,429,"Too many connections to agent");return}const k=e.headers.origin,p=h.security.allowedOrigins;if(k&&!(k===`http://${e.headers.host}`||k===`https://${e.headers.host}`)&&(p.length===0||!p.includes(k))){I(n,403,"Origin not allowed");return}const S=et();L.handleUpgrade(e,n,s,a=>{if(u.registerRelay(S,m,a),g.bumpRelayConns(t,1),!u.attach(S,m)){a.send(JSON.stringify({type:"data",data:`\r
4
+ `),e.destroy()}Ze.on("upgrade",(e,n,s)=>{const t=new URL(e.url||"/",`http://${e.headers.host}`),m=t.pathname;if(m==="/agent/ws"){nt(e,n,s);return}const r=m.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(r){qe(e,n,s,decodeURIComponent(r[1]),decodeURIComponent(r[2]));return}const a=m.match(/^\/ws\/(.+)$/);if(!a){n.destroy();return}const c=B(e),l=e.headers.origin,p=h.security.allowedOrigins;if(l&&!(l===`http://${e.headers.host}`||l===`https://${e.headers.host}`)&&(p.length===0||!p.includes(l))){d("ws_rejected_origin",{ip:c,origin:l}),R(n,403,"Origin not allowed");return}const f=Ge(c);if(f>=h.security.maxConnectionsPerIp){d("ws_rejected_per_ip_cap",{ip:c,liveFromIp:f}),R(n,429,"Too many connections");return}const g=W(e)||t.searchParams.get("token")||"",_=g?k.validateToken(g):null;L.handleUpgrade(e,n,s,S=>{L.emit("connection",S,e,decodeURIComponent(a[1]),c,_)})}),L.on("connection",(e,n,s,t,m)=>{const r={ws:e,ip:t,authenticated:!1,authTimeout:null},a=(()=>{try{const i=new URL(n.url||"/",`http://${n.headers.host}`).searchParams,o=Number(i.get("cols")),u=Number(i.get("rows"));if(Number.isInteger(o)&&Number.isInteger(u)&&o>0&&u>0&&o<=1e3&&u<=1e3)return{cols:o,rows:u}}catch{}})();P.set(e,r),d("ws_connected",{ip:t});let c=null,l=null;function p(i,o){r.authenticated=!0,r.authTimeout&&(clearTimeout(r.authTimeout),r.authTimeout=null),d("auth_success",{ip:t,method:i,tokenName:o}),I(e,{type:"auth.ok",setupMode:!h.passwordHash}),g()}function f(i,o={}){I(e,{type:"auth.failed",message:i,...o})}function g(){c||(c=Oe(s,{send:i=>I(e,i),onMessage:i=>{l=i},onClose:i=>{_=i}},{terminalBufferConfig:E,getSessionPaneTarget:Y,capturePaneTail:K,capturePaneHistoryChunk:Q,toCrlf:Z,handleClientMessage:z,acquireControlClient:X,initialCols:a?.cols,initialRows:a?.rows,tmuxSocketPath:be()??void 0,onPtyExit:(i,o)=>v(e,1e3,"pty exited"),onSpawnError:(i,o)=>v(e,1011,"pty spawn failed")}))}let _=null;async function S(i){let o;try{o=JSON.parse(i)}catch{return!1}if(o.type==="auth.token"&&typeof o.token=="string"){const u=k.validateToken(o.token);if(!u){d("token_auth_failed",{ip:t});const w=A.recordFailure(t);return w.permanentLock&&(k.revokeAll(),d("permanent_lock",{ip:t,failures:w.failures})),f("Invalid or expired token",{permanentLock:w.permanentLock,retryAfterMs:w.retryAfterMs}),!0}return k.touch(u.tokenHash),p("token",u.name),!0}if(o.type==="auth"&&typeof o.password=="string"){const u=A.check(t);if(!u.allowed)return d("rate_limited",{ip:t,retryAfterMs:u.retryAfterMs,permanentLock:u.permanentLock}),f("Too many attempts",{permanentLock:u.permanentLock,retryAfterMs:u.retryAfterMs}),!0;if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!rt(t))return d("setup_rejected_remote",{ip:t}),f("First-run setup must be performed from localhost"),!0;if(j)return f("Password setup in progress"),!0;const C=Se(o.password);if(C)return f(C),!0;j=!0;try{h.passwordHash=await Te(o.password),Be(h),d("password_set",{ip:t})}finally{j=!1}const{stored:F,plaintext:le}=k.createAccessToken("setup",h.security.tokenTtlDays);return I(e,{type:"auth.ok",setupMode:!0,token:le}),d("token_created",{ip:t,name:F.name,tokenId:F.id}),d("auth_success",{ip:t,method:"password",tokenName:F.name}),g(),!0}if(!h.passwordHash)return f("Server not configured"),!0;if(!await we(o.password,h.passwordHash)){const C=A.recordFailure(t);return d("auth_failed",{ip:t,method:"password",failures:C.failures,permanentLock:C.permanentLock}),C.permanentLock&&(k.revokeAll(),d("permanent_lock",{ip:t,failures:C.failures})),f("Incorrect password",{permanentLock:C.permanentLock,retryAfterMs:C.retryAfterMs}),!0}A.recordSuccess(t);const b=`ws-${t}`,{plaintext:ce}=k.createAccessToken(b,h.security.tokenTtlDays);return p("password",b),I(e,{type:"auth.ok",setupMode:!1,token:ce}),!0}return!1}m?(k.touch(m.tokenHash),p("token",m.name)):(I(e,{type:"auth.required",setupMode:!h.passwordHash}),r.authTimeout=setTimeout(()=>{r.authenticated||(d("auth_timeout",{ip:t}),f("Authentication timeout"),v(e,4e3,"Auth timeout"))},h.security.authTimeoutMs)),e.on("message",async i=>{const o=typeof i=="string"?i:i.toString("utf-8");if(!r.authenticated){if(o.length>1e6){I(e,{type:"auth.failed",message:"Message too large"});return}await S(o)||f("Authentication required");return}if(l)try{l(o)}catch{}}),e.on("close",()=>{r.authTimeout&&clearTimeout(r.authTimeout),P.delete(e),d("ws_disconnected",{ip:t}),_?.(),c&&(c.dispose(),c=null)}),e.on("error",()=>{r.authTimeout&&clearTimeout(r.authTimeout),P.delete(e),_?.(),c&&(c.dispose(),c=null)})});function qe(e,n,s,t,m){const r=B(e),a=new URL(e.url||"/",`http://${e.headers.host}`),c=W(e)||a.searchParams.get("token")||"";if(!(c?k.validateToken(c):null)){d("ws_rejected_remote_auth",{ip:r,agentId:t}),R(n,401,"Unauthorized");return}const p=oe(t);if(!p){d("ws_rejected_remote_offline",{ip:r,agentId:t}),R(n,503,"Agent offline");return}if(p.relayCount>=Fe){R(n,429,"Too many connections to agent");return}const f=e.headers.origin,g=h.security.allowedOrigins;if(f&&!(f===`http://${e.headers.host}`||f===`https://${e.headers.host}`)&&(g.length===0||!g.includes(f))){R(n,403,"Origin not allowed");return}const _=tt(),S=(()=>{try{const i=new URL(e.url||"/",`http://${e.headers.host}`).searchParams,o=Number(i.get("cols")),u=Number(i.get("rows"));if(Number.isInteger(o)&&Number.isInteger(u)&&o>0&&u>0&&o<=1e3&&u<=1e3)return{cols:o,rows:u}}catch{}})();L.handleUpgrade(e,n,s,i=>{if(p.registerRelay(_,m,i),y.bumpRelayConns(t,1),!p.attach(_,m,S)){i.send(JSON.stringify({type:"data",data:`\r
5
5
  \x1B[31magent offline\x1B[0m\r
6
- `})),a.close(1011,"agent offline"),u.relayClosed(S),g.bumpRelayConns(t,-1);return}a.on("message",d=>{const w=typeof d=="string"?d:d.toString("utf-8");let _;try{_=JSON.parse(w)}catch{return}(_.type==="input"||_.type==="resize"||_.type==="load_history")&&u.sendToAgent(S,_)});const c=()=>{u.relayClosed(S),g.bumpRelayConns(t,-1)};a.on("close",c),a.on("error",c)})}let qe=1;function et(){return qe++}function tt(e,n,s){const t=j(e),m=e.headers.origin;if(m){l("agent_rejected_origin",{ip:t,origin:m}),I(n,403,"Origin not allowed");return}L.handleUpgrade(e,n,s,r=>{let o=null,f=setTimeout(()=>{l("agent_hello_timeout",{ip:t}),v(r,4e3,"hello timeout")},1e4);r.on("message",(i,u)=>{if(o){g.touch(o.agentId),u?o.handleData(Array.isArray(i)?Buffer.concat(i):Buffer.from(i)):o.handleMessage(typeof i=="string"?i:Buffer.from(i).toString("utf-8"));return}const k=typeof i=="string"?i:i.toString("utf-8");let p;try{p=JSON.parse(k)}catch{v(r,1008,"bad hello");return}if(p.type!=="hello"||typeof p.token!="string"){v(r,1008,"expected hello");return}if(!A.check(t).allowed){l("agent_rate_limited",{ip:t}),v(r,429,"Too many attempts");return}const a=Fe(p.token);if(!a){A.recordFailure(t),l("agent_bad_token",{ip:t}),Z(r,{type:"hello_err",reason:"invalid token"}),v(r,4003,"invalid token");return}const c=typeof p.name=="string"&&p.name.trim()?p.name.trim().slice(0,64):"agent",d=a.id;if(!g.register(d,c,r,a.tokenHash)){l("agent_cap_reached",{ip:t}),Z(r,{type:"hello_err",reason:"too many agents"}),v(r,4004,"too many agents");return}clearTimeout(f),A.recordSuccess(t),l("agent_connected",{ip:t,agentId:d,name:c}),o=new Ue(r,d,{onSessions:_=>g.setSessions(d,_),onClose:_=>{T.get(d)===_&&T.delete(d)}}),T.set(d,o),g.touch(d),r.send(JSON.stringify({type:"hello_ok",agentId:d})),o.sendSessionsReq()}),r.on("close",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId),l("agent_disconnected",{ip:t,agentId:o.agentId})),clearTimeout(f)}),r.on("error",()=>{o&&(o.dispose(),T.delete(o.agentId),g.unregister(o.agentId)),clearTimeout(f)})})}const nt=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=g.pruneOffline(nt);for(const s of e){const t=g.peek(s);t&&t.ws.readyState===F.CLOSED&&(g.unregister(s),T.get(s)?.dispose(),T.delete(s))}const n=new Set(He().map(s=>s.tokenHash));for(const[s,t]of T){const m=g.peek(s);if(!(!m||n.has(m.tokenHash))){l("agent_token_revoked",{agentId:s}),t.dispose(),T.delete(s),g.unregister(s);try{m.ws.close(4003,"token revoked")}catch{}}}y.purgeExpired()},10*1e3).unref();function ae(){re(),$e(),Pe();for(const e of T.values())e.dispose();T.clear();for(const[e,n]of M){try{e.close(1001,"Server shutting down")}catch{}n.authTimeout&&clearTimeout(n.authTimeout)}M.clear();for(const e of ee)try{e.kill()}catch{}ee.clear(),A.dispose(),process.exit(0)}process.on("SIGINT",ae),process.on("SIGTERM",ae);function ot(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}async function rt(e){const{createAgentToken:n,listAgentTokens:s,removeAgentToken:t}=await import("./lib/agent-tokens.js"),[m,...r]=e;switch(m){case"add":{const o=r.find(u=>!u.startsWith("-"))||"agent",{id:f,token:i}=n(o);console.log("\u2713 agent token created"),console.log(` id: ${f}`),console.log(` name: ${o}`),console.log(` token: ${i}`),console.log(`
7
- Run on the remote machine:`),console.log(` tmux-web agent --hub wss://YOUR_HUB --token ${i} --name "${o}"`);return}case"list":{const o=s();if(!o.length){console.log("No agent tokens. Create one with: tmux-web agent-token add --name <name>");return}console.log("Agent tokens:");for(const f of o)console.log(` ${f.id} ${f.name} (created ${new Date(f.createdAt).toISOString()})`);return}case"remove":{const o=r.find(i=>!i.startsWith("-"));o||(console.error("usage: tmux-web agent-token remove <id>"),process.exit(1)),t(o)||(console.error(`no token with id ${o}`),process.exit(1)),console.log(`\u2713 token ${o} removed`);return}default:console.error("usage: tmux-web agent-token <add|list|remove>"),process.exit(1)}}
6
+ `})),i.close(1011,"agent offline"),p.relayClosed(_),y.bumpRelayConns(t,-1);return}i.on("message",u=>{const w=typeof u=="string"?u:u.toString("utf-8");let b;try{b=JSON.parse(w)}catch{return}(b.type==="input"||b.type==="resize"||b.type==="load_history")&&p.sendToAgent(_,b)});const o=()=>{p.relayClosed(_),y.bumpRelayConns(t,-1)};i.on("close",o),i.on("error",o)})}let et=1;function tt(){return et++}function nt(e,n,s){const t=B(e),m=e.headers.origin;if(m){d("agent_rejected_origin",{ip:t,origin:m}),R(n,403,"Origin not allowed");return}L.handleUpgrade(e,n,s,r=>{let a=null,c=setTimeout(()=>{d("agent_hello_timeout",{ip:t}),v(r,4e3,"hello timeout")},1e4);r.on("message",(l,p)=>{if(a){y.touch(a.agentId),p?a.handleData(Array.isArray(l)?Buffer.concat(l):Buffer.from(l)):a.handleMessage(typeof l=="string"?l:Buffer.from(l).toString("utf-8"));return}const f=typeof l=="string"?l:l.toString("utf-8");let g;try{g=JSON.parse(f)}catch{v(r,1008,"bad hello");return}if(g.type!=="hello"||typeof g.token!="string"){v(r,1008,"expected hello");return}if(!A.check(t).allowed){d("agent_rate_limited",{ip:t}),v(r,429,"Too many attempts");return}const S=He(g.token);if(!S){A.recordFailure(t),d("agent_bad_token",{ip:t}),q(r,{type:"hello_err",reason:"invalid token"}),v(r,4003,"invalid token");return}const i=typeof g.name=="string"&&g.name.trim()?g.name.trim().slice(0,64):"agent",o=S.id;if(!y.register(o,i,r,S.tokenHash)){d("agent_cap_reached",{ip:t}),q(r,{type:"hello_err",reason:"too many agents"}),v(r,4004,"too many agents");return}clearTimeout(c),A.recordSuccess(t),d("agent_connected",{ip:t,agentId:o,name:i}),a=new Ee(r,o,{onSessions:w=>y.setSessions(o,w),onClose:w=>{T.get(o)===w&&T.delete(o)}}),T.set(o,a),y.touch(o),r.send(JSON.stringify({type:"hello_ok",agentId:o})),a.sendSessionsReq()}),r.on("close",()=>{a&&(a.dispose(),T.delete(a.agentId),y.unregister(a.agentId),d("agent_disconnected",{ip:t,agentId:a.agentId})),clearTimeout(c)}),r.on("error",()=>{a&&(a.dispose(),T.delete(a.agentId),y.unregister(a.agentId)),clearTimeout(c)})})}const ot=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=y.pruneOffline(ot);for(const s of e){const t=y.peek(s);t&&t.ws.readyState===H.CLOSED&&(y.unregister(s),T.get(s)?.dispose(),T.delete(s))}const n=new Set(je().map(s=>s.tokenHash));for(const[s,t]of T){const m=y.peek(s);if(!(!m||n.has(m.tokenHash))){d("agent_token_revoked",{agentId:s}),t.dispose(),T.delete(s),y.unregister(s);try{m.ws.close(4003,"token revoked")}catch{}}}k.purgeExpired()},10*1e3).unref();function ie(){se(),$e(),Ne();for(const e of T.values())e.dispose();T.clear();for(const[e,n]of P){try{e.close(1001,"Server shutting down")}catch{}n.authTimeout&&clearTimeout(n.authTimeout)}P.clear();for(const e of te)try{e.kill()}catch{}te.clear(),A.dispose(),process.exit(0)}process.on("SIGINT",ie),process.on("SIGTERM",ie);function rt(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}async function st(e){const{createAgentToken:n,listAgentTokens:s,removeAgentToken:t}=await import("./lib/agent-tokens.js"),[m,...r]=e;switch(m){case"add":{const a=r.find(p=>!p.startsWith("-"))||"agent",{id:c,token:l}=n(a);console.log("\u2713 agent token created"),console.log(` id: ${c}`),console.log(` name: ${a}`),console.log(` token: ${l}`),console.log(`
7
+ Run on the remote machine:`),console.log(` tmux-web agent --hub wss://YOUR_HUB --token ${l} --name "${a}"`);return}case"list":{const a=s();if(!a.length){console.log("No agent tokens. Create one with: tmux-web agent-token add --name <name>");return}console.log("Agent tokens:");for(const c of a)console.log(` ${c.id} ${c.name} (created ${new Date(c.createdAt).toISOString()})`);return}case"remove":{const a=r.find(l=>!l.startsWith("-"));a||(console.error("usage: tmux-web agent-token remove <id>"),process.exit(1)),t(a)||(console.error(`no token with id ${a}`),process.exit(1)),console.log(`\u2713 token ${a} removed`);return}default:console.error("usage: tmux-web agent-token <add|list|remove>"),process.exit(1)}}
@@ -1 +1 @@
1
- import{WebSocket as C}from"ws";import{attachTerminal as D}from"./attach-terminal.js";import{encodeBinaryFrame as J,decodeBinaryFrame as j,BIN_KIND_HTTP_BODY as $}from"./agent-channel.js";const K=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),W=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),F=new Set(["content-type","content-disposition","location","cache-control","etag"]);function Q(o){const T=o.log??(e=>console.log(`[agent] ${e}`)),f=e=>console.error(`[agent] ${e}`);let s=null,S=1e3,B=null,m=!1;const d=new Map,h=new Map,y=new Map,b=new Set;function A(e){return b.has(e)?Promise.resolve():new Promise(t=>{y.set(e,t),setTimeout(()=>{y.delete(e)&&t()},3e4)})}let p=null,_=null,E="",w=null;const g={state:"idle",agentId:null,hub:o.hub,name:o.name};function u(e,t){t!==void 0&&(g.lastError=t),g.state=e,g.agentId=w,o.onStatus?.({...g})}function c(e){s&&s.readyState===C.OPEN&&s.send(JSON.stringify(e))}function k(){let e;try{e=o.listSessions()}catch{return}const t=JSON.stringify(e);t!==E&&(E=t,c({type:"sessions",sessions:e}))}function v(){p||(p=setInterval(k,W)),_||(_=setInterval(()=>c({type:"ping",ts:Date.now()}),K))}function H(){p&&(clearInterval(p),p=null),_&&(clearInterval(_),_=null)}async function O(e){let t;if(e.hasBody){await A(e.id),b.delete(e.id);const r=h.get(e.id);h.delete(e.id),t=r&&r.length?Buffer.concat(r):Buffer.alloc(0)}const n=new URL(e.path,"http://agent.invalid");let i;try{const r={method:e.method,headers:e.headers};t&&t.length&&(r.body=t),i=await Promise.race([o.app.request(new Request(n,r)),new Promise((G,x)=>setTimeout(()=>x(new Error("app.request timed out")),3e4))])}catch(r){f(`tunnel request failed: ${e.method} ${e.path} ${r.message}`),c({type:"http_resp",id:e.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const I={};i.headers.forEach((r,l)=>{F.has(l.toLowerCase())&&(I[l]=r)});const a=Buffer.from(await i.arrayBuffer());if(c({type:"http_resp",id:e.id,status:i.status,headers:I,hasBody:a.length>0}),a.length>0){for(let l=0;l<a.length;l+=65536){if(!s||s.readyState!==C.OPEN)return;s.send(J($,e.id,a.subarray(l,l+65536)))}c({type:"http_body_end",id:e.id})}}function L(e){const t=e.session;let n=!1;try{n=o.listSessions().some(a=>a.name===t)}catch{n=!1}if(!n){c({type:"attach_err",connId:e.connId,session:t,message:"no such session"});return}let i=null;const I=D(t,{send:a=>c({type:"ws_to_hub",connId:e.connId,msg:a}),onMessage:a=>{i=a},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,tmuxSocketPath:o.tmuxSocketPath,onPtyExit:(a,r)=>{c({type:"ws_close",connId:e.connId,code:1e3,reason:`tmux exited (${r})`})},onSpawnError:(a,r)=>{f(`attach failed for session "${e.session}": ${r}`),c({type:"attach_err",connId:e.connId,session:t,message:r})},onAttached:a=>{c({type:"attach_ok",connId:e.connId,session:t})}});d.set(e.connId,{onMessage:a=>i?.(a),dispose:()=>{d.delete(e.connId),I.dispose()}})}function R(e){let t;try{t=JSON.parse(e)}catch{return}switch(t.type){case"hello_ok":w=t.agentId,S=1e3,T(`connected to hub as "${w}"`),u("connected"),k(),v();break;case"hello_err":if(f(`hub rejected us: ${t.reason}`),u("error",t.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":k();break;case"http_req":O(t);break;case"http_body_end":{b.add(t.id);const n=y.get(t.id);n&&(y.delete(t.id),n());break}case"attach":L(t);break;case"ws_to_agent":{const n=d.get(t.connId);if(n)try{n.onMessage(JSON.stringify(t.msg))}catch{}break}case"detach":{const n=d.get(t.connId);n&&n.dispose();break}case"pong":break;default:break}}function q(e){const t=j(e);if(t&&t.kind===$){const n=h.get(t.id)??[];n.push(t.payload),h.set(t.id,n)}}function P(){H();for(const e of d.values())try{e.dispose()}catch{}d.clear(),h.clear(),y.clear(),b.clear(),E=""}function M(){if(s){try{s.removeAllListeners()}catch{}s=null}if(P(),m)return;u("connecting");const e=S+Math.random()*500;T(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout(N,e),S=Math.min(S*2,6e4)}function N(){if(m||s)return;const e=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let t;try{t=new C(e)}catch(n){f(`bad hub URL: ${n.message} (${e})`),u("error",`bad hub URL: ${n.message}`);return}s=t,u("connecting"),t.on("open",()=>{T(`connecting to ${e}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),t.on("message",(n,i)=>{i?q(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):R(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),t.on("close",()=>{s===t&&M()}),t.on("error",n=>{f(`connection error: ${n.message}`),s===t&&M()})}function U(){if(!m){if(m=!0,B&&clearTimeout(B),P(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}w=null,u("idle")}}return N(),{stop:U,status:()=>({...g})}}export{Q as startAgentClient};
1
+ import{WebSocket as C}from"ws";import{attachTerminal as D}from"./attach-terminal.js";import{encodeBinaryFrame as J,decodeBinaryFrame as j,BIN_KIND_HTTP_BODY as $}from"./agent-channel.js";const K=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),W=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),F=new Set(["content-type","content-disposition","location","cache-control","etag"]);function Q(o){const T=o.log??(e=>console.log(`[agent] ${e}`)),f=e=>console.error(`[agent] ${e}`);let s=null,g=1e3,B=null,m=!1;const d=new Map,h=new Map,y=new Map,w=new Set;function A(e){return w.has(e)?Promise.resolve():new Promise(t=>{y.set(e,t),setTimeout(()=>{y.delete(e)&&t()},3e4)})}let p=null,_=null,E="",b=null;const S={state:"idle",agentId:null,hub:o.hub,name:o.name};function u(e,t){t!==void 0&&(S.lastError=t),S.state=e,S.agentId=b,o.onStatus?.({...S})}function c(e){s&&s.readyState===C.OPEN&&s.send(JSON.stringify(e))}function k(){let e;try{e=o.listSessions()}catch{return}const t=JSON.stringify(e);t!==E&&(E=t,c({type:"sessions",sessions:e}))}function v(){p||(p=setInterval(k,W)),_||(_=setInterval(()=>c({type:"ping",ts:Date.now()}),K))}function H(){p&&(clearInterval(p),p=null),_&&(clearInterval(_),_=null)}async function O(e){let t;if(e.hasBody){await A(e.id),w.delete(e.id);const r=h.get(e.id);h.delete(e.id),t=r&&r.length?Buffer.concat(r):Buffer.alloc(0)}const n=new URL(e.path,"http://agent.invalid");let i;try{const r={method:e.method,headers:e.headers};t&&t.length&&(r.body=t),i=await Promise.race([o.app.request(new Request(n,r)),new Promise((G,x)=>setTimeout(()=>x(new Error("app.request timed out")),3e4))])}catch(r){f(`tunnel request failed: ${e.method} ${e.path} ${r.message}`),c({type:"http_resp",id:e.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const I={};i.headers.forEach((r,l)=>{F.has(l.toLowerCase())&&(I[l]=r)});const a=Buffer.from(await i.arrayBuffer());if(c({type:"http_resp",id:e.id,status:i.status,headers:I,hasBody:a.length>0}),a.length>0){for(let l=0;l<a.length;l+=65536){if(!s||s.readyState!==C.OPEN)return;s.send(J($,e.id,a.subarray(l,l+65536)))}c({type:"http_body_end",id:e.id})}}function R(e){const t=e.session;let n=!1;try{n=o.listSessions().some(a=>a.name===t)}catch{n=!1}if(!n){c({type:"attach_err",connId:e.connId,session:t,message:"no such session"});return}let i=null;const I=D(t,{send:a=>c({type:"ws_to_hub",connId:e.connId,msg:a}),onMessage:a=>{i=a},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,initialCols:e.cols,initialRows:e.rows,tmuxSocketPath:o.tmuxSocketPath,onPtyExit:(a,r)=>{c({type:"ws_close",connId:e.connId,code:1e3,reason:`tmux exited (${r})`})},onSpawnError:(a,r)=>{f(`attach failed for session "${e.session}": ${r}`),c({type:"attach_err",connId:e.connId,session:t,message:r})},onAttached:a=>{c({type:"attach_ok",connId:e.connId,session:t})}});d.set(e.connId,{onMessage:a=>i?.(a),dispose:()=>{d.delete(e.connId),I.dispose()}})}function L(e){let t;try{t=JSON.parse(e)}catch{return}switch(t.type){case"hello_ok":b=t.agentId,g=1e3,T(`connected to hub as "${b}"`),u("connected"),k(),v();break;case"hello_err":if(f(`hub rejected us: ${t.reason}`),u("error",t.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":k();break;case"http_req":O(t);break;case"http_body_end":{w.add(t.id);const n=y.get(t.id);n&&(y.delete(t.id),n());break}case"attach":R(t);break;case"ws_to_agent":{const n=d.get(t.connId);if(n)try{n.onMessage(JSON.stringify(t.msg))}catch{}break}case"detach":{const n=d.get(t.connId);n&&n.dispose();break}case"pong":break;default:break}}function q(e){const t=j(e);if(t&&t.kind===$){const n=h.get(t.id)??[];n.push(t.payload),h.set(t.id,n)}}function P(){H();for(const e of d.values())try{e.dispose()}catch{}d.clear(),h.clear(),y.clear(),w.clear(),E=""}function M(){if(s){try{s.removeAllListeners()}catch{}s=null}if(P(),m)return;u("connecting");const e=g+Math.random()*500;T(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout(N,e),g=Math.min(g*2,6e4)}function N(){if(m||s)return;const e=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let t;try{t=new C(e)}catch(n){f(`bad hub URL: ${n.message} (${e})`),u("error",`bad hub URL: ${n.message}`);return}s=t,u("connecting"),t.on("open",()=>{T(`connecting to ${e}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),t.on("message",(n,i)=>{i?q(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):L(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),t.on("close",()=>{s===t&&M()}),t.on("error",n=>{f(`connection error: ${n.message}`),s===t&&M()})}function U(){if(!m){if(m=!0,B&&clearTimeout(B),P(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}b=null,u("idle")}}return N(),{stop:U,status:()=>({...S})}}export{Q as startAgentClient};
@@ -1,3 +1,3 @@
1
- import{WebSocket as r}from"ws";import{decodeBinaryFrame as f,encodeBinaryFrame as g,BIN_KIND_TERM as u,BIN_KIND_HTTP_BODY as h}from"./agent-channel.js";const b=parseInt(process.env.TMUX_WEB_MAX_AGENT_CONNS||"64",10),_=parseInt(process.env.TMUX_WEB_TUNNEL_TIMEOUT_MS||"30000",10);let E=1,w=1;class O{ws;agentId;pending=new Map;relays=new Map;onSessions;onClose;disposed=!1;constructor(t,e,s){this.ws=t,this.agentId=e,this.onSessions=s.onSessions,this.onClose=s.onClose}send(t){return this.disposed||this.ws.readyState!==r.OPEN?!1:(this.ws.send(JSON.stringify(t)),!0)}attach(t,e){return this.send({type:"attach",connId:t,session:e})}detach(t){this.send({type:"detach",connId:t})}sendToAgent(t,e){this.send({type:"ws_to_agent",connId:t,msg:e})}sendSessionsReq(){this.send({type:"sessions_req"})}async httpRequest(t,e,s,n){const i=w++,o=!!n,c=new Promise((d,a)=>{const l=setTimeout(()=>{this.pending.delete(i),a(new Error("tunnel request timed out"))},_);this.pending.set(i,{id:i,resolve:d,reject:a,status:null,headers:null,body:[],hasBody:o,timer:l})});if(!this.send({type:"http_req",id:i,method:t,path:e,headers:s,hasBody:o}))return this.pending.delete(i),Promise.reject(new Error("agent offline"));if(n){const d=n.getReader();(async()=>{for(;;){const{done:l,value:p}=await d.read();if(l)break;const y=Buffer.from(p);this.ws.send(g(h,i,y))}this.send({type:"http_body_end",id:i})})().catch(()=>{this.send({type:"http_body_end",id:i})})}return c}registerRelay(t,e,s){this.relays.set(t,{connId:t,session:e,ws:s,closed:!1})}relayClosed(t){const e=this.relays.get(t);e&&!e.closed&&(e.closed=!0,this.relays.delete(t),this.detach(t))}get relayCount(){return this.relays.size}handleData(t){const e=Buffer.isBuffer(t)?t:Buffer.from(t),s=f(e);if(s){if(s.kind===u){const n=this.relays.get(s.id);n&&!n.closed&&n.ws.readyState===r.OPEN&&n.ws.send(JSON.stringify({type:"data",data:s.payload.toString("utf-8")}));return}if(s.kind===h){const n=this.pending.get(s.id);n&&n.body.push(s.payload)}}}handleMessage(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"sessions":this.onSessions?.(e.sessions);break;case"http_resp":{const s=this.pending.get(e.id);if(!s)break;s.status=e.status,s.headers=e.headers,e.hasBody||this.finalizeHttp(s.id);break}case"http_body_end":this.finalizeHttp(e.id);break;case"attach_ok":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"auth.ok",setupMode:!1}));break}case"attach_err":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"attach_failed",message:`Agent "${this.agentId}": cannot attach to session "${e.session}": ${e.message}`}));break}case"ws_to_hub":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify(e.msg));break}case"ws_close":{const s=this.relays.get(e.connId);if(s&&!s.closed){s.closed=!0;try{s.ws.close(e.code??1e3,e.reason??"agent closed")}catch{}this.relays.delete(e.connId)}break}case"ping":this.send({type:"pong",ts:e.ts});break;default:break}}finalizeHttp(t){const e=this.pending.get(t);if(!e)return;this.pending.delete(t),clearTimeout(e.timer);const s=e.status??500,n=e.headers??{},i=Buffer.concat(e.body),o={};for(const[c,d]of Object.entries(n)){const a=c.toLowerCase();(a==="content-type"||a==="content-disposition"||a==="cache-control"||a==="etag"||a==="location")&&(o[c]=d)}e.resolve(new Response(i,{status:s,headers:o}))}failAll(t){for(const e of this.pending.values())clearTimeout(e.timer),e.reject(new Error(t));this.pending.clear();for(const e of this.relays.values()){if(!e.closed&&e.ws.readyState===r.OPEN){e.ws.send(JSON.stringify({type:"data",data:`\r
1
+ import{WebSocket as r}from"ws";import{decodeBinaryFrame as f,encodeBinaryFrame as g,BIN_KIND_TERM as u,BIN_KIND_HTTP_BODY as h}from"./agent-channel.js";const b=parseInt(process.env.TMUX_WEB_MAX_AGENT_CONNS||"64",10),_=parseInt(process.env.TMUX_WEB_TUNNEL_TIMEOUT_MS||"30000",10);let E=1,w=1;class O{ws;agentId;pending=new Map;relays=new Map;onSessions;onClose;disposed=!1;constructor(t,e,s){this.ws=t,this.agentId=e,this.onSessions=s.onSessions,this.onClose=s.onClose}send(t){return this.disposed||this.ws.readyState!==r.OPEN?!1:(this.ws.send(JSON.stringify(t)),!0)}attach(t,e,s){return this.send({type:"attach",connId:t,session:e,cols:s?.cols,rows:s?.rows})}detach(t){this.send({type:"detach",connId:t})}sendToAgent(t,e){this.send({type:"ws_to_agent",connId:t,msg:e})}sendSessionsReq(){this.send({type:"sessions_req"})}async httpRequest(t,e,s,n){const a=w++,o=!!n,c=new Promise((d,i)=>{const l=setTimeout(()=>{this.pending.delete(a),i(new Error("tunnel request timed out"))},_);this.pending.set(a,{id:a,resolve:d,reject:i,status:null,headers:null,body:[],hasBody:o,timer:l})});if(!this.send({type:"http_req",id:a,method:t,path:e,headers:s,hasBody:o}))return this.pending.delete(a),Promise.reject(new Error("agent offline"));if(n){const d=n.getReader();(async()=>{for(;;){const{done:l,value:p}=await d.read();if(l)break;const y=Buffer.from(p);this.ws.send(g(h,a,y))}this.send({type:"http_body_end",id:a})})().catch(()=>{this.send({type:"http_body_end",id:a})})}return c}registerRelay(t,e,s){this.relays.set(t,{connId:t,session:e,ws:s,closed:!1})}relayClosed(t){const e=this.relays.get(t);e&&!e.closed&&(e.closed=!0,this.relays.delete(t),this.detach(t))}get relayCount(){return this.relays.size}handleData(t){const e=Buffer.isBuffer(t)?t:Buffer.from(t),s=f(e);if(s){if(s.kind===u){const n=this.relays.get(s.id);n&&!n.closed&&n.ws.readyState===r.OPEN&&n.ws.send(JSON.stringify({type:"data",data:s.payload.toString("utf-8")}));return}if(s.kind===h){const n=this.pending.get(s.id);n&&n.body.push(s.payload)}}}handleMessage(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"sessions":this.onSessions?.(e.sessions);break;case"http_resp":{const s=this.pending.get(e.id);if(!s)break;s.status=e.status,s.headers=e.headers,e.hasBody||this.finalizeHttp(s.id);break}case"http_body_end":this.finalizeHttp(e.id);break;case"attach_ok":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"auth.ok",setupMode:!1}));break}case"attach_err":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify({type:"attach_failed",message:`Agent "${this.agentId}": cannot attach to session "${e.session}": ${e.message}`}));break}case"ws_to_hub":{const s=this.relays.get(e.connId);s&&!s.closed&&s.ws.readyState===r.OPEN&&s.ws.send(JSON.stringify(e.msg));break}case"ws_close":{const s=this.relays.get(e.connId);if(s&&!s.closed){s.closed=!0;try{s.ws.close(e.code??1e3,e.reason??"agent closed")}catch{}this.relays.delete(e.connId)}break}case"ping":this.send({type:"pong",ts:e.ts});break;default:break}}finalizeHttp(t){const e=this.pending.get(t);if(!e)return;this.pending.delete(t),clearTimeout(e.timer);const s=e.status??500,n=e.headers??{},a=Buffer.concat(e.body),o={};for(const[c,d]of Object.entries(n)){const i=c.toLowerCase();(i==="content-type"||i==="content-disposition"||i==="cache-control"||i==="etag"||i==="location")&&(o[c]=d)}e.resolve(new Response(a,{status:s,headers:o}))}failAll(t){for(const e of this.pending.values())clearTimeout(e.timer),e.reject(new Error(t));this.pending.clear();for(const e of this.relays.values()){if(!e.closed&&e.ws.readyState===r.OPEN){e.ws.send(JSON.stringify({type:"data",data:`\r
2
2
  \x1B[31m--- agent disconnected (${t}) ---\x1B[0m\r
3
3
  `}));try{e.ws.close(1011,"agent disconnected")}catch{}}e.closed=!0}this.relays.clear()}dispose(){this.disposed||(this.disposed=!0,this.failAll("agent disconnected"),this.onClose?.(this))}}export{O as AgentChannel,b as MAX_RELAY_CONNS_PER_AGENT,_ as TUNNEL_REQUEST_TIMEOUT_MS};
@@ -1,6 +1,6 @@
1
- import*as O from"node-pty";import{tmuxEnv as _}from"./tmux-env.js";const f=new Set;function H(){for(const e of[...f])try{e()}catch{}f.clear()}function K(){return f.size}function j(e,r,n){const{initialLines:M,historyChunk:C,syncIdleMs:k,syncMaxMs:g}=n.terminalBufferConfig,A=n.spawn??(o=>{const c=n.tmuxSocketPath?["-S",n.tmuxSocketPath]:[];return c.push("attach-session","-t",o),O.spawn("tmux",c,{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:_()})});let a=null,y=e,h=null,w=0,S="",d=!0,m="",u=null,p=null,s=!1;const T=()=>{u&&(clearTimeout(u),u=null),p&&(clearTimeout(p),p=null)},P=()=>{if(!(!d||s)){T(),d=!1;try{y=n.getSessionPaneTarget(e)}catch{y=e}try{const o=n.capturePaneTail(y,M);r.send({type:"snapshot",data:n.toCrlf(o),lines:M})}catch{r.send({type:"data",data:`\r
2
- `})}m&&(r.send({type:"data",data:m}),m="")}},E=()=>{d&&(u&&clearTimeout(u),u=setTimeout(P,k))};function $(){if(s)return;try{a=A(e)}catch(t){const l=t?.message??String(t);r.send({type:"data",data:`\r
3
- \x1B[31mFailed to attach to tmux session "${e}": ${l}\x1B[0m\r
4
- `}),n.onSpawnError?.(e,l);return}let o=-1,c="";h=n.acquireControlClient(e,({activeIndex:t,windows:l})=>{r.send({type:"window_changed",activeIndex:t,windows:l});const x=l.map(D=>D.index).join(","),v=t!==o||x!==c;o=t,c=x}),w=Date.now(),p=setTimeout(P,g),n.onAttached?.(e),r.onMessage(b),a.onData(t=>{if(!s){if(Date.now()-w<g&&S.length<2048&&(S+=t),d){m+=t,E();return}r.send({type:"data",data:t})}}),a.onExit(({exitCode:t})=>{if(T(),a&&(a=null),!s){if(t!==0&&Date.now()-w<g){const l=S.trim();n.onSpawnError?.(e,l?`tmux attach-session failed: ${l}`:`tmux attach-session exited with code ${t}`);return}n.onPtyExit?.(e,t),r.send({type:"data",data:`\r
1
+ import*as O from"node-pty";import{tmuxEnv as _}from"./tmux-env.js";const f=new Set;function H(){for(const e of[...f])try{e()}catch{}f.clear()}function K(){return f.size}function j(e,r,n){const{initialLines:M,historyChunk:b,syncIdleMs:k,syncMaxMs:w}=n.terminalBufferConfig,A=n.spawn??(o=>{const l=n.tmuxSocketPath?["-S",n.tmuxSocketPath]:[];return l.push("attach-session","-t",o),O.spawn("tmux",l,{name:"xterm-256color",cols:n.initialCols??80,rows:n.initialRows??24,cwd:process.env.HOME||"/",env:_()})});let a=null,y=e,h=null,g=0,S="",d=!0,m="",s=null,p=null,i=!1;const T=()=>{s&&(clearTimeout(s),s=null),p&&(clearTimeout(p),p=null)},C=()=>{if(!(!d||i)){T(),d=!1;try{y=n.getSessionPaneTarget(e)}catch{y=e}try{const o=n.capturePaneTail(y,M);r.send({type:"snapshot",data:n.toCrlf(o),lines:M})}catch{r.send({type:"data",data:`\r
2
+ `})}m&&(r.send({type:"data",data:m}),m="")}},E=()=>{d&&(s&&clearTimeout(s),s=setTimeout(C,k))};function $(){if(i)return;try{a=A(e)}catch(t){const c=t?.message??String(t);r.send({type:"data",data:`\r
3
+ \x1B[31mFailed to attach to tmux session "${e}": ${c}\x1B[0m\r
4
+ `}),n.onSpawnError?.(e,c);return}let o=-1,l="";h=n.acquireControlClient(e,({activeIndex:t,windows:c})=>{r.send({type:"window_changed",activeIndex:t,windows:c});const x=c.map(D=>D.index).join(","),v=t!==o||x!==l;o=t,l=x}),g=Date.now(),p=setTimeout(C,w),n.onAttached?.(e),r.onMessage(P),a.onData(t=>{if(!i){if(Date.now()-g<w&&S.length<2048&&(S+=t),d){m+=t,E();return}r.send({type:"data",data:t})}}),a.onExit(({exitCode:t})=>{if(T(),a&&(a=null),!i){if(t!==0&&Date.now()-g<w){const c=S.trim();n.onSpawnError?.(e,c?`tmux attach-session failed: ${c}`:`tmux attach-session exited with code ${t}`);return}n.onPtyExit?.(e,t),r.send({type:"data",data:`\r
5
5
  \x1B[2m--- tmux exited (code ${t}) ---\x1B[0m\r
6
- `}),i()}})}function i(){if(!s){if(s=!0,T(),h&&(h(),h=null),a){try{a.kill()}catch{}a=null}f.delete(i)}}r.onClose(i);const b=o=>{if(s||!a)return;try{if(n.handleClientMessage(o,a))return}catch{return}let c;try{c=JSON.parse(o)}catch{return}if(c.type==="load_history"&&typeof c.before=="number"){const t=Math.max(0,Math.floor(c.before));try{const{data:l,lines:x}=n.capturePaneHistoryChunk(y,t,C);r.send({type:"history",data:n.toCrlf(l),before:t,lines:x})}catch{r.send({type:"history",data:"",before:t,lines:0})}}};return $(),f.add(i),{onMessage:b,dispose:i}}export{K as _liveAttachmentCount,j as attachTerminal,H as killAllAttachedPtys};
6
+ `}),u()}})}function u(){if(!i){if(i=!0,T(),h&&(h(),h=null),a){try{a.kill()}catch{}a=null}f.delete(u)}}r.onClose(u);const P=o=>{if(i||!a)return;try{if(n.handleClientMessage(o,a))return}catch{return}let l;try{l=JSON.parse(o)}catch{return}if(l.type==="load_history"&&typeof l.before=="number"){const t=Math.max(0,Math.floor(l.before));try{const{data:c,lines:x}=n.capturePaneHistoryChunk(y,t,b);r.send({type:"history",data:n.toCrlf(c),before:t,lines:x})}catch{r.send({type:"history",data:"",before:t,lines:0})}}};return $(),f.add(u),{onMessage:P,dispose:u}}export{K as _liveAttachmentCount,j as attachTerminal,H as killAllAttachedPtys};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {