tmux-weblink 1.0.1 → 1.0.3

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.
@@ -0,0 +1,4 @@
1
+ var wt="https://esm.sh/ghostty-web@0.4.0",vt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function M(o){return o.replace(vt,"")}var D;function gt(){return D||(document.querySelector("link[data-tmux-web-xterm-css]")?D=Promise.resolve():(D=new Promise(o=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>o(),{once:!0}),e.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(e)}),D))}var O=class J{terminal;fitAddon;container;constructor(e,i,r){this.container=e,this.terminal=i,this.fitAddon=r,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,i,r){let[{Terminal:s},{FitAddon:f}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),gt()]),g=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new J(e,g,new f)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(i=>this.terminal.write(e,i))}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=U(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))}},yt=class _{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,i){this.container=e,this.terminal=i,this.terminal.open(e)}static async create(e,i,r){let s=await import(wt);await s.init();let f=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new _(e,f)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(i=>this.terminal.write(e,i))}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=U(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let i=Math.floor(e.width/this.charW),r=Math.floor(e.height/this.charH);if(i<10||r<5)return!1;if(i!==this.colsValue||r!==this.rowsValue){this.colsValue=i,this.rowsValue=r,this.terminal.resize(i,r);for(let s of this.resizeCallbacks)s({cols:i,rows:r})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let i=this.terminal.getMode?.(2004)??!1;this.input(i?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(i=>e(i))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){let i=this.container.querySelector("canvas");if(i instanceof HTMLElement&&i.offsetWidth>0&&i.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let r=i.offsetWidth/this.colsValue,s=i.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=r,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function U(o){let e=o.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(e.width,i.width),height:Math.max(0,Math.min(e.height,i.height-Math.max(0,e.top)))}:e}function bt(o,e,i){return o.innerHTML="",o.classList.add("terminal-pending"),(async()=>{let r;if(i.renderer==="ghostty")try{r=await yt.create(o,i.scrollback,i.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),r=await O.create(o,i.scrollback,i.theme)}else r=await O.create(o,i.scrollback,i.theme);let s,f=0,g,l=null,Y=0,p="connecting",x=0,L=!1,k=[],w="",P=!1,y=1e3,C=!1,d="",m,G=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),Q="/api/session/"+encodeURIComponent(e)+"/upload",Z=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function $(){return k.join("")+w}async function tt(t,n=0){let a=t?r.viewportY():0,u=t?r.baseY():0,h=M($());if(r.reset(),!h){t||r.scrollToBottom();return}await r.write(h),t?r.scrollToLine(u+a+n):r.scrollToBottom()}function b(t){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(t))}function et(t){let n;try{n=JSON.parse(t)}catch{p==="live"&&(w+=t,r.write(M(t)));return}if(n.type==="auth.required"){let a=localStorage.getItem("tmux-web-token");a?b({type:"auth.token",token:a}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(n.type==="auth.ok"){n.token&&localStorage.setItem("tmux-web-token",n.token),y=1e3,T(),b({type:"resize",cols:r.cols,rows:r.rows}),c();return}if(n.type==="auth.failed"){if(n.permanentLock||n.retryAfterMs&&n.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(n.message);return}return}if(n.type==="snapshot"&&typeof n.data=="string"){let a=M(n.data);k=[a],w="",x=typeof n.lines=="number"?n.lines:i.terminal.initialLines,p="live",r.reset(),r.write(a).then(()=>r.scrollToBottom());return}if(n.type==="window_changed"&&typeof n.activeIndex=="number"){let a=new URL(location.href);a.searchParams.get("window")!==String(n.activeIndex)&&(a.searchParams.set("window",String(n.activeIndex)),history.replaceState(history.state,"",a)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:n.windows}));return}if(n.type==="history"&&typeof n.data=="string"){L=!1,n.lines>0&&n.data&&(k.unshift(n.data),x+=n.lines,tt(!0,n.lines));return}if(n.type==="data"&&typeof n.data=="string"){let a=M(n.data);if(p==="connecting"){p="live",w=a,r.write(a);return}p==="live"&&(w+=a,r.write(a))}}function A(){let t=G,n=localStorage.getItem("tmux-web-token");n&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(n)),s=new WebSocket(t),s.onopen=()=>{p="connecting",x=0,L=!1,k=[],w="",r.reset(),y=1e3},s.onmessage=a=>{typeof a.data=="string"&&et(a.data)},s.onclose=()=>{P||(p="connecting",setTimeout(()=>{y=Math.min(y*2,1e4),P||A()},y))},s.onerror=()=>s?.close()}function v(t){b({type:"input",data:t})}function V(){d&&(v(d),d="")}function T(){return r.fit()}function z(){o.classList.remove("terminal-pending")}function c(){f&&cancelAnimationFrame(f),g&&clearTimeout(g),T(),f=requestAnimationFrame(()=>{f=0,T()&&z()}),g=setTimeout(()=>{T()&&z()},120)}function nt(){c(),setTimeout(c,50),setTimeout(c,150),setTimeout(c,300)}function I(t){return typeof t=="string"&&t.startsWith("image/")}function W(t){return t?.files?.length?t.files[0]??null:null}function rt(t){if(!t?.items)return null;for(let n=0;n<t.items.length;n++){let a=t.items[n];if(a?.kind==="file"&&I(a.type))return a.getAsFile()}return null}async function it(t){let n=new FormData,a=t instanceof File&&t.name?t.name:"upload";n.append("file",t,a);let u=localStorage.getItem("tmux-web-token"),h={};u&&(h.Authorization="Bearer "+u);let H=await fetch(Q,{method:"POST",body:n,headers:h});if(!H.ok){let j="upload failed";try{let N=await H.json();N.error&&(j=N.error)}catch{}throw new Error(j)}let q=await H.json();if(!q.path)throw new Error("no path in response");return q.path}async function B(t){try{let n=await it(t);v(n)}catch{}}o.addEventListener("compositionstart",()=>{C=!0},!0),o.addEventListener("compositionend",t=>{C=!1,clearTimeout(m),m=void 0,t.data&&v(t.data),d=""},!0),r.onData(t=>{if(!C){if(d&&t.startsWith(d)&&t.length>d.length){d=t,clearTimeout(m),m=setTimeout(()=>{V(),m=void 0},40);return}d&&(clearTimeout(m),m=void 0,V()),v(t)}}),r.onResize(({cols:t,rows:n})=>b({type:"resize",cols:t,rows:n})),r.onScroll(()=>{p!=="live"||L||!r.isNearScrollbackTop()||(L=!0,b({type:"load_history",before:x}))}),r.attachCustomKeyEventHandler(t=>t.type!=="keydown"?!0:(t.ctrlKey||t.metaKey)&&t.code==="KeyV"?(t.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let n=await navigator.clipboard.read();for(let a of n)for(let u of a.types)if(I(u)){let h=await a.getType(u);await B(h);return}}}catch{}try{let n=await navigator.clipboard?.readText();n&&r.pasteText(F(n))}catch{}})(),!1):!0);let S=0;function F(t){return t.split(`\r
2
+ `).join(`
3
+ `).split(`
4
+ `).join("\r")}async function at(t){let n=rt(t.clipboardData);if(n){t.preventDefault(),t.stopPropagation();let h=Date.now();if(h-S<50)return;S=h,await B(n);return}let a=t.clipboardData?.getData("text/plain");if(!a)return;t.preventDefault(),t.stopPropagation();let u=Date.now();u-S<50||(S=u,r.pasteText(F(a)))}let E=0;function ot(t){t.preventDefault(),E++,W(t.dataTransfer)&&o.classList.add("terminal-drag-over")}function st(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function lt(t){t.preventDefault(),E--,E<=0&&(E=0,o.classList.remove("terminal-drag-over"))}async function ct(t){t.preventDefault(),E=0,o.classList.remove("terminal-drag-over");let n=W(t.dataTransfer);n&&await B(n)}function R(t){!Z||t.key!=="Escape"||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||r.isFocused()&&(t.preventDefault(),t.stopPropagation(),v("\x1B"))}o.addEventListener("paste",at),o.addEventListener("dragenter",ot),o.addEventListener("dragover",st),o.addEventListener("dragleave",lt),o.addEventListener("drop",ct),document.addEventListener("keydown",R,!0);function ut(t){if(t.touches.length!==1){l=null;return}let n=t.touches[0];n&&(l={startX:n.clientX,startY:n.clientY,lastX:n.clientX,lastY:n.clientY,scrolling:!1},t.stopPropagation())}function ht(t){if(!l||t.touches.length!==1)return;let n=t.touches[0];if(!n)return;let a=n.clientY-l.startY,u=n.clientX-l.startX;if(!l.scrolling){if(Math.abs(a)<8||Math.abs(a)<Math.abs(u))return;l.scrolling=!0,Y=Date.now()+500}t.preventDefault(),t.stopPropagation(),pt(-(n.clientY-l.lastY),n.clientX,n.clientY),l.lastX=n.clientX,l.lastY=n.clientY}function dt(t){if(!l)return;let n=l.scrolling;l=null,t.stopPropagation(),n?(Y=Date.now()+500,t.preventDefault()):r.focus()}function mt(t){l=null,t.stopPropagation()}function ft(t){t.pointerType==="touch"&&Date.now()<Y&&(t.preventDefault(),t.stopPropagation())}function pt(t,n,a){(o.querySelector(".xterm-screen")??o).dispatchEvent(new WheelEvent("wheel",{deltaY:t,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:n,clientY:a,bubbles:!0,cancelable:!0}))}o.addEventListener("touchstart",ut,{passive:!0,capture:!0}),o.addEventListener("touchmove",ht,{passive:!1,capture:!0}),o.addEventListener("touchend",dt,{passive:!1,capture:!0}),o.addEventListener("touchcancel",mt,{passive:!0,capture:!0}),o.addEventListener("pointerup",ft,!0);function K(){c()}window.addEventListener("resize",K),window.visualViewport?.addEventListener("resize",c),window.visualViewport?.addEventListener("scroll",c);let X=new ResizeObserver(()=>c());return X.observe(o),document.fonts?.ready.then(()=>{c(),setTimeout(c,50),setTimeout(c,200)}),o.addEventListener("focusin",nt,!0),requestAnimationFrame(()=>{T()&&z(),A(),c()}),{destroy(){if(P=!0,clearTimeout(m),m=void 0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}o.innerHTML="",o.classList.remove("terminal-pending"),window.removeEventListener("resize",K),X.disconnect(),document.removeEventListener("keydown",R,!0)},focus(){r.focus()},sendInput(t){v(t)}}})()}export{bt as a};
@@ -1,4 +1,4 @@
1
- import{a as F}from"./chunk-O3DBTEWH.js";var h=window.__TMUX_WEB_SHELL__,l=null,c=null,D="sessions",m="",B="",x="",H="",i=document.getElementById("sidebar-content"),g=document.getElementById("main-placeholder"),f=document.getElementById("terminal-container"),k=document.getElementById("file-editor"),P=document.getElementById("fe-path"),o=document.getElementById("fe-status"),E=document.getElementById("fe-content"),y=document.getElementById("fe-save"),R=document.getElementById("fe-delete"),V=document.getElementById("fe-back"),S=document.getElementById("fe-new-name"),$=document.getElementById("fe-new-btn"),T=document.getElementById("settings-backdrop"),N=document.getElementById("settings-popover"),j=document.querySelector(".sidebar");function J(){return window.matchMedia("(max-width: 640px)").matches}function O(){J()&&j.classList.add("collapsed")}function z(){j.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{z(),_("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{z(),_("files")});function _(e){D=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?L():U()}async function L(){i.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let t=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let n of t){let a=document.createElement("div");a.className="session-item"+(n.name===c?" active":""),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
1
+ import{a as F}from"./chunk-3ENIQONP.js";var h=window.__TMUX_WEB_SHELL__,l=null,c=null,D="sessions",m="",B="",x="",H="",i=document.getElementById("sidebar-content"),g=document.getElementById("main-placeholder"),f=document.getElementById("terminal-container"),k=document.getElementById("file-editor"),P=document.getElementById("fe-path"),o=document.getElementById("fe-status"),E=document.getElementById("fe-content"),y=document.getElementById("fe-save"),R=document.getElementById("fe-delete"),V=document.getElementById("fe-back"),S=document.getElementById("fe-new-name"),$=document.getElementById("fe-new-btn"),T=document.getElementById("settings-backdrop"),N=document.getElementById("settings-popover"),j=document.querySelector(".sidebar");function J(){return window.matchMedia("(max-width: 640px)").matches}function O(){J()&&j.classList.add("collapsed")}function z(){j.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{z(),_("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{z(),_("files")});function _(e){D=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?L():U()}async function L(){i.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let t=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let n of t){let a=document.createElement("div");a.className="session-item"+(n.name===c?" active":""),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
2
  <span>${b(n.name)}</span>
3
3
  <button class="session-edit-btn" data-session="${b(n.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
4
4
  `,a.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||M(n.name)}),i.appendChild(a);let s=a.querySelector(".session-edit-btn");s&&s.addEventListener("click",p=>{p.stopPropagation(),Z(n.name,s)})}}catch{i.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function b(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function M(e){if(!(c===e&&l)){k.style.display="none",g.style.display="none",f.style.display="",f.classList.add("terminal-pending"),O(),l&&(l.destroy(),l=null),c=e;try{l=await F(f,e,{terminal:h.terminal,scrollback:h.scrollback,theme:h.theme,renderer:h.renderer})}catch(t){console.error("[shell] terminal init failed:",t);let n=t&&typeof t=="object"&&"message"in t?t.message:String(t);f.textContent="Failed to open terminal for "+e+": "+n,f.classList.remove("terminal-pending")}}}var I=[];function U(){if(I=h.fsRoots||[],I.length===0){if(c){q(c);return}i.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}i.innerHTML="";for(let e of I){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${b(e)}`,t.addEventListener("click",()=>u(e)),i.appendChild(t)}}async function q(e){i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();t.path?u(t.path):i.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{i.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function u(e){m=e,i.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch("/api/fs/list?path="+encodeURIComponent(e))).json();i.innerHTML="";let n=A(e);if(n&&!I.some(s=>e===s)){let s=document.createElement("div");s.className="file-tree-item",s.innerHTML="..",s.addEventListener("click",()=>u(n)),i.appendChild(s)}if(t.dirs)for(let s of t.dirs){let p=s.split("/").pop()||s.split("\\").pop(),d=document.createElement("div");d.className="file-tree-item",d.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> ${b(p)}`,d.addEventListener("click",()=>u(s)),i.appendChild(d)}else i.innerHTML="";let a=t.files||[];for(let s of a){let p=s.split("/").pop()||s.split("\\").pop(),d=document.createElement("div");d.className="file-tree-item file",d.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${b(p)}`,d.addEventListener("click",()=>W(s)),i.appendChild(d)}}catch{i.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function A(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function W(e){B=e,f.style.display="none",l&&(l.destroy(),l=null,c=null),O(),g.style.display="none",k.style.display="flex",P.textContent=e,o.textContent="Loading...",y.style.display="none";try{let t=await fetch("/api/file?path="+encodeURIComponent(e));if(!t.ok){let a=await t.json();o.textContent=a.error||"Failed to load",E.value="";return}let n=await t.json();E.value=n.content,x=n.content,H=n.content,o.textContent=n.size+" bytes",y.style.display=""}catch{o.textContent="Failed to load",E.value=""}}E.addEventListener("input",()=>{x=E.value}),y.addEventListener("click",async()=>{y.textContent="Saving...",y.disabled=!0;try{let e=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B,content:x})});if(!e.ok){let t=await e.json();o.textContent=t.error||"Save failed";return}H=x,o.textContent="Saved"}catch{o.textContent="Save failed"}finally{y.disabled=!1,y.textContent="Save"}}),R.addEventListener("click",async()=>{if(confirm("Delete "+B+"?"))try{let e=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:B})});if(!e.ok){let t=await e.json();o.textContent=t.error||"Delete failed";return}k.style.display="none",g.style.display="flex",m&&u(m)}catch{o.textContent="Delete failed"}}),V.addEventListener("click",()=>{k.style.display="none",g.style.display="flex",m&&u(m)}),$.addEventListener("click",async()=>{let e=S.value.trim();if(!e)return;let t=m+"/"+e;try{let n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){let a=await n.json();o.textContent=a.error||"Create failed";return}S.value="",m&&u(m)}catch{o.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=N.classList.toggle("open");T.classList.toggle("open",e)}),T.addEventListener("click",()=>{N.classList.remove("open"),T.classList.remove("open")});var v="",w=document.createElement("div");w.className="session-popover-backdrop",document.body.appendChild(w);var r=document.createElement("div");r.id="session-popover",r.className="session-popover",r.style.display="none",r.innerHTML=`<div class="sp-field">
@@ -1 +1 @@
1
- import{a as t}from"./chunk-O3DBTEWH.js";var e=window.__TMUX_WEB_TERMINAL__;if(!e)throw new Error("missing tmux-web terminal config");var r=document.getElementById("terminal-container");if(!r)throw new Error("missing terminal container");var{sessionName:o,...m}=e;t(r,o,m).then(n=>{window.tmuxWeb={sendInput:i=>n.sendInput(i),focusTerminal:()=>n.focus()}});
1
+ import{a as t}from"./chunk-3ENIQONP.js";var e=window.__TMUX_WEB_TERMINAL__;if(!e)throw new Error("missing tmux-web terminal config");var r=document.getElementById("terminal-container");if(!r)throw new Error("missing terminal container");var{sessionName:o,...m}=e;t(r,o,m).then(n=>{window.tmuxWeb={sendInput:i=>n.sendInput(i),focusTerminal:()=>n.focus()}});
@@ -1,4 +1,4 @@
1
- const ht="https://esm.sh/ghostty-web@0.4.0",ft=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function k(o){return o.replace(ft,"")}let b;function dt(){return b||(document.querySelector("link[data-tmux-web-xterm-css]")?b=Promise.resolve():(b=new Promise(e=>{const n=document.createElement("link");n.rel="stylesheet",n.href="/assets/xterm.css",n.setAttribute("data-tmux-web-xterm-css",""),n.addEventListener("load",()=>e(),{once:!0}),n.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(n)}),b))}class D{terminal;fitAddon;container;constructor(e,n,i){this.container=e,this.terminal=n,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,i){const[{Terminal:s},{FitAddon:f}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),dt()]),p=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new D(e,p,new f)}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(){const e=X(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(){const e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}}class z{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,i){const s=await import(ht);await s.init();const f=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new z(e,f)}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(){const 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(){const e=X(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);const n=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(n<10||i<5)return!1;if(n!==this.colsValue||i!==this.rowsValue){this.colsValue=n,this.rowsValue=i,this.terminal.resize(n,i);for(const s of this.resizeCallbacks)s({cols:n,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){const 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(){const e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){const n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){const i=n.offsetWidth/this.colsValue,s=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}}function X(o){const e=o.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 pt(o,e,n){return o.innerHTML="",o.classList.add("terminal-pending"),(async()=>{let i;if(n.renderer==="ghostty")try{i=await z.create(o,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await D.create(o,n.scrollback,n.theme)}else i=await D.create(o,n.scrollback,n.theme);let s,f=0,p,l=null,M=0,d="connecting",T=0,x=!1,E=[],m="",Y=!1,w=1e3;const j=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),q="/api/session/"+encodeURIComponent(e)+"/upload",_=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function N(){return E.join("")+m}async function J(t,r=0){const a=t?i.viewportY():0,u=t?i.baseY():0,h=k(N());if(i.reset(),!h){t||i.scrollToBottom();return}await i.write(h),t?i.scrollToLine(u+a+r):i.scrollToBottom()}function g(t){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(t))}function G(t){let r;try{r=JSON.parse(t)}catch{d==="live"&&(m+=t,i.write(k(t)));return}if(r.type==="auth.required"){const a=localStorage.getItem("tmux-web-token");a?g({type:"auth.token",token:a}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(r.type==="auth.ok"){r.token&&localStorage.setItem("tmux-web-token",r.token),w=1e3,y(),g({type:"resize",cols:i.cols,rows:i.rows}),c();return}if(r.type==="auth.failed"){if(r.permanentLock||r.retryAfterMs&&r.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(r.message);return}return}if(r.type==="snapshot"&&typeof r.data=="string"){const a=k(r.data);E=[a],m="",T=typeof r.lines=="number"?r.lines:n.terminal.initialLines,d="live",i.reset(),i.write(a).then(()=>i.scrollToBottom());return}if(r.type==="window_changed"&&typeof r.activeIndex=="number"){const a=new URL(location.href);a.searchParams.get("window")!==String(r.activeIndex)&&(a.searchParams.set("window",String(r.activeIndex)),history.replaceState(history.state,"",a)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:r.windows}));return}if(r.type==="history"&&typeof r.data=="string"){x=!1,r.lines>0&&r.data&&(E.unshift(r.data),T+=r.lines,J(!0,r.lines));return}if(r.type==="data"&&typeof r.data=="string"){const a=k(r.data);if(d==="connecting"){d="live",m=a,i.write(a);return}d==="live"&&(m+=a,i.write(a))}}function H(){let t=j;const r=localStorage.getItem("tmux-web-token");r&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(r)),s=new WebSocket(t),s.onopen=()=>{d="connecting",T=0,x=!1,E=[],m="",i.reset(),w=1e3},s.onmessage=a=>{typeof a.data=="string"&&G(a.data)},s.onclose=()=>{Y||(d="connecting",setTimeout(()=>{w=Math.min(w*2,1e4),Y||H()},w))},s.onerror=()=>s?.close()}function L(t){g({type:"input",data:t})}function y(){return i.fit()}function C(){o.classList.remove("terminal-pending")}function c(){f&&cancelAnimationFrame(f),p&&clearTimeout(p),y(),f=requestAnimationFrame(()=>{f=0,y()&&C()}),p=setTimeout(()=>{y()&&C()},120)}function Q(){c(),setTimeout(c,50),setTimeout(c,150),setTimeout(c,300)}function I(t){return typeof t=="string"&&t.startsWith("image/")}function R(t){return t?.files?.length?t.files[0]??null:null}function Z(t){if(!t?.items)return null;for(let r=0;r<t.items.length;r++){const a=t.items[r];if(a?.kind==="file"&&I(a.type))return a.getAsFile()}return null}async function $(t){const r=new FormData,a=t instanceof File&&t.name?t.name:"upload";r.append("file",t,a);const u=localStorage.getItem("tmux-web-token"),h={};u&&(h.Authorization="Bearer "+u);const F=await fetch(q,{method:"POST",body:r,headers:h});if(!F.ok){let K="upload failed";try{const U=await F.json();U.error&&(K=U.error)}catch{}throw new Error(K)}const O=await F.json();if(!O.path)throw new Error("no path in response");return O.path}async function P(t){try{const r=await $(t);L(r)}catch{}}i.onData(t=>L(t)),i.onResize(({cols:t,rows:r})=>g({type:"resize",cols:t,rows:r})),i.onScroll(()=>{d!=="live"||x||!i.isNearScrollbackTop()||(x=!0,g({type:"load_history",before:T}))}),i.attachCustomKeyEventHandler(t=>t.type!=="keydown"?!0:(t.ctrlKey||t.metaKey)&&t.code==="KeyV"?(t.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){const r=await navigator.clipboard.read();for(const a of r)for(const u of a.types)if(I(u)){const h=await a.getType(u);await P(h);return}}}catch{}try{const r=await navigator.clipboard?.readText();r&&i.pasteText(A(r))}catch{}})(),!1):!0);let S=0;function A(t){return t.split(`\r
1
+ const pt="https://esm.sh/ghostty-web@0.4.0",wt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function M(o){return o.replace(wt,"")}let E;function gt(){return E||(document.querySelector("link[data-tmux-web-xterm-css]")?E=Promise.resolve():(E=new Promise(e=>{const r=document.createElement("link");r.rel="stylesheet",r.href="/assets/xterm.css",r.setAttribute("data-tmux-web-xterm-css",""),r.addEventListener("load",()=>e(),{once:!0}),r.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(r)}),E))}class Y{terminal;fitAddon;container;constructor(e,r,i){this.container=e,this.terminal=r,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,r,i){const[{Terminal:s},{FitAddon:m}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),gt()]),y=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:r,convertEol:!1,theme:i});return new Y(e,y,new m)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(r=>this.terminal.write(e,r))}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(){const e=N(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(){const e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}}class B{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,r){this.container=e,this.terminal=r,this.terminal.open(e)}static async create(e,r,i){const s=await import(pt);await s.init();const m=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:r,convertEol:!1,theme:i});return new B(e,m)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(r=>this.terminal.write(e,r))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){const 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(){const e=N(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);const r=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(r<10||i<5)return!1;if(r!==this.colsValue||i!==this.rowsValue){this.colsValue=r,this.rowsValue=i,this.terminal.resize(r,i);for(const s of this.resizeCallbacks)s({cols:r,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){const r=this.terminal.getMode?.(2004)??!1;this.input(r?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(r=>e(r))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){const e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){const r=this.container.querySelector("canvas");if(r instanceof HTMLElement&&r.offsetWidth>0&&r.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){const i=r.offsetWidth/this.colsValue,s=r.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}}function N(o){const e=o.getBoundingClientRect(),r=window.visualViewport;return r?{width:Math.min(e.width,r.width),height:Math.max(0,Math.min(e.height,r.height-Math.max(0,e.top)))}:e}function vt(o,e,r){return o.innerHTML="",o.classList.add("terminal-pending"),(async()=>{let i;if(r.renderer==="ghostty")try{i=await B.create(o,r.scrollback,r.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await Y.create(o,r.scrollback,r.theme)}else i=await Y.create(o,r.scrollback,r.theme);let s,m=0,y,l=null,C=0,p="connecting",L=0,S=!1,k=[],w="",P=!1,v=1e3,F=!1,h="",d;const J=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),G="/api/session/"+encodeURIComponent(e)+"/upload",Q=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Z(){return k.join("")+w}async function $(t,n=0){const a=t?i.viewportY():0,u=t?i.baseY():0,f=M(Z());if(i.reset(),!f){t||i.scrollToBottom();return}await i.write(f),t?i.scrollToLine(u+a+n):i.scrollToBottom()}function T(t){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(t))}function tt(t){let n;try{n=JSON.parse(t)}catch{p==="live"&&(w+=t,i.write(M(t)));return}if(n.type==="auth.required"){const a=localStorage.getItem("tmux-web-token");a?T({type:"auth.token",token:a}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(n.type==="auth.ok"){n.token&&localStorage.setItem("tmux-web-token",n.token),v=1e3,b(),T({type:"resize",cols:i.cols,rows:i.rows}),c();return}if(n.type==="auth.failed"){if(n.permanentLock||n.retryAfterMs&&n.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(n.message);return}return}if(n.type==="snapshot"&&typeof n.data=="string"){const a=M(n.data);k=[a],w="",L=typeof n.lines=="number"?n.lines:r.terminal.initialLines,p="live",i.reset(),i.write(a).then(()=>i.scrollToBottom());return}if(n.type==="window_changed"&&typeof n.activeIndex=="number"){const a=new URL(location.href);a.searchParams.get("window")!==String(n.activeIndex)&&(a.searchParams.set("window",String(n.activeIndex)),history.replaceState(history.state,"",a)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:n.windows}));return}if(n.type==="history"&&typeof n.data=="string"){S=!1,n.lines>0&&n.data&&(k.unshift(n.data),L+=n.lines,$(!0,n.lines));return}if(n.type==="data"&&typeof n.data=="string"){const a=M(n.data);if(p==="connecting"){p="live",w=a,i.write(a);return}p==="live"&&(w+=a,i.write(a))}}function R(){let t=J;const n=localStorage.getItem("tmux-web-token");n&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(n)),s=new WebSocket(t),s.onopen=()=>{p="connecting",L=0,S=!1,k=[],w="",i.reset(),v=1e3},s.onmessage=a=>{typeof a.data=="string"&&tt(a.data)},s.onclose=()=>{P||(p="connecting",setTimeout(()=>{v=Math.min(v*2,1e4),P||R()},v))},s.onerror=()=>s?.close()}function g(t){T({type:"input",data:t})}function W(){h&&(g(h),h="")}function b(){return i.fit()}function I(){o.classList.remove("terminal-pending")}function c(){m&&cancelAnimationFrame(m),y&&clearTimeout(y),b(),m=requestAnimationFrame(()=>{m=0,b()&&I()}),y=setTimeout(()=>{b()&&I()},120)}function et(){c(),setTimeout(c,50),setTimeout(c,150),setTimeout(c,300)}function A(t){return typeof t=="string"&&t.startsWith("image/")}function V(t){return t?.files?.length?t.files[0]??null:null}function nt(t){if(!t?.items)return null;for(let n=0;n<t.items.length;n++){const a=t.items[n];if(a?.kind==="file"&&A(a.type))return a.getAsFile()}return null}async function rt(t){const n=new FormData,a=t instanceof File&&t.name?t.name:"upload";n.append("file",t,a);const u=localStorage.getItem("tmux-web-token"),f={};u&&(f.Authorization="Bearer "+u);const H=await fetch(G,{method:"POST",body:n,headers:f});if(!H.ok){let j="upload failed";try{const q=await H.json();q.error&&(j=q.error)}catch{}throw new Error(j)}const _=await H.json();if(!_.path)throw new Error("no path in response");return _.path}async function z(t){try{const n=await rt(t);g(n)}catch{}}o.addEventListener("compositionstart",()=>{F=!0},!0),o.addEventListener("compositionend",t=>{F=!1,clearTimeout(d),d=void 0,t.data&&g(t.data),h=""},!0),i.onData(t=>{if(!F){if(h&&t.startsWith(h)&&t.length>h.length){h=t,clearTimeout(d),d=setTimeout(()=>{W(),d=void 0},40);return}h&&(clearTimeout(d),d=void 0,W()),g(t)}}),i.onResize(({cols:t,rows:n})=>T({type:"resize",cols:t,rows:n})),i.onScroll(()=>{p!=="live"||S||!i.isNearScrollbackTop()||(S=!0,T({type:"load_history",before:L}))}),i.attachCustomKeyEventHandler(t=>t.type!=="keydown"?!0:(t.ctrlKey||t.metaKey)&&t.code==="KeyV"?(t.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){const n=await navigator.clipboard.read();for(const a of n)for(const u of a.types)if(A(u)){const f=await a.getType(u);await z(f);return}}}catch{}try{const n=await navigator.clipboard?.readText();n&&i.pasteText(O(n))}catch{}})(),!1):!0);let D=0;function O(t){return t.split(`\r
2
2
  `).join(`
3
3
  `).split(`
4
- `).join("\r")}async function tt(t){const r=Z(t.clipboardData);if(r){t.preventDefault(),t.stopPropagation();const h=Date.now();if(h-S<50)return;S=h,await P(r);return}const a=t.clipboardData?.getData("text/plain");if(!a)return;t.preventDefault(),t.stopPropagation();const u=Date.now();u-S<50||(S=u,i.pasteText(A(a)))}let v=0;function et(t){t.preventDefault(),v++,R(t.dataTransfer)&&o.classList.add("terminal-drag-over")}function rt(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function nt(t){t.preventDefault(),v--,v<=0&&(v=0,o.classList.remove("terminal-drag-over"))}async function it(t){t.preventDefault(),v=0,o.classList.remove("terminal-drag-over");const r=R(t.dataTransfer);r&&await P(r)}function B(t){!_||t.key!=="Escape"||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||i.isFocused()&&(t.preventDefault(),t.stopPropagation(),L("\x1B"))}o.addEventListener("paste",tt),o.addEventListener("dragenter",et),o.addEventListener("dragover",rt),o.addEventListener("dragleave",nt),o.addEventListener("drop",it),document.addEventListener("keydown",B,!0);function at(t){if(t.touches.length!==1){l=null;return}const r=t.touches[0];r&&(l={startX:r.clientX,startY:r.clientY,lastX:r.clientX,lastY:r.clientY,scrolling:!1},t.stopPropagation())}function ot(t){if(!l||t.touches.length!==1)return;const r=t.touches[0];if(!r)return;const a=r.clientY-l.startY,u=r.clientX-l.startX;if(!l.scrolling){if(Math.abs(a)<8||Math.abs(a)<Math.abs(u))return;l.scrolling=!0,M=Date.now()+500}t.preventDefault(),t.stopPropagation(),ut(-(r.clientY-l.lastY),r.clientX,r.clientY),l.lastX=r.clientX,l.lastY=r.clientY}function st(t){if(!l)return;const r=l.scrolling;l=null,t.stopPropagation(),r?(M=Date.now()+500,t.preventDefault()):i.focus()}function lt(t){l=null,t.stopPropagation()}function ct(t){t.pointerType==="touch"&&Date.now()<M&&(t.preventDefault(),t.stopPropagation())}function ut(t,r,a){(o.querySelector(".xterm-screen")??o).dispatchEvent(new WheelEvent("wheel",{deltaY:t,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:r,clientY:a,bubbles:!0,cancelable:!0}))}o.addEventListener("touchstart",at,{passive:!0,capture:!0}),o.addEventListener("touchmove",ot,{passive:!1,capture:!0}),o.addEventListener("touchend",st,{passive:!1,capture:!0}),o.addEventListener("touchcancel",lt,{passive:!0,capture:!0}),o.addEventListener("pointerup",ct,!0);function V(){c()}window.addEventListener("resize",V),window.visualViewport?.addEventListener("resize",c),window.visualViewport?.addEventListener("scroll",c);const W=new ResizeObserver(()=>c());return W.observe(o),document.fonts?.ready.then(()=>{c(),setTimeout(c,50),setTimeout(c,200)}),o.addEventListener("focusin",Q,!0),requestAnimationFrame(()=>{y()&&C(),H(),c()}),{destroy(){if(Y=!0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}o.innerHTML="",o.classList.remove("terminal-pending"),window.removeEventListener("resize",V),W.disconnect(),document.removeEventListener("keydown",B,!0)},focus(){i.focus()},sendInput(t){L(t)}}})()}export{pt as initTerminal};
4
+ `).join("\r")}async function it(t){const n=nt(t.clipboardData);if(n){t.preventDefault(),t.stopPropagation();const f=Date.now();if(f-D<50)return;D=f,await z(n);return}const a=t.clipboardData?.getData("text/plain");if(!a)return;t.preventDefault(),t.stopPropagation();const u=Date.now();u-D<50||(D=u,i.pasteText(O(a)))}let x=0;function at(t){t.preventDefault(),x++,V(t.dataTransfer)&&o.classList.add("terminal-drag-over")}function ot(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function st(t){t.preventDefault(),x--,x<=0&&(x=0,o.classList.remove("terminal-drag-over"))}async function lt(t){t.preventDefault(),x=0,o.classList.remove("terminal-drag-over");const n=V(t.dataTransfer);n&&await z(n)}function K(t){!Q||t.key!=="Escape"||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||i.isFocused()&&(t.preventDefault(),t.stopPropagation(),g("\x1B"))}o.addEventListener("paste",it),o.addEventListener("dragenter",at),o.addEventListener("dragover",ot),o.addEventListener("dragleave",st),o.addEventListener("drop",lt),document.addEventListener("keydown",K,!0);function ct(t){if(t.touches.length!==1){l=null;return}const n=t.touches[0];n&&(l={startX:n.clientX,startY:n.clientY,lastX:n.clientX,lastY:n.clientY,scrolling:!1},t.stopPropagation())}function ut(t){if(!l||t.touches.length!==1)return;const n=t.touches[0];if(!n)return;const a=n.clientY-l.startY,u=n.clientX-l.startX;if(!l.scrolling){if(Math.abs(a)<8||Math.abs(a)<Math.abs(u))return;l.scrolling=!0,C=Date.now()+500}t.preventDefault(),t.stopPropagation(),mt(-(n.clientY-l.lastY),n.clientX,n.clientY),l.lastX=n.clientX,l.lastY=n.clientY}function ft(t){if(!l)return;const n=l.scrolling;l=null,t.stopPropagation(),n?(C=Date.now()+500,t.preventDefault()):i.focus()}function ht(t){l=null,t.stopPropagation()}function dt(t){t.pointerType==="touch"&&Date.now()<C&&(t.preventDefault(),t.stopPropagation())}function mt(t,n,a){(o.querySelector(".xterm-screen")??o).dispatchEvent(new WheelEvent("wheel",{deltaY:t,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:n,clientY:a,bubbles:!0,cancelable:!0}))}o.addEventListener("touchstart",ct,{passive:!0,capture:!0}),o.addEventListener("touchmove",ut,{passive:!1,capture:!0}),o.addEventListener("touchend",ft,{passive:!1,capture:!0}),o.addEventListener("touchcancel",ht,{passive:!0,capture:!0}),o.addEventListener("pointerup",dt,!0);function U(){c()}window.addEventListener("resize",U),window.visualViewport?.addEventListener("resize",c),window.visualViewport?.addEventListener("scroll",c);const X=new ResizeObserver(()=>c());return X.observe(o),document.fonts?.ready.then(()=>{c(),setTimeout(c,50),setTimeout(c,200)}),o.addEventListener("focusin",et,!0),requestAnimationFrame(()=>{b()&&I(),R(),c()}),{destroy(){if(P=!0,clearTimeout(d),d=void 0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}o.innerHTML="",o.classList.remove("terminal-pending"),window.removeEventListener("resize",U),X.disconnect(),document.removeEventListener("keydown",K,!0)},focus(){i.focus()},sendInput(t){g(t)}}})()}export{vt as initTerminal};
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import{chmodSync as $e,existsSync as K,mkdirSync as Be,readdirSync as pe,readFil
3
3
  <rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
4
4
  <path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
5
5
  <line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
6
- </svg>`,Le=e=>e.body(Ar,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Le),s.get("/favicon.ico",Le),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(rt({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:y}))}),s.get("/",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(mt({theme:y,commandbarEnabled:p,commandbarSessions:t,agentsEnabled:_,fsRoots:r,terminalCfg:B,renderer:Z,scrollback:B.initialLines+2*B.historyChunk}))}),s.get("/s/:session",I(),async e=>{const t=decodeURIComponent(e.req.param("session"));await jt(t),oe(t);const r=v(),n=A(),a=p?C(r,n):[];return e.html(tt(t,de,{commandbarEnabled:p,commandbarSessions:a,quickCommands:p?se():[],agentsEnabled:_,terminal:B,theme:y,renderer:Z}))}),s.get("/notes",I(),e=>{const t=p?C(v(),A()):[];return e.html(nt(j.data.notes,y,p,t,_))}),s.get("/notes/:session",I(),e=>{const t=decodeURIComponent(e.req.param("session")),r=p?C(v(),A()):[];return e.html(ot(t,y,p,r))}),s.get("/schedule",I(),e=>{const t=p?C(v(),A()):[];return e.html(it(M.list(),M.listTriggered(),y,Oe,p,t,_))}),s.get("/agents",I(),e=>{if(!_)return e.redirect("/settings",303);const t=p?C(v(),A()):[];return e.html(dt(y,p,t))}),s.get("/history",I(),e=>{const t=v(),r=p?C(t,A()):[],n=new Set(t.map(a=>a.name));return e.html(ut(kt(),y,p,r,_,n))}),s.get("/quick-commands",I(),e=>{const t=p?C(v(),A()):[];return e.html(ct(se(),y,p,t,_))}),s.get("/files",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(lt(y,p,t,_,r))}),s.post("/api/history/clear",i(),async e=>(await vt(),e.json({ok:!0}))),s.get("/api/quick-commands",i(),e=>e.json(se())),s.post("/api/quick-commands",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Qt(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Yt(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",i(),async e=>await Xt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.get("/api/agents",i(),async e=>{if(!_)return e.json({error:"agents page disabled"},403);const t=Me?yr():await fr();return e.json(t)}),s.post("/api/auth/password",async e=>{const t=J(e),r=E.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const a=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Ae(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(W)return e.json({error:"Password setup in progress"},409);const f=ge(a);if(f)return e.json({error:f},400);W=!0;try{l.passwordHash=await ye(a),he(l),c("password_set",{ip:t})}finally{W=!1}const{plaintext:T}=h.createAccessToken("setup",l.security.tokenTtlDays);return _e(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await we(a,l.passwordHash)){const f=E.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:f.failures,permanentLock:f.permanentLock}),f.permanentLock?(h.revokeAll(),c("permanent_lock",{ip:t,failures:f.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):f.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:f.retryAfterMs},429)}E.recordSuccess(t);const d=`browser-${t}`,{plaintext:S}=h.createAccessToken(d,l.security.tokenTtlDays);return _e(e,S),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:S})}),s.post("/api/auth/token",i(),async e=>{const t=J(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}`,a=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=h.createAccessToken(n,a);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",i(),e=>e.json(h.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",i(),e=>h.revoke(e.req.param("id"))?(c("token_revoked",{ip:J(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",i(),e=>(kr(e),e.json({ok:!0}))),s.get("/settings",I(),async e=>{const t=await ne(),r=t.terminalRenderer??"xterm";return e.html(st({settings:t,renderer:Z,rendererOverridden:Z!==r,theme:y,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await ne(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",a=t.defaultView==="recent"?"recent":"default",o=Ce(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Lt({...r,commandbar:t.commandbar!==void 0,agents:t.agents!==void 0,agentsBackgroundWatch:t.agentsBackgroundWatch!==void 0,terminalRenderer:n,defaultView:a,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const a=r==="remove"?await Ht(n):r==="add"?await Wt(n):{ok:!1,output:"unknown action"};return a.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(a.output.slice(0,800)),303)}),s.get("/settings/theme",I(),e=>e.html(at({theme:y,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!Te(r)?e.redirect("/settings/theme",303):(y=await Se(r),e.redirect("/settings/theme?saved=1",303))});const Ir={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",i(),e=>e.json({active:y.template,templates:Dt.map(t=>({id:t,name:Ir[t]}))})),s.post("/api/theme",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!Te(r)?e.json({error:"invalid theme template"},400):(y=await Se(r),e.json({ok:!0,active:y.template}))}),s.get("/api/sessions",i(),e=>p?e.json(C(v(),A())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",i(),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(v().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return ur(r,n),e.json({ok:!0})}catch(o){const d=o instanceof X?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",i(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(cr(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",i(),async e=>{try{const{name:t}=await e.req.json();return t?(lr(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",i(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=gt(t),a=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:a})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",i(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=g.join(t,r));const n=e.req.query("recursive")==="true";let a=r,o="",d=r.endsWith("/");if(!d)try{d=D(r).isDirectory()}catch{}d||(a=g.dirname(r),o=g.basename(r).toLowerCase());try{const S=pe(a),f=[],T=[];for(const O of S)if(!O.startsWith(".")&&!(o&&!O.toLowerCase().startsWith(o))){try{const b=g.join(a,O);D(b).isDirectory()?(f.push(b),n&&wt(b,f,T,0)):T.push(b)}catch{}if(f.length+T.length>=5e3)break}return e.json({dirs:f,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",i(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=Q(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>ke)return e.json({error:"file too large",size:n,maxBytes:ke},413);const a=Ve(r,"utf-8");return e.json({path:r,content:a,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",i(),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=Q(t.path);return yt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return D(r).isFile()?(Ge(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return K(r)?e.json({error:"file already exists"},409):(Be(g.dirname(r),{recursive:!0}),ze(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function ue(e){return{...Kt(v(),A(),$t()),currentSession:e??null}}function Ne(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",i(),e=>{const t=e.req.query("currentSession");return e.json(ue(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",i(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(be(t))}),s.post("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ut(r.sessionName,r.windowIndex),e.json(ue()))}),s.delete("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ft(r.sessionName,r.windowIndex),e.json(ue()))}),s.get("/api/notes",i(),e=>{const t=[...j.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",i(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=j.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",i(),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()},a=j.data.notes.findIndex(o=>o.scope===t);return a>=0?j.data.notes[a]=n:j.data.notes.push(n),await j.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!v().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const a=n.file;if(!(a instanceof File))return e.json({error:"missing file field"},400);try{const o=await a.arrayBuffer(),d=Buffer.from(o),{path:S}=await or(d,a.type||void 0,a.name||void 0);return e.json({path:S})}catch(o){return o instanceof nr?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",i(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Bt(t).map(o=>[o.windowIndex,o.label])),n=new Map(be(t).map(o=>[o.index,o])),a=sr(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(a)}),s.post("/api/session/:session/window-label",i(),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 a=typeof r.label=="string"?r.label:"",o=await Vt(t,n,a);return e.json(o)}),s.post("/api/session/:session/select-window",i(),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 ar(t,n),ee(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[select-window]",a),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",i(),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 dr(t,n,r.name),oe(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[rename-window]",a),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",i(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return ir(t),ee(t),oe(t),e.json({ok:!0})}catch(r){return r instanceof X?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",i(),e=>e.json(M.list(e.req.query("session")))),s.post("/api/schedule",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!qt(t))return e.json({error:"invalid body"},400);const n=await M.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",i(),async e=>await M.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!At(t))return e.json({error:"invalid body"},400);const n=await M.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const _r=parseInt(process.env.PORT||"21000",10),Cr=Je({fetch:s.fetch,port:_r},e=>{console.log(`tmux-web running at http://localhost:${e.port}`)});Me&&hr();const ce=new Ze({noServer:!0});function De(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
6
+ </svg>`,Le=e=>e.body(Ar,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Le),s.get("/favicon.ico",Le),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!l.passwordHash,n=e.req.query("error");return e.html(rt({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:y}))}),s.get("/",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(mt({theme:y,commandbarEnabled:p,commandbarSessions:t,agentsEnabled:_,fsRoots:r,terminalCfg:B,renderer:Z,scrollback:B.initialLines+2*B.historyChunk}))}),s.get("/s/:session",I(),async e=>{const t=decodeURIComponent(e.req.param("session"));await jt(t),oe(t);const r=v(),n=A(),a=p?C(r,n):[];return e.html(tt(t,de,{commandbarEnabled:p,commandbarSessions:a,quickCommands:p?se():[],agentsEnabled:_,terminal:B,theme:y,renderer:Z}))}),s.get("/notes",I(),e=>{const t=p?C(v(),A()):[];return e.html(nt(j.data.notes,y,p,t,_))}),s.get("/notes/:session",I(),e=>{const t=decodeURIComponent(e.req.param("session")),r=p?C(v(),A()):[];return e.html(ot(t,y,p,r))}),s.get("/schedule",I(),e=>{const t=p?C(v(),A()):[];return e.html(it(M.list(),M.listTriggered(),y,Oe,p,t,_))}),s.get("/agents",I(),e=>{if(!_)return e.redirect("/settings",303);const t=p?C(v(),A()):[];return e.html(dt(y,p,t))}),s.get("/history",I(),e=>{const t=v(),r=p?C(t,A()):[],n=new Set(t.map(a=>a.name));return e.html(ut(kt(),y,p,r,_,n))}),s.get("/quick-commands",I(),e=>{const t=p?C(v(),A()):[];return e.html(ct(se(),y,p,t,_))}),s.get("/files",I(),e=>{const t=p?C(v(),A()):[],r=je();return e.html(lt(y,p,t,_,r))}),s.post("/api/history/clear",i(),async e=>(await vt(),e.json({ok:!0}))),s.get("/api/quick-commands",i(),e=>e.json(se())),s.post("/api/quick-commands",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Qt(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Yt(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",i(),async e=>await Xt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.get("/api/agents",i(),async e=>{if(!_)return e.json({error:"agents page disabled"},403);const t=Me?yr():await fr();return e.json(t)}),s.post("/api/auth/password",async e=>{const t=J(e),r=E.check(t);if(!r.allowed)return c("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const a=typeof n.password=="string"?n.password:"";if(!l.passwordHash){if(!l.security.allowRemoteSetup&&!Ae(t))return c("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(W)return e.json({error:"Password setup in progress"},409);const f=ge(a);if(f)return e.json({error:f},400);W=!0;try{l.passwordHash=await ye(a),he(l),c("password_set",{ip:t})}finally{W=!1}const{plaintext:T}=h.createAccessToken("setup",l.security.tokenTtlDays);return _e(e,T),e.json({ok:!0,token:T,setupMode:!0})}if(!await we(a,l.passwordHash)){const f=E.recordFailure(t);return c("auth_failed",{ip:t,method:"password",failures:f.failures,permanentLock:f.permanentLock}),f.permanentLock?(h.revokeAll(),c("permanent_lock",{ip:t,failures:f.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):f.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:f.retryAfterMs},429)}E.recordSuccess(t);const d=`browser-${t}`,{plaintext:S}=h.createAccessToken(d,l.security.tokenTtlDays);return _e(e,S),c("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:S})}),s.post("/api/auth/token",i(),async e=>{const t=J(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}`,a=typeof r.ttlDays=="number"?r.ttlDays:l.security.tokenTtlDays,{stored:o,plaintext:d}=h.createAccessToken(n,a);return c("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",i(),e=>e.json(h.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",i(),e=>h.revoke(e.req.param("id"))?(c("token_revoked",{ip:J(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",i(),e=>(kr(e),e.json({ok:!0}))),s.get("/settings",I(),async e=>{const t=await ne(),r=t.terminalRenderer??"xterm";return e.html(st({settings:t,renderer:Z,rendererOverridden:Z!==r,theme:y,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await ne(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",a=t.defaultView==="recent"?"recent":"default",o=Ce(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Lt({...r,commandbar:t.commandbar!==void 0,agents:t.agents!==void 0,agentsBackgroundWatch:t.agentsBackgroundWatch!==void 0,terminalRenderer:n,defaultView:a,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const a=r==="remove"?await Ht(n):r==="add"?await Wt(n):{ok:!1,output:"unknown action"};return a.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(a.output.slice(0,800)),303)}),s.get("/settings/theme",I(),e=>e.html(at({theme:y,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",i(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!Te(r)?e.redirect("/settings/theme",303):(y=await Se(r),e.redirect("/settings/theme?saved=1",303))});const Ir={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",i(),e=>e.json({active:y.template,templates:Dt.map(t=>({id:t,name:Ir[t]}))})),s.post("/api/theme",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!Te(r)?e.json({error:"invalid theme template"},400):(y=await Se(r),e.json({ok:!0,active:y.template}))}),s.get("/api/sessions",i(),e=>p?e.json(C(v(),A())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",i(),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(v().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return ur(r,n),e.json({ok:!0})}catch(o){const d=o instanceof X?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",i(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(cr(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",i(),async e=>{try{const{name:t}=await e.req.json();return t?(lr(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",i(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=gt(t),a=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:a})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",i(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=g.join(t,r));const n=e.req.query("recursive")==="true";let a=r,o="",d=r.endsWith("/");if(!d)try{d=D(r).isDirectory()}catch{}d||(a=g.dirname(r),o=g.basename(r).toLowerCase());try{const S=pe(a),f=[],T=[];for(const O of S)if(!O.startsWith(".")&&!(o&&!O.toLowerCase().startsWith(o))){try{const b=g.join(a,O);D(b).isDirectory()?(f.push(b),n&&wt(b,f,T,0)):T.push(b)}catch{}if(f.length+T.length>=5e3)break}return e.json({dirs:f,files:T})}catch{return e.json({dirs:[],files:[]})}}),s.get("/api/file",i(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=Q(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>ke)return e.json({error:"file too large",size:n,maxBytes:ke},413);const a=Ve(r,"utf-8");return e.json({path:r,content:a,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",i(),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=Q(t.path);return yt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return D(r).isFile()?(Ge(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",i(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=Q(t.path);return K(r)?e.json({error:"file already exists"},409):(Be(g.dirname(r),{recursive:!0}),ze(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function ue(e){return{...Kt(v(),A(),$t()),currentSession:e??null}}function Ne(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",i(),e=>{const t=e.req.query("currentSession");return e.json(ue(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",i(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(be(t))}),s.post("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ut(r.sessionName,r.windowIndex),e.json(ue()))}),s.delete("/api/pinned-views",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=Ne(t);return"error"in r?e.json({error:r.error},400):(await Ft(r.sessionName,r.windowIndex),e.json(ue()))}),s.get("/api/notes",i(),e=>{const t=[...j.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",i(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=j.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",i(),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()},a=j.data.notes.findIndex(o=>o.scope===t);return a>=0?j.data.notes[a]=n:j.data.notes.push(n),await j.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",i(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!v().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const a=n.file;if(!(a instanceof File))return e.json({error:"missing file field"},400);try{const o=await a.arrayBuffer(),d=Buffer.from(o),{path:S}=await or(d,a.type||void 0,a.name||void 0);return e.json({path:S})}catch(o){return o instanceof nr?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",i(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Bt(t).map(o=>[o.windowIndex,o.label])),n=new Map(be(t).map(o=>[o.index,o])),a=sr(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(a)}),s.post("/api/session/:session/window-label",i(),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 a=typeof r.label=="string"?r.label:"",o=await Vt(t,n,a);return e.json(o)}),s.post("/api/session/:session/select-window",i(),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 ar(t,n),ee(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[select-window]",a),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",i(),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 dr(t,n,r.name),oe(t),e.json({ok:!0})}catch(a){return a instanceof X?e.json({error:a.message},a.status):(console.error("[rename-window]",a),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",i(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return ir(t),ee(t),oe(t),e.json({ok:!0})}catch(r){return r instanceof X?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",i(),e=>e.json(M.list(e.req.query("session")))),s.post("/api/schedule",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!qt(t))return e.json({error:"invalid body"},400);const n=await M.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",i(),async e=>await M.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",i(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ve(t);if(r)return e.json({error:r},400);if(!At(t))return e.json({error:"invalid body"},400);const n=await M.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const _r=parseInt(process.env.PORT||"21000",10),Cr=Je({fetch:s.fetch,port:_r,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)});Me&&hr();const ce=new Ze({noServer:!0});function De(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
7
7
  \r
8
8
  `),e.destroy()}Cr.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),a=n.pathname.match(/^\/ws\/(.+)$/);if(!a){t.destroy();return}const o=jr(e),d=e.headers.origin,S=l.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(S.length===0||!S.includes(d))){c("ws_rejected_origin",{ip:o,origin:d}),De(t,403,"Origin not allowed");return}const f=xr(o);if(f>=l.security.maxConnectionsPerIp){c("ws_rejected_per_ip_cap",{ip:o,liveFromIp:f}),De(t,429,"Too many connections");return}const T=Ie(e)||n.searchParams.get("token")||"",O=T?h.validateToken(T):null;ce.handleUpgrade(e,t,r,b=>{ce.emit("connection",b,e,decodeURIComponent(a[1]),o,O)})}),ce.on("connection",(e,t,r,n,a)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};H.set(e,o),c("ws_connected",{ip:n});let d=null;const{initialLines:S,historyChunk:f,syncIdleMs:T,syncMaxMs:O}=B;let b=!0,N=null,G=null,F=r,P=null;const z=()=>{N&&(clearTimeout(N),N=null),G&&(clearTimeout(G),G=null)},le=()=>{if(b){z(),b=!1;try{F=Jt(r)}catch{F=r}if(ee(r),!tr(F))try{const w=Zt(F,S);x(e,{type:"snapshot",data:qe(w),lines:S})}catch{}}},He=()=>{b&&(N&&clearTimeout(N),N=setTimeout(le,T))};function me(){try{d=et.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){x(e,{type:"data",data:`\r
9
9
  \x1B[31mFailed to attach to tmux session "${r}": ${u.message}\x1B[0m\r
@@ -1,4 +1,4 @@
1
- import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarHTML as m,commandbarScript as f}from"../commandbar.js";import{newSessionModalCSS as h,newSessionModalHTML as u,newSessionModalScript as v,reducedMotion as y}from"../shared-layout.js";import{icon as o}from"../icons.js";function C(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function a(n="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${n}; outline: none;`}function _(n){const{theme:e,commandbarEnabled:t,commandbarSessions:s,agentsEnabled:r,fsRoots:d,terminalCfg:l,renderer:i,scrollback:p}=n,c=`
1
+ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarHTML as m,commandbarScript as f}from"../commandbar.js";import{newSessionModalCSS as v,newSessionModalHTML as h,newSessionModalScript as u,reducedMotion as y}from"../shared-layout.js";import{icon as o}from"../icons.js";function C(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function a(n="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${n}; outline: none;`}function _(n){const{theme:e,commandbarEnabled:t,commandbarSessions:s,agentsEnabled:r,fsRoots:d,terminalCfg:l,renderer:i,scrollback:p}=n,c=`
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
  html, body {
4
4
  background: var(--page-bg); color: var(--page-fg);
@@ -28,6 +28,7 @@ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarH
28
28
  display: flex;
29
29
  height: 100vh;
30
30
  padding-top: 48px;
31
+ transition: height 0.05s;
31
32
  }
32
33
 
33
34
  /* \u2500\u2500 Sidebar \u2500\u2500 */
@@ -302,7 +303,7 @@ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarH
302
303
  /* \u2500\u2500 Commandbar \u2500\u2500 */
303
304
  ${t?x():""}
304
305
  ${y()}
305
- ${h()}
306
+ ${v()}
306
307
  @media (max-width: 640px) {
307
308
  .app-layout { flex-direction: column; }
308
309
  .sidebar { flex: 0 0 auto; border-right: none; border-bottom: 1px solid var(--panel-border); transition: max-height 0.2s; }
@@ -443,7 +444,7 @@ import{cssVarsStyle as g}from"../theme.js";import{commandbarCSS as x,commandbarH
443
444
  })();
444
445
  </script>
445
446
 
446
- ${u()}
447
+ ${h()}
447
448
  ${t?m():""}
448
449
 
449
450
  <script>
@@ -457,7 +458,18 @@ window.__onSessionCreated = async function(name) {
457
458
  await window.__refreshSidebar();
458
459
  window.__openSession(name);
459
460
  };
460
- ${v("__onSessionCreated")}
461
+ ${u("__onSessionCreated")}
462
+ </script>
463
+ <script>
464
+ // Sync app-layout height to visual viewport so mobile keyboard doesn't overlap
465
+ (function(){
466
+ var el = document.querySelector('.app-layout');
467
+ var vv = window.visualViewport;
468
+ if (!vv || !el) return;
469
+ function sync(){ el.style.height = vv.height + 'px'; }
470
+ vv.addEventListener('resize', sync);
471
+ sync();
472
+ })();
461
473
  </script>
462
474
  </body>
463
475
  </html>`}export{_ as renderShell};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +0,0 @@
1
- var dt="https://esm.sh/ghostty-web@0.4.0",mt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function k(a){return a.replace(mt,"")}var S;function ft(){return S||(document.querySelector("link[data-tmux-web-xterm-css]")?S=Promise.resolve():(S=new Promise(a=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>a(),{once:!0}),e.addEventListener("error",()=>a(),{once:!0}),document.head.appendChild(e)}),S))}var X=class j{terminal;fitAddon;container;constructor(e,i,r){this.container=e,this.terminal=i,this.fitAddon=r,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,i,r){let[{Terminal:s},{FitAddon:d}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),ft()]),p=new s({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new j(e,p,new d)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(i=>this.terminal.write(e,i))}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=q(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))}},pt=class N{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,i){this.container=e,this.terminal=i,this.terminal.open(e)}static async create(e,i,r){let s=await import(dt);await s.init();let d=new s.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new N(e,d)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(i=>this.terminal.write(e,i))}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=q(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let i=Math.floor(e.width/this.charW),r=Math.floor(e.height/this.charH);if(i<10||r<5)return!1;if(i!==this.colsValue||r!==this.rowsValue){this.colsValue=i,this.rowsValue=r,this.terminal.resize(i,r);for(let s of this.resizeCallbacks)s({cols:i,rows:r})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let i=this.terminal.getMode?.(2004)??!1;this.input(i?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(i=>e(i))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}updateCellMetrics(e=!1){let i=this.container.querySelector("canvas");if(i instanceof HTMLElement&&i.offsetWidth>0&&i.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let r=i.offsetWidth/this.colsValue,s=i.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=r,this.charH=s)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function q(a){let e=a.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(e.width,i.width),height:Math.max(0,Math.min(e.height,i.height-Math.max(0,e.top)))}:e}function wt(a,e,i){return a.innerHTML="",a.classList.add("terminal-pending"),(async()=>{let r;if(i.renderer==="ghostty")try{r=await pt.create(a,i.scrollback,i.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),r=await X.create(a,i.scrollback,i.theme)}else r=await X.create(a,i.scrollback,i.theme);let s,d=0,p,l=null,M=0,m="connecting",b=0,E=!1,T=[],f="",D=!1,w=1e3,O=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),U="/api/session/"+encodeURIComponent(e)+"/upload",J=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function _(){return T.join("")+f}async function G(t,n=0){let o=t?r.viewportY():0,u=t?r.baseY():0,h=k(_());if(r.reset(),!h){t||r.scrollToBottom();return}await r.write(h),t?r.scrollToLine(u+o+n):r.scrollToBottom()}function v(t){s?.readyState===WebSocket.OPEN&&s.send(JSON.stringify(t))}function Q(t){let n;try{n=JSON.parse(t)}catch{m==="live"&&(f+=t,r.write(k(t)));return}if(n.type==="auth.required"){let o=localStorage.getItem("tmux-web-token");o?v({type:"auth.token",token:o}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(n.type==="auth.ok"){n.token&&localStorage.setItem("tmux-web-token",n.token),w=1e3,g(),v({type:"resize",cols:r.cols,rows:r.rows}),c();return}if(n.type==="auth.failed"){if(n.permanentLock||n.retryAfterMs&&n.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(n.message);return}return}if(n.type==="snapshot"&&typeof n.data=="string"){let o=k(n.data);T=[o],f="",b=typeof n.lines=="number"?n.lines:i.terminal.initialLines,m="live",r.reset(),r.write(o).then(()=>r.scrollToBottom());return}if(n.type==="window_changed"&&typeof n.activeIndex=="number"){let o=new URL(location.href);o.searchParams.get("window")!==String(n.activeIndex)&&(o.searchParams.set("window",String(n.activeIndex)),history.replaceState(history.state,"",o)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:n.windows}));return}if(n.type==="history"&&typeof n.data=="string"){E=!1,n.lines>0&&n.data&&(T.unshift(n.data),b+=n.lines,G(!0,n.lines));return}if(n.type==="data"&&typeof n.data=="string"){let o=k(n.data);if(m==="connecting"){m="live",f=o,r.write(o);return}m==="live"&&(f+=o,r.write(o))}}function z(){let t=O,n=localStorage.getItem("tmux-web-token");n&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(n)),s=new WebSocket(t),s.onopen=()=>{m="connecting",b=0,E=!1,T=[],f="",r.reset(),w=1e3},s.onmessage=o=>{typeof o.data=="string"&&Q(o.data)},s.onclose=()=>{D||(m="connecting",setTimeout(()=>{w=Math.min(w*2,1e4),D||z()},w))},s.onerror=()=>s?.close()}function x(t){v({type:"input",data:t})}function g(){return r.fit()}function Y(){a.classList.remove("terminal-pending")}function c(){d&&cancelAnimationFrame(d),p&&clearTimeout(p),g(),d=requestAnimationFrame(()=>{d=0,g()&&Y()}),p=setTimeout(()=>{g()&&Y()},120)}function Z(){c(),setTimeout(c,50),setTimeout(c,150),setTimeout(c,300)}function B(t){return typeof t=="string"&&t.startsWith("image/")}function H(t){return t?.files?.length?t.files[0]??null:null}function $(t){if(!t?.items)return null;for(let n=0;n<t.items.length;n++){let o=t.items[n];if(o?.kind==="file"&&B(o.type))return o.getAsFile()}return null}async function tt(t){let n=new FormData,o=t instanceof File&&t.name?t.name:"upload";n.append("file",t,o);let u=localStorage.getItem("tmux-web-token"),h={};u&&(h.Authorization="Bearer "+u);let C=await fetch(U,{method:"POST",body:n,headers:h});if(!C.ok){let R="upload failed";try{let K=await C.json();K.error&&(R=K.error)}catch{}throw new Error(R)}let W=await C.json();if(!W.path)throw new Error("no path in response");return W.path}async function P(t){try{let n=await tt(t);x(n)}catch{}}r.onData(t=>x(t)),r.onResize(({cols:t,rows:n})=>v({type:"resize",cols:t,rows:n})),r.onScroll(()=>{m!=="live"||E||!r.isNearScrollbackTop()||(E=!0,v({type:"load_history",before:b}))}),r.attachCustomKeyEventHandler(t=>t.type!=="keydown"?!0:(t.ctrlKey||t.metaKey)&&t.code==="KeyV"?(t.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let n=await navigator.clipboard.read();for(let o of n)for(let u of o.types)if(B(u)){let h=await o.getType(u);await P(h);return}}}catch{}try{let n=await navigator.clipboard?.readText();n&&r.pasteText(A(n))}catch{}})(),!1):!0);let L=0;function A(t){return t.split(`\r
2
- `).join(`
3
- `).split(`
4
- `).join("\r")}async function et(t){let n=$(t.clipboardData);if(n){t.preventDefault(),t.stopPropagation();let h=Date.now();if(h-L<50)return;L=h,await P(n);return}let o=t.clipboardData?.getData("text/plain");if(!o)return;t.preventDefault(),t.stopPropagation();let u=Date.now();u-L<50||(L=u,r.pasteText(A(o)))}let y=0;function nt(t){t.preventDefault(),y++,H(t.dataTransfer)&&a.classList.add("terminal-drag-over")}function rt(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function it(t){t.preventDefault(),y--,y<=0&&(y=0,a.classList.remove("terminal-drag-over"))}async function ot(t){t.preventDefault(),y=0,a.classList.remove("terminal-drag-over");let n=H(t.dataTransfer);n&&await P(n)}function V(t){!J||t.key!=="Escape"||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||r.isFocused()&&(t.preventDefault(),t.stopPropagation(),x("\x1B"))}a.addEventListener("paste",et),a.addEventListener("dragenter",nt),a.addEventListener("dragover",rt),a.addEventListener("dragleave",it),a.addEventListener("drop",ot),document.addEventListener("keydown",V,!0);function at(t){if(t.touches.length!==1){l=null;return}let n=t.touches[0];n&&(l={startX:n.clientX,startY:n.clientY,lastX:n.clientX,lastY:n.clientY,scrolling:!1},t.stopPropagation())}function st(t){if(!l||t.touches.length!==1)return;let n=t.touches[0];if(!n)return;let o=n.clientY-l.startY,u=n.clientX-l.startX;if(!l.scrolling){if(Math.abs(o)<8||Math.abs(o)<Math.abs(u))return;l.scrolling=!0,M=Date.now()+500}t.preventDefault(),t.stopPropagation(),ht(-(n.clientY-l.lastY),n.clientX,n.clientY),l.lastX=n.clientX,l.lastY=n.clientY}function lt(t){if(!l)return;let n=l.scrolling;l=null,t.stopPropagation(),n?(M=Date.now()+500,t.preventDefault()):r.focus()}function ct(t){l=null,t.stopPropagation()}function ut(t){t.pointerType==="touch"&&Date.now()<M&&(t.preventDefault(),t.stopPropagation())}function ht(t,n,o){(a.querySelector(".xterm-screen")??a).dispatchEvent(new WheelEvent("wheel",{deltaY:t,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:n,clientY:o,bubbles:!0,cancelable:!0}))}a.addEventListener("touchstart",at,{passive:!0,capture:!0}),a.addEventListener("touchmove",st,{passive:!1,capture:!0}),a.addEventListener("touchend",lt,{passive:!1,capture:!0}),a.addEventListener("touchcancel",ct,{passive:!0,capture:!0}),a.addEventListener("pointerup",ut,!0);function I(){c()}window.addEventListener("resize",I),window.visualViewport?.addEventListener("resize",c),window.visualViewport?.addEventListener("scroll",c);let F=new ResizeObserver(()=>c());return F.observe(a),document.fonts?.ready.then(()=>{c(),setTimeout(c,50),setTimeout(c,200)}),a.addEventListener("focusin",Z,!0),requestAnimationFrame(()=>{g()&&Y(),z(),c()}),{destroy(){if(D=!0,s){try{s.close(1e3,"session switch")}catch{}s=void 0}a.innerHTML="",a.classList.remove("terminal-pending"),window.removeEventListener("resize",I),F.disconnect(),document.removeEventListener("keydown",V,!0)},focus(){r.focus()},sendInput(t){x(t)}}})()}export{wt as a};