tmux-weblink 2.0.31 → 2.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
var Zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function je(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function oe(e){return e.replace(Zt,"")}var ke;function Gt(){return ke||(document.querySelector("link[data-tmux-web-xterm-css]")?ke=Promise.resolve():(ke=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),ke))}var Qt=class Ct{terminal;fitAddon;container;constructor(t,n,
|
|
2
|
-
`)}selectRows(t,n){let
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function en(e){let t=e.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(t.width,n.width),height:Math.max(0,Math.min(t.height,n.height-Math.max(0,t.top)))}:t}function tn(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let
|
|
4
|
-
\x1B[31m`+oe(
|
|
5
|
-
`).then(()=>{be();try{o?.close()}catch{}});return}if(
|
|
1
|
+
var Zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function je(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function oe(e){return e.replace(Zt,"")}var ke;function Gt(){return ke||(document.querySelector("link[data-tmux-web-xterm-css]")?ke=Promise.resolve():(ke=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),ke))}var Qt=class Ct{terminal;fitAddon;container;constructor(t,n,s){this.container=t,this.terminal=n,this.fitAddon=s,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,n,s){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Gt()]),c=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,smoothScrollDuration:120,theme:s});return new Ct(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(n=>this.terminal.write(t,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,n=!1){this.terminal.scrollToLine(Math.max(0,t),n)}scrollLines(t){let n=this.terminal.buffer?.active;this.terminal.scrollLines(t)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=en(this.container);if(t.width<=0||t.height<=0)return!1;let n=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!n?.width||!n?.height)return this.fitAddon.fit(),!0;let s=n.width,o=n.height,r=this.terminal.element?.querySelector(".xterm-viewport"),c=r?Math.max(0,r.offsetWidth-r.clientWidth):0,l=Math.max(2,Math.floor((t.width-c)/s)),u=Math.max(2,Math.floor(t.height/o));return(l!==this.terminal.cols||u!==this.terminal.rows)&&this.terminal.resize(l,u),!0}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return t?t===this.container||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,n){let s=this.terminal.buffer.active,[o,r]=t<=n?[t,n]:[n,t],c=s.viewportY,l=[];for(let u=o;u<=r;u++){let p=s.getLine(c+u);l.push(p?p.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(t,n){let s=this.terminal.buffer.active,[o,r]=t<=n?[t,n]:[n,t];this.terminal.selectLines(s.viewportY+o,s.viewportY+r)}selectCells(t,n,s,o){let r=this.terminal.buffer.active,c=Math.min(n,o),l=Math.max(n,o);if(c===l){let u=Math.min(t,s),p=Math.max(t,s);this.terminal.select(u,r.viewportY+c,p-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+l)}getCellsText(t,n,s,o){let r=this.terminal.buffer.active,c=Math.min(n,o),l=Math.max(n,o),u=n<o?t:n>o?s:Math.min(t,s),p=n<o?s:n>o?t:Math.max(t,s),S=r.viewportY,k=[];for(let H=c;H<=l;H++){let ve=r.getLine(S+H);if(!ve){k.push("");continue}let O=H===c?u:0,Ne=H===l?p:this.terminal.cols-1,K="";for(let C=O;C<=Ne;C++){let z=ve.getCell(C);z&&(K+=z.getChars())}k.push(K.replace(/\s+$/,""))}return k.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function en(e){let t=e.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(t.width,n.width),height:Math.max(0,Math.min(t.height,n.height-Math.max(0,t.top)))}:t}function tn(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let s=await Qt.create(e,n.scrollback,n.theme),o,r=0,c,l=null,u=0,p=0,S=.2,k=10,H=.95,ve=.04;function O(){u&&(cancelAnimationFrame(u),u=0),p=0}function Ne(i){if(O(),Math.abs(i)<S)return;p=Math.max(-k,Math.min(k,i));let a=()=>{if(N){u=0;return}let d=e.querySelector(".xterm-rows > div"),f=d?d.getBoundingClientRect().height:16,b=f>0?Math.round(p*16/f):p>0?1:-1;b===0&&(b=p>0?1:-1);let I=s.viewportY()-b,W=s.baseY(),ae=Math.max(0,Math.min(I,W));if(s.scrollToLine(ae,!0),p*=H,ae!==I){u=0;return}if(Math.abs(p)<ve){u=0;return}u=requestAnimationFrame(a)};u=requestAnimationFrame(a)}let K=0,C="connecting",z=0,G=!1,ge=[],V="",N=!1,Q=1e3,Mt=location.protocol==="https:"?"wss:":"ws:",Tt=n.wsBase||"/ws",St=Mt+"//"+location.host+Tt+"/"+encodeURIComponent(t),Bt="/api/session/"+encodeURIComponent(t)+"/upload",It=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Dt(){return ge.join("")+V}async function Ht(i,a=0){let d=i?s.viewportY():0,f=oe(Dt());if(s.reset(),!f){i||s.scrollToBottom();return}await s.write(f),i?s.scrollToLine(a+d):s.scrollToBottom()}function j(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function At(i){let a;try{a=JSON.parse(i)}catch{C==="live"&&(V+=i,s.write(oe(i)));return}if(a.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?j({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="attach_failed"&&typeof a.message=="string"){ye=!0,C="dead",s.reset(),s.write(`\r
|
|
4
|
+
\x1B[31m`+oe(a.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{be();try{o?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),Q=1e3,F(),j({type:"resize",cols:s.cols,rows:s.rows}),M();return}if(a.type==="auth.failed"){if(a.permanentLock||a.retryAfterMs&&a.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(a.message);return}return}if(a.type==="snapshot"&&typeof a.data=="string"){let d=je(oe(a.data));ge=[d],V="",z=typeof a.lines=="number"?a.lines:n.terminal.initialLines,C="live",s.reset(),F(),j({type:"resize",cols:s.cols,rows:s.rows}),s.write(d).then(()=>s.scrollToBottom());return}if(a.type==="window_changed"&&typeof a.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(a.activeIndex)&&(d.searchParams.set("window",String(a.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:a.windows}));return}if(a.type==="history"&&typeof a.data=="string"){a.lines>0&&a.data?(ge.unshift(je(a.data)),z+=a.lines,Ht(!0,a.lines).finally(()=>{G=!1})):G=!1;return}if(a.type==="data"&&typeof a.data=="string"){let d=je(oe(a.data));if(C==="connecting"){C="live",V=d,s.write(d);return}C==="live"&&(V+=d,s.write(d))}}let ye=!1;function Ve(){if(ye)return;let i=St,a=localStorage.getItem("tmux-web-token");a&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),i+=(i.includes("?")?"&":"?")+"cols="+s.cols+"&rows="+s.rows,o=new WebSocket(i),o.onopen=()=>{C="connecting",z=0,G=!1,ge=[],V="",s.reset(),Q=1e3},o.onmessage=d=>{typeof d.data=="string"&&At(d.data)},o.onclose=()=>{N||(C="connecting",!ye&&setTimeout(()=>{Q=Math.min(Q*2,1e4),!N&&!ye&&Ve()},Q))},o.onerror=()=>o?.close()}function we(i){j({type:"input",data:i})}function F(){return s.fit()}function be(){e.classList.remove("terminal-pending")}function M(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),F(),r=requestAnimationFrame(()=>{r=0,F()&&be()}),c=setTimeout(()=>{F()&&be()},120)}function Rt(){M(),setTimeout(M,50),setTimeout(M,150),setTimeout(M,300)}function Xe(i){return typeof i=="string"&&i.startsWith("image/")}function Je(i){return i?.files?.length?i.files[0]??null:null}function $t(i){if(!i?.items)return null;for(let a=0;a<i.items.length;a++){let d=i.items[a];if(d?.kind==="file"&&Xe(d.type))return d.getAsFile()}return null}async function Nt(i){let a=new FormData,d=i instanceof File&&i.name?i.name:"upload";a.append("file",i,d);let f=localStorage.getItem("tmux-web-token"),b={};f&&(b.Authorization="Bearer "+f);let I=await fetch(Bt,{method:"POST",body:a,headers:b});if(!I.ok){let ae="upload failed";try{let st=await I.json();st.error&&(ae=st.error)}catch{}throw new Error(ae)}let W=await I.json();if(!W.path)throw new Error("no path in response");return W.path}async function Pe(i){try{let a=await Nt(i);we(a)}catch{}}s.onData(i=>we(i)),s.onResize(({cols:i,rows:a})=>j({type:"resize",cols:i,rows:a})),s.onScroll(()=>{C!=="live"||G||!s.isNearScrollbackTop()||(G=!0,j({type:"load_history",before:z}))});let ee=!1,te=!1,w=null,L=null,ne=document.createElement("div");ne.className="terminal-copy-banner",ne.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',ne.hidden=!0,e.appendChild(ne);let R=document.createElement("div");R.className="terminal-copy-banner terminal-copy-toast",R.textContent="Copied",R.hidden=!0,e.appendChild(R);function Ze(i,a){let d=e.querySelector(".xterm-screen, canvas"),f=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),b=f.height>0&&s.rows>0?f.height/s.rows:18,I=f.width>0&&s.cols>0?f.width/s.cols:9;return{row:Math.max(0,Math.min(s.rows-1,Math.floor((a-f.top)/b))),col:Math.max(0,Math.min(s.cols-1,Math.floor((i-f.left)/I)))}}async function Le(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let a=document.createElement("textarea");a.value=i,a.style.position="fixed",a.style.opacity="0",a.style.pointerEvents="none",document.body.appendChild(a),a.focus(),a.select();try{document.execCommand("copy")}catch{}a.remove()}let se;function Ee(){R.hidden=!1,se&&clearTimeout(se),se=setTimeout(()=>{R.hidden=!0},1e3)}function X(i){ee=i,ne.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&s.focus()}function Ge(){X(!ee)}function Pt(i){N||i.button!==0||te||(ee||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),te=!0,w=Ze(i.clientX,i.clientY),L=w,s.selectCells(w.col,w.row,L.col,L.row),document.addEventListener("mousemove",_e,!0),document.addEventListener("mouseup",ze,!0))}function _e(i){N||!te||!w||(i.preventDefault(),i.stopPropagation(),L=Ze(i.clientX,i.clientY),s.selectCells(w.col,w.row,L.col,L.row))}function ze(i){if(N||!te||!w||!L)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",_e,!0),document.removeEventListener("mouseup",ze,!0),te=!1;let a=s.getCellsText(w.col,w.row,L.col,L.row);w=null,L=null,s.clearSelection(),X(!1),a&&(R.textContent="\u5DF2\u590D\u5236",Le(a).then(Ee)),s.focus()}e.addEventListener("mousedown",Pt,!0),document.addEventListener("keydown",_t,!0);function _t(i){if(!(N||!s.isFocused()||i.defaultPrevented||i.isComposing)){if(ee){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),w=null,L=null,s.clearSelection(),X(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Le(s.getScreenText()).then(Ee),X(!1);return}}if(i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey){if(i.preventDefault(),i.stopPropagation(),w&&L){let a=s.getCellsText(w.col,w.row,L.col,L.row);w=null,L=null,s.clearSelection(),X(!1),a&&(R.textContent="Copied",Le(a).then(Ee))}else{let a=s.getSelection();a?(s.clearSelection(),R.textContent="Copied",Le(a).then(Ee)):Ge()}return}if((i.ctrlKey||i.metaKey)&&i.code==="KeyV"){i.preventDefault(),i.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let a=await navigator.clipboard.read();for(let d of a)for(let f of d.types)if(Xe(f)){let b=await d.getType(f);await Pe(b);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&s.pasteText(Qe(a))}catch{}})();return}}}let xe=0;function Qe(i){return i.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
8
|
+
`).join("\r")}async function zt(i){let a=$t(i.clipboardData);if(a){i.preventDefault(),i.stopPropagation();let b=Date.now();if(b-xe<50)return;xe=b,await Pe(a);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let f=Date.now();f-xe<50||(xe=f,s.pasteText(Qe(d)))}let ie=0;function jt(i){i.preventDefault(),ie++,Je(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function Ft(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function Wt(i){i.preventDefault(),ie--,ie<=0&&(ie=0,e.classList.remove("terminal-drag-over"))}async function Yt(i){i.preventDefault(),ie=0,e.classList.remove("terminal-drag-over");let a=Je(i.dataTransfer);a&&await Pe(a)}function et(i){!It||i.key!=="Escape"||ee||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),we("\x1B"))}e.addEventListener("paste",zt),e.addEventListener("dragenter",jt),e.addEventListener("dragover",Ft),e.addEventListener("dragleave",Wt),e.addEventListener("drop",Yt),document.addEventListener("keydown",et,!0);function Ut(i){if(i.touches.length!==1){l=null;return}let a=i.touches[0];a&&(O(),l={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,lastT:performance.now(),velocity:0,scrolling:!1},i.stopPropagation())}function qt(i){if(!l||i.touches.length!==1)return;let a=i.touches[0];if(!a)return;let d=a.clientY-l.startY,f=a.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(f))return;l.scrolling=!0,K=Date.now()+500}i.preventDefault(),i.stopPropagation();let b=performance.now(),I=a.clientY-l.lastY,W=b-l.lastT;W>0&&(l.velocity=I/W),Xt(-I),l.lastX=a.clientX,l.lastY=a.clientY,l.lastT=b}function Ot(i){if(!l)return;let a=l.scrolling,d=l.velocity;if(l=null,i.stopPropagation(),!a){s.focus();return}K=Date.now()+500,i.preventDefault(),d&&Ne(d)}function Kt(i){l=null,O(),i.stopPropagation()}function Vt(i){i.pointerType==="touch"&&Date.now()<K&&(i.preventDefault(),i.stopPropagation())}function Xt(i){let a=e.querySelector(".xterm-rows > div"),d=a?a.getBoundingClientRect().height:16,f=d>0?Math.round(i/d):i>0?1:-1;f===0&&(f=i>0?1:-1),s.scrollLines(f)}e.addEventListener("touchstart",Ut,{passive:!0,capture:!0}),e.addEventListener("touchmove",qt,{passive:!1,capture:!0}),e.addEventListener("touchend",Ot,{passive:!1,capture:!0}),e.addEventListener("touchcancel",Kt,{passive:!0,capture:!0}),e.addEventListener("pointerup",Vt,!0);function tt(){M()}window.addEventListener("resize",tt),window.visualViewport?.addEventListener("resize",M),window.visualViewport?.addEventListener("scroll",M);let nt=new ResizeObserver(()=>M());nt.observe(e),document.fonts?.ready.then(()=>{M(),setTimeout(M,50),setTimeout(M,200)}),e.addEventListener("focusin",Rt,!0);async function Jt(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{F()&&be(),(async()=>(await Jt(),F()&&j({type:"resize",cols:s.cols,rows:s.rows}),Ve(),M()))()}),{destroy(){if(N=!0,O(),X(!1),se&&clearTimeout(se),document.removeEventListener("mousemove",_e,!0),document.removeEventListener("mouseup",ze,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",tt),nt.disconnect(),document.removeEventListener("keydown",et,!0)},focus(){s.focus()},sendInput(i){we(i)},toggleCopyMode(){Ge()}}})()}var le=window.__TMUX_WEB_SHELL__,v=null,y=null,it=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,x=it;window.__tmuxWebScopeAgent=it;var Fe="sessions",P="",Ce="",Me="",at="";function T(e){return x?"/a/"+encodeURIComponent(x)+e:e}var h=document.getElementById("sidebar-content"),Y=document.getElementById("main-placeholder"),B=document.getElementById("terminal-container"),re=document.getElementById("file-editor"),nn=document.getElementById("fe-path"),D=document.getElementById("fe-status"),ce=document.getElementById("fe-content"),U=document.getElementById("fe-save"),sn=document.getElementById("fe-delete"),an=document.getElementById("fe-back"),ot=document.getElementById("fe-new-name"),on=document.getElementById("fe-new-btn"),We=document.getElementById("settings-backdrop"),lt=document.getElementById("settings-popover"),rt=document.querySelector(".sidebar");function ln(){return window.matchMedia("(max-width: 640px)").matches}function Ye(){ln()&&rt.classList.add("collapsed")}function ct(){rt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ct(),dt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ct(),dt("files")});function dt(e){Fe=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"?(Ue="",J()):Ln()}var Ue="";async function J(){if(Fe!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),n=JSON.stringify({recent:t.recent,agents:t.agents});if(n===Ue)return;Ue=n,pn(t)}catch{h.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")})}var ut="tmux-web-sidebar-group-collapsed";function pt(){try{return new Set(JSON.parse(localStorage.getItem(ut)||"[]"))}catch{return new Set}}function rn(e){try{localStorage.setItem(ut,JSON.stringify([...e]))}catch{}}function cn(e,t,n){if(!e)return"\u2026";let s=n&&(e===n||e.startsWith(n+"/"))?"~"+e.slice(n.length):e,o=s.split("/").filter(Boolean);if(o.length===0)return e;for(let r=1;r<=o.length;r++){let c=o.slice(-r).join("/");if(!t.some(l=>!l||l===e?!1:(n&&(l===n||l.startsWith(n+"/"))?"~"+l.slice(n.length):l).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return s}function dn(e,t){let n=new Map;for(let r of e){let c=r.path||"";n.has(c)||n.set(c,[]),n.get(c).push(r)}let s=[...n.keys()],o=pt();for(let[r,c]of n){let l=cn(r,s,t),u=o.has(r),p=document.createElement("div");p.className="session-group-header"+(u?" collapsed":""),p.title=r||"No working directory",p.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${m(l)}</span>`,p.addEventListener("click",()=>{let k=pt(),H=p.nextElementSibling;k.has(r)?(k.delete(r),p.classList.remove("collapsed"),H?.classList.remove("collapsed")):(k.add(r),p.classList.add("collapsed"),H?.classList.add("collapsed")),rn(k)});let S=document.createElement("div");S.className="session-group-body"+(u?" collapsed":"");for(let k of c)S.appendChild(un(k));h.appendChild(p),h.appendChild(S)}}function un(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!x?" active":""),t.setAttribute("data-session",e.name),t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
9
9
|
<span>${m(e.name)}</span>
|
|
10
10
|
<button class="session-edit-btn" data-session="${m(e.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
|
|
11
|
-
`,t.addEventListener("click",
|
|
12
|
-
<span>${m(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&u.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||de(l.name,o.agentId)}),
|
|
11
|
+
`,t.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||de(e.name)});let n=t.querySelector(".session-edit-btn");return n&&n.addEventListener("click",s=>{s.stopPropagation(),Mn(e.name,n)}),t}function pn(e){h.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],n=document.createElement("div");n.className="sidebar-section-label"+(x?" clickable":""),n.textContent=x?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",x&&(n.title="Back to hub",n.addEventListener("click",()=>{window.location.href="/"})),h.appendChild(n),dn(t,e.home);let s=e.agents||[];for(let o of s){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===x,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!r&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!r&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),h.appendChild(c);for(let l of o.sessions){let u=document.createElement("div");u.className="session-item"+(l.name===y&&x===o.agentId?" active":"")+(o.online?"":" session-offline"),u.setAttribute("data-session",l.name),u.setAttribute("data-agent",o.agentId),u.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${l.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
12
|
+
<span>${m(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&u.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||de(l.name,o.agentId)}),h.appendChild(u)}}}function m(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function de(e,t){if(!(y===e&&v&&x===(t||null))){re.style.display="none",Y.style.display="none",B.style.display="",B.classList.add("terminal-pending"),Ye(),v&&(Re(),v.destroy(),v=null),y=e,x=t||null,window.__tmuxWebScopeAgent=x,window.__tmuxWebCurrentSession=y;for(let n of h.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===e&&(n.getAttribute("data-agent")||null)===x);try{v=await tn(B,e,{terminal:le.terminal,scrollback:le.scrollback,theme:le.theme,wsBase:x?"/ws/a/"+encodeURIComponent(x):le.wsBase||"/ws"}),Et()}catch(n){console.error("[shell] terminal init failed:",n);let s=n&&typeof n=="object"&&"message"in n?n.message:String(n);B.textContent="Failed to open terminal for "+e+": "+s,B.classList.remove("terminal-pending")}}}var Te=[],g=null,ft=!1;function fn(e){if(!g||!g.files.length)return null;let t=g.repoRoot;if(!t||!e.startsWith(t))return null;let n=e.slice(t.length+1);for(let s of g.files)if(s.path===n)return s.status;return null}async function mn(e){ft=!0;try{let t=await fetch(T("/api/git/status?path="+encodeURIComponent(e)));t.ok?g=await t.json():g=null}catch{g=null}finally{ft=!1}}function hn(){if(!g||!g.branch)return"";let{branch:e,linesAdded:t,linesRemoved:n}=g,s=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
13
13
|
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
|
|
14
|
-
${m(e)}</div>`;return(t>0||n>0)&&(
|
|
15
|
-
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){let
|
|
16
|
-
${c}<span class="file-path">${m(
|
|
14
|
+
${m(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}h.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&Z&&g&&(e.stopPropagation(),yt())});var ue=document.getElementById("header-git"),mt=document.getElementById("git-diff-view"),ht=document.getElementById("gd-path"),Se=document.getElementById("gd-status"),Be=document.getElementById("gd-content"),vn=document.getElementById("gd-back"),Z=null;function pe(e){if(!e||!e.branch){ue.style.display="none",Z=null;return}Z=e.repoRoot;let{branch:t,linesAdded:n,linesRemoved:s}=e,o=`<span class="branch">${m(t)}</span>`;(n>0||s>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),ue.innerHTML=o,ue.style.display="block"}ue.addEventListener("click",e=>{Z&&g&&(e.stopPropagation(),yt())});var E=document.getElementById("git-popover"),Ie=document.getElementById("git-popover-backdrop");function qe(){let e=ue.getBoundingClientRect();E.style.top=e.bottom+4+"px",E.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function vt(){let e=Z,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(He(),e){Fe="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),_(e)}})}var gt="";function yt(){if(!g)return;let e=g.files||[];if(!e.length)E.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
15
|
+
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){let s=e[n],o=s.additions||0,r=s.deletions||0,c="";switch(s.status){case"M":c='<span class="git-file-status M">M</span>';break;case"A":c='<span class="git-file-status A">A</span>';break;case"D":c='<span class="git-file-status D">D</span>';break;case"?":c='<span class="git-file-status ?">?</span>';break;case"R":c='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${n}">
|
|
16
|
+
${c}<span class="file-path">${m(s.path)}</span>
|
|
17
17
|
<span class="file-add">${o>0?"+"+o:""}</span>
|
|
18
18
|
<span class="file-del">${r>0?"-"+r:""}</span>
|
|
19
|
-
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',gt=t,E.innerHTML=t,E.querySelectorAll(".git-popover-item").forEach(n=>{let
|
|
19
|
+
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',gt=t,E.innerHTML=t,E.querySelectorAll(".git-popover-item").forEach(n=>{let s=parseInt(n.dataset.idx||"0",10),o=e[s];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>bt(o.path)))})}qe(),E.classList.add("open"),Ie.classList.add("open"),vt()}function gn(){return window.innerWidth>=1024}function wt(e,t){let n=(e+(t&&e?`
|
|
20
20
|
`:"")+t).split(`
|
|
21
|
-
`),
|
|
21
|
+
`),s="",o=0,r=0;for(let c of n){let l=c;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),s+=`<div class="diff-line hunk"><span class="ln-body">${m(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(s+=`<div class="diff-line header"><span class="ln-body">${m(l)}</span></div>`);continue}if(l.startsWith("+"))r++,s+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("-"))o++,s+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("\\"))s+=`<div class="diff-line"><span class="ln-body">${m(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,s+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${m(l)}</span></div>`}}return{html:s,lineCount:n.length}}var De=null;async function bt(e){let t=Z;if(t)if(He(),gn()){B.style.display="none",v&&(Re(),v.destroy(),v=null,De=y,y=null),pe(null),re.style.display="none",Y.style.display="none",mt.style.display="flex",ht.textContent=e,Se.textContent="Loading\u2026",Be.innerHTML="";try{let n=await fetch(T("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();wn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);Se.textContent="Error",Be.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${m(s)}</div>`}}else{E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
22
22
|
<button class="diff-back">\u2190 Back</button>
|
|
23
23
|
<span class="diff-filename">${m(e)}</span>
|
|
24
24
|
<span class="diff-loading">Loading\u2026</span>
|
|
25
|
-
</div></div>`,
|
|
25
|
+
</div></div>`,qe(),E.classList.add("open"),Ie.classList.add("open");try{let n=await fetch(T("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();yn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
26
26
|
<button class="diff-back">\u2190 Back</button>
|
|
27
27
|
<span class="diff-filename">${m(e)}</span>
|
|
28
|
-
</div><div class="diff-empty">Error loading diff: ${m(
|
|
28
|
+
</div><div class="diff-empty">Error loading diff: ${m(s)}</div></div>`}}}function yn(e,t,n){if(!t&&!n){E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
29
29
|
<button class="diff-back">\u2190 Back</button>
|
|
30
30
|
<span class="diff-filename">${m(e)}</span>
|
|
31
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:
|
|
31
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:o}=wt(t,n);E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
32
32
|
<button class="diff-back">\u2190 Back</button>
|
|
33
33
|
<span class="diff-filename">${m(e)}</span>
|
|
34
34
|
<span class="diff-filesize">${o} lines</span>
|
|
35
|
-
</div><div class="diff-content">${
|
|
35
|
+
</div><div class="diff-content">${s}</div></div>`,E.querySelector(".diff-back")?.addEventListener("click",()=>{E.innerHTML=gt,qe(),g&&E.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),l=g.files[c];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>bt(l.path)))}),vt()})}function wn(e,t,n){if(!t&&!n){Se.textContent="No diff",Be.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:s,lineCount:o}=wt(t,n);ht.textContent=e,Se.textContent=o+" lines",Be.innerHTML=s}function bn(){mt.style.display="none",De?(B.style.display="",B.classList.add("terminal-pending"),de(De),De=null):y?(B.style.display="",B.classList.add("terminal-pending"),Et()):Y.style.display="flex"}vn.addEventListener("click",bn);function He(){E.classList.remove("open"),Ie.classList.remove("open")}Ie.addEventListener("click",He),document.addEventListener("keydown",e=>{e.key==="Escape"&&(He(),$e())});async function Lt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(n.ok){let s=await n.json();g=s,pe(s);return}}}catch{}g=null,pe(null)}var Ae=null;function Et(){Re(),y&&(Lt(y),Ae=setInterval(()=>{y&&Lt(y)},3e4))}function Re(){Ae&&(clearInterval(Ae),Ae=null)}function Ln(){if(Te=le.fsRoots||[],Te.length===0){if(y){En(y);return}h.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}h.innerHTML="";for(let e of Te){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> ${m(e)}`,t.addEventListener("click",()=>_(e)),h.appendChild(t)}}async function En(e){h.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(T("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?_(t.path):h.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{h.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function _(e){P=e,h.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=mn(e);try{let n=await(await fetch(T("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,h.innerHTML="",pe(g),h.innerHTML+=hn();let s=xn(e);if(s&&!Te.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>_(s)),h.appendChild(r)}if(n.dirs)for(let r of n.dirs){let c=r.split("/").pop()||r.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${m(c)}`,l.addEventListener("click",()=>_(r)),h.appendChild(l)}else h.innerHTML="";let o=n.files||[];for(let r of o){let c=r.split("/").pop()||r.split("\\").pop(),l=fn(r),u="",p="";if(l)switch(l){case"M":u="git-mod",p='<span class="git-file-status M">M</span>';break;case"A":u="git-add",p='<span class="git-file-status A">A</span>';break;case"D":u="git-del",p='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",p='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",p='<span class="git-file-status R">R</span>';break}let S=document.createElement("div");S.className="file-tree-item file"+(u?" "+u:""),S.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${p}${m(c)}`,S.addEventListener("click",()=>kn(r)),h.appendChild(S)}}catch{h.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function xn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function kn(e){Ce=e,B.style.display="none",v&&(Re(),v.destroy(),v=null,y=null),pe(null),Ye(),Y.style.display="none",re.style.display="flex",nn.textContent=e,D.textContent="Loading...",U.style.display="none";try{let t=await fetch(T("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let s=await t.json();D.textContent=s.error||"Failed to load",ce.value="";return}let n=await t.json();ce.value=n.content,Me=n.content,at=n.content,D.textContent=n.size+" bytes",U.style.display=""}catch{D.textContent="Failed to load",ce.value=""}}ce.addEventListener("input",()=>{Me=ce.value}),U.addEventListener("click",async()=>{U.textContent="Saving...",U.disabled=!0;try{let e=await fetch(T("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ce,content:Me})});if(!e.ok){let t=await e.json();D.textContent=t.error||"Save failed";return}at=Me,D.textContent="Saved"}catch{D.textContent="Save failed"}finally{U.disabled=!1,U.textContent="Save"}}),sn.addEventListener("click",async()=>{if(confirm("Delete "+Ce+"?"))try{let e=await fetch(T("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ce})});if(!e.ok){let t=await e.json();D.textContent=t.error||"Delete failed";return}re.style.display="none",Y.style.display="flex",P&&_(P)}catch{D.textContent="Delete failed"}}),an.addEventListener("click",()=>{re.style.display="none",Y.style.display="flex",P&&_(P)}),on.addEventListener("click",async()=>{let e=ot.value.trim();if(!e)return;let t=P+"/"+e;try{let n=await fetch(T("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){let s=await n.json();D.textContent=s.error||"Create failed";return}ot.value="",P&&_(P)}catch{D.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=lt.classList.toggle("open");We.classList.toggle("open",e)}),We.addEventListener("click",()=>{lt.classList.remove("open"),We.classList.remove("open")});var q="",fe=document.createElement("div");fe.className="session-popover-backdrop",document.body.appendChild(fe);var $=document.createElement("div");$.id="session-popover",$.className="session-popover",$.style.display="none",$.innerHTML=`<div class="sp-field">
|
|
36
36
|
<label>Rename session</label>
|
|
37
37
|
<input type="text" id="sp-name" />
|
|
38
38
|
</div>
|
|
@@ -41,5 +41,5 @@ var Zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
|
41
41
|
<button class="btn" id="sp-cancel">Cancel</button>
|
|
42
42
|
</div>
|
|
43
43
|
<hr class="sp-divider" />
|
|
44
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild($);function Cn(e){let t=e.getBoundingClientRect();$.style.top=t.bottom+4+"px",$.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function
|
|
45
|
-
`).length;A.rows=Math.min(e,4)}),A.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),kt())})),xt&&xt.addEventListener("click",kt),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var
|
|
44
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild($);function Cn(e){let t=e.getBoundingClientRect();$.style.top=t.bottom+4+"px",$.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Mn(e,t){q=e;let n=document.getElementById("sp-name");n.value=e,fe.classList.add("open"),$.style.display="block",Cn(t),setTimeout(()=>n.focus(),50)}function me(){fe.classList.remove("open"),$.style.display="none"}fe.addEventListener("click",me),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===q){me();return}try{if(!(await fetch(T("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:q,newName:e})})).ok)return;me(),J(),y===q&&de(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+q+'"?'))try{if(!(await fetch(T("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:q})})).ok)return;me(),y===q&&(v&&(v.destroy(),v=null),y=null,B.style.display="none",Y.style.display="flex"),J()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",me);var Tn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!v)return;let n=Tn[t.dataset.key];n&&(v.sendInput(n),v.focus())});var A=document.getElementById("mk-input"),xt=document.getElementById("mk-send");function kt(){if(!A||!v)return;let e=A.value;e&&(v.sendInput(e+"\r"),A.value="",A.rows=1,v.focus())}if(A&&(A.addEventListener("input",()=>{A.rows=1;let e=A.value.split(`
|
|
45
|
+
`).length;A.rows=Math.min(e,4)}),A.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),kt())})),xt&&xt.addEventListener("click",kt),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Oe=document.getElementById("process-panel"),he=document.getElementById("proc-list"),Sn=document.getElementById("proc-close"),Ke=document.getElementById("proc-backdrop"),Bn=document.getElementById("header-ram");function In(){he.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(T("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){he.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let n of e){let s=n.rss,o=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+m(n.command)+'">'+m(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">×</button></div>'}he.innerHTML=t;for(let n of he.querySelectorAll(".proc-row")){let s=n.querySelector(".proc-kill");s&&s.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(T("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{he.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Dn(){Oe.classList.add("open"),Ke.classList.add("open"),In()}function $e(){Oe.classList.remove("open"),Ke.classList.remove("open")}Bn.addEventListener("click",e=>{e.stopPropagation(),Oe.classList.contains("open")?$e():Dn()}),Sn.addEventListener("click",$e),Ke.addEventListener("click",$e),window.__openSession=de,window.__refreshSidebar=J,J(),Ye(),setInterval(()=>{document.visibilityState==="visible"&&J()},1e4);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const Oe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ne(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function R(s){return s.replace(Oe,"")}let X;function Ne(){return X||(document.querySelector("link[data-tmux-web-xterm-css]")?X=Promise.resolve():(X=new Promise(o=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>o(),{once:!0}),i.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(i)}),X))}class oe{terminal;fitAddon;container;constructor(o,i,n){this.container=o,this.terminal=i,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,i,n){const[{Terminal:a},{FitAddon:
|
|
2
|
-
`)}selectRows(o,i){const n=this.terminal.buffer.active,[a,
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Re(s){const o=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Xe(s,o,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await oe.create(s,i.scrollback,i.theme);let a,
|
|
1
|
+
const Oe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ne(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function R(s){return s.replace(Oe,"")}let X;function Ne(){return X||(document.querySelector("link[data-tmux-web-xterm-css]")?X=Promise.resolve():(X=new Promise(o=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>o(),{once:!0}),i.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(i)}),X))}class oe{terminal;fitAddon;container;constructor(o,i,n){this.container=o,this.terminal=i,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,i,n){const[{Terminal:a},{FitAddon:d}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),Ne()]),m=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,smoothScrollDuration:120,theme:n});return new oe(o,m,new d)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(o){return new Promise(i=>this.terminal.write(o,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(o,i=!1){this.terminal.scrollToLine(Math.max(0,o),i)}scrollLines(o){const i=this.terminal.buffer?.active;this.terminal.scrollLines(o)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){const o=Re(this.container);if(o.width<=0||o.height<=0)return!1;const n=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!n?.width||!n?.height)return this.fitAddon.fit(),!0;const a=n.width,d=n.height,m=this.terminal.element?.querySelector(".xterm-viewport"),l=m?Math.max(0,m.offsetWidth-m.clientWidth):0,f=Math.max(2,Math.floor((o.width-l)/a)),h=Math.max(2,Math.floor(o.height/d));return(f!==this.terminal.cols||h!==this.terminal.rows)&&this.terminal.resize(f,h),!0}focus(){this.terminal.focus()}pasteText(o){this.terminal.paste(o)}input(o){this.terminal.input(o,!1)}onData(o){this.terminal.onData(o),this.terminal.onBinary(o)}onResize(o){this.terminal.onResize(o)}onScroll(o){this.terminal.onScroll(o)}attachCustomKeyEventHandler(o){this.terminal.attachCustomKeyEventHandler(o)}isFocused(){const o=document.activeElement;return o?o===this.container||o===this.terminal.textarea||this.container.contains(o)||!!o.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(o,i){const n=this.terminal.buffer.active,[a,d]=o<=i?[o,i]:[i,o],m=n.viewportY,l=[];for(let f=a;f<=d;f++){const h=n.getLine(m+f);l.push(h?h.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(o,i){const n=this.terminal.buffer.active,[a,d]=o<=i?[o,i]:[i,o];this.terminal.selectLines(n.viewportY+a,n.viewportY+d)}selectCells(o,i,n,a){const d=this.terminal.buffer.active,m=Math.min(i,a),l=Math.max(i,a);if(m===l){const f=Math.min(o,n),h=Math.max(o,n);this.terminal.select(f,d.viewportY+m,h-f+1)}else this.terminal.selectLines(d.viewportY+m,d.viewportY+l)}getCellsText(o,i,n,a){const d=this.terminal.buffer.active,m=Math.min(i,a),l=Math.max(i,a),f=i<a?o:i>a?n:Math.min(o,n),h=i<a?n:i>a?o:Math.max(o,n),$=d.viewportY,C=[];for(let E=m;E<=l;E++){const _=d.getLine($+E);if(!_){C.push("");continue}const D=E===m?f:0,Q=E===l?h:this.terminal.cols-1;let P="";for(let g=D;g<=Q;g++){const L=_.getCell(g);L&&(P+=L.getChars())}C.push(P.replace(/\s+$/,""))}return C.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Re(s){const o=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Xe(s,o,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await oe.create(s,i.scrollback,i.theme);let a,d=0,m,l=null,f=0,h=0;const $=.2,C=10,E=.95,_=.04;function D(){f&&(cancelAnimationFrame(f),f=0),h=0}function Q(e){if(D(),Math.abs(e)<$)return;h=Math.max(-C,Math.min(C,e));const t=()=>{if(b){f=0;return}const r=s.querySelector(".xterm-rows > div"),c=r?r.getBoundingClientRect().height:16;let u=c>0?Math.round(h*16/c):h>0?1:-1;u===0&&(u=h>0?1:-1);const v=n.viewportY()-u,G=n.baseY(),N=Math.max(0,Math.min(v,G));if(n.scrollToLine(N,!0),h*=E,N!==v){f=0;return}if(Math.abs(h)<_){f=0;return}f=requestAnimationFrame(t)};f=requestAnimationFrame(t)}let P=0,g="connecting",L=0,Y=!1,q=[],I="",b=!1,F=1e3;const me=location.protocol==="https:"?"wss:":"ws:",he=i.wsBase||"/ws",pe=me+"//"+location.host+he+"/"+encodeURIComponent(o),we="/api/session/"+encodeURIComponent(o)+"/upload",ge=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function ye(){return q.join("")+I}async function ve(e,t=0){const r=e?n.viewportY():0,c=R(ye());if(n.reset(),!c){e||n.scrollToBottom();return}await n.write(c),e?n.scrollToLine(t+r):n.scrollToBottom()}function M(e){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(e))}function Te(e){let t;try{t=JSON.parse(e)}catch{g==="live"&&(I+=e,n.write(R(e)));return}if(t.type==="auth.required"){const r=localStorage.getItem("tmux-web-token");r?M({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(t.type==="attach_failed"&&typeof t.message=="string"){U=!0,g="dead",n.reset(),n.write(`\r
|
|
4
4
|
\x1B[31m`+R(t.message)+`\x1B[0m\r
|
|
5
|
-
`).then(()=>{H();try{a?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),F=1e3,S(),
|
|
5
|
+
`).then(()=>{H();try{a?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),F=1e3,S(),M({type:"resize",cols:n.cols,rows:n.rows}),y();return}if(t.type==="auth.failed"){if(t.permanentLock||t.retryAfterMs&&t.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(t.message);return}return}if(t.type==="snapshot"&&typeof t.data=="string"){const r=ne(R(t.data));q=[r],I="",L=typeof t.lines=="number"?t.lines:i.terminal.initialLines,g="live",n.reset(),S(),M({type:"resize",cols:n.cols,rows:n.rows}),n.write(r).then(()=>n.scrollToBottom());return}if(t.type==="window_changed"&&typeof t.activeIndex=="number"){const r=new URL(location.href);r.searchParams.get("window")!==String(t.activeIndex)&&(r.searchParams.set("window",String(t.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:t.windows}));return}if(t.type==="history"&&typeof t.data=="string"){t.lines>0&&t.data?(q.unshift(ne(t.data)),L+=t.lines,ve(!0,t.lines).finally(()=>{Y=!1})):Y=!1;return}if(t.type==="data"&&typeof t.data=="string"){const r=ne(R(t.data));if(g==="connecting"){g="live",I=r,n.write(r);return}g==="live"&&(I+=r,n.write(r))}}let U=!1;function re(){if(U)return;let e=pe;const t=localStorage.getItem("tmux-web-token");t&&(e+=(e.includes("?")?"&":"?")+"token="+encodeURIComponent(t)),e+=(e.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,a=new WebSocket(e),a.onopen=()=>{g="connecting",L=0,Y=!1,q=[],I="",n.reset(),F=1e3},a.onmessage=r=>{typeof r.data=="string"&&Te(r.data)},a.onclose=()=>{b||(g="connecting",!U&&setTimeout(()=>{F=Math.min(F*2,1e4),!b&&!U&&re()},F))},a.onerror=()=>a?.close()}function j(e){M({type:"input",data:e})}function S(){return n.fit()}function H(){s.classList.remove("terminal-pending")}function y(){d&&cancelAnimationFrame(d),m&&clearTimeout(m),S(),d=requestAnimationFrame(()=>{d=0,S()&&H()}),m=setTimeout(()=>{S()&&H()},120)}function xe(){y(),setTimeout(y,50),setTimeout(y,150),setTimeout(y,300)}function ie(e){return typeof e=="string"&&e.startsWith("image/")}function se(e){return e?.files?.length?e.files[0]??null:null}function be(e){if(!e?.items)return null;for(let t=0;t<e.items.length;t++){const r=e.items[t];if(r?.kind==="file"&&ie(r.type))return r.getAsFile()}return null}async function Ee(e){const t=new FormData,r=e instanceof File&&e.name?e.name:"upload";t.append("file",e,r);const c=localStorage.getItem("tmux-web-token"),u={};c&&(u.Authorization="Bearer "+c);const x=await fetch(we,{method:"POST",body:t,headers:u});if(!x.ok){let G="upload failed";try{const N=await x.json();N.error&&(G=N.error)}catch{}throw new Error(G)}const v=await x.json();if(!v.path)throw new Error("no path in response");return v.path}async function Z(e){try{const t=await Ee(e);j(t)}catch{}}n.onData(e=>j(e)),n.onResize(({cols:e,rows:t})=>M({type:"resize",cols:e,rows:t})),n.onScroll(()=>{g!=="live"||Y||!n.isNearScrollbackTop()||(Y=!0,M({type:"load_history",before:L}))});let A=!1,K=!1,p=null,w=null;const B=document.createElement("div");B.className="terminal-copy-banner",B.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',B.hidden=!0,s.appendChild(B);const T=document.createElement("div");T.className="terminal-copy-banner terminal-copy-toast",T.textContent="Copied",T.hidden=!0,s.appendChild(T);function ae(e,t){const r=s.querySelector(".xterm-screen, canvas"),u=(r&&r.getBoundingClientRect().width>0?r:s).getBoundingClientRect(),x=u.height>0&&n.rows>0?u.height/n.rows:18,v=u.width>0&&n.cols>0?u.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((t-u.top)/x))),col:Math.max(0,Math.min(n.cols-1,Math.floor((e-u.left)/v)))}}async function V(e){if(!e)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return}}catch{}const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",t.style.pointerEvents="none",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")}catch{}t.remove()}let z;function W(){T.hidden=!1,z&&clearTimeout(z),z=setTimeout(()=>{T.hidden=!0},1e3)}function k(e){A=e,B.hidden=!e,s.classList.toggle("terminal-copy-mode-active",e),e&&n.focus()}function le(){k(!A)}function Le(e){b||e.button!==0||K||(A||e.shiftKey)&&(e.preventDefault(),e.stopPropagation(),K=!0,p=ae(e.clientX,e.clientY),w=p,n.selectCells(p.col,p.row,w.col,w.row),document.addEventListener("mousemove",ee,!0),document.addEventListener("mouseup",te,!0))}function ee(e){b||!K||!p||(e.preventDefault(),e.stopPropagation(),w=ae(e.clientX,e.clientY),n.selectCells(p.col,p.row,w.col,w.row))}function te(e){if(b||!K||!p||!w)return;e.preventDefault(),e.stopPropagation(),document.removeEventListener("mousemove",ee,!0),document.removeEventListener("mouseup",te,!0),K=!1;const t=n.getCellsText(p.col,p.row,w.col,w.row);p=null,w=null,n.clearSelection(),k(!1),t&&(T.textContent="\u5DF2\u590D\u5236",V(t).then(W)),n.focus()}s.addEventListener("mousedown",Le,!0),document.addEventListener("keydown",Me,!0);function Me(e){if(!(b||!n.isFocused()||e.defaultPrevented||e.isComposing)){if(A){if(e.key==="Escape"){e.preventDefault(),e.stopPropagation(),p=null,w=null,n.clearSelection(),k(!1);return}if(e.key==="Enter"||(e.ctrlKey||e.metaKey)&&e.code==="KeyC"){e.preventDefault(),e.stopPropagation(),V(n.getScreenText()).then(W),k(!1);return}}if(e.altKey&&e.code==="KeyC"&&!e.ctrlKey&&!e.metaKey){if(e.preventDefault(),e.stopPropagation(),p&&w){const t=n.getCellsText(p.col,p.row,w.col,w.row);p=null,w=null,n.clearSelection(),k(!1),t&&(T.textContent="Copied",V(t).then(W))}else{const t=n.getSelection();t?(n.clearSelection(),T.textContent="Copied",V(t).then(W)):le()}return}if((e.ctrlKey||e.metaKey)&&e.code==="KeyV"){e.preventDefault(),e.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){const t=await navigator.clipboard.read();for(const r of t)for(const c of r.types)if(ie(c)){const u=await r.getType(c);await Z(u);return}}}catch{}try{const t=await navigator.clipboard?.readText();t&&n.pasteText(ce(t))}catch{}})();return}}}let J=0;function ce(e){return e.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function Se(e){const t=be(e.clipboardData);if(t){e.preventDefault(),e.stopPropagation();const u=Date.now();if(u-
|
|
8
|
+
`).join("\r")}async function Se(e){const t=be(e.clipboardData);if(t){e.preventDefault(),e.stopPropagation();const u=Date.now();if(u-J<50)return;J=u,await Z(t);return}const r=e.clipboardData?.getData("text/plain");if(!r)return;e.preventDefault(),e.stopPropagation();const c=Date.now();c-J<50||(J=c,n.pasteText(ce(r)))}let O=0;function Ce(e){e.preventDefault(),O++,se(e.dataTransfer)&&s.classList.add("terminal-drag-over")}function De(e){e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="copy")}function Pe(e){e.preventDefault(),O--,O<=0&&(O=0,s.classList.remove("terminal-drag-over"))}async function Ie(e){e.preventDefault(),O=0,s.classList.remove("terminal-drag-over");const t=se(e.dataTransfer);t&&await Z(t)}function ue(e){!ge||e.key!=="Escape"||A||e.defaultPrevented||e.repeat||e.metaKey||e.ctrlKey||e.altKey||n.isFocused()&&(e.preventDefault(),e.stopPropagation(),j("\x1B"))}s.addEventListener("paste",Se),s.addEventListener("dragenter",Ce),s.addEventListener("dragover",De),s.addEventListener("dragleave",Pe),s.addEventListener("drop",Ie),document.addEventListener("keydown",ue,!0);function ke(e){if(e.touches.length!==1){l=null;return}const t=e.touches[0];t&&(D(),l={startX:t.clientX,startY:t.clientY,lastX:t.clientX,lastY:t.clientY,lastT:performance.now(),velocity:0,scrolling:!1},e.stopPropagation())}function Ye(e){if(!l||e.touches.length!==1)return;const t=e.touches[0];if(!t)return;const r=t.clientY-l.startY,c=t.clientX-l.startX;if(!l.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(c))return;l.scrolling=!0,P=Date.now()+500}e.preventDefault(),e.stopPropagation();const u=performance.now(),x=t.clientY-l.lastY,v=u-l.lastT;v>0&&(l.velocity=x/v),Be(-x),l.lastX=t.clientX,l.lastY=t.clientY,l.lastT=u}function Fe(e){if(!l)return;const t=l.scrolling,r=l.velocity;if(l=null,e.stopPropagation(),!t){n.focus();return}P=Date.now()+500,e.preventDefault(),r&&Q(r)}function Ae(e){l=null,D(),e.stopPropagation()}function Ke(e){e.pointerType==="touch"&&Date.now()<P&&(e.preventDefault(),e.stopPropagation())}function Be(e){const t=s.querySelector(".xterm-rows > div"),r=t?t.getBoundingClientRect().height:16;let c=r>0?Math.round(e/r):e>0?1:-1;c===0&&(c=e>0?1:-1),n.scrollLines(c)}s.addEventListener("touchstart",ke,{passive:!0,capture:!0}),s.addEventListener("touchmove",Ye,{passive:!1,capture:!0}),s.addEventListener("touchend",Fe,{passive:!1,capture:!0}),s.addEventListener("touchcancel",Ae,{passive:!0,capture:!0}),s.addEventListener("pointerup",Ke,!0);function fe(){y()}window.addEventListener("resize",fe),window.visualViewport?.addEventListener("resize",y),window.visualViewport?.addEventListener("scroll",y);const de=new ResizeObserver(()=>y());de.observe(s),document.fonts?.ready.then(()=>{y(),setTimeout(y,50),setTimeout(y,200)}),s.addEventListener("focusin",xe,!0);async function ze(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(e=>setTimeout(e,1e3))])}catch{}}return requestAnimationFrame(()=>{S()&&H(),(async()=>(await ze(),S()&&M({type:"resize",cols:n.cols,rows:n.rows}),re(),y()))()}),{destroy(){if(b=!0,D(),k(!1),z&&clearTimeout(z),document.removeEventListener("mousemove",ee,!0),document.removeEventListener("mouseup",te,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}s.innerHTML="",s.classList.remove("terminal-pending"),window.removeEventListener("resize",fe),de.disconnect(),document.removeEventListener("keydown",ue,!0)},focus(){n.focus()},sendInput(e){j(e)},toggleCopyMode(){le()}}})()}export{Xe as initTerminal,ne as stripAltScreenSequences};
|
package/dist/lib/build-app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as P,readdirSync as ie,statSync as A,unlinkSync as ae,writeFileSync as de,mkdirSync as ue,readFileSync as $}from"node:fs";import{readFile as ce}from"node:fs/promises";import{execSync as _,execFileSync as G}from"node:child_process";import p from"node:path";import{fileURLToPath as le}from"node:url";import{Hono as me}from"hono";import{listSessions as L}from"../sessions.js";import{renderLoginPage as fe,renderNotesIndex as pe,renderNotesPage as he,renderSettings as ge,renderThemeSettings as ye,renderHistoryIndex as je,renderQuickCommandsPage as we,renderFilesIndex as ke,renderShell as ve}from"../frontend.js";import{renderFederationPage as qe}from"./pages/federation.js";import{createAgentToken as Se,removeAgentToken as Te,listAgentTokens as z}from"./agent-tokens.js";import{hashPassword as Ae,verifyPassword as be,validatePassword as xe}from"./auth.js";import{atomicWriteFileSync as _e}from"./atomicWrite.js";import{resolveFsPath as b,resolveFsRoots as B,MAX_FILE_BYTES as V,walkRecursive as Ce}from"./fs-access.js";import{captureSessionWindowsWithPath as Oe}from"./tmux-windows.js";import{audit as j}from"./auditLog.js";import{db as S}from"./db.js";import{getSessionAccessMap as Ee}from"./session-access.js";import{listWindowHistory as Ie,clearWindowHistory as Ne}from"./window-history.js";import{saveSecurityConfig as Re}from"./security-config.js";import{readSettings as Q,writeSettings as Pe}from"./settings.js";import{setActiveThemeTemplate as K}from"./theme-store.js";import{isThemeTemplateId as X,THEME_TEMPLATE_IDS as Le}from"./themes/index.js";import{pinView as De,unpinView as Fe,listPinnedViews as He}from"./pinned-views.js";import{getStoredWindows as
|
|
1
|
+
import{existsSync as P,readdirSync as ie,statSync as A,unlinkSync as ae,writeFileSync as de,mkdirSync as ue,readFileSync as $}from"node:fs";import{readFile as ce}from"node:fs/promises";import{execSync as _,execFileSync as G}from"node:child_process";import p from"node:path";import{fileURLToPath as le}from"node:url";import{Hono as me}from"hono";import{listSessions as L}from"../sessions.js";import{renderLoginPage as fe,renderNotesIndex as pe,renderNotesPage as he,renderSettings as ge,renderThemeSettings as ye,renderHistoryIndex as je,renderQuickCommandsPage as we,renderFilesIndex as ke,renderShell as ve}from"../frontend.js";import{renderFederationPage as qe}from"./pages/federation.js";import{createAgentToken as Se,removeAgentToken as Te,listAgentTokens as z}from"./agent-tokens.js";import{hashPassword as Ae,verifyPassword as be,validatePassword as xe}from"./auth.js";import{atomicWriteFileSync as _e}from"./atomicWrite.js";import{resolveFsPath as b,resolveFsRoots as B,MAX_FILE_BYTES as V,walkRecursive as Ce}from"./fs-access.js";import{captureSessionWindowsWithPath as Oe}from"./tmux-windows.js";import{audit as j}from"./auditLog.js";import{db as S}from"./db.js";import{getSessionAccessMap as Ee}from"./session-access.js";import{listWindowHistory as Ie,clearWindowHistory as Ne}from"./window-history.js";import{saveSecurityConfig as Re}from"./security-config.js";import{readSettings as Q,writeSettings as Pe}from"./settings.js";import{setActiveThemeTemplate as K}from"./theme-store.js";import{isThemeTemplateId as X,THEME_TEMPLATE_IDS as Le}from"./themes/index.js";import{pinView as De,unpinView as Fe,listPinnedViews as He}from"./pinned-views.js";import{getStoredWindows as Me}from"./session-windows.js";import{buildSidebarSessions as Ue}from"./sessions-sidebar.js";import{createQuickCommand as We,deleteQuickCommand as $e,listQuickCommands as Y,updateQuickCommand as Ge}from"./quick-commands.js";import{getSystemStatus as ze,getTopProcesses as Be,killProcess as Ve}from"./system-monitor.js";import{newTmuxSession as Qe,renameSession as Ke,killSession as Xe,TmuxWindowsError as Ye}from"./tmux-windows.js";import{registerAgentTunnel as Ze}from"./agent-tunnel.js";import{injectScopeScript as Je}from"./scope-script.js";const D="tmux-web-token",et=365;function tt(x){return x==="127.0.0.1"||x==="::1"||x==="::ffff:127.0.0.1"}function Rt(x){const{mode:Z,securityConfig:h,tokenStore:y,rateLimiter:O,settings:rt,terminalBufferConfig:E,getAgentId:F,state:u,federation:w,hub:q}=x,k=Z==="agent";function C(e){if(h.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function I(e){const t=e.req.header("authorization");if(t?.startsWith("Bearer "))return t.slice(7);const r=e.req.header("cookie");if(!r)return null;const n=r.match(new RegExp(`(?:^|;\\s*)${D}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function nt(e){const t=I(e);return t?y.validateToken(t):null}function H(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${D}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${et*86400}${r?"; Secure":""}`)}function J(e){e.header("Set-Cookie",`${D}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}const o=()=>k?async(e,t)=>t():async(e,t)=>{const r=I(e);return!r||!y.validateToken(r)?(j("http_unauthorized",{ip:C(e)}),e.json({error:"unauthorized"},401)):t()},g=()=>k?async(e,t)=>t():async(e,t)=>{const r=I(e);if(!r||!y.validateToken(r)){const n=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${n}`,302)}return t()};function N(e){const t=Ue(L(),Ee(),He()),r=[];if(q)for(const n of q.agents.list()){const i=q.agents.get(n.agentId);r.push({agentId:n.agentId,agentName:n.name,online:n.online,sessions:i?i.sessions:[]})}return{...t,currentSession:e??null,agents:r}}function M(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}const s=new me;s.use("*",async(e,t)=>{await t(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline' https://fonts.googleapis.com","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self' https://fonts.gstatic.com","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,t)=>{const r=e.req.method;if(r==="GET"||r==="HEAD"||r==="OPTIONS")return t();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):t()}),k||s.use("*",async(e,t)=>{if(await t(),e.req.method!=="GET"||e.req.path==="/login"||!(e.res.headers.get("content-type")??"").includes("text/html"))return;const n=await e.res.text();e.res=new Response(Je(n,{prefix:"",agentId:null}),e.res)});const U=p.dirname(le(import.meta.url)),ee=[p.join(U,"..","assets"),p.join(U,"assets"),p.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of ee){const n=p.join(r,t);if(!P(n))continue;const i=await ce(n),a=p.extname(t),c={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(i,200,{"Content-Type":c[a]??"application/octet-stream","Cache-Control":a===".js"||a===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const te=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
2
|
<rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
|
|
3
3
|
<path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
4
|
<line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
|
|
@@ -13,7 +13,7 @@ self.addEventListener("activate", (e) => { e.waitUntil(clients.claim()); });
|
|
|
13
13
|
self.addEventListener("fetch", (e) => {
|
|
14
14
|
e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
|
|
15
15
|
});
|
|
16
|
-
`;s.get("/sw.js",e=>e.body(ne,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!h.passwordHash,n=e.req.query("error");return e.html(fe({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:u.activeTheme}))}),s.get("/",g(),e=>{const t=B();return e.html(ve({theme:u.activeTheme,fsRoots:t,terminalCfg:E,scrollback:E.initialLines+2*E.historyChunk,wsBase:k&&F?.()?`/ws/a/${F()}`:void 0}))}),s.get("/notes",g(),e=>e.html(pe(S.data.notes,u.activeTheme))),s.get("/notes/:session",g(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(he(t,u.activeTheme))}),s.get("/history",g(),e=>{const t=L(),r=new Set(t.map(n=>n.name));return e.html(je(Ie(),u.activeTheme,r))}),s.get("/quick-commands",g(),e=>e.html(we(Y(),u.activeTheme))),s.get("/files",g(),e=>{const t=B();return e.html(ke(u.activeTheme,t))}),s.post("/api/history/clear",o(),async e=>(await Ne(),e.json({ok:!0}))),s.get("/api/quick-commands",o(),e=>e.json(Y())),s.post("/api/quick-commands",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await We(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Ge(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",o(),e=>$e(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(k)return e.json({error:"hub only"},403);const t=C(e),r=O.check(t);if(!r.allowed)return j("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!tt(t))return j("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(u.settingUpPassword)return e.json({error:"Password setup in progress"},409);const l=xe(i);if(l)return e.json({error:l},400);u.settingUpPassword=!0;try{h.passwordHash=await Ae(i),Re(h),j("password_set",{ip:t})}finally{u.settingUpPassword=!1}const{plaintext:d}=y.createAccessToken("setup",h.security.tokenTtlDays);return H(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await be(i,h.passwordHash)){const l=O.recordFailure(t);return j("auth_failed",{ip:t,method:"password",failures:l.failures,permanentLock:l.permanentLock}),l.permanentLock?(y.revokeAll(),j("permanent_lock",{ip:t,failures:l.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):l.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:l.retryAfterMs},429)}O.recordSuccess(t);const c=`browser-${t}`,{plaintext:v}=y.createAccessToken(c,h.security.tokenTtlDays);return H(e,v),j("auth_success",{ip:t,method:"password",tokenName:c}),e.json({ok:!0,token:v})}),s.post("/api/auth/token",o(),async e=>{if(k)return e.json({error:"hub only"},403);const t=C(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:h.security.tokenTtlDays,{stored:a,plaintext:c}=y.createAccessToken(n,i);return j("token_created",{ip:t,name:a.name,tokenId:a.id}),e.json({id:a.id,name:a.name,token:c,expiresAt:a.expiresAt})}),s.get("/api/auth/tokens",o(),e=>k?e.json({error:"hub only"},403):e.json(y.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",o(),e=>k?e.json({error:"hub only"},403):y.revoke(e.req.param("id"))?(j("token_revoked",{ip:C(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",o(),e=>(J(e),e.json({ok:!0}))),s.get("/settings",g(),async e=>{const t=await Q();return e.html(ge({settings:t,theme:u.activeTheme,saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",o(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await Q(),n=t.defaultView==="recent"?"recent":"default";return await Pe({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",g(),e=>e.html(ye({theme:u.activeTheme,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",o(),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"||!X(r)?e.redirect("/settings/theme",303):(u.activeTheme=await K(r),e.redirect("/settings/theme?saved=1",303))});const se={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",o(),e=>e.json({active:u.activeTheme.template,templates:Le.map(t=>({id:t,name:se[t]}))})),s.post("/api/theme",o(),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"||!X(r)?e.json({error:"invalid theme template"},400):(u.activeTheme=await K(r),e.json({ok:!0,active:u.activeTheme.template}))}),s.get("/api/system/status",o(),e=>e.json(ze())),s.get("/api/system/processes",o(),e=>e.json(Be())),w&&(s.get("/settings/federation",g(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",g(),async e=>e.html(qe({config:w.getConfig(),status:w.status(),tokens:z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:w.hostname},u.activeTheme))),s.get("/api/machines",o(),e=>e.json({config:w.getConfig(),status:w.status()})),s.post("/api/machines",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.hub=="string"?t.hub.trim():"",n=typeof t.token=="string"?t.token.trim():"",i=typeof t.name=="string"?t.name.trim().slice(0,64):"",a=t.enabled===!0;if(a){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const c={hub:r||void 0,token:n||void 0,name:i||void 0,enabled:a};return await w.setConfig(c),e.json({ok:!0,status:w.status()})}),s.get("/api/agent-tokens",o(),e=>e.json(z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})))),s.post("/api/agent-tokens",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim().slice(0,64):"";if(!r)return e.json({error:"name is required"},400);const{id:n,token:i}=Se(r);return e.json({ok:!0,id:n,name:r,token:i})}),s.delete("/api/agent-tokens/:id",o(),e=>{const t=e.req.param("id");return Te(t)?e.json({ok:!0}):e.json({error:"no such token"},404)})),s.post("/api/system/kill",o(),async e=>{const{pid:t}=await e.req.json();if(typeof t!="number"||!Number.isInteger(t)||t<=0)return e.json({error:"invalid pid"},400);const r=Ve(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",o(),e=>q?e.json(q.agents.list()):e.json([])),s.post("/api/sessions/new",o(),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(L().some(a=>a.name===r))return e.json({error:"session already exists"},409);try{return Qe(r,n),e.json({ok:!0})}catch(a){const c=a instanceof Ye?a.message:"failed to create session";return e.json({error:c},500)}}),s.post("/api/sessions/rename",o(),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)?(Ke(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",o(),async e=>{try{const{name:t}=await e.req.json();return t?(Xe(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",o(),e=>{const t=e.req.query("session");if(!t)return e.json({
|
|
16
|
+
`;s.get("/sw.js",e=>e.body(ne,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!h.passwordHash,n=e.req.query("error");return e.html(fe({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:u.activeTheme}))}),s.get("/",g(),e=>{const t=B();return e.html(ve({theme:u.activeTheme,fsRoots:t,terminalCfg:E,scrollback:E.initialLines+2*E.historyChunk,wsBase:k&&F?.()?`/ws/a/${F()}`:void 0}))}),s.get("/notes",g(),e=>e.html(pe(S.data.notes,u.activeTheme))),s.get("/notes/:session",g(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(he(t,u.activeTheme))}),s.get("/history",g(),e=>{const t=L(),r=new Set(t.map(n=>n.name));return e.html(je(Ie(),u.activeTheme,r))}),s.get("/quick-commands",g(),e=>e.html(we(Y(),u.activeTheme))),s.get("/files",g(),e=>{const t=B();return e.html(ke(u.activeTheme,t))}),s.post("/api/history/clear",o(),async e=>(await Ne(),e.json({ok:!0}))),s.get("/api/quick-commands",o(),e=>e.json(Y())),s.post("/api/quick-commands",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await We(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Ge(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",o(),e=>$e(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(k)return e.json({error:"hub only"},403);const t=C(e),r=O.check(t);if(!r.allowed)return j("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!h.passwordHash){if(!h.security.allowRemoteSetup&&!tt(t))return j("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(u.settingUpPassword)return e.json({error:"Password setup in progress"},409);const l=xe(i);if(l)return e.json({error:l},400);u.settingUpPassword=!0;try{h.passwordHash=await Ae(i),Re(h),j("password_set",{ip:t})}finally{u.settingUpPassword=!1}const{plaintext:d}=y.createAccessToken("setup",h.security.tokenTtlDays);return H(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await be(i,h.passwordHash)){const l=O.recordFailure(t);return j("auth_failed",{ip:t,method:"password",failures:l.failures,permanentLock:l.permanentLock}),l.permanentLock?(y.revokeAll(),j("permanent_lock",{ip:t,failures:l.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):l.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:l.retryAfterMs},429)}O.recordSuccess(t);const c=`browser-${t}`,{plaintext:v}=y.createAccessToken(c,h.security.tokenTtlDays);return H(e,v),j("auth_success",{ip:t,method:"password",tokenName:c}),e.json({ok:!0,token:v})}),s.post("/api/auth/token",o(),async e=>{if(k)return e.json({error:"hub only"},403);const t=C(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:h.security.tokenTtlDays,{stored:a,plaintext:c}=y.createAccessToken(n,i);return j("token_created",{ip:t,name:a.name,tokenId:a.id}),e.json({id:a.id,name:a.name,token:c,expiresAt:a.expiresAt})}),s.get("/api/auth/tokens",o(),e=>k?e.json({error:"hub only"},403):e.json(y.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",o(),e=>k?e.json({error:"hub only"},403):y.revoke(e.req.param("id"))?(j("token_revoked",{ip:C(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",o(),e=>(J(e),e.json({ok:!0}))),s.get("/settings",g(),async e=>{const t=await Q();return e.html(ge({settings:t,theme:u.activeTheme,saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",o(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await Q(),n=t.defaultView==="recent"?"recent":"default";return await Pe({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",g(),e=>e.html(ye({theme:u.activeTheme,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",o(),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"||!X(r)?e.redirect("/settings/theme",303):(u.activeTheme=await K(r),e.redirect("/settings/theme?saved=1",303))});const se={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",o(),e=>e.json({active:u.activeTheme.template,templates:Le.map(t=>({id:t,name:se[t]}))})),s.post("/api/theme",o(),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"||!X(r)?e.json({error:"invalid theme template"},400):(u.activeTheme=await K(r),e.json({ok:!0,active:u.activeTheme.template}))}),s.get("/api/system/status",o(),e=>e.json(ze())),s.get("/api/system/processes",o(),e=>e.json(Be())),w&&(s.get("/settings/federation",g(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",g(),async e=>e.html(qe({config:w.getConfig(),status:w.status(),tokens:z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:w.hostname},u.activeTheme))),s.get("/api/machines",o(),e=>e.json({config:w.getConfig(),status:w.status()})),s.post("/api/machines",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.hub=="string"?t.hub.trim():"",n=typeof t.token=="string"?t.token.trim():"",i=typeof t.name=="string"?t.name.trim().slice(0,64):"",a=t.enabled===!0;if(a){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const c={hub:r||void 0,token:n||void 0,name:i||void 0,enabled:a};return await w.setConfig(c),e.json({ok:!0,status:w.status()})}),s.get("/api/agent-tokens",o(),e=>e.json(z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})))),s.post("/api/agent-tokens",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim().slice(0,64):"";if(!r)return e.json({error:"name is required"},400);const{id:n,token:i}=Se(r);return e.json({ok:!0,id:n,name:r,token:i})}),s.delete("/api/agent-tokens/:id",o(),e=>{const t=e.req.param("id");return Te(t)?e.json({ok:!0}):e.json({error:"no such token"},404)})),s.post("/api/system/kill",o(),async e=>{const{pid:t}=await e.req.json();if(typeof t!="number"||!Number.isInteger(t)||t<=0)return e.json({error:"invalid pid"},400);const r=Ve(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",o(),e=>q?e.json(q.agents.list()):e.json([])),s.post("/api/sessions/new",o(),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(L().some(a=>a.name===r))return e.json({error:"session already exists"},409);try{return Qe(r,n),e.json({ok:!0})}catch(a){const c=a instanceof Ye?a.message:"failed to create session";return e.json({error:c},500)}}),s.post("/api/sessions/rename",o(),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)?(Ke(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",o(),async e=>{try{const{name:t}=await e.req.json();return t?(Xe(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",o(),e=>{const t=e.req.query("session");if(!t)return e.json({path:process.env.HOME??"/"});try{const r=Oe(t),i=r.find(a=>a.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:i})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",o(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=p.join(t,r));const n=e.req.query("recursive")==="true";let i=r,a="",c=r.endsWith("/");if(!c)try{c=A(r).isDirectory()}catch{}c||(i=p.dirname(r),a=p.basename(r).toLowerCase());try{const v=ie(i),l=[],d=[];for(const m of v)if(!m.startsWith(".")&&!(a&&!m.toLowerCase().startsWith(a))){try{const f=p.join(i,m);A(f).isDirectory()?(l.push(f),n&&Ce(f,l,d,0)):d.push(f)}catch{}if(l.length+d.length>=5e3)break}return e.json({dirs:l,files:d})}catch{return e.json({dirs:[],files:[]})}});function oe(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=_("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=_("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=_("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let i="";try{i=_("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const a=[];for(const d of i.split(`
|
|
17
17
|
`)){if(!d.trim())continue;const m=d[0],f=d[1],T=d.substring(3).trim(),R=f!==" "?f:m;a.push({path:T,status:R,staged:m,unstaged:f,additions:0,deletions:0})}const c=new Map;try{const d=_("git diff --numstat HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3});for(const m of d.split(`
|
|
18
18
|
`)){if(!m.trim())continue;const f=m.split(" ");if(f.length>=3){const T=parseInt(f[0],10)||0,R=parseInt(f[1],10)||0;c.set(f[2],{added:T,deleted:R})}}}catch{}for(const d of a){const m=c.get(d.path);m&&(d.additions=m.added,d.deletions=m.deleted)}let v=0,l=0;for(const d of a)v+=d.additions,l+=d.deletions;for(const d of a)if(d.status==="?"&&d.additions===0)try{const m=p.join(r,d.path),T=$(m,"utf-8").split(`
|
|
19
|
-
`).length;d.additions=T,v+=T}catch{}return{repoRoot:r,branch:n,files:a,linesAdded:v,linesRemoved:l}}catch{return t}}return s.get("/api/git/status",o(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=b(t),n=oe(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",o(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=b(t);if(!P(n)||!A(n).isDirectory())return e.json({error:"invalid repository path: "+n},400);try{const i=G("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),a=G("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:i,stagedDiff:a})}catch(i){throw console.error("[git/diff] execFileSync failed:",i?.code,i.message),i}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",o(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=b(t);if(!A(r).isFile())return e.json({error:"not a file"},400);const n=A(r).size;if(n>V)return e.json({error:"file too large",size:n,maxBytes:V},413);const i=$(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",o(),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=b(t.path);return _e(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",o(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=b(t.path);return A(r).isFile()?(ae(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",o(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=b(t.path);return P(r)?e.json({error:"file already exists"},409):(ue(p.dirname(r),{recursive:!0}),de(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}}),s.get("/api/sidebar/sessions",o(),e=>{const t=e.req.query("currentSession");return e.json(N(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",o(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(
|
|
19
|
+
`).length;d.additions=T,v+=T}catch{}return{repoRoot:r,branch:n,files:a,linesAdded:v,linesRemoved:l}}catch{return t}}return s.get("/api/git/status",o(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=b(t),n=oe(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",o(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=b(t);if(!P(n)||!A(n).isDirectory())return e.json({error:"invalid repository path: "+n},400);try{const i=G("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),a=G("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:i,stagedDiff:a})}catch(i){throw console.error("[git/diff] execFileSync failed:",i?.code,i.message),i}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",o(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=b(t);if(!A(r).isFile())return e.json({error:"not a file"},400);const n=A(r).size;if(n>V)return e.json({error:"file too large",size:n,maxBytes:V},413);const i=$(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",o(),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=b(t.path);return _e(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",o(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=b(t.path);return A(r).isFile()?(ae(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",o(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=b(t.path);return P(r)?e.json({error:"file already exists"},409):(ue(p.dirname(r),{recursive:!0}),de(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}}),s.get("/api/sidebar/sessions",o(),e=>{const t=e.req.query("currentSession");return e.json(N(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",o(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(Me(t))}),s.post("/api/pinned-views",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=M(t);return"error"in r?e.json({error:r.error},400):(await De(r.sessionName,r.windowIndex),e.json(N()))}),s.delete("/api/pinned-views",o(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=M(t);return"error"in r?e.json({error:r.error},400):(await Fe(r.sessionName,r.windowIndex),e.json(N()))}),s.get("/api/notes",o(),e=>{const t=[...S.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",o(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=S.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",o(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},i=S.data.notes.findIndex(a=>a.scope===t);return i>=0?S.data.notes[i]=n:S.data.notes.push(n),await S.write(),e.json({ok:!0})}),q&&(s.use("/a/:agentId/*",o()),Ze(s,{getChannel:q.getChannel})),s}export{Rt as buildApp};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./icons.js";const d=[{id:"vscode",name:"VS Code",dot:"#007acc"},{id:"ghostty",name:"Ghostty",dot:"#ff5f00"},{id:"warm-clay",name:"Warm Clay",dot:"#b86b52"},{id:"dark-cove",name:"Dark Cove",dot:"#7aa2f7"}];function n(e="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${e}; outline: none;`}function
|
|
1
|
+
import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./icons.js";const d=[{id:"vscode",name:"VS Code",dot:"#007acc"},{id:"ghostty",name:"Ghostty",dot:"#ff5f00"},{id:"warm-clay",name:"Warm Clay",dot:"#b86b52"},{id:"dark-cove",name:"Dark Cove",dot:"#7aa2f7"}];function n(e="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${e}; outline: none;`}function w(e=""){return`
|
|
2
2
|
@media (prefers-reduced-motion: reduce) {
|
|
3
3
|
*, *::before, *::after {
|
|
4
4
|
animation-duration: 0.01ms !important;
|
|
@@ -7,7 +7,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
7
7
|
scroll-behavior: auto !important;
|
|
8
8
|
}
|
|
9
9
|
${e}
|
|
10
|
-
}`}function
|
|
10
|
+
}`}function v(e="vscode"){const o=d.find(t=>t.id===e)??d[0],a=d.map(t=>{const s=t.id===e;return`<button type="button" class="theme-option${s?" active":""}" data-theme="${t.id}" role="menuitem">
|
|
11
11
|
<span class="theme-dot" style="background:${t.dot}"></span>
|
|
12
12
|
${c(t.name)}
|
|
13
13
|
${s?i("check",'class="theme-check" aria-hidden="true"'):""}
|
|
@@ -47,7 +47,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
47
47
|
document.addEventListener('click', (e) => {
|
|
48
48
|
if (switcher.classList.contains('open') && !switcher.contains(e.target)) close();
|
|
49
49
|
});
|
|
50
|
-
})();`}function
|
|
50
|
+
})();`}function L(e=""){return`
|
|
51
51
|
*, *::before, *::after { box-sizing: border-box; }
|
|
52
52
|
html, body { background: var(--page-bg); color: var(--page-fg); min-height: 100%; font-family: var(--font-sans); margin: 0; padding: 0; }
|
|
53
53
|
|
|
@@ -216,7 +216,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
216
216
|
.header-btn, .theme-switcher-btn { padding: 8px; }
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
${
|
|
219
|
+
${w()}
|
|
220
220
|
${e}`}function R(e){const{title:o="TMUX Sessions",themeTemplate:a="vscode"}=e;return`<script>
|
|
221
221
|
(function() {
|
|
222
222
|
const token = localStorage.getItem('tmux-web-token');
|
|
@@ -240,15 +240,15 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
240
240
|
${i("notes")}
|
|
241
241
|
<span>Notes</span>
|
|
242
242
|
</button>
|
|
243
|
-
${
|
|
243
|
+
${v(a)}
|
|
244
244
|
<a class="header-btn" href="/settings" title="Settings">
|
|
245
245
|
${i("settings")}
|
|
246
246
|
<span>Settings</span>
|
|
247
247
|
</a>
|
|
248
248
|
</div>
|
|
249
249
|
</header>
|
|
250
|
-
<script>${y()}</script>`}function A(e){const{activePage:o,refreshHref:a}=e;function t(l,b,
|
|
251
|
-
<svg viewBox="0 0 24 24" fill="currentColor">${
|
|
250
|
+
<script>${y()}</script>`}function A(e){const{activePage:o,refreshHref:a}=e;function t(l,f,b,x,g=""){return`<a href="${f}" class="${l!==null&&l===o?"sidebar-btn current":"sidebar-btn"}"${g}>
|
|
251
|
+
<svg viewBox="0 0 24 24" fill="currentColor">${b}</svg>
|
|
252
252
|
${x}
|
|
253
253
|
</a>`}const s=r("sessions"),p=r("notes"),h=r("history"),m=r("quick-commands"),u=r("file"),k=r("refresh");return`<aside class="action-sidebar">
|
|
254
254
|
<p class="sidebar-label">Actions</p>
|
|
@@ -482,6 +482,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
482
482
|
let treeRoot = '/';
|
|
483
483
|
let treeExpanded = new Set(); // paths currently expanded
|
|
484
484
|
let treeSelected = ''; // path currently selected (highlighted)
|
|
485
|
+
let treeUserSelected = ''; // path the user actively clicked (closes on second click)
|
|
485
486
|
let treeRows = []; // visible rows [{path, el}] for keyboard nav
|
|
486
487
|
let treeActiveIdx = -1;
|
|
487
488
|
let treeOpen = false;
|
|
@@ -524,11 +525,24 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
524
525
|
e.stopPropagation();
|
|
525
526
|
void treeToggle(path, depth);
|
|
526
527
|
});
|
|
527
|
-
// Clicking the row selects the directory
|
|
528
|
-
|
|
528
|
+
// Clicking the row selects the directory and expands it so the user can
|
|
529
|
+
// keep browsing without the tree closing on them. Clicking the already
|
|
530
|
+
// selected row confirms and closes.
|
|
531
|
+
row.addEventListener('click', async () => {
|
|
529
532
|
dirInput.value = path;
|
|
530
|
-
|
|
531
|
-
|
|
533
|
+
if (treeUserSelected === path) {
|
|
534
|
+
closeTree();
|
|
535
|
+
dirInput.focus();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
treeUserSelected = path;
|
|
539
|
+
treeSelected = path;
|
|
540
|
+
if (!treeExpanded.has(path)) {
|
|
541
|
+
treeExpanded.add(path);
|
|
542
|
+
await treeRender();
|
|
543
|
+
} else {
|
|
544
|
+
treeRefreshActive();
|
|
545
|
+
}
|
|
532
546
|
});
|
|
533
547
|
return row;
|
|
534
548
|
}
|
|
@@ -608,40 +622,48 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
608
622
|
treeOpen = false;
|
|
609
623
|
treeExpanded = new Set();
|
|
610
624
|
treeSelected = '';
|
|
625
|
+
treeUserSelected = '';
|
|
611
626
|
treeRows = [];
|
|
612
627
|
treeActiveIdx = -1;
|
|
613
628
|
}
|
|
614
629
|
|
|
630
|
+
// Resolve the path the tree should open at: the current session's working
|
|
631
|
+
// directory if one is open, otherwise the home directory (both served by
|
|
632
|
+
// /api/fs/session-path \u2014 it returns HOME when no session is given).
|
|
633
|
+
async function treeOpenTarget() {
|
|
634
|
+
const session = (typeof window.__tmuxWebCurrentSession === 'string' && window.__tmuxWebCurrentSession)
|
|
635
|
+
? window.__tmuxWebCurrentSession : '';
|
|
636
|
+
try {
|
|
637
|
+
const url = session
|
|
638
|
+
? selectedBase() + '/api/fs/session-path?session=' + encodeURIComponent(session)
|
|
639
|
+
: selectedBase() + '/api/fs/session-path';
|
|
640
|
+
const res = await fetch(url);
|
|
641
|
+
const data = await res.json();
|
|
642
|
+
if (data && typeof data.path === 'string' && data.path) {
|
|
643
|
+
if (session) dirInput.value = data.path;
|
|
644
|
+
return data.path.startsWith('/') ? data.path : '/' + data.path;
|
|
645
|
+
}
|
|
646
|
+
} catch {}
|
|
647
|
+
return '/'
|
|
648
|
+
}
|
|
649
|
+
|
|
615
650
|
async function openTree() {
|
|
616
651
|
closeDropdown();
|
|
617
|
-
const v = dirInput.value.trim();
|
|
618
|
-
const target = (!v || v === '~') ? '/' : (v.startsWith('/') ? v : '/' + v);
|
|
619
652
|
treeRoot = '/';
|
|
620
653
|
treePanel.classList.add('open');
|
|
621
654
|
treeOpen = true;
|
|
655
|
+
const target = await treeOpenTarget();
|
|
622
656
|
await treeExpandTo(target);
|
|
623
657
|
}
|
|
624
658
|
|
|
625
659
|
// On modal open, expand the tree directly to the currently selected
|
|
626
660
|
// session's working directory (window.__tmuxWebCurrentSession is set by
|
|
627
|
-
// the shell client; falls back to
|
|
661
|
+
// the shell client; falls back to the home directory otherwise).
|
|
628
662
|
async function openTreeForSession() {
|
|
629
|
-
const session = (typeof window.__tmuxWebCurrentSession === 'string' && window.__tmuxWebCurrentSession)
|
|
630
|
-
? window.__tmuxWebCurrentSession : '';
|
|
631
|
-
let target = '/';
|
|
632
|
-
if (session) {
|
|
633
|
-
try {
|
|
634
|
-
const res = await fetch(selectedBase() + '/api/fs/session-path?session=' + encodeURIComponent(session));
|
|
635
|
-
const data = await res.json();
|
|
636
|
-
if (data && typeof data.path === 'string' && data.path) {
|
|
637
|
-
dirInput.value = data.path;
|
|
638
|
-
target = data.path.startsWith('/') ? data.path : '/' + data.path;
|
|
639
|
-
}
|
|
640
|
-
} catch {}
|
|
641
|
-
}
|
|
642
663
|
treeRoot = '/';
|
|
643
664
|
treePanel.classList.add('open');
|
|
644
665
|
treeOpen = true;
|
|
666
|
+
const target = await treeOpenTarget();
|
|
645
667
|
await treeExpandTo(target);
|
|
646
668
|
}
|
|
647
669
|
|
|
@@ -718,4 +740,4 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./ico
|
|
|
718
740
|
if (e.key === 'Enter') submit();
|
|
719
741
|
});
|
|
720
742
|
window.__openNewSessionModal = openModal;
|
|
721
|
-
})();`}export{T as newSessionModalCSS,B as newSessionModalHTML,z as newSessionModalScript,
|
|
743
|
+
})();`}export{T as newSessionModalCSS,B as newSessionModalHTML,z as newSessionModalScript,w as reducedMotion,R as sharedHeader,L as sharedLayoutCSS,A as sharedSidebar,v as themeSwitcherButtonHTML,y as themeSwitcherScript};
|