tmux-weblink 2.2.13 → 2.2.14
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`+ue(
|
|
5
|
-
`).then(()=>{
|
|
1
|
+
var vn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Ge(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function ue(e){return e.replace(vn,"")}var He;function gn(){return He||(document.querySelector("link[data-tmux-web-xterm-css]")?He=Promise.resolve():(He=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)}),He))}var yn=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"),gn()]),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=wn(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,S=[];for(let H=c;H<=o;H++){let y=r.getLine(k+H);if(!y){S.push("");continue}let B=H===c?u:0,Oe=H===o?h:this.terminal.cols-1,te="";for(let ne=B;ne<=Oe;ne++){let xe=y.getCell(ne);xe&&(te+=xe.getChars())}S.push(te.replace(/\s+$/,""))}return S.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function wn(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 bn(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let s=await yn.create(e,n.scrollback,n.theme),l,r=0,c,o=null,u=null,h=null;async function k(){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}}catch{}}function S(){h&&clearTimeout(h),h=setTimeout(()=>{k()},600)}function H(i){return u!==null&&/(?:^|\/)(?:vim|nvim|vi)$/.test(u)?i?"":"":i?"\x1B[A":"\x1B[B"}let y=0,B=0,Oe=.2,te=10,ne=.95,xe=.04;function Ce(){y&&(cancelAnimationFrame(y),y=0),B=0}function Wt(i){if(Ce(),Math.abs(i)<Oe)return;B=Math.max(-te,Math.min(te,i));let a=()=>{if(j){y=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,b=p>0?Math.round(B*16/p):B>0?1:-1;b===0&&(b=B>0?1:-1);let I=s.viewportY()-b,K=s.baseY(),_=Math.max(0,Math.min(I,K));if(s.scrollToLine(_,!0),B*=ne,_!==I){y=0;return}if(Math.abs(B)<xe){y=0;return}y=requestAnimationFrame(a)};y=requestAnimationFrame(a)}let Ke=0,P="connecting",ke=0,se=!1,Se=[],G="",j=!1,ie=1e3,Yt=location.protocol==="https:"?"wss:":"ws:",qt=n.wsBase||"/ws",Ot=Yt+"//"+location.host+qt+"/"+encodeURIComponent(t),Kt="/api/session/"+encodeURIComponent(t)+"/upload",Vt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Xt(){return Se.join("")+G}async function Jt(i,a=0){let d=i?s.viewportY():0,p=ue(Xt());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(ue(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"){Te=!0,P="dead",s.reset(),s.write(`\r
|
|
4
|
+
\x1B[31m`+ue(a.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{Me();try{l?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),ie=1e3,O(),q({type:"resize",cols:s.cols,rows:s.rows}),T(),k();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=Ge(ue(a.data));Se=[d],G="",ke=typeof a.lines=="number"?a.lines:n.terminal.initialLines,P="live",s.reset(),O(),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?(Se.unshift(Ge(a.data)),ke+=a.lines,Jt(!0,a.lines).finally(()=>{se=!1})):se=!1;return}if(a.type==="data"&&typeof a.data=="string"){/\x1b\[\?1049h/.test(a.data)&&S();let d=Ge(ue(a.data));if(P==="connecting"){P="live",G=d,s.write(d);return}P==="live"&&(G+=d,s.write(d))}}let Te=!1;function rt(){if(Te)return;let i=Ot,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",ke=0,se=!1,Se=[],G="",s.reset(),ie=1e3},l.onmessage=d=>{typeof d.data=="string"&&Gt(d.data)},l.onclose=()=>{j||(P="connecting",!Te&&setTimeout(()=>{ie=Math.min(ie*2,1e4),!j&&!Te&&rt()},ie))},l.onerror=()=>l?.close()}function ae(i){q({type:"input",data:i})}function O(){return s.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),O(),r=requestAnimationFrame(()=>{r=0,O()&&Me()}),c=setTimeout(()=>{O()&&Me()},120)}function Zt(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function ct(i){return typeof i=="string"&&i.startsWith("image/")}function dt(i){return i?.files?.length?i.files[0]??null:null}function Qt(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 en(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"),b={};p&&(b.Authorization="Bearer "+p);let I=await fetch(Kt,{method:"POST",body:a,headers:b});if(!I.ok){let _="upload failed";try{let Ae=await I.json();Ae.error&&(_=Ae.error)}catch{}throw new Error(_)}let K=await I.json();if(!K.path)throw new Error("no path in response");return K.path}async function Ve(i){try{let a=await en(i);ae(a)}catch{}}s.onData(i=>ae(i)),s.onResize(({cols:i,rows:a})=>q({type:"resize",cols:i,rows:a})),s.onScroll(()=>{P!=="live"||se||!s.isNearScrollbackTop()||(se=!0,q({type:"load_history",before:ke}))});let oe=!1,le=!1,L=null,E=null,re=document.createElement("div");re.className="terminal-copy-banner",re.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',re.hidden=!0,e.appendChild(re);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,e.appendChild($);function ut(i,a){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),b=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)/b))),col:Math.max(0,Math.min(s.cols-1,Math.floor((i-p.left)/I)))}}async function Be(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 ce;function Ie(){$.hidden=!1,ce&&clearTimeout(ce),ce=setTimeout(()=>{$.hidden=!0},1e3)}function Z(i){oe=i,re.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&s.focus()}function pt(){Z(!oe)}function tn(i){j||i.button!==0||le||(oe||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),le=!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){j||!le||!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(j||!le||!L||!E)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",Xe,!0),document.removeEventListener("mouseup",Je,!0),le=!1;let a=s.getCellsText(L.col,L.row,E.col,E.row);L=null,E=null,s.clearSelection(),Z(!1),a&&($.textContent="\u5DF2\u590D\u5236",Be(a).then(Ie)),s.focus()}e.addEventListener("mousedown",tn,!0),document.addEventListener("keydown",nn,!0);function nn(i){if(!(j||!s.isFocused()||i.defaultPrevented||i.isComposing)){if(oe){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),L=null,E=null,s.clearSelection(),Z(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Be(s.getScreenText()).then(Ie),Z(!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(),Z(!1),a&&($.textContent="Copied",Be(a).then(Ie))}else{let a=s.getSelection();a?(s.clearSelection(),$.textContent="Copied",Be(a).then(Ie)):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 b=await d.getType(p);await Ve(b);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&s.pasteText(mt(a))}catch{}})();return}}}let De=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")||ve(e.name)});let n=t.querySelector(".session-edit-btn");return n&&n.addEventListener("click",s=>{s.stopPropagation(),
|
|
13
|
-
<span>${
|
|
14
|
-
<button class="session-edit-btn" data-session="${
|
|
8
|
+
`).join("\r")}async function sn(i){let a=Qt(i.clipboardData);if(a){i.preventDefault(),i.stopPropagation();let b=Date.now();if(b-De<50)return;De=b,await Ve(a);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let p=Date.now();p-De<50||(De=p,s.pasteText(mt(d)))}let de=0;function an(i){i.preventDefault(),de++,dt(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function on(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function ln(i){i.preventDefault(),de--,de<=0&&(de=0,e.classList.remove("terminal-drag-over"))}async function rn(i){i.preventDefault(),de=0,e.classList.remove("terminal-drag-over");let a=dt(i.dataTransfer);a&&await Ve(a)}function ft(i){!Vt||i.key!=="Escape"||oe||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),ae("\x1B"))}e.addEventListener("paste",sn),e.addEventListener("dragenter",an),e.addEventListener("dragover",on),e.addEventListener("dragleave",ln),e.addEventListener("drop",rn),document.addEventListener("keydown",ft,!0);function cn(i){if(i.touches.length!==1){o=null;return}let a=i.touches[0];if(!a)return;Ce();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 dn(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 b=performance.now(),I=a.clientY-o.lastY;if(o.zone==="tui"){o.tuiAccum+=I;let _=e.querySelector(".xterm-rows > div"),Ae=_?_.getBoundingClientRect().height:16,gt=Math.max(16,Ae);for(;Math.abs(o.tuiAccum)>=gt;)ae(H(o.tuiAccum>0)),o.tuiAccum-=Math.sign(o.tuiAccum)*gt;o.lastX=a.clientX,o.lastY=a.clientY,o.lastT=b;return}let K=b-o.lastT;K>0&&(o.velocity=I/K),fn(-I),o.lastX=a.clientX,o.lastY=a.clientY,o.lastT=b}function un(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&&Wt(p)}function pn(i){o=null,Ce(),i.stopPropagation()}function mn(i){i.pointerType==="touch"&&Date.now()<Ke&&(i.preventDefault(),i.stopPropagation())}function fn(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",cn,{passive:!0,capture:!0}),e.addEventListener("touchmove",dn,{passive:!1,capture:!0}),e.addEventListener("touchend",un,{passive:!1,capture:!0}),e.addEventListener("touchcancel",pn,{passive:!0,capture:!0}),e.addEventListener("pointerup",mn,!0);function ht(){T()}window.addEventListener("resize",ht),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let vt=new ResizeObserver(()=>T());vt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",Zt,!0);async function hn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{O()&&Me(),(async()=>(await hn(),O()&&q({type:"resize",cols:s.cols,rows:s.rows}),rt(),T()))()}),{destroy(){if(j=!0,Ce(),Z(!1),ce&&clearTimeout(ce),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){ae(i)},toggleCopyMode(){pt()}}})()}var pe=window.__TMUX_WEB_SHELL__,v=null,g=null,me=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,M=me;window.__tmuxWebScopeAgent=me;var Ze="sessions",U="",Fe="",Re="",yt="";function x(e){return M?"/a/"+encodeURIComponent(M)+e:e}var m=document.getElementById("sidebar-content"),V=document.getElementById("main-placeholder"),D=document.getElementById("terminal-container"),fe=document.getElementById("file-editor"),Ln=document.getElementById("fe-path"),F=document.getElementById("fe-status"),he=document.getElementById("fe-content"),X=document.getElementById("fe-save"),En=document.getElementById("fe-delete"),xn=document.getElementById("fe-back"),wt=document.getElementById("fe-new-name"),Cn=document.getElementById("fe-new-btn"),Qe=document.getElementById("settings-backdrop"),bt=document.getElementById("settings-popover"),Lt=document.querySelector(".sidebar");function kn(){return window.matchMedia("(max-width: 640px)").matches}function et(){kn()&&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="",W()):zn()}var tt="";async function W(){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,In(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 Sn(e){try{localStorage.setItem(Ct,JSON.stringify([...e]))}catch{}}function Tn(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=Tn(o,r,t),k=c.has(o),S=document.createElement("div");S.className="session-group-header"+(k?" collapsed":""),S.title=o||"No working directory",S.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${f(h)}</span>`,S.addEventListener("click",()=>{let y=kt(),B=S.nextElementSibling;y.has(o)?(y.delete(o),S.classList.remove("collapsed"),B?.classList.remove("collapsed")):(y.add(o),S.classList.add("collapsed"),B?.classList.add("collapsed")),Sn(y)});let H=document.createElement("div");H.className="session-group-body"+(k?" collapsed":"");for(let y of u)H.appendChild(n?Mn(y,n,s):Bn(y));m.appendChild(S),m.appendChild(H)}}function Mn(e,t,n){let s=document.createElement("div");return s.className="session-item"+(e.name===g&&M===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")||ve(e.name,t)}),s}function Bn(e){let t=document.createElement("div");t.className="session-item"+(e.name===g&&!M?" 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")||ve(e.name)});let n=t.querySelector(".session-edit-btn");return n&&n.addEventListener("click",s=>{s.stopPropagation(),Pt(e.name,n)}),t}function In(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"+(me?" clickable":""),n.textContent=me?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",me&&(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===M,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(),Pt(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 ve(e,t){if(!(g===e&&v&&M===(t||null))){fe.style.display="none",V.style.display="none",D.style.display="",D.classList.add("terminal-pending"),et(),v&&(We(),v.destroy(),v=null),g=e,M=t||null,window.__tmuxWebScopeAgent=M,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)===M);try{v=await bn(D,e,{terminal:pe.terminal,scrollback:pe.scrollback,theme:pe.theme,wsBase:M?"/ws/a/"+encodeURIComponent(M):pe.wsBase||"/ws"}),Nt()}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 Ne=[],w=null,Tt=!1;function Dn(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let n=e.slice(t.length+1);for(let s of w.files)if(s.path===n)return s.status;return null}async function An(e){Tt=!0;try{let t=await fetch(x("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{Tt=!1}}function Hn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:n}=w,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")&&Q&&w&&(e.stopPropagation(),At())});var ge=document.getElementById("header-git"),Mt=document.getElementById("git-diff-view"),Bt=document.getElementById("gd-path"),Pe=document.getElementById("gd-status"),$e=document.getElementById("gd-content"),Fn=document.getElementById("gd-back"),Q=null;function ye(e){if(!e||!e.branch){ge.style.display="none",Q=null;return}Q=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>`),ge.innerHTML=l,ge.style.display="block"}ge.addEventListener("click",e=>{Q&&w&&(e.stopPropagation(),At())});var C=document.getElementById("git-popover"),ze=document.getElementById("git-popover-backdrop");function nt(){let e=ge.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=Q,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(_e(),e){Ze="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),Y(e)}})}var Dt="";function At(){if(!w)return;let e=w.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"),ze.classList.add("open"),It()}function Rn(){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 je=null;async function Ft(e){let t=Q;if(t)if(_e(),Rn()){D.style.display="none",v&&(We(),v.destroy(),v=null,je=g,g=null),ye(null),fe.style.display="none",V.style.display="none",Mt.style.display="flex",Bt.textContent=e,Pe.textContent="Loading\u2026",$e.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();Pn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);Pe.textContent="Error",$e.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"),ze.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();Nn(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 Nn(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(),w&&C.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),o=w.files[c];!o||o.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>Ft(o.path)))}),It()})}function Pn(e,t,n){if(!t&&!n){Pe.textContent="No diff",$e.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",$e.innerHTML=s}function $n(){Mt.style.display="none",je?(D.style.display="",D.classList.add("terminal-pending"),ve(je),je=null):g?(D.style.display="",D.classList.add("terminal-pending"),Nt()):V.style.display="flex"}Fn.addEventListener("click",$n);function _e(){C.classList.remove("open"),ze.classList.remove("open")}ze.addEventListener("click",_e),document.addEventListener("keydown",e=>{e.key==="Escape"&&(_e(),qe())});async function Rt(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();w=s,ye(s);return}}}catch{}w=null,ye(null)}var Ue=null;function Nt(){We(),g&&(Rt(g),Ue=setInterval(()=>{g&&Rt(g)},3e4))}function We(){Ue&&(clearInterval(Ue),Ue=null)}function zn(){if(Ne=pe.fsRoots||[],Ne.length===0){if(g){jn(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 Ne){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",()=>Y(e)),m.appendChild(t)}}async function jn(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?Y(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 Y(e){U=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=An(e);try{let n=await(await fetch(x("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",ye(w),m.innerHTML+=Hn();let s=_n(e);if(s&&!Ne.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>Y(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",()=>Y(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=Dn(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",()=>Un(r)),m.appendChild(k)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function _n(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Un(e){Fe=e,D.style.display="none",v&&(We(),v.destroy(),v=null,g=null),ye(null),et(),V.style.display="none",fe.style.display="flex",Ln.textContent=e,F.textContent="Loading...",X.style.display="none";try{let t=await fetch(x("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let s=await t.json();F.textContent=s.error||"Failed to load",he.value="";return}let n=await t.json();he.value=n.content,Re=n.content,yt=n.content,F.textContent=n.size+" bytes",X.style.display=""}catch{F.textContent="Failed to load",he.value=""}}he.addEventListener("input",()=>{Re=he.value}),X.addEventListener("click",async()=>{X.textContent="Saving...",X.disabled=!0;try{let e=await fetch(x("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Fe,content:Re})});if(!e.ok){let t=await e.json();F.textContent=t.error||"Save failed";return}yt=Re,F.textContent="Saved"}catch{F.textContent="Save failed"}finally{X.disabled=!1,X.textContent="Save"}}),En.addEventListener("click",async()=>{if(confirm("Delete "+Fe+"?"))try{let e=await fetch(x("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Fe})});if(!e.ok){let t=await e.json();F.textContent=t.error||"Delete failed";return}fe.style.display="none",V.style.display="flex",U&&Y(U)}catch{F.textContent="Delete failed"}}),xn.addEventListener("click",()=>{fe.style.display="none",V.style.display="flex",U&&Y(U)}),Cn.addEventListener("click",async()=>{let e=wt.value.trim();if(!e)return;let t=U+"/"+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();F.textContent=s.error||"Create failed";return}wt.value="",U&&Y(U)}catch{F.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=bt.classList.toggle("open");Qe.classList.toggle("open",e)}),Qe.addEventListener("click",()=>{bt.classList.remove("open"),Qe.classList.remove("open")});var A="",z=!1,Ye,we=document.createElement("div");we.className="session-popover-backdrop",document.body.appendChild(we);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;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),
|
|
47
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(R);function Wn(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 Pt(e,t,n){A=e,z=!!n?.tombstone,Ye=n?.agentId;let s=document.getElementById("sp-name");s.value=e;let l=R.querySelector(".sp-field label");l.textContent=z?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=z?"":"none";let c=document.getElementById("sp-delete");c.textContent=z?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let o=document.getElementById("sp-save");o.textContent=z?"\u91CD\u547D\u540D":"Save",we.classList.add("open"),R.style.display="block",Wn(t),setTimeout(()=>s.focus(),50)}function J(){we.classList.remove("open"),R.style.display="none"}we.addEventListener("click",J),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===A){J();return}try{let t=z?{oldName:A,newName:e,agentId:Ye}:{oldName:A,newName:e};if(!(await fetch(x(z?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;J(),W(),!z&&g===A&&ve(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:Ye})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}J(),W()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(z){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:Ye})})).ok)return;J(),W()}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;J(),g===A&&(v&&(v.destroy(),v=null),g=null,D.style.display="none",V.style.display="flex"),W()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",J);var Yn={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=Yn[t.dataset.key];n&&(v.sendInput(n),v.focus())});var N=document.getElementById("mk-input"),$t=document.getElementById("mk-send");function zt(){if(!N||!v)return;let e=N.value;e&&(v.sendInput(e+"\r"),N.value="",N.rows=1,v.focus())}if(N&&(N.addEventListener("input",()=>{N.rows=1;let e=N.value.split(`
|
|
48
|
+
`).length;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),zt())})),$t&&$t.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"),be=document.getElementById("proc-list"),qn=document.getElementById("proc-close"),it=document.getElementById("proc-backdrop"),On=document.getElementById("header-ram");function Kn(){be.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){be.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>'}be.innerHTML=t;for(let n of be.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(()=>{be.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Vn(){st.classList.add("open"),it.classList.add("open"),Kn()}function qe(){st.classList.remove("open"),it.classList.remove("open")}On.addEventListener("click",e=>{e.stopPropagation(),st.classList.contains("open")?qe():Vn()}),qn.addEventListener("click",qe),it.addEventListener("click",qe);var Xn=2e3,Le=null,at=null,ee=new Map,Ee=new Map;function jt(e,t){return(t?"a:"+t+":":"l:")+e}function Jn(e,t,n){let s=jt(e,t),l=Ee.get(s),r=e===g&&(t??null)===M,c=n==="idle"&&l?.state==="working";Ee.set(s,{session:e,agentId:t,state:n,doneUntil:c?Date.now()+Xn: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,Ee.get(s))}function ot(e,t,n){let s=ee.get(t);s&&(clearTimeout(s),ee.delete(t)),e.classList.remove("working","just-done");let l=n.session===g&&(n.agentId??null)===M;n.state==="working"?e.classList.add("working"):l&&n.doneUntil&&Date.now()<n.doneUntil?(e.classList.add("just-done"),ee.set(t,setTimeout(()=>{e.classList.remove("just-done"),ee.delete(t)},Math.max(0,n.doneUntil-Date.now())))):!l&&n.unread&&e.classList.add("just-done")}function lt(){for(let[e,t]of Ee){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=Ee.get(n);if(!s)return;let l=ee.get(n);l&&(clearTimeout(l),ee.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(Le||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);Le=n,n.onmessage=s=>{try{let l=JSON.parse(String(s.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Jn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},n.onclose=()=>{Le===n&&(Le=null,at=setTimeout(()=>{at=null,_t()},5e3))},n.onerror=()=>{try{n.close()}catch{}}}catch{Le=null}}window.__openSession=ve,window.__refreshSidebar=W,W(),et(),lt(),_t(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&<()}),setInterval(()=>{document.visibilityState==="visible"&&W()},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
|
|
1
|
+
const Ut=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function st(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function N(s){return s.replace(Ut,"")}let U;function jt(){return U||(document.querySelector("link[data-tmux-web-xterm-css]")?U=Promise.resolve():(U=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)}),U))}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"),jt()]),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=Ht(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,h=Math.max(2,Math.floor((o.width-a)/l)),g=Math.max(2,Math.floor(o.height/f));return(h!==this.terminal.cols||g!==this.terminal.rows)&&this.terminal.resize(h,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 h=l;h<=f;h++){const g=n.getLine(d+h);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 h=Math.min(o,n),g=Math.max(o,n);this.terminal.select(h,f.viewportY+d,g-h+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),h=r<l?o:r>l?n:Math.min(o,n),g=r<l?n:r>l?o:Math.max(o,n),j=f.viewportY,A=[];for(let L=d;L<=a;L++){const y=f.getLine(j+L);if(!y){A.push("");continue}const T=L===d?h:0,et=L===a?g:this.terminal.cols-1;let Y="";for(let F=T;F<=et;F++){const H=y.getCell(F);H&&(Y+=H.getChars())}A.push(Y.replace(/\s+$/,""))}return A.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Ht(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 Wt(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,h=null,g=null;async function j(){try{const t=await fetch("/api/session-program?session="+encodeURIComponent(o));if(t.ok){const e=await t.json();h=typeof e.program=="string"&&e.program?e.program:null}}catch{}}function A(){g&&clearTimeout(g),g=setTimeout(()=>{j()},600)}function L(t){return h!==null&&/(?:^|\/)(?:vim|nvim|vi)$/.test(h)?t?"":"":t?"\x1B[A":"\x1B[B"}let y=0,T=0;const et=.2,Y=10,F=.95,H=.04;function W(){y&&(cancelAnimationFrame(y),y=0),T=0}function yt(t){if(W(),Math.abs(t)<et)return;T=Math.max(-Y,Math.min(Y,t));const e=()=>{if(M){y=0;return}const i=s.querySelector(".xterm-rows > div"),c=i?i.getBoundingClientRect().height:16;let u=c>0?Math.round(T*16/c):T>0?1:-1;u===0&&(u=T>0?1:-1);const x=n.viewportY()-u,D=n.baseY(),P=Math.max(0,Math.min(x,D));if(n.scrollToLine(P,!0),T*=F,P!==x){y=0;return}if(Math.abs(T)<H){y=0;return}y=requestAnimationFrame(e)};y=requestAnimationFrame(e)}let nt=0,b="connecting",V=0,B=!1,J=[],I="",M=!1,z=1e3;const vt=location.protocol==="https:"?"wss:":"ws:",xt=r.wsBase||"/ws",Tt=vt+"//"+location.host+xt+"/"+encodeURIComponent(o),bt="/api/session/"+encodeURIComponent(o)+"/upload",Et=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Mt(){return J.join("")+I}async function Lt(t,e=0){const i=t?n.viewportY():0,c=N(Mt());if(n.reset(),!c){t||n.scrollToBottom();return}await n.write(c),t?n.scrollToLine(e+i):n.scrollToBottom()}function S(t){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(t))}function St(t){let e;try{e=JSON.parse(t)}catch{b==="live"&&(I+=t,n.write(N(t)));return}if(e.type==="auth.required"){const i=localStorage.getItem("tmux-web-token");i?S({type:"auth.token",token:i}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(e.type==="attach_failed"&&typeof e.message=="string"){$=!0,b="dead",n.reset(),n.write(`\r
|
|
4
4
|
\x1B[31m`+N(e.message)+`\x1B[0m\r
|
|
5
|
-
`).then(()=>{
|
|
5
|
+
`).then(()=>{G();try{l?.close()}catch{}});return}if(e.type==="auth.ok"){e.token&&localStorage.setItem("tmux-web-token",e.token),z=1e3,C(),S({type:"resize",cols:n.cols,rows:n.rows}),w(),j();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(N(e.data));J=[i],I="",V=typeof e.lines=="number"?e.lines:r.terminal.initialLines,b="live",n.reset(),C(),S({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?(J.unshift(st(e.data)),V+=e.lines,Lt(!0,e.lines).finally(()=>{B=!1})):B=!1;return}if(e.type==="data"&&typeof e.data=="string"){/\x1b\[\?1049h/.test(e.data)&&A();const i=st(N(e.data));if(b==="connecting"){b="live",I=i,n.write(i);return}b==="live"&&(I+=i,n.write(i))}}let $=!1;function lt(){if($)return;let t=Tt;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",V=0,B=!1,J=[],I="",n.reset(),z=1e3},l.onmessage=i=>{typeof i.data=="string"&&St(i.data)},l.onclose=()=>{M||(b="connecting",!$&&setTimeout(()=>{z=Math.min(z*2,1e4),!M&&!$&<()},z))},l.onerror=()=>l?.close()}function K(t){S({type:"input",data:t})}function C(){return n.fit()}function G(){s.classList.remove("terminal-pending")}function w(){f&&cancelAnimationFrame(f),d&&clearTimeout(d),C(),f=requestAnimationFrame(()=>{f=0,C()&&G()}),d=setTimeout(()=>{C()&&G()},120)}function Ct(){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 Dt(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 Pt(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 v=await fetch(bt,{method:"POST",body:e,headers:u});if(!v.ok){let D="upload failed";try{const P=await v.json();P.error&&(D=P.error)}catch{}throw new Error(D)}const x=await v.json();if(!x.path)throw new Error("no path in response");return x.path}async function ot(t){try{const e=await Pt(t);K(e)}catch{}}n.onData(t=>K(t)),n.onResize(({cols:t,rows:e})=>S({type:"resize",cols:t,rows:e})),n.onScroll(()=>{b!=="live"||B||!n.isNearScrollbackTop()||(B=!0,S({type:"load_history",before:V}))});let R=!1,X=!1,m=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(),v=u.height>0&&n.rows>0?u.height/n.rows:18,x=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)/v))),col:Math.max(0,Math.min(n.cols-1,Math.floor((t-u.left)/x)))}}async function Q(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 q;function Z(){E.hidden=!1,q&&clearTimeout(q),q=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 It(t){M||t.button!==0||X||(R||t.shiftKey)&&(t.preventDefault(),t.stopPropagation(),X=!0,m=ft(t.clientX,t.clientY),p=m,n.selectCells(m.col,m.row,p.col,p.row),document.addEventListener("mousemove",it,!0),document.addEventListener("mouseup",rt,!0))}function it(t){M||!X||!m||(t.preventDefault(),t.stopPropagation(),p=ft(t.clientX,t.clientY),n.selectCells(m.col,m.row,p.col,p.row))}function rt(t){if(M||!X||!m||!p)return;t.preventDefault(),t.stopPropagation(),document.removeEventListener("mousemove",it,!0),document.removeEventListener("mouseup",rt,!0),X=!1;const e=n.getCellsText(m.col,m.row,p.col,p.row);m=null,p=null,n.clearSelection(),k(!1),e&&(E.textContent="\u5DF2\u590D\u5236",Q(e).then(Z)),n.focus()}s.addEventListener("mousedown",It,!0),document.addEventListener("keydown",kt,!0);function kt(t){if(!(M||!n.isFocused()||t.defaultPrevented||t.isComposing)){if(R){if(t.key==="Escape"){t.preventDefault(),t.stopPropagation(),m=null,p=null,n.clearSelection(),k(!1);return}if(t.key==="Enter"||(t.ctrlKey||t.metaKey)&&t.code==="KeyC"){t.preventDefault(),t.stopPropagation(),Q(n.getScreenText()).then(Z),k(!1);return}}if(t.altKey&&t.code==="KeyC"&&!t.ctrlKey&&!t.metaKey){if(t.preventDefault(),t.stopPropagation(),m&&p){const e=n.getCellsText(m.col,m.row,p.col,p.row);m=null,p=null,n.clearSelection(),k(!1),e&&(E.textContent="Copied",Q(e).then(Z))}else{const e=n.getSelection();e?(n.clearSelection(),E.textContent="Copied",Q(e).then(Z)):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 tt=0;function mt(t){return t.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
8
|
+
`).join("\r")}async function At(t){const e=Dt(t.clipboardData);if(e){t.preventDefault(),t.stopPropagation();const u=Date.now();if(u-tt<50)return;tt=u,await ot(e);return}const i=t.clipboardData?.getData("text/plain");if(!i)return;t.preventDefault(),t.stopPropagation();const c=Date.now();c-tt<50||(tt=c,n.pasteText(mt(i)))}let _=0;function Yt(t){t.preventDefault(),_++,ut(t.dataTransfer)&&s.classList.add("terminal-drag-over")}function Ft(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function Bt(t){t.preventDefault(),_--,_<=0&&(_=0,s.classList.remove("terminal-drag-over"))}async function zt(t){t.preventDefault(),_=0,s.classList.remove("terminal-drag-over");const e=ut(t.dataTransfer);e&&await ot(e)}function ht(t){!Et||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",At),s.addEventListener("dragenter",Yt),s.addEventListener("dragover",Ft),s.addEventListener("dragleave",Bt),s.addEventListener("drop",zt),document.addEventListener("keydown",ht,!0);function Kt(t){if(t.touches.length!==1){a=null;return}const e=t.touches[0];if(!e)return;W();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 Rt(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(),v=e.clientY-a.lastY;if(a.zone==="tui"){a.tuiAccum+=v;const D=s.querySelector(".xterm-rows > div"),P=D?D.getBoundingClientRect().height:16,wt=Math.max(16,P);for(;Math.abs(a.tuiAccum)>=wt;)K(L(a.tuiAccum>0)),a.tuiAccum-=Math.sign(a.tuiAccum)*wt;a.lastX=e.clientX,a.lastY=e.clientY,a.lastT=u;return}const x=u-a.lastT;x>0&&(a.velocity=v/x),_t(-v),a.lastX=e.clientX,a.lastY=e.clientY,a.lastT=u}function Xt(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&&yt(c)}function Ot(t){a=null,W(),t.stopPropagation()}function qt(t){t.pointerType==="touch"&&Date.now()<nt&&(t.preventDefault(),t.stopPropagation())}function _t(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",Kt,{passive:!0,capture:!0}),s.addEventListener("touchmove",Rt,{passive:!1,capture:!0}),s.addEventListener("touchend",Xt,{passive:!1,capture:!0}),s.addEventListener("touchcancel",Ot,{passive:!0,capture:!0}),s.addEventListener("pointerup",qt,!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",Ct,!0);async function Nt(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(t=>setTimeout(t,1e3))])}catch{}}return requestAnimationFrame(()=>{C()&&G(),(async()=>(await Nt(),C()&&S({type:"resize",cols:n.cols,rows:n.rows}),lt(),w()))()}),{destroy(){if(M=!0,W(),k(!1),q&&clearTimeout(q),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{Wt 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};
|