tmux-weblink 2.2.13 → 2.2.15
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,40 +1,40 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}selectRows(t,n){let s=this.terminal.buffer.active,[l,r]=t<=n?[t,n]:[n,t];this.terminal.selectLines(s.viewportY+l,s.viewportY+r)}selectCells(t,n,s,l){let r=this.terminal.buffer.active,c=Math.min(n,l),
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function
|
|
4
|
-
\x1B[31m`+
|
|
5
|
-
`).then(()=>{
|
|
1
|
+
var yn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Qe(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function me(e){return e.replace(yn,"")}var Fe;function wn(){return Fe||(document.querySelector("link[data-tmux-web-xterm-css]")?Fe=Promise.resolve():(Fe=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)}),Fe))}var bn=class Ut{terminal;fitAddon;container;constructor(t,n,s){this.container=t,this.terminal=n,this.fitAddon=s,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,n,s){let[{Terminal:l},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),wn()]),c=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,smoothScrollDuration:120,theme:s});return new Ut(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=Ln(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,l=n.height,r=this.terminal.element?.querySelector(".xterm-viewport"),c=r?Math.max(0,r.offsetWidth-r.clientWidth):0,o=Math.max(2,Math.floor((t.width-c)/s)),u=Math.max(2,Math.floor(t.height/l));return(o!==this.terminal.cols||u!==this.terminal.rows)&&this.terminal.resize(o,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,[l,r]=t<=n?[t,n]:[n,t],c=s.viewportY,o=[];for(let u=l;u<=r;u++){let h=s.getLine(c+u);o.push(h?h.translateToString(!0):"")}return o.join(`
|
|
2
|
+
`)}selectRows(t,n){let s=this.terminal.buffer.active,[l,r]=t<=n?[t,n]:[n,t];this.terminal.selectLines(s.viewportY+l,s.viewportY+r)}selectCells(t,n,s,l){let r=this.terminal.buffer.active,c=Math.min(n,l),o=Math.max(n,l);if(c===o){let u=Math.min(t,s),h=Math.max(t,s);this.terminal.select(u,r.viewportY+c,h-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+o)}getCellsText(t,n,s,l){let r=this.terminal.buffer.active,c=Math.min(n,l),o=Math.max(n,l),u=n<l?t:n>l?s:Math.min(t,s),h=n<l?s:n>l?t:Math.max(t,s),k=r.viewportY,b=[];for(let M=c;M<=o;M++){let B=r.getLine(k+M);if(!B){b.push("");continue}let Z=M===c?u:0,F=M===o?h:this.terminal.cols-1,N="";for(let se=Z;se<=F;se++){let ie=B.getCell(se);ie&&(N+=ie.getChars())}b.push(N.replace(/\s+$/,""))}return b.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function Ln(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 En(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let s=await bn.create(e,n.scrollback,n.theme),l,r=0,c,o=null,u=null,h=!1,k=/(?:^|\/)(?:vim|nvim|vi|less|more|htop|btop|top|hx|helix|lazygit)$/,b=null;async function M(){try{let i=await fetch("/api/session-program?session="+encodeURIComponent(t));if(i.ok){let a=await i.json();u=typeof a.program=="string"&&a.program?a.program:null,u!==null&&k.test(u)&&(h=!0)}}catch{}}function B(){b&&clearTimeout(b),b=setTimeout(()=>{M()},600)}function Z(i){return u!==null&&/(?:^|\/)(?:vim|nvim|vi)$/.test(u)?i?"":"":i?"\x1B[A":"\x1B[B"}let F=0,N=0,se=.2,ie=10,Wt=.95,Yt=.04;function ke(){F&&(cancelAnimationFrame(F),F=0),N=0}function Ot(i){if(ke(),Math.abs(i)<se)return;N=Math.max(-ie,Math.min(ie,i));let a=()=>{if(_){F=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,w=p>0?Math.round(N*16/p):N>0?1:-1;w===0&&(w=N>0?1:-1);let I=s.viewportY()-w,V=s.baseY(),U=Math.max(0,Math.min(I,V));if(s.scrollToLine(U,!0),N*=Wt,U!==I){F=0;return}if(Math.abs(N)<Yt){F=0;return}F=requestAnimationFrame(a)};F=requestAnimationFrame(a)}let Ke=0,P="connecting",Se=0,ae=!1,Te=[],G="",_=!1,oe=1e3,qt=location.protocol==="https:"?"wss:":"ws:",Kt=n.wsBase||"/ws",Vt=qt+"//"+location.host+Kt+"/"+encodeURIComponent(t),Xt="/api/session/"+encodeURIComponent(t)+"/upload",Jt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Qt(){return Te.join("")+G}async function Zt(i,a=0){let d=i?s.viewportY():0,p=me(Qt());if(s.reset(),!p){i||s.scrollToBottom();return}await s.write(p),i?s.scrollToLine(a+d):s.scrollToBottom()}function q(i){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(i))}function Gt(i){let a;try{a=JSON.parse(i)}catch{P==="live"&&(G+=i,s.write(me(i)));return}if(a.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?q({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="attach_failed"&&typeof a.message=="string"){Me=!0,P="dead",s.reset(),s.write(`\r
|
|
4
|
+
\x1B[31m`+me(a.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{Be();try{l?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),oe=1e3,K(),q({type:"resize",cols:s.cols,rows:s.rows}),S(),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=Qe(me(a.data));Te=[d],G="",Se=typeof a.lines=="number"?a.lines:n.terminal.initialLines,P="live",s.reset(),K(),q({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?(Te.unshift(Qe(a.data)),Se+=a.lines,Zt(!0,a.lines).finally(()=>{ae=!1})):ae=!1;return}if(a.type==="data"&&typeof a.data=="string"){/\x1b\[\?1049h/.test(a.data)&&B(),/\x1b\[\?1049l/.test(a.data)&&(h=!1);let d=Qe(me(a.data));if(P==="connecting"){P="live",G=d,s.write(d);return}P==="live"&&(G+=d,s.write(d))}}let Me=!1;function rt(){if(Me)return;let i=Vt,a=localStorage.getItem("tmux-web-token");a&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),i+=(i.includes("?")?"&":"?")+"cols="+s.cols+"&rows="+s.rows,l=new WebSocket(i),l.onopen=()=>{P="connecting",Se=0,ae=!1,Te=[],G="",s.reset(),oe=1e3},l.onmessage=d=>{typeof d.data=="string"&&Gt(d.data)},l.onclose=()=>{_||(P="connecting",!Me&&setTimeout(()=>{oe=Math.min(oe*2,1e4),!_&&!Me&&rt()},oe))},l.onerror=()=>l?.close()}function le(i){q({type:"input",data:i})}function K(){return s.fit()}function Be(){e.classList.remove("terminal-pending")}function S(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Be()}),c=setTimeout(()=>{K()&&Be()},120)}function en(){S(),setTimeout(S,50),setTimeout(S,150),setTimeout(S,300)}function ct(i){return typeof i=="string"&&i.startsWith("image/")}function dt(i){return i?.files?.length?i.files[0]??null:null}function tn(i){if(!i?.items)return null;for(let a=0;a<i.items.length;a++){let d=i.items[a];if(d?.kind==="file"&&ct(d.type))return d.getAsFile()}return null}async function nn(i){let a=new FormData,d=i instanceof File&&i.name?i.name:"upload";a.append("file",i,d);let p=localStorage.getItem("tmux-web-token"),w={};p&&(w.Authorization="Bearer "+p);let I=await fetch(Xt,{method:"POST",body:a,headers:w});if(!I.ok){let U="upload failed";try{let He=await I.json();He.error&&(U=He.error)}catch{}throw new Error(U)}let V=await I.json();if(!V.path)throw new Error("no path in response");return V.path}async function Ve(i){try{let a=await nn(i);le(a)}catch{}}s.onData(i=>le(i)),s.onResize(({cols:i,rows:a})=>q({type:"resize",cols:i,rows:a})),s.onScroll(()=>{P!=="live"||ae||!s.isNearScrollbackTop()||(ae=!0,q({type:"load_history",before:Se}))});let re=!1,ce=!1,L=null,E=null,de=document.createElement("div");de.className="terminal-copy-banner",de.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',de.hidden=!0,e.appendChild(de);let z=document.createElement("div");z.className="terminal-copy-banner terminal-copy-toast",z.textContent="Copied",z.hidden=!0,e.appendChild(z);function ut(i,a){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),w=p.height>0&&s.rows>0?p.height/s.rows:18,I=p.width>0&&s.cols>0?p.width/s.cols:9;return{row:Math.max(0,Math.min(s.rows-1,Math.floor((a-p.top)/w))),col:Math.max(0,Math.min(s.cols-1,Math.floor((i-p.left)/I)))}}async function Ie(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 ue;function De(){z.hidden=!1,ue&&clearTimeout(ue),ue=setTimeout(()=>{z.hidden=!0},1e3)}function ee(i){re=i,de.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&s.focus()}function pt(){ee(!re)}function sn(i){_||i.button!==0||ce||(re||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),ce=!0,L=ut(i.clientX,i.clientY),E=L,s.selectCells(L.col,L.row,E.col,E.row),document.addEventListener("mousemove",Xe,!0),document.addEventListener("mouseup",Je,!0))}function Xe(i){_||!ce||!L||(i.preventDefault(),i.stopPropagation(),E=ut(i.clientX,i.clientY),s.selectCells(L.col,L.row,E.col,E.row))}function Je(i){if(_||!ce||!L||!E)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",Xe,!0),document.removeEventListener("mouseup",Je,!0),ce=!1;let a=s.getCellsText(L.col,L.row,E.col,E.row);L=null,E=null,s.clearSelection(),ee(!1),a&&(z.textContent="\u5DF2\u590D\u5236",Ie(a).then(De)),s.focus()}e.addEventListener("mousedown",sn,!0),document.addEventListener("keydown",an,!0);function an(i){if(!(_||!s.isFocused()||i.defaultPrevented||i.isComposing)){if(re){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),L=null,E=null,s.clearSelection(),ee(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Ie(s.getScreenText()).then(De),ee(!1);return}}if(i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey){if(i.preventDefault(),i.stopPropagation(),L&&E){let a=s.getCellsText(L.col,L.row,E.col,E.row);L=null,E=null,s.clearSelection(),ee(!1),a&&(z.textContent="Copied",Ie(a).then(De))}else{let a=s.getSelection();a?(s.clearSelection(),z.textContent="Copied",Ie(a).then(De)):pt()}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 p of d.types)if(ct(p)){let w=await d.getType(p);await Ve(w);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&s.pasteText(mt(a))}catch{}})();return}}}let Ae=0;function mt(i){return i.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
9
|
-
<span>${
|
|
10
|
-
<span>${
|
|
11
|
-
<button class="session-edit-btn" data-session="${
|
|
12
|
-
`,t.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||
|
|
13
|
-
<span>${
|
|
14
|
-
<button class="session-edit-btn" data-session="${
|
|
8
|
+
`).join("\r")}async function on(i){let a=tn(i.clipboardData);if(a){i.preventDefault(),i.stopPropagation();let w=Date.now();if(w-Ae<50)return;Ae=w,await Ve(a);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let p=Date.now();p-Ae<50||(Ae=p,s.pasteText(mt(d)))}let pe=0;function ln(i){i.preventDefault(),pe++,dt(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function rn(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function cn(i){i.preventDefault(),pe--,pe<=0&&(pe=0,e.classList.remove("terminal-drag-over"))}async function dn(i){i.preventDefault(),pe=0,e.classList.remove("terminal-drag-over");let a=dt(i.dataTransfer);a&&await Ve(a)}function ft(i){!Jt||i.key!=="Escape"||re||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),le("\x1B"))}e.addEventListener("paste",on),e.addEventListener("dragenter",ln),e.addEventListener("dragover",rn),e.addEventListener("dragleave",cn),e.addEventListener("drop",dn),document.addEventListener("keydown",ft,!0);function un(i){if(i.touches.length!==1){o=null;return}let a=i.touches[0];if(!a)return;ke();let d=a.clientX-e.getBoundingClientRect().left<e.clientWidth*.4?"tui":"history";o={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:d,tuiAccum:0},i.stopPropagation()}function pn(i){if(!o||i.touches.length!==1)return;let a=i.touches[0];if(!a)return;let d=a.clientY-o.startY,p=a.clientX-o.startX;if(!o.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;o.scrolling=!0,Ke=Date.now()+500}i.preventDefault(),i.stopPropagation();let w=performance.now(),I=a.clientY-o.lastY;if(o.zone==="tui"&&h){o.tuiAccum+=I;let U=e.querySelector(".xterm-rows > div"),He=U?U.getBoundingClientRect().height:16,gt=Math.max(16,He);for(;Math.abs(o.tuiAccum)>=gt;)le(Z(o.tuiAccum>0)),o.tuiAccum-=Math.sign(o.tuiAccum)*gt;o.lastX=a.clientX,o.lastY=a.clientY,o.lastT=w;return}let V=w-o.lastT;V>0&&(o.velocity=I/V),vn(-I),o.lastX=a.clientX,o.lastY=a.clientY,o.lastT=w}function mn(i){if(!o)return;let a=o.scrolling,d=o.zone,p=o.velocity;if(o=null,i.stopPropagation(),!a){s.focus();return}Ke=Date.now()+500,i.preventDefault(),d!=="tui"&&p&&Ot(p)}function fn(i){o=null,ke(),i.stopPropagation()}function hn(i){i.pointerType==="touch"&&Date.now()<Ke&&(i.preventDefault(),i.stopPropagation())}function vn(i){let a=e.querySelector(".xterm-rows > div"),d=a?a.getBoundingClientRect().height:16,p=d>0?Math.round(i/d):i>0?1:-1;p===0&&(p=i>0?1:-1),s.scrollLines(p)}e.addEventListener("touchstart",un,{passive:!0,capture:!0}),e.addEventListener("touchmove",pn,{passive:!1,capture:!0}),e.addEventListener("touchend",mn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",fn,{passive:!0,capture:!0}),e.addEventListener("pointerup",hn,!0);function ht(){S()}window.addEventListener("resize",ht),window.visualViewport?.addEventListener("resize",S),window.visualViewport?.addEventListener("scroll",S);let vt=new ResizeObserver(()=>S());vt.observe(e),document.fonts?.ready.then(()=>{S(),setTimeout(S,50),setTimeout(S,200)}),e.addEventListener("focusin",en,!0);async function gn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{K()&&Be(),(async()=>(await gn(),K()&&q({type:"resize",cols:s.cols,rows:s.rows}),rt(),S()))()}),{destroy(){if(_=!0,ke(),ee(!1),ue&&clearTimeout(ue),document.removeEventListener("mousemove",Xe,!0),document.removeEventListener("mouseup",Je,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",ht),vt.disconnect(),document.removeEventListener("keydown",ft,!0)},focus(){s.focus()},sendInput(i){le(i)},toggleCopyMode(){pt()}}})()}var fe=window.__TMUX_WEB_SHELL__,v=null,g=null,he=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,T=he;window.__tmuxWebScopeAgent=he;var Ze="sessions",W="",Ne="",Re="",yt="";function x(e){return T?"/a/"+encodeURIComponent(T)+e:e}var m=document.getElementById("sidebar-content"),X=document.getElementById("main-placeholder"),D=document.getElementById("terminal-container"),ve=document.getElementById("file-editor"),xn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ge=document.getElementById("fe-content"),J=document.getElementById("fe-save"),Cn=document.getElementById("fe-delete"),kn=document.getElementById("fe-back"),wt=document.getElementById("fe-new-name"),Sn=document.getElementById("fe-new-btn"),Ge=document.getElementById("settings-backdrop"),bt=document.getElementById("settings-popover"),Lt=document.querySelector(".sidebar");function Tn(){return window.matchMedia("(max-width: 640px)").matches}function et(){Tn()&&Lt.classList.add("collapsed")}function Et(){Lt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Et(),xt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Et(),xt("files")});function xt(e){Ze=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"?(tt="",Y()):_n()}var tt="";async function Y(){if(Ze!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),n=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(n===tt)return;tt=n,An(t)}catch{m.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")}),lt()}var Ct="tmux-web-sidebar-group-collapsed";function kt(){try{return new Set(JSON.parse(localStorage.getItem(Ct)||"[]"))}catch{return new Set}}function Mn(e){try{localStorage.setItem(Ct,JSON.stringify([...e]))}catch{}}function Bn(e,t,n){if(!e)return"\u2026";let s=n&&(e===n||e.startsWith(n+"/"))?"~"+e.slice(n.length):e,l=s.split("/").filter(Boolean);if(l.length===0)return e;for(let r=1;r<=l.length;r++){let c=l.slice(-r).join("/");if(!t.some(o=>!o||o===e?!1:(n&&(o===n||o.startsWith(n+"/"))?"~"+o.slice(n.length):o).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return s}function St(e,t,n,s=!0){let l=new Map;for(let o of e){let u=o.path||"";l.has(u)||l.set(u,[]),l.get(u).push(o)}let r=[...l.keys()],c=kt();for(let[o,u]of l){let h=Bn(o,r,t),k=c.has(o),b=document.createElement("div");b.className="session-group-header"+(k?" collapsed":""),b.title=o||"No working directory",b.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${f(h)}</span>`,b.addEventListener("click",()=>{let B=kt(),Z=b.nextElementSibling;B.has(o)?(B.delete(o),b.classList.remove("collapsed"),Z?.classList.remove("collapsed")):(B.add(o),b.classList.add("collapsed"),Z?.classList.add("collapsed")),Mn(B)});let M=document.createElement("div");M.className="session-group-body"+(k?" collapsed":"");for(let B of u)M.appendChild(n?In(B,n,s):Dn(B));m.appendChild(b),m.appendChild(M)}}function In(e,t,n){let s=document.createElement("div");return s.className="session-item"+(e.name===g&&T===t?" active":"")+(n?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",t),s.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
|
+
<span>${f(e.name)}</span>${n?"":'<span class="meta">offline</span>'}`,n&&s.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||ye(e.name,t)}),s}function Dn(e){let t=document.createElement("div");t.className="session-item"+(e.name===g&&!T?" 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>
|
|
10
|
+
<span>${f(e.name)}</span>
|
|
11
|
+
<button class="session-edit-btn" data-session="${f(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>
|
|
12
|
+
`,t.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||ye(e.name)});let n=t.querySelector(".session-edit-btn");return n&&n.addEventListener("click",s=>{s.stopPropagation(),$t(e.name,n)}),t}function An(e){m.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"+(he?" clickable":""),n.textContent=he?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",he&&(n.title="Back to hub",n.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(n),St(t,e.home);let s=e.agents||[];for(let r of s){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===T,o=document.createElement("div");o.className="sidebar-section-label machine-label"+(r.online?"":" agent-offline-label")+(!c&&r.online?" clickable":""),o.textContent=r.agentName+(r.online?"":" (offline)"),!c&&r.online&&(o.title="\u6253\u5F00 "+r.agentName,o.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(r.agentId)+"/"})),m.appendChild(o),St(r.sessions,e.home,r.agentId,r.online)}let l=e.tombstones||[];if(l.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",m.appendChild(r);for(let c of l){let o=document.createElement("div");o.className="session-item session-tombstone",o.setAttribute("data-session",c.name),c.agentId&&o.setAttribute("data-agent",c.agentId);let u=c.agentId?c.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
|
|
13
|
+
<span>${f(c.name)}</span><span class="meta">\u5931\u6548${u}</span>
|
|
14
|
+
<button class="session-edit-btn" data-session="${f(c.name)}" title="\u6062\u590D\u4F1A\u8BDD"><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>`;let h=o.querySelector(".session-edit-btn");h&&h.addEventListener("click",k=>{k.stopPropagation(),$t(c.name,h,{tombstone:!0,agentId:c.agentId})}),m.appendChild(o)}}}function f(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function ye(e,t){if(!(g===e&&v&&T===(t||null))){ve.style.display="none",X.style.display="none",D.style.display="",D.classList.add("terminal-pending"),et(),v&&(Ye(),v.destroy(),v=null),g=e,T=t||null,window.__tmuxWebScopeAgent=T,window.__tmuxWebCurrentSession=g,Gn(e,t);for(let n of m.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===e&&(n.getAttribute("data-agent")||null)===T);try{v=await En(D,e,{terminal:fe.terminal,scrollback:fe.scrollback,theme:fe.theme,wsBase:T?"/ws/a/"+encodeURIComponent(T):fe.wsBase||"/ws"}),Rt()}catch(n){console.error("[shell] terminal init failed:",n);let s=n&&typeof n=="object"&&"message"in n?n.message:String(n);D.textContent="Failed to open terminal for "+e+": "+s,D.classList.remove("terminal-pending")}}}var $e=[],y=null,Tt=!1;function Hn(e){if(!y||!y.files.length)return null;let t=y.repoRoot;if(!t||!e.startsWith(t))return null;let n=e.slice(t.length+1);for(let s of y.files)if(s.path===n)return s.status;return null}async function Fn(e){Tt=!0;try{let t=await fetch(x("/api/git/status?path="+encodeURIComponent(e)));t.ok?y=await t.json():y=null}catch{y=null}finally{Tt=!1}}function Nn(){if(!y||!y.branch)return"";let{branch:e,linesAdded:t,linesRemoved:n}=y,s=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
15
15
|
<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>
|
|
16
|
-
${
|
|
16
|
+
${f(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&te&&y&&(e.stopPropagation(),At())});var we=document.getElementById("header-git"),Mt=document.getElementById("git-diff-view"),Bt=document.getElementById("gd-path"),Pe=document.getElementById("gd-status"),ze=document.getElementById("gd-content"),Rn=document.getElementById("gd-back"),te=null;function be(e){if(!e||!e.branch){we.style.display="none",te=null;return}te=e.repoRoot;let{branch:t,linesAdded:n,linesRemoved:s}=e,l=`<span class="branch">${f(t)}</span>`;(n>0||s>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),we.innerHTML=l,we.style.display="block"}we.addEventListener("click",e=>{te&&y&&(e.stopPropagation(),At())});var C=document.getElementById("git-popover"),je=document.getElementById("git-popover-backdrop");function nt(){let e=we.getBoundingClientRect();C.style.top=e.bottom+4+"px",C.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function It(){let e=te,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Ue(),e){Ze="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),O(e)}})}var Dt="";function At(){if(!y)return;let e=y.files||[];if(!e.length)C.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
17
17
|
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){let s=e[n],l=s.additions||0,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}">
|
|
18
|
-
${c}<span class="file-path">${
|
|
18
|
+
${c}<span class="file-path">${f(s.path)}</span>
|
|
19
19
|
<span class="file-add">${l>0?"+"+l:""}</span>
|
|
20
20
|
<span class="file-del">${r>0?"-"+r:""}</span>
|
|
21
|
-
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',
|
|
21
|
+
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Dt=t,C.innerHTML=t,C.querySelectorAll(".git-popover-item").forEach(n=>{let s=parseInt(n.dataset.idx||"0",10),l=e[s];!l||l.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>Ft(l.path)))})}nt(),C.classList.add("open"),je.classList.add("open"),It()}function $n(){return window.innerWidth>=1024}function Ht(e,t){let n=(e+(t&&e?`
|
|
22
22
|
`:"")+t).split(`
|
|
23
|
-
`),s="",l=0,r=0;for(let c of n){let
|
|
23
|
+
`),s="",l=0,r=0;for(let c of n){let o=c;if(o.startsWith("@@")){let u=o.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(l=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),s+=`<div class="diff-line hunk"><span class="ln-body">${f(o)}</span></div>`;continue}if(o.startsWith("diff --git")||o.startsWith("index ")||o.startsWith("--- ")||o.startsWith("+++ ")||o.startsWith("new file")||o.startsWith("deleted file")){o.startsWith("diff --git")&&(s+=`<div class="diff-line header"><span class="ln-body">${f(o)}</span></div>`);continue}if(o.startsWith("+"))r++,s+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${f(o)}</span></div>`;else if(o.startsWith("-"))l++,s+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${f(o)}</span></div>`;else if(o.startsWith("\\"))s+=`<div class="diff-line"><span class="ln-body">${f(o)}</span></div>`;else{if(!o&&!l&&!r)continue;l++,r++,s+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${f(o)}</span></div>`}}return{html:s,lineCount:n.length}}var _e=null;async function Ft(e){let t=te;if(t)if(Ue(),$n()){D.style.display="none",v&&(Ye(),v.destroy(),v=null,_e=g,g=null),be(null),ve.style.display="none",X.style.display="none",Mt.style.display="flex",Bt.textContent=e,Pe.textContent="Loading\u2026",ze.innerHTML="";try{let n=await fetch(x("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();zn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);Pe.textContent="Error",ze.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${f(s)}</div>`}}else{C.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
24
24
|
<button class="diff-back">\u2190 Back</button>
|
|
25
|
-
<span class="diff-filename">${
|
|
25
|
+
<span class="diff-filename">${f(e)}</span>
|
|
26
26
|
<span class="diff-loading">Loading\u2026</span>
|
|
27
|
-
</div></div>`,
|
|
27
|
+
</div></div>`,nt(),C.classList.add("open"),je.classList.add("open");try{let n=await fetch(x("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();Pn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);C.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
28
28
|
<button class="diff-back">\u2190 Back</button>
|
|
29
|
-
<span class="diff-filename">${
|
|
30
|
-
</div><div class="diff-empty">Error loading diff: ${
|
|
29
|
+
<span class="diff-filename">${f(e)}</span>
|
|
30
|
+
</div><div class="diff-empty">Error loading diff: ${f(s)}</div></div>`}}}function Pn(e,t,n){if(!t&&!n){C.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
31
31
|
<button class="diff-back">\u2190 Back</button>
|
|
32
|
-
<span class="diff-filename">${
|
|
33
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:l}=
|
|
32
|
+
<span class="diff-filename">${f(e)}</span>
|
|
33
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:l}=Ht(t,n);C.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
34
34
|
<button class="diff-back">\u2190 Back</button>
|
|
35
|
-
<span class="diff-filename">${
|
|
35
|
+
<span class="diff-filename">${f(e)}</span>
|
|
36
36
|
<span class="diff-filesize">${l} lines</span>
|
|
37
|
-
</div><div class="diff-content">${s}</div></div>`,C.querySelector(".diff-back")?.addEventListener("click",()=>{C.innerHTML=
|
|
37
|
+
</div><div class="diff-content">${s}</div></div>`,C.querySelector(".diff-back")?.addEventListener("click",()=>{C.innerHTML=Dt,nt(),y&&C.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),o=y.files[c];!o||o.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>Ft(o.path)))}),It()})}function zn(e,t,n){if(!t&&!n){Pe.textContent="No diff",ze.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:s,lineCount:l}=Ht(t,n);Bt.textContent=e,Pe.textContent=l+" lines",ze.innerHTML=s}function jn(){Mt.style.display="none",_e?(D.style.display="",D.classList.add("terminal-pending"),ye(_e),_e=null):g?(D.style.display="",D.classList.add("terminal-pending"),Rt()):X.style.display="flex"}Rn.addEventListener("click",jn);function Ue(){C.classList.remove("open"),je.classList.remove("open")}je.addEventListener("click",Ue),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Ue(),qe())});async function Nt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(n.ok){let s=await n.json();y=s,be(s);return}}}catch{}y=null,be(null)}var We=null;function Rt(){Ye(),g&&(Nt(g),We=setInterval(()=>{g&&Nt(g)},3e4))}function Ye(){We&&(clearInterval(We),We=null)}function _n(){if($e=fe.fsRoots||[],$e.length===0){if(g){Un(g);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let e of $e){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${f(e)}`,t.addEventListener("click",()=>O(e)),m.appendChild(t)}}async function Un(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(x("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?O(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function O(e){W=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Fn(e);try{let n=await(await fetch(x("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",be(y),m.innerHTML+=Nn();let s=Wn(e);if(s&&!$e.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>O(s)),m.appendChild(r)}if(n.dirs)for(let r of n.dirs){let c=r.split("/").pop()||r.split("\\").pop(),o=document.createElement("div");o.className="file-tree-item",o.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${f(c)}`,o.addEventListener("click",()=>O(r)),m.appendChild(o)}else m.innerHTML="";let l=n.files||[];for(let r of l){let c=r.split("/").pop()||r.split("\\").pop(),o=Hn(r),u="",h="";if(o)switch(o){case"M":u="git-mod",h='<span class="git-file-status M">M</span>';break;case"A":u="git-add",h='<span class="git-file-status A">A</span>';break;case"D":u="git-del",h='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",h='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",h='<span class="git-file-status R">R</span>';break}let k=document.createElement("div");k.className="file-tree-item file"+(u?" "+u:""),k.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${h}${f(c)}`,k.addEventListener("click",()=>Yn(r)),m.appendChild(k)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function Wn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Yn(e){Ne=e,D.style.display="none",v&&(Ye(),v.destroy(),v=null,g=null),be(null),et(),X.style.display="none",ve.style.display="flex",xn.textContent=e,H.textContent="Loading...",J.style.display="none";try{let t=await fetch(x("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let s=await t.json();H.textContent=s.error||"Failed to load",ge.value="";return}let n=await t.json();ge.value=n.content,Re=n.content,yt=n.content,H.textContent=n.size+" bytes",J.style.display=""}catch{H.textContent="Failed to load",ge.value=""}}ge.addEventListener("input",()=>{Re=ge.value}),J.addEventListener("click",async()=>{J.textContent="Saving...",J.disabled=!0;try{let e=await fetch(x("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ne,content:Re})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}yt=Re,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{J.disabled=!1,J.textContent="Save"}}),Cn.addEventListener("click",async()=>{if(confirm("Delete "+Ne+"?"))try{let e=await fetch(x("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ne})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}ve.style.display="none",X.style.display="flex",W&&O(W)}catch{H.textContent="Delete failed"}}),kn.addEventListener("click",()=>{ve.style.display="none",X.style.display="flex",W&&O(W)}),Sn.addEventListener("click",async()=>{let e=wt.value.trim();if(!e)return;let t=W+"/"+e;try{let n=await fetch(x("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){let s=await n.json();H.textContent=s.error||"Create failed";return}wt.value="",W&&O(W)}catch{H.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=bt.classList.toggle("open");Ge.classList.toggle("open",e)}),Ge.addEventListener("click",()=>{bt.classList.remove("open"),Ge.classList.remove("open")});var A="",j=!1,Oe,Le=document.createElement("div");Le.className="session-popover-backdrop",document.body.appendChild(Le);var R=document.createElement("div");R.id="session-popover",R.className="session-popover",R.style.display="none",R.innerHTML=`<div class="sp-field">
|
|
38
38
|
<label>Rename session</label>
|
|
39
39
|
<input type="text" id="sp-name" />
|
|
40
40
|
</div>
|
|
@@ -44,5 +44,5 @@ var un=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
|
44
44
|
</div>
|
|
45
45
|
<hr class="sp-divider" />
|
|
46
46
|
<button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
|
|
47
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(R);function
|
|
48
|
-
`).length
|
|
47
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(R);function On(e){let t=e.getBoundingClientRect();R.style.top=t.bottom+4+"px",R.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function $t(e,t,n){A=e,j=!!n?.tombstone,Oe=n?.agentId;let s=document.getElementById("sp-name");s.value=e;let l=R.querySelector(".sp-field label");l.textContent=j?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=j?"":"none";let c=document.getElementById("sp-delete");c.textContent=j?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let o=document.getElementById("sp-save");o.textContent=j?"\u91CD\u547D\u540D":"Save",Le.classList.add("open"),R.style.display="block",On(t),setTimeout(()=>s.focus(),50)}function Q(){Le.classList.remove("open"),R.style.display="none"}Le.addEventListener("click",Q),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===A){Q();return}try{let t=j?{oldName:A,newName:e,agentId:Oe}:{oldName:A,newName:e};if(!(await fetch(x(j?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;Q(),Y(),!j&&g===A&&ye(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+A+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(x("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:A,agentId:Oe})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}Q(),Y()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(j){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+A+'"\uFF1F'))return;try{if(!(await fetch(x("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:A,agentId:Oe})})).ok)return;Q(),Y()}catch{}return}if(confirm('Delete session "'+A+'"?'))try{if(!(await fetch(x("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:A})})).ok)return;Q(),g===A&&(v&&(v.destroy(),v=null),g=null,D.style.display="none",X.style.display="flex"),Y()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",Q);var qn={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=qn[t.dataset.key];n&&(v.sendInput(n),v.focus())});var $=document.getElementById("mk-input"),Pt=document.getElementById("mk-send");function zt(){if(!$||!v)return;let e=$.value;e&&(v.sendInput(e+"\r"),$.value="",$.rows=1,v.focus())}if($&&($.addEventListener("input",()=>{$.rows=1;let e=$.value.split(`
|
|
48
|
+
`).length;$.rows=Math.min(e,4)}),$.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),zt())})),Pt&&Pt.addEventListener("click",zt),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 st=document.getElementById("process-panel"),Ee=document.getElementById("proc-list"),Kn=document.getElementById("proc-close"),it=document.getElementById("proc-backdrop"),Vn=document.getElementById("header-ram");function Xn(){Ee.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(x("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){Ee.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let n of e){let s=n.rss,l=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+f(n.command)+'">'+f(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">×</button></div>'}Ee.innerHTML=t;for(let n of Ee.querySelectorAll(".proc-row")){let s=n.querySelector(".proc-kill");s&&s.addEventListener("click",l=>{let r=l.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(x("/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(()=>{Ee.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Jn(){st.classList.add("open"),it.classList.add("open"),Xn()}function qe(){st.classList.remove("open"),it.classList.remove("open")}Vn.addEventListener("click",e=>{e.stopPropagation(),st.classList.contains("open")?qe():Jn()}),Kn.addEventListener("click",qe),it.addEventListener("click",qe);var Qn=2e3,xe=null,at=null,ne=new Map,Ce=new Map;function jt(e,t){return(t?"a:"+t+":":"l:")+e}function Zn(e,t,n){let s=jt(e,t),l=Ce.get(s),r=e===g&&(t??null)===T,c=n==="idle"&&l?.state==="working";Ce.set(s,{session:e,agentId:t,state:n,doneUntil:c?Date.now()+Qn:void 0,unread:c?!r:!1});let o=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',u=m.querySelector(o);u&&ot(u,s,Ce.get(s))}function ot(e,t,n){let s=ne.get(t);s&&(clearTimeout(s),ne.delete(t)),e.classList.remove("working","just-done");let l=n.session===g&&(n.agentId??null)===T;n.state==="working"?e.classList.add("working"):l&&n.doneUntil&&Date.now()<n.doneUntil?(e.classList.add("just-done"),ne.set(t,setTimeout(()=>{e.classList.remove("just-done"),ne.delete(t)},Math.max(0,n.doneUntil-Date.now())))):!l&&n.unread&&e.classList.add("just-done")}function lt(){for(let[e,t]of Ce){let n=t.agentId?'.session-item[data-agent="'+CSS.escape(t.agentId)+'"][data-session="'+CSS.escape(t.session)+'"]':'.session-item[data-session="'+CSS.escape(t.session)+'"]:not([data-agent])',s=m.querySelector(n);s&&ot(s,e,t)}}function Gn(e,t){let n=jt(e,t),s=Ce.get(n);if(!s)return;let l=ne.get(n);l&&(clearTimeout(l),ne.delete(n)),s.doneUntil=void 0,s.unread=!1;let r=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=m.querySelector(r);c&&ot(c,n,s)}function _t(){if(xe||at)return;let e=localStorage.getItem("tmux-web-token"),t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{let n=new WebSocket(t);xe=n,n.onmessage=s=>{try{let l=JSON.parse(String(s.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Zn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},n.onclose=()=>{xe===n&&(xe=null,at=setTimeout(()=>{at=null,_t()},5e3))},n.onerror=()=>{try{n.close()}catch{}}}catch{xe=null}}window.__openSession=ye,window.__refreshSidebar=Y,Y(),et(),lt(),_t(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&<()}),setInterval(()=>{document.visibilityState==="visible"&&Y()},1e4);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
`)}selectRows(o,r){const n=this.terminal.buffer.active,[l,
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function
|
|
4
|
-
\x1B[31m`+
|
|
5
|
-
`).then(()=>{
|
|
1
|
+
const Ht=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function st(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function U(s){return s.replace(Ht,"")}let N;function Wt(){return N||(document.querySelector("link[data-tmux-web-xterm-css]")?N=Promise.resolve():(N=new Promise(o=>{const r=document.createElement("link");r.rel="stylesheet",r.href="/assets/xterm.css",r.setAttribute("data-tmux-web-xterm-css",""),r.addEventListener("load",()=>o(),{once:!0}),r.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(r)}),N))}class at{terminal;fitAddon;container;constructor(o,r,n){this.container=o,this.terminal=r,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,r,n){const[{Terminal:l},{FitAddon:f}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),Wt()]),d=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:r,convertEol:!1,smoothScrollDuration:120,theme:n});return new at(o,d,new f)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(o){return new Promise(r=>this.terminal.write(o,r))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(o,r=!1){this.terminal.scrollToLine(Math.max(0,o),r)}scrollLines(o){const r=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=Vt(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 l=n.width,f=n.height,d=this.terminal.element?.querySelector(".xterm-viewport"),a=d?Math.max(0,d.offsetWidth-d.clientWidth):0,m=Math.max(2,Math.floor((o.width-a)/l)),g=Math.max(2,Math.floor(o.height/f));return(m!==this.terminal.cols||g!==this.terminal.rows)&&this.terminal.resize(m,g),!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,r){const n=this.terminal.buffer.active,[l,f]=o<=r?[o,r]:[r,o],d=n.viewportY,a=[];for(let m=l;m<=f;m++){const g=n.getLine(d+m);a.push(g?g.translateToString(!0):"")}return a.join(`
|
|
2
|
+
`)}selectRows(o,r){const n=this.terminal.buffer.active,[l,f]=o<=r?[o,r]:[r,o];this.terminal.selectLines(n.viewportY+l,n.viewportY+f)}selectCells(o,r,n,l){const f=this.terminal.buffer.active,d=Math.min(r,l),a=Math.max(r,l);if(d===a){const m=Math.min(o,n),g=Math.max(o,n);this.terminal.select(m,f.viewportY+d,g-m+1)}else this.terminal.selectLines(f.viewportY+d,f.viewportY+a)}getCellsText(o,r,n,l){const f=this.terminal.buffer.active,d=Math.min(r,l),a=Math.max(r,l),m=r<l?o:r>l?n:Math.min(o,n),g=r<l?n:r>l?o:Math.max(o,n),tt=f.viewportY,L=[];for(let M=d;M<=a;M++){const j=f.getLine(tt+M);if(!j){L.push("");continue}const et=M===d?m:0,y=M===a?g:this.terminal.cols-1;let v="";for(let Y=et;Y<=y;Y++){const F=j.getCell(Y);F&&(v+=F.getChars())}L.push(v.replace(/\s+$/,""))}return L.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Vt(s){const o=s.getBoundingClientRect(),r=window.visualViewport;return r?{width:Math.min(o.width,r.width),height:Math.max(0,Math.min(o.height,r.height-Math.max(0,o.top)))}:o}function Jt(s,o,r){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await at.create(s,r.scrollback,r.theme);let l,f=0,d,a=null,m=null,g=!1;const tt=/(?:^|\/)(?:vim|nvim|vi|less|more|htop|btop|top|hx|helix|lazygit)$/;let L=null;async function M(){try{const t=await fetch("/api/session-program?session="+encodeURIComponent(o));if(t.ok){const e=await t.json();m=typeof e.program=="string"&&e.program?e.program:null,m!==null&&tt.test(m)&&(g=!0)}}catch{}}function j(){L&&clearTimeout(L),L=setTimeout(()=>{M()},600)}function et(t){return m!==null&&/(?:^|\/)(?:vim|nvim|vi)$/.test(m)?t?"":"":t?"\x1B[A":"\x1B[B"}let y=0,v=0;const Y=.2,F=10,yt=.95,vt=.04;function H(){y&&(cancelAnimationFrame(y),y=0),v=0}function xt(t){if(H(),Math.abs(t)<Y)return;v=Math.max(-F,Math.min(F,t));const e=()=>{if(S){y=0;return}const i=s.querySelector(".xterm-rows > div"),c=i?i.getBoundingClientRect().height:16;let u=c>0?Math.round(v*16/c):v>0?1:-1;u===0&&(u=v>0?1:-1);const T=n.viewportY()-u,P=n.baseY(),I=Math.max(0,Math.min(T,P));if(n.scrollToLine(I,!0),v*=yt,I!==T){y=0;return}if(Math.abs(v)<vt){y=0;return}y=requestAnimationFrame(e)};y=requestAnimationFrame(e)}let nt=0,b="connecting",W=0,z=!1,V=[],A="",S=!1,B=1e3;const Tt=location.protocol==="https:"?"wss:":"ws:",bt=r.wsBase||"/ws",Et=Tt+"//"+location.host+bt+"/"+encodeURIComponent(o),Mt="/api/session/"+encodeURIComponent(o)+"/upload",St=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Lt(){return V.join("")+A}async function Ct(t,e=0){const i=t?n.viewportY():0,c=U(Lt());if(n.reset(),!c){t||n.scrollToBottom();return}await n.write(c),t?n.scrollToLine(e+i):n.scrollToBottom()}function C(t){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(t))}function Dt(t){let e;try{e=JSON.parse(t)}catch{b==="live"&&(A+=t,n.write(U(t)));return}if(e.type==="auth.required"){const i=localStorage.getItem("tmux-web-token");i?C({type:"auth.token",token:i}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(e.type==="attach_failed"&&typeof e.message=="string"){J=!0,b="dead",n.reset(),n.write(`\r
|
|
4
|
+
\x1B[31m`+U(e.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{$();try{l?.close()}catch{}});return}if(e.type==="auth.ok"){e.token&&localStorage.setItem("tmux-web-token",e.token),B=1e3,D(),C({type:"resize",cols:n.cols,rows:n.rows}),w(),M();return}if(e.type==="auth.failed"){if(e.permanentLock||e.retryAfterMs&&e.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(e.message);return}return}if(e.type==="snapshot"&&typeof e.data=="string"){const i=st(U(e.data));V=[i],A="",W=typeof e.lines=="number"?e.lines:r.terminal.initialLines,b="live",n.reset(),D(),C({type:"resize",cols:n.cols,rows:n.rows}),n.write(i).then(()=>n.scrollToBottom());return}if(e.type==="window_changed"&&typeof e.activeIndex=="number"){const i=new URL(location.href);i.searchParams.get("window")!==String(e.activeIndex)&&(i.searchParams.set("window",String(e.activeIndex)),history.replaceState(history.state,"",i)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:e.windows}));return}if(e.type==="history"&&typeof e.data=="string"){e.lines>0&&e.data?(V.unshift(st(e.data)),W+=e.lines,Ct(!0,e.lines).finally(()=>{z=!1})):z=!1;return}if(e.type==="data"&&typeof e.data=="string"){/\x1b\[\?1049h/.test(e.data)&&j(),/\x1b\[\?1049l/.test(e.data)&&(g=!1);const i=st(U(e.data));if(b==="connecting"){b="live",A=i,n.write(i);return}b==="live"&&(A+=i,n.write(i))}}let J=!1;function lt(){if(J)return;let t=Et;const e=localStorage.getItem("tmux-web-token");e&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(e)),t+=(t.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,l=new WebSocket(t),l.onopen=()=>{b="connecting",W=0,z=!1,V=[],A="",n.reset(),B=1e3},l.onmessage=i=>{typeof i.data=="string"&&Dt(i.data)},l.onclose=()=>{S||(b="connecting",!J&&setTimeout(()=>{B=Math.min(B*2,1e4),!S&&!J&<()},B))},l.onerror=()=>l?.close()}function K(t){C({type:"input",data:t})}function D(){return n.fit()}function $(){s.classList.remove("terminal-pending")}function w(){f&&cancelAnimationFrame(f),d&&clearTimeout(d),D(),f=requestAnimationFrame(()=>{f=0,D()&&$()}),d=setTimeout(()=>{D()&&$()},120)}function Pt(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function ct(t){return typeof t=="string"&&t.startsWith("image/")}function ut(t){return t?.files?.length?t.files[0]??null:null}function It(t){if(!t?.items)return null;for(let e=0;e<t.items.length;e++){const i=t.items[e];if(i?.kind==="file"&&ct(i.type))return i.getAsFile()}return null}async function At(t){const e=new FormData,i=t instanceof File&&t.name?t.name:"upload";e.append("file",t,i);const c=localStorage.getItem("tmux-web-token"),u={};c&&(u.Authorization="Bearer "+c);const x=await fetch(Mt,{method:"POST",body:e,headers:u});if(!x.ok){let P="upload failed";try{const I=await x.json();I.error&&(P=I.error)}catch{}throw new Error(P)}const T=await x.json();if(!T.path)throw new Error("no path in response");return T.path}async function ot(t){try{const e=await At(t);K(e)}catch{}}n.onData(t=>K(t)),n.onResize(({cols:t,rows:e})=>C({type:"resize",cols:t,rows:e})),n.onScroll(()=>{b!=="live"||z||!n.isNearScrollbackTop()||(z=!0,C({type:"load_history",before:W}))});let R=!1,X=!1,h=null,p=null;const O=document.createElement("div");O.className="terminal-copy-banner",O.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',O.hidden=!0,s.appendChild(O);const E=document.createElement("div");E.className="terminal-copy-banner terminal-copy-toast",E.textContent="Copied",E.hidden=!0,s.appendChild(E);function ft(t,e){const i=s.querySelector(".xterm-screen, canvas"),u=(i&&i.getBoundingClientRect().width>0?i:s).getBoundingClientRect(),x=u.height>0&&n.rows>0?u.height/n.rows:18,T=u.width>0&&n.cols>0?u.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((e-u.top)/x))),col:Math.max(0,Math.min(n.cols-1,Math.floor((t-u.left)/T)))}}async function G(t){if(!t)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(t);return}}catch{}const e=document.createElement("textarea");e.value=t,e.style.position="fixed",e.style.opacity="0",e.style.pointerEvents="none",document.body.appendChild(e),e.focus(),e.select();try{document.execCommand("copy")}catch{}e.remove()}let _;function Q(){E.hidden=!1,_&&clearTimeout(_),_=setTimeout(()=>{E.hidden=!0},1e3)}function k(t){R=t,O.hidden=!t,s.classList.toggle("terminal-copy-mode-active",t),t&&n.focus()}function dt(){k(!R)}function kt(t){S||t.button!==0||X||(R||t.shiftKey)&&(t.preventDefault(),t.stopPropagation(),X=!0,h=ft(t.clientX,t.clientY),p=h,n.selectCells(h.col,h.row,p.col,p.row),document.addEventListener("mousemove",it,!0),document.addEventListener("mouseup",rt,!0))}function it(t){S||!X||!h||(t.preventDefault(),t.stopPropagation(),p=ft(t.clientX,t.clientY),n.selectCells(h.col,h.row,p.col,p.row))}function rt(t){if(S||!X||!h||!p)return;t.preventDefault(),t.stopPropagation(),document.removeEventListener("mousemove",it,!0),document.removeEventListener("mouseup",rt,!0),X=!1;const e=n.getCellsText(h.col,h.row,p.col,p.row);h=null,p=null,n.clearSelection(),k(!1),e&&(E.textContent="\u5DF2\u590D\u5236",G(e).then(Q)),n.focus()}s.addEventListener("mousedown",kt,!0),document.addEventListener("keydown",Yt,!0);function Yt(t){if(!(S||!n.isFocused()||t.defaultPrevented||t.isComposing)){if(R){if(t.key==="Escape"){t.preventDefault(),t.stopPropagation(),h=null,p=null,n.clearSelection(),k(!1);return}if(t.key==="Enter"||(t.ctrlKey||t.metaKey)&&t.code==="KeyC"){t.preventDefault(),t.stopPropagation(),G(n.getScreenText()).then(Q),k(!1);return}}if(t.altKey&&t.code==="KeyC"&&!t.ctrlKey&&!t.metaKey){if(t.preventDefault(),t.stopPropagation(),h&&p){const e=n.getCellsText(h.col,h.row,p.col,p.row);h=null,p=null,n.clearSelection(),k(!1),e&&(E.textContent="Copied",G(e).then(Q))}else{const e=n.getSelection();e?(n.clearSelection(),E.textContent="Copied",G(e).then(Q)):dt()}return}if((t.ctrlKey||t.metaKey)&&t.code==="KeyV"){t.preventDefault(),t.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){const e=await navigator.clipboard.read();for(const i of e)for(const c of i.types)if(ct(c)){const u=await i.getType(c);await ot(u);return}}}catch{}try{const e=await navigator.clipboard?.readText();e&&n.pasteText(mt(e))}catch{}})();return}}}let Z=0;function mt(t){return t.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
8
|
+
`).join("\r")}async function Ft(t){const e=It(t.clipboardData);if(e){t.preventDefault(),t.stopPropagation();const u=Date.now();if(u-Z<50)return;Z=u,await ot(e);return}const i=t.clipboardData?.getData("text/plain");if(!i)return;t.preventDefault(),t.stopPropagation();const c=Date.now();c-Z<50||(Z=c,n.pasteText(mt(i)))}let q=0;function zt(t){t.preventDefault(),q++,ut(t.dataTransfer)&&s.classList.add("terminal-drag-over")}function Bt(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function Kt(t){t.preventDefault(),q--,q<=0&&(q=0,s.classList.remove("terminal-drag-over"))}async function Rt(t){t.preventDefault(),q=0,s.classList.remove("terminal-drag-over");const e=ut(t.dataTransfer);e&&await ot(e)}function ht(t){!St||t.key!=="Escape"||R||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||n.isFocused()&&(t.preventDefault(),t.stopPropagation(),K("\x1B"))}s.addEventListener("paste",Ft),s.addEventListener("dragenter",zt),s.addEventListener("dragover",Bt),s.addEventListener("dragleave",Kt),s.addEventListener("drop",Rt),document.addEventListener("keydown",ht,!0);function Xt(t){if(t.touches.length!==1){a=null;return}const e=t.touches[0];if(!e)return;H();const i=e.clientX-s.getBoundingClientRect().left<s.clientWidth*.4?"tui":"history";a={startX:e.clientX,startY:e.clientY,lastX:e.clientX,lastY:e.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:i,tuiAccum:0},t.stopPropagation()}function Ot(t){if(!a||t.touches.length!==1)return;const e=t.touches[0];if(!e)return;const i=e.clientY-a.startY,c=e.clientX-a.startX;if(!a.scrolling){if(Math.abs(i)<8||Math.abs(i)<Math.abs(c))return;a.scrolling=!0,nt=Date.now()+500}t.preventDefault(),t.stopPropagation();const u=performance.now(),x=e.clientY-a.lastY;if(a.zone==="tui"&&g){a.tuiAccum+=x;const P=s.querySelector(".xterm-rows > div"),I=P?P.getBoundingClientRect().height:16,wt=Math.max(16,I);for(;Math.abs(a.tuiAccum)>=wt;)K(et(a.tuiAccum>0)),a.tuiAccum-=Math.sign(a.tuiAccum)*wt;a.lastX=e.clientX,a.lastY=e.clientY,a.lastT=u;return}const T=u-a.lastT;T>0&&(a.velocity=x/T),Nt(-x),a.lastX=e.clientX,a.lastY=e.clientY,a.lastT=u}function _t(t){if(!a)return;const e=a.scrolling,i=a.zone,c=a.velocity;if(a=null,t.stopPropagation(),!e){n.focus();return}nt=Date.now()+500,t.preventDefault(),i!=="tui"&&c&&xt(c)}function qt(t){a=null,H(),t.stopPropagation()}function Ut(t){t.pointerType==="touch"&&Date.now()<nt&&(t.preventDefault(),t.stopPropagation())}function Nt(t){const e=s.querySelector(".xterm-rows > div"),i=e?e.getBoundingClientRect().height:16;let c=i>0?Math.round(t/i):t>0?1:-1;c===0&&(c=t>0?1:-1),n.scrollLines(c)}s.addEventListener("touchstart",Xt,{passive:!0,capture:!0}),s.addEventListener("touchmove",Ot,{passive:!1,capture:!0}),s.addEventListener("touchend",_t,{passive:!1,capture:!0}),s.addEventListener("touchcancel",qt,{passive:!0,capture:!0}),s.addEventListener("pointerup",Ut,!0);function pt(){w()}window.addEventListener("resize",pt),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);const gt=new ResizeObserver(()=>w());gt.observe(s),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),s.addEventListener("focusin",Pt,!0);async function jt(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(t=>setTimeout(t,1e3))])}catch{}}return requestAnimationFrame(()=>{D()&&$(),(async()=>(await jt(),D()&&C({type:"resize",cols:n.cols,rows:n.rows}),lt(),w()))()}),{destroy(){if(S=!0,H(),k(!1),_&&clearTimeout(_),document.removeEventListener("mousemove",it,!0),document.removeEventListener("mouseup",rt,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}s.innerHTML="",s.classList.remove("terminal-pending"),window.removeEventListener("resize",pt),gt.disconnect(),document.removeEventListener("keydown",ht,!0)},focus(){n.focus()},sendInput(t){K(t)},toggleCopyMode(){dt()}}})()}export{Jt as initTerminal,st as stripAltScreenSequences};
|
package/dist/lib/build-app.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{existsSync as P,readdirSync as le,statSync as A,unlinkSync as me,writeFileSync as fe,mkdirSync as pe,readFileSync as
|
|
1
|
+
import{existsSync as P,readdirSync as le,statSync as A,unlinkSync as me,writeFileSync as fe,mkdirSync as pe,readFileSync as G}from"node:fs";import{readFile as he}from"node:fs/promises";import{execSync as _,execFileSync as L}from"node:child_process";import{tmuxEnv as ge}from"./tmux-env.js";import h from"node:path";import{fileURLToPath as ye}from"node:url";import{Hono as je}from"hono";import{listSessions as D}from"../sessions.js";import{renderLoginPage as we,renderNotesIndex as ve,renderNotesPage as ke,renderSettings as qe,renderThemeSettings as Se,renderHistoryIndex as Te,renderQuickCommandsPage as Ae,renderFilesIndex as be,renderShell as xe}from"../frontend.js";import{renderFederationPage as _e}from"./pages/federation.js";import{createAgentToken as Ce,removeAgentToken as Ie,listAgentTokens as z}from"./agent-tokens.js";import{hashPassword as Ee,verifyPassword as Oe,validatePassword as Ne}from"./auth.js";import{atomicWriteFileSync as Re}from"./atomicWrite.js";import{resolveFsPath as b,resolveFsRoots as B,MAX_FILE_BYTES as V,walkRecursive as Pe}from"./fs-access.js";import{captureSessionWindowsWithPath as Le}from"./tmux-windows.js";import{audit as v}from"./auditLog.js";import{db as S}from"./db.js";import{getSessionAccessMap as De}from"./session-access.js";import{listWindowHistory as Fe,clearWindowHistory as He}from"./window-history.js";import{saveSecurityConfig as Me}from"./security-config.js";import{readSettings as Q,writeSettings as Ue}from"./settings.js";import{setActiveThemeTemplate as K}from"./theme-store.js";import{isThemeTemplateId as X,THEME_TEMPLATE_IDS as We}from"./themes/index.js";import{pinView as $e,unpinView as Ge,listPinnedViews as ze}from"./pinned-views.js";import{getStoredWindows as Be}from"./session-windows.js";import{buildSidebarSessions as Ve}from"./sessions-sidebar.js";import{upsertSessionState as Z,removeSessionState as Y,renameSessionState as J,computeTombstones as Qe,getSessionState as Ke}from"./session-state.js";import{createQuickCommand as Xe,deleteQuickCommand as Ze,listQuickCommands as ee,updateQuickCommand as Ye}from"./quick-commands.js";import{getSystemStatus as Je,getTopProcesses as et,killProcess as tt}from"./system-monitor.js";import{newTmuxSession as te,renameSession as rt,killSession as nt,sessionExists as st,TmuxWindowsError as re}from"./tmux-windows.js";import{registerAgentTunnel as ot}from"./agent-tunnel.js";import{injectScopeScript as it}from"./scope-script.js";const F="tmux-web-token",at=365;function dt(x){return x==="127.0.0.1"||x==="::1"||x==="::ffff:127.0.0.1"}function $t(x){const{mode:ne,securityConfig:g,tokenStore:j,rateLimiter:I,settings:ut,terminalBufferConfig:E,getAgentId:H,state:l,federation:k,hub:w}=x,q=ne==="agent";function C(e){if(g.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function O(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*)${F}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function ct(e){const t=O(e);return t?j.validateToken(t):null}function M(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${F}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${at*86400}${r?"; Secure":""}`)}function se(e){e.header("Set-Cookie",`${F}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}const o=()=>q?async(e,t)=>t():async(e,t)=>{const r=O(e);return!r||!j.validateToken(r)?(v("http_unauthorized",{ip:C(e)}),e.json({error:"unauthorized"},401)):t()},y=()=>q?async(e,t)=>t():async(e,t)=>{const r=O(e);if(!r||!j.validateToken(r)){const n=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${n}`,302)}return t()};function N(e){const t=D(),r=new Set(t.map(c=>c.name)),n=Ve(t,De(),ze()),a=[],i=new Map,u=new Map;if(w)for(const c of w.agents.list()){const d=w.agents.get(c.agentId);i.set(c.agentId,new Set((d?d.sessions:[]).map(m=>m.name))),u.set(c.agentId,c.online),a.push({agentId:c.agentId,agentName:c.name,online:c.online,sessions:d?d.sessions:[]})}const p=Qe(r,i,u);return{...n,currentSession:e??null,agents:a,tombstones:p}}function U(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 je;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()}),q||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(it(n,{prefix:"",agentId:null}),e.res)});const W=h.dirname(ye(import.meta.url)),oe=[h.join(W,"..","assets"),h.join(W,"assets"),h.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of oe){const n=h.join(r,t);if(!P(n))continue;const a=await he(n),i=h.extname(t),u={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(a,200,{"Content-Type":u[i]??"application/octet-stream","Cache-Control":i===".js"||i===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const ie=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
2
|
<rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
|
|
3
3
|
<path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
4
|
<line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
|
|
5
|
-
</svg
|
|
5
|
+
</svg>`,$=e=>e.body(ie,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",$),s.get("/favicon.ico",$);const ae={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(ae,200,{"Cache-Control":"public, max-age=3600"}));const de=`// tmux-weblink Service Worker
|
|
6
6
|
const CACHE = "tmux-weblink-v1";
|
|
7
7
|
const ASSETS = ["/", "/favicon.svg"];
|
|
8
8
|
self.addEventListener("install", (e) => {
|
|
@@ -13,7 +13,7 @@ self.addEventListener("activate", (e) => { e.waitUntil(clients.claim()); });
|
|
|
13
13
|
self.addEventListener("fetch", (e) => {
|
|
14
14
|
e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
|
|
15
15
|
});
|
|
16
|
-
`;s.get("/sw.js",e=>e.body(de,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!g.passwordHash,n=e.req.query("error");return e.html(je({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:l.activeTheme}))}),s.get("/",y(),e=>{const t=B();return e.html(be({theme:l.activeTheme,fsRoots:t,terminalCfg:O,scrollback:O.initialLines+2*O.historyChunk,wsBase:q&&F?.()?`/ws/a/${F()}`:void 0}))}),s.get("/notes",y(),e=>e.html(we(S.data.notes,l.activeTheme))),s.get("/notes/:session",y(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(ve(t,l.activeTheme))}),s.get("/history",y(),e=>{const t=L(),r=new Set(t.map(n=>n.name));return e.html(Se(De(),l.activeTheme,r))}),s.get("/quick-commands",y(),e=>e.html(Te(ee(),l.activeTheme))),s.get("/files",y(),e=>{const t=B();return e.html(Ae(l.activeTheme,t))}),s.post("/api/history/clear",o(),async e=>(await Fe(),e.json({ok:!0}))),s.get("/api/quick-commands",o(),e=>e.json(ee())),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 Ke(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 Ze(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=>Xe(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(q)return e.json({error:"hub only"},403);const t=C(e),r=I.check(t);if(!r.allowed)return v("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const a=typeof n.password=="string"?n.password:"";if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!at(t))return v("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(l.settingUpPassword)return e.json({error:"Password setup in progress"},409);const c=Ee(a);if(c)return e.json({error:c},400);l.settingUpPassword=!0;try{g.passwordHash=await Ie(a),He(g),v("password_set",{ip:t})}finally{l.settingUpPassword=!1}const{plaintext:d}=j.createAccessToken("setup",g.security.tokenTtlDays);return H(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await Oe(a,g.passwordHash)){const c=I.recordFailure(t);return v("auth_failed",{ip:t,method:"password",failures:c.failures,permanentLock:c.permanentLock}),c.permanentLock?(j.revokeAll(),v("permanent_lock",{ip:t,failures:c.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):c.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:c.retryAfterMs},429)}I.recordSuccess(t);const u=`browser-${t}`,{plaintext:p}=j.createAccessToken(u,g.security.tokenTtlDays);return H(e,p),v("auth_success",{ip:t,method:"password",tokenName:u}),e.json({ok:!0,token:p})}),s.post("/api/auth/token",o(),async e=>{if(q)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}`,a=typeof r.ttlDays=="number"?r.ttlDays:g.security.tokenTtlDays,{stored:i,plaintext:u}=j.createAccessToken(n,a);return v("token_created",{ip:t,name:i.name,tokenId:i.id}),e.json({id:i.id,name:i.name,token:u,expiresAt:i.expiresAt})}),s.get("/api/auth/tokens",o(),e=>q?e.json({error:"hub only"},403):e.json(j.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=>q?e.json({error:"hub only"},403):j.revoke(e.req.param("id"))?(v("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=>(se(e),e.json({ok:!0}))),s.get("/settings",y(),async e=>{const t=await Q();return e.html(ke({settings:t,theme:l.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 Me({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",y(),e=>e.html(qe({theme:l.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):(l.activeTheme=await K(r),e.redirect("/settings/theme?saved=1",303))});const ue={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",o(),e=>e.json({active:l.activeTheme.template,templates:Ue.map(t=>({id:t,name:ue[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):(l.activeTheme=await K(r),e.json({ok:!0,active:l.activeTheme.template}))}),s.get("/api/system/status",o(),e=>e.json(Ye())),s.get("/api/system/processes",o(),e=>e.json(Je())),k&&(s.get("/settings/federation",y(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",y(),async e=>e.html(xe({config:k.getConfig(),status:k.status(),tokens:z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:k.hostname},l.activeTheme))),s.get("/api/machines",o(),e=>e.json({config:k.getConfig(),status:k.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():"",a=typeof t.name=="string"?t.name.trim().slice(0,64):"",i=t.enabled===!0;if(i){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const u={hub:r||void 0,token:n||void 0,name:a||void 0,enabled:i};return await k.setConfig(u),e.json({ok:!0,status:k.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:a}=_e(r);return e.json({ok:!0,id:n,name:r,token:a})}),s.delete("/api/agent-tokens/:id",o(),e=>{const t=e.req.param("id");return Ce(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=et(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",o(),e=>w?e.json(w.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(i=>i.name===r))return e.json({error:"session already exists"},409);try{return te(r,n),Z(r,{path:n}),e.json({ok:!0})}catch(i){const u=i instanceof re?i.message:"failed to create session";return e.json({error:u},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)?(tt(t,r),J(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?(rt(t),Y(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.post("/api/sessions/rebuild",o(),async e=>{try{const t=await e.req.json(),r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name required"},400);const n=typeof t.agentId=="string"?t.agentId:void 0,i=Qe().find(u=>u.name===r&&(u.agentId??null)===(n??null))?.path;if(n){if(!w)return e.json({error:"not a hub"},400);const u=w.getChannel(n);return u?u.rebuildSession(r,i)?e.json({ok:!0,async:!0}):e.json({error:"agent offline"},409):e.json({error:"agent offline"},409)}if(nt(r))return e.json({error:"session already exists"},409);try{te(r,i)}catch(u){const p=u instanceof re?u.message:"rebuild failed";return e.json({error:p},500)}return Z(r,{path:i}),e.json({ok:!0})}catch{return e.json({error:"rebuild failed"},500)}}),s.post("/api/sessions/dismiss",o(),async e=>{try{const t=await e.req.json(),r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name required"},400);const n=typeof t.agentId=="string"?t.agentId:void 0;return Y(r,n),e.json({ok:!0})}catch{return e.json({error:"dismiss failed"},500)}}),s.post("/api/sessions/rename-tombstone",o(),async e=>{try{const t=await e.req.json(),r=typeof t.oldName=="string"?t.oldName.trim():"",n=typeof t.newName=="string"?t.newName.trim():"";if(!r||!n)return e.json({error:"oldName and newName required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(n))return e.json({error:"invalid characters in name"},400);const a=typeof t.agentId=="string"?t.agentId:void 0;return J(r,n,a),e.json({ok:!0})}catch{return e.json({error:"rename 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=Pe(t),a=r.find(i=>i.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:a})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",o(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=h.join(t,r));const n=e.req.query("recursive")==="true";let a=r,i="",u=r.endsWith("/");if(!u)try{u=A(r).isDirectory()}catch{}u||(a=h.dirname(r),i=h.basename(r).toLowerCase());try{const p=le(a),c=[],d=[];for(const m of p)if(!m.startsWith(".")&&!(i&&!m.toLowerCase().startsWith(i))){try{const f=h.join(a,m);A(f).isDirectory()?(c.push(f),n&&Re(f,c,d,0)):d.push(f)}catch{}if(c.length+d.length>=5e3)break}return e.json({dirs:c,files:d})}catch{return e.json({dirs:[],files:[]})}});function ce(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 a="";try{a=_("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const i=[];for(const d of a.split(`
|
|
16
|
+
`;s.get("/sw.js",e=>e.body(de,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!g.passwordHash,n=e.req.query("error");return e.html(we({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:l.activeTheme}))}),s.get("/",y(),e=>{const t=B();return e.html(xe({theme:l.activeTheme,fsRoots:t,terminalCfg:E,scrollback:E.initialLines+2*E.historyChunk,wsBase:q&&H?.()?`/ws/a/${H()}`:void 0}))}),s.get("/notes",y(),e=>e.html(ve(S.data.notes,l.activeTheme))),s.get("/notes/:session",y(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.html(ke(t,l.activeTheme))}),s.get("/history",y(),e=>{const t=D(),r=new Set(t.map(n=>n.name));return e.html(Te(Fe(),l.activeTheme,r))}),s.get("/quick-commands",y(),e=>e.html(Ae(ee(),l.activeTheme))),s.get("/files",y(),e=>{const t=B();return e.html(be(l.activeTheme,t))}),s.post("/api/history/clear",o(),async e=>(await He(),e.json({ok:!0}))),s.get("/api/quick-commands",o(),e=>e.json(ee())),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 Xe(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 Ye(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=>Ze(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{if(q)return e.json({error:"hub only"},403);const t=C(e),r=I.check(t);if(!r.allowed)return v("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const a=typeof n.password=="string"?n.password:"";if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!dt(t))return v("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(l.settingUpPassword)return e.json({error:"Password setup in progress"},409);const c=Ne(a);if(c)return e.json({error:c},400);l.settingUpPassword=!0;try{g.passwordHash=await Ee(a),Me(g),v("password_set",{ip:t})}finally{l.settingUpPassword=!1}const{plaintext:d}=j.createAccessToken("setup",g.security.tokenTtlDays);return M(e,d),e.json({ok:!0,token:d,setupMode:!0})}if(!await Oe(a,g.passwordHash)){const c=I.recordFailure(t);return v("auth_failed",{ip:t,method:"password",failures:c.failures,permanentLock:c.permanentLock}),c.permanentLock?(j.revokeAll(),v("permanent_lock",{ip:t,failures:c.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):c.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:c.retryAfterMs},429)}I.recordSuccess(t);const u=`browser-${t}`,{plaintext:p}=j.createAccessToken(u,g.security.tokenTtlDays);return M(e,p),v("auth_success",{ip:t,method:"password",tokenName:u}),e.json({ok:!0,token:p})}),s.post("/api/auth/token",o(),async e=>{if(q)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}`,a=typeof r.ttlDays=="number"?r.ttlDays:g.security.tokenTtlDays,{stored:i,plaintext:u}=j.createAccessToken(n,a);return v("token_created",{ip:t,name:i.name,tokenId:i.id}),e.json({id:i.id,name:i.name,token:u,expiresAt:i.expiresAt})}),s.get("/api/auth/tokens",o(),e=>q?e.json({error:"hub only"},403):e.json(j.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=>q?e.json({error:"hub only"},403):j.revoke(e.req.param("id"))?(v("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=>(se(e),e.json({ok:!0}))),s.get("/settings",y(),async e=>{const t=await Q();return e.html(qe({settings:t,theme:l.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 Ue({...r,defaultView:n}),e.redirect("/settings?saved=1",303)}),s.get("/settings/theme",y(),e=>e.html(Se({theme:l.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):(l.activeTheme=await K(r),e.redirect("/settings/theme?saved=1",303))});const ue={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",o(),e=>e.json({active:l.activeTheme.template,templates:We.map(t=>({id:t,name:ue[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):(l.activeTheme=await K(r),e.json({ok:!0,active:l.activeTheme.template}))}),s.get("/api/system/status",o(),e=>e.json(Je())),s.get("/api/system/processes",o(),e=>e.json(et())),k&&(s.get("/settings/federation",y(),e=>e.redirect("/settings/machines",301)),s.get("/settings/machines",y(),async e=>e.html(_e({config:k.getConfig(),status:k.status(),tokens:z().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt})),hostname:k.hostname},l.activeTheme))),s.get("/api/machines",o(),e=>e.json({config:k.getConfig(),status:k.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():"",a=typeof t.name=="string"?t.name.trim().slice(0,64):"",i=t.enabled===!0;if(i){if(!r)return e.json({error:"hub URL is required"},400);if(!/^wss?:\/\//i.test(r))return e.json({error:"hub URL must start with ws:// or wss://"},400);if(!n)return e.json({error:"registration token is required"},400)}const u={hub:r||void 0,token:n||void 0,name:a||void 0,enabled:i};return await k.setConfig(u),e.json({ok:!0,status:k.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:a}=Ce(r);return e.json({ok:!0,id:n,name:r,token:a})}),s.delete("/api/agent-tokens/:id",o(),e=>{const t=e.req.param("id");return Ie(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=tt(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/agents",o(),e=>w?e.json(w.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(D().some(i=>i.name===r))return e.json({error:"session already exists"},409);try{return te(r,n),Z(r,{path:n}),e.json({ok:!0})}catch(i){const u=i instanceof re?i.message:"failed to create session";return e.json({error:u},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)?(rt(t,r),J(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?(nt(t),Y(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.post("/api/sessions/rebuild",o(),async e=>{try{const t=await e.req.json(),r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name required"},400);const n=typeof t.agentId=="string"?t.agentId:void 0,i=Ke().find(u=>u.name===r&&(u.agentId??null)===(n??null))?.path;if(n){if(!w)return e.json({error:"not a hub"},400);const u=w.getChannel(n);return u?u.rebuildSession(r,i)?e.json({ok:!0,async:!0}):e.json({error:"agent offline"},409):e.json({error:"agent offline"},409)}if(st(r))return e.json({error:"session already exists"},409);try{te(r,i)}catch(u){const p=u instanceof re?u.message:"rebuild failed";return e.json({error:p},500)}return Z(r,{path:i}),e.json({ok:!0})}catch{return e.json({error:"rebuild failed"},500)}}),s.post("/api/sessions/dismiss",o(),async e=>{try{const t=await e.req.json(),r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name required"},400);const n=typeof t.agentId=="string"?t.agentId:void 0;return Y(r,n),e.json({ok:!0})}catch{return e.json({error:"dismiss failed"},500)}}),s.post("/api/sessions/rename-tombstone",o(),async e=>{try{const t=await e.req.json(),r=typeof t.oldName=="string"?t.oldName.trim():"",n=typeof t.newName=="string"?t.newName.trim():"";if(!r||!n)return e.json({error:"oldName and newName required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(n))return e.json({error:"invalid characters in name"},400);const a=typeof t.agentId=="string"?t.agentId:void 0;return J(r,n,a),e.json({ok:!0})}catch{return e.json({error:"rename failed"},500)}}),s.get("/api/session-program",o(),e=>{const t=e.req.query("session");if(!t)return e.json({program:null});try{const n=L("tmux",["display-message","-p","-t",t,"#{pane_current_command}"],{encoding:"utf-8",timeout:3e3,env:ge()}).trim()||null;return e.json({program:n})}catch{return e.json({program:null})}}),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=Le(t),a=r.find(i=>i.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:a})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",o(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=h.join(t,r));const n=e.req.query("recursive")==="true";let a=r,i="",u=r.endsWith("/");if(!u)try{u=A(r).isDirectory()}catch{}u||(a=h.dirname(r),i=h.basename(r).toLowerCase());try{const p=le(a),c=[],d=[];for(const m of p)if(!m.startsWith(".")&&!(i&&!m.toLowerCase().startsWith(i))){try{const f=h.join(a,m);A(f).isDirectory()?(c.push(f),n&&Pe(f,c,d,0)):d.push(f)}catch{}if(c.length+d.length>=5e3)break}return e.json({dirs:c,files:d})}catch{return e.json({dirs:[],files:[]})}});function ce(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 a="";try{a=_("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const i=[];for(const d of a.split(`
|
|
17
17
|
`)){if(!d.trim())continue;const m=d[0],f=d[1],T=d.substring(3).trim(),R=f!==" "?f:m;i.push({path:T,status:R,staged:m,unstaged:f,additions:0,deletions:0})}const u=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
|
-
`)){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;u.set(f[2],{added:T,deleted:R})}}}catch{}for(const d of i){const m=u.get(d.path);m&&(d.additions=m.added,d.deletions=m.deleted)}let p=0,c=0;for(const d of i)p+=d.additions,c+=d.deletions;for(const d of i)if(d.status==="?"&&d.additions===0)try{const m=h.join(r,d.path),T
|
|
19
|
-
`).length;d.additions=T,p+=T}catch{}return{repoRoot:r,branch:n,files:i,linesAdded:p,linesRemoved:c}}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=ce(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 a=
|
|
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;u.set(f[2],{added:T,deleted:R})}}}catch{}for(const d of i){const m=u.get(d.path);m&&(d.additions=m.added,d.deletions=m.deleted)}let p=0,c=0;for(const d of i)p+=d.additions,c+=d.deletions;for(const d of i)if(d.status==="?"&&d.additions===0)try{const m=h.join(r,d.path),T=G(m,"utf-8").split(`
|
|
19
|
+
`).length;d.additions=T,p+=T}catch{}return{repoRoot:r,branch:n,files:i,linesAdded:p,linesRemoved:c}}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=ce(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 a=L("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),i=L("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:a,stagedDiff:i})}catch(a){throw console.error("[git/diff] execFileSync failed:",a?.code,a.message),a}}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 a=G(r,"utf-8");return e.json({path:r,content:a,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",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 Re(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()?(me(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):(pe(h.dirname(r),{recursive:!0}),fe(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(Be(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=U(t);return"error"in r?e.json({error:r.error},400):(await $e(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=U(t);return"error"in r?e.json({error:r.error},400):(await Ge(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()},a=S.data.notes.findIndex(i=>i.scope===t);return a>=0?S.data.notes[a]=n:S.data.notes.push(n),await S.write(),e.json({ok:!0})}),w&&(s.use("/a/:agentId/*",o()),ot(s,{getChannel:w.getChannel})),s}export{$t as buildApp};
|