tmux-weblink 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,36 +1,36 @@
1
- var Nt="https://esm.sh/ghostty-web@0.4.0",Wt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ce(t){return t.replace(Wt,"")}var de;function jt(){return de||(document.querySelector("link[data-tmux-web-xterm-css]")?de=Promise.resolve():(de=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),de))}var qe=class ft{terminal;fitAddon;container;constructor(e,n,a){this.container=e,this.terminal=n,this.fitAddon=a,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,a){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:n,convertEol:!1,theme:a});return new ft(e,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=Oe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e],c=a.viewportY,l=[];for(let u=o;u<=r;u++){let f=a.getLine(c+u);l.push(f?f.translateToString(!0):"")}return l.join(`
2
- `)}selectRows(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e];this.terminal.selectLines(a.viewportY+o,a.viewportY+r)}hasSelectionSupport(){return!0}},Vt=class mt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,a){let o=await import(Nt);await o.init();let r=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:a});return new mt(e,r)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=Oe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let n=Math.floor(e.width/this.charW),a=Math.floor(e.height/this.charH);if(n<10||a<5)return!1;if(n!==this.colsValue||a!==this.rowsValue){this.colsValue=n,this.rowsValue=a,this.terminal.resize(n,a);for(let o of this.resizeCallbacks)o({cols:n,rows:a})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,n){try{let a=this.terminal.buffer?.active;if(!a||typeof a.getLine!="function")return"";let[o,r]=e<=n?[e,n]:[n,e],c=typeof a.viewportY=="number"?a.viewportY:0,l=[];for(let u=o;u<=r;u++){let f=a.getLine(c+u);l.push(typeof f?.translateToString=="function"?f.translateToString(!0):"")}return l.join(`
3
- `)}catch{return""}}selectRows(e,n){try{if(typeof this.terminal?.selectLines!="function")return;let a=this.terminal.buffer?.active,o=typeof a?.viewportY=="number"?a.viewportY:0,[r,c]=e<=n?[e,n]:[n,e];this.terminal.selectLines(o+r,o+c)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let a=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=a,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Oe(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function Ft(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let a;if(n.renderer==="ghostty")try{a=await Vt.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),a=await qe.create(t,n.scrollback,n.theme)}else a=await qe.create(t,n.scrollback,n.theme);let o,r=0,c,l=null,u=0,f="connecting",B=0,se=!1,oe=[],z="",Y=!1,V=1e3,ht=location.protocol==="https:"?"wss:":"ws:",vt=n.wsBase||"/ws",yt=ht+"//"+location.host+vt+"/"+encodeURIComponent(e),gt="/api/session/"+encodeURIComponent(e)+"/upload",wt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function bt(){return oe.join("")+z}async function Lt(i,s=0){let d=i?a.viewportY():0,L=i?a.baseY():0,T=ce(bt());if(a.reset(),!T){i||a.scrollToBottom();return}await a.write(T),i?a.scrollToLine(L+d+s):a.scrollToBottom()}function F(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function Et(i){let s;try{s=JSON.parse(i)}catch{f==="live"&&(z+=i,a.write(ce(i)));return}if(s.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?F({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),V=1e3,K(),F({type:"resize",cols:a.cols,rows:a.rows}),w();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=ce(s.data);oe=[d],z="",B=typeof s.lines=="number"?s.lines:n.terminal.initialLines,f="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"){se=!1,s.lines>0&&s.data&&(oe.unshift(s.data),B+=s.lines,Lt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let d=ce(s.data);if(f==="connecting"){f="live",z=d,a.write(d);return}f==="live"&&(z+=d,a.write(d))}}function De(){let i=yt,s=localStorage.getItem("tmux-web-token");s&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(i),o.onopen=()=>{f="connecting",B=0,se=!1,oe=[],z="",a.reset(),V=1e3},o.onmessage=d=>{typeof d.data=="string"&&Et(d.data)},o.onclose=()=>{Y||(f="connecting",setTimeout(()=>{V=Math.min(V*2,1e4),Y||De()},V))},o.onerror=()=>o?.close()}function le(i){F({type:"input",data:i})}function K(){return a.fit()}function Ee(){t.classList.remove("terminal-pending")}function w(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Ee()}),c=setTimeout(()=>{K()&&Ee()},120)}function kt(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function Re(i){return typeof i=="string"&&i.startsWith("image/")}function $e(i){return i?.files?.length?i.files[0]??null:null}function xt(i){if(!i?.items)return null;for(let s=0;s<i.items.length;s++){let d=i.items[s];if(d?.kind==="file"&&Re(d.type))return d.getAsFile()}return null}async function Tt(i){let s=new FormData,d=i instanceof File&&i.name?i.name:"upload";s.append("file",i,d);let L=localStorage.getItem("tmux-web-token"),T={};L&&(T.Authorization="Bearer "+L);let Ce=await fetch(gt,{method:"POST",body:s,headers:T});if(!Ce.ok){let Ke="upload failed";try{let Ue=await Ce.json();Ue.error&&(Ke=Ue.error)}catch{}throw new Error(Ke)}let Fe=await Ce.json();if(!Fe.path)throw new Error("no path in response");return Fe.path}async function ke(i){try{let s=await Tt(i);le(s)}catch{}}a.onData(i=>le(i)),a.onResize(({cols:i,rows:s})=>F({type:"resize",cols:i,rows:s})),a.onScroll(()=>{f!=="live"||se||!a.isNearScrollbackTop()||(se=!0,F({type:"load_history",before:B}))});let U=!1,q=!1,k=null,D=null,O=document.createElement("div");O.className="terminal-copy-banner",O.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',O.hidden=!0,t.appendChild(O);let P=document.createElement("div");P.className="terminal-copy-banner terminal-copy-toast",P.textContent="Copied",P.hidden=!0,t.appendChild(P);function Ae(i){let s=t.getBoundingClientRect(),d=s.height>0&&a.rows>0?s.height/a.rows:18;return Math.max(0,Math.min(a.rows-1,Math.floor((i-s.top)/d)))}async function ze(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let s=document.createElement("textarea");s.value=i,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 _;function Ye(){P.hidden=!1,_&&clearTimeout(_),_=setTimeout(()=>{P.hidden=!0},1e3)}function X(i){U=i,O.hidden=!i,t.classList.toggle("terminal-copy-mode-active",i),i&&a.focus()}function Pe(){X(!U)}function Ct(){return k===null||D===null?null:k<=D?[k,D]:[D,k]}function Mt(i){Y||i.button!==0||q||(U||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),q=!0,k=Ae(i.clientY),D=k,a.selectRows(k,k),document.addEventListener("mousemove",xe,!0),document.addEventListener("mouseup",Te,!0))}function xe(i){Y||!q||k===null||(i.preventDefault(),i.stopPropagation(),D=Ae(i.clientY),a.selectRows(k,D))}function Te(i){if(Y||!q||k===null)return;i.preventDefault(),i.stopPropagation();let s=Ct();document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),q=!1,X(!1),s&&(ze(a.getRowsText(s[0],s[1])).then(Ye),a.focus())}t.addEventListener("mousedown",Mt,!0),a.attachCustomKeyEventHandler(i=>{if(i.type!=="keydown")return!0;if(U){if(i.key==="Escape")return i.preventDefault(),X(!1),!1;if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC")return i.preventDefault(),ze(a.getScreenText()).then(Ye),X(!1),!1}return i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey?(i.preventDefault(),Pe(),!1):(i.ctrlKey||i.metaKey)&&i.code==="KeyV"?(i.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let d of s)for(let L of d.types)if(Re(L)){let T=await d.getType(L);await ke(T);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&a.pasteText(Ne(s))}catch{}})(),!1):!0});let re=0;function Ne(i){return i.split(`\r
1
+ var jt="https://esm.sh/ghostty-web@0.4.0",Vt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ce(t){return t.replace(Vt,"")}var de;function Ft(){return de||(document.querySelector("link[data-tmux-web-xterm-css]")?de=Promise.resolve():(de=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),de))}var Ue=class ht{terminal;fitAddon;container;constructor(e,n,a){this.container=e,this.terminal=n,this.fitAddon=a,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,a){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Ft()]),c=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:a});return new ht(e,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=Oe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e],c=a.viewportY,l=[];for(let u=o;u<=r;u++){let p=a.getLine(c+u);l.push(p?p.translateToString(!0):"")}return l.join(`
2
+ `)}selectRows(e,n){let a=this.terminal.buffer.active,[o,r]=e<=n?[e,n]:[n,e];this.terminal.selectLines(a.viewportY+o,a.viewportY+r)}hasSelectionSupport(){return!0}},_t=class vt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,a){let o=await import(jt);await o.init();let r=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:a});return new vt(e,r)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=Oe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let n=Math.floor(e.width/this.charW),a=Math.floor(e.height/this.charH);if(n<10||a<5)return!1;if(n!==this.colsValue||a!==this.rowsValue){this.colsValue=n,this.rowsValue=a,this.terminal.resize(n,a);for(let o of this.resizeCallbacks)o({cols:n,rows:a})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,n){try{let a=this.terminal.buffer?.active;if(!a||typeof a.getLine!="function")return"";let[o,r]=e<=n?[e,n]:[n,e],c=typeof a.viewportY=="number"?a.viewportY:0,l=[];for(let u=o;u<=r;u++){let p=a.getLine(c+u);l.push(typeof p?.translateToString=="function"?p.translateToString(!0):"")}return l.join(`
3
+ `)}catch{return""}}selectRows(e,n){try{if(typeof this.terminal?.selectLines!="function")return;let a=this.terminal.buffer?.active,o=typeof a?.viewportY=="number"?a.viewportY:0,[r,c]=e<=n?[e,n]:[n,e];this.terminal.selectLines(o+r,o+c)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let a=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=a,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function Oe(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function Kt(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let a;if(n.renderer==="ghostty")try{a=await _t.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),a=await Ue.create(t,n.scrollback,n.theme)}else a=await Ue.create(t,n.scrollback,n.theme);let o,r=0,c,l=null,u=0,p="connecting",I=0,se=!1,oe=[],z="",N=!1,V=1e3,yt=location.protocol==="https:"?"wss:":"ws:",gt=n.wsBase||"/ws",wt=yt+"//"+location.host+gt+"/"+encodeURIComponent(e),bt="/api/session/"+encodeURIComponent(e)+"/upload",Et=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Lt(){return oe.join("")+z}async function kt(i,s=0){let d=i?a.viewportY():0,L=i?a.baseY():0,C=ce(Lt());if(a.reset(),!C){i||a.scrollToBottom();return}await a.write(C),i?a.scrollToLine(L+d+s):a.scrollToBottom()}function F(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function xt(i){let s;try{s=JSON.parse(i)}catch{p==="live"&&(z+=i,a.write(ce(i)));return}if(s.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?F({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),V=1e3,_(),F({type:"resize",cols:a.cols,rows:a.rows}),b();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=ce(s.data);oe=[d],z="",I=typeof s.lines=="number"?s.lines:n.terminal.initialLines,p="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"){se=!1,s.lines>0&&s.data&&(oe.unshift(s.data),I+=s.lines,kt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let d=ce(s.data);if(p==="connecting"){p="live",z=d,a.write(d);return}p==="live"&&(z+=d,a.write(d))}}function De(){let i=wt,s=localStorage.getItem("tmux-web-token");s&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(i),o.onopen=()=>{p="connecting",I=0,se=!1,oe=[],z="",a.reset(),V=1e3},o.onmessage=d=>{typeof d.data=="string"&&xt(d.data)},o.onclose=()=>{N||(p="connecting",setTimeout(()=>{V=Math.min(V*2,1e4),N||De()},V))},o.onerror=()=>o?.close()}function le(i){F({type:"input",data:i})}function _(){return a.fit()}function Le(){t.classList.remove("terminal-pending")}function b(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),_(),r=requestAnimationFrame(()=>{r=0,_()&&Le()}),c=setTimeout(()=>{_()&&Le()},120)}function Tt(){b(),setTimeout(b,50),setTimeout(b,150),setTimeout(b,300)}function Re(i){return typeof i=="string"&&i.startsWith("image/")}function Ae(i){return i?.files?.length?i.files[0]??null:null}function Ct(i){if(!i?.items)return null;for(let s=0;s<i.items.length;s++){let d=i.items[s];if(d?.kind==="file"&&Re(d.type))return d.getAsFile()}return null}async function Mt(i){let s=new FormData,d=i instanceof File&&i.name?i.name:"upload";s.append("file",i,d);let L=localStorage.getItem("tmux-web-token"),C={};L&&(C.Authorization="Bearer "+L);let Ce=await fetch(bt,{method:"POST",body:s,headers:C});if(!Ce.ok){let _e="upload failed";try{let Ke=await Ce.json();Ke.error&&(_e=Ke.error)}catch{}throw new Error(_e)}let Fe=await Ce.json();if(!Fe.path)throw new Error("no path in response");return Fe.path}async function ke(i){try{let s=await Mt(i);le(s)}catch{}}a.onData(i=>le(i)),a.onResize(({cols:i,rows:s})=>F({type:"resize",cols:i,rows:s})),a.onScroll(()=>{p!=="live"||se||!a.isNearScrollbackTop()||(se=!0,F({type:"load_history",before:I}))});let K=!1,U=!1,x=null,D=null,O=document.createElement("div");O.className="terminal-copy-banner",O.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',O.hidden=!0,t.appendChild(O);let P=document.createElement("div");P.className="terminal-copy-banner terminal-copy-toast",P.textContent="Copied",P.hidden=!0,t.appendChild(P);function $e(i){let s=t.getBoundingClientRect(),d=s.height>0&&a.rows>0?s.height/a.rows:18;return Math.max(0,Math.min(a.rows-1,Math.floor((i-s.top)/d)))}async function ze(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let s=document.createElement("textarea");s.value=i,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 Ne(){P.hidden=!1,q&&clearTimeout(q),q=setTimeout(()=>{P.hidden=!0},1e3)}function X(i){K=i,O.hidden=!i,t.classList.toggle("terminal-copy-mode-active",i),i&&a.focus()}function Pe(){X(!K)}function It(){return x===null||D===null?null:x<=D?[x,D]:[D,x]}function Bt(i){N||i.button!==0||U||(K||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),U=!0,x=$e(i.clientY),D=x,a.selectRows(x,x),document.addEventListener("mousemove",xe,!0),document.addEventListener("mouseup",Te,!0))}function xe(i){N||!U||x===null||(i.preventDefault(),i.stopPropagation(),D=$e(i.clientY),a.selectRows(x,D))}function Te(i){if(N||!U||x===null)return;i.preventDefault(),i.stopPropagation();let s=It();document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),U=!1,X(!1),s&&(ze(a.getRowsText(s[0],s[1])).then(Ne),a.focus())}t.addEventListener("mousedown",Bt,!0),a.attachCustomKeyEventHandler(i=>{if(i.type!=="keydown")return!0;if(K){if(i.key==="Escape")return i.preventDefault(),X(!1),!1;if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC")return i.preventDefault(),ze(a.getScreenText()).then(Ne),X(!1),!1}return i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey?(i.preventDefault(),Pe(),!1):(i.ctrlKey||i.metaKey)&&i.code==="KeyV"?(i.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let d of s)for(let L of d.types)if(Re(L)){let C=await d.getType(L);await ke(C);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&a.pasteText(Ye(s))}catch{}})(),!1):!0});let re=0;function Ye(i){return i.split(`\r
4
4
  `).join(`
5
5
  `).split(`
6
- `).join("\r")}async function Bt(i){let s=xt(i.clipboardData);if(s){i.preventDefault(),i.stopPropagation();let T=Date.now();if(T-re<50)return;re=T,await ke(s);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let L=Date.now();L-re<50||(re=L,a.pasteText(Ne(d)))}let J=0;function It(i){i.preventDefault(),J++,$e(i.dataTransfer)&&t.classList.add("terminal-drag-over")}function St(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function Ht(i){i.preventDefault(),J--,J<=0&&(J=0,t.classList.remove("terminal-drag-over"))}async function Dt(i){i.preventDefault(),J=0,t.classList.remove("terminal-drag-over");let s=$e(i.dataTransfer);s&&await ke(s)}function We(i){!wt||i.key!=="Escape"||U||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||a.isFocused()&&(i.preventDefault(),i.stopPropagation(),le("\x1B"))}t.addEventListener("paste",Bt),t.addEventListener("dragenter",It),t.addEventListener("dragover",St),t.addEventListener("dragleave",Ht),t.addEventListener("drop",Dt),document.addEventListener("keydown",We,!0);function Rt(i){if(i.touches.length!==1){l=null;return}let s=i.touches[0];s&&(l={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},i.stopPropagation())}function $t(i){if(!l||i.touches.length!==1)return;let s=i.touches[0];if(!s)return;let d=s.clientY-l.startY,L=s.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(L))return;l.scrolling=!0,u=Date.now()+500}i.preventDefault(),i.stopPropagation(),Pt(-(s.clientY-l.lastY),s.clientX,s.clientY),l.lastX=s.clientX,l.lastY=s.clientY}function At(i){if(!l)return;let s=l.scrolling;l=null,i.stopPropagation(),s?(u=Date.now()+500,i.preventDefault()):a.focus()}function zt(i){l=null,i.stopPropagation()}function Yt(i){i.pointerType==="touch"&&Date.now()<u&&(i.preventDefault(),i.stopPropagation())}function Pt(i,s,d){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:i,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:d,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",Rt,{passive:!0,capture:!0}),t.addEventListener("touchmove",$t,{passive:!1,capture:!0}),t.addEventListener("touchend",At,{passive:!1,capture:!0}),t.addEventListener("touchcancel",zt,{passive:!0,capture:!0}),t.addEventListener("pointerup",Yt,!0);function je(){w()}window.addEventListener("resize",je),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);let Ve=new ResizeObserver(()=>w());return Ve.observe(t),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),t.addEventListener("focusin",kt,!0),requestAnimationFrame(()=>{K()&&Ee(),De(),w()}),{destroy(){if(Y=!0,X(!1),_&&clearTimeout(_),document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",je),Ve.disconnect(),document.removeEventListener("keydown",We,!0)},focus(){a.focus()},sendInput(i){le(i)},toggleCopyMode(){Pe()}}})()}var N=window.__TMUX_WEB_SHELL__,h=null,y=null,C=null,_e="sessions",S="",ue="",pe="",Xe="";function b(t){return C?"/a/"+encodeURIComponent(C)+t:t}var m=document.getElementById("sidebar-content"),R=document.getElementById("main-placeholder"),E=document.getElementById("terminal-container"),Z=document.getElementById("file-editor"),Kt=document.getElementById("fe-path"),x=document.getElementById("fe-status"),G=document.getElementById("fe-content"),$=document.getElementById("fe-save"),Ut=document.getElementById("fe-delete"),qt=document.getElementById("fe-back"),Je=document.getElementById("fe-new-name"),Ot=document.getElementById("fe-new-btn"),Me=document.getElementById("settings-backdrop"),Ze=document.getElementById("settings-popover"),Ge=document.querySelector(".sidebar");function _t(){return window.matchMedia("(max-width: 640px)").matches}function Be(){_t()&&Ge.classList.add("collapsed")}function Qe(){Ge.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Qe(),et("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Qe(),et("files")});function et(t){_e=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?W():an()}async function W(){m.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=await(await fetch("/api/sidebar/sessions")).json(),n=e.recent||[];for(let o of n){let r=document.createElement("div");r.className="session-item"+(o.name===y&&!C?" 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>
7
- <span>${p(o.name)}</span>
8
- <button class="session-edit-btn" data-session="${p(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>
9
- `,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||Q(o.name)}),m.appendChild(r);let c=r.querySelector(".session-edit-btn");c&&c.addEventListener("click",l=>{l.stopPropagation(),cn(o.name,c)})}let a=e.agents||[];for(let o of a){if(!o.sessions||o.sessions.length===0)continue;let r=document.createElement("div");r.className="sidebar-section-label"+(o.online?"":" agent-offline-label"),r.textContent=o.agentName+(o.online?"":" (offline)"),m.appendChild(r);for(let c of o.sessions){let l=document.createElement("div");l.className="session-item"+(c.name===y&&C===o.agentId?" active":"")+(o.online?"":" session-offline"),l.setAttribute("data-session",c.name),l.setAttribute("data-agent",o.agentId),l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${c.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>
10
- <span>${p(c.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&l.addEventListener("click",u=>{u.target.closest(".session-edit-btn")||Q(c.name,o.agentId)}),m.appendChild(l)}}}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function p(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function Q(t,e){if(!(y===t&&h&&C===(e||null))){Z.style.display="none",R.style.display="none",E.style.display="",E.classList.add("terminal-pending"),Be(),h&&(be(),h.destroy(),h=null),y=t,C=e||null,window.__tmuxWebScopeAgent=C;for(let n of m.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===t&&(n.getAttribute("data-agent")||null)===C);try{h=await Ft(E,t,{terminal:N.terminal,scrollback:N.scrollback,theme:N.theme,renderer:N.renderer,wsBase:C?"/ws/a/"+encodeURIComponent(C):N.wsBase||"/ws"}),dt()}catch(n){console.error("[shell] terminal init failed:",n);let a=n&&typeof n=="object"&&"message"in n?n.message:String(n);E.textContent="Failed to open terminal for "+t+": "+a,E.classList.remove("terminal-pending")}}}var fe=[],v=null,tt=!1;function Xt(t){if(!v||!v.files.length)return null;let e=v.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let a of v.files)if(a.path===n)return a.status;return null}async function Jt(t){tt=!0;try{let e=await fetch(b("/api/git/status?path="+encodeURIComponent(t)));e.ok?v=await e.json():v=null}catch{v=null}finally{tt=!1}}function Zt(){if(!v||!v.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=v,a=`<div class="sidebar-git"><div class="git-branch-badge">
6
+ `).join("\r")}async function St(i){let s=Ct(i.clipboardData);if(s){i.preventDefault(),i.stopPropagation();let C=Date.now();if(C-re<50)return;re=C,await ke(s);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let L=Date.now();L-re<50||(re=L,a.pasteText(Ye(d)))}let J=0;function Ht(i){i.preventDefault(),J++,Ae(i.dataTransfer)&&t.classList.add("terminal-drag-over")}function Dt(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function Rt(i){i.preventDefault(),J--,J<=0&&(J=0,t.classList.remove("terminal-drag-over"))}async function At(i){i.preventDefault(),J=0,t.classList.remove("terminal-drag-over");let s=Ae(i.dataTransfer);s&&await ke(s)}function We(i){!Et||i.key!=="Escape"||K||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||a.isFocused()&&(i.preventDefault(),i.stopPropagation(),le("\x1B"))}t.addEventListener("paste",St),t.addEventListener("dragenter",Ht),t.addEventListener("dragover",Dt),t.addEventListener("dragleave",Rt),t.addEventListener("drop",At),document.addEventListener("keydown",We,!0);function $t(i){if(i.touches.length!==1){l=null;return}let s=i.touches[0];s&&(l={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},i.stopPropagation())}function zt(i){if(!l||i.touches.length!==1)return;let s=i.touches[0];if(!s)return;let d=s.clientY-l.startY,L=s.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(L))return;l.scrolling=!0,u=Date.now()+500}i.preventDefault(),i.stopPropagation(),Wt(-(s.clientY-l.lastY),s.clientX,s.clientY),l.lastX=s.clientX,l.lastY=s.clientY}function Nt(i){if(!l)return;let s=l.scrolling;l=null,i.stopPropagation(),s?(u=Date.now()+500,i.preventDefault()):a.focus()}function Pt(i){l=null,i.stopPropagation()}function Yt(i){i.pointerType==="touch"&&Date.now()<u&&(i.preventDefault(),i.stopPropagation())}function Wt(i,s,d){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:i,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:d,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",$t,{passive:!0,capture:!0}),t.addEventListener("touchmove",zt,{passive:!1,capture:!0}),t.addEventListener("touchend",Nt,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Pt,{passive:!0,capture:!0}),t.addEventListener("pointerup",Yt,!0);function je(){b()}window.addEventListener("resize",je),window.visualViewport?.addEventListener("resize",b),window.visualViewport?.addEventListener("scroll",b);let Ve=new ResizeObserver(()=>b());return Ve.observe(t),document.fonts?.ready.then(()=>{b(),setTimeout(b,50),setTimeout(b,200)}),t.addEventListener("focusin",Tt,!0),requestAnimationFrame(()=>{_()&&Le(),De(),b()}),{destroy(){if(N=!0,X(!1),q&&clearTimeout(q),document.removeEventListener("mousemove",xe,!0),document.removeEventListener("mouseup",Te,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",je),Ve.disconnect(),document.removeEventListener("keydown",We,!0)},focus(){a.focus()},sendInput(i){le(i)},toggleCopyMode(){Pe()}}})()}var Y=window.__TMUX_WEB_SHELL__,h=null,y=null,qe=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,w=qe;window.__tmuxWebScopeAgent=qe;var Xe="sessions",S="",ue="",pe="",Je="";function E(t){return w?"/a/"+encodeURIComponent(w)+t:t}var m=document.getElementById("sidebar-content"),R=document.getElementById("main-placeholder"),k=document.getElementById("terminal-container"),Z=document.getElementById("file-editor"),Ut=document.getElementById("fe-path"),T=document.getElementById("fe-status"),G=document.getElementById("fe-content"),A=document.getElementById("fe-save"),Ot=document.getElementById("fe-delete"),qt=document.getElementById("fe-back"),Ze=document.getElementById("fe-new-name"),Xt=document.getElementById("fe-new-btn"),Me=document.getElementById("settings-backdrop"),Ge=document.getElementById("settings-popover"),Qe=document.querySelector(".sidebar");function Jt(){return window.matchMedia("(max-width: 640px)").matches}function Ie(){Jt()&&Qe.classList.add("collapsed")}function et(){Qe.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{et(),tt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{et(),tt("files")});function tt(t){Xe=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?W():ln()}var nt="";async function W(){try{let e=await(await fetch("/api/sidebar/sessions")).json(),n=JSON.stringify({recent:e.recent,agents:e.agents});if(n===nt)return;nt=n,Zt(e)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Zt(t){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let e=t.recent||[],n=document.createElement("div");n.className="sidebar-section-label"+(w?" clickable":""),n.textContent=w?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",w&&(n.title="Back to hub",n.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(n);for(let o of e){let r=document.createElement("div");r.className="session-item"+(o.name===y&&!w?" 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>
7
+ <span>${f(o.name)}</span>
8
+ <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>
9
+ `,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||Q(o.name)}),m.appendChild(r);let c=r.querySelector(".session-edit-btn");c&&c.addEventListener("click",l=>{l.stopPropagation(),pn(o.name,c)})}let a=t.agents||[];for(let o of a){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===w,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 u=document.createElement("div");u.className="session-item"+(l.name===y&&w===o.agentId?" active":"")+(o.online?"":" session-offline"),u.setAttribute("data-session",l.name),u.setAttribute("data-agent",o.agentId),u.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>
10
+ <span>${f(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&u.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||Q(l.name,o.agentId)}),m.appendChild(u)}}}function f(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function Q(t,e){if(!(y===t&&h&&w===(e||null))){Z.style.display="none",R.style.display="none",k.style.display="",k.classList.add("terminal-pending"),Ie(),h&&(be(),h.destroy(),h=null),y=t,w=e||null,window.__tmuxWebScopeAgent=w;for(let n of m.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===t&&(n.getAttribute("data-agent")||null)===w);try{h=await Kt(k,t,{terminal:Y.terminal,scrollback:Y.scrollback,theme:Y.theme,renderer:Y.renderer,wsBase:w?"/ws/a/"+encodeURIComponent(w):Y.wsBase||"/ws"}),pt()}catch(n){console.error("[shell] terminal init failed:",n);let a=n&&typeof n=="object"&&"message"in n?n.message:String(n);k.textContent="Failed to open terminal for "+t+": "+a,k.classList.remove("terminal-pending")}}}var fe=[],v=null,it=!1;function Gt(t){if(!v||!v.files.length)return null;let e=v.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let a of v.files)if(a.path===n)return a.status;return null}async function Qt(t){it=!0;try{let e=await fetch(E("/api/git/status?path="+encodeURIComponent(t)));e.ok?v=await e.json():v=null}catch{v=null}finally{it=!1}}function en(){if(!v||!v.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=v,a=`<div class="sidebar-git"><div class="git-branch-badge">
11
11
  <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>
12
- ${p(t)}</div>`;return(e>0||n>0)&&(a+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),a+"</div>"}m.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&j&&v&&(t.stopPropagation(),ot())});var ee=document.getElementById("header-git"),nt=document.getElementById("git-diff-view"),it=document.getElementById("gd-path"),me=document.getElementById("gd-status"),he=document.getElementById("gd-content"),Gt=document.getElementById("gd-back"),j=null;function te(t){if(!t||!t.branch){ee.style.display="none",j=null;return}j=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:a}=t,o=`<span class="branch">${p(e)}</span>`;(n>0||a>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${a}</span>`),ee.innerHTML=o,ee.style.display="block"}ee.addEventListener("click",t=>{j&&v&&(t.stopPropagation(),ot())});var g=document.getElementById("git-popover"),ve=document.getElementById("git-popover-backdrop");function Ie(){let t=ee.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function at(){let t=j,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(ge(),t){_e="files",document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),H(t)}})}var st="";function ot(){if(!v)return;let t=v.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
12
+ ${f(t)}</div>`;return(e>0||n>0)&&(a+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),a+"</div>"}m.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&j&&v&&(t.stopPropagation(),rt())});var ee=document.getElementById("header-git"),at=document.getElementById("git-diff-view"),st=document.getElementById("gd-path"),me=document.getElementById("gd-status"),he=document.getElementById("gd-content"),tn=document.getElementById("gd-back"),j=null;function te(t){if(!t||!t.branch){ee.style.display="none",j=null;return}j=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:a}=t,o=`<span class="branch">${f(e)}</span>`;(n>0||a>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${a}</span>`),ee.innerHTML=o,ee.style.display="block"}ee.addEventListener("click",t=>{j&&v&&(t.stopPropagation(),rt())});var g=document.getElementById("git-popover"),ve=document.getElementById("git-popover-backdrop");function Be(){let t=ee.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function ot(){let t=j,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(ge(),t){Xe="files",document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),H(t)}})}var lt="";function rt(){if(!v)return;let t=v.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
13
13
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let a=t[n],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}e+=`<div class="git-popover-item" data-idx="${n}">
14
- ${c}<span class="file-path">${p(a.path)}</span>
14
+ ${c}<span class="file-path">${f(a.path)}</span>
15
15
  <span class="file-add">${o>0?"+"+o:""}</span>
16
16
  <span class="file-del">${r>0?"-"+r:""}</span>
17
- </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',st=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let a=parseInt(n.dataset.idx||"0",10),o=t[a];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>rt(o.path)))})}Ie(),g.classList.add("open"),ve.classList.add("open"),at()}function Qt(){return window.innerWidth>=1024}function lt(t,e){let n=(t+(e&&t?`
17
+ </div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',lt=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let a=parseInt(n.dataset.idx||"0",10),o=t[a];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>dt(o.path)))})}Be(),g.classList.add("open"),ve.classList.add("open"),ot()}function nn(){return window.innerWidth>=1024}function ct(t,e){let n=(t+(e&&t?`
18
18
  `:"")+e).split(`
19
- `),a="",o=0,r=0;for(let c of n){let l=c;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),a+=`<div class="diff-line hunk"><span class="ln-body">${p(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">${p(l)}</span></div>`);continue}if(l.startsWith("+"))r++,a+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${p(l)}</span></div>`;else if(l.startsWith("-"))o++,a+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${p(l)}</span></div>`;else if(l.startsWith("\\"))a+=`<div class="diff-line"><span class="ln-body">${p(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">${p(l)}</span></div>`}}return{html:a,lineCount:n.length}}var ye=null;async function rt(t){let e=j;if(e)if(ge(),Qt()){E.style.display="none",h&&(be(),h.destroy(),h=null,ye=y,y=null),te(null),Z.style.display="none",R.style.display="none",nt.style.display="flex",it.textContent=t,me.textContent="Loading\u2026",he.innerHTML="";try{let n=await fetch(b("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();tn(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);me.textContent="Error",he.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(a)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
+ `),a="",o=0,r=0;for(let c of n){let l=c;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[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:n.length}}var ye=null;async function dt(t){let e=j;if(e)if(ge(),nn()){k.style.display="none",h&&(be(),h.destroy(),h=null,ye=y,y=null),te(null),Z.style.display="none",R.style.display="none",at.style.display="flex",st.textContent=t,me.textContent="Loading\u2026",he.innerHTML="";try{let n=await fetch(E("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();sn(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);me.textContent="Error",he.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${f(a)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
20
20
  <button class="diff-back">\u2190 Back</button>
21
- <span class="diff-filename">${p(t)}</span>
21
+ <span class="diff-filename">${f(t)}</span>
22
22
  <span class="diff-loading">Loading\u2026</span>
23
- </div></div>`,Ie(),g.classList.add("open"),ve.classList.add("open");try{let n=await fetch(b("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();en(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ </div></div>`,Be(),g.classList.add("open"),ve.classList.add("open");try{let n=await fetch(E("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t)));if(!n.ok)throw new Error(await n.text());let a=await n.json();an(t,a.diff,a.stagedDiff)}catch(n){let a=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
- <span class="diff-filename">${p(t)}</span>
26
- </div><div class="diff-empty">Error loading diff: ${p(a)}</div></div>`}}}function en(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
25
+ <span class="diff-filename">${f(t)}</span>
26
+ </div><div class="diff-empty">Error loading diff: ${f(a)}</div></div>`}}}function an(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
27
  <button class="diff-back">\u2190 Back</button>
28
- <span class="diff-filename">${p(t)}</span>
29
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:a,lineCount:o}=lt(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
28
+ <span class="diff-filename">${f(t)}</span>
29
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:a,lineCount:o}=ct(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
30
30
  <button class="diff-back">\u2190 Back</button>
31
- <span class="diff-filename">${p(t)}</span>
31
+ <span class="diff-filename">${f(t)}</span>
32
32
  <span class="diff-filesize">${o} lines</span>
33
- </div><div class="diff-content">${a}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=st,Ie(),v&&g.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=v.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>rt(l.path)))}),at()})}function tn(t,e,n){if(!e&&!n){me.textContent="No diff",he.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}=lt(e,n);it.textContent=t,me.textContent=o+" lines",he.innerHTML=a}function nn(){nt.style.display="none",ye?(E.style.display="",E.classList.add("terminal-pending"),Q(ye),ye=null):y?(E.style.display="",E.classList.add("terminal-pending"),dt()):R.style.display="flex"}Gt.addEventListener("click",nn);function ge(){g.classList.remove("open"),ve.classList.remove("open")}ve.addEventListener("click",ge),document.addEventListener("keydown",t=>{t.key==="Escape"&&(ge(),Le())});async function ct(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let a=await n.json();v=a,te(a);return}}}catch{}v=null,te(null)}var we=null;function dt(){be(),y&&(ct(y),we=setInterval(()=>{y&&ct(y)},3e4))}function be(){we&&(clearInterval(we),we=null)}function an(){if(fe=N.fsRoots||[],fe.length===0){if(y){sn(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 t of fe){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${p(t)}`,e.addEventListener("click",()=>H(t)),m.appendChild(e)}}async function sn(t){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch(b("/api/fs/session-path?session="+encodeURIComponent(t)))).json();e.path?H(e.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 H(t){S=t,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=Jt(t);try{let n=await(await fetch(b("/api/fs/list?path="+encodeURIComponent(t)))).json();await e,m.innerHTML="",te(v),m.innerHTML+=Zt();let a=on(t);if(a&&!fe.some(r=>t===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>H(a)),m.appendChild(r)}if(n.dirs)for(let r of n.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> ${p(c)}`,l.addEventListener("click",()=>H(r)),m.appendChild(l)}else m.innerHTML="";let o=n.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=Xt(r),u="",f="";if(l)switch(l){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let B=document.createElement("div");B.className="file-tree-item file"+(u?" "+u:""),B.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${p(c)}`,B.addEventListener("click",()=>ln(r)),m.appendChild(B)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function on(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function ln(t){ue=t,E.style.display="none",h&&(be(),h.destroy(),h=null,y=null),te(null),Be(),R.style.display="none",Z.style.display="flex",Kt.textContent=t,x.textContent="Loading...",$.style.display="none";try{let e=await fetch(b("/api/file?path="+encodeURIComponent(t)));if(!e.ok){let a=await e.json();x.textContent=a.error||"Failed to load",G.value="";return}let n=await e.json();G.value=n.content,pe=n.content,Xe=n.content,x.textContent=n.size+" bytes",$.style.display=""}catch{x.textContent="Failed to load",G.value=""}}G.addEventListener("input",()=>{pe=G.value}),$.addEventListener("click",async()=>{$.textContent="Saving...",$.disabled=!0;try{let t=await fetch(b("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue,content:pe})});if(!t.ok){let e=await t.json();x.textContent=e.error||"Save failed";return}Xe=pe,x.textContent="Saved"}catch{x.textContent="Save failed"}finally{$.disabled=!1,$.textContent="Save"}}),Ut.addEventListener("click",async()=>{if(confirm("Delete "+ue+"?"))try{let t=await fetch(b("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue})});if(!t.ok){let e=await t.json();x.textContent=e.error||"Delete failed";return}Z.style.display="none",R.style.display="flex",S&&H(S)}catch{x.textContent="Delete failed"}}),qt.addEventListener("click",()=>{Z.style.display="none",R.style.display="flex",S&&H(S)}),Ot.addEventListener("click",async()=>{let t=Je.value.trim();if(!t)return;let e=S+"/"+t;try{let n=await fetch(b("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let a=await n.json();x.textContent=a.error||"Create failed";return}Je.value="",S&&H(S)}catch{x.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=Ze.classList.toggle("open");Me.classList.toggle("open",t)}),Me.addEventListener("click",()=>{Ze.classList.remove("open"),Me.classList.remove("open")});var A="",ne=document.createElement("div");ne.className="session-popover-backdrop",document.body.appendChild(ne);var I=document.createElement("div");I.id="session-popover",I.className="session-popover",I.style.display="none",I.innerHTML=`<div class="sp-field">
33
+ </div><div class="diff-content">${a}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=lt,Be(),v&&g.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=v.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>dt(l.path)))}),ot()})}function sn(t,e,n){if(!e&&!n){me.textContent="No diff",he.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}=ct(e,n);st.textContent=t,me.textContent=o+" lines",he.innerHTML=a}function on(){at.style.display="none",ye?(k.style.display="",k.classList.add("terminal-pending"),Q(ye),ye=null):y?(k.style.display="",k.classList.add("terminal-pending"),pt()):R.style.display="flex"}tn.addEventListener("click",on);function ge(){g.classList.remove("open"),ve.classList.remove("open")}ve.addEventListener("click",ge),document.addEventListener("keydown",t=>{t.key==="Escape"&&(ge(),Ee())});async function ut(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let a=await n.json();v=a,te(a);return}}}catch{}v=null,te(null)}var we=null;function pt(){be(),y&&(ut(y),we=setInterval(()=>{y&&ut(y)},3e4))}function be(){we&&(clearInterval(we),we=null)}function ln(){if(fe=Y.fsRoots||[],fe.length===0){if(y){rn(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 t of fe){let e=document.createElement("div");e.className="file-tree-item",e.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${f(t)}`,e.addEventListener("click",()=>H(t)),m.appendChild(e)}}async function rn(t){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch(E("/api/fs/session-path?session="+encodeURIComponent(t)))).json();e.path?H(e.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 H(t){S=t,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=Qt(t);try{let n=await(await fetch(E("/api/fs/list?path="+encodeURIComponent(t)))).json();await e,m.innerHTML="",te(v),m.innerHTML+=en();let a=cn(t);if(a&&!fe.some(r=>t===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>H(a)),m.appendChild(r)}if(n.dirs)for(let r of n.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",()=>H(r)),m.appendChild(l)}else m.innerHTML="";let o=n.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=Gt(r),u="",p="";if(l)switch(l){case"M":u="git-mod",p='<span class="git-file-status M">M</span>';break;case"A":u="git-add",p='<span class="git-file-status A">A</span>';break;case"D":u="git-del",p='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",p='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",p='<span class="git-file-status R">R</span>';break}let I=document.createElement("div");I.className="file-tree-item file"+(u?" "+u:""),I.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${p}${f(c)}`,I.addEventListener("click",()=>dn(r)),m.appendChild(I)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function cn(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function dn(t){ue=t,k.style.display="none",h&&(be(),h.destroy(),h=null,y=null),te(null),Ie(),R.style.display="none",Z.style.display="flex",Ut.textContent=t,T.textContent="Loading...",A.style.display="none";try{let e=await fetch(E("/api/file?path="+encodeURIComponent(t)));if(!e.ok){let a=await e.json();T.textContent=a.error||"Failed to load",G.value="";return}let n=await e.json();G.value=n.content,pe=n.content,Je=n.content,T.textContent=n.size+" bytes",A.style.display=""}catch{T.textContent="Failed to load",G.value=""}}G.addEventListener("input",()=>{pe=G.value}),A.addEventListener("click",async()=>{A.textContent="Saving...",A.disabled=!0;try{let t=await fetch(E("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue,content:pe})});if(!t.ok){let e=await t.json();T.textContent=e.error||"Save failed";return}Je=pe,T.textContent="Saved"}catch{T.textContent="Save failed"}finally{A.disabled=!1,A.textContent="Save"}}),Ot.addEventListener("click",async()=>{if(confirm("Delete "+ue+"?"))try{let t=await fetch(E("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:ue})});if(!t.ok){let e=await t.json();T.textContent=e.error||"Delete failed";return}Z.style.display="none",R.style.display="flex",S&&H(S)}catch{T.textContent="Delete failed"}}),qt.addEventListener("click",()=>{Z.style.display="none",R.style.display="flex",S&&H(S)}),Xt.addEventListener("click",async()=>{let t=Ze.value.trim();if(!t)return;let e=S+"/"+t;try{let n=await fetch(E("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let a=await n.json();T.textContent=a.error||"Create failed";return}Ze.value="",S&&H(S)}catch{T.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=Ge.classList.toggle("open");Me.classList.toggle("open",t)}),Me.addEventListener("click",()=>{Ge.classList.remove("open"),Me.classList.remove("open")});var $="",ne=document.createElement("div");ne.className="session-popover-backdrop",document.body.appendChild(ne);var B=document.createElement("div");B.id="session-popover",B.className="session-popover",B.style.display="none",B.innerHTML=`<div class="sp-field">
34
34
  <label>Rename session</label>
35
35
  <input type="text" id="sp-name" />
36
36
  </div>
@@ -39,5 +39,5 @@ var Nt="https://esm.sh/ghostty-web@0.4.0",Wt=/\x1b\](?:10|11|110|111|104)(?:;[^\
39
39
  <button class="btn" id="sp-cancel">Cancel</button>
40
40
  </div>
41
41
  <hr class="sp-divider" />
42
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(I);function rn(t){let e=t.getBoundingClientRect();I.style.top=e.bottom+4+"px",I.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function cn(t,e){A=t;let n=document.getElementById("sp-name");n.value=t,ne.classList.add("open"),I.style.display="block",rn(e),setTimeout(()=>n.focus(),50)}function ie(){ne.classList.remove("open"),I.style.display="none"}ne.addEventListener("click",ie),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===A){ie();return}try{if(!(await fetch(b("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:A,newName:t})})).ok)return;ie(),W(),y===A&&Q(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+A+'"?'))try{if(!(await fetch(b("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:A})})).ok)return;ie(),y===A&&(h&&(h.destroy(),h=null),y=null,E.style.display="none",R.style.display="flex"),W()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ie);var dn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!h)return;let n=dn[e.dataset.key];n&&(h.sendInput(n),h.focus())});var M=document.getElementById("mk-input"),ut=document.getElementById("mk-send");function pt(){if(!M||!h)return;let t=M.value;t&&(h.sendInput(t+"\r"),M.value="",M.rows=1,h.focus())}if(M&&(M.addEventListener("input",()=>{M.rows=1;let t=M.value.split(`
43
- `).length;M.rows=Math.min(t,4)}),M.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),pt())})),ut&&ut.addEventListener("click",pt),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Se=document.getElementById("process-panel"),ae=document.getElementById("proc-list"),un=document.getElementById("proc-close"),He=document.getElementById("proc-backdrop"),pn=document.getElementById("header-ram");function fn(){ae.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(b("/api/system/processes")).then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){ae.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let a=n.rss,o=a<1048576?(a/1024).toFixed(0)+"K":(a/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}ae.innerHTML=e;for(let n of ae.querySelectorAll(".proc-row")){let a=n.querySelector(".proc-kill");a&&a.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(b("/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(()=>{ae.innerHTML='<div class="proc-empty">Failed to load</div>'})}function mn(){Se.classList.add("open"),He.classList.add("open"),fn()}function Le(){Se.classList.remove("open"),He.classList.remove("open")}pn.addEventListener("click",t=>{t.stopPropagation(),Se.classList.contains("open")?Le():mn()}),un.addEventListener("click",Le),He.addEventListener("click",Le),window.__openSession=Q,window.__refreshSidebar=W,W(),Be(),setInterval(()=>{document.visibilityState==="visible"&&W()},1e4);
42
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(B);function un(t){let e=t.getBoundingClientRect();B.style.top=e.bottom+4+"px",B.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function pn(t,e){$=t;let n=document.getElementById("sp-name");n.value=t,ne.classList.add("open"),B.style.display="block",un(e),setTimeout(()=>n.focus(),50)}function ie(){ne.classList.remove("open"),B.style.display="none"}ne.addEventListener("click",ie),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===$){ie();return}try{if(!(await fetch(E("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:$,newName:t})})).ok)return;ie(),W(),y===$&&Q(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+$+'"?'))try{if(!(await fetch(E("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:$})})).ok)return;ie(),y===$&&(h&&(h.destroy(),h=null),y=null,k.style.display="none",R.style.display="flex"),W()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ie);var fn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!h)return;let n=fn[e.dataset.key];n&&(h.sendInput(n),h.focus())});var M=document.getElementById("mk-input"),ft=document.getElementById("mk-send");function mt(){if(!M||!h)return;let t=M.value;t&&(h.sendInput(t+"\r"),M.value="",M.rows=1,h.focus())}if(M&&(M.addEventListener("input",()=>{M.rows=1;let t=M.value.split(`
43
+ `).length;M.rows=Math.min(t,4)}),M.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),mt())})),ft&&ft.addEventListener("click",mt),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Se=document.getElementById("process-panel"),ae=document.getElementById("proc-list"),mn=document.getElementById("proc-close"),He=document.getElementById("proc-backdrop"),hn=document.getElementById("header-ram");function vn(){ae.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(E("/api/system/processes")).then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){ae.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let a=n.rss,o=a<1048576?(a/1024).toFixed(0)+"K":(a/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+f(n.command)+'">'+f(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}ae.innerHTML=e;for(let n of ae.querySelectorAll(".proc-row")){let a=n.querySelector(".proc-kill");a&&a.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(E("/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(()=>{ae.innerHTML='<div class="proc-empty">Failed to load</div>'})}function yn(){Se.classList.add("open"),He.classList.add("open"),vn()}function Ee(){Se.classList.remove("open"),He.classList.remove("open")}hn.addEventListener("click",t=>{t.stopPropagation(),Se.classList.contains("open")?Ee():yn()}),mn.addEventListener("click",Ee),He.addEventListener("click",Ee),window.__openSession=Q,window.__refreshSidebar=W,W(),Ie(),setInterval(()=>{document.visibilityState==="visible"&&W()},1e4);
@@ -1,31 +1,31 @@
1
- import{initTerminal as we}from"./terminal-core.js";const M=window.__TMUX_WEB_SHELL__;let p=null,u=null,b=null,ne="sessions",E="",z="",W="",se="";function g(e){return b?"/a/"+encodeURIComponent(b)+e:e}const r=document.getElementById("sidebar-content"),B=document.getElementById("main-placeholder"),y=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ee=document.getElementById("fe-path"),h=document.getElementById("fe-status"),$=document.getElementById("fe-content"),C=document.getElementById("fe-save"),ke=document.getElementById("fe-delete"),Be=document.getElementById("fe-back"),ie=document.getElementById("fe-new-name"),Ce=document.getElementById("fe-new-btn"),Y=document.getElementById("settings-backdrop"),oe=document.getElementById("settings-popover"),ae=document.querySelector(".sidebar");function Ie(){return window.matchMedia("(max-width: 640px)").matches}function Z(){Ie()&&ae.classList.add("collapsed")}function le(){ae.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{le(),ce("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{le(),ce("files")});function ce(e){ne=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?S():De()}async function S(){r.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=t.recent||[];for(const i of s){const o=document.createElement("div");o.className="session-item"+(i.name===u&&!b?" active":""),o.setAttribute("data-session",i.name),o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
1
+ import{initTerminal as ke}from"./terminal-core.js";const M=window.__TMUX_WEB_SHELL__;let p=null,u=null;const ne=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let v=ne;window.__tmuxWebScopeAgent=ne;let se="sessions",E="",j="",W="",ie="";function y(e){return v?"/a/"+encodeURIComponent(v)+e:e}const r=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),h=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ce=document.getElementById("fe-path"),b=document.getElementById("fe-status"),$=document.getElementById("fe-content"),I=document.getElementById("fe-save"),Ie=document.getElementById("fe-delete"),Be=document.getElementById("fe-back"),oe=document.getElementById("fe-new-name"),xe=document.getElementById("fe-new-btn"),Y=document.getElementById("settings-backdrop"),ae=document.getElementById("settings-popover"),le=document.querySelector(".sidebar");function Me(){return window.matchMedia("(max-width: 640px)").matches}function Z(){Me()&&le.classList.add("collapsed")}function ce(){le.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ce(),de("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ce(),de("files")});function de(e){se=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?S():_e()}let re="";async function S(){try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===re)return;re=s,Se(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function Se(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(v?" clickable":""),t.textContent=v?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",v&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t);for(const i of n){const l=document.createElement("div");l.className="session-item"+(i.name===u&&!v?" active":""),l.setAttribute("data-session",i.name),l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
2
  <span>${d(i.name)}</span>
3
3
  <button class="session-edit-btn" data-session="${d(i.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
4
- `,o.addEventListener("click",a=>{a.target.closest(".session-edit-btn")||R(i.name)}),r.appendChild(o);const c=o.querySelector(".session-edit-btn");c&&c.addEventListener("click",a=>{a.stopPropagation(),ze(i.name,c)})}const l=t.agents||[];for(const i of l){if(!i.sessions||i.sessions.length===0)continue;const o=document.createElement("div");o.className="sidebar-section-label"+(i.online?"":" agent-offline-label"),o.textContent=i.agentName+(i.online?"":" (offline)"),r.appendChild(o);for(const c of i.sessions){const a=document.createElement("div");a.className="session-item"+(c.name===u&&b===i.agentId?" active":"")+(i.online?"":" session-offline"),a.setAttribute("data-session",c.name),a.setAttribute("data-agent",i.agentId),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${c.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
5
- <span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",v=>{v.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(e,n){if(!(u===e&&p&&b===(n||null))){H.style.display="none",B.style.display="none",y.style.display="",y.classList.add("terminal-pending"),Z(),p&&(K(),p.destroy(),p=null),u=e,b=n||null,window.__tmuxWebScopeAgent=b;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===b);try{p=await we(y,e,{terminal:M.terminal,scrollback:M.scrollback,theme:M.theme,renderer:M.renderer,wsBase:b?"/ws/a/"+encodeURIComponent(b):M.wsBase||"/ws"}),he()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);y.textContent="Failed to open terminal for "+e+": "+s,y.classList.remove("terminal-pending")}}}let G=[],f=null,de=!1;function xe(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function Me(e){de=!0;try{const n=await fetch(g("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{de=!1}}function Se(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
4
+ `,l.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||R(i.name)}),r.appendChild(l);const o=l.querySelector(".session-edit-btn");o&&o.addEventListener("click",c=>{c.stopPropagation(),Oe(i.name,o)})}const s=e.agents||[];for(const i of s){if(!i.sessions||i.sessions.length===0)continue;const l=i.agentId===v,o=document.createElement("div");o.className="sidebar-section-label machine-label"+(i.online?"":" agent-offline-label")+(!l&&i.online?" clickable":""),o.textContent=i.agentName+(i.online?"":" (offline)"),!l&&i.online&&(o.title="\u6253\u5F00 "+i.agentName,o.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(i.agentId)+"/"})),r.appendChild(o);for(const c of i.sessions){const a=document.createElement("div");a.className="session-item"+(c.name===u&&v===i.agentId?" active":"")+(i.online?"":" session-offline"),a.setAttribute("data-session",c.name),a.setAttribute("data-agent",i.agentId),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${c.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
5
+ <span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",g=>{g.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(e,n){if(!(u===e&&p&&v===(n||null))){H.style.display="none",C.style.display="none",h.style.display="",h.classList.add("terminal-pending"),Z(),p&&(K(),p.destroy(),p=null),u=e,v=n||null,window.__tmuxWebScopeAgent=v;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===v);try{p=await ke(h,e,{terminal:M.terminal,scrollback:M.scrollback,theme:M.theme,renderer:M.renderer,wsBase:v?"/ws/a/"+encodeURIComponent(v):M.wsBase||"/ws"}),we()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);h.textContent="Failed to open terminal for "+e+": "+s,h.classList.remove("terminal-pending")}}}let z=[],f=null,pe=!1;function Te(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function He(e){pe=!0;try{const n=await fetch(y("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{pe=!1}}function $e(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
6
6
  <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>
7
- ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&T&&f&&(e.stopPropagation(),me())});const N=document.getElementById("header-git"),re=document.getElementById("git-diff-view"),pe=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Te=document.getElementById("gd-back");let T=null;function D(e){if(!e||!e.branch){N.style.display="none",T=null;return}T=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let l=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),N.innerHTML=l,N.style.display="block"}N.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),me())});const m=document.getElementById("git-popover"),V=document.getElementById("git-popover-backdrop");function Q(){const e=N.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function fe(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(_(),e){ne="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),k(e)}})}let ue="";function me(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
8
- <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],l=s.additions||0,i=s.deletions||0;let o="";switch(s.status){case"M":o='<span class="git-file-status M">M</span>';break;case"A":o='<span class="git-file-status A">A</span>';break;case"D":o='<span class="git-file-status D">D</span>';break;case"?":o='<span class="git-file-status ?">?</span>';break;case"R":o='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
7
+ ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&T&&f&&(e.stopPropagation(),ge())});const N=document.getElementById("header-git"),fe=document.getElementById("git-diff-view"),ue=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Re=document.getElementById("gd-back");let T=null;function D(e){if(!e||!e.branch){N.style.display="none",T=null;return}T=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),N.innerHTML=i,N.style.display="block"}N.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),ge())});const m=document.getElementById("git-popover"),G=document.getElementById("git-popover-backdrop");function Q(){const e=N.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function me(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(q(),e){se="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),k(e)}})}let ve="";function ge(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
8
+ <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],i=s.additions||0,l=s.deletions||0;let o="";switch(s.status){case"M":o='<span class="git-file-status M">M</span>';break;case"A":o='<span class="git-file-status A">A</span>';break;case"D":o='<span class="git-file-status D">D</span>';break;case"?":o='<span class="git-file-status ?">?</span>';break;case"R":o='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
9
9
  ${o}<span class="file-path">${d(s.path)}</span>
10
- <span class="file-add">${l>0?"+"+l:""}</span>
11
- <span class="file-del">${i>0?"-"+i:""}</span>
12
- </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ue=n,m.innerHTML=n,m.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),l=e[s];!l||l.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>ge(l.path)))})}Q(),m.classList.add("open"),V.classList.add("open"),fe()}function He(){return window.innerWidth>=1024}function ve(e,n){const s=(e+(n&&e?`
10
+ <span class="file-add">${i>0?"+"+i:""}</span>
11
+ <span class="file-del">${l>0?"-"+l:""}</span>
12
+ </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ve=n,m.innerHTML=n,m.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>he(i.path)))})}Q(),m.classList.add("open"),G.classList.add("open"),me()}function Ne(){return window.innerWidth>=1024}function ye(e,n){const s=(e+(n&&e?`
13
13
  `:"")+n).split(`
14
- `);let l="",i=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const v=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);v&&(i=parseInt(v[1],10)-1,o=parseInt(v[2],10)-1),l+=`<div class="diff-line hunk"><span class="ln-body">${d(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(l+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,l+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))i++,l+=`<div class="diff-line del"><span class="ln-no">${i}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))l+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!i&&!o)continue;i++,o++,l+=`<div class="diff-line"><span class="ln-no">${i}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:l,lineCount:s.length}}let q=null;async function ge(e){const n=T;if(n)if(_(),He()){y.style.display="none",p&&(K(),p.destroy(),p=null,q=u,u=null),D(null),H.style.display="none",B.style.display="none",re.style.display="flex",pe.textContent=e,O.textContent="Loading\u2026",U.innerHTML="";try{const t=await fetch(g("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();Re(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);O.textContent="Error",U.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
14
+ `);let i="",l=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const g=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);g&&(l=parseInt(g[1],10)-1,o=parseInt(g[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))l++,i+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!l&&!o)continue;l++,o++,i+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:i,lineCount:s.length}}let V=null;async function he(e){const n=T;if(n)if(q(),Ne()){h.style.display="none",p&&(K(),p.destroy(),p=null,V=u,u=null),D(null),H.style.display="none",C.style.display="none",fe.style.display="flex",ue.textContent=e,O.textContent="Loading\u2026",U.innerHTML="";try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();Pe(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);O.textContent="Error",U.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
15
15
  <button class="diff-back">\u2190 Back</button>
16
16
  <span class="diff-filename">${d(e)}</span>
17
17
  <span class="diff-loading">Loading\u2026</span>
18
- </div></div>`,Q(),m.classList.add("open"),V.classList.add("open");try{const t=await fetch(g("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();$e(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
18
+ </div></div>`,Q(),m.classList.add("open"),G.classList.add("open");try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();De(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
19
19
  <button class="diff-back">\u2190 Back</button>
20
20
  <span class="diff-filename">${d(e)}</span>
21
- </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function $e(e,n,t){if(!n&&!t){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
+ </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function De(e,n,t){if(!n&&!t){m.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">${d(e)}</span>
24
- </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:l}=ve(n,t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
+ </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=ye(n,t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
25
25
  <button class="diff-back">\u2190 Back</button>
26
26
  <span class="diff-filename">${d(e)}</span>
27
- <span class="diff-filesize">${l} lines</span>
28
- </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ue,Q(),f&&m.querySelectorAll(".git-popover-item").forEach(i=>{const o=parseInt(i.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(i.style.cursor="pointer",i.addEventListener("click",()=>ge(c.path)))}),fe()})}function Re(e,n,t){if(!n&&!t){O.textContent="No diff",U.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:l}=ve(n,t);pe.textContent=e,O.textContent=l+" lines",U.innerHTML=s}function Ne(){re.style.display="none",q?(y.style.display="",y.classList.add("terminal-pending"),R(q),q=null):u?(y.style.display="",y.classList.add("terminal-pending"),he()):B.style.display="flex"}Te.addEventListener("click",Ne);function _(){m.classList.remove("open"),V.classList.remove("open")}V.addEventListener("click",_),document.addEventListener("keydown",e=>{e.key==="Escape"&&(_(),X())});async function ye(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const l=await s.json();f=l,D(l);return}}}catch{}f=null,D(null)}let J=null;function he(){K(),u&&(ye(u),J=setInterval(()=>{u&&ye(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function De(){if(G=M.fsRoots||[],G.length===0){if(u){Ae(u);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of G){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>k(e)),r.appendChild(n)}}async function Ae(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(g("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?k(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(e){E=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Me(e);try{const s=await(await fetch(g("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",D(f),r.innerHTML+=Se();const l=Fe(e);if(l&&!G.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(l)),r.appendChild(c)}if(s.dirs)for(const o of s.dirs){const c=o.split("/").pop()||o.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(c)}`,a.addEventListener("click",()=>k(o)),r.appendChild(a)}else r.innerHTML="";const i=s.files||[];for(const o of i){const c=o.split("/").pop()||o.split("\\").pop(),a=xe(o);let v="",x="";if(a)switch(a){case"M":v="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":v="git-add",x='<span class="git-file-status A">A</span>';break;case"D":v="git-del",x='<span class="git-file-status D">D</span>';break;case"?":v="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":v="git-mod",x='<span class="git-file-status R">R</span>';break}const j=document.createElement("div");j.className="file-tree-item file"+(v?" "+v:""),j.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${d(c)}`,j.addEventListener("click",()=>Pe(o)),r.appendChild(j)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Fe(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function Pe(e){z=e,y.style.display="none",p&&(K(),p.destroy(),p=null,u=null),D(null),Z(),B.style.display="none",H.style.display="flex",Ee.textContent=e,h.textContent="Loading...",C.style.display="none";try{const n=await fetch(g("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();h.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,se=t.content,h.textContent=t.size+" bytes",C.style.display=""}catch{h.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),C.addEventListener("click",async()=>{C.textContent="Saving...",C.disabled=!0;try{const e=await fetch(g("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:z,content:W})});if(!e.ok){const n=await e.json();h.textContent=n.error||"Save failed";return}se=W,h.textContent="Saved"}catch{h.textContent="Save failed"}finally{C.disabled=!1,C.textContent="Save"}}),ke.addEventListener("click",async()=>{if(confirm("Delete "+z+"?"))try{const e=await fetch(g("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:z})});if(!e.ok){const n=await e.json();h.textContent=n.error||"Delete failed";return}H.style.display="none",B.style.display="flex",E&&k(E)}catch{h.textContent="Delete failed"}}),Be.addEventListener("click",()=>{H.style.display="none",B.style.display="flex",E&&k(E)}),Ce.addEventListener("click",async()=>{const e=ie.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(g("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();h.textContent=s.error||"Create failed";return}ie.value="",E&&k(E)}catch{h.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=oe.classList.toggle("open");Y.classList.toggle("open",e)}),Y.addEventListener("click",()=>{oe.classList.remove("open"),Y.classList.remove("open")});let I="";const A=document.createElement("div");A.className="session-popover-backdrop",document.body.appendChild(A);const w=document.createElement("div");w.id="session-popover",w.className="session-popover",w.style.display="none",w.innerHTML=`<div class="sp-field">
27
+ <span class="diff-filesize">${i} lines</span>
28
+ </div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ve,Q(),f&&m.querySelectorAll(".git-popover-item").forEach(l=>{const o=parseInt(l.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>he(c.path)))}),me()})}function Pe(e,n,t){if(!n&&!t){O.textContent="No diff",U.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=ye(n,t);ue.textContent=e,O.textContent=i+" lines",U.innerHTML=s}function Ae(){fe.style.display="none",V?(h.style.display="",h.classList.add("terminal-pending"),R(V),V=null):u?(h.style.display="",h.classList.add("terminal-pending"),we()):C.style.display="flex"}Re.addEventListener("click",Ae);function q(){m.classList.remove("open"),G.classList.remove("open")}G.addEventListener("click",q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(q(),X())});async function be(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();f=i,D(i);return}}}catch{}f=null,D(null)}let J=null;function we(){K(),u&&(be(u),J=setInterval(()=>{u&&be(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function _e(){if(z=M.fsRoots||[],z.length===0){if(u){Fe(u);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of z){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>k(e)),r.appendChild(n)}}async function Fe(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(y("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?k(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(e){E=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=He(e);try{const s=await(await fetch(y("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",D(f),r.innerHTML+=$e();const i=je(e);if(i&&!z.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(i)),r.appendChild(c)}if(s.dirs)for(const o of s.dirs){const c=o.split("/").pop()||o.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(c)}`,a.addEventListener("click",()=>k(o)),r.appendChild(a)}else r.innerHTML="";const l=s.files||[];for(const o of l){const c=o.split("/").pop()||o.split("\\").pop(),a=Te(o);let g="",x="";if(a)switch(a){case"M":g="git-mod",x='<span class="git-file-status M">M</span>';break;case"A":g="git-add",x='<span class="git-file-status A">A</span>';break;case"D":g="git-del",x='<span class="git-file-status D">D</span>';break;case"?":g="git-untracked",x='<span class="git-file-status ?">?</span>';break;case"R":g="git-mod",x='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(g?" "+g:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${x}${d(c)}`,F.addEventListener("click",()=>We(o)),r.appendChild(F)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function je(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function We(e){j=e,h.style.display="none",p&&(K(),p.destroy(),p=null,u=null),D(null),Z(),C.style.display="none",H.style.display="flex",Ce.textContent=e,b.textContent="Loading...",I.style.display="none";try{const n=await fetch(y("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();b.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,ie=t.content,b.textContent=t.size+" bytes",I.style.display=""}catch{b.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),I.addEventListener("click",async()=>{I.textContent="Saving...",I.disabled=!0;try{const e=await fetch(y("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j,content:W})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Save failed";return}ie=W,b.textContent="Saved"}catch{b.textContent="Save failed"}finally{I.disabled=!1,I.textContent="Save"}}),Ie.addEventListener("click",async()=>{if(confirm("Delete "+j+"?"))try{const e=await fetch(y("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Delete failed";return}H.style.display="none",C.style.display="flex",E&&k(E)}catch{b.textContent="Delete failed"}}),Be.addEventListener("click",()=>{H.style.display="none",C.style.display="flex",E&&k(E)}),xe.addEventListener("click",async()=>{const e=oe.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(y("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();b.textContent=s.error||"Create failed";return}oe.value="",E&&k(E)}catch{b.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=ae.classList.toggle("open");Y.classList.toggle("open",e)}),Y.addEventListener("click",()=>{ae.classList.remove("open"),Y.classList.remove("open")});let B="";const P=document.createElement("div");P.className="session-popover-backdrop",document.body.appendChild(P);const L=document.createElement("div");L.id="session-popover",L.className="session-popover",L.style.display="none",L.innerHTML=`<div class="sp-field">
29
29
  <label>Rename session</label>
30
30
  <input type="text" id="sp-name" />
31
31
  </div>
@@ -34,5 +34,5 @@ import{initTerminal as we}from"./terminal-core.js";const M=window.__TMUX_WEB_SHE
34
34
  <button class="btn" id="sp-cancel">Cancel</button>
35
35
  </div>
36
36
  <hr class="sp-divider" />
37
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(w);function je(e){const n=e.getBoundingClientRect();w.style.top=n.bottom+4+"px",w.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function ze(e,n){I=e;const t=document.getElementById("sp-name");t.value=e,A.classList.add("open"),w.style.display="block",je(n),setTimeout(()=>t.focus(),50)}function F(){A.classList.remove("open"),w.style.display="none"}A.addEventListener("click",F),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===I){F();return}try{if(!(await fetch(g("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:I,newName:e})})).ok)return;F(),S(),u===I&&R(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+I+'"?'))try{if(!(await fetch(g("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:I})})).ok)return;F(),u===I&&(p&&(p.destroy(),p=null),u=null,y.style.display="none",B.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",F);const We={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!p)return;const t=We[n.dataset.key];t&&(p.sendInput(t),p.focus())});const L=document.getElementById("mk-input"),be=document.getElementById("mk-send");function Le(){if(!L||!p)return;const e=L.value;e&&(p.sendInput(e+"\r"),L.value="",L.rows=1,p.focus())}if(L&&(L.addEventListener("input",()=>{L.rows=1;const e=L.value.split(`
38
- `).length;L.rows=Math.min(e,4)}),L.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Le())})),be&&be.addEventListener("click",Le),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const ee=document.getElementById("process-panel"),P=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),te=document.getElementById("proc-backdrop"),Oe=document.getElementById("header-ram");function Ue(){P.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(g("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){P.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,l=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}P.innerHTML=n;for(const t of P.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",l=>{const i=l.target.closest(".proc-row");!i||!confirm("Kill PID "+i.dataset.pid+"?")||fetch(g("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(i.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?i.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{P.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Ve(){ee.classList.add("open"),te.classList.add("open"),Ue()}function X(){ee.classList.remove("open"),te.classList.remove("open")}Oe.addEventListener("click",e=>{e.stopPropagation(),ee.classList.contains("open")?X():Ve()}),Ge.addEventListener("click",X),te.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=S,S(),Z(),setInterval(()=>{document.visibilityState==="visible"&&S()},1e4);
37
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(L);function ze(e){const n=e.getBoundingClientRect();L.style.top=n.bottom+4+"px",L.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Oe(e,n){B=e;const t=document.getElementById("sp-name");t.value=e,P.classList.add("open"),L.style.display="block",ze(n),setTimeout(()=>t.focus(),50)}function A(){P.classList.remove("open"),L.style.display="none"}P.addEventListener("click",A),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===B){A();return}try{if(!(await fetch(y("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:B,newName:e})})).ok)return;A(),S(),u===B&&R(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+B+'"?'))try{if(!(await fetch(y("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B})})).ok)return;A(),u===B&&(p&&(p.destroy(),p=null),u=null,h.style.display="none",C.style.display="flex"),S()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",A);const Ue={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!p)return;const t=Ue[n.dataset.key];t&&(p.sendInput(t),p.focus())});const w=document.getElementById("mk-input"),Le=document.getElementById("mk-send");function Ee(){if(!w||!p)return;const e=w.value;e&&(p.sendInput(e+"\r"),w.value="",w.rows=1,p.focus())}if(w&&(w.addEventListener("input",()=>{w.rows=1;const e=w.value.split(`
38
+ `).length;w.rows=Math.min(e,4)}),w.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ee())})),Le&&Le.addEventListener("click",Ee),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const ee=document.getElementById("process-panel"),_=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),te=document.getElementById("proc-backdrop"),Ve=document.getElementById("header-ram");function qe(){_.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(y("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){_.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}_.innerHTML=n;for(const t of _.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const l=i.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(y("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?l.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{_.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Je(){ee.classList.add("open"),te.classList.add("open"),qe()}function X(){ee.classList.remove("open"),te.classList.remove("open")}Ve.addEventListener("click",e=>{e.stopPropagation(),ee.classList.contains("open")?X():Je()}),Ge.addEventListener("click",X),te.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=S,S(),Z(),setInterval(()=>{document.visibilityState==="visible"&&S()},1e4);
@@ -1 +1 @@
1
- import{WebSocket as E}from"ws";import{attachTerminal as R}from"./attach-terminal.js";import{encodeBinaryFrame as U,decodeBinaryFrame as D,BIN_KIND_HTTP_BODY as N}from"./agent-channel.js";const J=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),j=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),x=new Set(["content-type","content-disposition","location","cache-control","etag"]);function X(s){const I=s.log??(e=>console.log(`[agent] ${e}`)),b=e=>console.error(`[agent] ${e}`);let o=null,_=1e3,T=null,g=!1;const u=new Map,d=new Map;let h=null,y=null,B="",S=null;const p={state:"idle",agentId:null,hub:s.hub,name:s.name};function f(e,n){n!==void 0&&(p.lastError=n),p.state=e,p.agentId=S,s.onStatus?.({...p})}function c(e){o&&o.readyState===E.OPEN&&o.send(JSON.stringify(e))}function w(){let e;try{e=s.listSessions()}catch{return}const n=JSON.stringify(e);n!==B&&(B=n,c({type:"sessions",sessions:e}))}function P(){h||(h=setInterval(w,j)),y||(y=setInterval(()=>c({type:"ping",ts:Date.now()}),J))}function A(){h&&(clearInterval(h),h=null),y&&(clearInterval(y),y=null)}async function v(e){let n;if(e.hasBody){const a=d.get(e.id);d.delete(e.id),n=a&&a.length?Buffer.concat(a):Buffer.alloc(0)}const t=new URL(e.path,"http://agent.invalid");let i;try{const a={method:e.method,headers:e.headers};n&&n.length&&(a.body=n),i=await Promise.race([s.app.request(new Request(t,a)),new Promise((K,L)=>setTimeout(()=>L(new Error("app.request timed out")),3e4))])}catch(a){b(`tunnel request failed: ${e.method} ${e.path} ${a.message}`),c({type:"http_resp",id:e.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const m={};i.headers.forEach((a,l)=>{x.has(l.toLowerCase())&&(m[l]=a)});const r=Buffer.from(await i.arrayBuffer());if(c({type:"http_resp",id:e.id,status:i.status,headers:m,hasBody:r.length>0}),r.length>0){for(let l=0;l<r.length;l+=65536){if(!o||o.readyState!==E.OPEN)return;o.send(U(N,e.id,r.subarray(l,l+65536)))}c({type:"http_body_end",id:e.id})}}function $(e){const n=e.session;let t=!1;try{t=s.listSessions().some(r=>r.name===n)}catch{t=!1}if(!t){c({type:"attach_err",connId:e.connId,session:n,message:"no such session"});return}let i=null;const m=R(n,{send:r=>c({type:"ws_to_hub",connId:e.connId,msg:r}),onMessage:r=>{i=r},onClose:()=>{}},{terminalBufferConfig:s.terminalBufferConfig,getSessionPaneTarget:s.getSessionPaneTarget,capturePaneTail:s.capturePaneTail,capturePaneHistoryChunk:s.capturePaneHistoryChunk,toCrlf:s.toCrlf,handleClientMessage:s.handleClientMessage,acquireControlClient:s.acquireControlClient,onPtyExit:(r,a)=>{c({type:"ws_close",connId:e.connId,code:1e3,reason:`tmux exited (${a})`})},onSpawnError:(r,a)=>{c({type:"attach_err",connId:e.connId,session:n,message:a})},onAttached:r=>{c({type:"attach_ok",connId:e.connId,session:n})}});u.set(e.connId,{onMessage:r=>i?.(r),dispose:()=>{u.delete(e.connId),m.dispose()}})}function H(e){let n;try{n=JSON.parse(e)}catch{return}switch(n.type){case"hello_ok":S=n.agentId,_=1e3,I(`connected to hub as "${S}"`),f("connected"),w(),P();break;case"hello_err":if(b(`hub rejected us: ${n.reason}`),f("error",n.reason),o)try{o.close(4003,"hello rejected")}catch{}break;case"sessions_req":w();break;case"http_req":v(n);break;case"http_body_end":break;case"attach":$(n);break;case"ws_to_agent":{const t=u.get(n.connId);if(t)try{t.onMessage(JSON.stringify(n.msg))}catch{}break}case"detach":{const t=u.get(n.connId);t&&t.dispose();break}case"pong":break;default:break}}function O(e){const n=D(e);if(n&&n.kind===N){const t=d.get(n.id)??[];t.push(n.payload),d.set(n.id,t)}}function C(){A();for(const e of u.values())try{e.dispose()}catch{}u.clear(),d.clear(),B=""}function k(){if(o){try{o.removeAllListeners()}catch{}o=null}if(C(),g)return;f("connecting");const e=_+Math.random()*500;I(`reconnecting in ${Math.round(e)}ms\u2026`),T=setTimeout(M,e),_=Math.min(_*2,6e4)}function M(){if(g||o)return;const e=s.hub.replace(/\/+$/,"")+"/agent/ws";let n;try{n=new E(e)}catch(t){f("error",`bad hub URL: ${t.message}`);return}o=n,f("connecting"),n.on("open",()=>{I(`connecting to ${e}\u2026`),c({type:"hello",token:s.token,name:s.name,version:s.version})}),n.on("message",(t,i)=>{i?O(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(t)):H(typeof t=="string"?t:Buffer.from(t).toString("utf-8"))}),n.on("close",()=>{o===n&&k()}),n.on("error",t=>{b(`connection error: ${t.message}`),o===n&&k()})}function q(){if(!g){if(g=!0,T&&clearTimeout(T),C(),o){try{o.removeAllListeners(),o.close(1e3,"shutdown")}catch{}o=null}S=null,f("idle")}}return M(),{stop:q,status:()=>({...p})}}export{X as startAgentClient};
1
+ import{WebSocket as E}from"ws";import{attachTerminal as q}from"./attach-terminal.js";import{encodeBinaryFrame as U,decodeBinaryFrame as D,BIN_KIND_HTTP_BODY as N}from"./agent-channel.js";const J=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),j=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),x=new Set(["content-type","content-disposition","location","cache-control","etag"]);function X(o){const I=o.log??(e=>console.log(`[agent] ${e}`)),_=e=>console.error(`[agent] ${e}`);let s=null,g=1e3,T=null,S=!1;const u=new Map,f=new Map;let h=null,p=null,w="",m=null;const y={state:"idle",agentId:null,hub:o.hub,name:o.name};function d(e,t){t!==void 0&&(y.lastError=t),y.state=e,y.agentId=m,o.onStatus?.({...y})}function c(e){s&&s.readyState===E.OPEN&&s.send(JSON.stringify(e))}function B(){let e;try{e=o.listSessions()}catch{return}const t=JSON.stringify(e);t!==w&&(w=t,c({type:"sessions",sessions:e}))}function P(){h||(h=setInterval(B,j)),p||(p=setInterval(()=>c({type:"ping",ts:Date.now()}),J))}function A(){h&&(clearInterval(h),h=null),p&&(clearInterval(p),p=null)}async function $(e){let t;if(e.hasBody){const a=f.get(e.id);f.delete(e.id),t=a&&a.length?Buffer.concat(a):Buffer.alloc(0)}const n=new URL(e.path,"http://agent.invalid");let i;try{const a={method:e.method,headers:e.headers};t&&t.length&&(a.body=t),i=await Promise.race([o.app.request(new Request(n,a)),new Promise((K,R)=>setTimeout(()=>R(new Error("app.request timed out")),3e4))])}catch(a){_(`tunnel request failed: ${e.method} ${e.path} ${a.message}`),c({type:"http_resp",id:e.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const b={};i.headers.forEach((a,l)=>{x.has(l.toLowerCase())&&(b[l]=a)});const r=Buffer.from(await i.arrayBuffer());if(c({type:"http_resp",id:e.id,status:i.status,headers:b,hasBody:r.length>0}),r.length>0){for(let l=0;l<r.length;l+=65536){if(!s||s.readyState!==E.OPEN)return;s.send(U(N,e.id,r.subarray(l,l+65536)))}c({type:"http_body_end",id:e.id})}}function v(e){const t=e.session;let n=!1;try{n=o.listSessions().some(r=>r.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 b=q(t,{send:r=>c({type:"ws_to_hub",connId:e.connId,msg:r}),onMessage:r=>{i=r},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,onPtyExit:(r,a)=>{c({type:"ws_close",connId:e.connId,code:1e3,reason:`tmux exited (${a})`})},onSpawnError:(r,a)=>{c({type:"attach_err",connId:e.connId,session:t,message:a})},onAttached:r=>{c({type:"attach_ok",connId:e.connId,session:t})}});u.set(e.connId,{onMessage:r=>i?.(r),dispose:()=>{u.delete(e.connId),b.dispose()}})}function H(e){let t;try{t=JSON.parse(e)}catch{return}switch(t.type){case"hello_ok":m=t.agentId,g=1e3,I(`connected to hub as "${m}"`),d("connected"),B(),P();break;case"hello_err":if(_(`hub rejected us: ${t.reason}`),d("error",t.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":B();break;case"http_req":$(t);break;case"http_body_end":break;case"attach":v(t);break;case"ws_to_agent":{const n=u.get(t.connId);if(n)try{n.onMessage(JSON.stringify(t.msg))}catch{}break}case"detach":{const n=u.get(t.connId);n&&n.dispose();break}case"pong":break;default:break}}function O(e){const t=D(e);if(t&&t.kind===N){const n=f.get(t.id)??[];n.push(t.payload),f.set(t.id,n)}}function C(){A();for(const e of u.values())try{e.dispose()}catch{}u.clear(),f.clear(),w=""}function k(){if(s){try{s.removeAllListeners()}catch{}s=null}if(C(),S)return;d("connecting");const e=g+Math.random()*500;I(`reconnecting in ${Math.round(e)}ms\u2026`),T=setTimeout(M,e),g=Math.min(g*2,6e4)}function M(){if(S||s)return;const e=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let t;try{t=new E(e)}catch(n){_(`bad hub URL: ${n.message} (${e})`),d("error",`bad hub URL: ${n.message}`);return}s=t,d("connecting"),t.on("open",()=>{I(`connecting to ${e}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),t.on("message",(n,i)=>{i?O(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):H(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),t.on("close",()=>{s===t&&k()}),t.on("error",n=>{_(`connection error: ${n.message}`),s===t&&k()})}function L(){if(!S){if(S=!0,T&&clearTimeout(T),C(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}m=null,d("idle")}}return M(),{stop:L,status:()=>({...y})}}export{X as startAgentClient};
@@ -1,8 +1,8 @@
1
- import{existsSync as W,readdirSync as je,statSync as C,unlinkSync as ke,writeFileSync as ve,mkdirSync as be,readFileSync as Q}from"node:fs";import{readFile as qe}from"node:fs/promises";import{execSync as E,execFileSync as Y}from"node:child_process";import h from"node:path";import{fileURLToPath as Se}from"node:url";import{Hono as Te}from"hono";import{listSessions as R}from"../sessions.js";import{renderLoginPage as Ae,renderNotesIndex as xe,renderNotesPage as Ie,renderSettings as Ce,renderThemeSettings as _e,renderScheduleIndex as Ee,renderHistoryIndex as Re,renderQuickCommandsPage as Oe,renderFilesIndex as Ne,renderShell as De}from"../frontend.js";import{renderFederationPage as Le}from"./pages/federation.js";import{createAgentToken as Pe,removeAgentToken as Ue,listAgentTokens as K}from"./agent-tokens.js";import{hashPassword as Fe,verifyPassword as He,validatePassword as We}from"./auth.js";import{atomicWriteFileSync as Me}from"./atomicWrite.js";import{resolveFsPath as _,resolveFsRoots as X,MAX_FILE_BYTES as Z,walkRecursive as Be}from"./fs-access.js";import{captureSessionWindowsWithPath as Ve}from"./tmux-windows.js";import{audit as q}from"./auditLog.js";import{db as A}from"./db.js";import{getSessionAccessMap as M}from"./session-access.js";import{listWindowHistory as $e,clearWindowHistory as Ge}from"./window-history.js";import{registerExtensionRoutes as ze}from"./ext-loader.js";import{saveSecurityConfig as Qe}from"./security-config.js";import{isValidScheduleInput as Ye,isValidRescheduleInput as Ke}from"./scheduler.js";import{ImageUploadError as Xe,saveUploadedImage as Ze}from"./image-upload.js";import{getScheduleDelayError as J}from"./schedule-delay.js";import{readSettings as ee,writeSettings as Je}from"./settings.js";import{setActiveThemeTemplate as re}from"./theme-store.js";import{isThemeTemplateId as te,THEME_TEMPLATE_IDS as er}from"./themes/index.js";import{installPlugin as rr,uninstallPlugin as tr}from"./plugins.js";import{buildCommandbarSessions as ne}from"./commandbar.js";import{pinView as nr,unpinView as sr,listPinnedViews as or}from"./pinned-views.js";import{listWindowLabels as ir,setWindowLabel as ar}from"./window-labels.js";import{captureAndStoreWindows as se,getStoredWindows as oe}from"./session-windows.js";import{buildSidebarSessions as dr}from"./sessions-sidebar.js";import{createQuickCommand as ur,deleteQuickCommand as cr,listQuickCommands as ie,updateQuickCommand as lr}from"./quick-commands.js";import{getSystemStatus as mr,getTopProcesses as fr,killProcess as pr}from"./system-monitor.js";import{listSessionWindows as hr,selectSessionWindow as gr,newSessionWindow as yr,renameSessionWindow as wr,newTmuxSession as jr,renameSession as kr,killSession as vr,TmuxWindowsError as N}from"./tmux-windows.js";import{registerAgentTunnel as br}from"./agent-tunnel.js";import{injectScopeScript as qr}from"./scope-script.js";const B="tmux-web-token",Sr=365;function Tr(k){return k==="127.0.0.1"||k==="::1"||k==="::ffff:127.0.0.1"}function ft(k){const{mode:ae,securityConfig:g,tokenStore:v,rateLimiter:D,scheduler:x,settings:Ir,commandbarEnabled:m,terminalRenderer:L,scheduleHistoryDays:de,extsDir:ue,extensions:ce,terminalBufferConfig:P,getAgentId:V,state:c,federation:S,hub:y}=k,b=ae==="agent";function O(e){if(g.security.trustProxy){const r=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(r)return r}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function U(e){const r=e.req.header("authorization");if(r?.startsWith("Bearer "))return r.slice(7);const t=e.req.header("cookie");if(!t)return null;const n=t.match(new RegExp(`(?:^|;\\s*)${B}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Cr(e){const r=U(e);return r?v.validateToken(r):null}function $(e,r){const t=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${B}=${encodeURIComponent(r)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${Sr*86400}${t?"; Secure":""}`)}function le(e){e.header("Set-Cookie",`${B}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}const a=()=>b?async(e,r)=>r():async(e,r)=>{const t=U(e);return!t||!v.validateToken(t)?(q("http_unauthorized",{ip:O(e)}),e.json({error:"unauthorized"},401)):r()},w=()=>b?async(e,r)=>r():async(e,r)=>{const t=U(e);if(!t||!v.validateToken(t)){const n=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${n}`,302)}return r()};function T(){const e=ne(R(),M());if(!y)return e;for(const r of y.agents.list()){if(!r.online)continue;const t=y.agents.get(r.agentId);if(t)for(const n of t.sessions)e.push({...n,agentId:r.agentId,agentName:r.name,remote:!0})}return e}function F(e){const r=dr(R(),M(),or()),t=[];if(y)for(const n of y.agents.list()){const o=y.agents.get(n.agentId);t.push({agentId:n.agentId,agentName:n.name,online:n.online,sessions:o?o.sessions:[]})}return{...r,currentSession:e??null,agents:t}}function G(e){const r=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!r)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:r};const t=e.windowIndex;return typeof t!="number"||!Number.isInteger(t)||t<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:r,windowIndex:t}}const s=new Te;s.use("*",async(e,r)=>{await r(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline'","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self'","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,r)=>{const t=e.req.method;if(t==="GET"||t==="HEAD"||t==="OPTIONS")return r();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):r()}),b||s.use("*",async(e,r)=>{if(await r(),e.req.method!=="GET"||e.req.path==="/login"||!(e.res.headers.get("content-type")??"").includes("text/html"))return;const n=await e.res.text();e.res=new Response(qr(n,{prefix:"",agentId:null}),e.res)}),ze(s,ue,ce);const me=h.dirname(Se(import.meta.url)),fe=[h.join(me,"assets"),h.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const r=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(r))return e.notFound();for(const t of fe){const n=h.join(t,r);if(!W(n))continue;const o=await qe(n),i=h.extname(r),u={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(o,200,{"Content-Type":u[i]??"application/octet-stream","Cache-Control":i===".js"||i===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const pe=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
1
+ import{existsSync as W,readdirSync as je,statSync as C,unlinkSync as ke,writeFileSync as ve,mkdirSync as be,readFileSync as Y}from"node:fs";import{readFile as qe}from"node:fs/promises";import{execSync as E,execFileSync as K}from"node:child_process";import h from"node:path";import{fileURLToPath as Se}from"node:url";import{Hono as Te}from"hono";import{listSessions as R}from"../sessions.js";import{renderLoginPage as Ae,renderNotesIndex as xe,renderNotesPage as Ie,renderSettings as Ce,renderThemeSettings as _e,renderScheduleIndex as Ee,renderHistoryIndex as Re,renderQuickCommandsPage as Oe,renderFilesIndex as Ne,renderShell as De}from"../frontend.js";import{renderFederationPage as Le}from"./pages/federation.js";import{createAgentToken as Pe,removeAgentToken as Ue,listAgentTokens as X}from"./agent-tokens.js";import{hashPassword as Fe,verifyPassword as He,validatePassword as We}from"./auth.js";import{atomicWriteFileSync as Me}from"./atomicWrite.js";import{resolveFsPath as _,resolveFsRoots as Z,MAX_FILE_BYTES as J,walkRecursive as Be}from"./fs-access.js";import{captureSessionWindowsWithPath as Ve}from"./tmux-windows.js";import{audit as q}from"./auditLog.js";import{db as A}from"./db.js";import{getSessionAccessMap as M}from"./session-access.js";import{listWindowHistory as $e,clearWindowHistory as Ge}from"./window-history.js";import{registerExtensionRoutes as ze}from"./ext-loader.js";import{saveSecurityConfig as Qe}from"./security-config.js";import{isValidScheduleInput as Ye,isValidRescheduleInput as Ke}from"./scheduler.js";import{ImageUploadError as Xe,saveUploadedImage as Ze}from"./image-upload.js";import{getScheduleDelayError as ee}from"./schedule-delay.js";import{readSettings as te,writeSettings as Je}from"./settings.js";import{setActiveThemeTemplate as re}from"./theme-store.js";import{isThemeTemplateId as ne,THEME_TEMPLATE_IDS as et}from"./themes/index.js";import{installPlugin as tt,uninstallPlugin as rt}from"./plugins.js";import{buildCommandbarSessions as se}from"./commandbar.js";import{pinView as nt,unpinView as st,listPinnedViews as ot}from"./pinned-views.js";import{listWindowLabels as it,setWindowLabel as at}from"./window-labels.js";import{captureAndStoreWindows as oe,getStoredWindows as ie}from"./session-windows.js";import{buildSidebarSessions as dt}from"./sessions-sidebar.js";import{createQuickCommand as ut,deleteQuickCommand as ct,listQuickCommands as ae,updateQuickCommand as lt}from"./quick-commands.js";import{getSystemStatus as mt,getTopProcesses as ft,killProcess as pt}from"./system-monitor.js";import{listSessionWindows as ht,selectSessionWindow as gt,newSessionWindow as yt,renameSessionWindow as wt,newTmuxSession as jt,renameSession as kt,killSession as vt,TmuxWindowsError as N}from"./tmux-windows.js";import{registerAgentTunnel as bt}from"./agent-tunnel.js";import{injectScopeScript as qt}from"./scope-script.js";const B="tmux-web-token",St=365;function Tt(k){return k==="127.0.0.1"||k==="::1"||k==="::ffff:127.0.0.1"}function fr(k){const{mode:de,securityConfig:g,tokenStore:v,rateLimiter:D,scheduler:x,settings:It,commandbarEnabled:m,terminalRenderer:L,scheduleHistoryDays:ue,extsDir:ce,extensions:le,terminalBufferConfig:P,getAgentId:V,state:c,federation:S,hub:y}=k,b=de==="agent";function O(e){if(g.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function U(e){const t=e.req.header("authorization");if(t?.startsWith("Bearer "))return t.slice(7);const r=e.req.header("cookie");if(!r)return null;const n=r.match(new RegExp(`(?:^|;\\s*)${B}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Ct(e){const t=U(e);return t?v.validateToken(t):null}function $(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${B}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${St*86400}${r?"; Secure":""}`)}function me(e){e.header("Set-Cookie",`${B}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}const a=()=>b?async(e,t)=>t():async(e,t)=>{const r=U(e);return!r||!v.validateToken(r)?(q("http_unauthorized",{ip:O(e)}),e.json({error:"unauthorized"},401)):t()},w=()=>b?async(e,t)=>t():async(e,t)=>{const r=U(e);if(!r||!v.validateToken(r)){const n=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${n}`,302)}return t()};function T(){const e=se(R(),M());if(!y)return e;for(const t of y.agents.list()){if(!t.online)continue;const r=y.agents.get(t.agentId);if(r)for(const n of r.sessions)e.push({...n,agentId:t.agentId,agentName:t.name,remote:!0})}return e}function F(e){const t=dt(R(),M(),ot()),r=[];if(y)for(const n of y.agents.list()){const o=y.agents.get(n.agentId);r.push({agentId:n.agentId,agentName:n.name,online:n.online,sessions:o?o.sessions:[]})}return{...t,currentSession:e??null,agents:r}}function G(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}const s=new Te;s.use("*",async(e,t)=>{await t(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline' https://fonts.googleapis.com","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self' https://fonts.gstatic.com","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,t)=>{const r=e.req.method;if(r==="GET"||r==="HEAD"||r==="OPTIONS")return t();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):t()}),b||s.use("*",async(e,t)=>{if(await t(),e.req.method!=="GET"||e.req.path==="/login"||!(e.res.headers.get("content-type")??"").includes("text/html"))return;const n=await e.res.text();e.res=new Response(qt(n,{prefix:"",agentId:null}),e.res)}),ze(s,ce,le);const z=h.dirname(Se(import.meta.url)),fe=[h.join(z,"..","assets"),h.join(z,"assets"),h.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of fe){const n=h.join(r,t);if(!W(n))continue;const o=await qe(n),i=h.extname(t),u={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(o,200,{"Content-Type":u[i]??"application/octet-stream","Cache-Control":i===".js"||i===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const pe=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
2
  <rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
3
3
  <path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
4
4
  <line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
5
- </svg>`,z=e=>e.body(pe,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",z),s.get("/favicon.ico",z);const he={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(he,200,{"Cache-Control":"public, max-age=3600"}));const ge=`// tmux-weblink Service Worker
5
+ </svg>`,Q=e=>e.body(pe,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Q),s.get("/favicon.ico",Q);const he={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(he,200,{"Cache-Control":"public, max-age=3600"}));const ge=`// tmux-weblink Service Worker
6
6
  const CACHE = "tmux-weblink-v1";
7
7
  const ASSETS = ["/", "/favicon.svg"];
8
8
  self.addEventListener("install", (e) => {
@@ -13,7 +13,7 @@ self.addEventListener("activate", (e) => { e.waitUntil(clients.claim()); });
13
13
  self.addEventListener("fetch", (e) => {
14
14
  e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
15
15
  });
16
- `;s.get("/sw.js",e=>e.body(ge,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const t=e.req.query("setup")==="1"?!0:!g.passwordHash,n=e.req.query("error");return e.html(Ae({setupMode:t,error:n?decodeURIComponent(n):void 0,theme:c.activeTheme}))}),s.get("/",w(),e=>{const r=m?T():[],t=X();return e.html(De({theme:c.activeTheme,commandbarEnabled:m,commandbarSessions:r,fsRoots:t,terminalCfg:P,renderer:L,scrollback:P.initialLines+2*P.historyChunk,wsBase:b&&V?.()?`/ws/a/${V()}`:void 0}))}),s.get("/notes",w(),e=>{const r=m?T():[];return e.html(xe(A.data.notes,c.activeTheme,m,r))}),s.get("/notes/:session",w(),e=>{const r=decodeURIComponent(e.req.param("session")),t=m?T():[];return e.html(Ie(r,c.activeTheme,m,t))}),s.get("/schedule",w(),e=>{const r=m?T():[];return e.html(Ee(x.list(),x.listTriggered(),c.activeTheme,de,m,r))}),s.get("/history",w(),e=>{const r=R(),t=m?ne(r,M()):[],n=new Set(r.map(o=>o.name));return e.html(Re($e(),c.activeTheme,m,t,n))}),s.get("/quick-commands",w(),e=>{const r=m?T():[];return e.html(Oe(ie(),c.activeTheme,m,r))}),s.get("/files",w(),e=>{const r=m?T():[],t=X();return e.html(Ne(c.activeTheme,m,r,t))}),s.post("/api/history/clear",a(),async e=>(await Ge(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(ie())),s.post("/api/quick-commands",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=await ur(r);return"error"in t?e.json({error:t.error},400):e.json(t,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=await lr(e.req.param("id"),r);return"error"in t?e.json({error:t.error},t.status):e.json(t)}),s.delete("/api/quick-commands/:id",a(),e=>cr(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(b)return e.json({error:"hub only"},403);const r=O(e),t=D.check(r);if(!t.allowed)return q("rate_limited",{ip:r,retryAfterMs:t.retryAfterMs,permanentLock:t.permanentLock}),t.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:t.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const o=typeof n.password=="string"?n.password:"";if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!Tr(r))return q("setup_rejected_remote",{ip:r}),e.json({error:"First-run setup must be performed from localhost"},403);if(c.settingUpPassword)return e.json({error:"Password setup in progress"},409);const l=We(o);if(l)return e.json({error:l},400);c.settingUpPassword=!0;try{g.passwordHash=await Fe(o),Qe(g),q("password_set",{ip:r})}finally{c.settingUpPassword=!1}const{plaintext:d}=v.createAccessToken("setup",g.security.tokenTtlDays);return $(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await He(o,g.passwordHash)){const l=D.recordFailure(r);return q("auth_failed",{ip:r,method:"password",failures:l.failures,permanentLock:l.permanentLock}),l.permanentLock?(v.revokeAll(),q("permanent_lock",{ip:r,failures:l.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):l.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:l.retryAfterMs},429)}D.recordSuccess(r);const u=`browser-${r}`,{plaintext:j}=v.createAccessToken(u,g.security.tokenTtlDays);return $(e,j),q("auth_success",{ip:r,method:"password",tokenName:u}),e.json({ok:!0,token:j})}),s.post("/api/auth/token",a(),async e=>{if(b)return e.json({error:"hub only"},403);const r=O(e);let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof t.name=="string"?t.name:`api-${r}`,o=typeof t.ttlDays=="number"?t.ttlDays:g.security.tokenTtlDays,{stored:i,plaintext:u}=v.createAccessToken(n,o);return q("token_created",{ip:r,name:i.name,tokenId:i.id}),e.json({id:i.id,name:i.name,token:u,expiresAt:i.expiresAt})}),s.get("/api/auth/tokens",a(),e=>b?e.json({error:"hub only"},403):e.json(v.list().map(r=>({id:r.id,name:r.name,createdAt:r.createdAt,lastUsedAt:r.lastUsedAt,expiresAt:r.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>b?e.json({error:"hub only"},403):v.revoke(e.req.param("id"))?(q("token_revoked",{ip:O(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(le(e),e.json({ok:!0}))),s.get("/settings",w(),async e=>{const r=await ee(),t=r.terminalRenderer??"xterm";return e.html(Ce({settings:r,renderer:L,rendererOverridden:L!==t,theme:c.activeTheme,plugins:r.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),async e=>{let r;try{r=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const t=await ee(),n=r.terminalRenderer==="ghostty"?"ghostty":"xterm",o=r.defaultView==="recent"?"recent":"default",i=xr(typeof r.scheduleHistoryDays=="string"?Number(r.scheduleHistoryDays):void 0);return await Je({...t,commandbar:r.commandbar!==void 0,terminalRenderer:n,defaultView:o,scheduleHistoryDays:i}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{if(b)return e.json({error:"plugin management is hub-only"},403);let r;try{r=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const t=r.action,n=typeof r.pkg=="string"?r.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const o=t==="remove"?await tr(n):t==="add"?await rr(n):{ok:!1,output:"unknown action"};return o.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(o.output.slice(0,800)),303)}),s.get("/settings/theme",w(),e=>e.html(_e({theme:c.activeTheme,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),async e=>{let r;try{r=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const t=r.template;return typeof t!="string"||!te(t)?e.redirect("/settings/theme",303):(c.activeTheme=await re(t),e.redirect("/settings/theme?saved=1",303))});const ye={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:c.activeTheme.template,templates:er.map(r=>({id:r,name:ye[r]}))})),s.post("/api/theme",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=r.template;return typeof t!="string"||!te(t)?e.json({error:"invalid theme template"},400):(c.activeTheme=await re(t),e.json({ok:!0,active:c.activeTheme.template}))}),s.get("/api/system/status",a(),e=>e.json(mr())),s.get("/api/system/processes",a(),e=>e.json(fr())),S&&(s.get("/settings/federation",w(),async e=>e.html(Le({config:S.getConfig(),status:S.status(),tokens:K().map(r=>({id:r.id,name:r.name,createdAt:r.createdAt})),hostname:S.hostname},c.activeTheme))),s.get("/api/federation",a(),e=>e.json({config:S.getConfig(),status:S.status()})),s.post("/api/federation",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=typeof r.hub=="string"?r.hub.trim():"",n=typeof r.token=="string"?r.token.trim():"",o=typeof r.name=="string"?r.name.trim().slice(0,64):"",i=r.enabled===!0;if(i){if(!t)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(t))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const u={hub:t||void 0,token:n||void 0,name:o||void 0,enabled:i};return await S.setConfig(u),e.json({ok:!0,status:S.status()})}),s.get("/api/agent-tokens",a(),e=>e.json(K().map(r=>({id:r.id,name:r.name,createdAt:r.createdAt})))),s.post("/api/agent-tokens",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=typeof r.name=="string"?r.name.trim().slice(0,64):"";if(!t)return e.json({error:"name is required"},400);const{id:n,token:o}=Pe(t);return e.json({ok:!0,id:n,name:t,token:o})}),s.delete("/api/agent-tokens/:id",a(),e=>{const r=e.req.param("id");return Ue(r)?e.json({ok:!0}):e.json({error:"no such token"},404)})),s.post("/api/system/kill",a(),async e=>{const{pid:r}=await e.req.json();if(typeof r!="number"||!Number.isInteger(r)||r<=0)return e.json({error:"invalid pid"},400);const t=pr(r);return t.ok?e.json({ok:!0}):e.json({error:t.error},500)}),s.get("/api/agents",a(),e=>y?e.json(y.agents.list()):e.json([])),s.get("/api/sessions",a(),e=>m?e.json(T()):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=typeof r.name=="string"?r.name.trim():"";if(!t)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(t))return e.json({error:"name contains invalid characters"},400);const n=typeof r.dir=="string"&&r.dir.trim()?r.dir.trim():void 0;if(R().some(i=>i.name===t))return e.json({error:"session already exists"},409);try{return jr(t,n),e.json({ok:!0})}catch(i){const u=i instanceof N?i.message:"failed to create session";return e.json({error:u},500)}}),s.post("/api/sessions/rename",a(),async e=>{try{const{oldName:r,newName:t}=await e.req.json();return!r||!t?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(t)?(kr(r,t),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",a(),async e=>{try{const{name:r}=await e.req.json();return r?(vr(r),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const r=e.req.query("session");if(!r)return e.json({error:"session is required"},400);try{const t=Ve(r),o=t.find(i=>i.active)?.path??t[0]?.path??process.env.HOME??"/";return e.json({path:o})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const r=process.env.HOME??"/";let t=e.req.query("path")??r;t.startsWith("~")&&(t=r+t.slice(1)),t.startsWith("/")||(t=h.join(r,t));const n=e.req.query("recursive")==="true";let o=t,i="",u=t.endsWith("/");if(!u)try{u=C(t).isDirectory()}catch{}u||(o=h.dirname(t),i=h.basename(t).toLowerCase());try{const j=je(o),l=[],d=[];for(const f of j)if(!f.startsWith(".")&&!(i&&!f.toLowerCase().startsWith(i))){try{const p=h.join(o,f);C(p).isDirectory()?(l.push(p),n&&Be(p,l,d,0)):d.push(p)}catch{}if(l.length+d.length>=5e3)break}return e.json({dirs:l,files:d})}catch{return e.json({dirs:[],files:[]})}});function we(e){const r={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const t=E("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!t)return r;let n;try{n=E("git branch --show-current 2>/dev/null",{cwd:t,encoding:"utf-8",timeout:3e3}).trim()}catch{n=E("git rev-parse --short HEAD 2>/dev/null",{cwd:t,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let o="";try{o=E("git status --porcelain 2>/dev/null",{cwd:t,encoding:"utf-8",timeout:3e3})}catch{}const i=[];for(const d of o.split(`
17
- `)){if(!d.trim())continue;const f=d[0],p=d[1],I=d.substring(3).trim(),H=p!==" "?p:f;i.push({path:I,status:H,staged:f,unstaged:p,additions:0,deletions:0})}const u=new Map;try{const d=E("git diff --numstat HEAD 2>/dev/null",{cwd:t,encoding:"utf-8",timeout:3e3});for(const f of d.split(`
18
- `)){if(!f.trim())continue;const p=f.split(" ");if(p.length>=3){const I=parseInt(p[0],10)||0,H=parseInt(p[1],10)||0;u.set(p[2],{added:I,deleted:H})}}}catch{}for(const d of i){const f=u.get(d.path);f&&(d.additions=f.added,d.deletions=f.deleted)}let j=0,l=0;for(const d of i)j+=d.additions,l+=d.deletions;for(const d of i)if(d.status==="?"&&d.additions===0)try{const f=h.join(t,d.path),I=Q(f,"utf-8").split(`
19
- `).length;d.additions=I,j+=I}catch{}return{repoRoot:t,branch:n,files:i,linesAdded:j,linesRemoved:l}}catch{return r}}return s.get("/api/git/status",a(),e=>{const r=e.req.query("path");if(!r)return e.json({error:"path is required"},400);try{const t=_(r),n=we(t);return e.json(n)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",a(),e=>{const r=e.req.query("path"),t=e.req.query("file");if(!r||!t)return e.json({error:"path and file are required"},400);try{const n=_(r);if(!W(n)||!C(n).isDirectory())return e.json({error:"invalid repository path: "+n},400);try{const o=Y("/usr/bin/git",["diff","HEAD","--",t],{cwd:n,encoding:"utf-8",timeout:5e3}),i=Y("/usr/bin/git",["diff","--cached","--",t],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:o,stagedDiff:i})}catch(o){throw console.error("[git/diff] execFileSync failed:",o?.code,o.message),o}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",a(),e=>{try{const r=e.req.query("path");if(!r)return e.json({error:"path is required"},400);const t=_(r);if(!C(t).isFile())return e.json({error:"not a file"},400);const n=C(t).size;if(n>Z)return e.json({error:"file too large",size:n,maxBytes:Z},413);const o=Q(t);return e.json({path:t,content:o,size:n})}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):r.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),async e=>{try{const r=await e.req.json();if(!r.path||typeof r.content!="string")return e.json({error:"path and content are required"},400);const t=_(r.path);return Me(t,r.content),e.json({ok:!0})}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",a(),async e=>{try{const r=await e.req.json();if(!r.path)return e.json({error:"path is required"},400);const t=_(r.path);return C(t).isFile()?(ke(t),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const r=await e.req.json();if(!r.path)return e.json({error:"path is required"},400);const t=_(r.path);return W(t)?e.json({error:"file already exists"},409):(be(h.dirname(t),{recursive:!0}),ve(t,"","utf-8"),e.json({ok:!0,path:t}))}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}}),s.get("/api/sidebar/sessions",a(),e=>{const r=e.req.query("currentSession");return e.json(F(typeof r=="string"&&r?r:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const r=decodeURIComponent(e.req.param("session"));return e.json(oe(r))}),s.post("/api/pinned-views",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=G(r);return"error"in t?e.json({error:t.error},400):(await nr(t.sessionName,t.windowIndex),e.json(F()))}),s.delete("/api/pinned-views",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=G(r);return"error"in t?e.json({error:t.error},400):(await sr(t.sessionName,t.windowIndex),e.json(F()))}),s.get("/api/notes",a(),e=>{const r=[...A.data.notes].sort((t,n)=>(n.updatedAt??0)-(t.updatedAt??0));return e.json(r)}),s.get("/api/notes/:scope",a(),e=>{const r=decodeURIComponent(e.req.param("scope")),t=A.data.notes.find(n=>n.scope===r);return t?e.json(t):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const r=decodeURIComponent(e.req.param("scope"));let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof t.content!="string")return e.json({error:"content must be string"},400);const n={scope:r,content:t.content,updatedAt:Date.now()},o=A.data.notes.findIndex(i=>i.scope===r);return o>=0?A.data.notes[o]=n:A.data.notes.push(n),await A.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const r=decodeURIComponent(e.req.param("session"));if(!R().some(i=>i.name===r))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const o=n.file;if(!(o instanceof File))return e.json({error:"missing file field"},400);try{const i=await o.arrayBuffer(),u=Buffer.from(i),{path:j}=await Ze(u,o.type||void 0,o.name||void 0);return e.json({path:j})}catch(i){return i instanceof Xe?e.json({error:i.message},i.status):(console.error("[upload]",i),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const r=decodeURIComponent(e.req.param("session")),t=new Map(ir(r).map(i=>[i.windowIndex,i.label])),n=new Map(oe(r).map(i=>[i.index,i])),o=hr(r).map(i=>({...i,label:t.get(i.index)??null,worktree:n.get(i.index)?.worktree??!1}));return e.json(o)}),s.post("/api/session/:session/window-label",a(),async e=>{const r=decodeURIComponent(e.req.param("session"));let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=t;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const o=typeof t.label=="string"?t.label:"",i=await ar(r,n,o);return e.json(i)}),s.post("/api/session/:session/select-window",a(),async e=>{const r=decodeURIComponent(e.req.param("session"));let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=t;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return gr(r,n),e.json({ok:!0})}catch(o){return o instanceof N?e.json({error:o.message},o.status):(console.error("[select-window]",o),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const r=decodeURIComponent(e.req.param("session"));let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=t;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof t.name!="string"||!t.name.trim())return e.json({error:"name is required"},400);try{return wr(r,n,t.name),se(r),e.json({ok:!0})}catch(o){return o instanceof N?e.json({error:o.message},o.status):(console.error("[rename-window]",o),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const r=decodeURIComponent(e.req.param("session"));try{return yr(r),se(r),e.json({ok:!0})}catch(t){return t instanceof N?e.json({error:t.message},t.status):(console.error("[new-window]",t),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(x.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=J(r);if(t)return e.json({error:t},400);if(!Ye(r))return e.json({error:"invalid body"},400);const n=await x.create(r);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await x.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const t=J(r);if(t)return e.json({error:t},400);if(!Ke(r))return e.json({error:"invalid body"},400);const n=await x.reschedule(e.req.param("id"),r.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)}),y&&(s.use("/a/:agentId/*",a()),br(s,{getChannel:y.getChannel})),s}const Ar=7;function xr(k){return typeof k!="number"||!Number.isFinite(k)?Ar:Math.min(365,Math.max(1,Math.round(k)))}export{ft as buildApp};
16
+ `;s.get("/sw.js",e=>e.body(ge,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!g.passwordHash,n=e.req.query("error");return e.html(Ae({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:c.activeTheme}))}),s.get("/",w(),e=>{const t=m?T():[],r=Z();return e.html(De({theme:c.activeTheme,commandbarEnabled:m,commandbarSessions:t,fsRoots:r,terminalCfg:P,renderer:L,scrollback:P.initialLines+2*P.historyChunk,wsBase:b&&V?.()?`/ws/a/${V()}`:void 0}))}),s.get("/notes",w(),e=>{const t=m?T():[];return e.html(xe(A.data.notes,c.activeTheme,m,t))}),s.get("/notes/:session",w(),e=>{const t=decodeURIComponent(e.req.param("session")),r=m?T():[];return e.html(Ie(t,c.activeTheme,m,r))}),s.get("/schedule",w(),e=>{const t=m?T():[];return e.html(Ee(x.list(),x.listTriggered(),c.activeTheme,ue,m,t))}),s.get("/history",w(),e=>{const t=R(),r=m?se(t,M()):[],n=new Set(t.map(o=>o.name));return e.html(Re($e(),c.activeTheme,m,r,n))}),s.get("/quick-commands",w(),e=>{const t=m?T():[];return e.html(Oe(ae(),c.activeTheme,m,t))}),s.get("/files",w(),e=>{const t=m?T():[],r=Z();return e.html(Ne(c.activeTheme,m,t,r))}),s.post("/api/history/clear",a(),async e=>(await Ge(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(ae())),s.post("/api/quick-commands",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await ut(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await lt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",a(),e=>ct(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(b)return e.json({error:"hub only"},403);const t=O(e),r=D.check(t);if(!r.allowed)return q("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const o=typeof n.password=="string"?n.password:"";if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!Tt(t))return q("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(c.settingUpPassword)return e.json({error:"Password setup in progress"},409);const l=We(o);if(l)return e.json({error:l},400);c.settingUpPassword=!0;try{g.passwordHash=await Fe(o),Qe(g),q("password_set",{ip:t})}finally{c.settingUpPassword=!1}const{plaintext:d}=v.createAccessToken("setup",g.security.tokenTtlDays);return $(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await He(o,g.passwordHash)){const l=D.recordFailure(t);return q("auth_failed",{ip:t,method:"password",failures:l.failures,permanentLock:l.permanentLock}),l.permanentLock?(v.revokeAll(),q("permanent_lock",{ip:t,failures:l.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):l.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:l.retryAfterMs},429)}D.recordSuccess(t);const u=`browser-${t}`,{plaintext:j}=v.createAccessToken(u,g.security.tokenTtlDays);return $(e,j),q("auth_success",{ip:t,method:"password",tokenName:u}),e.json({ok:!0,token:j})}),s.post("/api/auth/token",a(),async e=>{if(b)return e.json({error:"hub only"},403);const t=O(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,o=typeof r.ttlDays=="number"?r.ttlDays:g.security.tokenTtlDays,{stored:i,plaintext:u}=v.createAccessToken(n,o);return q("token_created",{ip:t,name:i.name,tokenId:i.id}),e.json({id:i.id,name:i.name,token:u,expiresAt:i.expiresAt})}),s.get("/api/auth/tokens",a(),e=>b?e.json({error:"hub only"},403):e.json(v.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>b?e.json({error:"hub only"},403):v.revoke(e.req.param("id"))?(q("token_revoked",{ip:O(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(me(e),e.json({ok:!0}))),s.get("/settings",w(),async e=>{const t=await te(),r=t.terminalRenderer??"xterm";return e.html(Ce({settings:t,renderer:L,rendererOverridden:L!==r,theme:c.activeTheme,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await te(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",o=t.defaultView==="recent"?"recent":"default",i=xt(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Je({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:o,scheduleHistoryDays:i}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{if(b)return e.json({error:"plugin management is hub-only"},403);let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const o=r==="remove"?await rt(n):r==="add"?await tt(n):{ok:!1,output:"unknown action"};return o.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(o.output.slice(0,800)),303)}),s.get("/settings/theme",w(),e=>e.html(_e({theme:c.activeTheme,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!ne(r)?e.redirect("/settings/theme",303):(c.activeTheme=await re(r),e.redirect("/settings/theme?saved=1",303))});const ye={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:c.activeTheme.template,templates:et.map(t=>({id:t,name:ye[t]}))})),s.post("/api/theme",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!ne(r)?e.json({error:"invalid theme template"},400):(c.activeTheme=await re(r),e.json({ok:!0,active:c.activeTheme.template}))}),s.get("/api/system/status",a(),e=>e.json(mt())),s.get("/api/system/processes",a(),e=>e.json(ft())),S&&(s.get("/settings/federation",w(),async e=>e.html(Le({config:S.getConfig(),status:S.status(),tokens:X().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:S.hostname},c.activeTheme))),s.get("/api/federation",a(),e=>e.json({config:S.getConfig(),status:S.status()})),s.post("/api/federation",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.hub=="string"?t.hub.trim():"",n=typeof t.token=="string"?t.token.trim():"",o=typeof t.name=="string"?t.name.trim().slice(0,64):"",i=t.enabled===!0;if(i){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const u={hub:r||void 0,token:n||void 0,name:o||void 0,enabled:i};return await S.setConfig(u),e.json({ok:!0,status:S.status()})}),s.get("/api/agent-tokens",a(),e=>e.json(X().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})))),s.post("/api/agent-tokens",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim().slice(0,64):"";if(!r)return e.json({error:"name is required"},400);const{id:n,token:o}=Pe(r);return e.json({ok:!0,id:n,name:r,token:o})}),s.delete("/api/agent-tokens/:id",a(),e=>{const t=e.req.param("id");return Ue(t)?e.json({ok:!0}):e.json({error:"no such token"},404)})),s.post("/api/system/kill",a(),async e=>{const{pid:t}=await e.req.json();if(typeof t!="number"||!Number.isInteger(t)||t<=0)return e.json({error:"invalid pid"},400);const r=pt(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",a(),e=>y?e.json(y.agents.list()):e.json([])),s.get("/api/sessions",a(),e=>m?e.json(T()):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(R().some(i=>i.name===r))return e.json({error:"session already exists"},409);try{return jt(r,n),e.json({ok:!0})}catch(i){const u=i instanceof N?i.message:"failed to create session";return e.json({error:u},500)}}),s.post("/api/sessions/rename",a(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(kt(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",a(),async e=>{try{const{name:t}=await e.req.json();return t?(vt(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=Ve(t),o=r.find(i=>i.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:o})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=h.join(t,r));const n=e.req.query("recursive")==="true";let o=r,i="",u=r.endsWith("/");if(!u)try{u=C(r).isDirectory()}catch{}u||(o=h.dirname(r),i=h.basename(r).toLowerCase());try{const j=je(o),l=[],d=[];for(const f of j)if(!f.startsWith(".")&&!(i&&!f.toLowerCase().startsWith(i))){try{const p=h.join(o,f);C(p).isDirectory()?(l.push(p),n&&Be(p,l,d,0)):d.push(p)}catch{}if(l.length+d.length>=5e3)break}return e.json({dirs:l,files:d})}catch{return e.json({dirs:[],files:[]})}});function we(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=E("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=E("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=E("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let o="";try{o=E("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const i=[];for(const d of o.split(`
17
+ `)){if(!d.trim())continue;const f=d[0],p=d[1],I=d.substring(3).trim(),H=p!==" "?p:f;i.push({path:I,status:H,staged:f,unstaged:p,additions:0,deletions:0})}const u=new Map;try{const d=E("git diff --numstat HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3});for(const f of d.split(`
18
+ `)){if(!f.trim())continue;const p=f.split(" ");if(p.length>=3){const I=parseInt(p[0],10)||0,H=parseInt(p[1],10)||0;u.set(p[2],{added:I,deleted:H})}}}catch{}for(const d of i){const f=u.get(d.path);f&&(d.additions=f.added,d.deletions=f.deleted)}let j=0,l=0;for(const d of i)j+=d.additions,l+=d.deletions;for(const d of i)if(d.status==="?"&&d.additions===0)try{const f=h.join(r,d.path),I=Y(f,"utf-8").split(`
19
+ `).length;d.additions=I,j+=I}catch{}return{repoRoot:r,branch:n,files:i,linesAdded:j,linesRemoved:l}}catch{return t}}return s.get("/api/git/status",a(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=_(t),n=we(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",a(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=_(t);if(!W(n)||!C(n).isDirectory())return e.json({error:"invalid repository path: "+n},400);try{const o=K("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),i=K("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:o,stagedDiff:i})}catch(o){throw console.error("[git/diff] execFileSync failed:",o?.code,o.message),o}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",a(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=_(t);if(!C(r).isFile())return e.json({error:"not a file"},400);const n=C(r).size;if(n>J)return e.json({error:"file too large",size:n,maxBytes:J},413);const o=Y(r);return e.json({path:r,content:o,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=_(t.path);return Me(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=_(t.path);return C(r).isFile()?(ke(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=_(t.path);return W(r)?e.json({error:"file already exists"},409):(be(h.dirname(r),{recursive:!0}),ve(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}}),s.get("/api/sidebar/sessions",a(),e=>{const t=e.req.query("currentSession");return e.json(F(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(ie(t))}),s.post("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=G(t);return"error"in r?e.json({error:r.error},400):(await nt(r.sessionName,r.windowIndex),e.json(F()))}),s.delete("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=G(t);return"error"in r?e.json({error:r.error},400):(await st(r.sessionName,r.windowIndex),e.json(F()))}),s.get("/api/notes",a(),e=>{const t=[...A.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",a(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=A.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},o=A.data.notes.findIndex(i=>i.scope===t);return o>=0?A.data.notes[o]=n:A.data.notes.push(n),await A.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!R().some(i=>i.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const o=n.file;if(!(o instanceof File))return e.json({error:"missing file field"},400);try{const i=await o.arrayBuffer(),u=Buffer.from(i),{path:j}=await Ze(u,o.type||void 0,o.name||void 0);return e.json({path:j})}catch(i){return i instanceof Xe?e.json({error:i.message},i.status):(console.error("[upload]",i),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(it(t).map(i=>[i.windowIndex,i.label])),n=new Map(ie(t).map(i=>[i.index,i])),o=ht(t).map(i=>({...i,label:r.get(i.index)??null,worktree:n.get(i.index)?.worktree??!1}));return e.json(o)}),s.post("/api/session/:session/window-label",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const o=typeof r.label=="string"?r.label:"",i=await at(t,n,o);return e.json(i)}),s.post("/api/session/:session/select-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return gt(t,n),e.json({ok:!0})}catch(o){return o instanceof N?e.json({error:o.message},o.status):(console.error("[select-window]",o),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return wt(t,n,r.name),oe(t),e.json({ok:!0})}catch(o){return o instanceof N?e.json({error:o.message},o.status):(console.error("[rename-window]",o),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return yt(t),oe(t),e.json({ok:!0})}catch(r){return r instanceof N?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(x.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ee(t);if(r)return e.json({error:r},400);if(!Ye(t))return e.json({error:"invalid body"},400);const n=await x.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await x.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ee(t);if(r)return e.json({error:r},400);if(!Ke(t))return e.json({error:"invalid body"},400);const n=await x.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)}),y&&(s.use("/a/:agentId/*",a()),bt(s,{getChannel:y.getChannel})),s}const At=7;function xt(k){return typeof k!="number"||!Number.isFinite(k)?At:Math.min(365,Math.max(1,Math.round(k)))}export{fr as buildApp};
@@ -1,4 +1,4 @@
1
- import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as b,commandbarHTML as x,commandbarScript as m}from"../commandbar.js";import{newSessionModalCSS as v,newSessionModalHTML as u,newSessionModalScript as h,reducedMotion as y}from"../shared-layout.js";import{icon as i}from"../icons.js";function $(o){return o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function n(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function E(o){const{theme:e,commandbarEnabled:t,commandbarSessions:a,fsRoots:s,terminalCfg:p,renderer:r,scrollback:l,wsBase:d}=o,c=`
1
+ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as b,commandbarHTML as x,commandbarScript as m}from"../commandbar.js";import{newSessionModalCSS as v,newSessionModalHTML as u,newSessionModalScript as h,reducedMotion as y}from"../shared-layout.js";import{icon as i}from"../icons.js";function $(o){return o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function n(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function T(o){const{theme:e,commandbarEnabled:t,commandbarSessions:a,fsRoots:s,terminalCfg:p,renderer:r,scrollback:l,wsBase:d}=o,c=`
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
  html, body {
4
4
  background: var(--page-bg); color: var(--page-fg);
@@ -116,23 +116,14 @@ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as b,commandbarH
116
116
  color: var(--panel-muted); padding: 8px 12px 4px; font-weight: 500;
117
117
  }
118
118
  .sidebar-section-label.agent-offline-label { color: color-mix(in srgb, var(--panel-muted) 60%, transparent); }
119
- .session-item.session-offline { opacity: 0.5; cursor: default; }
120
- .session-item .meta { margin-left: auto; }
121
- #tw-scope-native {
122
- flex-shrink: 0;
123
- max-width: 160px;
124
- background: color-mix(in srgb, var(--panel-bg) 80%, transparent);
125
- color: var(--page-fg);
126
- border: 1px solid var(--panel-border);
127
- border-radius: 8px;
128
- padding: 4px 8px;
129
- font-size: 12px;
130
- font-family: var(--font-sans);
131
- outline: none;
119
+ .sidebar-section-label.clickable {
132
120
  cursor: pointer;
121
+ user-select: none;
122
+ transition: color 0.15s;
133
123
  }
134
- #tw-scope-native:focus-visible { ${n()} }
135
- #tw-scope-native option { background: var(--panel-bg); color: var(--page-fg); }
124
+ .sidebar-section-label.clickable:hover { color: var(--panel-accent); }
125
+ .session-item.session-offline { opacity: 0.5; cursor: default; }
126
+ .session-item .meta { margin-left: auto; }
136
127
  .new-session-sidebar-btn {
137
128
  display: flex; align-items: center; gap: 8px;
138
129
  width: 100%; padding: 10px 12px; border-radius: 10px; margin-bottom: 6px;
@@ -644,7 +635,6 @@ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as b,commandbarH
644
635
  <div class="header-actions">
645
636
  ${t?`<button class="header-btn" id="cmdbar-btn" title="Search" aria-label="Search">${i("search")}</button>`:""}
646
637
  </div>
647
- <select id="tw-scope-native" title="Switch machine"><option value="">\u672C\u673A</option></select>
648
638
  <div id="header-git" title="Click to browse repository"></div>
649
639
  <span id="header-ram"></span>
650
640
  </header>
@@ -815,29 +805,6 @@ ${h("__onSessionCreated")}
815
805
  })();
816
806
  </script>
817
807
  <script>
818
- (function(){
819
- var sel = document.getElementById('tw-scope-native');
820
- if (!sel) return;
821
- function refresh(){
822
- fetch('/api/agents').then(function(r){ return r.json(); }).then(function(agents){
823
- var cur = window.__tmuxWebScopeAgent || null;
824
- var opts = '<option value="/">\u672C\u673A</option>';
825
- (agents || []).forEach(function(a){
826
- if (!a.online) return;
827
- var selAttr = a.agentId === cur ? ' selected' : '';
828
- opts += '<option value="/a/' + encodeURIComponent(a.agentId) + '/"' + selAttr + '>' + a.name + '</option>';
829
- });
830
- sel.innerHTML = opts;
831
- }).catch(function(){});
832
- }
833
- refresh();
834
- setInterval(refresh, 15000);
835
- sel.addEventListener('change', function(){
836
- if (sel.value) window.location.href = sel.value;
837
- });
838
- })();
839
- </script>
840
- <script>
841
808
  (function(){
842
809
  /* Logo toggle sidebar */
843
810
  var brand = document.getElementById('brand-toggle');
@@ -874,4 +841,4 @@ if ('serviceWorker' in navigator) {
874
841
  }
875
842
  </script>
876
843
  </body>
877
- </html>`}export{E as renderShell};
844
+ </html>`}export{T as renderShell};
@@ -1,4 +1,4 @@
1
- function r(e){const i=e.prefix,t=e.agentId??"";return`<script>
1
+ function n(e){const i=e.prefix,t=e.agentId??"";return`<script>
2
2
  window.__TMUX_WEB_SCOPE__ = { prefix: ${JSON.stringify(i)}, agentId: ${JSON.stringify(t)} };
3
3
  (function(){
4
4
  var prefix = window.__TMUX_WEB_SCOPE__.prefix || '';
@@ -37,35 +37,4 @@ window.__TMUX_WEB_SCOPE__ = { prefix: ${JSON.stringify(i)}, agentId: ${JSON.stri
37
37
  if (action && isRel(action) && action.indexOf(prefix) !== 0) f.setAttribute('action', pref(action));
38
38
  }, true);
39
39
  })();
40
- </script>
41
- <script>
42
- (function(){
43
- if (document.getElementById('tw-scope-native')) return; // shell has a native switcher
44
- if (document.getElementById('tw-scope-chip')) return;
45
- var prefix = window.__TMUX_WEB_SCOPE__ ? window.__TMUX_WEB_SCOPE__.prefix : '';
46
- var agentId = window.__TMUX_WEB_SCOPE__ ? window.__TMUX_WEB_SCOPE__.agentId : null;
47
- var style = 'position:fixed;bottom:12px;right:12px;z-index:9990;display:flex;align-items:center;gap:6px;';
48
- style += 'background:rgba(20,20,24,0.92);color:#e6e6e6;border:1px solid rgba(255,255,255,0.12);border-radius:20px;';
49
- style += 'padding:6px 12px;font-size:12px;font-family:system-ui,sans-serif;box-shadow:0 4px 16px rgba(0,0,0,0.3);';
50
- var chip = document.createElement('div');
51
- chip.id = 'tw-scope-chip';
52
- chip.style.cssText = style;
53
- chip.innerHTML = '<span>Machine</span><select id="tw-scope-select" style="background:#2a2a31;color:#e6e6e6;border:1px solid rgba(255,255,255,0.15);border-radius:8px;padding:3px 8px;font-size:12px;max-width:180px"></select>';
54
- document.body.appendChild(chip);
55
- var sel = document.getElementById('tw-scope-select');
56
- sel.innerHTML = '<option value="">Loading\u2026</option>';
57
- var origFetch = window.fetch;
58
- origFetch(location.origin + '/api/agents').then(function(r){ return r.json(); }).then(function(agents){
59
- var opts = '<option value="/" ' + (agentId ? '' : 'selected') + '>\u672C\u673A</option>';
60
- (agents || []).forEach(function(a){
61
- if (!a.online) return;
62
- var isCur = a.agentId === agentId;
63
- opts += '<option value="/a/' + encodeURIComponent(a.agentId) + '/" ' + (isCur ? 'selected' : '') + '>' + (isCur ? '\u2022 ' : '') + a.name + '</option>';
64
- });
65
- sel.innerHTML = opts;
66
- }).catch(function(){ sel.innerHTML = '<option value="">offline</option>'; });
67
- sel.addEventListener('change', function(){
68
- if (sel.value) window.location.href = sel.value;
69
- });
70
- })();
71
- </script>`}function o(e,i){if(e.includes("window.__TMUX_WEB_SCOPE__"))return e;const t=r(i),n=e.indexOf("</head>");return n===-1?e+t:e.slice(0,n)+t+e.slice(n)}export{o as injectScopeScript,r as scopeScriptHtml};
40
+ </script>`}function f(e,i){if(e.includes("window.__TMUX_WEB_SCOPE__"))return e;const t=n(i),r=e.indexOf("</head>");return r===-1?e+t:e.slice(0,r)+t+e.slice(r)}export{f as injectScopeScript,n as scopeScriptHtml};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {